Compare commits
287 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f1c1fd070 | ||
|
|
ddf483012b | ||
|
|
2b0e62dafe | ||
|
|
9979c220ce | ||
|
|
530b212f55 | ||
|
|
9fe64deea0 | ||
|
|
0ca0930066 | ||
|
|
8734ebe4c4 | ||
|
|
bc6e1e7a0d | ||
|
|
bb8aead078 | ||
|
|
9daf362df6 | ||
|
|
311678af80 | ||
|
|
a0f995b134 | ||
|
|
e9e9ea2d69 | ||
|
|
4bd34b35ae | ||
|
|
9d1b93fe50 | ||
|
|
9dee4e2d25 | ||
|
|
783269e256 | ||
|
|
f598bf0b42 | ||
|
|
039ed17683 | ||
|
|
62e0ced6a6 | ||
|
|
f377ac54fd | ||
|
|
9d98682089 | ||
|
|
efdf5f1767 | ||
|
|
8ca0b614d7 | ||
|
|
704dcc8d65 | ||
|
|
9f4d05b04c | ||
|
|
08cd7450ff | ||
|
|
4b684a4926 | ||
|
|
65a7f2e7d8 | ||
|
|
26a45885ff | ||
|
|
4cc46f1ffa | ||
|
|
fcb5292a4f | ||
|
|
1ca096e7ce | ||
|
|
2b2ac2e48f | ||
|
|
da14588235 | ||
|
|
60612635ef | ||
|
|
c2f58d3ab5 | ||
|
|
5937b24799 | ||
|
|
8e222d3501 | ||
|
|
c3463dec63 | ||
|
|
f2ef109940 | ||
|
|
91fb9917bc | ||
|
|
6ded5b74d0 | ||
|
|
91a6632a1b | ||
|
|
f1c2d4fe3d | ||
|
|
23d958e457 | ||
|
|
b84b1e71f7 | ||
|
|
e2f037537f | ||
|
|
a84c7e0b06 | ||
|
|
f7144a55e2 | ||
|
|
496faef0b3 | ||
|
|
50bf4dad36 | ||
|
|
0743e71ab6 | ||
|
|
c207a7c0d3 | ||
|
|
d83cf0e560 | ||
|
|
28032e5e0d | ||
|
|
7f77db8c7f | ||
|
|
f9d02740aa | ||
|
|
ec7a2dce2f | ||
|
|
6f672ecdc3 | ||
|
|
818f5cd004 | ||
|
|
147e8cc467 | ||
|
|
db322cc19a | ||
|
|
9bdcd08233 | ||
|
|
a954b459b4 | ||
|
|
f133210db3 | ||
|
|
ee8028cd53 | ||
|
|
f72cb979c0 | ||
|
|
49c4d35afa | ||
|
|
ee3e9af63a | ||
|
|
1980c1004e | ||
|
|
a3f19c073b | ||
|
|
388173f0ad | ||
|
|
7cffb0ef9d | ||
|
|
c8d2ac9583 | ||
|
|
6357529901 | ||
|
|
b5dcd84513 | ||
|
|
5b7f7ed70e | ||
|
|
c04f3a7048 | ||
|
|
32d93e2651 | ||
|
|
9cccea9a87 | ||
|
|
b0e1ae3948 | ||
|
|
70408f0e22 | ||
|
|
db2aa7000a | ||
|
|
02bc999bd5 | ||
|
|
5bdc0db9e2 | ||
|
|
ca1623f34a | ||
|
|
bbc516cf43 | ||
|
|
8128f851d1 | ||
|
|
742de6282f | ||
|
|
9d0ae61ee0 | ||
|
|
901a199035 | ||
|
|
775d5b6dcc | ||
|
|
c5c77ddb67 | ||
|
|
2f698de3b6 | ||
|
|
82aa64ca0a | ||
|
|
3bb9e8c7eb | ||
|
|
555fe70df3 | ||
|
|
05c95a0307 | ||
|
|
7bf2b607f9 | ||
|
|
23bab2aeb9 | ||
|
|
f5fdcc3af0 | ||
|
|
5c079b0bbd | ||
|
|
6bbcec0c23 | ||
|
|
0beec6e335 | ||
|
|
a88423a33f | ||
|
|
1a2b2c15c5 | ||
|
|
1210ba37c4 | ||
|
|
356e3c6907 | ||
|
|
3c5f8d08ad | ||
|
|
75de655642 | ||
|
|
3574a9c874 | ||
|
|
bcd0fe38f0 | ||
|
|
5171c0bd77 | ||
|
|
4fececb94f | ||
|
|
9c562931a2 | ||
|
|
38bef584e1 | ||
|
|
e62f727135 | ||
|
|
a0e7ef61fc | ||
|
|
fd8ae60dc1 | ||
|
|
093c2887c3 | ||
|
|
770678e32a | ||
|
|
07cc05f62e | ||
|
|
77719750ee | ||
|
|
fb2bbd87b7 | ||
|
|
6206b6f843 | ||
|
|
9e3fa2e4bc | ||
|
|
1f16d72667 | ||
|
|
6cf9157fb5 | ||
|
|
76ff9a562e | ||
|
|
ffeff09561 | ||
|
|
cc71bdce8f | ||
|
|
5d43df4f33 | ||
|
|
d29c3add79 | ||
|
|
4544b091a0 | ||
|
|
91244d5211 | ||
|
|
5cae7b3db1 | ||
|
|
702fe024c0 | ||
|
|
1ad0ff34df | ||
|
|
282c502b71 | ||
|
|
d6e989cad5 | ||
|
|
23388b5705 | ||
|
|
9ed56aa5d6 | ||
|
|
d9c5ab645c | ||
|
|
2cfb3c6755 | ||
|
|
e095c325b3 | ||
|
|
47f9978c46 | ||
|
|
2e1e13b843 | ||
|
|
7c2110c1f3 | ||
|
|
2a7ac6896c | ||
|
|
e13325ca22 | ||
|
|
e889a52f6f | ||
|
|
4d737b35da | ||
|
|
0fbd263562 | ||
|
|
f81f37505b | ||
|
|
58dd33d8a2 | ||
|
|
f24f27a13c | ||
|
|
87cc18aff8 | ||
|
|
b88219902f | ||
|
|
63090fb75f | ||
|
|
b5b520ab66 | ||
|
|
7a6052db81 | ||
|
|
a73520c9d8 | ||
|
|
55b63cd2e3 | ||
|
|
612ee18a93 | ||
|
|
ca5c9271a3 | ||
|
|
9feea4a724 | ||
|
|
3da99b6058 | ||
|
|
3b4dfa1381 | ||
|
|
1b1f9d9985 | ||
|
|
3f5eaa8f0a | ||
|
|
28a567986d | ||
|
|
68027fd23e | ||
|
|
2807c5ef19 | ||
|
|
0477bda929 | ||
|
|
d80b3fda7d | ||
|
|
a831c1703a | ||
|
|
e6cec49646 | ||
|
|
74d848311b | ||
|
|
c03df169b3 | ||
|
|
9b19cba161 | ||
|
|
9536a3c98e | ||
|
|
f0de8131ec | ||
|
|
aa18cb64bc | ||
|
|
7b36c91e0d | ||
|
|
fb0ceb110e | ||
|
|
20fbf0a655 | ||
|
|
46de86f1e4 | ||
|
|
05eabfd539 | ||
|
|
74942cd06e | ||
|
|
24da40ef05 | ||
|
|
e7fbcce9d9 | ||
|
|
ef5055f4f3 | ||
|
|
7cbc5ef902 | ||
|
|
03d96a32f2 | ||
|
|
5406c17158 | ||
|
|
29c6228616 | ||
|
|
3f4a44c828 | ||
|
|
327c9caed7 | ||
|
|
07560188cf | ||
|
|
2c64676415 | ||
|
|
29ddd88a5d | ||
|
|
543dc80b0b | ||
|
|
2008b1beea | ||
|
|
9aa597d6f0 | ||
|
|
1d85416434 | ||
|
|
bb64285c3a | ||
|
|
6a581830f4 | ||
|
|
f1d155c3f6 | ||
|
|
66e3b529b7 | ||
|
|
04cfd598e2 | ||
|
|
b94c8436eb | ||
|
|
dbfc555d9c | ||
|
|
5aacf867cd | ||
|
|
2f9db1d069 | ||
|
|
3c022b893a | ||
|
|
b74adc5311 | ||
|
|
ed8b237364 | ||
|
|
7170808d2b | ||
|
|
578a80dfb8 | ||
|
|
d72a5e0d3d | ||
|
|
2699af5d5a | ||
|
|
8298caddc3 | ||
|
|
1bcde1b55c | ||
|
|
5d492bfe93 | ||
|
|
655b3d7c50 | ||
|
|
e4278745d5 | ||
|
|
e266dc153b | ||
|
|
a482b744d2 | ||
|
|
d5bf742912 | ||
|
|
4346aecb61 | ||
|
|
bdfa080701 | ||
|
|
465a42a825 | ||
|
|
7d519990b2 | ||
|
|
19fd3a15e1 | ||
|
|
12250676f9 | ||
|
|
a1bb9cbb2b | ||
|
|
836df873f0 | ||
|
|
58cc8fc08b | ||
|
|
d1d5312ead | ||
|
|
f3595e379c | ||
|
|
5f8c2f90ff | ||
|
|
abc7b8364c | ||
|
|
627a105ba9 | ||
|
|
faef5d8af6 | ||
|
|
cf1dc3df78 | ||
|
|
a26e4eee18 | ||
|
|
efa4deef6a | ||
|
|
7e00930319 | ||
|
|
c08b2ae3df | ||
|
|
8ebbeb5274 | ||
|
|
c3b01d8573 | ||
|
|
43d8dedec4 | ||
|
|
cd97b649ff | ||
|
|
ac650b08fd | ||
|
|
e6c005dcba | ||
|
|
5acb7448b7 | ||
|
|
80168a58a7 | ||
|
|
ebae0d71b8 | ||
|
|
8c11e1724a | ||
|
|
d0597407d8 | ||
|
|
c3aa2abe11 | ||
|
|
3e0b2f5553 | ||
|
|
3d1275e19a | ||
|
|
d72d1aabe6 | ||
|
|
aac6d0df27 | ||
|
|
f700220ec1 | ||
|
|
04d9b93e17 | ||
|
|
989fad8554 | ||
|
|
2981a16e17 | ||
|
|
afff7634f9 | ||
|
|
95b2886bad | ||
|
|
d57905c2b3 | ||
|
|
ffe037f9f1 | ||
|
|
3a748e20c2 | ||
|
|
b0c2ed839d | ||
|
|
eee252bb74 | ||
|
|
59a8acc667 | ||
|
|
960f50824d | ||
|
|
63020ec302 | ||
|
|
71d4563b9d | ||
|
|
98bfd7370d | ||
|
|
45a81a5016 | ||
|
|
389fb0c4e9 | ||
|
|
fc72fe3a78 | ||
|
|
5f646dd125 |
@@ -1,86 +0,0 @@
|
||||
@echo off
|
||||
|
||||
IF "%BUILD_DIR%"=="" SET BUILD_DIR=C:\TBuild
|
||||
SET LIB_DIR=%BUILD_DIR%\Libraries
|
||||
SET SRC_DIR=%BUILD_DIR%\tdesktop
|
||||
SET QT_VERSION=5_6_2
|
||||
|
||||
call:configureBuild
|
||||
call:getDependencies
|
||||
call:setupGYP
|
||||
cd %SRC_DIR%
|
||||
|
||||
echo Finished!
|
||||
|
||||
GOTO:EOF
|
||||
|
||||
:: FUNCTIONS
|
||||
:logInfo
|
||||
echo [INFO] %~1
|
||||
GOTO:EOF
|
||||
|
||||
:logError
|
||||
echo [ERROR] %~1
|
||||
GOTO:EOF
|
||||
|
||||
:getDependencies
|
||||
call:logInfo "Clone dependencies repository"
|
||||
git clone -q --depth 1 --branch master https://github.com/telegramdesktop/dependencies_windows.git %LIB_DIR%
|
||||
cd %LIB_DIR%
|
||||
|
||||
git clone --depth 1 --branch 0.9.1 https://github.com/ericniebler/range-v3
|
||||
|
||||
if exist prepare.bat (
|
||||
call prepare.bat
|
||||
) else (
|
||||
call:logError "Error cloning dependencies, trying again"
|
||||
rmdir %LIB_DIR% /S /Q
|
||||
call:getDependencies
|
||||
)
|
||||
GOTO:EOF
|
||||
|
||||
:setupGYP
|
||||
call:logInfo "Setup GYP/Ninja and generate VS solution"
|
||||
cd %LIB_DIR%
|
||||
git clone https://github.com/telegramdesktop/gyp.git
|
||||
cd gyp
|
||||
git checkout tdesktop
|
||||
SET PATH=%PATH%;%BUILD_DIR%\Libraries\gyp;%BUILD_DIR%\Libraries\ninja;
|
||||
cd %SRC_DIR%
|
||||
git submodule init
|
||||
git submodule update
|
||||
cd %SRC_DIR%\Telegram
|
||||
call gyp\refresh.bat --api-id 17349 --api-hash 344583e45741c457fe1862106095a5eb --ci-build
|
||||
GOTO:EOF
|
||||
|
||||
:configureBuild
|
||||
call:logInfo "Configuring build"
|
||||
call:logInfo "Build version: %BUILD_VERSION%"
|
||||
set TDESKTOP_BUILD_DEFINES=
|
||||
|
||||
echo %BUILD_VERSION% | findstr /C:"disable_register_custom_scheme">nul && (
|
||||
set TDESKTOP_BUILD_DEFINES=%TDESKTOP_BUILD_DEFINES%,TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME
|
||||
)
|
||||
|
||||
echo %BUILD_VERSION% | findstr /C:"disable_crash_reports">nul && (
|
||||
set TDESKTOP_BUILD_DEFINES=%TDESKTOP_BUILD_DEFINES%,DESKTOP_APP_DISABLE_CRASH_REPORTS
|
||||
)
|
||||
|
||||
echo %BUILD_VERSION% | findstr /C:"disable_network_proxy">nul && (
|
||||
set TDESKTOP_BUILD_DEFINES=%TDESKTOP_BUILD_DEFINES%,TDESKTOP_DISABLE_NETWORK_PROXY
|
||||
)
|
||||
|
||||
echo %BUILD_VERSION% | findstr /C:"disable_desktop_file_generation">nul && (
|
||||
set TDESKTOP_BUILD_DEFINES=%TDESKTOP_BUILD_DEFINES%,TDESKTOP_DISABLE_DESKTOP_FILE_GENERATION
|
||||
)
|
||||
|
||||
echo %BUILD_VERSION% | findstr /C:"disable_gtk_integration">nul && (
|
||||
set TDESKTOP_BUILD_DEFINES=%TDESKTOP_BUILD_DEFINES%,TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
)
|
||||
|
||||
if not "%TDESKTOP_BUILD_DEFINES%" == "" (
|
||||
set "TDESKTOP_BUILD_DEFINES=%TDESKTOP_BUILD_DEFINES:~1%"
|
||||
)
|
||||
|
||||
call:logInfo "Build Defines: %TDESKTOP_BUILD_DEFINES%"
|
||||
GOTO:EOF
|
||||
103
.github/workflows/issue_closer.yml
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
name: Issue closer.
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: opened
|
||||
|
||||
jobs:
|
||||
comment:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get the latest version.
|
||||
run: |
|
||||
tag=$(git ls-remote --tags git://github.com/$GITHUB_REPOSITORY | cut -f 2 | tail -n1)
|
||||
echo $tag
|
||||
echo ::set-env name=LATEST_TAG::$tag
|
||||
|
||||
- name: Check a version from an issue.
|
||||
uses: actions/github-script@0.4.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
let errorStr = "Version not found.";
|
||||
|
||||
let item1 = "Version of Telegram Desktop";
|
||||
let item2 = "Used theme";
|
||||
let body = context.payload.issue.body;
|
||||
|
||||
console.log("Body of issue:\n" + body);
|
||||
let index1 = body.indexOf(item1);
|
||||
let index2 = body.indexOf(item2);
|
||||
index2 = (index2 == -1) ? Number.MAX_SAFE_INTEGER : index2;
|
||||
|
||||
console.log("Index 1: " + index1);
|
||||
console.log("Index 2: " + index2);
|
||||
|
||||
if (index1 == -1) {
|
||||
console.log(errorStr);
|
||||
return;
|
||||
}
|
||||
|
||||
function parseVersion(str) {
|
||||
let pattern = /[0-9]\.[0-9][0-9.]{0,}/g;
|
||||
return str.match(pattern);
|
||||
}
|
||||
function firstNum(version) {
|
||||
return version[0].split(".")[0];
|
||||
}
|
||||
|
||||
let issueVer = parseVersion(body.substring(index1 + item1.length, index2));
|
||||
|
||||
if (issueVer == undefined) {
|
||||
console.log(errorStr);
|
||||
return;
|
||||
}
|
||||
console.log("Version from issue: " + issueVer[0]);
|
||||
|
||||
let latestVer = parseVersion(process.env.LATEST_TAG);
|
||||
|
||||
if (latestVer == undefined) {
|
||||
console.log(errorStr);
|
||||
return;
|
||||
}
|
||||
console.log("Version from tags: " + latestVer[0]);
|
||||
|
||||
let issueNum = firstNum(issueVer);
|
||||
let latestNum = firstNum(latestVer);
|
||||
|
||||
if (issueNum <= latestNum && issueNum < 5) {
|
||||
console.log("Seems the version of this issue is fine!");
|
||||
return;
|
||||
}
|
||||
|
||||
let message = `
|
||||
Sorry, but according to the version you specify in this issue, \
|
||||
you are using the [Telegram for macOS](https://macos.telegram.org), \
|
||||
not the [Telegram Desktop](https://desktop.telegram.org).
|
||||
You can report your issue to [the group](https://t.me/macswift) \
|
||||
or to [the repository of Telegram for macOS](https://github.com/overtake/TelegramSwift).
|
||||
|
||||
If I made a mistake and closed your issue wrongly, please reopen it. Thanks!
|
||||
`;
|
||||
|
||||
let params = {
|
||||
owner: context.issue.owner,
|
||||
repo: context.issue.repo,
|
||||
issue_number: context.issue.number
|
||||
};
|
||||
|
||||
github.issues.createComment({
|
||||
...params,
|
||||
body: message
|
||||
});
|
||||
|
||||
github.issues.addLabels({
|
||||
...params,
|
||||
labels: ['TG macOS Swift']
|
||||
});
|
||||
|
||||
github.issues.update({
|
||||
...params,
|
||||
state: 'closed'
|
||||
});
|
||||
|
||||
532
.github/workflows/linux.yml
vendored
Normal file
@@ -0,0 +1,532 @@
|
||||
name: Linux.
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '*.md'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '*.md'
|
||||
|
||||
jobs:
|
||||
|
||||
linux:
|
||||
name: Ubuntu 14.04
|
||||
runs-on: ubuntu-latest
|
||||
container: ubuntu:trusty
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
defines:
|
||||
- ""
|
||||
|
||||
env:
|
||||
GIT: "https://github.com"
|
||||
QT: "5_12_5"
|
||||
QT_PREFIX: "/usr/local/desktop-app/Qt-5.12.5"
|
||||
OPENSSL_VER: "1_1_1"
|
||||
OPENSSL_PREFIX: "/usr/local/desktop-app/openssl-1.1.1"
|
||||
CMAKE_VER: "3.16.3"
|
||||
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 ::set-env name=REPO_NAME::${GITHUB_REPOSITORY##*/}
|
||||
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Disable man for further package installs.
|
||||
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: First set up.
|
||||
shell: bash
|
||||
run: |
|
||||
cd ..
|
||||
mv $REPO_NAME temp
|
||||
mkdir $REPO_NAME
|
||||
mv temp $REPO_NAME/$REPO_NAME
|
||||
cd $REPO_NAME
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install software-properties-common -y && \
|
||||
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 libenchant-dev libxcb1-dev libxcb-image0-dev libxcb-shm0-dev \
|
||||
libxcb-xfixes0-dev libxcb-keysyms1-dev libxcb-icccm4-dev libatspi2.0-dev \
|
||||
libxcb-render-util0-dev libxcb-util0-dev libxcb-xkb-dev libxrender-dev \
|
||||
libasound-dev libpulse-dev libxcb-sync0-dev libxcb-randr0-dev libegl1-mesa-dev \
|
||||
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
|
||||
|
||||
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 ::set-env name=CACHE_KEY::$md5cache
|
||||
|
||||
mkdir -p Libraries
|
||||
cd Libraries
|
||||
echo ::set-env name=LibrariesPath::`pwd`
|
||||
|
||||
- name: Range-v3.
|
||||
run: |
|
||||
echo "Find necessary branch from doc."
|
||||
cloneRange=$(grep -A 1 "range-v3" $REPO_NAME/$DOC_PATH | sed -n 1p)
|
||||
cd $LibrariesPath
|
||||
echo $cloneRange
|
||||
eval $cloneRange
|
||||
|
||||
- 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
|
||||
|
||||
cmake --version
|
||||
|
||||
- name: Opus cache.
|
||||
id: cache-opus
|
||||
uses: actions/cache@v1
|
||||
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/01org/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@v1
|
||||
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 --prefix=$LibrariesPath/ffmpeg-cache \
|
||||
--enable-protocol=file --enable-libopus \
|
||||
--disable-programs \
|
||||
--disable-doc \
|
||||
--disable-network \
|
||||
--disable-everything \
|
||||
--enable-hwaccel=h264_vaapi \
|
||||
--enable-hwaccel=h264_vdpau \
|
||||
--enable-hwaccel=mpeg4_vaapi \
|
||||
--enable-hwaccel=mpeg4_vdpau \
|
||||
--enable-decoder=aac \
|
||||
--enable-decoder=aac_at \
|
||||
--enable-decoder=aac_fixed \
|
||||
--enable-decoder=aac_latm \
|
||||
--enable-decoder=aasc \
|
||||
--enable-decoder=alac \
|
||||
--enable-decoder=alac_at \
|
||||
--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_alaw_at \
|
||||
--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_mulaw_at \
|
||||
--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 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/lib/$1.a ffmpeg/$1/$1.a
|
||||
}
|
||||
copyLib libavformat
|
||||
copyLib libavcodec
|
||||
copyLib libswresample
|
||||
copyLib libswscale
|
||||
copyLib libavutil
|
||||
|
||||
sudo cp -R ffmpeg-cache/. /usr/local/
|
||||
|
||||
- name: PortAudio.
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone https://git.assembla.com/portaudio.git
|
||||
cd portaudio
|
||||
git checkout 396fe4b669
|
||||
./configure
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
cd ..
|
||||
rm -rf portaudio
|
||||
|
||||
- name: OpenAL Soft.
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone -b openal-soft-1.19.1 --depth=1 $GIT/kcat/openal-soft.git
|
||||
cd openal-soft/build
|
||||
cmake -D LIBTYPE:STRING=STATIC ..
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
cd -
|
||||
rm -rf openal-soft
|
||||
|
||||
- name: OpenSSL cache.
|
||||
id: cache-openssl
|
||||
uses: actions/cache@v1
|
||||
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
|
||||
|
||||
git clone -b OpenSSL_${OPENSSL_VER}-stable --depth=1 \
|
||||
$GIT/openssl/openssl openssl_${OPENSSL_VER}
|
||||
cd openssl_${OPENSSL_VER}
|
||||
./config --prefix=$LibrariesPath/openssl-cache
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
cd ..
|
||||
rm -rf openssl_${OPENSSL_VER}
|
||||
- name: OpenSSL install.
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
sudo mkdir -p $OPENSSL_PREFIX
|
||||
sudo cp -R openssl-cache/. $OPENSSL_PREFIX/
|
||||
|
||||
- name: Libxkbcommon.
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone -b xkbcommon-0.8.4 --depth=1 $GIT/xkbcommon/libxkbcommon.git
|
||||
cd libxkbcommon
|
||||
./autogen.sh
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
cd ..
|
||||
rm -rf libxkbcommon
|
||||
|
||||
- name: Libwayland.
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone -b 1.16 https://gitlab.freedesktop.org/wayland/wayland
|
||||
cd wayland
|
||||
./autogen.sh --enable-static --disable-documentation
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
cd ..
|
||||
rm -rf wayland
|
||||
|
||||
- name: Qt 5.12.5 cache.
|
||||
id: cache-qt
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/qt-cache
|
||||
key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qtbase_5_12_5.diff') }}
|
||||
- name: Qt 5.12.5 build.
|
||||
if: steps.cache-qt.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone -b v5.12.5 --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
|
||||
git apply ../../patches/qtbase_${QT}.diff
|
||||
cd src/plugins/platforminputcontexts
|
||||
git clone $GIT/desktop-app/fcitx.git
|
||||
git clone $GIT/desktop-app/hime.git
|
||||
git clone $GIT/desktop-app/nimf.git
|
||||
cd ../../../..
|
||||
|
||||
./configure -prefix "$LibrariesPath/qt-cache" \
|
||||
-release \
|
||||
-force-debug-info \
|
||||
-opensource \
|
||||
-confirm-license \
|
||||
-qt-zlib \
|
||||
-qt-libpng \
|
||||
-qt-libjpeg \
|
||||
-qt-harfbuzz \
|
||||
-qt-pcre \
|
||||
-qt-xcb \
|
||||
-system-freetype \
|
||||
-fontconfig \
|
||||
-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
|
||||
cd ..
|
||||
rm -rf qt_${QT}
|
||||
- name: Qt 5.12.5 install.
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
sudo mkdir -p $QT_PREFIX
|
||||
sudo cp -R qt-cache/. $QT_PREFIX/
|
||||
|
||||
- name: Breakpad cache.
|
||||
id: cache-breakpad
|
||||
uses: actions/cache@v1
|
||||
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 --prefix=$BreakpadCache
|
||||
make -j$(nproc)
|
||||
sudo make 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/. /usr/local/
|
||||
mkdir -p breakpad/out/Default/
|
||||
cp breakpad-cache/dump_syms breakpad/out/Default/dump_syms
|
||||
|
||||
- name: Telegram Desktop build.
|
||||
if: env.ONLY_CACHE == 'false'
|
||||
run: |
|
||||
cd $REPO_NAME/Telegram
|
||||
|
||||
DEFINE=""
|
||||
if [ -n "${{ matrix.defines }}" ]; then
|
||||
DEFINE="-D ${{ matrix.defines }}=ON"
|
||||
echo Define from matrix: $DEFINE
|
||||
fi
|
||||
|
||||
./configure.sh -D TDESKTOP_API_TEST=ON -D DESKTOP_APP_USE_PACKAGED=OFF $DEFINE
|
||||
|
||||
cd ../out/Debug
|
||||
make -j$(nproc)
|
||||
strip -s bin/Telegram
|
||||
|
||||
- name: Check.
|
||||
if: env.ONLY_CACHE == 'false'
|
||||
run: |
|
||||
filePath="$REPO_NAME/out/Debug/bin/Telegram"
|
||||
if test -f "$filePath"; then
|
||||
echo "Build successfully done! :)"
|
||||
|
||||
size=$(stat -c %s "$filePath")
|
||||
echo "File size of ${filePath}: ${size} Bytes."
|
||||
else
|
||||
echo "Build error, output file does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Move artifact.
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
run: |
|
||||
cd $REPO_NAME/out/Debug/bin
|
||||
mkdir artifact
|
||||
mv Telegram artifact/
|
||||
- uses: actions/upload-artifact@master
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
name: Upload artifact.
|
||||
with:
|
||||
name: Telegram
|
||||
path: ${{ env.REPO_NAME }}/out/Debug/bin/artifact/
|
||||
52
.github/workflows/mac.yml
vendored
@@ -32,8 +32,11 @@ jobs:
|
||||
QT_PREFIX: "/usr/local/desktop-app/Qt-5.12.5"
|
||||
LIBICONV_VER: "libiconv-1.15"
|
||||
UPLOAD_ARTIFACT: "false"
|
||||
MANUAL_CACHING: "1"
|
||||
ONLY_CACHE: "false"
|
||||
MANUAL_CACHING: "2"
|
||||
DOC_PATH: "docs/building-xcode.md"
|
||||
AUTO_CACHING: "1"
|
||||
|
||||
steps:
|
||||
- name: Get repository name.
|
||||
run: echo ::set-env name=REPO_NAME::${GITHUB_REPOSITORY##*/}
|
||||
@@ -60,6 +63,10 @@ jobs:
|
||||
echo $MIN_MAC >> CACHE_KEY.txt
|
||||
echo $PREFIX >> CACHE_KEY.txt
|
||||
echo $MANUAL_CACHING >> CACHE_KEY.txt
|
||||
if [ "$AUTO_CACHING" == "1" ]; then
|
||||
thisFile=$REPO_NAME/.github/workflows/mac.yml
|
||||
echo `md5 -q $thisFile` >> CACHE_KEY.txt
|
||||
fi
|
||||
echo ::set-env name=CACHE_KEY::`md5 -q CACHE_KEY.txt`
|
||||
|
||||
echo ::add-path::$PWD/Libraries/depot_tools
|
||||
@@ -117,8 +124,8 @@ jobs:
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone $GIT/openssl/openssl openssl_$OPENSSL_VER
|
||||
cd openssl_$OPENSSL_VER
|
||||
git clone $GIT/openssl/openssl openssl
|
||||
cd openssl
|
||||
git checkout OpenSSL_"$OPENSSL_VER"-stable
|
||||
./Configure \
|
||||
--prefix=$PREFIX \
|
||||
@@ -127,6 +134,15 @@ jobs:
|
||||
$MIN_MAC
|
||||
make build_libs -j$(nproc)
|
||||
|
||||
SSL_DIR=$LibrariesPath/openssl_${{ env.OPENSSL_VER }}
|
||||
mkdir -p $SSL_DIR/include
|
||||
copyLib() {
|
||||
cp $1.a $SSL_DIR/$1.a
|
||||
}
|
||||
copyLib libssl
|
||||
copyLib libcrypto
|
||||
sudo cp -R include/. $SSL_DIR/include/
|
||||
|
||||
- name: Opus cache.
|
||||
id: cache-opus
|
||||
uses: actions/cache@v1
|
||||
@@ -174,7 +190,7 @@ jobs:
|
||||
id: cache-ffmpeg
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/ffmpeg
|
||||
path: ${{ env.LibrariesPath }}/ffmpeg-cache
|
||||
key: ${{ runner.OS }}-ffmpeg-${{ env.CACHE_KEY }}
|
||||
- name: FFmpeg.
|
||||
if: steps.cache-ffmpeg.outputs.cache-hit != 'true'
|
||||
@@ -188,7 +204,7 @@ jobs:
|
||||
LDFLAGS=`freetype-config --libs`
|
||||
PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig:/usr/X11/lib/pkgconfig
|
||||
|
||||
./configure --prefix=/usr/local \
|
||||
./configure --prefix=$LibrariesPath/ffmpeg-cache \
|
||||
--extra-cflags="$MIN_MAC $UNGUARDED" \
|
||||
--extra-cxxflags="$MIN_MAC $UNGUARDED" \
|
||||
--extra-ldflags="$MIN_MAC" \
|
||||
@@ -290,10 +306,23 @@ jobs:
|
||||
--enable-muxer=opus
|
||||
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
- name: FFmpeg install.
|
||||
run: |
|
||||
cd $LibrariesPath/ffmpeg
|
||||
sudo make install
|
||||
cd $LibrariesPath
|
||||
#List of files from cmake/external/ffmpeg/CMakeLists.txt.
|
||||
copyLib() {
|
||||
mkdir -p ffmpeg/$1
|
||||
\cp -fR ffmpeg-cache/lib/$1.a ffmpeg/$1/$1.a
|
||||
}
|
||||
copyLib libavformat
|
||||
copyLib libavcodec
|
||||
copyLib libswresample
|
||||
copyLib libswscale
|
||||
copyLib libavutil
|
||||
|
||||
sudo cp -R ffmpeg-cache/. /usr/local/
|
||||
sudo cp -R ffmpeg-cache/include/. ffmpeg/
|
||||
|
||||
- name: OpenAL Soft.
|
||||
run: |
|
||||
@@ -364,8 +393,8 @@ jobs:
|
||||
cd $LibrariesPath
|
||||
mv qt-cache Qt-5.12.5
|
||||
sudo mkdir -p $QT_PREFIX
|
||||
sudo mv -f Qt-5.12.5 /usr/local/desktop-app/
|
||||
- name: Build Qt 5.12.5.
|
||||
sudo mv -f Qt-5.12.5 "$(dirname "$QT_PREFIX")"/
|
||||
- name: Qt 5.12.5 build.
|
||||
if: steps.cache-qt.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
@@ -400,7 +429,8 @@ jobs:
|
||||
make clean
|
||||
cp -r $QT_PREFIX $LibrariesPath/qt-cache
|
||||
|
||||
- name: Build Telegram Desktop.
|
||||
- name: Telegram Desktop build.
|
||||
if: env.ONLY_CACHE == 'false'
|
||||
run: |
|
||||
cd $REPO_NAME/Telegram
|
||||
|
||||
@@ -429,4 +459,4 @@ jobs:
|
||||
name: Upload artifact.
|
||||
with:
|
||||
name: Telegram
|
||||
path: $REPO_NAME\out\Debug\artifact\
|
||||
path: ${{ env.REPO_NAME }}/out/Debug/artifact/
|
||||
|
||||
99
.github/workflows/snap.yml
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
name: Snap.
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '*.md'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '*.md'
|
||||
|
||||
jobs:
|
||||
|
||||
linux:
|
||||
name: Ubuntu 18.04
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
env:
|
||||
UPLOAD_ARTIFACT: "false"
|
||||
ONLY_CACHE: "false"
|
||||
MANUAL_CACHING: "3"
|
||||
|
||||
steps:
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: First set up.
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install gcc-8 g++-8 -y
|
||||
sudo snap install --classic snapcraft
|
||||
|
||||
# Workaround for snapcraft
|
||||
# See https://forum.snapcraft.io/t/permissions-problem-using-snapcraft-in-azure-pipelines/13258
|
||||
sudo chown root:root /
|
||||
|
||||
snapcraft --version > CACHE_KEY.txt
|
||||
gcc-8 --version >> CACHE_KEY.txt
|
||||
echo $MANUAL_CACHING >> CACHE_KEY.txt
|
||||
md5cache=$(md5sum CACHE_KEY.txt | cut -c -32)
|
||||
echo ::set-env name=CACHE_KEY::$md5cache
|
||||
|
||||
awk -v RS="" -v ORS="\n\n" '/^ cmake:/' snap/snapcraft.yaml > CMAKE_CACHE_KEY.txt
|
||||
md5cache=$(md5sum CMAKE_CACHE_KEY.txt | cut -c -32)
|
||||
echo ::set-env name=CMAKE_CACHE_KEY::$md5cache
|
||||
|
||||
awk -v RS="" -v ORS="\n\n" '/^ enchant:/' snap/snapcraft.yaml > ENCHANT_CACHE_KEY.txt
|
||||
md5cache=$(md5sum ENCHANT_CACHE_KEY.txt | cut -c -32)
|
||||
echo ::set-env name=ENCHANT_CACHE_KEY::$md5cache
|
||||
|
||||
- name: CMake cache.
|
||||
id: cache-cmake
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: parts/cmake
|
||||
key: ${{ runner.OS }}-cmake-${{ env.CACHE_KEY }}-${{ env.CMAKE_CACHE_KEY }}
|
||||
|
||||
- name: CMake build.
|
||||
if: steps.cache-cmake.outputs.cache-hit != 'true'
|
||||
run: snapcraft build --destructive-mode cmake
|
||||
|
||||
- name: Enchant cache.
|
||||
id: cache-enchant
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: parts/enchant
|
||||
key: ${{ runner.OS }}-enchant-${{ env.CACHE_KEY }}-${{ env.ENCHANT_CACHE_KEY }}
|
||||
|
||||
- name: Enchant build.
|
||||
if: steps.cache-enchant.outputs.cache-hit != 'true'
|
||||
run: snapcraft build --destructive-mode enchant
|
||||
|
||||
- name: Telegram Desktop snap build.
|
||||
if: env.ONLY_CACHE == 'false'
|
||||
run: snapcraft --destructive-mode
|
||||
|
||||
- name: Move artifact.
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
run: |
|
||||
artifact_name=$(echo telegram-desktop_*.snap)
|
||||
echo ::set-env name=ARTIFACT_NAME::$artifact_name
|
||||
|
||||
mkdir artifact
|
||||
mv $artifact_name artifact
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
name: Upload artifact.
|
||||
with:
|
||||
name: ${{ env.ARTIFACT_NAME }}
|
||||
path: artifact
|
||||
|
||||
- name: Remove unneeded directories for cache.
|
||||
run: |
|
||||
rm -rf parts/{cmake,enchant}/{build,src,ubuntu}
|
||||
rm -rf parts/{cmake,enchant}/state/{stage,prime}
|
||||
22
.github/workflows/win.yml
vendored
@@ -27,8 +27,11 @@ jobs:
|
||||
QT: "5_12_5"
|
||||
OPENSSL_VER: "1_1_1"
|
||||
UPLOAD_ARTIFACT: "false"
|
||||
MANUAL_CACHING: "1"
|
||||
ONLY_CACHE: "false"
|
||||
MANUAL_CACHING: "2"
|
||||
DOC_PATH: "docs/building-msvc.md"
|
||||
AUTO_CACHING: "1"
|
||||
|
||||
steps:
|
||||
- name: Get repository name.
|
||||
shell: bash
|
||||
@@ -68,6 +71,10 @@ jobs:
|
||||
- name: Generate cache key.
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "$AUTO_CACHING" == "1" ]; then
|
||||
thisFile=$REPO_NAME/.github/workflows/win.yml
|
||||
echo `md5sum $thisFile | awk '{ print $1 }'` >> CACHE_KEY.txt
|
||||
fi
|
||||
echo ::set-env name=CACHE_KEY::`md5sum CACHE_KEY.txt | awk '{ print $1 }'`
|
||||
|
||||
- name: Choco installs.
|
||||
@@ -134,6 +141,7 @@ jobs:
|
||||
move ossl_static.pdb out32
|
||||
|
||||
rmdir /S /Q test
|
||||
rmdir /S /Q .git
|
||||
|
||||
- name: Zlib.
|
||||
shell: cmd
|
||||
@@ -226,6 +234,7 @@ jobs:
|
||||
path: ${{ env.LibrariesPath }}/opus
|
||||
key: ${{ runner.OS }}-opus-${{ env.CACHE_KEY }}
|
||||
- name: Opus.
|
||||
if: steps.cache-opus.outputs.cache-hit != 'true'
|
||||
shell: cmd
|
||||
run: |
|
||||
%VC%
|
||||
@@ -242,7 +251,7 @@ jobs:
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/ffmpeg
|
||||
key: ${{ runner.OS }}-ffmpeg-${{ env.CACHE_KEY }}-${{ hashFiles('**/build_ffmpeg_win.sh') }}
|
||||
key: ${{ runner.OS }}-ffmpeg-${{ env.CACHE_KEY }}-2-${{ hashFiles('**/build_ffmpeg_win.sh') }}
|
||||
- name: FFmpeg.
|
||||
if: steps.cache-ffmpeg.outputs.cache-hit != 'true'
|
||||
shell: cmd
|
||||
@@ -257,6 +266,8 @@ jobs:
|
||||
set MSYS2_PATH_TYPE=inherit
|
||||
call c:\tools\msys64\usr\bin\bash --login ../../%REPO_NAME%/Telegram/Patches/build_ffmpeg_win.sh
|
||||
|
||||
rmdir /S /Q .git
|
||||
|
||||
- name: Qt 5.12.5 cache.
|
||||
id: cache-qt
|
||||
uses: actions/cache@v1
|
||||
@@ -298,7 +309,7 @@ jobs:
|
||||
-nomake examples ^
|
||||
-nomake tests ^
|
||||
-platform win32-msvc
|
||||
- name: Build Qt 5.12.5.
|
||||
- name: Qt 5.12.5 build.
|
||||
if: steps.cache-qt.outputs.cache-hit != 'true'
|
||||
shell: cmd
|
||||
run: |
|
||||
@@ -321,7 +332,8 @@ jobs:
|
||||
fi
|
||||
echo "::set-env name=TDESKTOP_BUILD_DEFINE::$DEFINE"
|
||||
|
||||
- name: Build Telegram Desktop.
|
||||
- name: Telegram Desktop build.
|
||||
if: env.ONLY_CACHE == 'false'
|
||||
shell: cmd
|
||||
run: |
|
||||
cd %REPO_NAME%\Telegram
|
||||
@@ -348,4 +360,4 @@ jobs:
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
with:
|
||||
name: Telegram
|
||||
path: ${{ env.REPO_NAME }}\out\Debug\artifact\
|
||||
path: ${{ env.REPO_NAME }}\out\Debug\artifact\
|
||||
|
||||
6
.gitmodules
vendored
@@ -61,3 +61,9 @@
|
||||
[submodule "Telegram/lib_qr"]
|
||||
path = Telegram/lib_qr
|
||||
url = https://github.com/desktop-app/lib_qr.git
|
||||
[submodule "Telegram/ThirdParty/libdbusmenu-qt"]
|
||||
path = Telegram/ThirdParty/libdbusmenu-qt
|
||||
url = https://github.com/desktop-app/libdbusmenu-qt.git
|
||||
[submodule "Telegram/ThirdParty/hunspell"]
|
||||
path = Telegram/ThirdParty/hunspell
|
||||
url = https://github.com/hunspell/hunspell
|
||||
|
||||
67
.travis.yml
@@ -1,67 +0,0 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
language: cpp
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/travisCacheDir
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- BUILD_VERSION=""
|
||||
- BUILD_VERSION="disable_register_custom_scheme"
|
||||
- BUILD_VERSION="disable_crash_reports"
|
||||
- BUILD_VERSION="disable_network_proxy"
|
||||
- BUILD_VERSION="disable_desktop_file_generation"
|
||||
- BUILD_VERSION="disable_gtk_integration"
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- bison
|
||||
- build-essential
|
||||
- cmake
|
||||
- devscripts
|
||||
- dpatch
|
||||
- equivs
|
||||
- fakeroot
|
||||
- g++-8
|
||||
- gcc-8
|
||||
- git
|
||||
- gnome-common
|
||||
- gobject-introspection
|
||||
- gtk-doc-tools
|
||||
- libappindicator-dev
|
||||
- libasound2-dev
|
||||
- libdbusmenu-glib-dev
|
||||
- liblzma-dev
|
||||
- libopus-dev
|
||||
- libpulse-dev
|
||||
- libenchant-dev
|
||||
- libssl-dev
|
||||
- libdee-dev
|
||||
- libva-dev
|
||||
- libvdpau-dev
|
||||
- libxcb-xkb-dev
|
||||
- libxkbcommon-dev
|
||||
- libatspi2.0-dev
|
||||
- lintian
|
||||
- quilt
|
||||
- valac
|
||||
- xutils-dev
|
||||
- yasm
|
||||
|
||||
before_install:
|
||||
- export CXX="g++-8" CC="gcc-8"
|
||||
- sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 60 --slave /usr/bin/g++ g++ /usr/bin/g++-8
|
||||
- sudo update-alternatives --config gcc
|
||||
- g++ --version
|
||||
|
||||
script:
|
||||
- .travis/build.sh
|
||||
725
.travis/build.sh
@@ -1,725 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
REPO="$PWD"
|
||||
|
||||
BUILD="$REPO/build"
|
||||
UPSTREAM="$REPO/upstream"
|
||||
EXTERNAL="$REPO/external"
|
||||
CACHE="$HOME/travisCacheDir"
|
||||
|
||||
QT_WAS_BUILT="0"
|
||||
|
||||
QT_VERSION=5.12.5
|
||||
|
||||
XKB_PATH="$BUILD/libxkbcommon"
|
||||
XKB_CACHE_VERSION="3"
|
||||
|
||||
QT_PATH="$BUILD/qt"
|
||||
QT_CACHE_VERSION="5"
|
||||
QT_PATCH="$EXTERNAL/patches/qtbase_${QT_VERSION//\./_}.diff"
|
||||
|
||||
BREAKPAD_PATH="$BUILD/breakpad"
|
||||
BREAKPAD_CACHE_VERSION="3"
|
||||
|
||||
GYP_PATH="$BUILD/gyp"
|
||||
GYP_CACHE_VERSION="3"
|
||||
GYP_PATCH="$EXTERNAL/patches/gyp.diff"
|
||||
|
||||
RANGE_PATH="$BUILD/range-v3"
|
||||
RANGE_CACHE_VERSION="3"
|
||||
|
||||
VA_PATH="$BUILD/libva"
|
||||
VA_CACHE_VERSION="3"
|
||||
|
||||
VDPAU_PATH="$BUILD/libvdpau"
|
||||
VDPAU_CACHE_VERSION="3"
|
||||
|
||||
FFMPEG_PATH="$BUILD/ffmpeg"
|
||||
FFMPEG_CACHE_VERSION="3"
|
||||
|
||||
OPENAL_PATH="$BUILD/openal-soft"
|
||||
OPENAL_CACHE_VERSION="4"
|
||||
|
||||
GYP_DEFINES=""
|
||||
|
||||
[[ ! $MAKE_ARGS ]] && MAKE_ARGS="--silent -j4"
|
||||
|
||||
run() {
|
||||
# Move files to subdir
|
||||
cd ..
|
||||
mv tdesktop tdesktop2
|
||||
mkdir tdesktop
|
||||
mv tdesktop2 "$UPSTREAM"
|
||||
|
||||
mkdir "$BUILD"
|
||||
|
||||
build
|
||||
check
|
||||
}
|
||||
|
||||
build() {
|
||||
mkdir -p "$EXTERNAL"
|
||||
|
||||
BUILD_VERSION_DATA=$(echo $BUILD_VERSION | cut -d'-' -f 1)
|
||||
|
||||
getPatches
|
||||
|
||||
# libxkbcommon
|
||||
getXkbCommon
|
||||
|
||||
# libva
|
||||
getVa
|
||||
|
||||
# libvdpau
|
||||
getVdpau
|
||||
|
||||
# ffmpeg
|
||||
getFFmpeg
|
||||
|
||||
# openal_soft
|
||||
getOpenAL
|
||||
|
||||
# Patched Qt
|
||||
getCustomQt
|
||||
|
||||
# Breakpad
|
||||
getBreakpad
|
||||
|
||||
# Patched GYP (supports cmake precompiled headers)
|
||||
getGYP
|
||||
|
||||
# Range v3
|
||||
getRange
|
||||
|
||||
# Guideline Support Library
|
||||
getGSL
|
||||
|
||||
if [ "$QT_WAS_BUILT" == "1" ]; then
|
||||
error_msg "Qt was built, please restart the job :("
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Configure the build
|
||||
if [[ $BUILD_VERSION == *"disable_register_custom_scheme"* ]]; then
|
||||
GYP_DEFINES+=",TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME"
|
||||
fi
|
||||
|
||||
if [[ $BUILD_VERSION == *"disable_crash_reports"* ]]; then
|
||||
GYP_DEFINES+=",DESKTOP_APP_DISABLE_CRASH_REPORTS"
|
||||
fi
|
||||
|
||||
if [[ $BUILD_VERSION == *"disable_network_proxy"* ]]; then
|
||||
GYP_DEFINES+=",TDESKTOP_DISABLE_NETWORK_PROXY"
|
||||
fi
|
||||
|
||||
if [[ $BUILD_VERSION == *"disable_desktop_file_generation"* ]]; then
|
||||
GYP_DEFINES+=",TDESKTOP_DISABLE_DESKTOP_FILE_GENERATION"
|
||||
fi
|
||||
|
||||
if [[ $BUILD_VERSION == *"disable_gtk_integration"* ]]; then
|
||||
GYP_DEFINES+=",TDESKTOP_DISABLE_GTK_INTEGRATION"
|
||||
fi
|
||||
|
||||
info_msg "Build defines: ${GYP_DEFINES}"
|
||||
|
||||
buildTelegram
|
||||
|
||||
travisEndFold
|
||||
}
|
||||
|
||||
getPatches() {
|
||||
cd "$EXTERNAL"
|
||||
git clone --depth 1 https://github.com/desktop-app/patches
|
||||
}
|
||||
|
||||
getXkbCommon() {
|
||||
travisStartFold "Getting xkbcommon"
|
||||
|
||||
local XKB_CACHE="$CACHE/libxkbcommon"
|
||||
local XKB_CACHE_FILE="$XKB_CACHE/.cache.txt"
|
||||
local XKB_CACHE_KEY="${XKB_CACHE_VERSION}"
|
||||
local XKB_CACHE_OUTDATED="1"
|
||||
|
||||
if [ ! -d "$XKB_CACHE" ]; then
|
||||
mkdir -p "$XKB_CACHE"
|
||||
fi
|
||||
|
||||
ln -sf "$XKB_CACHE" "$XKB_PATH"
|
||||
|
||||
if [ -f "$XKB_CACHE_FILE" ]; then
|
||||
local XKB_CACHE_KEY_FOUND=`tail -n 1 $XKB_CACHE_FILE`
|
||||
if [ "$XKB_CACHE_KEY" == "$XKB_CACHE_KEY_FOUND" ]; then
|
||||
XKB_CACHE_OUTDATED="0"
|
||||
else
|
||||
info_msg "Cache key '$XKB_CACHE_KEY_FOUND' does not match '$XKB_CACHE_KEY', rebuilding libxkbcommon"
|
||||
fi
|
||||
fi
|
||||
if [ "$XKB_CACHE_OUTDATED" == "1" ]; then
|
||||
buildXkbCommon
|
||||
sudo echo $XKB_CACHE_KEY > "$XKB_CACHE_FILE"
|
||||
else
|
||||
info_msg "Using cached libxkbcommon"
|
||||
fi
|
||||
}
|
||||
|
||||
buildXkbCommon() {
|
||||
info_msg "Downloading and building libxkbcommon"
|
||||
|
||||
if [ -d "$EXTERNAL/libxkbcommon" ]; then
|
||||
rm -rf "$EXTERNAL/libxkbcommon"
|
||||
fi
|
||||
cd $XKB_PATH
|
||||
rm -rf *
|
||||
|
||||
cd "$EXTERNAL"
|
||||
git clone https://github.com/xkbcommon/libxkbcommon.git
|
||||
|
||||
cd "$EXTERNAL/libxkbcommon"
|
||||
git checkout xkbcommon-0.8.4
|
||||
./autogen.sh --prefix=$XKB_PATH
|
||||
make $MAKE_ARGS
|
||||
sudo make install
|
||||
sudo ldconfig
|
||||
}
|
||||
|
||||
getRange() {
|
||||
travisStartFold "Getting range-v3"
|
||||
|
||||
local RANGE_CACHE="$CACHE/range-v3"
|
||||
local RANGE_CACHE_FILE="$RANGE_CACHE/.cache.txt"
|
||||
local RANGE_CACHE_KEY="${RANGE_CACHE_VERSION}"
|
||||
local RANGE_CACHE_OUTDATED="1"
|
||||
|
||||
if [ ! -d "$RANGE_CACHE" ]; then
|
||||
mkdir -p "$RANGE_CACHE"
|
||||
fi
|
||||
|
||||
ln -sf "$RANGE_CACHE" "$RANGE_PATH"
|
||||
|
||||
if [ -f "$RANGE_CACHE_FILE" ]; then
|
||||
local RANGE_CACHE_KEY_FOUND=`tail -n 1 $RANGE_CACHE_FILE`
|
||||
if [ "$RANGE_CACHE_KEY" == "$RANGE_CACHE_KEY_FOUND" ]; then
|
||||
RANGE_CACHE_OUTDATED="0"
|
||||
else
|
||||
info_msg "Cache key '$RANGE_CACHE_KEY_FOUND' does not match '$RANGE_CACHE_KEY', getting range-v3"
|
||||
fi
|
||||
fi
|
||||
if [ "$RANGE_CACHE_OUTDATED" == "1" ]; then
|
||||
buildRange
|
||||
sudo echo $RANGE_CACHE_KEY > "$RANGE_CACHE_FILE"
|
||||
else
|
||||
info_msg "Using cached range-v3"
|
||||
fi
|
||||
}
|
||||
|
||||
buildRange() {
|
||||
info_msg "Downloading range-v3"
|
||||
|
||||
if [ -d "$EXTERNAL/range-v3" ]; then
|
||||
rm -rf "$EXTERNAL/range-v3"
|
||||
fi
|
||||
cd $RANGE_PATH
|
||||
rm -rf *
|
||||
|
||||
cd "$EXTERNAL"
|
||||
git clone --depth 1 --branch 0.9.1 https://github.com/ericniebler/range-v3
|
||||
|
||||
cd "$EXTERNAL/range-v3"
|
||||
cp -r * "$RANGE_PATH/"
|
||||
}
|
||||
|
||||
getVa() {
|
||||
travisStartFold "Getting libva"
|
||||
|
||||
local VA_CACHE="$CACHE/libva"
|
||||
local VA_CACHE_FILE="$VA_CACHE/.cache.txt"
|
||||
local VA_CACHE_KEY="${VA_CACHE_VERSION}"
|
||||
local VA_CACHE_OUTDATED="1"
|
||||
|
||||
if [ ! -d "$VA_CACHE" ]; then
|
||||
mkdir -p "$VA_CACHE"
|
||||
fi
|
||||
|
||||
ln -sf "$VA_CACHE" "$VA_PATH"
|
||||
|
||||
if [ -f "$VA_CACHE_FILE" ]; then
|
||||
local VA_CACHE_KEY_FOUND=`tail -n 1 $VA_CACHE_FILE`
|
||||
if [ "$VA_CACHE_KEY" == "$VA_CACHE_KEY_FOUND" ]; then
|
||||
VA_CACHE_OUTDATED="0"
|
||||
else
|
||||
info_msg "Cache key '$VA_CACHE_KEY_FOUND' does not match '$VA_CACHE_KEY', rebuilding libva"
|
||||
fi
|
||||
fi
|
||||
if [ "$VA_CACHE_OUTDATED" == "1" ]; then
|
||||
buildVa
|
||||
sudo echo $VA_CACHE_KEY > "$VA_CACHE_FILE"
|
||||
else
|
||||
info_msg "Using cached libva"
|
||||
fi
|
||||
}
|
||||
|
||||
buildVa() {
|
||||
info_msg "Downloading and building libva"
|
||||
|
||||
if [ -d "$EXTERNAL/libva" ]; then
|
||||
rm -rf "$EXTERNAL/libva"
|
||||
fi
|
||||
cd $VA_PATH
|
||||
rm -rf *
|
||||
|
||||
cd "$EXTERNAL"
|
||||
git clone https://github.com/01org/libva
|
||||
|
||||
cd "$EXTERNAL/libva"
|
||||
./autogen.sh --prefix=$VA_PATH --enable-static
|
||||
make $MAKE_ARGS
|
||||
sudo make install
|
||||
sudo ldconfig
|
||||
}
|
||||
|
||||
getVdpau() {
|
||||
travisStartFold "Getting libvdpau"
|
||||
|
||||
local VDPAU_CACHE="$CACHE/libvdpau"
|
||||
local VDPAU_CACHE_FILE="$VDPAU_CACHE/.cache.txt"
|
||||
local VDPAU_CACHE_KEY="${VDPAU_CACHE_VERSION}"
|
||||
local VDPAU_CACHE_OUTDATED="1"
|
||||
|
||||
if [ ! -d "$VDPAU_CACHE" ]; then
|
||||
mkdir -p "$VDPAU_CACHE"
|
||||
fi
|
||||
|
||||
ln -sf "$VDPAU_CACHE" "$VDPAU_PATH"
|
||||
|
||||
if [ -f "$VDPAU_CACHE_FILE" ]; then
|
||||
local VDPAU_CACHE_KEY_FOUND=`tail -n 1 $VDPAU_CACHE_FILE`
|
||||
if [ "$VDPAU_CACHE_KEY" == "$VDPAU_CACHE_KEY_FOUND" ]; then
|
||||
VDPAU_CACHE_OUTDATED="0"
|
||||
else
|
||||
info_msg "Cache key '$VDPAU_CACHE_KEY_FOUND' does not match '$VDPAU_CACHE_KEY', rebuilding libvdpau"
|
||||
fi
|
||||
fi
|
||||
if [ "$VDPAU_CACHE_OUTDATED" == "1" ]; then
|
||||
buildVdpau
|
||||
sudo echo $VDPAU_CACHE_KEY > "$VDPAU_CACHE_FILE"
|
||||
else
|
||||
info_msg "Using cached libvdpau"
|
||||
fi
|
||||
}
|
||||
|
||||
buildVdpau() {
|
||||
info_msg "Downloading and building libvdpau"
|
||||
|
||||
if [ -d "$EXTERNAL/libvdpau" ]; then
|
||||
rm -rf "$EXTERNAL/libvdpau"
|
||||
fi
|
||||
cd $VDPAU_PATH
|
||||
rm -rf *
|
||||
|
||||
cd "$EXTERNAL"
|
||||
git clone git://anongit.freedesktop.org/vdpau/libvdpau
|
||||
|
||||
cd "$EXTERNAL/libvdpau"
|
||||
git checkout libvdpau-1.2
|
||||
./autogen.sh --prefix=$VDPAU_PATH --enable-static
|
||||
make $MAKE_ARGS
|
||||
sudo make install
|
||||
sudo ldconfig
|
||||
}
|
||||
|
||||
getFFmpeg() {
|
||||
travisStartFold "Getting ffmpeg"
|
||||
|
||||
local FFMPEG_CACHE="$CACHE/ffmpeg"
|
||||
local FFMPEG_CACHE_FILE="$FFMPEG_CACHE/.cache.txt"
|
||||
local FFMPEG_CACHE_KEY="${FFMPEG_CACHE_VERSION}"
|
||||
local FFMPEG_CACHE_OUTDATED="1"
|
||||
|
||||
if [ ! -d "$FFMPEG_CACHE" ]; then
|
||||
mkdir -p "$FFMPEG_CACHE"
|
||||
fi
|
||||
|
||||
ln -sf "$FFMPEG_CACHE" "$FFMPEG_PATH"
|
||||
|
||||
if [ -f "$FFMPEG_CACHE_FILE" ]; then
|
||||
local FFMPEG_CACHE_KEY_FOUND=`tail -n 1 $FFMPEG_CACHE_FILE`
|
||||
if [ "$FFMPEG_CACHE_KEY" == "$FFMPEG_CACHE_KEY_FOUND" ]; then
|
||||
FFMPEG_CACHE_OUTDATED="0"
|
||||
else
|
||||
info_msg "Cache key '$FFMPEG_CACHE_KEY_FOUND' does not match '$FFMPEG_CACHE_KEY', rebuilding ffmpeg"
|
||||
fi
|
||||
fi
|
||||
if [ "$FFMPEG_CACHE_OUTDATED" == "1" ]; then
|
||||
buildFFmpeg
|
||||
sudo echo $FFMPEG_CACHE_KEY > "$FFMPEG_CACHE_FILE"
|
||||
else
|
||||
info_msg "Using cached ffmpeg"
|
||||
fi
|
||||
}
|
||||
|
||||
buildFFmpeg() {
|
||||
info_msg "Downloading and building ffmpeg"
|
||||
|
||||
if [ -d "$EXTERNAL/ffmpeg" ]; then
|
||||
rm -rf "$EXTERNAL/ffmpeg"
|
||||
fi
|
||||
cd $FFMPEG_PATH
|
||||
rm -rf *
|
||||
|
||||
cd "$EXTERNAL"
|
||||
git clone https://git.ffmpeg.org/ffmpeg.git
|
||||
|
||||
cd "$EXTERNAL/ffmpeg"
|
||||
git checkout release/3.4
|
||||
|
||||
./configure \
|
||||
--prefix=$FFMPEG_PATH \
|
||||
--disable-debug \
|
||||
--disable-programs \
|
||||
--disable-doc \
|
||||
--disable-everything \
|
||||
--enable-gpl \
|
||||
--enable-version3 \
|
||||
--enable-libopus \
|
||||
--enable-decoder=aac \
|
||||
--enable-decoder=aac_latm \
|
||||
--enable-decoder=aasc \
|
||||
--enable-decoder=flac \
|
||||
--enable-decoder=gif \
|
||||
--enable-decoder=h264 \
|
||||
--enable-decoder=h264_vdpau \
|
||||
--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=vorbis \
|
||||
--enable-decoder=wavpack \
|
||||
--enable-decoder=wmalossless \
|
||||
--enable-decoder=wmapro \
|
||||
--enable-decoder=wmav1 \
|
||||
--enable-decoder=wmav2 \
|
||||
--enable-decoder=wmavoice \
|
||||
--enable-encoder=libopus \
|
||||
--enable-hwaccel=h264_vaapi \
|
||||
--enable-hwaccel=h264_vdpau \
|
||||
--enable-hwaccel=mpeg4_vaapi \
|
||||
--enable-hwaccel=mpeg4_vdpau \
|
||||
--enable-parser=aac \
|
||||
--enable-parser=aac_latm \
|
||||
--enable-parser=flac \
|
||||
--enable-parser=h264 \
|
||||
--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=mov \
|
||||
--enable-demuxer=mp3 \
|
||||
--enable-demuxer=ogg \
|
||||
--enable-demuxer=wav \
|
||||
--enable-muxer=ogg \
|
||||
--enable-muxer=opus
|
||||
make $MAKE_ARGS
|
||||
sudo make install
|
||||
sudo ldconfig
|
||||
}
|
||||
|
||||
getOpenAL() {
|
||||
travisStartFold "Getting openal-soft"
|
||||
|
||||
local OPENAL_CACHE="$CACHE/openal-soft"
|
||||
local OPENAL_CACHE_FILE="$OPENAL_CACHE/.cache.txt"
|
||||
local OPENAL_CACHE_KEY="${OPENAL_CACHE_VERSION}"
|
||||
local OPENAL_CACHE_OUTDATED="1"
|
||||
|
||||
if [ ! -d "$OPENAL_CACHE" ]; then
|
||||
mkdir -p "$OPENAL_CACHE"
|
||||
fi
|
||||
|
||||
ln -sf "$OPENAL_CACHE" "$OPENAL_PATH"
|
||||
|
||||
if [ -f "$OPENAL_CACHE_FILE" ]; then
|
||||
local OPENAL_CACHE_KEY_FOUND=`tail -n 1 $OPENAL_CACHE_FILE`
|
||||
if [ "$OPENAL_CACHE_KEY" == "$OPENAL_CACHE_KEY_FOUND" ]; then
|
||||
OPENAL_CACHE_OUTDATED="0"
|
||||
else
|
||||
info_msg "Cache key '$OPENAL_CACHE_KEY_FOUND' does not match '$OPENAL_CACHE_KEY', rebuilding openal-soft"
|
||||
fi
|
||||
fi
|
||||
if [ "$OPENAL_CACHE_OUTDATED" == "1" ]; then
|
||||
buildOpenAL
|
||||
sudo echo $OPENAL_CACHE_KEY > "$OPENAL_CACHE_FILE"
|
||||
else
|
||||
info_msg "Using cached openal-soft"
|
||||
fi
|
||||
}
|
||||
|
||||
buildOpenAL() {
|
||||
info_msg "Downloading and building openal-soft"
|
||||
|
||||
if [ -d "$EXTERNAL/openal-soft" ]; then
|
||||
rm -rf "$EXTERNAL/openal-soft"
|
||||
fi
|
||||
cd $OPENAL_PATH
|
||||
sudo rm -rf *
|
||||
|
||||
cd "$EXTERNAL"
|
||||
git clone https://github.com/kcat/openal-soft.git
|
||||
cd openal-soft
|
||||
git checkout openal-soft-1.19.1
|
||||
|
||||
cd "$EXTERNAL/openal-soft/build"
|
||||
cmake \
|
||||
-D CMAKE_INSTALL_PREFIX=$OPENAL_PATH \
|
||||
-D CMAKE_BUILD_TYPE=Release \
|
||||
-D LIBTYPE=STATIC \
|
||||
-D ALSOFT_EXAMPLES=OFF \
|
||||
-D ALSOFT_TESTS=OFF \
|
||||
-D ALSOFT_UTILS=OFF \
|
||||
..
|
||||
make $MAKE_ARGS
|
||||
sudo make install
|
||||
sudo ldconfig
|
||||
}
|
||||
|
||||
getBreakpad() {
|
||||
travisStartFold "Getting breakpad"
|
||||
|
||||
local BREAKPAD_CACHE="$CACHE/breakpad"
|
||||
local BREAKPAD_CACHE_FILE="$BREAKPAD_CACHE/.cache.txt"
|
||||
local BREAKPAD_CACHE_KEY="${BREAKPAD_CACHE_VERSION}"
|
||||
local BREAKPAD_CACHE_OUTDATED="1"
|
||||
|
||||
if [ ! -d "$BREAKPAD_CACHE" ]; then
|
||||
mkdir -p "$BREAKPAD_CACHE"
|
||||
fi
|
||||
|
||||
ln -sf "$BREAKPAD_CACHE" "$BREAKPAD_PATH"
|
||||
|
||||
if [ -f "$BREAKPAD_CACHE_FILE" ]; then
|
||||
local BREAKPAD_CACHE_KEY_FOUND=`tail -n 1 $BREAKPAD_CACHE_FILE`
|
||||
if [ "$BREAKPAD_CACHE_KEY" == "$BREAKPAD_CACHE_KEY_FOUND" ]; then
|
||||
BREAKPAD_CACHE_OUTDATED="0"
|
||||
else
|
||||
info_msg "Cache key '$BREAKPAD_CACHE_KEY_FOUND' does not match '$BREAKPAD_CACHE_KEY', rebuilding breakpad"
|
||||
fi
|
||||
fi
|
||||
if [ "$BREAKPAD_CACHE_OUTDATED" == "1" ]; then
|
||||
buildBreakpad
|
||||
sudo echo $BREAKPAD_CACHE_KEY > "$BREAKPAD_CACHE_FILE"
|
||||
else
|
||||
info_msg "Using cached breakpad"
|
||||
fi
|
||||
}
|
||||
|
||||
buildBreakpad() {
|
||||
info_msg "Downloading and building breakpad"
|
||||
|
||||
if [ -d "$EXTERNAL/breakpad" ]; then
|
||||
rm -rf "$EXTERNAL/breakpad"
|
||||
fi
|
||||
cd $BREAKPAD_PATH
|
||||
rm -rf *
|
||||
|
||||
cd "$EXTERNAL"
|
||||
git clone https://chromium.googlesource.com/breakpad/breakpad
|
||||
|
||||
cd "$EXTERNAL/breakpad/src/third_party"
|
||||
git clone https://chromium.googlesource.com/linux-syscall-support lss
|
||||
|
||||
cd "$EXTERNAL/breakpad"
|
||||
./configure --prefix=$BREAKPAD_PATH
|
||||
make $MAKE_ARGS
|
||||
sudo make install
|
||||
sudo ldconfig
|
||||
}
|
||||
|
||||
getCustomQt() {
|
||||
travisStartFold "Getting patched Qt"
|
||||
|
||||
local QT_CACHE="$CACHE/qtPatched"
|
||||
local QT_CACHE_FILE="$QT_CACHE/.cache.txt"
|
||||
local QT_PATCH_CHECKSUM=`sha1sum $QT_PATCH`
|
||||
local QT_CACHE_KEY="${QT_VERSION}_${QT_CACHE_VERSION}_${QT_PATCH_CHECKSUM:0:32}"
|
||||
local QT_CACHE_OUTDATED="1"
|
||||
|
||||
if [ ! -d "$QT_CACHE" ]; then
|
||||
mkdir -p "$QT_CACHE"
|
||||
fi
|
||||
|
||||
ln -sf "$QT_CACHE" "$QT_PATH"
|
||||
|
||||
if [ -f "$QT_CACHE_FILE" ]; then
|
||||
local QT_CACHE_KEY_FOUND=`tail -n 1 $QT_CACHE_FILE`
|
||||
if [ "$QT_CACHE_KEY" == "$QT_CACHE_KEY_FOUND" ]; then
|
||||
QT_CACHE_OUTDATED="0"
|
||||
else
|
||||
info_msg "Cache key '$QT_CACHE_KEY_FOUND' does not match '$QT_CACHE_KEY', rebuilding patched Qt"
|
||||
fi
|
||||
fi
|
||||
if [ "$QT_CACHE_OUTDATED" == "1" ]; then
|
||||
buildCustomQt
|
||||
sudo echo $QT_CACHE_KEY > "$QT_CACHE_FILE"
|
||||
else
|
||||
info_msg "Using cached patched Qt"
|
||||
fi
|
||||
|
||||
export PATH="$QT_PATH/bin:$PATH"
|
||||
}
|
||||
|
||||
buildCustomQt() {
|
||||
QT_WAS_BUILT="1"
|
||||
info_msg "Downloading and building patched qt"
|
||||
|
||||
if [ -d "$EXTERNAL/qt_${QT_VERSION}" ]; then
|
||||
sudo rm -rf "$EXTERNAL/qt_${QT_VERSION}"
|
||||
fi
|
||||
cd $QT_PATH
|
||||
sudo rm -rf *
|
||||
|
||||
cd "$EXTERNAL"
|
||||
git clone git://code.qt.io/qt/qt5.git qt${QT_VERSION}
|
||||
|
||||
cd "$EXTERNAL/qt${QT_VERSION}"
|
||||
perl init-repository --branch --module-subset=qtbase,qtimageformats
|
||||
git checkout v${QT_VERSION}
|
||||
git submodule update qtbase
|
||||
git submodule update qtimageformats
|
||||
|
||||
cd "$EXTERNAL/qt${QT_VERSION}/qtbase"
|
||||
git apply "$QT_PATCH"
|
||||
cd ..
|
||||
|
||||
cd "$EXTERNAL/qt${QT_VERSION}/qtbase/src/plugins/platforminputcontexts"
|
||||
git clone https://github.com/telegramdesktop/fcitx.git
|
||||
git clone https://github.com/telegramdesktop/hime.git
|
||||
git clone https://github.com/telegramdesktop/nimf.git
|
||||
cd ../../../..
|
||||
|
||||
./configure -prefix $QT_PATH -release -opensource -confirm-license -qt-zlib \
|
||||
-qt-libpng -qt-libjpeg -qt-harfbuzz -qt-pcre -qt-xcb \
|
||||
-system-freetype -fontconfig -no-opengl -no-gtk -static \
|
||||
-nomake examples -nomake tests -no-mirclient \
|
||||
-dbus-runtime -no-mtdev # <- Not sure about these
|
||||
make $MAKE_ARGS
|
||||
sudo make install
|
||||
}
|
||||
|
||||
getGSL() {
|
||||
cd "$UPSTREAM"
|
||||
git submodule init
|
||||
git submodule update
|
||||
}
|
||||
|
||||
getGYP() {
|
||||
travisStartFold "Getting patched GYP"
|
||||
|
||||
local GYP_CACHE="$CACHE/gyp"
|
||||
local GYP_CACHE_FILE="$GYP_CACHE/.cache.txt"
|
||||
local GYP_PATCH_CHECKSUM=`sha1sum $GYP_PATCH`
|
||||
local GYP_CACHE_KEY="${GYP_CACHE_VERSION}_${GYP_PATCH_CHECKSUM:0:32}"
|
||||
local GYP_CACHE_OUTDATED="1"
|
||||
|
||||
if [ ! -d "$GYP_CACHE" ]; then
|
||||
mkdir -p "$GYP_CACHE"
|
||||
fi
|
||||
|
||||
ln -sf "$GYP_CACHE" "$GYP_PATH"
|
||||
|
||||
if [ -f "$GYP_CACHE_FILE" ]; then
|
||||
local GYP_CACHE_KEY_FOUND=`tail -n 1 $GYP_CACHE_FILE`
|
||||
if [ "$GYP_CACHE_KEY" == "$GYP_CACHE_KEY_FOUND" ]; then
|
||||
GYP_CACHE_OUTDATED="0"
|
||||
else
|
||||
info_msg "Cache key '$GYP_CACHE_KEY_FOUND' does not match '$GYP_CACHE_KEY', rebuilding patched GYP"
|
||||
fi
|
||||
fi
|
||||
if [ "$GYP_CACHE_OUTDATED" == "1" ]; then
|
||||
buildGYP
|
||||
sudo echo $GYP_CACHE_KEY > "$GYP_CACHE_FILE"
|
||||
else
|
||||
info_msg "Using cached patched GYP"
|
||||
fi
|
||||
}
|
||||
|
||||
buildGYP() {
|
||||
info_msg "Downloading and building patched GYP"
|
||||
|
||||
if [ -d "$EXTERNAL/gyp" ]; then
|
||||
rm -rf "$EXTERNAL/gyp"
|
||||
fi
|
||||
cd $GYP_PATH
|
||||
rm -rf *
|
||||
|
||||
cd "$EXTERNAL"
|
||||
git clone https://chromium.googlesource.com/external/gyp
|
||||
|
||||
cd "$EXTERNAL/gyp"
|
||||
git checkout 9f2a7bb1
|
||||
git apply "$GYP_PATCH"
|
||||
cp -r * "$GYP_PATH/"
|
||||
}
|
||||
|
||||
buildTelegram() {
|
||||
travisStartFold "Build tdesktop"
|
||||
|
||||
cd "$UPSTREAM/Telegram/gyp"
|
||||
"$GYP_PATH/gyp" \
|
||||
-Dapi_id=17349 \
|
||||
-Dapi_hash=344583e45741c457fe1862106095a5eb \
|
||||
-Dspecial_build_target= \
|
||||
-Dbuild_defines=${GYP_DEFINES:1} \
|
||||
-Dlinux_path_xkbcommon=$XKB_PATH \
|
||||
-Dlinux_path_va=$VA_PATH \
|
||||
-Dlinux_path_vdpau=$VDPAU_PATH \
|
||||
-Dlinux_path_ffmpeg=$FFMPEG_PATH \
|
||||
-Dlinux_path_openal=$OPENAL_PATH \
|
||||
-Dlinux_path_range=$RANGE_PATH \
|
||||
-Dlinux_path_qt=$QT_PATH \
|
||||
-Dlinux_path_breakpad=$BREAKPAD_PATH \
|
||||
-Dlinux_path_libexif_lib=/usr/local/lib \
|
||||
-Dlinux_lib_ssl=-lssl \
|
||||
-Dlinux_lib_crypto=-lcrypto \
|
||||
-Dlinux_lib_icu=-licuuc\ -licutu\ -licui18n \
|
||||
--depth=. --generator-output=.. --format=cmake -Goutput_dir=../out \
|
||||
Telegram.gyp
|
||||
cd "$UPSTREAM/out/Debug"
|
||||
|
||||
export ASM="gcc"
|
||||
cmake .
|
||||
make $MAKE_ARGS
|
||||
}
|
||||
|
||||
check() {
|
||||
local filePath="$UPSTREAM/out/Debug/Telegram"
|
||||
if test -f "$filePath"; then
|
||||
success_msg "Build successfully done! :)"
|
||||
|
||||
local size;
|
||||
size=$(stat -c %s "$filePath")
|
||||
success_msg "File size of ${filePath}: ${size} Bytes"
|
||||
else
|
||||
error_msg "Build error, output file does not exist"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
source ./.travis/common.sh
|
||||
|
||||
run
|
||||
@@ -1,64 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# set colors
|
||||
RCol='\e[0m' # Text Reset
|
||||
|
||||
# Regular Bold Underline High Intensity BoldHigh Intens Background High Intensity Backgrounds
|
||||
Bla='\e[0;30m'; BBla='\e[1;30m'; UBla='\e[4;30m'; IBla='\e[0;90m'; BIBla='\e[1;90m'; On_Bla='\e[40m'; On_IBla='\e[0;100m';
|
||||
Red='\e[0;31m'; BRed='\e[1;31m'; URed='\e[4;31m'; IRed='\e[0;91m'; BIRed='\e[1;91m'; On_Red='\e[41m'; On_IRed='\e[0;101m';
|
||||
Gre='\e[0;32m'; BGre='\e[1;32m'; UGre='\e[4;32m'; IGre='\e[0;92m'; BIGre='\e[1;92m'; On_Gre='\e[42m'; On_IGre='\e[0;102m';
|
||||
Yel='\e[0;33m'; BYel='\e[1;33m'; UYel='\e[4;33m'; IYel='\e[0;93m'; BIYel='\e[1;93m'; On_Yel='\e[43m'; On_IYel='\e[0;103m';
|
||||
Blu='\e[0;34m'; BBlu='\e[1;34m'; UBlu='\e[4;34m'; IBlu='\e[0;94m'; BIBlu='\e[1;94m'; On_Blu='\e[44m'; On_IBlu='\e[0;104m';
|
||||
Pur='\e[0;35m'; BPur='\e[1;35m'; UPur='\e[4;35m'; IPur='\e[0;95m'; BIPur='\e[1;95m'; On_Pur='\e[45m'; On_IPur='\e[0;105m';
|
||||
Cya='\e[0;36m'; BCya='\e[1;36m'; UCya='\e[4;36m'; ICya='\e[0;96m'; BICya='\e[1;96m'; On_Cya='\e[46m'; On_ICya='\e[0;106m';
|
||||
Whi='\e[0;37m'; BWhi='\e[1;37m'; UWhi='\e[4;37m'; IWhi='\e[0;97m'; BIWhi='\e[1;97m'; On_Whi='\e[47m'; On_IWhi='\e[0;107m';
|
||||
|
||||
start_msg() {
|
||||
echo -e "\n${Gre}$*${RCol}"
|
||||
}
|
||||
|
||||
info_msg() {
|
||||
sameLineInfoMessage "\n$1"
|
||||
}
|
||||
|
||||
error_msg() {
|
||||
echo -e "\n${BRed}$*${RCol}"
|
||||
}
|
||||
|
||||
success_msg() {
|
||||
echo -e "\n${BGre}$*${RCol}"
|
||||
}
|
||||
|
||||
sameLineInfoMessage() {
|
||||
echo -e "${Cya}$*${RCol}"
|
||||
}
|
||||
|
||||
TRAVIS_LAST_FOLD=""
|
||||
|
||||
travisStartFold() {
|
||||
local TITLE="$1"
|
||||
local NAME=$(sanitizeName "$TITLE")
|
||||
|
||||
if [ "$TRAVIS_LAST_FOLD" != "" ]; then
|
||||
travisEndFold
|
||||
fi
|
||||
|
||||
echo "travis_fold:start:$NAME"
|
||||
sameLineInfoMessage "$TITLE"
|
||||
|
||||
TRAVIS_LAST_FOLD="$NAME"
|
||||
}
|
||||
|
||||
travisEndFold() {
|
||||
if [ "$TRAVIS_LAST_FOLD" == "" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo "travis_fold:end:$TRAVIS_LAST_FOLD"
|
||||
TRAVIS_LAST_FOLD=""
|
||||
}
|
||||
|
||||
sanitizeName() {
|
||||
local NAME="${1// /_}"
|
||||
local NAME="${NAME,,}"
|
||||
echo "$NAME"
|
||||
}
|
||||
@@ -5,8 +5,7 @@ This is the complete source code and the build instructions for the alpha versio
|
||||
[](https://github.com/telegramdesktop/tdesktop/releases)
|
||||
[](https://github.com/telegramdesktop/tdesktop/actions)
|
||||
[](https://github.com/telegramdesktop/tdesktop/actions)
|
||||
[](https://travis-ci.org/telegramdesktop/tdesktop)
|
||||
[](https://ci.appveyor.com/project/telegramdesktop/tdesktop)
|
||||
[](https://github.com/telegramdesktop/tdesktop/actions)
|
||||
|
||||
[![Preview of Telegram Desktop][preview_image]][preview_image_url]
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ add_subdirectory(lib_lottie)
|
||||
add_subdirectory(lib_qr)
|
||||
add_subdirectory(codegen)
|
||||
|
||||
include(CheckCXXSourceCompiles)
|
||||
include(lib_ui/cmake/generate_styles.cmake)
|
||||
include(cmake/generate_lang.cmake)
|
||||
include(cmake/generate_numbers.cmake)
|
||||
@@ -42,7 +43,7 @@ set(style_files
|
||||
history/history.style
|
||||
info/info.style
|
||||
intro/intro.style
|
||||
media/view/mediaview.style
|
||||
media/view/media_view.style
|
||||
media/player/media_player.style
|
||||
overview/overview.style
|
||||
passport/passport.style
|
||||
@@ -65,17 +66,18 @@ generate_numbers(Telegram ${res_loc}/numbers.txt)
|
||||
|
||||
set_target_properties(Telegram PROPERTIES AUTOMOC ON AUTORCC ON)
|
||||
|
||||
if (DESKTOP_APP_USE_PACKAGED)
|
||||
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
||||
find_package(Threads)
|
||||
|
||||
if (LINUX AND NOT DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
${CMAKE_DL_LIBS}
|
||||
Threads::Threads
|
||||
desktop-app::external_statusnotifieritem
|
||||
desktop-app::external_dbusmenu_qt
|
||||
)
|
||||
endif()
|
||||
|
||||
if (add_hunspell_library)
|
||||
target_link_libraries(Telegram PRIVATE desktop-app::external_hunspell)
|
||||
endif()
|
||||
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
tdesktop::lib_mtproto
|
||||
@@ -105,6 +107,27 @@ if (NOT DESKTOP_APP_USE_PACKAGED)
|
||||
target_link_libraries(Telegram PRIVATE desktop-app::external_opus)
|
||||
endif()
|
||||
|
||||
# Telegram uses long atomic types, so on some architectures libatomic is needed.
|
||||
check_cxx_source_compiles("
|
||||
#include <atomic>
|
||||
std::atomic_int64_t foo;
|
||||
int main() {return foo;}
|
||||
" HAVE_LONG_ATOMIC_WITHOUT_LIB)
|
||||
if (NOT HAVE_LONG_ATOMIC_WITHOUT_LIB)
|
||||
target_link_libraries(Telegram PRIVATE atomic)
|
||||
endif()
|
||||
|
||||
if (DESKTOP_APP_USE_PACKAGED)
|
||||
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
||||
find_package(Threads)
|
||||
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
${CMAKE_DL_LIBS}
|
||||
Threads::Threads
|
||||
)
|
||||
endif()
|
||||
|
||||
target_precompile_headers(Telegram PRIVATE ${src_loc}/stdafx.h)
|
||||
nice_target_sources(Telegram ${src_loc}
|
||||
PRIVATE
|
||||
@@ -166,6 +189,8 @@ PRIVATE
|
||||
boxes/connection_box.h
|
||||
boxes/create_poll_box.cpp
|
||||
boxes/create_poll_box.h
|
||||
boxes/dictionaries_manager.cpp
|
||||
boxes/dictionaries_manager.h
|
||||
boxes/download_path_box.cpp
|
||||
boxes/download_path_box.h
|
||||
boxes/edit_caption_box.cpp
|
||||
@@ -238,6 +263,8 @@ PRIVATE
|
||||
chat_helpers/gifs_list_widget.h
|
||||
chat_helpers/message_field.cpp
|
||||
chat_helpers/message_field.h
|
||||
chat_helpers/spellchecker_common.cpp
|
||||
chat_helpers/spellchecker_common.h
|
||||
chat_helpers/stickers.cpp
|
||||
chat_helpers/stickers.h
|
||||
chat_helpers/stickers_emoji_pack.cpp
|
||||
@@ -316,8 +343,12 @@ PRIVATE
|
||||
data/data_game.h
|
||||
data/data_groups.cpp
|
||||
data/data_groups.h
|
||||
data/data_histories.cpp
|
||||
data/data_histories.h
|
||||
data/data_location.cpp
|
||||
data/data_location.h
|
||||
data/data_media_rotation.cpp
|
||||
data/data_media_rotation.h
|
||||
data/data_media_types.cpp
|
||||
data/data_media_types.h
|
||||
data/data_messages.cpp
|
||||
@@ -518,6 +549,10 @@ PRIVATE
|
||||
info/media/info_media_widget.h
|
||||
info/members/info_members_widget.cpp
|
||||
info/members/info_members_widget.h
|
||||
info/polls/info_polls_results_inner_widget.cpp
|
||||
info/polls/info_polls_results_inner_widget.h
|
||||
info/polls/info_polls_results_widget.cpp
|
||||
info/polls/info_polls_results_widget.h
|
||||
info/profile/info_profile_actions.cpp
|
||||
info/profile/info_profile_actions.h
|
||||
info/profile/info_profile_cover.cpp
|
||||
@@ -649,14 +684,16 @@ PRIVATE
|
||||
media/streaming/media_streaming_utility.h
|
||||
media/streaming/media_streaming_video_track.cpp
|
||||
media/streaming/media_streaming_video_track.h
|
||||
media/view/media_view_playback_controls.cpp
|
||||
media/view/media_view_playback_controls.h
|
||||
media/view/media_view_playback_progress.cpp
|
||||
media/view/media_view_playback_progress.h
|
||||
media/view/media_view_group_thumbs.cpp
|
||||
media/view/media_view_group_thumbs.h
|
||||
media/view/media_view_overlay_widget.cpp
|
||||
media/view/media_view_overlay_widget.h
|
||||
media/view/media_view_pip.cpp
|
||||
media/view/media_view_pip.h
|
||||
media/view/media_view_playback_controls.cpp
|
||||
media/view/media_view_playback_controls.h
|
||||
media/view/media_view_playback_progress.cpp
|
||||
media/view/media_view_playback_progress.h
|
||||
mtproto/config_loader.cpp
|
||||
mtproto/config_loader.h
|
||||
mtproto/connection_abstract.cpp
|
||||
@@ -819,6 +856,8 @@ PRIVATE
|
||||
storage/serialize_common.h
|
||||
storage/serialize_document.cpp
|
||||
storage/serialize_document.h
|
||||
storage/storage_cloud_blob.cpp
|
||||
storage/storage_cloud_blob.h
|
||||
storage/storage_facade.cpp
|
||||
storage/storage_facade.h
|
||||
# storage/storage_feed_messages.cpp
|
||||
@@ -841,6 +880,8 @@ PRIVATE
|
||||
support/support_helper.h
|
||||
support/support_templates.cpp
|
||||
support/support_templates.h
|
||||
ui/effects/fireworks_animation.cpp
|
||||
ui/effects/fireworks_animation.h
|
||||
ui/effects/round_checkbox.cpp
|
||||
ui/effects/round_checkbox.h
|
||||
ui/effects/send_action_animations.cpp
|
||||
@@ -1042,35 +1083,13 @@ elseif (LINUX)
|
||||
find_library(X11_LIBRARY X11)
|
||||
target_link_libraries(Telegram PRIVATE ${X11_LIBRARY})
|
||||
endif()
|
||||
|
||||
set(appindicator_packages
|
||||
ayatana-appindicator3-0.1
|
||||
ayatana-appindicator-0.1
|
||||
appindicator3-0.1
|
||||
appindicator-0.1
|
||||
)
|
||||
set(appindicator_found 0)
|
||||
foreach (package ${appindicator_packages})
|
||||
pkg_check_modules(APPIND_${package} ${package})
|
||||
if (APPIND_${package}_FOUND)
|
||||
set(appindicator_found 1)
|
||||
target_include_directories(Telegram PRIVATE "${APPIND_${package}_INCLUDE_DIRS}")
|
||||
if (${package} MATCHES "ayatana")
|
||||
target_compile_definitions(Telegram PRIVATE TDESKTOP_USE_AYATANA_INDICATORS)
|
||||
endif()
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
if (NOT ${appindicator_found})
|
||||
message(FATAL_ERROR "No libappindicator found by pkg-config.")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (build_macstore)
|
||||
set(bundle_identifier "org.telegram.desktop")
|
||||
set(bundle_entitlements "Telegram Desktop.entitlements")
|
||||
set(output_name "Telegram Desktop")
|
||||
set(bundle_entitlements "Telegram Lite.entitlements")
|
||||
set(output_name "Telegram Lite")
|
||||
set_target_properties(Telegram PROPERTIES
|
||||
XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${libs_loc}/breakpad/src/client/mac/build/Release
|
||||
)
|
||||
@@ -1088,7 +1107,11 @@ elseif (build_osx)
|
||||
else()
|
||||
set(bundle_identifier "com.tdesktop.Telegram$<$<CONFIG:Debug>:Debug>")
|
||||
set(bundle_entitlements "Telegram.entitlements")
|
||||
set(output_name "Telegram")
|
||||
if (LINUX AND DESKTOP_APP_USE_PACKAGED)
|
||||
set(output_name "telegram-desktop")
|
||||
else()
|
||||
set(output_name "Telegram")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set_target_properties(Telegram PROPERTIES
|
||||
@@ -1110,7 +1133,7 @@ set_target_properties(Telegram PROPERTIES
|
||||
)
|
||||
set(entitlement_sources
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Telegram/Telegram.entitlements"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Telegram/Telegram Desktop.entitlements"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Telegram/Telegram Lite.entitlements"
|
||||
)
|
||||
target_sources(Telegram PRIVATE ${entitlement_sources})
|
||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/Telegram PREFIX Resources FILES ${entitlement_sources})
|
||||
@@ -1159,7 +1182,7 @@ if ((NOT disable_autoupdate OR NOT LINUX) AND NOT build_macstore AND NOT build_w
|
||||
set_target_properties(Updater PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
|
||||
|
||||
if (LINUX)
|
||||
target_link_options(Updater INTERFACE -static-libstdc++)
|
||||
target_link_options(Updater PRIVATE -static-libstdc++)
|
||||
endif()
|
||||
|
||||
if (DESKTOP_APP_SPECIAL_TARGET)
|
||||
@@ -1185,3 +1208,17 @@ if ((NOT disable_autoupdate OR NOT LINUX) AND NOT build_macstore AND NOT build_w
|
||||
set_target_properties(Packer PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (LINUX AND DESKTOP_APP_USE_PACKAGED)
|
||||
include(GNUInstallDirs)
|
||||
install(TARGETS Telegram RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
install(FILES "Resources/art/icon16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "telegram.png")
|
||||
install(FILES "Resources/art/icon32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "telegram.png")
|
||||
install(FILES "Resources/art/icon48.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48/apps" RENAME "telegram.png")
|
||||
install(FILES "Resources/art/icon64.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/64x64/apps" RENAME "telegram.png")
|
||||
install(FILES "Resources/art/icon128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "telegram.png")
|
||||
install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "telegram.png")
|
||||
install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "telegram.png")
|
||||
install(FILES "../lib/xdg/telegramdesktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications" RENAME "${TDESKTOP_LAUNCHER_BASENAME}.desktop")
|
||||
install(FILES "../lib/xdg/telegramdesktop.appdata.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo" RENAME "${TDESKTOP_LAUNCHER_BASENAME}.appdata.xml")
|
||||
endif()
|
||||
|
||||
|
Before Width: | Height: | Size: 177 B After Width: | Height: | Size: 513 B |
|
Before Width: | Height: | Size: 278 B After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 417 B After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/mediaview_rotate.png
Normal file
|
After Width: | Height: | Size: 912 B |
BIN
Telegram/Resources/icons/mediaview_rotate@2x.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
Telegram/Resources/icons/mediaview_rotate@3x.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
Telegram/Resources/icons/player_check.png
Normal file
|
After Width: | Height: | Size: 281 B |
BIN
Telegram/Resources/icons/player_check@2x.png
Normal file
|
After Width: | Height: | Size: 476 B |
BIN
Telegram/Resources/icons/player_check@3x.png
Normal file
|
After Width: | Height: | Size: 695 B |
BIN
Telegram/Resources/icons/player_fullscreen.png
Normal file
|
After Width: | Height: | Size: 359 B |
BIN
Telegram/Resources/icons/player_fullscreen@2x.png
Normal file
|
After Width: | Height: | Size: 639 B |
BIN
Telegram/Resources/icons/player_fullscreen@3x.png
Normal file
|
After Width: | Height: | Size: 919 B |
BIN
Telegram/Resources/icons/player_minimize.png
Normal file
|
After Width: | Height: | Size: 341 B |
BIN
Telegram/Resources/icons/player_minimize@2x.png
Normal file
|
After Width: | Height: | Size: 594 B |
BIN
Telegram/Resources/icons/player_minimize@3x.png
Normal file
|
After Width: | Height: | Size: 879 B |
BIN
Telegram/Resources/icons/player_more.png
Normal file
|
After Width: | Height: | Size: 183 B |
BIN
Telegram/Resources/icons/player_more@2x.png
Normal file
|
After Width: | Height: | Size: 361 B |
BIN
Telegram/Resources/icons/player_more@3x.png
Normal file
|
After Width: | Height: | Size: 566 B |
BIN
Telegram/Resources/icons/player_pause.png
Normal file
|
After Width: | Height: | Size: 215 B |
BIN
Telegram/Resources/icons/player_pause@2x.png
Normal file
|
After Width: | Height: | Size: 400 B |
BIN
Telegram/Resources/icons/player_pause@3x.png
Normal file
|
After Width: | Height: | Size: 593 B |
BIN
Telegram/Resources/icons/player_pip.png
Normal file
|
After Width: | Height: | Size: 319 B |
BIN
Telegram/Resources/icons/player_pip@2x.png
Normal file
|
After Width: | Height: | Size: 530 B |
BIN
Telegram/Resources/icons/player_pip@3x.png
Normal file
|
After Width: | Height: | Size: 877 B |
BIN
Telegram/Resources/icons/player_pip_close.png
Normal file
|
After Width: | Height: | Size: 253 B |
BIN
Telegram/Resources/icons/player_pip_close@2x.png
Normal file
|
After Width: | Height: | Size: 427 B |
BIN
Telegram/Resources/icons/player_pip_close@3x.png
Normal file
|
After Width: | Height: | Size: 747 B |
BIN
Telegram/Resources/icons/player_pip_enlarge.png
Normal file
|
After Width: | Height: | Size: 361 B |
BIN
Telegram/Resources/icons/player_pip_enlarge@2x.png
Normal file
|
After Width: | Height: | Size: 574 B |
BIN
Telegram/Resources/icons/player_pip_enlarge@3x.png
Normal file
|
After Width: | Height: | Size: 845 B |
BIN
Telegram/Resources/icons/player_pip_pause.png
Normal file
|
After Width: | Height: | Size: 242 B |
BIN
Telegram/Resources/icons/player_pip_pause@2x.png
Normal file
|
After Width: | Height: | Size: 452 B |
BIN
Telegram/Resources/icons/player_pip_pause@3x.png
Normal file
|
After Width: | Height: | Size: 662 B |
BIN
Telegram/Resources/icons/player_pip_play.png
Normal file
|
After Width: | Height: | Size: 334 B |
BIN
Telegram/Resources/icons/player_pip_play@2x.png
Normal file
|
After Width: | Height: | Size: 631 B |
BIN
Telegram/Resources/icons/player_pip_play@3x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/player_play.png
Normal file
|
After Width: | Height: | Size: 362 B |
BIN
Telegram/Resources/icons/player_play@2x.png
Normal file
|
After Width: | Height: | Size: 659 B |
BIN
Telegram/Resources/icons/player_play@3x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/player_volume_off.png
Normal file
|
After Width: | Height: | Size: 445 B |
BIN
Telegram/Resources/icons/player_volume_off@2x.png
Normal file
|
After Width: | Height: | Size: 854 B |
BIN
Telegram/Resources/icons/player_volume_off@3x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/player_volume_on.png
Normal file
|
After Width: | Height: | Size: 482 B |
BIN
Telegram/Resources/icons/player_volume_on@2x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/player_volume_on@3x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/player_volume_small.png
Normal file
|
After Width: | Height: | Size: 341 B |
BIN
Telegram/Resources/icons/player_volume_small@2x.png
Normal file
|
After Width: | Height: | Size: 668 B |
BIN
Telegram/Resources/icons/player_volume_small@3x.png
Normal file
|
After Width: | Height: | Size: 962 B |
BIN
Telegram/Resources/icons/poll_choice_right.png
Normal file
|
After Width: | Height: | Size: 166 B |
BIN
Telegram/Resources/icons/poll_choice_right@2x.png
Normal file
|
After Width: | Height: | Size: 219 B |
BIN
Telegram/Resources/icons/poll_choice_right@3x.png
Normal file
|
After Width: | Height: | Size: 294 B |
BIN
Telegram/Resources/icons/poll_choice_wrong.png
Normal file
|
After Width: | Height: | Size: 157 B |
BIN
Telegram/Resources/icons/poll_choice_wrong@2x.png
Normal file
|
After Width: | Height: | Size: 213 B |
BIN
Telegram/Resources/icons/poll_choice_wrong@3x.png
Normal file
|
After Width: | Height: | Size: 260 B |
BIN
Telegram/Resources/icons/poll_select_check.png
Normal file
|
After Width: | Height: | Size: 249 B |
BIN
Telegram/Resources/icons/poll_select_check@2x.png
Normal file
|
After Width: | Height: | Size: 364 B |
BIN
Telegram/Resources/icons/poll_select_check@3x.png
Normal file
|
After Width: | Height: | Size: 556 B |
@@ -158,6 +158,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_deleted_message" = "Deleted message";
|
||||
"lng_pinned_message" = "Pinned message";
|
||||
"lng_pinned_poll" = "Pinned poll";
|
||||
"lng_pinned_quiz" = "Pinned quiz";
|
||||
"lng_pinned_unpin_sure" = "Would you like to unpin this message?";
|
||||
"lng_pinned_pin_sure" = "Would you like to pin this message?";
|
||||
"lng_pinned_pin" = "Pin";
|
||||
@@ -421,6 +422,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_settings_spellchecker" = "Spell checker";
|
||||
"lng_settings_system_spellchecker" = "Use system spell checker";
|
||||
"lng_settings_custom_spellchecker" = "Use spell checker";
|
||||
"lng_settings_auto_download_dictionaries" = "Automatic dictionaries download";
|
||||
"lng_settings_manage_dictionaries" = "Manage dictionaries";
|
||||
"lng_settings_manage_enabled_dictionary" = "Dictionary is enabled";
|
||||
"lng_settings_manage_remove_dictionary" = "Remove Dictionary";
|
||||
|
||||
"lng_backgrounds_header" = "Choose your new chat background";
|
||||
"lng_theme_sure_keep" = "Keep this theme?";
|
||||
@@ -1267,6 +1273,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_cant_invite_make_admin" = "Make admin";
|
||||
|
||||
"lng_send_button" = "Send";
|
||||
"lng_schedule_button" = "Schedule";
|
||||
"lng_send_silent_message" = "Send without sound";
|
||||
"lng_schedule_message" = "Schedule message";
|
||||
"lng_reminder_message" = "Set a reminder";
|
||||
@@ -1294,6 +1301,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_scheduled_messages" = "Scheduled Messages";
|
||||
"lng_reminder_messages" = "Reminders";
|
||||
"lng_scheduled_date" = "Scheduled for {date}";
|
||||
"lng_scheduled_date_until_online" = "Scheduled until online";
|
||||
"lng_scheduled_send_until_online" = "Send when online";
|
||||
"lng_scheduled_send_now" = "Send message now?";
|
||||
"lng_scheduled_send_now_many#one" = "Send {count} message now?";
|
||||
"lng_scheduled_send_now_many#other" = "Send {count} messages now?";
|
||||
@@ -1364,7 +1374,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_user_action_upload_file" = "{user} is sending a file";
|
||||
"lng_unread_bar#one" = "{count} unread message";
|
||||
"lng_unread_bar#other" = "{count} unread messages";
|
||||
//"lng_unread_bar_some" = "Unread messages";
|
||||
"lng_unread_bar_some" = "Unread messages";
|
||||
|
||||
"lng_maps_point" = "Location";
|
||||
"lng_save_photo" = "Save image";
|
||||
@@ -1453,6 +1463,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_send_album" = "Send as an album";
|
||||
"lng_send_photo" = "Send as a photo";
|
||||
"lng_send_file" = "Send as a file";
|
||||
"lng_send_media_invalid_files" = "Sorry, no valid files found.";
|
||||
|
||||
"lng_forward_choose" = "Choose recipient...";
|
||||
"lng_forward_cant" = "Sorry, no way to forward here :(";
|
||||
@@ -1573,6 +1584,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_mediaview_saved_to" = "Image was saved to your {downloads} folder";
|
||||
"lng_mediaview_downloads" = "Downloads";
|
||||
"lng_mediaview_video_loading" = "Loading - {percent}";
|
||||
"lng_mediaview_playback_speed" = "Playback speed";
|
||||
"lng_mediaview_playback_speed_normal" = "Normal";
|
||||
"lng_mediaview_rotate_video" = "Rotate video";
|
||||
|
||||
"lng_theme_preview_title" = "Theme Preview";
|
||||
"lng_theme_preview_generating" = "Generating color theme preview...";
|
||||
@@ -1602,6 +1616,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_formatting_link_url" = "URL";
|
||||
"lng_formatting_link_create" = "Create";
|
||||
|
||||
"lng_spellchecker_submenu" = "Spelling";
|
||||
"lng_spellchecker_add" = "Add to Dictionary";
|
||||
"lng_spellchecker_remove" = "Remove from Dictionary";
|
||||
"lng_spellchecker_ignore" = "Ignore word";
|
||||
@@ -1797,6 +1812,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_restricted_send_inline_all" = "Posting inline content isn't allowed in this group.";
|
||||
"lng_restricted_send_polls_all" = "Posting polls isn't allowed in this group.";
|
||||
|
||||
"lng_restricted_send_public_polls" = "Sorry, public polls can't be forwarded to channels.";
|
||||
|
||||
"lng_exceptions_list_title" = "Exceptions";
|
||||
"lng_removed_list_title" = "Removed users";
|
||||
|
||||
@@ -2166,10 +2183,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_launch_exe_dont_ask" = "Don't ask me again";
|
||||
|
||||
"lng_polls_anonymous" = "Anonymous Poll";
|
||||
"lng_polls_public" = "Poll";
|
||||
"lng_polls_anonymous_quiz" = "Anonymous Quiz";
|
||||
"lng_polls_public_quiz" = "Quiz";
|
||||
"lng_polls_closed" = "Final results";
|
||||
"lng_polls_votes_count#one" = "{count} vote";
|
||||
"lng_polls_votes_count#other" = "{count} votes";
|
||||
"lng_polls_votes_none" = "No votes";
|
||||
"lng_polls_answers_count#one" = "{count} answer";
|
||||
"lng_polls_answers_count#other" = "{count} answers";
|
||||
"lng_polls_answers_none" = "No answers";
|
||||
"lng_polls_submit_votes" = "Vote";
|
||||
"lng_polls_view_results" = "View results";
|
||||
"lng_polls_retract" = "Retract vote";
|
||||
"lng_polls_stop" = "Stop poll";
|
||||
"lng_polls_stop_warning" = "If you stop this poll now, nobody will be able to vote in it anymore. This action cannot be undone.";
|
||||
@@ -2183,7 +2208,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_polls_create_limit#one" = "You can add {count} more option.";
|
||||
"lng_polls_create_limit#other" = "You can add {count} more options.";
|
||||
"lng_polls_create_maximum" = "You have added the maximum number of options.";
|
||||
"lng_polls_create_settings" = "Settings";
|
||||
"lng_polls_create_anonymous" = "Anonymous Votes";
|
||||
"lng_polls_create_multiple_choice" = "Multiple Answers";
|
||||
"lng_polls_create_quiz_mode" = "Quiz Mode";
|
||||
"lng_polls_create_button" = "Create";
|
||||
"lng_polls_create_one_answer" = "Quiz has only one right answer.";
|
||||
"lng_polls_choose_question" = "Please enter a question.";
|
||||
"lng_polls_choose_answers" = "Please enter at least two options.";
|
||||
"lng_polls_choose_correct" = "Please choose the correct answer.";
|
||||
|
||||
"lng_polls_poll_results_title" = "Poll results";
|
||||
"lng_polls_quiz_results_title" = "Quiz results";
|
||||
"lng_polls_show_more#one" = "Show more ({count})";
|
||||
"lng_polls_show_more#other" = "Show more ({count})";
|
||||
"lng_polls_votes_collapse" = "Collapse";
|
||||
|
||||
"lng_outdated_title" = "PLEASE UPDATE YOUR OPERATING SYSTEM.";
|
||||
"lng_outdated_soon" = "Otherwise, Telegram Desktop will stop updating on {date}.";
|
||||
|
||||
@@ -56,5 +56,6 @@
|
||||
</qresource>
|
||||
<qresource prefix="/misc">
|
||||
<file alias="default_shortcuts-custom.json">../../default_shortcuts-custom.json</file>
|
||||
<file alias="telegramdesktop.desktop">../../../../lib/xdg/telegramdesktop.desktop</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -71,7 +71,7 @@ inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int =
|
||||
inputMediaGame#d33f43f3 id:InputGame = InputMedia;
|
||||
inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia;
|
||||
inputMediaGeoLive#ce4e82fd flags:# stopped:flags.0?true geo_point:InputGeoPoint period:flags.1?int = InputMedia;
|
||||
inputMediaPoll#6b3765b poll:Poll = InputMedia;
|
||||
inputMediaPoll#abe9ca25 flags:# poll:Poll correct_answers:flags.0?Vector<bytes> = InputMedia;
|
||||
|
||||
inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
|
||||
inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto;
|
||||
@@ -351,6 +351,7 @@ updateDeleteScheduledMessages#90866cee peer:Peer messages:Vector<int> = Update;
|
||||
updateTheme#8216fba3 theme:Theme = Update;
|
||||
updateGeoLiveViewed#871fb939 peer:Peer msg_id:int = Update;
|
||||
updateLoginToken#564fe691 = Update;
|
||||
updateMessagePollVote#42f88f2c poll_id:long user_id:int options:Vector<bytes> = Update;
|
||||
|
||||
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
|
||||
|
||||
@@ -546,6 +547,7 @@ keyboardButtonGame#50f41ccf text:string = KeyboardButton;
|
||||
keyboardButtonBuy#afd93fbb text:string = KeyboardButton;
|
||||
keyboardButtonUrlAuth#10b78d29 flags:# text:string fwd_text:flags.0?string url:string button_id:int = KeyboardButton;
|
||||
inputKeyboardButtonUrlAuth#d02e7fd4 flags:# request_write_access:flags.0?true text:string fwd_text:flags.1?string url:string bot:InputUser = KeyboardButton;
|
||||
keyboardButtonRequestPoll#bbc7515d flags:# quiz:flags.0?Bool text:string = KeyboardButton;
|
||||
|
||||
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
|
||||
|
||||
@@ -1015,11 +1017,11 @@ help.userInfo#1eb3758 message:string entities:Vector<MessageEntity> author:strin
|
||||
|
||||
pollAnswer#6ca9c2e9 text:string option:bytes = PollAnswer;
|
||||
|
||||
poll#d5529d06 id:long flags:# closed:flags.0?true question:string answers:Vector<PollAnswer> = Poll;
|
||||
poll#d5529d06 id:long flags:# closed:flags.0?true public_voters:flags.1?true multiple_choice:flags.2?true quiz:flags.3?true question:string answers:Vector<PollAnswer> = Poll;
|
||||
|
||||
pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true option:bytes voters:int = PollAnswerVoters;
|
||||
pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true correct:flags.1?true option:bytes voters:int = PollAnswerVoters;
|
||||
|
||||
pollResults#5755785a flags:# min:flags.0?true results:flags.1?Vector<PollAnswerVoters> total_voters:flags.2?int = PollResults;
|
||||
pollResults#c87024a2 flags:# min:flags.0?true results:flags.1?Vector<PollAnswerVoters> total_voters:flags.2?int recent_voters:flags.3?Vector<int> = PollResults;
|
||||
|
||||
chatOnlines#f041e250 onlines:int = ChatOnlines;
|
||||
|
||||
@@ -1077,16 +1079,11 @@ restrictionReason#d072acb4 platform:string reason:string text:string = Restricti
|
||||
inputTheme#3c5693e9 id:long access_hash:long = InputTheme;
|
||||
inputThemeSlug#f5890df1 slug:string = InputTheme;
|
||||
|
||||
themeDocumentNotModified#483d270c = Theme;
|
||||
theme#28f1114 flags:# creator:flags.0?true default:flags.1?true id:long access_hash:long slug:string title:string document:flags.2?Document settings:flags.3?ThemeSettings installs_count:int = Theme;
|
||||
|
||||
account.themesNotModified#f41eb622 = account.Themes;
|
||||
account.themes#7f676421 hash:int themes:Vector<Theme> = account.Themes;
|
||||
|
||||
wallet.liteResponse#764386d7 response:bytes = wallet.LiteResponse;
|
||||
|
||||
wallet.secretSalt#dd484d64 salt:bytes = wallet.KeySecretSalt;
|
||||
|
||||
auth.loginToken#629f1980 expires:int token:bytes = auth.LoginToken;
|
||||
auth.loginTokenMigrateTo#68e9916 dc_id:int token:bytes = auth.LoginToken;
|
||||
auth.loginTokenSuccess#390d5c5e authorization:auth.Authorization = auth.LoginToken;
|
||||
@@ -1107,6 +1104,12 @@ themeSettings#9c14984a flags:# base_theme:BaseTheme accent_color:int message_top
|
||||
|
||||
webPageAttributeTheme#54b56617 flags:# documents:flags.0?Vector<Document> settings:flags.1?ThemeSettings = WebPageAttribute;
|
||||
|
||||
messageUserVote#a28e5559 user_id:int option:bytes date:int = MessageUserVote;
|
||||
messageUserVoteInputOption#36377430 user_id:int date:int = MessageUserVote;
|
||||
messageUserVoteMultiple#e8fe0de user_id:int options:Vector<bytes> date:int = MessageUserVote;
|
||||
|
||||
messages.votesList#823f649 flags:# count:int votes:Vector<MessageUserVote> users:Vector<User> next_offset:flags.0?string = messages.VotesList;
|
||||
|
||||
---functions---
|
||||
|
||||
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
|
||||
@@ -1343,6 +1346,7 @@ messages.getScheduledHistory#e2c2685b peer:InputPeer hash:int = messages.Message
|
||||
messages.getScheduledMessages#bdbb0464 peer:InputPeer id:Vector<int> = messages.Messages;
|
||||
messages.sendScheduledMessages#bd38850a peer:InputPeer id:Vector<int> = Updates;
|
||||
messages.deleteScheduledMessages#59ae2b16 peer:InputPeer id:Vector<int> = Updates;
|
||||
messages.getPollVotes#b86e380e flags:# peer:InputPeer id:int option:flags.0?bytes offset:flags.1?string limit:int = messages.VotesList;
|
||||
|
||||
updates.getState#edd4882a = updates.State;
|
||||
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
|
||||
@@ -1354,7 +1358,7 @@ photos.deletePhotos#87cf7f2f id:Vector<InputPhoto> = Vector<long>;
|
||||
photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos;
|
||||
|
||||
upload.saveFilePart#b304a621 file_id:long file_part:int bytes:bytes = Bool;
|
||||
upload.getFile#b15a9afc flags:# precise:flags.0?true location:InputFileLocation offset:int limit:int = upload.File;
|
||||
upload.getFile#b15a9afc flags:# precise:flags.0?true cdn_supported:flags.1?true location:InputFileLocation offset:int limit:int = upload.File;
|
||||
upload.saveBigFilePart#de7b673d file_id:long file_part:int file_total_parts:int bytes:bytes = Bool;
|
||||
upload.getWebFile#24e6818d location:InputWebFileLocation offset:int limit:int = upload.WebFile;
|
||||
upload.getCdnFile#2000bcc3 file_token:bytes offset:int limit:int = upload.CdnFile;
|
||||
@@ -1451,7 +1455,4 @@ langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLangua
|
||||
folders.editPeerFolders#6847d0ab folder_peers:Vector<InputFolderPeer> = Updates;
|
||||
folders.deleteFolder#1c295881 folder_id:int = Updates;
|
||||
|
||||
wallet.sendLiteRequest#e2c9d33e body:bytes = wallet.LiteResponse;
|
||||
wallet.getKeySecretSalt#b57f346 revoke:Bool = wallet.KeySecretSalt;
|
||||
|
||||
// LAYER 108
|
||||
// LAYER 109
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="1.9.5.0" />
|
||||
Version="1.9.17.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram FZ-LLC</PublisherDisplayName>
|
||||
|
||||
@@ -33,8 +33,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1,9,5,0
|
||||
PRODUCTVERSION 1,9,5,0
|
||||
FILEVERSION 1,9,17,0
|
||||
PRODUCTVERSION 1,9,17,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -51,10 +51,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "1.9.5.0"
|
||||
VALUE "FileVersion", "1.9.17.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "1.9.5.0"
|
||||
VALUE "ProductVersion", "1.9.17.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -24,8 +24,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1,9,5,0
|
||||
PRODUCTVERSION 1,9,5,0
|
||||
FILEVERSION 1,9,17,0
|
||||
PRODUCTVERSION 1,9,17,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -42,10 +42,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "1.9.5.0"
|
||||
VALUE "FileVersion", "1.9.17.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "1.9.5.0"
|
||||
VALUE "ProductVersion", "1.9.17.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -21,6 +21,7 @@ struct SendOptions {
|
||||
enum class SendType {
|
||||
Normal,
|
||||
Scheduled,
|
||||
ScheduledToUser, // For "Send when online".
|
||||
};
|
||||
|
||||
struct SendAction {
|
||||
|
||||
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h" // UserData::name
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_message.h" // NewMessageFlags.
|
||||
#include "chat_helpers/message_field.h" // ConvertTextTagsToEntities.
|
||||
@@ -110,23 +111,30 @@ void SendExistingMedia(
|
||||
|
||||
auto failHandler = std::make_shared<Fn<void(const RPCError&, QByteArray)>>();
|
||||
auto performRequest = [=] {
|
||||
const auto usedFileReference = media->fileReference();
|
||||
history->sendRequestId = api->request(MTPmessages_SendMedia(
|
||||
MTP_flags(sendFlags),
|
||||
peer->input,
|
||||
MTP_int(replyTo),
|
||||
inputMedia(),
|
||||
MTP_string(captionText),
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(message.action.options.scheduled)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
api->applyUpdates(result, randomId);
|
||||
}).fail([=](const RPCError &error) {
|
||||
(*failHandler)(error, usedFileReference);
|
||||
}).afterRequest(history->sendRequestId
|
||||
).send();
|
||||
auto &histories = history->owner().histories();
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
const auto usedFileReference = media->fileReference();
|
||||
history->sendRequestId = api->request(MTPmessages_SendMedia(
|
||||
MTP_flags(sendFlags),
|
||||
peer->input,
|
||||
MTP_int(replyTo),
|
||||
inputMedia(),
|
||||
MTP_string(captionText),
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(message.action.options.scheduled)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
api->applyUpdates(result, randomId);
|
||||
finish();
|
||||
}).fail([=](const RPCError &error) {
|
||||
(*failHandler)(error, usedFileReference);
|
||||
finish();
|
||||
}).afterRequest(history->sendRequestId
|
||||
).send();
|
||||
return history->sendRequestId;
|
||||
});
|
||||
};
|
||||
*failHandler = [=](const RPCError &error, QByteArray usedFileReference) {
|
||||
if (error.code() == 400
|
||||
@@ -169,7 +177,7 @@ void SendExistingDocument(
|
||||
if (document->sticker()) {
|
||||
if (const auto main = App::main()) {
|
||||
main->incrementSticker(document);
|
||||
document->session().data().notifyRecentStickersUpdated();
|
||||
document->owner().notifyRecentStickersUpdated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_cloud_themes.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "dialogs/dialogs_key.h"
|
||||
#include "core/core_cloud_password.h"
|
||||
#include "core/application.h"
|
||||
@@ -343,7 +344,7 @@ void ApiWrap::proxyPromotionDone(const MTPhelp_ProxyData &proxy) {
|
||||
const auto peer = _session->data().peer(peerId);
|
||||
_session->data().setProxyPromoted(peer);
|
||||
if (const auto history = _session->data().historyLoaded(peer)) {
|
||||
requestDialogEntry(history);
|
||||
history->owner().histories().requestDialogEntry(history);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -579,6 +580,13 @@ void ApiWrap::sendMessageFail(
|
||||
requestFullPeer(peer);
|
||||
}
|
||||
}
|
||||
} else if (error.type() == qstr("SCHEDULE_STATUS_PRIVATE")) {
|
||||
auto &scheduled = _session->data().scheduledMessages();
|
||||
Assert(peer->isUser());
|
||||
if (const auto item = scheduled.lookupItem(peer->id, itemId.msg)) {
|
||||
scheduled.removeSending(item);
|
||||
Ui::show(Box<InformBox>(tr::lng_cant_do_this(tr::now)));
|
||||
}
|
||||
}
|
||||
if (const auto item = _session->data().message(itemId)) {
|
||||
Assert(randomId != 0);
|
||||
@@ -1004,162 +1012,6 @@ rpl::producer<bool> ApiWrap::dialogsLoadBlockedByDate() const {
|
||||
return _dialogsLoadBlockedByDate.value();
|
||||
}
|
||||
|
||||
void ApiWrap::requestDialogEntry(not_null<Data::Folder*> folder) {
|
||||
if (_dialogFolderRequests.contains(folder)) {
|
||||
return;
|
||||
}
|
||||
_dialogFolderRequests.emplace(folder);
|
||||
|
||||
auto peers = QVector<MTPInputDialogPeer>(
|
||||
1,
|
||||
MTP_inputDialogPeerFolder(MTP_int(folder->id())));
|
||||
request(MTPmessages_GetPeerDialogs(
|
||||
MTP_vector(std::move(peers))
|
||||
)).done([=](const MTPmessages_PeerDialogs &result) {
|
||||
applyPeerDialogs(result);
|
||||
_dialogFolderRequests.remove(folder);
|
||||
}).fail([=](const RPCError &error) {
|
||||
_dialogFolderRequests.remove(folder);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::requestDialogEntry(
|
||||
not_null<History*> history,
|
||||
Fn<void()> callback) {
|
||||
const auto i = _dialogRequests.find(history);
|
||||
if (i != end(_dialogRequests)) {
|
||||
if (callback) {
|
||||
i->second.push_back(std::move(callback));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto [j, ok] = _dialogRequestsPending.try_emplace(history);
|
||||
if (callback) {
|
||||
j->second.push_back(std::move(callback));
|
||||
}
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
if (_dialogRequestsPending.size() > 1) {
|
||||
return;
|
||||
}
|
||||
Core::App().postponeCall(crl::guard(_session, [=] {
|
||||
sendDialogRequests();
|
||||
}));
|
||||
}
|
||||
|
||||
void ApiWrap::sendDialogRequests() {
|
||||
if (_dialogRequestsPending.empty()) {
|
||||
return;
|
||||
}
|
||||
auto histories = std::vector<not_null<History*>>();
|
||||
ranges::transform(
|
||||
_dialogRequestsPending,
|
||||
ranges::back_inserter(histories),
|
||||
[](const auto &pair) { return pair.first; });
|
||||
auto peers = QVector<MTPInputDialogPeer>();
|
||||
const auto dialogPeer = [](not_null<History*> history) {
|
||||
return MTP_inputDialogPeer(history->peer->input);
|
||||
};
|
||||
ranges::transform(
|
||||
histories,
|
||||
ranges::back_inserter(peers),
|
||||
dialogPeer);
|
||||
for (auto &[history, callbacks] : base::take(_dialogRequestsPending)) {
|
||||
_dialogRequests.emplace(history, std::move(callbacks));
|
||||
}
|
||||
|
||||
const auto finalize = [=] {
|
||||
for (const auto history : histories) {
|
||||
dialogEntryApplied(history);
|
||||
history->updateChatListExistence();
|
||||
}
|
||||
};
|
||||
request(MTPmessages_GetPeerDialogs(
|
||||
MTP_vector(std::move(peers))
|
||||
)).done([=](const MTPmessages_PeerDialogs &result) {
|
||||
applyPeerDialogs(result);
|
||||
finalize();
|
||||
}).fail([=](const RPCError &error) {
|
||||
finalize();
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::dialogEntryApplied(not_null<History*> history) {
|
||||
history->dialogEntryApplied();
|
||||
if (const auto callbacks = _dialogRequestsPending.take(history)) {
|
||||
for (const auto &callback : *callbacks) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
if (const auto callbacks = _dialogRequests.take(history)) {
|
||||
for (const auto &callback : *callbacks) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs) {
|
||||
Expects(dialogs.type() == mtpc_messages_peerDialogs);
|
||||
|
||||
const auto &data = dialogs.c_messages_peerDialogs();
|
||||
_session->data().processUsers(data.vusers());
|
||||
_session->data().processChats(data.vchats());
|
||||
_session->data().processMessages(data.vmessages(), NewMessageType::Last);
|
||||
for (const auto &dialog : data.vdialogs().v) {
|
||||
dialog.match([&](const MTPDdialog &data) {
|
||||
if (const auto peerId = peerFromMTP(data.vpeer())) {
|
||||
_session->data().history(peerId)->applyDialog(nullptr, data);
|
||||
}
|
||||
}, [&](const MTPDdialogFolder &data) {
|
||||
const auto folder = _session->data().processFolder(data.vfolder());
|
||||
folder->applyDialog(data);
|
||||
});
|
||||
}
|
||||
_session->data().sendHistoryChangeNotifications();
|
||||
}
|
||||
|
||||
void ApiWrap::changeDialogUnreadMark(
|
||||
not_null<History*> history,
|
||||
bool unread) {
|
||||
history->setUnreadMark(unread);
|
||||
|
||||
using Flag = MTPmessages_MarkDialogUnread::Flag;
|
||||
request(MTPmessages_MarkDialogUnread(
|
||||
MTP_flags(unread ? Flag::f_unread : Flag(0)),
|
||||
MTP_inputDialogPeer(history->peer->input)
|
||||
)).send();
|
||||
}
|
||||
|
||||
void ApiWrap::requestFakeChatListMessage(
|
||||
not_null<History*> history) {
|
||||
if (_fakeChatListRequests.contains(history)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_fakeChatListRequests.emplace(history);
|
||||
request(MTPmessages_GetHistory(
|
||||
history->peer->input,
|
||||
MTP_int(0), // offset_id
|
||||
MTP_int(0), // offset_date
|
||||
MTP_int(0), // add_offset
|
||||
MTP_int(2), // limit
|
||||
MTP_int(0), // max_id
|
||||
MTP_int(0), // min_id
|
||||
MTP_int(0)
|
||||
)).done([=](const MTPmessages_Messages &result) {
|
||||
_fakeChatListRequests.erase(history);
|
||||
history->setFakeChatListMessageFrom(result);
|
||||
}).fail([=](const RPCError &error) {
|
||||
_fakeChatListRequests.erase(history);
|
||||
history->setFakeChatListMessageFrom(MTP_messages_messages(
|
||||
MTP_vector<MTPMessage>(0),
|
||||
MTP_vector<MTPChat>(0),
|
||||
MTP_vector<MTPUser>(0)));
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::requestWallPaper(
|
||||
const QString &slug,
|
||||
Fn<void(const Data::WallPaper &)> done,
|
||||
@@ -1779,7 +1631,7 @@ void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
|
||||
history->checkLocalMessages();
|
||||
history->owner().sendHistoryChangeNotifications();
|
||||
} else {
|
||||
requestDialogEntry(history);
|
||||
history->owner().histories().requestDialogEntry(history);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -2415,44 +2267,7 @@ int ApiWrap::OnlineTillFromStatus(
|
||||
}
|
||||
|
||||
void ApiWrap::clearHistory(not_null<PeerData*> peer, bool revoke) {
|
||||
auto deleteTillId = MsgId(0);
|
||||
if (const auto history = _session->data().historyLoaded(peer)) {
|
||||
while (history->lastMessageKnown()) {
|
||||
const auto last = history->lastMessage();
|
||||
if (!last) {
|
||||
// History is empty.
|
||||
return;
|
||||
} else if (!IsServerMsgId(last->id)) {
|
||||
// Destroy client-side message locally.
|
||||
last->destroy();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!history->lastMessageKnown()) {
|
||||
requestDialogEntry(history, [=] {
|
||||
Expects(history->lastMessageKnown());
|
||||
|
||||
clearHistory(peer, revoke);
|
||||
});
|
||||
return;
|
||||
}
|
||||
deleteTillId = history->lastMessage()->id;
|
||||
history->clear(History::ClearType::ClearHistory);
|
||||
}
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
if (const auto migrated = peer->migrateFrom()) {
|
||||
clearHistory(migrated, revoke);
|
||||
}
|
||||
if (IsServerMsgId(deleteTillId)) {
|
||||
request(MTPchannels_DeleteHistory(
|
||||
channel->inputChannel,
|
||||
MTP_int(deleteTillId)
|
||||
)).send();
|
||||
}
|
||||
} else {
|
||||
deleteHistory(peer, true, revoke);
|
||||
}
|
||||
deleteHistory(peer, true, revoke);
|
||||
}
|
||||
|
||||
void ApiWrap::deleteConversation(not_null<PeerData*> peer, bool revoke) {
|
||||
@@ -2466,30 +2281,69 @@ void ApiWrap::deleteConversation(not_null<PeerData*> peer, bool revoke) {
|
||||
}).fail([=](const RPCError &error) {
|
||||
deleteHistory(peer, false, revoke);
|
||||
}).send();
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
channel->ptsWaitingForShortPoll(-1);
|
||||
leaveChannel(channel);
|
||||
} else {
|
||||
deleteHistory(peer, false, revoke);
|
||||
}
|
||||
_session->data().deleteConversationLocally(peer);
|
||||
}
|
||||
|
||||
void ApiWrap::deleteHistory(not_null<PeerData*> peer, bool justClear, bool revoke) {
|
||||
using Flag = MTPmessages_DeleteHistory::Flag;
|
||||
const auto flags = Flag(0)
|
||||
| (justClear ? Flag::f_just_clear : Flag(0))
|
||||
| ((peer->isUser() && revoke) ? Flag::f_revoke : Flag(0));
|
||||
request(MTPmessages_DeleteHistory(
|
||||
MTP_flags(flags),
|
||||
peer->input,
|
||||
MTP_int(0)
|
||||
)).done([=](const MTPmessages_AffectedHistory &result) {
|
||||
const auto offset = applyAffectedHistory(peer, result);
|
||||
if (offset > 0) {
|
||||
deleteHistory(peer, justClear, revoke);
|
||||
void ApiWrap::deleteHistory(
|
||||
not_null<PeerData*> peer,
|
||||
bool justClear,
|
||||
bool revoke) {
|
||||
auto deleteTillId = MsgId(0);
|
||||
const auto history = _session->data().history(peer);
|
||||
if (justClear) {
|
||||
// In case of clear history we need to know the last server message.
|
||||
while (history->lastMessageKnown()) {
|
||||
const auto last = history->lastMessage();
|
||||
if (!last) {
|
||||
// History is empty.
|
||||
return;
|
||||
} else if (!IsServerMsgId(last->id)) {
|
||||
// Destroy client-side message locally.
|
||||
last->destroy();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}).send();
|
||||
if (!history->lastMessageKnown()) {
|
||||
history->owner().histories().requestDialogEntry(history, [=] {
|
||||
Expects(history->lastMessageKnown());
|
||||
|
||||
deleteHistory(peer, justClear, revoke);
|
||||
});
|
||||
return;
|
||||
}
|
||||
deleteTillId = history->lastMessage()->id;
|
||||
}
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
if (!justClear) {
|
||||
channel->ptsWaitingForShortPoll(-1);
|
||||
leaveChannel(channel);
|
||||
} else {
|
||||
if (const auto migrated = peer->migrateFrom()) {
|
||||
deleteHistory(migrated, justClear, revoke);
|
||||
}
|
||||
if (IsServerMsgId(deleteTillId)) {
|
||||
history->owner().histories().deleteAllMessages(
|
||||
history,
|
||||
deleteTillId,
|
||||
justClear,
|
||||
revoke);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
history->owner().histories().deleteAllMessages(
|
||||
history,
|
||||
deleteTillId,
|
||||
justClear,
|
||||
revoke);
|
||||
}
|
||||
if (!justClear) {
|
||||
_session->data().deleteConversationLocally(peer);
|
||||
} else if (history) {
|
||||
history->clear(History::ClearType::ClearHistory);
|
||||
}
|
||||
}
|
||||
|
||||
int ApiWrap::applyAffectedHistory(
|
||||
@@ -2521,30 +2375,6 @@ void ApiWrap::applyAffectedMessages(
|
||||
App::main()->ptsUpdateAndApply(data.vpts().v, data.vpts_count().v);
|
||||
}
|
||||
|
||||
void ApiWrap::deleteMessages(
|
||||
not_null<PeerData*> peer,
|
||||
const QVector<MTPint> &ids,
|
||||
bool revoke) {
|
||||
const auto done = [=](const MTPmessages_AffectedMessages & result) {
|
||||
applyAffectedMessages(peer, result);
|
||||
if (const auto history = peer->owner().historyLoaded(peer)) {
|
||||
history->requestChatListMessage();
|
||||
}
|
||||
};
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
request(MTPchannels_DeleteMessages(
|
||||
channel->inputChannel,
|
||||
MTP_vector<MTPint>(ids)
|
||||
)).done(done).send();
|
||||
} else {
|
||||
using Flag = MTPmessages_DeleteMessages::Flag;
|
||||
request(MTPmessages_DeleteMessages(
|
||||
MTP_flags(revoke ? Flag::f_revoke : Flag(0)),
|
||||
MTP_vector<MTPint>(ids)
|
||||
)).done(done).send();
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::saveDraftsToCloud() {
|
||||
for (auto i = _draftsSaveRequestIds.begin(), e = _draftsSaveRequestIds.end(); i != e; ++i) {
|
||||
if (i->second) continue; // sent already
|
||||
@@ -3652,10 +3482,22 @@ void ApiWrap::applyUpdateNoPtsCheck(const MTPUpdate &update) {
|
||||
auto &d = update.c_updateNewMessage();
|
||||
auto needToAdd = true;
|
||||
if (d.vmessage().type() == mtpc_message) { // index forwarded messages to links _overview
|
||||
if (_session->data().checkEntitiesAndViewsUpdate(d.vmessage().c_message())) { // already in blocks
|
||||
const auto &data = d.vmessage().c_message();
|
||||
if (_session->data().checkEntitiesAndViewsUpdate(data)) { // already in blocks
|
||||
LOG(("Skipping message, because it is already in blocks!"));
|
||||
needToAdd = false;
|
||||
}
|
||||
if (needToAdd && !data.is_from_scheduled()) {
|
||||
// If we still need to add a new message,
|
||||
// we should first check if this message is in
|
||||
// the list of scheduled messages.
|
||||
// This is necessary to correctly update the file reference.
|
||||
// Note that when a message is scheduled until online
|
||||
// while the recipient is already online, the server sends
|
||||
// an ordinary new message with skipped "from_scheduled" flag.
|
||||
_session->data().scheduledMessages().checkEntitiesAndUpdate(
|
||||
data);
|
||||
}
|
||||
}
|
||||
if (needToAdd) {
|
||||
_session->data().addNewMessage(
|
||||
@@ -4038,12 +3880,12 @@ void ApiWrap::requestSharedMedia(
|
||||
SharedMediaType type,
|
||||
MsgId messageId,
|
||||
SliceType slice) {
|
||||
auto key = std::make_tuple(peer, type, messageId, slice);
|
||||
const auto key = std::make_tuple(peer, type, messageId, slice);
|
||||
if (_sharedMediaRequests.contains(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto prepared = Api::PrepareSearchRequest(
|
||||
const auto prepared = Api::PrepareSearchRequest(
|
||||
peer,
|
||||
type,
|
||||
QString(),
|
||||
@@ -4053,17 +3895,23 @@ void ApiWrap::requestSharedMedia(
|
||||
return;
|
||||
}
|
||||
|
||||
auto requestId = request(
|
||||
std::move(*prepared)
|
||||
).done([this, peer, type, messageId, slice](
|
||||
const MTPmessages_Messages &result) {
|
||||
auto key = std::make_tuple(peer, type, messageId, slice);
|
||||
_sharedMediaRequests.remove(key);
|
||||
sharedMediaDone(peer, type, messageId, slice, result);
|
||||
}).fail([this, key](const RPCError &error) {
|
||||
_sharedMediaRequests.remove(key);
|
||||
}).send();
|
||||
_sharedMediaRequests.emplace(key, requestId);
|
||||
const auto history = session().data().history(peer);
|
||||
auto &histories = history->owner().histories();
|
||||
const auto requestType = Data::Histories::RequestType::History;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
return request(
|
||||
std::move(*prepared)
|
||||
).done([=](const MTPmessages_Messages &result) {
|
||||
const auto key = std::make_tuple(peer, type, messageId, slice);
|
||||
_sharedMediaRequests.remove(key);
|
||||
sharedMediaDone(peer, type, messageId, slice, result);
|
||||
finish();
|
||||
}).fail([=](const RPCError &error) {
|
||||
_sharedMediaRequests.remove(key);
|
||||
finish();
|
||||
}).send();
|
||||
});
|
||||
_sharedMediaRequests.emplace(key);
|
||||
}
|
||||
|
||||
void ApiWrap::sharedMediaDone(
|
||||
@@ -4413,7 +4261,7 @@ void ApiWrap::userPhotosDone(
|
||||
//}
|
||||
|
||||
void ApiWrap::sendAction(const SendAction &action) {
|
||||
readServerHistory(action.history);
|
||||
session().data().histories().readInbox(action.history);
|
||||
action.history->getReadyFor(ShowAtTheEndMsgId);
|
||||
_sendActions.fire_copy(action);
|
||||
}
|
||||
@@ -4424,6 +4272,8 @@ void ApiWrap::forwardMessages(
|
||||
FnMut<void()> &&successCallback) {
|
||||
Expects(!items.empty());
|
||||
|
||||
auto &histories = session().data().histories();
|
||||
|
||||
struct SharedCallback {
|
||||
int requestsLeft = 0;
|
||||
FnMut<void()> callback;
|
||||
@@ -4440,7 +4290,7 @@ void ApiWrap::forwardMessages(
|
||||
const auto history = action.history;
|
||||
const auto peer = history->peer;
|
||||
|
||||
readServerHistory(history);
|
||||
histories.readInbox(history);
|
||||
|
||||
const auto channelPost = peer->isChannel() && !peer->isMegagroup();
|
||||
const auto silentPost = action.options.silent
|
||||
@@ -4472,7 +4322,7 @@ void ApiWrap::forwardMessages(
|
||||
auto currentGroupId = items.front()->groupId();
|
||||
auto ids = QVector<MTPint>();
|
||||
auto randomIds = QVector<MTPlong>();
|
||||
auto localIds = std::unique_ptr<base::flat_map<uint64, FullMsgId>>();
|
||||
auto localIds = std::shared_ptr<base::flat_map<uint64, FullMsgId>>();
|
||||
|
||||
const auto sendAccumulated = [&] {
|
||||
if (shared) {
|
||||
@@ -4482,30 +4332,36 @@ void ApiWrap::forwardMessages(
|
||||
| (currentGroupId == MessageGroupId()
|
||||
? MTPmessages_ForwardMessages::Flag(0)
|
||||
: MTPmessages_ForwardMessages::Flag::f_grouped);
|
||||
history->sendRequestId = request(MTPmessages_ForwardMessages(
|
||||
MTP_flags(finalFlags),
|
||||
forwardFrom->input,
|
||||
MTP_vector<MTPint>(ids),
|
||||
MTP_vector<MTPlong>(randomIds),
|
||||
peer->input,
|
||||
MTP_int(action.options.scheduled)
|
||||
)).done([=, callback = std::move(successCallback)](
|
||||
const MTPUpdates &updates) {
|
||||
applyUpdates(updates);
|
||||
if (shared && !--shared->requestsLeft) {
|
||||
shared->callback();
|
||||
}
|
||||
}).fail([=, ids = std::move(localIds)](const RPCError &error) {
|
||||
if (ids) {
|
||||
for (const auto &[randomId, itemId] : *ids) {
|
||||
sendMessageFail(error, peer, randomId, itemId);
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
history->sendRequestId = request(MTPmessages_ForwardMessages(
|
||||
MTP_flags(finalFlags),
|
||||
forwardFrom->input,
|
||||
MTP_vector<MTPint>(ids),
|
||||
MTP_vector<MTPlong>(randomIds),
|
||||
peer->input,
|
||||
MTP_int(action.options.scheduled)
|
||||
)).done([=](
|
||||
const MTPUpdates &updates) {
|
||||
applyUpdates(updates);
|
||||
if (shared && !--shared->requestsLeft) {
|
||||
shared->callback();
|
||||
}
|
||||
} else {
|
||||
sendMessageFail(error, peer);
|
||||
}
|
||||
}).afterRequest(
|
||||
history->sendRequestId
|
||||
).send();
|
||||
finish();
|
||||
}).fail([=, ids = localIds](const RPCError &error) {
|
||||
if (ids) {
|
||||
for (const auto &[randomId, itemId] : *ids) {
|
||||
sendMessageFail(error, peer, randomId, itemId);
|
||||
}
|
||||
} else {
|
||||
sendMessageFail(error, peer);
|
||||
}
|
||||
finish();
|
||||
}).afterRequest(
|
||||
history->sendRequestId
|
||||
).send();
|
||||
return history->sendRequestId;
|
||||
});
|
||||
|
||||
ids.resize(0);
|
||||
randomIds.resize(0);
|
||||
@@ -4538,7 +4394,7 @@ void ApiWrap::forwardMessages(
|
||||
message);
|
||||
_session->data().registerMessageRandomId(randomId, newId);
|
||||
if (!localIds) {
|
||||
localIds = std::make_unique<base::flat_map<uint64, FullMsgId>>();
|
||||
localIds = std::make_shared<base::flat_map<uint64, FullMsgId>>();
|
||||
}
|
||||
localIds->emplace(randomId, newId);
|
||||
}
|
||||
@@ -4953,6 +4809,9 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
|
||||
HistoryItem *lastMessage = nullptr;
|
||||
|
||||
auto &histories = history->owner().histories();
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
|
||||
while (TextUtilities::CutPart(sending, left, MaxMessageSize)) {
|
||||
auto newId = FullMsgId(
|
||||
peerToChannel(peer->id),
|
||||
@@ -5043,27 +4902,32 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
MTPVector<MTPRestrictionReason>()),
|
||||
clientFlags,
|
||||
NewMessageType::Unread);
|
||||
history->sendRequestId = request(MTPmessages_SendMessage(
|
||||
MTP_flags(sendFlags),
|
||||
peer->input,
|
||||
MTP_int(action.replyTo),
|
||||
msgText,
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(action.options.scheduled)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result, randomId);
|
||||
history->clearSentDraftText(QString());
|
||||
}).fail([=](const RPCError &error) {
|
||||
if (error.type() == qstr("MESSAGE_EMPTY")) {
|
||||
lastMessage->destroy();
|
||||
} else {
|
||||
sendMessageFail(error, peer, randomId, newId);
|
||||
}
|
||||
history->clearSentDraftText(QString());
|
||||
}).afterRequest(history->sendRequestId
|
||||
).send();
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
history->sendRequestId = request(MTPmessages_SendMessage(
|
||||
MTP_flags(sendFlags),
|
||||
peer->input,
|
||||
MTP_int(action.replyTo),
|
||||
msgText,
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(action.options.scheduled)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result, randomId);
|
||||
history->clearSentDraftText(QString());
|
||||
finish();
|
||||
}).fail([=](const RPCError &error) {
|
||||
if (error.type() == qstr("MESSAGE_EMPTY")) {
|
||||
lastMessage->destroy();
|
||||
} else {
|
||||
sendMessageFail(error, peer, randomId, newId);
|
||||
}
|
||||
history->clearSentDraftText(QString());
|
||||
finish();
|
||||
}).afterRequest(history->sendRequestId
|
||||
).send();
|
||||
return history->sendRequestId;
|
||||
});
|
||||
}
|
||||
|
||||
if (const auto main = App::main()) {
|
||||
@@ -5169,23 +5033,29 @@ void ApiWrap::sendInlineResult(
|
||||
history->clearCloudDraft();
|
||||
history->setSentDraftText(QString());
|
||||
|
||||
history->sendRequestId = request(MTPmessages_SendInlineBotResult(
|
||||
MTP_flags(sendFlags),
|
||||
peer->input,
|
||||
MTP_int(action.replyTo),
|
||||
MTP_long(randomId),
|
||||
MTP_long(data->getQueryId()),
|
||||
MTP_string(data->getId()),
|
||||
MTP_int(action.options.scheduled)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result, randomId);
|
||||
history->clearSentDraftText(QString());
|
||||
}).fail([=](const RPCError &error) {
|
||||
sendMessageFail(error, peer, randomId, newId);
|
||||
history->clearSentDraftText(QString());
|
||||
}).afterRequest(history->sendRequestId
|
||||
).send();
|
||||
|
||||
auto &histories = history->owner().histories();
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
history->sendRequestId = request(MTPmessages_SendInlineBotResult(
|
||||
MTP_flags(sendFlags),
|
||||
peer->input,
|
||||
MTP_int(action.replyTo),
|
||||
MTP_long(randomId),
|
||||
MTP_long(data->getQueryId()),
|
||||
MTP_string(data->getId()),
|
||||
MTP_int(action.options.scheduled)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result, randomId);
|
||||
history->clearSentDraftText(QString());
|
||||
finish();
|
||||
}).fail([=](const RPCError &error) {
|
||||
sendMessageFail(error, peer, randomId, newId);
|
||||
history->clearSentDraftText(QString());
|
||||
finish();
|
||||
}).afterRequest(history->sendRequestId
|
||||
).send();
|
||||
return history->sendRequestId;
|
||||
});
|
||||
if (const auto main = App::main()) {
|
||||
main->finishForwarding(action);
|
||||
}
|
||||
@@ -5304,25 +5174,32 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
? MTPmessages_SendMedia::Flag::f_schedule_date
|
||||
: MTPmessages_SendMedia::Flag(0));
|
||||
|
||||
const auto peer = history->peer;
|
||||
const auto itemId = item->fullId();
|
||||
history->sendRequestId = request(MTPmessages_SendMedia(
|
||||
MTP_flags(flags),
|
||||
peer->input,
|
||||
MTP_int(replyTo),
|
||||
media,
|
||||
MTP_string(caption.text),
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(options.scheduled)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
}).fail([=](const RPCError &error) {
|
||||
sendMessageFail(error, peer, randomId, itemId);
|
||||
}).afterRequest(
|
||||
history->sendRequestId
|
||||
).send();
|
||||
auto &histories = history->owner().histories();
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
const auto peer = history->peer;
|
||||
const auto itemId = item->fullId();
|
||||
history->sendRequestId = request(MTPmessages_SendMedia(
|
||||
MTP_flags(flags),
|
||||
peer->input,
|
||||
MTP_int(replyTo),
|
||||
media,
|
||||
MTP_string(caption.text),
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(options.scheduled)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
finish();
|
||||
}).fail([=](const RPCError &error) {
|
||||
sendMessageFail(error, peer, randomId, itemId);
|
||||
finish();
|
||||
}).afterRequest(
|
||||
history->sendRequestId
|
||||
).send();
|
||||
return history->sendRequestId;
|
||||
});
|
||||
}
|
||||
|
||||
void ApiWrap::sendAlbumWithUploaded(
|
||||
@@ -5400,27 +5277,34 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
| (album->options.scheduled
|
||||
? MTPmessages_SendMultiMedia::Flag::f_schedule_date
|
||||
: MTPmessages_SendMultiMedia::Flag(0));
|
||||
const auto peer = history->peer;
|
||||
history->sendRequestId = request(MTPmessages_SendMultiMedia(
|
||||
MTP_flags(flags),
|
||||
peer->input,
|
||||
MTP_int(replyTo),
|
||||
MTP_vector<MTPInputSingleMedia>(medias),
|
||||
MTP_int(album->options.scheduled)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_sendingAlbums.remove(groupId);
|
||||
applyUpdates(result);
|
||||
}).fail([=](const RPCError &error) {
|
||||
if (const auto album = _sendingAlbums.take(groupId)) {
|
||||
for (const auto &item : (*album)->items) {
|
||||
sendMessageFail(error, peer, item.randomId, item.msgId);
|
||||
auto &histories = history->owner().histories();
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
const auto peer = history->peer;
|
||||
history->sendRequestId = request(MTPmessages_SendMultiMedia(
|
||||
MTP_flags(flags),
|
||||
peer->input,
|
||||
MTP_int(replyTo),
|
||||
MTP_vector<MTPInputSingleMedia>(medias),
|
||||
MTP_int(album->options.scheduled)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_sendingAlbums.remove(groupId);
|
||||
applyUpdates(result);
|
||||
finish();
|
||||
}).fail([=](const RPCError &error) {
|
||||
if (const auto album = _sendingAlbums.take(groupId)) {
|
||||
for (const auto &item : (*album)->items) {
|
||||
sendMessageFail(error, peer, item.randomId, item.msgId);
|
||||
}
|
||||
} else {
|
||||
sendMessageFail(error, peer);
|
||||
}
|
||||
} else {
|
||||
sendMessageFail(error, peer);
|
||||
}
|
||||
}).afterRequest(
|
||||
history->sendRequestId
|
||||
).send();
|
||||
finish();
|
||||
}).afterRequest(
|
||||
history->sendRequestId
|
||||
).send();
|
||||
return history->sendRequestId;
|
||||
});
|
||||
}
|
||||
|
||||
FileLoadTo ApiWrap::fileLoadTaskOptions(const SendAction &action) const {
|
||||
@@ -5817,8 +5701,8 @@ Api::SensitiveContent &ApiWrap::sensitiveContent() {
|
||||
void ApiWrap::createPoll(
|
||||
const PollData &data,
|
||||
const SendAction &action,
|
||||
FnMut<void()> done,
|
||||
FnMut<void(const RPCError &error)> fail) {
|
||||
Fn<void()> done,
|
||||
Fn<void(const RPCError &error)> fail) {
|
||||
sendAction(action);
|
||||
|
||||
const auto history = action.history;
|
||||
@@ -5842,24 +5726,43 @@ void ApiWrap::createPoll(
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||
}
|
||||
|
||||
const auto replyTo = action.replyTo;
|
||||
history->sendRequestId = request(MTPmessages_SendMedia(
|
||||
MTP_flags(sendFlags),
|
||||
peer->input,
|
||||
MTP_int(replyTo),
|
||||
MTP_inputMediaPoll(PollDataToMTP(&data)),
|
||||
MTP_string(),
|
||||
MTP_long(rand_value<uint64>()),
|
||||
MTPReplyMarkup(),
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
MTP_int(action.options.scheduled)
|
||||
)).done([=, done = std::move(done)](const MTPUpdates &result) mutable {
|
||||
applyUpdates(result);
|
||||
done();
|
||||
}).fail([=, fail = std::move(fail)](const RPCError &error) mutable {
|
||||
fail(error);
|
||||
}).afterRequest(history->sendRequestId
|
||||
).send();
|
||||
const auto inputFlags = data.quiz()
|
||||
? MTPDinputMediaPoll::Flag::f_correct_answers
|
||||
: MTPDinputMediaPoll::Flag(0);
|
||||
auto correct = QVector<MTPbytes>();
|
||||
for (const auto &answer : data.answers) {
|
||||
if (answer.correct) {
|
||||
correct.push_back(MTP_bytes(answer.option));
|
||||
}
|
||||
}
|
||||
auto &histories = history->owner().histories();
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
const auto replyTo = action.replyTo;
|
||||
history->sendRequestId = request(MTPmessages_SendMedia(
|
||||
MTP_flags(sendFlags),
|
||||
peer->input,
|
||||
MTP_int(replyTo),
|
||||
MTP_inputMediaPoll(
|
||||
MTP_flags(inputFlags),
|
||||
PollDataToMTP(&data),
|
||||
MTP_vector<MTPbytes>(correct)),
|
||||
MTP_string(),
|
||||
MTP_long(rand_value<uint64>()),
|
||||
MTPReplyMarkup(),
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
MTP_int(action.options.scheduled)
|
||||
)).done([=](const MTPUpdates &result) mutable {
|
||||
applyUpdates(result);
|
||||
done();
|
||||
finish();
|
||||
}).fail([=](const RPCError &error) mutable {
|
||||
fail(error);
|
||||
finish();
|
||||
}).afterRequest(history->sendRequestId
|
||||
).send();
|
||||
return history->sendRequestId;
|
||||
});
|
||||
}
|
||||
|
||||
void ApiWrap::sendPollVotes(
|
||||
@@ -5879,13 +5782,13 @@ void ApiWrap::sendPollVotes(
|
||||
const auto hideSending = [=] {
|
||||
if (showSending) {
|
||||
if (const auto item = _session->data().message(itemId)) {
|
||||
poll->sendingVote = QByteArray();
|
||||
poll->sendingVotes.clear();
|
||||
_session->data().requestItemRepaint(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (showSending) {
|
||||
poll->sendingVote = options.front();
|
||||
poll->sendingVotes = options;
|
||||
_session->data().requestItemRepaint(item);
|
||||
}
|
||||
|
||||
@@ -5921,12 +5824,24 @@ void ApiWrap::closePoll(not_null<HistoryItem*> item) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto inputFlags = poll->quiz()
|
||||
? MTPDinputMediaPoll::Flag::f_correct_answers
|
||||
: MTPDinputMediaPoll::Flag(0);
|
||||
auto correct = QVector<MTPbytes>();
|
||||
for (const auto &answer : poll->answers) {
|
||||
if (answer.correct) {
|
||||
correct.push_back(MTP_bytes(answer.option));
|
||||
}
|
||||
}
|
||||
const auto requestId = request(MTPmessages_EditMessage(
|
||||
MTP_flags(MTPmessages_EditMessage::Flag::f_media),
|
||||
item->history()->peer->input,
|
||||
MTP_int(item->id),
|
||||
MTPstring(),
|
||||
MTP_inputMediaPoll(PollDataToMTP(poll)),
|
||||
MTP_inputMediaPoll(
|
||||
MTP_flags(inputFlags),
|
||||
PollDataToMTP(poll, true),
|
||||
MTP_vector<MTPbytes>(correct)),
|
||||
MTPReplyMarkup(),
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
MTP_int(0) // schedule_date
|
||||
@@ -5957,43 +5872,6 @@ void ApiWrap::reloadPollResults(not_null<HistoryItem*> item) {
|
||||
_pollReloadRequestIds.emplace(itemId, requestId);
|
||||
}
|
||||
|
||||
void ApiWrap::readServerHistory(not_null<History*> history) {
|
||||
if (history->unreadCount()) {
|
||||
readServerHistoryForce(history);
|
||||
}
|
||||
if (history->unreadMark()) {
|
||||
changeDialogUnreadMark(history, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::readServerHistoryForce(not_null<History*> history) {
|
||||
const auto peer = history->peer;
|
||||
const auto upTo = history->readInbox();
|
||||
if (!upTo) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
if (!channel->amIn()) {
|
||||
return; // no read request for channels that I didn't join
|
||||
} else if (const auto migrateFrom = channel->migrateFrom()) {
|
||||
if (const auto migrated = _session->data().historyLoaded(migrateFrom)) {
|
||||
readServerHistory(migrated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_readRequests.contains(peer)) {
|
||||
const auto i = _readRequestsPending.find(peer);
|
||||
if (i == _readRequestsPending.cend()) {
|
||||
_readRequestsPending.emplace(peer, upTo);
|
||||
} else if (i->second < upTo) {
|
||||
i->second = upTo;
|
||||
}
|
||||
} else {
|
||||
sendReadRequest(peer, upTo);
|
||||
}
|
||||
}
|
||||
// // #feed
|
||||
//void ApiWrap::readFeed(
|
||||
// not_null<Data::Feed*> feed,
|
||||
@@ -6047,39 +5925,3 @@ void ApiWrap::readServerHistoryForce(not_null<History*> history) {
|
||||
// _feedReadTimer.callOnce(delay);
|
||||
// }
|
||||
//}
|
||||
|
||||
void ApiWrap::sendReadRequest(not_null<PeerData*> peer, MsgId upTo) {
|
||||
const auto requestId = [&] {
|
||||
const auto finished = [=] {
|
||||
_readRequests.remove(peer);
|
||||
if (const auto next = _readRequestsPending.take(peer)) {
|
||||
sendReadRequest(peer, *next);
|
||||
} else if (const auto history
|
||||
= _session->data().historyLoaded(peer)) {
|
||||
if (!history->unreadCountKnown()) {
|
||||
requestDialogEntry(history);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
return request(MTPchannels_ReadHistory(
|
||||
channel->inputChannel,
|
||||
MTP_int(upTo)
|
||||
)).done([=](const MTPBool &result) {
|
||||
finished();
|
||||
}).fail([=](const RPCError &error) {
|
||||
finished();
|
||||
}).send();
|
||||
}
|
||||
return request(MTPmessages_ReadHistory(
|
||||
peer->input,
|
||||
MTP_int(upTo)
|
||||
)).done([=](const MTPmessages_AffectedMessages &result) {
|
||||
applyAffectedMessages(peer, result);
|
||||
finished();
|
||||
}).fail([=](const RPCError &error) {
|
||||
finished();
|
||||
}).send();
|
||||
}();
|
||||
_readRequests.emplace(peer, requestId, upTo);
|
||||
}
|
||||
|
||||
@@ -141,6 +141,9 @@ public:
|
||||
void applyUpdates(
|
||||
const MTPUpdates &updates,
|
||||
uint64 sentMessageRandomId = 0);
|
||||
int applyAffectedHistory(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPmessages_AffectedHistory &result);
|
||||
|
||||
void registerModifyRequest(const QString &key, mtpRequestId requestId);
|
||||
void clearModifyRequest(const QString &key);
|
||||
@@ -170,18 +173,10 @@ public:
|
||||
rpl::producer<bool> dialogsLoadMayBlockByDate() const;
|
||||
rpl::producer<bool> dialogsLoadBlockedByDate() const;
|
||||
|
||||
void requestDialogEntry(not_null<Data::Folder*> folder);
|
||||
void requestDialogEntry(
|
||||
not_null<History*> history,
|
||||
Fn<void()> callback = nullptr);
|
||||
void dialogEntryApplied(not_null<History*> history);
|
||||
//void applyFeedSources(const MTPDchannels_feedSources &data); // #feed
|
||||
//void setFeedChannels(
|
||||
// not_null<Data::Feed*> feed,
|
||||
// const std::vector<not_null<ChannelData*>> &channels);
|
||||
void changeDialogUnreadMark(not_null<History*> history, bool unread);
|
||||
//void changeDialogUnreadMark(not_null<Data::Feed*> feed, bool unread); // #feed
|
||||
void requestFakeChatListMessage(not_null<History*> history);
|
||||
|
||||
void requestWallPaper(
|
||||
const QString &slug,
|
||||
@@ -305,10 +300,6 @@ public:
|
||||
|
||||
void clearHistory(not_null<PeerData*> peer, bool revoke);
|
||||
void deleteConversation(not_null<PeerData*> peer, bool revoke);
|
||||
void deleteMessages(
|
||||
not_null<PeerData*> peer,
|
||||
const QVector<MTPint> &ids,
|
||||
bool revoke);
|
||||
|
||||
base::Observable<PeerData*> &fullPeerUpdated() {
|
||||
return _fullPeerUpdated;
|
||||
@@ -389,11 +380,12 @@ public:
|
||||
const QString &lastName,
|
||||
const SendAction &action);
|
||||
void shareContact(not_null<UserData*> user, const SendAction &action);
|
||||
void readServerHistory(not_null<History*> history);
|
||||
void readServerHistoryForce(not_null<History*> history);
|
||||
//void readFeed( // #feed
|
||||
// not_null<Data::Feed*> feed,
|
||||
// Data::MessagePosition position);
|
||||
void applyAffectedMessages(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPmessages_AffectedMessages &result);
|
||||
|
||||
void sendVoiceMessage(
|
||||
QByteArray result,
|
||||
@@ -475,8 +467,8 @@ public:
|
||||
void createPoll(
|
||||
const PollData &data,
|
||||
const SendAction &action,
|
||||
FnMut<void()> done,
|
||||
FnMut<void(const RPCError &error)> fail);
|
||||
Fn<void()> done,
|
||||
Fn<void(const RPCError &error)> fail);
|
||||
void sendPollVotes(
|
||||
FullMsgId itemId,
|
||||
const std::vector<QByteArray> &options);
|
||||
@@ -531,7 +523,6 @@ private:
|
||||
|
||||
QVector<MTPInputMessage> collectMessageIds(const MessageDataRequests &requests);
|
||||
MessageDataRequests *messageDataRequests(ChannelData *channel, bool onlyExisting = false);
|
||||
void applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs);
|
||||
|
||||
void gotChatFull(
|
||||
not_null<PeerData*> peer,
|
||||
@@ -623,14 +614,7 @@ private:
|
||||
not_null<PeerData*> peer,
|
||||
bool justClear,
|
||||
bool revoke);
|
||||
void sendReadRequest(not_null<PeerData*> peer, MsgId upTo);
|
||||
int applyAffectedHistory(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPmessages_AffectedHistory &result);
|
||||
void applyAffectedMessages(const MTPmessages_AffectedMessages &result);
|
||||
void applyAffectedMessages(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPmessages_AffectedMessages &result);
|
||||
|
||||
void deleteAllFromUserSend(
|
||||
not_null<ChannelData*> channel,
|
||||
@@ -685,8 +669,6 @@ private:
|
||||
not_null<ChannelData*> channel);
|
||||
void migrateFail(not_null<PeerData*> peer, const RPCError &error);
|
||||
|
||||
void sendDialogRequests();
|
||||
|
||||
not_null<Main::Session*> _session;
|
||||
|
||||
base::flat_map<QString, int> _modifyRequests;
|
||||
@@ -756,22 +738,14 @@ private:
|
||||
|
||||
mtpRequestId _contactsRequestId = 0;
|
||||
mtpRequestId _contactsStatusesRequestId = 0;
|
||||
base::flat_set<not_null<Data::Folder*>> _dialogFolderRequests;
|
||||
base::flat_map<
|
||||
not_null<History*>,
|
||||
std::vector<Fn<void()>>> _dialogRequests;
|
||||
base::flat_map<
|
||||
not_null<History*>,
|
||||
std::vector<Fn<void()>>> _dialogRequestsPending;
|
||||
base::flat_set<not_null<History*>> _fakeChatListRequests;
|
||||
|
||||
base::flat_map<not_null<History*>, mtpRequestId> _unreadMentionsRequests;
|
||||
|
||||
base::flat_map<std::tuple<
|
||||
base::flat_set<std::tuple<
|
||||
not_null<PeerData*>,
|
||||
SharedMediaType,
|
||||
MsgId,
|
||||
SliceType>, mtpRequestId> _sharedMediaRequests;
|
||||
SliceType>> _sharedMediaRequests;
|
||||
|
||||
base::flat_map<not_null<UserData*>, mtpRequestId> _userPhotosRequests;
|
||||
|
||||
@@ -800,18 +774,6 @@ private:
|
||||
|
||||
rpl::event_stream<SendAction> _sendActions;
|
||||
|
||||
struct ReadRequest {
|
||||
ReadRequest(mtpRequestId requestId, MsgId upTo)
|
||||
: requestId(requestId)
|
||||
, upTo(upTo) {
|
||||
}
|
||||
|
||||
mtpRequestId requestId = 0;
|
||||
MsgId upTo = 0;
|
||||
};
|
||||
base::flat_map<not_null<PeerData*>, ReadRequest> _readRequests;
|
||||
base::flat_map<not_null<PeerData*>, MsgId> _readRequestsPending;
|
||||
|
||||
std::unique_ptr<TaskQueue> _fileLoader;
|
||||
base::flat_map<uint64, std::shared_ptr<SendingAlbum>> _sendingAlbums;
|
||||
|
||||
|
||||
@@ -41,8 +41,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "numbers.h"
|
||||
#include "observer_peer.h"
|
||||
#include "main/main_session.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_overview.h"
|
||||
#include "styles/style_mediaview.h"
|
||||
#include "styles/style_media_view.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_history.h"
|
||||
#include "styles/style_layers.h"
|
||||
@@ -180,6 +181,8 @@ namespace App {
|
||||
prepareCorners(MessageInSelectedCorners, st::historyMessageRadius, st::msgInBgSelected, &st::msgInShadowSelected);
|
||||
prepareCorners(MessageOutCorners, st::historyMessageRadius, st::msgOutBg, &st::msgOutShadow);
|
||||
prepareCorners(MessageOutSelectedCorners, st::historyMessageRadius, st::msgOutBgSelected, &st::msgOutShadowSelected);
|
||||
|
||||
prepareCorners(SendFilesBoxAlbumGroupCorners, st::sendBoxAlbumGroupRadius, st::callFingerprintBg);
|
||||
}
|
||||
|
||||
void createCorners() {
|
||||
|
||||
@@ -63,6 +63,8 @@ enum RoundCorners : int {
|
||||
MessageOutCorners,
|
||||
MessageOutSelectedCorners,
|
||||
|
||||
SendFilesBoxAlbumGroupCorners,
|
||||
|
||||
RoundCornersCount
|
||||
};
|
||||
|
||||
|
||||
@@ -494,6 +494,7 @@ void GroupInfoBox::prepare() {
|
||||
_description->setInstantReplaces(Ui::InstantReplaces::Default());
|
||||
_description->setInstantReplacesEnabled(
|
||||
_navigation->session().settings().replaceEmojiValue());
|
||||
_description->setSubmitSettings(_navigation->session().settings().sendSubmitWay());
|
||||
|
||||
connect(_description, &Ui::InputField::resized, [=] { descriptionResized(); });
|
||||
connect(_description, &Ui::InputField::submitted, [=] { submit(); });
|
||||
|
||||
@@ -293,8 +293,7 @@ AdminLog::OwnedItem GenerateTextItem(
|
||||
const auto clientFlags = MTPDmessage_ClientFlag::f_fake_history_item;
|
||||
const auto replyTo = 0;
|
||||
const auto viaBotId = 0;
|
||||
const auto item = history->owner().makeMessage(
|
||||
history,
|
||||
const auto item = history->makeMessage(
|
||||
++id,
|
||||
flags,
|
||||
clientFlags,
|
||||
|
||||
@@ -514,6 +514,29 @@ editMediaButton: IconButton {
|
||||
ripple: defaultRippleAnimation;
|
||||
}
|
||||
|
||||
// SendFilesBox
|
||||
|
||||
sendBoxAlbumGroupEditInternalSkip: 9px;
|
||||
sendBoxAlbumGroupSkipRight: 6px;
|
||||
sendBoxAlbumGroupSkipTop: 6px;
|
||||
sendBoxAlbumGroupRadius: 12px;
|
||||
sendBoxAlbumGroupHeight: 25px;
|
||||
|
||||
sendBoxAlbumGroupEditButtonIcon: editMediaButtonIconPhoto;
|
||||
sendBoxAlbumGroupEditButtonIconPosition: point(4px, -1px);
|
||||
|
||||
sendBoxAlbumGroupButtonFile: IconButton(editMediaButton) {
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgRipple;
|
||||
}
|
||||
}
|
||||
|
||||
sendBoxAlbumGroupDeleteButtonIconPosition: point(-3px, 0px);
|
||||
sendBoxAlbumGroupDeleteButtonIcon: icon {{ "history_file_cancel", msgServiceFg}};
|
||||
sendBoxAlbumGroupDeleteButtonIconFile: icon {{ "history_file_cancel", menuIconFg, point(6px, 6px) }};
|
||||
|
||||
// End of SendFilesBox
|
||||
|
||||
calendarTitleHeight: boxTitleHeight;
|
||||
calendarPrevious: IconButton {
|
||||
width: calendarTitleHeight;
|
||||
@@ -812,15 +835,15 @@ createPollField: InputField(defaultInputField) {
|
||||
}
|
||||
createPollFieldPadding: margins(22px, 5px, 22px, 5px);
|
||||
createPollOptionField: InputField(createPollField) {
|
||||
textMargins: margins(22px, 8px, 40px, 8px);
|
||||
textMargins: margins(22px, 11px, 40px, 11px);
|
||||
placeholderMargins: margins(2px, 0px, 2px, 0px);
|
||||
heightMax: 64px;
|
||||
heightMax: 68px;
|
||||
}
|
||||
createPollLimitLabel: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 274px;
|
||||
align: align(topleft);
|
||||
}
|
||||
createPollLimitPadding: margins(22px, 10px, 22px, 5px);
|
||||
createPollLimitPadding: margins(22px, 10px, 22px, 16px);
|
||||
createPollOptionRemove: CrossButton {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
@@ -841,7 +864,7 @@ createPollOptionRemove: CrossButton {
|
||||
color: windowBgOver;
|
||||
}
|
||||
}
|
||||
createPollOptionRemovePosition: point(10px, 7px);
|
||||
createPollOptionRemovePosition: point(11px, 9px);
|
||||
createPollWarning: FlatLabel(defaultFlatLabel) {
|
||||
textFg: windowSubTextFg;
|
||||
palette: TextPalette(defaultTextPalette) {
|
||||
@@ -849,6 +872,8 @@ createPollWarning: FlatLabel(defaultFlatLabel) {
|
||||
}
|
||||
}
|
||||
createPollWarningPosition: point(16px, 6px);
|
||||
createPollCheckboxMargin: margins(23px, 10px, 23px, 10px);
|
||||
createPollFieldTitlePadding: margins(22px, 7px, 10px, 6px);
|
||||
|
||||
callSettingsButton: IconButton {
|
||||
width: 50px;
|
||||
@@ -921,3 +946,30 @@ customBadgeField: InputField(defaultInputField) {
|
||||
|
||||
heightMin: 32px;
|
||||
}
|
||||
|
||||
pollResultsQuestion: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 320px;
|
||||
textFg: windowBoldFg;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(16px semibold);
|
||||
linkFont: font(16px semibold);
|
||||
linkFontOver: font(16px semibold underline);
|
||||
}
|
||||
}
|
||||
pollResultsVotesCount: FlatLabel(defaultFlatLabel) {
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
pollResultsHeaderPadding: margins(22px, 22px, 22px, 8px);
|
||||
pollResultsShowMore: SettingsButton {
|
||||
textFg: lightButtonFg;
|
||||
textFgOver: lightButtonFgOver;
|
||||
textBg: windowBg;
|
||||
textBgOver: windowBgOver;
|
||||
|
||||
font: semiboldFont;
|
||||
|
||||
height: 20px;
|
||||
padding: margins(71px, 10px, 8px, 8px);
|
||||
|
||||
ripple: defaultRippleAnimation;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "main/main_session.h"
|
||||
#include "observer_peer.h"
|
||||
@@ -785,7 +786,9 @@ void DeleteMessagesBox::deleteAndClear() {
|
||||
_deleteConfirmedCallback();
|
||||
}
|
||||
|
||||
base::flat_map<not_null<PeerData*>, QVector<MTPint>> idsByPeer;
|
||||
auto remove = std::vector<not_null<HistoryItem*>>();
|
||||
remove.reserve(_ids.size());
|
||||
base::flat_map<not_null<History*>, QVector<MTPint>> idsByPeer;
|
||||
base::flat_map<not_null<PeerData*>, QVector<MTPint>> scheduledIdsByPeer;
|
||||
for (const auto itemId : _ids) {
|
||||
if (const auto item = _session->data().message(itemId)) {
|
||||
@@ -801,21 +804,15 @@ void DeleteMessagesBox::deleteAndClear() {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const auto wasOnServer = IsServerMsgId(item->id);
|
||||
const auto wasLast = (history->lastMessage() == item);
|
||||
const auto wasInChats = (history->chatListMessage() == item);
|
||||
item->destroy();
|
||||
|
||||
if (wasOnServer) {
|
||||
idsByPeer[history->peer].push_back(MTP_int(itemId.msg));
|
||||
} else if (wasLast || wasInChats) {
|
||||
history->requestChatListMessage();
|
||||
remove.push_back(item);
|
||||
if (IsServerMsgId(item->id)) {
|
||||
idsByPeer[history].push_back(MTP_int(itemId.msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &[peer, ids] : idsByPeer) {
|
||||
peer->session().api().deleteMessages(peer, ids, revoke);
|
||||
for (const auto &[history, ids] : idsByPeer) {
|
||||
history->owner().histories().deleteMessages(history, ids, revoke);
|
||||
}
|
||||
for (const auto &[peer, ids] : scheduledIdsByPeer) {
|
||||
peer->session().api().request(MTPmessages_DeleteScheduledMessages(
|
||||
@@ -826,6 +823,17 @@ void DeleteMessagesBox::deleteAndClear() {
|
||||
}).send();
|
||||
}
|
||||
|
||||
for (const auto item : remove) {
|
||||
const auto history = item->history();
|
||||
const auto wasLast = (history->lastMessage() == item);
|
||||
const auto wasInChats = (history->chatListMessage() == item);
|
||||
item->destroy();
|
||||
|
||||
if (wasLast || wasInChats) {
|
||||
history->requestChatListMessage();
|
||||
}
|
||||
}
|
||||
|
||||
const auto session = _session;
|
||||
Ui::hideLayer();
|
||||
session->data().sendHistoryChangeNotifications();
|
||||
|
||||
@@ -12,10 +12,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/wrap/fade_wrap.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "main/main_session.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
@@ -42,13 +45,17 @@ public:
|
||||
Options(
|
||||
not_null<QWidget*> outer,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Main::Session*> session);
|
||||
not_null<Main::Session*> session,
|
||||
bool chooseCorrectEnabled);
|
||||
|
||||
[[nodiscard]] bool hasOptions() const;
|
||||
[[nodiscard]] bool isValid() const;
|
||||
[[nodiscard]] rpl::producer<bool> isValidChanged() const;
|
||||
[[nodiscard]] bool hasCorrect() const;
|
||||
[[nodiscard]] std::vector<PollAnswer> toPollAnswers() const;
|
||||
void focusFirst();
|
||||
|
||||
void enableChooseCorrect(bool enabled);
|
||||
|
||||
[[nodiscard]] rpl::producer<int> usedCount() const;
|
||||
[[nodiscard]] rpl::producer<not_null<QWidget*>> scrollToWidget() const;
|
||||
[[nodiscard]] rpl::producer<> backspaceInFront() const;
|
||||
@@ -56,23 +63,31 @@ public:
|
||||
private:
|
||||
class Option {
|
||||
public:
|
||||
static Option Create(
|
||||
Option(
|
||||
not_null<QWidget*> outer,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Main::Session*> session,
|
||||
int position);
|
||||
int position,
|
||||
std::shared_ptr<Ui::RadiobuttonGroup> group);
|
||||
|
||||
Option(const Option &other) = delete;
|
||||
Option &operator=(const Option &other) = delete;
|
||||
|
||||
void toggleRemoveAlways(bool toggled);
|
||||
void enableChooseCorrect(
|
||||
std::shared_ptr<Ui::RadiobuttonGroup> group);
|
||||
|
||||
void show(anim::type animated);
|
||||
void destroy(FnMut<void()> done);
|
||||
|
||||
//[[nodisacrd]] bool hasShadow() const;
|
||||
//void destroyShadow();
|
||||
[[nodisacrd]] bool hasShadow() const;
|
||||
void createShadow();
|
||||
void destroyShadow();
|
||||
|
||||
[[nodiscard]] bool isEmpty() const;
|
||||
[[nodiscard]] bool isGood() const;
|
||||
[[nodiscard]] bool isTooLong() const;
|
||||
[[nodiscard]] bool isCorrect() const;
|
||||
[[nodiscard]] bool hasFocus() const;
|
||||
void setFocus() const;
|
||||
void clearValue();
|
||||
@@ -86,29 +101,18 @@ private:
|
||||
|
||||
[[nodiscard]] rpl::producer<Qt::MouseButton> removeClicks() const;
|
||||
|
||||
inline bool operator<(const Option &other) const {
|
||||
return field() < other.field();
|
||||
}
|
||||
|
||||
friend inline bool operator<(
|
||||
const Option &option,
|
||||
Ui::InputField *field) {
|
||||
return option.field() < field;
|
||||
}
|
||||
friend inline bool operator<(
|
||||
Ui::InputField *field,
|
||||
const Option &option) {
|
||||
return field < option.field();
|
||||
}
|
||||
|
||||
private:
|
||||
Option() = default;
|
||||
|
||||
void createShadow();
|
||||
void createRemove();
|
||||
void createWarning();
|
||||
void toggleCorrectSpace(bool visible);
|
||||
void updateFieldGeometry();
|
||||
|
||||
base::unique_qptr<Ui::SlideWrap<Ui::InputField>> _field;
|
||||
base::unique_qptr<Ui::SlideWrap<Ui::RpWidget>> _wrap;
|
||||
not_null<Ui::RpWidget*> _content;
|
||||
base::unique_qptr<Ui::FadeWrapScaled<Ui::Radiobutton>> _correct;
|
||||
Ui::Animations::Simple _correctShown;
|
||||
bool _hasCorrect = false;
|
||||
Ui::InputField *_field = nullptr;
|
||||
base::unique_qptr<Ui::PlainShadow> _shadow;
|
||||
base::unique_qptr<Ui::CrossButton> _remove;
|
||||
rpl::variable<bool> *_removeAlways = nullptr;
|
||||
@@ -116,25 +120,30 @@ private:
|
||||
};
|
||||
|
||||
[[nodiscard]] bool full() const;
|
||||
//[[nodiscard]] bool correctShadows() const;
|
||||
//void fixShadows();
|
||||
[[nodiscard]] bool correctShadows() const;
|
||||
void fixShadows();
|
||||
void removeEmptyTail();
|
||||
void addEmptyOption();
|
||||
void checkLastOption();
|
||||
void validateState();
|
||||
void fixAfterErase();
|
||||
void destroy(Option &&option);
|
||||
void removeDestroyed(not_null<Ui::InputField*> field);
|
||||
void destroy(std::unique_ptr<Option> option);
|
||||
void removeDestroyed(not_null<Option*> field);
|
||||
int findField(not_null<Ui::InputField*> field) const;
|
||||
[[nodiscard]] auto createChooseCorrectGroup()
|
||||
-> std::shared_ptr<Ui::RadiobuttonGroup>;
|
||||
|
||||
not_null<QWidget*> _outer;
|
||||
not_null<Ui::VerticalLayout*> _container;
|
||||
const not_null<Main::Session*> _session;
|
||||
std::shared_ptr<Ui::RadiobuttonGroup> _chooseCorrectGroup;
|
||||
int _position = 0;
|
||||
std::vector<Option> _list;
|
||||
std::set<Option, std::less<>> _destroyed;
|
||||
rpl::variable<bool> _valid = false;
|
||||
std::vector<std::unique_ptr<Option>> _list;
|
||||
std::vector<std::unique_ptr<Option>> _destroyed;
|
||||
rpl::variable<int> _usedCount = 0;
|
||||
bool _hasOptions = false;
|
||||
bool _isValid = false;
|
||||
bool _hasCorrect = false;
|
||||
rpl::event_stream<not_null<QWidget*>> _scrollToWidget;
|
||||
rpl::event_stream<> _backspaceInFront;
|
||||
|
||||
@@ -187,58 +196,85 @@ void FocusAtEnd(not_null<Ui::InputField*> field) {
|
||||
field->ensureCursorVisible();
|
||||
}
|
||||
|
||||
Options::Option Options::Option::Create(
|
||||
not_null<QWidget*> outer,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Main::Session*> session,
|
||||
int position) {
|
||||
auto result = Option();
|
||||
const auto field = container->insert(
|
||||
position,
|
||||
object_ptr<Ui::SlideWrap<Ui::InputField>>(
|
||||
container,
|
||||
object_ptr<Ui::InputField>(
|
||||
container,
|
||||
st::createPollOptionField,
|
||||
Ui::InputField::Mode::NoNewlines,
|
||||
tr::lng_polls_create_option_add())));
|
||||
InitField(outer, field->entity(), session);
|
||||
field->entity()->setMaxLength(kOptionLimit + kErrorLimit);
|
||||
result._field.reset(field);
|
||||
Options::Option::Option(
|
||||
not_null<QWidget*> outer,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Main::Session*> session,
|
||||
int position,
|
||||
std::shared_ptr<Ui::RadiobuttonGroup> group)
|
||||
: _wrap(container->insert(
|
||||
position,
|
||||
object_ptr<Ui::SlideWrap<Ui::RpWidget>>(
|
||||
container,
|
||||
object_ptr<Ui::RpWidget>(container))))
|
||||
, _content(_wrap->entity())
|
||||
, _field(
|
||||
Ui::CreateChild<Ui::InputField>(
|
||||
_content.get(),
|
||||
st::createPollOptionField,
|
||||
Ui::InputField::Mode::NoNewlines,
|
||||
tr::lng_polls_create_option_add())) {
|
||||
InitField(outer, _field, session);
|
||||
_field->setMaxLength(kOptionLimit + kErrorLimit);
|
||||
_field->show();
|
||||
|
||||
result.createShadow();
|
||||
result.createRemove();
|
||||
result.createWarning();
|
||||
return result;
|
||||
_wrap->hide(anim::type::instant);
|
||||
|
||||
_content->widthValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateFieldGeometry();
|
||||
}, _field->lifetime());
|
||||
|
||||
_field->heightValue(
|
||||
) | rpl::start_with_next([=](int height) {
|
||||
_content->resize(_content->width(), height);
|
||||
}, _field->lifetime());
|
||||
|
||||
QObject::connect(_field, &Ui::InputField::changed, [=] {
|
||||
Ui::PostponeCall(crl::guard(_field, [=] {
|
||||
if (_hasCorrect) {
|
||||
_correct->toggle(isGood(), anim::type::normal);
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
createShadow();
|
||||
createRemove();
|
||||
createWarning();
|
||||
enableChooseCorrect(group);
|
||||
_correctShown.stop();
|
||||
if (_correct) {
|
||||
_correct->finishAnimating();
|
||||
}
|
||||
updateFieldGeometry();
|
||||
}
|
||||
|
||||
//bool Options::Option::hasShadow() const {
|
||||
// return (_shadow != nullptr);
|
||||
//}
|
||||
bool Options::Option::hasShadow() const {
|
||||
return (_shadow != nullptr);
|
||||
}
|
||||
|
||||
void Options::Option::createShadow() {
|
||||
Expects(_field != nullptr);
|
||||
Expects(_content != nullptr);
|
||||
|
||||
if (_shadow) {
|
||||
return;
|
||||
}
|
||||
const auto value = Ui::CreateChild<Ui::PlainShadow>(field().get());
|
||||
value->show();
|
||||
_shadow.reset(Ui::CreateChild<Ui::PlainShadow>(field().get()));
|
||||
_shadow->show();
|
||||
field()->sizeValue(
|
||||
) | rpl::start_with_next([=](QSize size) {
|
||||
const auto left = st::createPollFieldPadding.left();
|
||||
value->setGeometry(
|
||||
_shadow->setGeometry(
|
||||
left,
|
||||
size.height() - st::lineWidth,
|
||||
size.width() - left,
|
||||
st::lineWidth);
|
||||
}, value->lifetime());
|
||||
_shadow.reset(value);
|
||||
}, _shadow->lifetime());
|
||||
}
|
||||
|
||||
//void Options::Option::destroyShadow() {
|
||||
// _shadow = nullptr;
|
||||
//}
|
||||
void Options::Option::destroyShadow() {
|
||||
_shadow = nullptr;
|
||||
}
|
||||
|
||||
void Options::Option::createRemove() {
|
||||
using namespace rpl::mappers;
|
||||
@@ -313,6 +349,10 @@ bool Options::Option::isTooLong() const {
|
||||
return (field()->getLastText().size() > kOptionLimit);
|
||||
}
|
||||
|
||||
bool Options::Option::isCorrect() const {
|
||||
return isGood() && _correct && _correct->entity()->Checkbox::checked();
|
||||
}
|
||||
|
||||
bool Options::Option::hasFocus() const {
|
||||
return field()->hasFocus();
|
||||
}
|
||||
@@ -333,8 +373,66 @@ void Options::Option::toggleRemoveAlways(bool toggled) {
|
||||
*_removeAlways = toggled;
|
||||
}
|
||||
|
||||
void Options::Option::enableChooseCorrect(
|
||||
std::shared_ptr<Ui::RadiobuttonGroup> group) {
|
||||
if (!group) {
|
||||
if (_correct) {
|
||||
_hasCorrect = false;
|
||||
_correct->hide(anim::type::normal);
|
||||
toggleCorrectSpace(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
static auto Index = 0;
|
||||
const auto button = Ui::CreateChild<Ui::FadeWrapScaled<Ui::Radiobutton>>(
|
||||
_content.get(),
|
||||
object_ptr<Ui::Radiobutton>(
|
||||
_content.get(),
|
||||
group,
|
||||
++Index,
|
||||
QString(),
|
||||
st::defaultCheckbox));
|
||||
button->entity()->resize(
|
||||
button->entity()->height(),
|
||||
button->entity()->height());
|
||||
button->hide(anim::type::instant);
|
||||
_content->sizeValue(
|
||||
) | rpl::start_with_next([=](QSize size) {
|
||||
const auto left = st::createPollFieldPadding.left();
|
||||
button->moveToLeft(
|
||||
left,
|
||||
(size.height() - button->heightNoMargins()) / 2);
|
||||
}, button->lifetime());
|
||||
_correct.reset(button);
|
||||
_hasCorrect = true;
|
||||
if (isGood()) {
|
||||
_correct->show(anim::type::normal);
|
||||
} else {
|
||||
_correct->hide(anim::type::instant);
|
||||
}
|
||||
toggleCorrectSpace(true);
|
||||
}
|
||||
|
||||
void Options::Option::toggleCorrectSpace(bool visible) {
|
||||
_correctShown.start(
|
||||
[=] { updateFieldGeometry(); },
|
||||
visible ? 0. : 1.,
|
||||
visible ? 1. : 0.,
|
||||
st::fadeWrapDuration);
|
||||
}
|
||||
|
||||
void Options::Option::updateFieldGeometry() {
|
||||
const auto shown = _correctShown.value(_hasCorrect ? 1. : 0.);
|
||||
const auto skip = st::defaultRadio.diameter
|
||||
+ st::defaultCheckbox.textPosition.x();
|
||||
const auto left = anim::interpolate(0, skip, shown);
|
||||
const auto width = _content->width() - left;
|
||||
_field->resizeToWidth(_content->width() - left);
|
||||
_field->moveToLeft(left, 0);
|
||||
}
|
||||
|
||||
not_null<Ui::InputField*> Options::Option::field() const {
|
||||
return _field->entity();
|
||||
return _field;
|
||||
}
|
||||
|
||||
void Options::Option::removePlaceholder() const {
|
||||
@@ -344,10 +442,12 @@ void Options::Option::removePlaceholder() const {
|
||||
PollAnswer Options::Option::toPollAnswer(int index) const {
|
||||
Expects(index >= 0 && index < kMaxOptionsCount);
|
||||
|
||||
return PollAnswer{
|
||||
auto result = PollAnswer{
|
||||
field()->getLastText().trimmed(),
|
||||
QByteArray(1, ('0' + index))
|
||||
};
|
||||
result.correct = _correct ? _correct->entity()->Checkbox::checked() : false;
|
||||
return result;
|
||||
}
|
||||
|
||||
rpl::producer<Qt::MouseButton> Options::Option::removeClicks() const {
|
||||
@@ -357,10 +457,14 @@ rpl::producer<Qt::MouseButton> Options::Option::removeClicks() const {
|
||||
Options::Options(
|
||||
not_null<QWidget*> outer,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Main::Session*> session)
|
||||
not_null<Main::Session*> session,
|
||||
bool chooseCorrectEnabled)
|
||||
: _outer(outer)
|
||||
, _container(container)
|
||||
, _session(session)
|
||||
, _chooseCorrectGroup(chooseCorrectEnabled
|
||||
? createChooseCorrectGroup()
|
||||
: nullptr)
|
||||
, _position(_container->count()) {
|
||||
checkLastOption();
|
||||
}
|
||||
@@ -369,12 +473,16 @@ bool Options::full() const {
|
||||
return (_list.size() == kMaxOptionsCount);
|
||||
}
|
||||
|
||||
bool Options::isValid() const {
|
||||
return _valid.current();
|
||||
bool Options::hasOptions() const {
|
||||
return _hasOptions;
|
||||
}
|
||||
|
||||
rpl::producer<bool> Options::isValidChanged() const {
|
||||
return _valid.changes();
|
||||
bool Options::isValid() const {
|
||||
return _isValid;
|
||||
}
|
||||
|
||||
bool Options::hasCorrect() const {
|
||||
return _hasCorrect;
|
||||
}
|
||||
|
||||
rpl::producer<int> Options::usedCount() const {
|
||||
@@ -390,19 +498,18 @@ rpl::producer<> Options::backspaceInFront() const {
|
||||
}
|
||||
|
||||
void Options::Option::show(anim::type animated) {
|
||||
_field->hide(anim::type::instant);
|
||||
_field->show(animated);
|
||||
_wrap->show(animated);
|
||||
}
|
||||
|
||||
void Options::Option::destroy(FnMut<void()> done) {
|
||||
if (anim::Disabled() || _field->isHidden()) {
|
||||
if (anim::Disabled() || _wrap->isHidden()) {
|
||||
Ui::PostponeCall(std::move(done));
|
||||
return;
|
||||
}
|
||||
_field->hide(anim::type::normal);
|
||||
_wrap->hide(anim::type::normal);
|
||||
base::call_delayed(
|
||||
st::slideWrapDuration * 2,
|
||||
_field.get(),
|
||||
_content.get(),
|
||||
std::move(done));
|
||||
}
|
||||
|
||||
@@ -410,8 +517,8 @@ std::vector<PollAnswer> Options::toPollAnswers() const {
|
||||
auto result = std::vector<PollAnswer>();
|
||||
result.reserve(_list.size());
|
||||
auto counter = int(0);
|
||||
const auto makeAnswer = [&](const Option &option) {
|
||||
return option.toPollAnswer(counter++);
|
||||
const auto makeAnswer = [&](const std::unique_ptr<Option> &option) {
|
||||
return option->toPollAnswer(counter++);
|
||||
};
|
||||
ranges::copy(
|
||||
_list
|
||||
@@ -424,29 +531,45 @@ std::vector<PollAnswer> Options::toPollAnswers() const {
|
||||
void Options::focusFirst() {
|
||||
Expects(!_list.empty());
|
||||
|
||||
_list.front().setFocus();
|
||||
_list.front()->setFocus();
|
||||
}
|
||||
|
||||
std::shared_ptr<Ui::RadiobuttonGroup> Options::createChooseCorrectGroup() {
|
||||
auto result = std::make_shared<Ui::RadiobuttonGroup>(0);
|
||||
result->setChangedCallback([=](int) {
|
||||
validateState();
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void Options::enableChooseCorrect(bool enabled) {
|
||||
_chooseCorrectGroup = enabled
|
||||
? createChooseCorrectGroup()
|
||||
: nullptr;
|
||||
for (auto &option : _list) {
|
||||
option->enableChooseCorrect(_chooseCorrectGroup);
|
||||
}
|
||||
validateState();
|
||||
}
|
||||
|
||||
bool Options::correctShadows() const {
|
||||
// Last one should be without shadow.
|
||||
const auto noShadow = ranges::find(
|
||||
_list,
|
||||
true,
|
||||
ranges::not_fn(&Option::hasShadow));
|
||||
return (noShadow == end(_list) - 1);
|
||||
}
|
||||
|
||||
void Options::fixShadows() {
|
||||
if (correctShadows()) {
|
||||
return;
|
||||
}
|
||||
for (auto &option : _list) {
|
||||
option->createShadow();
|
||||
}
|
||||
_list.back()->destroyShadow();
|
||||
}
|
||||
//
|
||||
//bool Options::correctShadows() const {
|
||||
// // Last one should be without shadow if all options were used.
|
||||
// const auto noShadow = ranges::find(
|
||||
// _list,
|
||||
// true,
|
||||
// ranges::not_fn(&Option::hasShadow));
|
||||
// return (noShadow == end(_list) - (full() ? 1 : 0));
|
||||
//}
|
||||
//
|
||||
//void Options::fixShadows() {
|
||||
// if (correctShadows()) {
|
||||
// return;
|
||||
// }
|
||||
// for (auto &option : _list) {
|
||||
// option.createShadow();
|
||||
// }
|
||||
// if (full()) {
|
||||
// _list.back().destroyShadow();
|
||||
// }
|
||||
//}
|
||||
|
||||
void Options::removeEmptyTail() {
|
||||
// Only one option at the end of options list can be empty.
|
||||
@@ -465,7 +588,7 @@ void Options::removeEmptyTail() {
|
||||
return;
|
||||
}
|
||||
if (focusLast) {
|
||||
emptyItem->setFocus();
|
||||
(*emptyItem)->setFocus();
|
||||
}
|
||||
for (auto i = emptyItem + 1; i != end; ++i) {
|
||||
destroy(std::move(*i));
|
||||
@@ -474,44 +597,46 @@ void Options::removeEmptyTail() {
|
||||
fixAfterErase();
|
||||
}
|
||||
|
||||
void Options::destroy(Option &&option) {
|
||||
const auto field = option.field();
|
||||
option.destroy([=] { removeDestroyed(field); });
|
||||
_destroyed.emplace(std::move(option));
|
||||
void Options::destroy(std::unique_ptr<Option> option) {
|
||||
const auto value = option.get();
|
||||
option->destroy([=] { removeDestroyed(value); });
|
||||
_destroyed.push_back(std::move(option));
|
||||
}
|
||||
|
||||
void Options::fixAfterErase() {
|
||||
Expects(!_list.empty());
|
||||
|
||||
const auto last = _list.end() - 1;
|
||||
last->setPlaceholder();
|
||||
last->toggleRemoveAlways(false);
|
||||
(*last)->setPlaceholder();
|
||||
(*last)->toggleRemoveAlways(false);
|
||||
if (last != begin(_list)) {
|
||||
(last - 1)->setPlaceholder();
|
||||
(last - 1)->toggleRemoveAlways(false);
|
||||
(*(last - 1))->setPlaceholder();
|
||||
(*(last - 1))->toggleRemoveAlways(false);
|
||||
}
|
||||
fixShadows();
|
||||
}
|
||||
|
||||
void Options::addEmptyOption() {
|
||||
if (full()) {
|
||||
return;
|
||||
} else if (!_list.empty() && _list.back().isEmpty()) {
|
||||
} else if (!_list.empty() && _list.back()->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (_list.size() > 1) {
|
||||
(_list.end() - 2)->removePlaceholder();
|
||||
(_list.end() - 2)->toggleRemoveAlways(true);
|
||||
(*(_list.end() - 2))->removePlaceholder();
|
||||
(*(_list.end() - 2))->toggleRemoveAlways(true);
|
||||
}
|
||||
_list.push_back(Option::Create(
|
||||
_list.push_back(std::make_unique<Option>(
|
||||
_outer,
|
||||
_container,
|
||||
_session,
|
||||
_position + _list.size() + _destroyed.size()));
|
||||
const auto field = _list.back().field();
|
||||
_position + _list.size() + _destroyed.size(),
|
||||
_chooseCorrectGroup));
|
||||
const auto field = _list.back()->field();
|
||||
QObject::connect(field, &Ui::InputField::submitted, [=] {
|
||||
const auto index = findField(field);
|
||||
if (_list[index].isGood() && index + 1 < _list.size()) {
|
||||
_list[index + 1].setFocus();
|
||||
if (_list[index]->isGood() && index + 1 < _list.size()) {
|
||||
_list[index + 1]->setFocus();
|
||||
}
|
||||
});
|
||||
QObject::connect(field, &Ui::InputField::changed, [=] {
|
||||
@@ -534,25 +659,25 @@ void Options::addEmptyOption() {
|
||||
|
||||
const auto index = findField(field);
|
||||
if (index > 0) {
|
||||
_list[index - 1].setFocus();
|
||||
_list[index - 1]->setFocus();
|
||||
} else {
|
||||
_backspaceInFront.fire({});
|
||||
}
|
||||
return base::EventFilterResult::Cancel;
|
||||
});
|
||||
|
||||
_list.back().removeClicks(
|
||||
) | rpl::start_with_next([=] {
|
||||
_list.back()->removeClicks(
|
||||
) | rpl::take(1) | rpl::start_with_next([=] {
|
||||
Ui::PostponeCall(crl::guard(field, [=] {
|
||||
Expects(!_list.empty());
|
||||
|
||||
const auto item = begin(_list) + findField(field);
|
||||
if (item == _list.end() - 1) {
|
||||
item->clearValue();
|
||||
(*item)->clearValue();
|
||||
return;
|
||||
}
|
||||
if (item->hasFocus()) {
|
||||
(item + 1)->setFocus();
|
||||
if ((*item)->hasFocus()) {
|
||||
(*(item + 1))->setFocus();
|
||||
}
|
||||
destroy(std::move(*item));
|
||||
_list.erase(item);
|
||||
@@ -561,21 +686,29 @@ void Options::addEmptyOption() {
|
||||
}));
|
||||
}, field->lifetime());
|
||||
|
||||
_list.back().show((_list.size() == 1)
|
||||
_list.back()->show((_list.size() == 1)
|
||||
? anim::type::instant
|
||||
: anim::type::normal);
|
||||
//fixShadows();
|
||||
fixShadows();
|
||||
}
|
||||
|
||||
void Options::removeDestroyed(not_null<Ui::InputField*> field) {
|
||||
_destroyed.erase(_destroyed.find(field));
|
||||
void Options::removeDestroyed(not_null<Option*> option) {
|
||||
const auto i = ranges::find(
|
||||
_destroyed,
|
||||
option.get(),
|
||||
&std::unique_ptr<Option>::get);
|
||||
Assert(i != end(_destroyed));
|
||||
_destroyed.erase(i);
|
||||
}
|
||||
|
||||
void Options::validateState() {
|
||||
checkLastOption();
|
||||
_valid = (ranges::count_if(_list, &Option::isGood) > 1)
|
||||
_hasOptions = (ranges::count_if(_list, &Option::isGood) > 1);
|
||||
_isValid = _hasOptions
|
||||
&& (ranges::find_if(_list, &Option::isTooLong) == end(_list));
|
||||
const auto lastEmpty = !_list.empty() && _list.back().isEmpty();
|
||||
_hasCorrect = ranges::find_if(_list, &Option::isCorrect) != end(_list);
|
||||
|
||||
const auto lastEmpty = !_list.empty() && _list.back()->isEmpty();
|
||||
_usedCount = _list.size() - (lastEmpty ? 1 : 0);
|
||||
}
|
||||
|
||||
@@ -599,8 +732,12 @@ void Options::checkLastOption() {
|
||||
CreatePollBox::CreatePollBox(
|
||||
QWidget*,
|
||||
not_null<Main::Session*> session,
|
||||
PollData::Flags chosen,
|
||||
PollData::Flags disabled,
|
||||
Api::SendType sendType)
|
||||
: _session(session)
|
||||
, _chosen(chosen)
|
||||
, _disabled(disabled)
|
||||
, _sendType(sendType) {
|
||||
}
|
||||
|
||||
@@ -630,6 +767,7 @@ not_null<Ui::InputField*> CreatePollBox::setupQuestion(
|
||||
st::createPollFieldPadding);
|
||||
InitField(getDelegate()->outerContainer(), question, _session);
|
||||
question->setMaxLength(kQuestionLimit + kErrorLimit);
|
||||
question->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
|
||||
|
||||
const auto warning = CreateWarningLabel(
|
||||
container,
|
||||
@@ -660,7 +798,7 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||
using namespace Settings;
|
||||
|
||||
const auto id = rand_value<uint64>();
|
||||
const auto valid = lifetime().make_state<rpl::event_stream<bool>>();
|
||||
const auto error = lifetime().make_state<Errors>(Error::Question);
|
||||
|
||||
auto result = object_ptr<Ui::VerticalLayout>(this);
|
||||
const auto container = result.data();
|
||||
@@ -668,11 +806,17 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||
const auto question = setupQuestion(container);
|
||||
AddDivider(container);
|
||||
AddSkip(container);
|
||||
AddSubsectionTitle(container, tr::lng_polls_create_options());
|
||||
container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
tr::lng_polls_create_options(),
|
||||
st::settingsSubsectionTitle),
|
||||
st::createPollFieldTitlePadding);
|
||||
const auto options = lifetime().make_state<Options>(
|
||||
getDelegate()->outerContainer(),
|
||||
container,
|
||||
_session);
|
||||
_session,
|
||||
(_chosen & PollData::Flag::Quiz));
|
||||
auto limit = options->usedCount() | rpl::after_next([=](int count) {
|
||||
setCloseByEscape(!count);
|
||||
setCloseByOutsideClick(!count);
|
||||
@@ -684,11 +828,68 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||
container->resizeToWidth(container->widthNoMargins());
|
||||
});
|
||||
container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
object_ptr<Ui::DividerLabel>(
|
||||
container,
|
||||
std::move(limit),
|
||||
st::createPollLimitLabel),
|
||||
st::createPollLimitPadding);
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
std::move(limit),
|
||||
st::boxDividerLabel),
|
||||
st::createPollLimitPadding));
|
||||
|
||||
AddSkip(container);
|
||||
AddSubsectionTitle(container, tr::lng_polls_create_settings());
|
||||
|
||||
const auto anonymous = (!(_disabled & PollData::Flag::PublicVotes))
|
||||
? container->add(
|
||||
object_ptr<Ui::Checkbox>(
|
||||
container,
|
||||
tr::lng_polls_create_anonymous(tr::now),
|
||||
!(_chosen & PollData::Flag::PublicVotes),
|
||||
st::defaultCheckbox),
|
||||
st::createPollCheckboxMargin)
|
||||
: nullptr;
|
||||
const auto hasMultiple = !(_chosen & PollData::Flag::Quiz)
|
||||
|| !(_disabled & PollData::Flag::Quiz);
|
||||
const auto multiple = hasMultiple
|
||||
? container->add(
|
||||
object_ptr<Ui::Checkbox>(
|
||||
container,
|
||||
tr::lng_polls_create_multiple_choice(tr::now),
|
||||
(_chosen & PollData::Flag::MultiChoice),
|
||||
st::defaultCheckbox),
|
||||
st::createPollCheckboxMargin)
|
||||
: nullptr;
|
||||
const auto quiz = container->add(
|
||||
object_ptr<Ui::Checkbox>(
|
||||
container,
|
||||
tr::lng_polls_create_quiz_mode(tr::now),
|
||||
(_chosen & PollData::Flag::Quiz),
|
||||
st::defaultCheckbox),
|
||||
st::createPollCheckboxMargin);
|
||||
quiz->setDisabled(_disabled & PollData::Flag::Quiz);
|
||||
if (multiple) {
|
||||
multiple->setDisabled((_disabled & PollData::Flag::MultiChoice)
|
||||
|| (_chosen & PollData::Flag::Quiz));
|
||||
multiple->events(
|
||||
) | rpl::filter([=](not_null<QEvent*> e) {
|
||||
return (e->type() == QEvent::MouseButtonPress) && quiz->checked();
|
||||
}) | rpl::start_with_next([=] {
|
||||
Ui::Toast::Show(tr::lng_polls_create_one_answer(tr::now));
|
||||
}, multiple->lifetime());
|
||||
}
|
||||
|
||||
using namespace rpl::mappers;
|
||||
quiz->checkedChanges(
|
||||
) | rpl::start_with_next([=](bool checked) {
|
||||
if (multiple) {
|
||||
if (checked && multiple->checked()) {
|
||||
multiple->setChecked(false);
|
||||
}
|
||||
multiple->setDisabled(checked
|
||||
|| (_disabled & PollData::Flag::MultiChoice));
|
||||
}
|
||||
options->enableChooseCorrect(checked);
|
||||
}, quiz->lifetime());
|
||||
|
||||
const auto isValidQuestion = [=] {
|
||||
const auto text = question->getLastText().trimmed();
|
||||
@@ -706,13 +907,53 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||
};
|
||||
|
||||
const auto collectResult = [=] {
|
||||
auto result = PollData(id);
|
||||
using Flag = PollData::Flag;
|
||||
auto result = PollData(&_session->data(), id);
|
||||
result.question = question->getLastText().trimmed();
|
||||
result.answers = options->toPollAnswers();
|
||||
const auto publicVotes = (anonymous && !anonymous->checked());
|
||||
const auto multiChoice = (multiple && multiple->checked());
|
||||
result.setFlags(Flag(0)
|
||||
| (publicVotes ? Flag::PublicVotes : Flag(0))
|
||||
| (multiChoice ? Flag::MultiChoice : Flag(0))
|
||||
| (quiz->checked() ? Flag::Quiz : Flag(0)));
|
||||
return result;
|
||||
};
|
||||
const auto send = [=](Api::SendOptions options) {
|
||||
_submitRequests.fire({ collectResult(), options });
|
||||
const auto collectError = [=] {
|
||||
if (isValidQuestion()) {
|
||||
*error &= ~Error::Question;
|
||||
} else {
|
||||
*error |= Error::Question;
|
||||
}
|
||||
if (!options->hasOptions()) {
|
||||
*error |= Error::Options;
|
||||
} else if (!options->isValid()) {
|
||||
*error |= Error::Other;
|
||||
} else {
|
||||
*error &= ~(Error::Options | Error::Other);
|
||||
}
|
||||
if (quiz->checked() && !options->hasCorrect()) {
|
||||
*error |= Error::Correct;
|
||||
} else {
|
||||
*error &= ~Error::Correct;
|
||||
}
|
||||
};
|
||||
const auto showError = [=](const QString &text) {
|
||||
Ui::Toast::Show(text);
|
||||
};
|
||||
const auto send = [=](Api::SendOptions sendOptions) {
|
||||
collectError();
|
||||
if (*error & Error::Question) {
|
||||
showError(tr::lng_polls_choose_question(tr::now));
|
||||
question->setFocus();
|
||||
} else if (*error & Error::Options) {
|
||||
showError(tr::lng_polls_choose_answers(tr::now));
|
||||
options->focusFirst();
|
||||
} else if (*error & Error::Correct) {
|
||||
showError(tr::lng_polls_choose_correct(tr::now));
|
||||
} else if (!*error) {
|
||||
_submitRequests.fire({ collectResult(), sendOptions });
|
||||
}
|
||||
};
|
||||
const auto sendSilent = [=] {
|
||||
auto options = Api::SendOptions();
|
||||
@@ -727,36 +968,6 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||
send),
|
||||
Ui::LayerOption::KeepOther);
|
||||
};
|
||||
const auto updateValid = [=] {
|
||||
valid->fire(isValidQuestion() && options->isValid());
|
||||
};
|
||||
connect(question, &Ui::InputField::changed, [=] {
|
||||
updateValid();
|
||||
});
|
||||
valid->events_starting_with(
|
||||
false
|
||||
) | rpl::distinct_until_changed(
|
||||
) | rpl::start_with_next([=](bool valid) {
|
||||
clearButtons();
|
||||
if (valid) {
|
||||
const auto submit = addButton(
|
||||
tr::lng_polls_create_button(),
|
||||
[=] { send({}); });
|
||||
if (_sendType == Api::SendType::Normal) {
|
||||
SetupSendMenu(
|
||||
submit.data(),
|
||||
[=] { return SendMenuType::Scheduled; },
|
||||
sendSilent,
|
||||
sendScheduled);
|
||||
}
|
||||
}
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
}, lifetime());
|
||||
|
||||
options->isValidChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateValid();
|
||||
}, lifetime());
|
||||
|
||||
options->scrollToWidget(
|
||||
) | rpl::start_with_next([=](not_null<QWidget*> widget) {
|
||||
@@ -768,6 +979,22 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||
FocusAtEnd(question);
|
||||
}, lifetime());
|
||||
|
||||
const auto submit = addButton(
|
||||
tr::lng_polls_create_button(),
|
||||
[=] { send({}); });
|
||||
if (_sendType == Api::SendType::Normal) {
|
||||
const auto sendMenuType = [=] {
|
||||
collectError();
|
||||
return *error ? SendMenuType::Disabled : SendMenuType::Scheduled;
|
||||
};
|
||||
SetupSendMenuAndShortcuts(
|
||||
submit.data(),
|
||||
sendMenuType,
|
||||
sendSilent,
|
||||
sendScheduled);
|
||||
}
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "api/api_common.h"
|
||||
#include "data/data_poll.h"
|
||||
#include "base/flags.h"
|
||||
|
||||
struct PollData;
|
||||
|
||||
@@ -31,6 +32,8 @@ public:
|
||||
CreatePollBox(
|
||||
QWidget*,
|
||||
not_null<Main::Session*> session,
|
||||
PollData::Flags chosen,
|
||||
PollData::Flags disabled,
|
||||
Api::SendType sendType);
|
||||
|
||||
rpl::producer<Result> submitRequests() const;
|
||||
@@ -42,11 +45,22 @@ protected:
|
||||
void prepare() override;
|
||||
|
||||
private:
|
||||
enum class Error {
|
||||
Question = 0x01,
|
||||
Options = 0x02,
|
||||
Correct = 0x04,
|
||||
Other = 0x08,
|
||||
};
|
||||
friend constexpr inline bool is_flag_type(Error) { return true; }
|
||||
using Errors = base::flags<Error>;
|
||||
|
||||
object_ptr<Ui::RpWidget> setupContent();
|
||||
not_null<Ui::InputField*> setupQuestion(
|
||||
not_null<Ui::VerticalLayout*> container);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
const PollData::Flags _chosen = PollData::Flags();
|
||||
const PollData::Flags _disabled = PollData::Flags();
|
||||
const Api::SendType _sendType = Api::SendType();
|
||||
Fn<void()> _setInnerFocus;
|
||||
Fn<rpl::producer<bool>()> _dataIsValidValue;
|
||||
|
||||
442
Telegram/SourceFiles/boxes/dictionaries_manager.cpp
Normal file
@@ -0,0 +1,442 @@
|
||||
/*
|
||||
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 "boxes/dictionaries_manager.h"
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_SPELLCHECK
|
||||
|
||||
#include "base/event_filter.h"
|
||||
#include "chat_helpers/spellchecker_common.h"
|
||||
#include "core/application.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mtproto/dedicated_file_loader.h"
|
||||
#include "spellcheck/spellcheck_utils.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_settings.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/multi_select.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/effects/animations.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
using Dictionaries = std::vector<int>;
|
||||
using namespace Storage::CloudBlob;
|
||||
|
||||
using Loading = MTP::DedicatedLoader::Progress;
|
||||
using DictState = BlobState;
|
||||
using QueryCallback = Fn<void(const QString &)>;
|
||||
constexpr auto kMaxQueryLength = 15;
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
|
||||
#define OLD_QT
|
||||
using QStringView = QString;
|
||||
#endif
|
||||
|
||||
class Inner : public Ui::RpWidget {
|
||||
public:
|
||||
Inner(QWidget *parent, Dictionaries enabledDictionaries);
|
||||
|
||||
Dictionaries enabledRows() const;
|
||||
QueryCallback queryCallback() const;
|
||||
|
||||
private:
|
||||
void setupContent(Dictionaries enabledDictionaries);
|
||||
|
||||
Dictionaries _enabledRows;
|
||||
QueryCallback _queryCallback;
|
||||
|
||||
};
|
||||
|
||||
inline auto DictExists(int langId) {
|
||||
return Spellchecker::DictionaryExists(langId);
|
||||
}
|
||||
|
||||
inline auto FilterEnabledDict(Dictionaries dicts) {
|
||||
return dicts | ranges::views::filter(
|
||||
DictExists
|
||||
) | ranges::to_vector;
|
||||
}
|
||||
|
||||
DictState ComputeState(int id, bool enabled) {
|
||||
const auto result = enabled ? DictState(Active()) : DictState(Ready());
|
||||
if (DictExists(id)) {
|
||||
return result;
|
||||
}
|
||||
return Available{ Spellchecker::GetDownloadSize(id) };
|
||||
}
|
||||
|
||||
QString StateDescription(const DictState &state) {
|
||||
return StateDescription(
|
||||
state,
|
||||
tr::lng_settings_manage_enabled_dictionary);
|
||||
}
|
||||
|
||||
auto CreateMultiSelect(QWidget *parent) {
|
||||
const auto result = Ui::CreateChild<Ui::MultiSelect>(
|
||||
parent,
|
||||
st::contactsMultiSelect,
|
||||
tr::lng_participant_filter());
|
||||
|
||||
result->resizeToWidth(st::boxWidth);
|
||||
result->moveToLeft(0, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
Inner::Inner(
|
||||
QWidget *parent,
|
||||
Dictionaries enabledDictionaries) : RpWidget(parent) {
|
||||
setupContent(std::move(enabledDictionaries));
|
||||
}
|
||||
|
||||
QueryCallback Inner::queryCallback() const {
|
||||
return _queryCallback;
|
||||
}
|
||||
|
||||
Dictionaries Inner::enabledRows() const {
|
||||
return _enabledRows;
|
||||
}
|
||||
|
||||
auto AddButtonWithLoader(
|
||||
not_null<Ui::VerticalLayout*> content,
|
||||
const Spellchecker::Dict &dict,
|
||||
bool buttonEnabled,
|
||||
rpl::producer<QStringView> query) {
|
||||
const auto id = dict.id;
|
||||
buttonEnabled &= DictExists(id);
|
||||
|
||||
const auto locale = Spellchecker::LocaleFromLangId(id);
|
||||
const std::vector<QString> indexList = {
|
||||
dict.name,
|
||||
QLocale::languageToString(locale.language()),
|
||||
QLocale::countryToString(locale.country())
|
||||
};
|
||||
|
||||
const auto wrap = content->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
||||
content,
|
||||
object_ptr<Ui::SettingsButton>(
|
||||
content,
|
||||
rpl::single(dict.name),
|
||||
st::dictionariesSectionButton
|
||||
)
|
||||
)
|
||||
);
|
||||
const auto button = wrap->entity();
|
||||
|
||||
std::move(
|
||||
query
|
||||
) | rpl::start_with_next([=](auto string) {
|
||||
wrap->toggle(
|
||||
ranges::any_of(indexList, [&](const QString &s) {
|
||||
return s.startsWith(string, Qt::CaseInsensitive);
|
||||
}),
|
||||
anim::type::instant);
|
||||
}, button->lifetime());
|
||||
|
||||
using Loader = Spellchecker::DictLoader;
|
||||
using GlobalLoaderPtr = std::shared_ptr<base::unique_qptr<Loader>>;
|
||||
|
||||
const auto localLoader = button->lifetime()
|
||||
.make_state<base::unique_qptr<Loader>>();
|
||||
const auto localLoaderValues = button->lifetime()
|
||||
.make_state<rpl::event_stream<Loader*>>();
|
||||
const auto setLocalLoader = [=](base::unique_qptr<Loader> loader) {
|
||||
*localLoader = std::move(loader);
|
||||
localLoaderValues->fire(localLoader->get());
|
||||
};
|
||||
const auto destroyLocalLoader = [=] {
|
||||
setLocalLoader(nullptr);
|
||||
};
|
||||
|
||||
const auto buttonState = button->lifetime()
|
||||
.make_state<rpl::variable<DictState>>();
|
||||
const auto dictionaryRemoved = button->lifetime()
|
||||
.make_state<rpl::event_stream<>>();
|
||||
const auto dictionaryFromGlobalLoader = button->lifetime()
|
||||
.make_state<rpl::event_stream<>>();
|
||||
|
||||
const auto globalLoader = button->lifetime()
|
||||
.make_state<GlobalLoaderPtr>();
|
||||
|
||||
const auto rawGlobalLoaderPtr = [=]() -> Loader* {
|
||||
if (!globalLoader || !*globalLoader || !*globalLoader->get()) {
|
||||
return nullptr;
|
||||
}
|
||||
return globalLoader->get()->get();
|
||||
};
|
||||
|
||||
const auto setGlobalLoaderPtr = [=](GlobalLoaderPtr loader) {
|
||||
if (localLoader->get()) {
|
||||
if (loader && loader->get()) {
|
||||
loader->get()->destroy();
|
||||
}
|
||||
return;
|
||||
}
|
||||
*globalLoader = std::move(loader);
|
||||
localLoaderValues->fire(rawGlobalLoaderPtr());
|
||||
if (rawGlobalLoaderPtr()) {
|
||||
dictionaryFromGlobalLoader->fire({});
|
||||
}
|
||||
};
|
||||
|
||||
Spellchecker::GlobalLoaderChanged(
|
||||
) | rpl::start_with_next([=](int langId) {
|
||||
if (!langId && rawGlobalLoaderPtr()) {
|
||||
setGlobalLoaderPtr(nullptr);
|
||||
} else if (langId == id) {
|
||||
setGlobalLoaderPtr(Spellchecker::GlobalLoader());
|
||||
}
|
||||
}, button->lifetime());
|
||||
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
button,
|
||||
buttonState->value() | rpl::map(StateDescription),
|
||||
st::settingsUpdateState);
|
||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
rpl::combine(
|
||||
button->widthValue(),
|
||||
label->widthValue()
|
||||
) | rpl::start_with_next([=] {
|
||||
label->moveToLeft(
|
||||
st::settingsUpdateStatePosition.x(),
|
||||
st::settingsUpdateStatePosition.y());
|
||||
}, label->lifetime());
|
||||
|
||||
buttonState->value(
|
||||
) | rpl::start_with_next([=](const DictState &state) {
|
||||
const auto isToggledSet = state.is<Active>();
|
||||
const auto toggled = isToggledSet ? 1. : 0.;
|
||||
const auto over = !button->isDisabled()
|
||||
&& (button->isDown() || button->isOver());
|
||||
|
||||
if (toggled == 0. && !over) {
|
||||
label->setTextColorOverride(std::nullopt);
|
||||
} else {
|
||||
label->setTextColorOverride(anim::color(
|
||||
over ? st::contactsStatusFgOver : st::contactsStatusFg,
|
||||
st::contactsStatusFgOnline,
|
||||
toggled));
|
||||
}
|
||||
}, label->lifetime());
|
||||
|
||||
button->toggleOn(
|
||||
rpl::single(
|
||||
buttonEnabled
|
||||
) | rpl::then(
|
||||
rpl::merge(
|
||||
// Events to toggle on.
|
||||
dictionaryFromGlobalLoader->events(
|
||||
) | rpl::map([] {
|
||||
return true;
|
||||
}),
|
||||
// Events to toggle off.
|
||||
rpl::merge(
|
||||
dictionaryRemoved->events(),
|
||||
buttonState->value(
|
||||
) | rpl::filter([](const DictState &state) {
|
||||
return state.is<Failed>();
|
||||
}) | rpl::map([] {
|
||||
return rpl::empty_value();
|
||||
})
|
||||
) | rpl::map([] {
|
||||
return false;
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
*buttonState = localLoaderValues->events_starting_with(
|
||||
rawGlobalLoaderPtr() ? rawGlobalLoaderPtr() : localLoader->get()
|
||||
) | rpl::map([=](Loader *loader) {
|
||||
return (loader && loader->id() == id)
|
||||
? loader->state()
|
||||
: rpl::single(
|
||||
buttonEnabled
|
||||
) | rpl::then(
|
||||
rpl::merge(
|
||||
dictionaryRemoved->events(
|
||||
) | rpl::map([] {
|
||||
return false;
|
||||
}),
|
||||
button->toggledValue()
|
||||
)
|
||||
) | rpl::map([=](auto enabled) {
|
||||
return ComputeState(id, enabled);
|
||||
});
|
||||
}) | rpl::flatten_latest(
|
||||
) | rpl::filter([=](const DictState &state) {
|
||||
return !buttonState->current().is<Failed>() || !state.is<Available>();
|
||||
});
|
||||
|
||||
button->toggledValue(
|
||||
) | rpl::start_with_next([=](bool toggled) {
|
||||
const auto &state = buttonState->current();
|
||||
if (toggled && (state.is<Available>() || state.is<Failed>())) {
|
||||
const auto weak = Ui::MakeWeak(button);
|
||||
setLocalLoader(base::make_unique_q<Loader>(
|
||||
App::main(),
|
||||
id,
|
||||
Spellchecker::GetDownloadLocation(id),
|
||||
Spellchecker::DictPathByLangId(id),
|
||||
Spellchecker::GetDownloadSize(id),
|
||||
crl::guard(weak, destroyLocalLoader)));
|
||||
} else if (!toggled && state.is<Loading>()) {
|
||||
if (const auto g = rawGlobalLoaderPtr()) {
|
||||
g->destroy();
|
||||
return;
|
||||
}
|
||||
if (localLoader && localLoader->get()->id() == id) {
|
||||
destroyLocalLoader();
|
||||
}
|
||||
}
|
||||
}, button->lifetime());
|
||||
|
||||
const auto contextMenu = button->lifetime()
|
||||
.make_state<base::unique_qptr<Ui::PopupMenu>>();
|
||||
const auto showMenu = [=] {
|
||||
if (!DictExists(id)) {
|
||||
return false;
|
||||
}
|
||||
*contextMenu = base::make_unique_q<Ui::PopupMenu>(button);
|
||||
contextMenu->get()->addAction(
|
||||
tr::lng_settings_manage_remove_dictionary(tr::now), [=] {
|
||||
Spellchecker::RemoveDictionary(id);
|
||||
dictionaryRemoved->fire({});
|
||||
});
|
||||
contextMenu->get()->popup(QCursor::pos());
|
||||
return true;
|
||||
};
|
||||
|
||||
base::install_event_filter(button, [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::ContextMenu && showMenu()) {
|
||||
return base::EventFilterResult::Cancel;
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
|
||||
if (const auto g = Spellchecker::GlobalLoader()) {
|
||||
if (g.get() && g->get()->id() == id) {
|
||||
setGlobalLoaderPtr(g);
|
||||
}
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
void Inner::setupContent(Dictionaries enabledDictionaries) {
|
||||
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||
|
||||
const auto queryStream = content->lifetime()
|
||||
.make_state<rpl::event_stream<QStringView>>();
|
||||
|
||||
for (const auto &dict : Spellchecker::Dictionaries()) {
|
||||
const auto id = dict.id;
|
||||
const auto row = AddButtonWithLoader(
|
||||
content,
|
||||
dict,
|
||||
ranges::contains(enabledDictionaries, id),
|
||||
queryStream->events());
|
||||
row->toggledValue(
|
||||
) | rpl::start_with_next([=](auto enabled) {
|
||||
if (enabled) {
|
||||
_enabledRows.push_back(id);
|
||||
} else {
|
||||
auto &rows = _enabledRows;
|
||||
rows.erase(ranges::remove(rows, id), end(rows));
|
||||
}
|
||||
}, row->lifetime());
|
||||
}
|
||||
|
||||
_queryCallback = [=](const QString &query) {
|
||||
if (query.size() >= kMaxQueryLength) {
|
||||
return;
|
||||
}
|
||||
queryStream->fire_copy(query);
|
||||
};
|
||||
|
||||
content->resizeToWidth(st::boxWidth);
|
||||
Ui::ResizeFitChild(this, content);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ManageDictionariesBox::ManageDictionariesBox(
|
||||
QWidget*,
|
||||
not_null<Main::Session*> session)
|
||||
: _session(session) {
|
||||
}
|
||||
|
||||
void ManageDictionariesBox::setInnerFocus() {
|
||||
_setInnerFocus();
|
||||
}
|
||||
|
||||
void ManageDictionariesBox::prepare() {
|
||||
const auto multiSelect = CreateMultiSelect(this);
|
||||
|
||||
const auto inner = setInnerWidget(
|
||||
object_ptr<Inner>(
|
||||
this,
|
||||
_session->settings().dictionariesEnabled()),
|
||||
st::boxScroll,
|
||||
multiSelect->height()
|
||||
);
|
||||
|
||||
multiSelect->setQueryChangedCallback(inner->queryCallback());
|
||||
_setInnerFocus = [=] {
|
||||
multiSelect->setInnerFocus();
|
||||
};
|
||||
|
||||
// The initial list of enabled rows may differ from the list of languages
|
||||
// in settings, so we should store it when box opens
|
||||
// and save it when box closes (don't do it when "Save" was pressed).
|
||||
const auto initialEnabledRows = inner->enabledRows();
|
||||
|
||||
setTitle(tr::lng_settings_manage_dictionaries());
|
||||
|
||||
addButton(tr::lng_settings_save(), [=] {
|
||||
_session->settings().setDictionariesEnabled(
|
||||
FilterEnabledDict(inner->enabledRows()));
|
||||
_session->saveSettingsDelayed();
|
||||
// Ignore boxClosing() when the Save button was pressed.
|
||||
lifetime().destroy();
|
||||
closeBox();
|
||||
});
|
||||
addButton(tr::lng_close(), [=] { closeBox(); });
|
||||
|
||||
boxClosing() | rpl::start_with_next([=] {
|
||||
_session->settings().setDictionariesEnabled(
|
||||
FilterEnabledDict(initialEnabledRows));
|
||||
_session->saveSettingsDelayed();
|
||||
}, lifetime());
|
||||
|
||||
setDimensionsToContent(st::boxWidth, inner);
|
||||
|
||||
using namespace rpl::mappers;
|
||||
const auto max = lifetime().make_state<int>(0);
|
||||
rpl::combine(
|
||||
inner->heightValue(),
|
||||
multiSelect->heightValue(),
|
||||
_1 + _2
|
||||
) | rpl::start_with_next([=](int height) {
|
||||
using std::min;
|
||||
accumulate_max(*max, height);
|
||||
setDimensions(st::boxWidth, min(*max, st::boxMaxListHeight), true);
|
||||
}, inner->lifetime());
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
#endif // !TDESKTOP_DISABLE_SPELLCHECK
|
||||
38
Telegram/SourceFiles/boxes/dictionaries_manager.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_SPELLCHECK
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class ManageDictionariesBox : public Ui::BoxContent {
|
||||
public:
|
||||
ManageDictionariesBox(
|
||||
QWidget*,
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
void setInnerFocus() override;
|
||||
|
||||
private:
|
||||
const not_null<Main::Session*> _session;
|
||||
Fn<void()> _setInnerFocus;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
#endif // !TDESKTOP_DISABLE_SPELLCHECK
|
||||
@@ -260,7 +260,7 @@ EditCaptionBox::EditCaptionBox(
|
||||
tr::lng_photo_caption(),
|
||||
editData);
|
||||
_field->setMaxLength(Global::CaptionLengthMax());
|
||||
_field->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
|
||||
_field->setSubmitSettings(_controller->session().settings().sendSubmitWay());
|
||||
_field->setInstantReplaces(Ui::InstantReplaces::Default());
|
||||
_field->setInstantReplacesEnabled(
|
||||
_controller->session().settings().replaceEmojiValue());
|
||||
@@ -488,97 +488,30 @@ void EditCaptionBox::updateEditMediaButton() {
|
||||
|
||||
void EditCaptionBox::createEditMediaButton() {
|
||||
const auto callback = [=](FileDialog::OpenResult &&result) {
|
||||
if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto isValidFile = [](QString mimeType) {
|
||||
if (mimeType == qstr("image/webp")) {
|
||||
Ui::show(
|
||||
Box<InformBox>(tr::lng_edit_media_invalid_file(tr::now)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
auto showBoxErrorCallback = [](tr::phrase<> t) {
|
||||
Ui::show(Box<InformBox>(t(tr::now)), Ui::LayerOption::KeepOther);
|
||||
};
|
||||
|
||||
if (!result.remoteContent.isEmpty()) {
|
||||
auto list = Storage::PreparedList::PreparedFileFromFilesDialog(
|
||||
std::move(result),
|
||||
_isAlbum,
|
||||
std::move(showBoxErrorCallback),
|
||||
st::sendMediaPreviewSize);
|
||||
|
||||
auto list = Storage::PrepareMediaFromImage(
|
||||
QImage(),
|
||||
std::move(result.remoteContent),
|
||||
st::sendMediaPreviewSize);
|
||||
|
||||
if (!isValidFile(list.files.front().mime)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isAlbum) {
|
||||
const auto albumMimes = {
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"video/mp4",
|
||||
};
|
||||
const auto file = &list.files.front();
|
||||
if (ranges::find(albumMimes, file->mime) == end(albumMimes)
|
||||
|| file->type == Storage::PreparedFile::AlbumType::None) {
|
||||
Ui::show(
|
||||
Box<InformBox>(tr::lng_edit_media_album_error(tr::now)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_preparedList = std::move(list);
|
||||
} else if (!result.paths.isEmpty()) {
|
||||
auto list = Storage::PrepareMediaList(
|
||||
QStringList(result.paths.front()),
|
||||
st::sendMediaPreviewSize);
|
||||
|
||||
// Don't rewrite _preparedList if new list is not valid for album.
|
||||
if (_isAlbum) {
|
||||
using Info = FileMediaInformation;
|
||||
|
||||
const auto media = &list.files.front().information->media;
|
||||
const auto valid = media->match([&](const Info::Image &data) {
|
||||
return Storage::ValidateThumbDimensions(
|
||||
data.data.width(),
|
||||
data.data.height())
|
||||
&& !data.animated;
|
||||
}, [&](Info::Video &data) {
|
||||
data.isGifv = false;
|
||||
return true;
|
||||
}, [](auto &&other) {
|
||||
return false;
|
||||
});
|
||||
if (!valid) {
|
||||
Ui::show(
|
||||
Box<InformBox>(tr::lng_edit_media_album_error(tr::now)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto info = QFileInfo(result.paths.front());
|
||||
if (!isValidFile(Core::MimeTypeForFile(info).name())) {
|
||||
return;
|
||||
}
|
||||
|
||||
_preparedList = std::move(list);
|
||||
} else {
|
||||
return;
|
||||
if (list) {
|
||||
_preparedList = std::move(*list);
|
||||
updateEditPreview();
|
||||
}
|
||||
|
||||
updateEditPreview();
|
||||
};
|
||||
|
||||
const auto buttonCallback = [=] {
|
||||
const auto filters = _isAlbum
|
||||
? QStringList(qsl("Image and Video Files (*.png *.jpg *.mp4)"))
|
||||
: QStringList(FileDialog::AllFilesFilter());
|
||||
? FileDialog::AlbumFilesFilter()
|
||||
: FileDialog::AllFilesFilter();
|
||||
FileDialog::GetOpenPath(
|
||||
this,
|
||||
tr::lng_choose_file(tr::now),
|
||||
filters.join(qsl(";;")),
|
||||
filters,
|
||||
crl::guard(this, callback));
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "app.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_mediaview.h"
|
||||
#include "styles/style_media_view.h"
|
||||
|
||||
class EditColorBox::Picker : public TWidget {
|
||||
public:
|
||||
|
||||
@@ -76,7 +76,7 @@ void MuteSettingsBox::prepare() {
|
||||
|
||||
_save = [=] {
|
||||
const auto muteForSeconds = group->value() * 3600;
|
||||
_peer->session().data().updateNotifySettings(
|
||||
_peer->owner().updateNotifySettings(
|
||||
_peer,
|
||||
muteForSeconds);
|
||||
closeBox();
|
||||
|
||||
@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "apiwrap.h"
|
||||
#include "mainwidget.h"
|
||||
#include "lang/lang_keys.h"
|
||||
@@ -30,33 +31,36 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace {
|
||||
|
||||
void ShareBotGame(not_null<UserData*> bot, not_null<PeerData*> chat) {
|
||||
const auto history = chat->owner().historyLoaded(chat);
|
||||
const auto randomId = rand_value<uint64>();
|
||||
const auto api = &chat->session().api();
|
||||
const auto requestId = api->request(MTPmessages_SendMedia(
|
||||
MTP_flags(0),
|
||||
chat->input,
|
||||
MTP_int(0),
|
||||
MTP_inputMediaGame(
|
||||
MTP_inputGameShortName(
|
||||
bot->inputUser,
|
||||
MTP_string(bot->botInfo->shareGameShortName))),
|
||||
MTP_string(),
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
MTP_int(0) // schedule_date
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
api->applyUpdates(result, randomId);
|
||||
}).fail([=](const RPCError &error) {
|
||||
api->sendMessageFail(error, chat);
|
||||
}).afterRequest(
|
||||
history ? history->sendRequestId : 0
|
||||
).send();
|
||||
|
||||
if (history) {
|
||||
history->sendRequestId = requestId;
|
||||
}
|
||||
const auto history = chat->owner().history(chat);
|
||||
auto &histories = history->owner().histories();
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
const auto randomId = rand_value<uint64>();
|
||||
const auto api = &chat->session().api();
|
||||
history->sendRequestId = api->request(MTPmessages_SendMedia(
|
||||
MTP_flags(0),
|
||||
chat->input,
|
||||
MTP_int(0),
|
||||
MTP_inputMediaGame(
|
||||
MTP_inputGameShortName(
|
||||
bot->inputUser,
|
||||
MTP_string(bot->botInfo->shareGameShortName))),
|
||||
MTP_string(),
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
MTP_int(0) // schedule_date
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
api->applyUpdates(result, randomId);
|
||||
finish();
|
||||
}).fail([=](const RPCError &error) {
|
||||
api->sendMessageFail(error, chat);
|
||||
finish();
|
||||
}).afterRequest(
|
||||
history->sendRequestId
|
||||
).send();
|
||||
return history->sendRequestId;
|
||||
});
|
||||
Ui::hideLayer();
|
||||
Ui::showPeerHistory(chat, ShowAtUnreadMsgId);
|
||||
}
|
||||
|
||||
@@ -500,6 +500,7 @@ object_ptr<Ui::RpWidget> Controller::createDescriptionEdit() {
|
||||
result->entity()->setInstantReplaces(Ui::InstantReplaces::Default());
|
||||
result->entity()->setInstantReplacesEnabled(
|
||||
_peer->session().settings().replaceEmojiValue());
|
||||
result->entity()->setSubmitSettings(_peer->session().settings().sendSubmitWay());
|
||||
Ui::Emoji::SuggestionsController::Init(
|
||||
_wrap->window(),
|
||||
result->entity(),
|
||||
|
||||
@@ -85,7 +85,7 @@ void RateCallBox::ratingChanged(int value) {
|
||||
Ui::InputField::Mode::MultiLine,
|
||||
tr::lng_call_rate_comment());
|
||||
_comment->show();
|
||||
_comment->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
|
||||
_comment->setSubmitSettings(_session->settings().sendSubmitWay());
|
||||
_comment->setMaxLength(kRateCallCommentLengthMax);
|
||||
_comment->resize(width() - (st::callRatingPadding.left() + st::callRatingPadding.right()), _comment->height());
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "main/main_session.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
@@ -106,7 +107,7 @@ void ReportBox::reasonChanged(Reason reason) {
|
||||
Ui::InputField::Mode::MultiLine,
|
||||
tr::lng_report_reason_description());
|
||||
_reasonOtherText->show();
|
||||
_reasonOtherText->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
|
||||
_reasonOtherText->setSubmitSettings(_peer->session().settings().sendSubmitWay());
|
||||
_reasonOtherText->setMaxLength(kReportReasonLengthMax);
|
||||
_reasonOtherText->resize(width() - (st::boxPadding.left() + st::boxOptionListPadding.left() + st::boxPadding.right()), _reasonOtherText->height());
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "confirm_box.h"
|
||||
#include "history/view/history_view_schedule_box.h"
|
||||
#include "core/file_utilities.h"
|
||||
#include "core/mime_type.h"
|
||||
@@ -48,8 +49,102 @@ namespace {
|
||||
constexpr auto kMinPreviewWidth = 20;
|
||||
constexpr auto kShrinkDuration = crl::time(150);
|
||||
constexpr auto kDragDuration = crl::time(200);
|
||||
const auto kStickerMimeString = qstr("image/webp");
|
||||
const auto kAnimatedStickerMimeString = qstr("application/x-tgsticker");
|
||||
|
||||
enum class ButtonType {
|
||||
Edit,
|
||||
Delete,
|
||||
None,
|
||||
};
|
||||
|
||||
inline bool CanAddUrls(const QList<QUrl> &urls) {
|
||||
return !urls.isEmpty() && ranges::find_if(
|
||||
urls,
|
||||
[](const QUrl &url) { return !url.isLocalFile(); }
|
||||
) == urls.end();
|
||||
}
|
||||
|
||||
inline bool IsFirstAlbumItem(const Storage::PreparedList &list) {
|
||||
using AlbumType = Storage::PreparedFile::AlbumType;
|
||||
return (list.files.size() > 0)
|
||||
&& (list.files.front().type != AlbumType::None);
|
||||
}
|
||||
|
||||
inline bool IsSingleItem(const Storage::PreparedList &list) {
|
||||
return list.files.size() == 1;
|
||||
}
|
||||
|
||||
QRect PaintAlbumThumbButtons(
|
||||
Painter &p,
|
||||
QPoint point,
|
||||
int outerWidth,
|
||||
float64 shrinkProgress) {
|
||||
|
||||
const auto skipInternal = st::sendBoxAlbumGroupEditInternalSkip;
|
||||
const auto size = st::sendBoxAlbumGroupHeight;
|
||||
const auto skipRight = st::sendBoxAlbumGroupSkipRight;
|
||||
const auto skipTop = st::sendBoxAlbumGroupSkipTop;
|
||||
const auto groupWidth = size * 2 + skipInternal;
|
||||
|
||||
// If the width is tiny, it would be better to not display the buttons.
|
||||
if (groupWidth > outerWidth) {
|
||||
return QRect();
|
||||
}
|
||||
|
||||
// If the width is too small,
|
||||
// it would be better to display the buttons in the center.
|
||||
const auto groupX = point.x() + ((groupWidth + skipRight * 2 > outerWidth)
|
||||
? (outerWidth - groupWidth) / 2
|
||||
: outerWidth - skipRight - groupWidth);
|
||||
const auto groupY = point.y() + skipTop;
|
||||
const auto deleteLeft = skipInternal + size;
|
||||
|
||||
p.setOpacity(1.0 - shrinkProgress);
|
||||
|
||||
QRect groupRect(groupX, groupY, groupWidth, size);
|
||||
App::roundRect(
|
||||
p,
|
||||
groupRect,
|
||||
st::callFingerprintBg,
|
||||
SendFilesBoxAlbumGroupCorners);
|
||||
|
||||
const auto editP = st::sendBoxAlbumGroupEditButtonIconPosition;
|
||||
const auto deleteP = st::sendBoxAlbumGroupDeleteButtonIconPosition;
|
||||
|
||||
st::sendBoxAlbumGroupEditButtonIcon.paintInCenter(
|
||||
p,
|
||||
QRect(groupX + editP.x(), groupY + editP.y(), size, size));
|
||||
st::sendBoxAlbumGroupDeleteButtonIcon.paintInCenter(
|
||||
p,
|
||||
QRect(
|
||||
groupX + deleteLeft + deleteP.x(),
|
||||
groupY + deleteP.y(),
|
||||
size,
|
||||
size));
|
||||
p.setOpacity(1);
|
||||
|
||||
return groupRect;
|
||||
}
|
||||
|
||||
void FileDialogCallback(
|
||||
FileDialog::OpenResult &&result,
|
||||
bool isAlbum,
|
||||
Fn<void(Storage::PreparedList)> callback) {
|
||||
auto showBoxErrorCallback = [](tr::phrase<> text) {
|
||||
Ui::show(Box<InformBox>(text(tr::now)), Ui::LayerOption::KeepOther);
|
||||
};
|
||||
|
||||
auto list = Storage::PreparedList::PreparedFileFromFilesDialog(
|
||||
std::move(result),
|
||||
isAlbum,
|
||||
std::move(showBoxErrorCallback),
|
||||
st::sendMediaPreviewSize);
|
||||
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback(std::move(*list));
|
||||
}
|
||||
|
||||
class SingleMediaPreview : public Ui::RpWidget {
|
||||
public:
|
||||
@@ -123,7 +218,10 @@ class AlbumThumb {
|
||||
public:
|
||||
AlbumThumb(
|
||||
const Storage::PreparedFile &file,
|
||||
const Ui::GroupMediaLayout &layout);
|
||||
const Ui::GroupMediaLayout &layout,
|
||||
QWidget *parent,
|
||||
Fn<void()> editCallback,
|
||||
Fn<void()> deleteCallback);
|
||||
|
||||
void moveToLayout(const Ui::GroupMediaLayout &layout);
|
||||
void animateLayoutToInitial();
|
||||
@@ -141,6 +239,8 @@ public:
|
||||
void paintFile(Painter &p, int left, int top, int outerWidth);
|
||||
|
||||
bool containsPoint(QPoint position) const;
|
||||
bool buttonsContainPoint(QPoint position) const;
|
||||
ButtonType buttonTypeFromPoint(QPoint position) const;
|
||||
int distanceTo(QPoint position) const;
|
||||
bool isPointAfter(QPoint position) const;
|
||||
void moveInAlbum(QPoint to);
|
||||
@@ -148,6 +248,8 @@ public:
|
||||
void suggestMove(float64 delta, Fn<void()> callback);
|
||||
void finishAnimations();
|
||||
|
||||
void updateFileRow(int row);
|
||||
|
||||
private:
|
||||
QRect countRealGeometry() const;
|
||||
QRect countCurrentGeometry(float64 progress) const;
|
||||
@@ -173,11 +275,19 @@ private:
|
||||
Ui::Animations::Simple _suggestedMoveAnimation;
|
||||
int _lastShrinkValue = 0;
|
||||
|
||||
QRect _lastRectOfButtons;
|
||||
|
||||
object_ptr<Ui::IconButton> _editMedia = nullptr;
|
||||
object_ptr<Ui::IconButton> _deleteMedia = nullptr;
|
||||
|
||||
};
|
||||
|
||||
AlbumThumb::AlbumThumb(
|
||||
const Storage::PreparedFile &file,
|
||||
const Ui::GroupMediaLayout &layout)
|
||||
const Ui::GroupMediaLayout &layout,
|
||||
QWidget *parent,
|
||||
Fn<void()> editCallback,
|
||||
Fn<void()> deleteCallback)
|
||||
: _layout(layout)
|
||||
, _fullPreview(file.preview)
|
||||
, _shrinkSize(int(std::ceil(st::historyMessageRadius / 1.4)))
|
||||
@@ -218,7 +328,11 @@ AlbumThumb::AlbumThumb(
|
||||
|
||||
const auto availableFileWidth = st::sendMediaPreviewSize
|
||||
- st::sendMediaFileThumbSkip
|
||||
- st::sendMediaFileThumbSize;
|
||||
- st::sendMediaFileThumbSize
|
||||
// Right buttons.
|
||||
- st::sendBoxAlbumGroupButtonFile.width * 2
|
||||
- st::sendBoxAlbumGroupEditInternalSkip
|
||||
- st::sendBoxAlbumGroupSkipRight;
|
||||
const auto filepath = file.path;
|
||||
if (filepath.isEmpty()) {
|
||||
_name = filedialogDefaultName(
|
||||
@@ -245,6 +359,45 @@ AlbumThumb::AlbumThumb(
|
||||
_nameWidth = st::semiboldFont->width(_name);
|
||||
}
|
||||
_statusWidth = st::normalFont->width(_status);
|
||||
|
||||
_editMedia.create(parent, st::sendBoxAlbumGroupButtonFile);
|
||||
_deleteMedia.create(parent, st::sendBoxAlbumGroupButtonFile);
|
||||
|
||||
const auto duration = st::historyAttach.ripple.hideDuration;
|
||||
_editMedia->setClickedCallback(App::LambdaDelayed(
|
||||
duration,
|
||||
parent,
|
||||
std::move(editCallback)));
|
||||
_deleteMedia->setClickedCallback(App::LambdaDelayed(
|
||||
duration,
|
||||
parent,
|
||||
std::move(deleteCallback)));
|
||||
|
||||
_editMedia->setIconOverride(&st::editMediaButtonIconFile);
|
||||
_deleteMedia->setIconOverride(&st::sendBoxAlbumGroupDeleteButtonIconFile);
|
||||
|
||||
updateFileRow(-1);
|
||||
}
|
||||
|
||||
void AlbumThumb::updateFileRow(int row) {
|
||||
if (row < 0) {
|
||||
_editMedia->hide();
|
||||
_deleteMedia->hide();
|
||||
return;
|
||||
}
|
||||
_editMedia->show();
|
||||
_deleteMedia->show();
|
||||
|
||||
const auto fileHeight = st::sendMediaFileThumbSize
|
||||
+ st::sendMediaFileThumbSkip;
|
||||
|
||||
const auto top = row * fileHeight + st::sendBoxAlbumGroupSkipTop;
|
||||
const auto size = st::editMediaButtonSize;
|
||||
|
||||
auto right = st::sendBoxAlbumGroupSkipRight + size;
|
||||
_deleteMedia->moveToRight(right, top);
|
||||
right += st::sendBoxAlbumGroupEditInternalSkip + size;
|
||||
_editMedia->moveToRight(right, top);
|
||||
}
|
||||
|
||||
void AlbumThumb::resetLayoutAnimation() {
|
||||
@@ -337,6 +490,12 @@ void AlbumThumb::paintInAlbum(
|
||||
}
|
||||
st::historyFileThumbPlay.paintInCenter(p, inner);
|
||||
}
|
||||
|
||||
_lastRectOfButtons = PaintAlbumThumbButtons(
|
||||
p,
|
||||
{ x, y },
|
||||
geometry.width(),
|
||||
shrinkProgress);
|
||||
}
|
||||
|
||||
void AlbumThumb::prepareCache(QSize size, int shrink) {
|
||||
@@ -490,6 +649,12 @@ void AlbumThumb::paintPhoto(Painter &p, int left, int top, int outerWidth) {
|
||||
top,
|
||||
outerWidth,
|
||||
_photo);
|
||||
|
||||
_lastRectOfButtons = PaintAlbumThumbButtons(
|
||||
p,
|
||||
{ left, top },
|
||||
st::sendMediaPreviewSize,
|
||||
0);
|
||||
}
|
||||
|
||||
void AlbumThumb::paintFile(Painter &p, int left, int top, int outerWidth) {
|
||||
@@ -520,6 +685,19 @@ bool AlbumThumb::containsPoint(QPoint position) const {
|
||||
return _layout.geometry.contains(position);
|
||||
}
|
||||
|
||||
bool AlbumThumb::buttonsContainPoint(QPoint position) const {
|
||||
return _lastRectOfButtons.contains(position);
|
||||
}
|
||||
|
||||
ButtonType AlbumThumb::buttonTypeFromPoint(QPoint position) const {
|
||||
if (!buttonsContainPoint(position)) {
|
||||
return ButtonType::None;
|
||||
}
|
||||
return (position.x() < _lastRectOfButtons.center().x())
|
||||
? ButtonType::Edit
|
||||
: ButtonType::Delete;
|
||||
}
|
||||
|
||||
int AlbumThumb::distanceTo(QPoint position) const {
|
||||
const auto delta = (_layout.geometry.center() - position);
|
||||
return QPoint::dotProduct(delta, delta);
|
||||
@@ -601,14 +779,12 @@ SingleMediaPreview *SingleMediaPreview::Create(
|
||||
preview.height())) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto sticker = (file.information->filemime == kStickerMimeString)
|
||||
|| (file.information->filemime == kAnimatedStickerMimeString);
|
||||
return Ui::CreateChild<SingleMediaPreview>(
|
||||
parent,
|
||||
controller,
|
||||
preview,
|
||||
animated,
|
||||
sticker,
|
||||
Core::IsMimeSticker(file.information->filemime),
|
||||
animationPreview ? file.path : QString());
|
||||
}
|
||||
|
||||
@@ -960,6 +1136,14 @@ public:
|
||||
void setSendWay(SendFilesWay way);
|
||||
std::vector<int> takeOrder();
|
||||
|
||||
auto thumbDeleted() {
|
||||
return _thumbDeleted.events();
|
||||
}
|
||||
|
||||
auto thumbChanged() {
|
||||
return _thumbChanged.events();
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
@@ -974,6 +1158,15 @@ private:
|
||||
void prepareThumbs();
|
||||
void updateSizeAnimated(const std::vector<Ui::GroupMediaLayout> &layout);
|
||||
void updateSize();
|
||||
void updateFileRows();
|
||||
|
||||
int thumbIndex(AlbumThumb *thumb);
|
||||
AlbumThumb *thumbUnderCursor();
|
||||
void deleteThumbByIndex(int index);
|
||||
void changeThumbByIndex(int index);
|
||||
void thumbButtonsCallback(
|
||||
not_null<AlbumThumb*> thumb,
|
||||
ButtonType type);
|
||||
|
||||
void paintAlbum(Painter &p) const;
|
||||
void paintPhotos(Painter &p, QRect clip) const;
|
||||
@@ -1003,6 +1196,9 @@ private:
|
||||
AlbumThumb *_paintedAbove = nullptr;
|
||||
QPoint _draggedStartPosition;
|
||||
|
||||
rpl::event_stream<int> _thumbDeleted;
|
||||
rpl::event_stream<int> _thumbChanged;
|
||||
|
||||
mutable Ui::Animations::Simple _thumbsHeightAnimation;
|
||||
mutable Ui::Animations::Simple _shrinkAnimation;
|
||||
mutable Ui::Animations::Simple _finishDragAnimation;
|
||||
@@ -1019,6 +1215,7 @@ SendFilesBox::AlbumPreview::AlbumPreview(
|
||||
setMouseTracking(true);
|
||||
prepareThumbs();
|
||||
updateSize();
|
||||
updateFileRows();
|
||||
}
|
||||
|
||||
void SendFilesBox::AlbumPreview::setSendWay(SendFilesWay way) {
|
||||
@@ -1027,9 +1224,18 @@ void SendFilesBox::AlbumPreview::setSendWay(SendFilesWay way) {
|
||||
_sendWay = way;
|
||||
}
|
||||
updateSize();
|
||||
updateFileRows();
|
||||
update();
|
||||
}
|
||||
|
||||
void SendFilesBox::AlbumPreview::updateFileRows() {
|
||||
Expects(_order.size() == _thumbs.size());
|
||||
const auto isFile = (_sendWay == SendFilesWay::Files);
|
||||
for (auto i = 0; i < _order.size(); i++) {
|
||||
_thumbs[i]->updateFileRow(isFile ? _order[i] : -1);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> SendFilesBox::AlbumPreview::takeOrder() {
|
||||
auto reordered = std::vector<std::unique_ptr<AlbumThumb>>();
|
||||
reordered.reserve(_thumbs.size());
|
||||
@@ -1071,7 +1277,10 @@ void SendFilesBox::AlbumPreview::prepareThumbs() {
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
_thumbs.push_back(std::make_unique<AlbumThumb>(
|
||||
_list.files[i],
|
||||
layout[i]));
|
||||
layout[i],
|
||||
this,
|
||||
[=] { changeThumbByIndex(thumbIndex(thumbUnderCursor())); },
|
||||
[=] { deleteThumbByIndex(thumbIndex(thumbUnderCursor())); }));
|
||||
}
|
||||
_thumbsHeight = countLayoutHeight(layout);
|
||||
_photosHeight = ranges::accumulate(ranges::view::all(
|
||||
@@ -1094,9 +1303,27 @@ int SendFilesBox::AlbumPreview::contentTop() const {
|
||||
|
||||
AlbumThumb *SendFilesBox::AlbumPreview::findThumb(QPoint position) const {
|
||||
position -= QPoint(contentLeft(), contentTop());
|
||||
const auto i = ranges::find_if(_thumbs, [&](const auto &thumb) {
|
||||
return thumb->containsPoint(position);
|
||||
});
|
||||
|
||||
auto top = 0;
|
||||
const auto isPhotosWay = (_sendWay == SendFilesWay::Photos);
|
||||
const auto skip = isPhotosWay
|
||||
? st::sendMediaPreviewPhotoSkip
|
||||
: st::sendMediaFileThumbSkip;
|
||||
auto find = [&](const auto &thumb) {
|
||||
if (_sendWay == SendFilesWay::Album) {
|
||||
return thumb->containsPoint(position);
|
||||
} else if (isPhotosWay || _sendWay == SendFilesWay::Files) {
|
||||
const auto bottom = top + (isPhotosWay
|
||||
? thumb->photoHeight()
|
||||
: st::sendMediaFileThumbSize);
|
||||
const auto isUnderTop = (position.y() > top);
|
||||
top = bottom + skip;
|
||||
return isUnderTop && (position.y() < bottom);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const auto i = ranges::find_if(_thumbs, std::move(find));
|
||||
return (i == _thumbs.end()) ? nullptr : i->get();
|
||||
}
|
||||
|
||||
@@ -1277,6 +1504,56 @@ void SendFilesBox::AlbumPreview::paintFiles(Painter &p, QRect clip) const {
|
||||
}
|
||||
}
|
||||
|
||||
int SendFilesBox::AlbumPreview::thumbIndex(AlbumThumb *thumb) {
|
||||
if (!thumb) {
|
||||
return -1;
|
||||
}
|
||||
const auto thumbIt = ranges::find_if(_thumbs, [&](auto &t) {
|
||||
return t.get() == thumb;
|
||||
});
|
||||
Expects(thumbIt != _thumbs.end());
|
||||
return std::distance(_thumbs.begin(), thumbIt);
|
||||
}
|
||||
|
||||
AlbumThumb *SendFilesBox::AlbumPreview::thumbUnderCursor() {
|
||||
return findThumb(mapFromGlobal(QCursor::pos()));
|
||||
}
|
||||
|
||||
void SendFilesBox::AlbumPreview::deleteThumbByIndex(int index) {
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
const auto orderIt = ranges::find(_order, index);
|
||||
Expects(orderIt != _order.end());
|
||||
|
||||
_order.erase(orderIt);
|
||||
ranges::for_each(_order, [=](auto &i) {
|
||||
if (i > index) {
|
||||
i--;
|
||||
}
|
||||
});
|
||||
_thumbDeleted.fire(std::move(index));
|
||||
}
|
||||
|
||||
void SendFilesBox::AlbumPreview::changeThumbByIndex(int index) {
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
_thumbChanged.fire(std::move(index));
|
||||
}
|
||||
|
||||
void SendFilesBox::AlbumPreview::thumbButtonsCallback(
|
||||
not_null<AlbumThumb*> thumb,
|
||||
ButtonType type) {
|
||||
const auto index = thumbIndex(thumb);
|
||||
|
||||
switch (type) {
|
||||
case ButtonType::None: return;
|
||||
case ButtonType::Edit: changeThumbByIndex(index); break;
|
||||
case ButtonType::Delete: deleteThumbByIndex(index); break;
|
||||
}
|
||||
}
|
||||
|
||||
void SendFilesBox::AlbumPreview::mousePressEvent(QMouseEvent *e) {
|
||||
if (_finishDragAnimation.animating()) {
|
||||
return;
|
||||
@@ -1284,6 +1561,10 @@ void SendFilesBox::AlbumPreview::mousePressEvent(QMouseEvent *e) {
|
||||
const auto position = e->pos();
|
||||
cancelDrag();
|
||||
if (const auto thumb = findThumb(position)) {
|
||||
if (thumb->buttonsContainPoint(e->pos())) {
|
||||
thumbButtonsCallback(thumb, thumb->buttonTypeFromPoint(e->pos()));
|
||||
return;
|
||||
}
|
||||
_paintedAbove = _suggestedThumb = _draggedThumb = thumb;
|
||||
_draggedStartPosition = position;
|
||||
_shrinkAnimation.start([=] { update(); }, 0., 1., kShrinkDuration);
|
||||
@@ -1291,19 +1572,26 @@ void SendFilesBox::AlbumPreview::mousePressEvent(QMouseEvent *e) {
|
||||
}
|
||||
|
||||
void SendFilesBox::AlbumPreview::mouseMoveEvent(QMouseEvent *e) {
|
||||
if (_sendWay != SendFilesWay::Album) {
|
||||
if (_sendWay == SendFilesWay::Files) {
|
||||
applyCursor(style::cur_default);
|
||||
return;
|
||||
}
|
||||
if (_draggedThumb) {
|
||||
const auto isAlbum = (_sendWay == SendFilesWay::Album);
|
||||
if (isAlbum && _draggedThumb) {
|
||||
const auto position = e->pos();
|
||||
_draggedThumb->moveInAlbum(position - _draggedStartPosition);
|
||||
updateSuggestedDrag(_draggedThumb->center());
|
||||
update();
|
||||
} else {
|
||||
const auto cursor = findThumb(e->pos())
|
||||
const auto thumb = findThumb(e->pos());
|
||||
const auto regularCursor = isAlbum
|
||||
? style::cur_sizeall
|
||||
: style::cur_default;
|
||||
const auto cursor = thumb
|
||||
? (thumb->buttonsContainPoint(e->pos())
|
||||
? style::cur_pointer
|
||||
: regularCursor)
|
||||
: style::cur_default;
|
||||
applyCursor(cursor);
|
||||
}
|
||||
}
|
||||
@@ -1405,7 +1693,8 @@ void SendFilesBox::initPreview(rpl::producer<int> desiredPreviewHeight) {
|
||||
) | rpl::start_with_next([=](int height) {
|
||||
setDimensions(
|
||||
st::boxWideWidth,
|
||||
std::min(st::sendMediaPreviewHeightMax, height));
|
||||
std::min(st::sendMediaPreviewHeightMax, height),
|
||||
true);
|
||||
}, lifetime());
|
||||
|
||||
if (_preview) {
|
||||
@@ -1414,7 +1703,7 @@ void SendFilesBox::initPreview(rpl::producer<int> desiredPreviewHeight) {
|
||||
}
|
||||
|
||||
void SendFilesBox::prepareSingleFilePreview() {
|
||||
Expects(_list.files.size() == 1);
|
||||
Expects(IsSingleItem(_list));
|
||||
|
||||
const auto &file = _list.files[0];
|
||||
const auto media = SingleMediaPreview::Create(this, _controller, file);
|
||||
@@ -1442,11 +1731,74 @@ void SendFilesBox::prepareAlbumPreview() {
|
||||
this,
|
||||
_list,
|
||||
_sendWay->value()));
|
||||
|
||||
addThumbButtonHandlers(wrap);
|
||||
|
||||
_preview = wrap;
|
||||
_albumPreview->show();
|
||||
setupShadows(wrap, _albumPreview);
|
||||
|
||||
initPreview(_albumPreview->desiredHeightValue());
|
||||
|
||||
crl::on_main([=] {
|
||||
wrap->scrollToY(_lastScrollTop);
|
||||
_lastScrollTop = 0;
|
||||
});
|
||||
}
|
||||
|
||||
void SendFilesBox::addThumbButtonHandlers(not_null<Ui::ScrollArea*> wrap) {
|
||||
_albumPreview->thumbDeleted(
|
||||
) | rpl::start_with_next([=](auto index) {
|
||||
_lastScrollTop = wrap->scrollTop();
|
||||
|
||||
_list.files.erase(_list.files.begin() + index);
|
||||
applyAlbumOrder();
|
||||
|
||||
if (_preview) {
|
||||
_preview->deleteLater();
|
||||
}
|
||||
_albumPreview = nullptr;
|
||||
|
||||
if (IsSingleItem(_list)) {
|
||||
_list.albumIsPossible = false;
|
||||
if (_sendWay->value() == SendFilesWay::Album) {
|
||||
_sendWay->setValue(SendFilesWay::Photos);
|
||||
}
|
||||
}
|
||||
|
||||
_compressConfirm = _compressConfirmInitial;
|
||||
refreshAllAfterAlbumChanges();
|
||||
|
||||
}, _albumPreview->lifetime());
|
||||
|
||||
_albumPreview->thumbChanged(
|
||||
) | rpl::start_with_next([=](auto index) {
|
||||
_lastScrollTop = wrap->scrollTop();
|
||||
|
||||
const auto callback = [=](FileDialog::OpenResult &&result) {
|
||||
FileDialogCallback(
|
||||
std::move(result),
|
||||
true,
|
||||
[=] (auto list) {
|
||||
_list.files[index] = std::move(list.files.front());
|
||||
applyAlbumOrder();
|
||||
|
||||
if (_preview) {
|
||||
_preview->deleteLater();
|
||||
}
|
||||
_albumPreview = nullptr;
|
||||
|
||||
refreshAllAfterAlbumChanges();
|
||||
});
|
||||
};
|
||||
|
||||
FileDialog::GetOpenPath(
|
||||
this,
|
||||
tr::lng_choose_file(tr::now),
|
||||
FileDialog::AlbumFilesFilter(),
|
||||
crl::guard(this, callback));
|
||||
|
||||
}, _albumPreview->lifetime());
|
||||
}
|
||||
|
||||
void SendFilesBox::setupShadows(
|
||||
@@ -1480,9 +1832,13 @@ 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_schedule_button()),
|
||||
[=] { send({}); });
|
||||
if (_sendType == Api::SendType::Normal) {
|
||||
SetupSendMenu(
|
||||
SetupSendMenuAndShortcuts(
|
||||
_send,
|
||||
[=] { return _sendMenuType; },
|
||||
[=] { sendSilent(); },
|
||||
@@ -1497,6 +1853,49 @@ void SendFilesBox::prepare() {
|
||||
_cancelledCallback();
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
const auto title = tr::lng_stickers_featured_add(tr::now) + qsl("...");
|
||||
_addFileToAlbum = addLeftButton(
|
||||
rpl::single(title),
|
||||
App::LambdaDelayed(st::historyAttach.ripple.hideDuration, this, [=] {
|
||||
openDialogToAddFileToAlbum();
|
||||
}));
|
||||
|
||||
updateLeftButtonVisibility();
|
||||
}
|
||||
|
||||
void SendFilesBox::updateLeftButtonVisibility() {
|
||||
const auto isAlbum = _list.albumIsPossible
|
||||
&& (_list.files.size() < Storage::MaxAlbumItems());
|
||||
if (isAlbum || (IsSingleItem(_list) && IsFirstAlbumItem(_list))) {
|
||||
_addFileToAlbum->show();
|
||||
} else {
|
||||
_addFileToAlbum->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void SendFilesBox::refreshAllAfterAlbumChanges() {
|
||||
refreshAlbumMediaCount();
|
||||
preparePreview();
|
||||
captionResized();
|
||||
updateLeftButtonVisibility();
|
||||
}
|
||||
|
||||
void SendFilesBox::openDialogToAddFileToAlbum() {
|
||||
const auto callback = [=](FileDialog::OpenResult &&result) {
|
||||
FileDialogCallback(
|
||||
std::move(result),
|
||||
true,
|
||||
[=] (auto list) {
|
||||
addFiles(std::move(list));
|
||||
});
|
||||
};
|
||||
|
||||
FileDialog::GetOpenPaths(
|
||||
this,
|
||||
tr::lng_choose_file(tr::now),
|
||||
FileDialog::AlbumFilesFilter(),
|
||||
crl::guard(this, callback));
|
||||
}
|
||||
|
||||
void SendFilesBox::initSendWay() {
|
||||
@@ -1574,7 +1973,7 @@ void SendFilesBox::refreshAlbumMediaCount() {
|
||||
}
|
||||
|
||||
void SendFilesBox::preparePreview() {
|
||||
if (_list.files.size() == 1) {
|
||||
if (IsSingleItem(_list)) {
|
||||
prepareSingleFilePreview();
|
||||
} else {
|
||||
if (_list.albumIsPossible) {
|
||||
@@ -1611,7 +2010,7 @@ void SendFilesBox::setupSendWayControls() {
|
||||
addRadio(_sendAlbum, SendFilesWay::Album, tr::lng_send_album(tr::now));
|
||||
}
|
||||
if (!_list.albumIsPossible || _albumPhotosCount > 0) {
|
||||
addRadio(_sendPhotos, SendFilesWay::Photos, (_list.files.size() == 1)
|
||||
addRadio(_sendPhotos, SendFilesWay::Photos, IsSingleItem(_list)
|
||||
? tr::lng_send_photo(tr::now)
|
||||
: (_albumVideosCount > 0)
|
||||
? tr::lng_send_separate_photos_videos(tr::now)
|
||||
@@ -1619,7 +2018,7 @@ void SendFilesBox::setupSendWayControls() {
|
||||
? tr::lng_send_separate_photos(tr::now)
|
||||
: tr::lng_send_photos(tr::now, lt_count, _list.files.size())));
|
||||
}
|
||||
addRadio(_sendFiles, SendFilesWay::Files, (_list.files.size() == 1)
|
||||
addRadio(_sendFiles, SendFilesWay::Files, (IsSingleItem(_list))
|
||||
? tr::lng_send_file(tr::now)
|
||||
: tr::lng_send_files(tr::now, lt_count, _list.files.size()));
|
||||
}
|
||||
@@ -1647,7 +2046,7 @@ void SendFilesBox::applyAlbumOrder() {
|
||||
|
||||
void SendFilesBox::setupCaption() {
|
||||
_caption->setMaxLength(Global::CaptionLengthMax());
|
||||
_caption->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
|
||||
_caption->setSubmitSettings(_controller->session().settings().sendSubmitWay());
|
||||
connect(_caption, &Ui::InputField::resized, [=] {
|
||||
captionResized();
|
||||
});
|
||||
@@ -1746,16 +2145,9 @@ void SendFilesBox::captionResized() {
|
||||
update();
|
||||
}
|
||||
|
||||
bool SendFilesBox::canAddUrls(const QList<QUrl> &urls) const {
|
||||
return !urls.isEmpty() && ranges::find_if(
|
||||
urls,
|
||||
[](const QUrl &url) { return !url.isLocalFile(); }
|
||||
) == urls.end();
|
||||
}
|
||||
|
||||
bool SendFilesBox::canAddFiles(not_null<const QMimeData*> data) const {
|
||||
const auto urls = data->hasUrls() ? data->urls() : QList<QUrl>();
|
||||
auto filesCount = canAddUrls(urls) ? urls.size() : 0;
|
||||
auto filesCount = CanAddUrls(urls) ? urls.size() : 0;
|
||||
if (!filesCount && data->hasImage()) {
|
||||
++filesCount;
|
||||
}
|
||||
@@ -1764,8 +2156,7 @@ bool SendFilesBox::canAddFiles(not_null<const QMimeData*> data) const {
|
||||
return false;
|
||||
} else if (_list.files.size() > 1 && !_albumPreview) {
|
||||
return false;
|
||||
} else if (_list.files.front().type
|
||||
== Storage::PreparedFile::AlbumType::None) {
|
||||
} else if (!IsFirstAlbumItem(_list)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -1774,7 +2165,7 @@ bool SendFilesBox::canAddFiles(not_null<const QMimeData*> data) const {
|
||||
bool SendFilesBox::addFiles(not_null<const QMimeData*> data) {
|
||||
auto list = [&] {
|
||||
const auto urls = data->hasUrls() ? data->urls() : QList<QUrl>();
|
||||
auto result = canAddUrls(urls)
|
||||
auto result = CanAddUrls(urls)
|
||||
? Storage::PrepareMediaList(urls, st::sendMediaPreviewSize)
|
||||
: Storage::PreparedList(
|
||||
Storage::PreparedList::Error::EmptyFile,
|
||||
@@ -1792,35 +2183,35 @@ bool SendFilesBox::addFiles(not_null<const QMimeData*> data) {
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
if (_list.files.size() + list.files.size() > Storage::MaxAlbumItems()) {
|
||||
return addFiles(std::move(list));
|
||||
}
|
||||
|
||||
bool SendFilesBox::addFiles(Storage::PreparedList list) {
|
||||
const auto sumFiles = _list.files.size() + list.files.size();
|
||||
const auto cutToAlbumSize = (sumFiles > Storage::MaxAlbumItems());
|
||||
if (list.error != Storage::PreparedList::Error::None) {
|
||||
return false;
|
||||
} else if (list.error != Storage::PreparedList::Error::None) {
|
||||
} else if (!IsSingleItem(list) && !list.albumIsPossible) {
|
||||
return false;
|
||||
} else if (list.files.size() != 1 && !list.albumIsPossible) {
|
||||
return false;
|
||||
} else if (list.files.front().type
|
||||
== Storage::PreparedFile::AlbumType::None) {
|
||||
} else if (!IsFirstAlbumItem(list)) {
|
||||
return false;
|
||||
} else if (_list.files.size() > 1 && !_albumPreview) {
|
||||
return false;
|
||||
} else if (_list.files.front().type
|
||||
== Storage::PreparedFile::AlbumType::None) {
|
||||
} else if (!IsFirstAlbumItem(_list)) {
|
||||
return false;
|
||||
}
|
||||
applyAlbumOrder();
|
||||
delete base::take(_preview);
|
||||
_albumPreview = nullptr;
|
||||
|
||||
if (_list.files.size() == 1
|
||||
if (IsSingleItem(_list)
|
||||
&& _sendWay->value() == SendFilesWay::Photos) {
|
||||
_sendWay->setValue(SendFilesWay::Album);
|
||||
}
|
||||
_list.mergeToEnd(std::move(list));
|
||||
_list.mergeToEnd(std::move(list), cutToAlbumSize);
|
||||
|
||||
_compressConfirm = _compressConfirmInitial;
|
||||
refreshAlbumMediaCount();
|
||||
preparePreview();
|
||||
captionResized();
|
||||
refreshAllAfterAlbumChanges();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1858,7 +2249,9 @@ void SendFilesBox::updateBoxSize() {
|
||||
}
|
||||
|
||||
void SendFilesBox::keyPressEvent(QKeyEvent *e) {
|
||||
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
|
||||
if (e->matches(QKeySequence::Open) && !_addFileToAlbum->isHidden()) {
|
||||
openDialogToAddFileToAlbum();
|
||||
} else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
|
||||
const auto modifiers = e->modifiers();
|
||||
const auto ctrl = modifiers.testFlag(Qt::ControlModifier)
|
||||
|| modifiers.testFlag(Qt::MetaModifier);
|
||||
@@ -1937,7 +2330,9 @@ void SendFilesBox::setInnerFocus() {
|
||||
void SendFilesBox::send(
|
||||
Api::SendOptions options,
|
||||
bool ctrlShiftEnter) {
|
||||
if (_sendType == Api::SendType::Scheduled && !options.scheduled) {
|
||||
if ((_sendType == Api::SendType::Scheduled
|
||||
|| _sendType == Api::SendType::ScheduledToUser)
|
||||
&& !options.scheduled) {
|
||||
return sendScheduled();
|
||||
}
|
||||
|
||||
@@ -1982,9 +2377,12 @@ void SendFilesBox::sendSilent() {
|
||||
}
|
||||
|
||||
void SendFilesBox::sendScheduled() {
|
||||
const auto type = (_sendType == Api::SendType::ScheduledToUser)
|
||||
? SendMenuType::ScheduledToUser
|
||||
: _sendMenuType;
|
||||
const auto callback = [=](Api::SendOptions options) { send(options); };
|
||||
Ui::show(
|
||||
HistoryView::PrepareScheduleBox(this, _sendMenuType, callback),
|
||||
HistoryView::PrepareScheduleBox(this, type, callback),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
|
||||
@@ -120,9 +120,15 @@ private:
|
||||
void updateControlsGeometry();
|
||||
void updateCaptionPlaceholder();
|
||||
|
||||
void addThumbButtonHandlers(not_null<Ui::ScrollArea*> wrap);
|
||||
|
||||
bool canAddFiles(not_null<const QMimeData*> data) const;
|
||||
bool canAddUrls(const QList<QUrl> &urls) const;
|
||||
bool addFiles(not_null<const QMimeData*> data);
|
||||
bool addFiles(Storage::PreparedList list);
|
||||
|
||||
void openDialogToAddFileToAlbum();
|
||||
void updateLeftButtonVisibility();
|
||||
void refreshAllAfterAlbumChanges();
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const Api::SendType _sendType = Api::SendType();
|
||||
@@ -163,6 +169,9 @@ private:
|
||||
int _albumVideosCount = 0;
|
||||
int _albumPhotosCount = 0;
|
||||
|
||||
int _lastScrollTop = 0;
|
||||
|
||||
QPointer<Ui::RoundButton> _send;
|
||||
QPointer<Ui::RoundButton> _addFileToAlbum;
|
||||
|
||||
};
|
||||
|
||||
@@ -206,6 +206,7 @@ void ShareBox::prepareCommentField() {
|
||||
field->setMarkdownReplacesEnabled(rpl::single(true));
|
||||
field->setEditLinkCallback(
|
||||
DefaultEditLinkCallback(&_navigation->session(), field));
|
||||
field->setSubmitSettings(_navigation->session().settings().sendSubmitWay());
|
||||
|
||||
InitSpellchecker(&_navigation->session(), field);
|
||||
Ui::SendPendingMoveResizeEvents(_comment);
|
||||
@@ -413,7 +414,9 @@ void ShareBox::keyPressEvent(QKeyEvent *e) {
|
||||
|
||||
SendMenuType ShareBox::sendMenuType() const {
|
||||
const auto selected = _inner->selected();
|
||||
return (selected.size() == 1 && selected.front()->isSelf())
|
||||
return ranges::all_of(selected, HistoryView::CanScheduleUntilOnline)
|
||||
? SendMenuType::ScheduledToUser
|
||||
: (selected.size() == 1 && selected.front()->isSelf())
|
||||
? SendMenuType::Reminder
|
||||
: SendMenuType::Scheduled;
|
||||
}
|
||||
@@ -424,7 +427,7 @@ void ShareBox::createButtons() {
|
||||
const auto send = addButton(tr::lng_share_confirm(), [=] {
|
||||
submit({});
|
||||
});
|
||||
SetupSendMenu(
|
||||
SetupSendMenuAndShortcuts(
|
||||
send,
|
||||
[=] { return sendMenuType(); },
|
||||
[=] { submitSilent(); },
|
||||
|
||||
@@ -44,7 +44,7 @@ private:
|
||||
MTP::Sender _api;
|
||||
|
||||
MsgId _offsetId = 0;
|
||||
mtpRequestId _loadRequestId = 0;
|
||||
int _loadRequestId = 0; // Not a real mtpRequestId.
|
||||
bool _allLoaded = false;
|
||||
|
||||
};
|
||||
|
||||