Compare commits

...

147 Commits

Author SHA1 Message Date
John Preston
7aeffa242e Beta version 2.9.6.
- Debugging a video playback crash in 32 bit Windows version.
2021-08-21 09:34:26 +03:00
John Preston
3136c0586e Add some more assertions to debug a crash. 2021-08-21 09:33:20 +03:00
John Preston
fb0fcbca7f Remove redundant -static-libstdc++ for Updater. 2021-08-20 21:14:39 +03:00
John Preston
0d449c037b Beta version 2.9.5: Fix hide workaround for media viewer. 2021-08-20 19:56:40 +03:00
John Preston
4d98230694 Beta version 2.9.5.
- Tile chat background patterns horizontally.
- Fix a rare crash in spellchecker on Windows.
- Fix animated chat backgrounds in Saved Messages.
- Fix "Sorry, group is inaccessible" message in scheduled voice chats.
2021-08-20 18:42:13 +03:00
John Preston
3d36e501a1 Add some assertions to debug a crash in video playback. 2021-08-20 18:42:13 +03:00
John Preston
b4eb9a0827 Fix single-colored patterns. 2021-08-20 16:24:26 +03:00
John Preston
aaf0015be4 Improve pattern wallpaper preview in Settings. 2021-08-20 16:14:09 +03:00
John Preston
1e8e163bb1 Tile patterns horizontally. 2021-08-20 15:55:10 +03:00
John Preston
f3f741e1eb Hide 'Tile' option in generated backgrounds. 2021-08-20 15:14:28 +03:00
John Preston
44f52ca6cd Fix solid color background preview. 2021-08-20 14:54:11 +03:00
John Preston
2b6e04bca3 Apply initial bubble opacity in QImage bubble backgrounds. 2021-08-20 14:54:11 +03:00
John Preston
784d57a2bc Use QImage bubbles background for some Media parts. 2021-08-20 14:54:11 +03:00
John Preston
f4fdadd3b0 Allow arbitrary QImage as outgoing bubbles background. 2021-08-20 14:54:11 +03:00
John Preston
1cc9a52461 Fix my speaking status freeze in voice chats. 2021-08-18 17:52:23 +03:00
John Preston
cd52982752 Hopefully fix an assertion violation in voice chats. 2021-08-18 17:45:21 +03:00
John Preston
4dd58b79e9 Fix forward of a bot message with a game from a group. 2021-08-18 16:43:22 +03:00
John Preston
c5c94276c2 Allow editing caption of forwarded audio file.
Fixes https://bugs.telegram.org/c/3025
2021-08-18 16:29:05 +03:00
John Preston
8e0b9b685c Show 'X subscribers' in channel ConfirmInviteBox.
Fixes https://bugs.telegram.org/c/2059
2021-08-18 15:57:52 +03:00
John Preston
1792bed721 Remove additional padding in shared contacts. 2021-08-18 15:52:04 +03:00
John Preston
a502cbc06e Move message link up in registered shared contacts.
Fixes #16804.
2021-08-18 15:48:30 +03:00
John Preston
ddda7b8c52 Fix crash in application Dock menu on macOS. 2021-08-18 15:44:38 +03:00
John Preston
0f19ba3231 Fix spellcheck crash on Windows on certain strings. 2021-08-18 15:22:27 +03:00
John Preston
36486a3d73 Fix creating a theme from a default one.
Regression was introduced in 79cc797aff.
2021-08-18 14:52:57 +03:00
John Preston
02f48a7781 Don't allow selected items together with text. 2021-08-18 14:52:49 +03:00
John Preston
c77f4dd794 Fix build on Linux. 2021-08-18 13:06:47 +03:00
John Preston
ca31a08182 Fix 'Sorry, group is inac...' in scheduled voice chat. 2021-08-18 11:34:16 +03:00
John Preston
33936195a1 Animate the chat background in Saved Messages. 2021-08-18 11:33:37 +03:00
John Preston
67bafa02fe Remove attempt of a shadow Qt build. 2021-08-17 18:15:55 +03:00
John Preston
1ef0046002 Beta version 2.9.4.
- Choose one from dozens of new gorgeous animated backgrounds
in Chat Settings > Chat background.
2021-08-17 18:09:18 +03:00
John Preston
f2f19b14eb Fix reading one message if last one is outgoing. 2021-08-17 18:07:20 +03:00
John Preston
662966ba31 Support patterns with negative intensity. 2021-08-17 17:35:10 +03:00
John Preston
5383ae3d96 Fix log in after password change by email. 2021-08-17 15:51:36 +03:00
John Preston
d80b25944e Fix Snap build on GitHub Actions. 2021-08-17 15:17:58 +03:00
23rd
76813db3ad Completed ability to recover cloud password with email. 2021-08-17 15:08:57 +03:00
John Preston
c3595f2e31 Ask for a new password when recovering by email. 2021-08-17 15:07:58 +03:00
John Preston
5a882d1fdc Rotate background in ListWidget. 2021-08-17 14:55:40 +03:00
John Preston
ce6f9f580f Remove unused ColorizePattern function. 2021-08-17 13:06:28 +03:00
John Preston
52b9a1fceb Log all Qt messages as usual in debug builds. 2021-08-17 12:25:13 +03:00
John Preston
1209b2692a Generate correct next rotated gradient. 2021-08-16 17:15:02 +03:00
John Preston
2abcb51dda Filter out patterns without background colors. 2021-08-16 17:14:34 +03:00
John Preston
7a06eccaec Make complex gradients animate on outgoing messages. 2021-08-16 16:07:43 +03:00
John Preston
a1f81e4de8 Generate static complex gradients. 2021-08-16 13:24:15 +03:00
John Preston
689378ee04 Prefer 1280x720 resolution for camera video. 2021-08-16 10:26:08 +03:00
John Preston
b239506150 Fix pattern wallpaper preview on retina screens. 2021-08-16 10:26:08 +03:00
John Preston
3dadcd9352 Animated transition on pattern-on-gradient resize. 2021-08-16 10:26:08 +03:00
John Preston
b9a9520ef5 Don't blend SoftLight patterns in realtime. 2021-08-16 10:26:08 +03:00
John Preston
2b46f87d7b Cache background quickly if no buttons pressed. 2021-08-16 10:26:08 +03:00
John Preston
2667bb3568 Move background caching to Window::SessionController. 2021-08-16 10:26:08 +03:00
John Preston
1bc5277d51 Show color / gradient wallpapers in WebPage previews. 2021-08-16 10:26:08 +03:00
John Preston
436d7b9d82 Add support for linear gradients without patterns. 2021-08-16 10:26:08 +03:00
John Preston
ba7e976fe2 Fix background sample generation. 2021-08-16 10:26:08 +03:00
John Preston
c2b1187948 Start support of linear gradient wallpapers. 2021-08-16 10:26:08 +03:00
23rd
1fd28d5cfb Removed MTP* from public interface of Api::CloudPassword. 2021-08-15 13:44:43 +03:00
23rd
eec58137e9 Fixed build for Linux. 2021-08-15 13:26:43 +03:00
Ilya Fedin
e7d39e6046 Get rid of GtkIntegration::initializeSettings use 2021-08-13 15:21:19 +03:00
Ilya Fedin
63a92cb90a Log icon theme 2021-08-13 15:21:19 +03:00
John Preston
85cc3b30a0 Don't use MTP* for WallPaper flags. 2021-08-12 12:32:30 +03:00
John Preston
474a6a71d9 Move unread mentions menu to chat_helpers/send_context_menu. 2021-08-12 10:06:16 +03:00
John Preston
393173c1da Finish 'Mark mentions as read' context menu. 2021-08-12 09:46:02 +03:00
John Preston
badc27eda4 Fix build with a correct lib_ui revision. 2021-08-12 09:03:19 +03:00
test
920f3b245b Update lang.strings 2021-08-12 08:52:56 +03:00
test
f189ffc6ac Summary 2021-08-12 08:52:56 +03:00
Nicholas Guriev
840bb447ba Mention some missing includes in payments/ subdirectory 2021-08-12 08:51:45 +03:00
Ilya Fedin
414456d003 Revert "Use gtk clipboard when available to avoid https://bugreports.qt.io/browse/QTBUG-56595"
Fixed in Qt by https://codereview.qt-project.org/c/qt/qtbase/+/306771

This reverts commit 3a91003eea.
2021-08-12 08:51:10 +03:00
23rd
ea3191badf Fixed Github CI build. 2021-08-12 08:34:51 +03:00
John Preston
4452edcaad Update release asset uploading script. 2021-08-12 08:34:47 +03:00
23rd
76eb00cea9 Added drafts saving on account switching. 2021-08-12 02:36:41 +03:00
23rd
b3622b413e Added ability to set custom auto-lock timer. 2021-08-12 02:36:41 +03:00
23rd
b55383efe7 Moved time input widgets to lib_ui. 2021-08-12 02:36:41 +03:00
23rd
852e46f0c9 Added filepath removal for modified images in photo editor.
Fixed #16791.
2021-08-12 02:35:53 +03:00
John Preston
84fef1f045 Fix linking to QtSvg properly. 2021-08-11 20:22:45 +03:00
John Preston
6cadf54874 Add support for SVG patterns in wallpapers. 2021-08-11 19:56:12 +03:00
John Preston
e8fc874456 Build and link with QtSvg. 2021-08-11 19:55:47 +03:00
John Preston
c79cd0b692 Use Images::Read instead of App::readImage. 2021-08-11 18:55:08 +03:00
John Preston
b150ab8ef5 Add .tgv as a known mime type. 2021-08-11 18:23:14 +03:00
Ilya Fedin
8b7b0fa570 Remove -externalupdater flag
Having a path to executable in /etc/tdesktop/externalupdater is a way more convenient and is enough
2021-08-11 18:20:52 +03:00
Ilya Fedin
a3ee1e4ed5 Remove mentions of unused -testmode flag 2021-08-11 18:20:52 +03:00
Ilya Fedin
349446e6b0 Lock issues once a day rather than once a hour 2021-08-11 17:56:25 +03:00
Ilya Fedin
97262a99c7 Get rid of osx and linux32 special targets 2021-08-11 16:59:27 +03:00
Ilya Fedin
1d2e34f5e9 Write Qt messages only to log in debug mode 2021-08-11 15:49:31 +03:00
Ilya Fedin
bc2fc94e25 Don't check libtgvoip defines, too big queue 2021-08-11 15:46:38 +03:00
Ilya Fedin
6f0e94a04a WebKitGTK support doesn't depend on GTK integration anymore 2021-08-11 15:46:38 +03:00
John Preston
60e43cfa3f Version 2.9.3.
- Fix requesting screencast rights on macOS (again).
2021-08-11 11:35:48 +03:00
John Preston
46885b7f9f Fix mouse input in layers in call window. 2021-08-11 11:06:34 +03:00
23rd
fff42a664c Fixed Github CI Windows build. 2021-08-11 05:53:16 +03:00
John Preston
437d35d8c4 Version 2.9.2: Fix build on macOS. 2021-08-10 19:24:56 +03:00
John Preston
36a5dd8d8b Version 2.9.2: Buffer more audio data.
I hope this fixes #16733.
2021-08-10 16:44:59 +03:00
John Preston
d8e99b4860 Version 2.9.2.
- Fix crashes and bugs in scheduled messages.
- Fix file sending after a call or voice chat on Windows.
- Fix main window title glitches on Windows 7.
2021-08-10 16:10:59 +03:00
John Preston
3cddcaa039 Update submodules. 2021-08-10 16:05:08 +03:00
John Preston
49078e5679 Apply a window title workaround on Windows 7.
Fixes #16785, fixes #16758, fixes #16721.
2021-08-10 15:46:18 +03:00
John Preston
1dec054766 Check if native window frame fits for saved geometry. 2021-08-10 15:46:18 +03:00
John Preston
91ef6f13c8 Use good bitrate for screen capture in calls. 2021-08-10 15:46:18 +03:00
John Preston
73c8f16340 Fix maximized window on Linux, update tg_angle. 2021-08-10 15:46:18 +03:00
23rd
cbad2469db Moved MTP cloud password from ApiWrap to Api::CloudPassword. 2021-08-10 15:46:18 +03:00
23rd
0ae260c6e1 Moved MTP blocked peers from ApiWrap to Api::BlockedPeers. 2021-08-10 15:46:18 +03:00
23rd
221ded6d54 Fixed handle of last message on Up arrow in sections on macOS. 2021-08-10 15:46:18 +03:00
23rd
6f80811ecd Fixed applying server messages as scheduled.
Fixed #16726 and many other problems.
2021-08-10 15:46:18 +03:00
23rd
5bd73bab9b Moved user privacy from ApiWrap to Api::UserPrivacy. 2021-08-10 15:46:18 +03:00
Ilya Fedin
ac86f3e5bd Use Communications category for tray icon
Since tdesktop fits it
2021-08-10 15:42:21 +03:00
GitHub Action
bae6a29326 Update User-Agent for DNS to Chrome 92.0.4515.107. 2021-08-09 10:21:57 +03:00
John Preston
dcf86f55af Version 2.9.1.
- Fix requesting screencast rights on macOS.
2021-07-30 21:28:24 +03:00
John Preston
2d223b3a2d Version 2.9: Add jemalloc submodule. 2021-07-30 21:26:05 +03:00
John Preston
f3ab01604c Version 2.9: Fix build on Linux. 2021-07-30 21:03:49 +03:00
John Preston
66bcc20f58 Fix requesting screencast rights on macOS. 2021-07-30 20:45:00 +03:00
John Preston
d69a1d3cd9 Version 2.9: Updated changelog. 2021-07-30 18:21:54 +03:00
John Preston
ccc5aeb8f7 Version 2.9.
- Reset your Two-Step Verification password even if you forgot
your old password and don't have a recovery email.
- To do this, you must be logged into your account.
The reset takes 7 days and can be cancelled from any of your devices.
2021-07-30 17:35:30 +03:00
John Preston
5899d60363 Remove some legacy langs scripts. 2021-07-30 16:11:42 +03:00
John Preston
4f89216db0 Disable noise suppression by default. 2021-07-30 16:11:13 +03:00
John Preston
bd78bac4bf Warn admins about many voice chat participants. 2021-07-30 16:06:27 +03:00
John Preston
b2e829904f Add support for 1 month self-destruct messages. 2021-07-30 16:05:13 +03:00
John Preston
256546071b Add ability to reset cloud password in 7 days. 2021-07-30 14:32:49 +03:00
John Preston
c100055fac Allow sharing screen or window in one-on-one calls. 2021-07-30 00:40:25 +03:00
John Preston
ae30366cbf Fix window move by title on macOS. 2021-07-29 15:30:22 +03:00
John Preston
663c99cc2d Fix build on macOS. 2021-07-29 15:30:22 +03:00
Ilya Fedin
d986e70a89 Fix global menu on 64-bit systems with X11
When Wayland support for global menu was added (0b86feeeb5), X11 support was broken since QWindow::winId returns WId what is a quintptr that expands to uint32 on 32-bit and to uint64 on 64-bit, while AppMenu d-bus service accepts only uint32.
2021-07-28 23:41:36 +03:00
Ilya Fedin
1ebf27bfa1 Switch MainWindow to Ui::RpWindow 2021-07-28 18:05:39 +03:00
John Preston
4ef2d3b957 Fix crash in mosaic layout. 2021-07-28 17:34:39 +03:00
John Preston
868c494299 Extract Mosaic::Layout::AbstractMosaicLayout. 2021-07-28 17:00:46 +03:00
John Preston
4a86b172d4 Rename Ui::Window to Ui::RpWindow. 2021-07-28 16:24:11 +03:00
Ilya Fedin
f71ce9afdf Fix dark titlebar on Windows 10 17763 2021-07-28 16:01:50 +03:00
John Preston
c170a86189 Fix build on Windows. 2021-07-28 15:59:42 +03:00
John Preston
116a768fde Don't use MTP* for Message flags. 2021-07-28 15:28:17 +03:00
John Preston
22e77bf3af Don't use MTP* for ReplyMarkup flags. 2021-07-28 15:16:23 +03:00
John Preston
bbbcd37b8f Provide latency values to AEC. 2021-07-28 15:16:23 +03:00
John Preston
bc707320f8 Add 'invited' icon in narrow voice chat members list. 2021-07-28 15:16:23 +03:00
23rd
000911ecfa Updated section icon for shared GIFs. 2021-07-27 20:40:44 +03:00
23rd
479611f6df Removed ui_getPeerForMouseAction. 2021-07-27 02:50:08 +03:00
23rd
dcc8a64d37 Removed App::sendBotCommand. 2021-07-27 02:50:08 +03:00
23rd
a030907598 Refactored sending bot commands. 2021-07-27 02:50:08 +03:00
23rd
34cac3092f Moved utils for sending bot commands to separated file. 2021-07-27 02:50:08 +03:00
23rd
05f1caf944 Removed App::searchByHashtag from facades. 2021-07-27 02:18:49 +03:00
23rd
93bcd90fd4 Provided more context for click handlers. 2021-07-27 02:18:49 +03:00
23rd
7c8b1cd5b1 Disabled forced session switching when closing PiP.
Regression was introduced in 7decf68122.
2021-07-26 18:54:58 +03:00
23rd
82165bec5e Replaced MainWidget::replyToItem with history element delegate. 2021-07-26 17:39:18 +03:00
23rd
65aecf16a6 Removed App::wnd() from HistoryView::Message. 2021-07-26 17:38:52 +03:00
23rd
ca83b8a8c6 Removed App::wnd() from HistoryView::Contact. 2021-07-26 17:38:52 +03:00
23rd
024bb5e54f Moved App::formatPhone to td_ui. 2021-07-26 17:38:51 +03:00
23rd
a14f2144e1 Removed layout.cpp/h. 2021-07-26 17:38:51 +03:00
23rd
c6071d1148 Moved generic document preview info to separated file. 2021-07-26 17:38:51 +03:00
23rd
6e2f8eb9a7 Moved text selection utils to td_ui. 2021-07-26 17:38:51 +03:00
23rd
793f748d2e Replaced overview and inline mosaic layouts with new mosaic layout. 2021-07-26 17:38:51 +03:00
23rd
bb404a38d3 Merged mosaic layouts from overview and inline to td_ui generic class. 2021-07-26 17:38:51 +03:00
23rd
c82006c6f8 Moved position info from ItemBases to AbstractLayoutItem. 2021-07-26 17:38:51 +03:00
23rd
2256482ae0 Moved layout utils to td_ui. 2021-07-26 17:38:51 +03:00
23rd
d11e756381 Detached abstract part of LayoutItemBase and moved to td_ui. 2021-07-26 17:38:51 +03:00
John Preston
a128c16f59 Define 'KHRONOS_STATIC' globally. 2021-07-26 16:23:41 +03:00
385 changed files with 7903 additions and 8329 deletions

View File

@@ -63,8 +63,6 @@ jobs:
- "DESKTOP_APP_DISABLE_X11_INTEGRATION"
- "DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION"
- "DESKTOP_APP_DISABLE_GTK_INTEGRATION"
- "LIBTGVOIP_DISABLE_ALSA"
- "LIBTGVOIP_DISABLE_PULSEAUDIO"
env:
UPLOAD_ARTIFACT: "false"
@@ -94,9 +92,6 @@ jobs:
if [ "${{ matrix.defines }}" == "DESKTOP_APP_DISABLE_DBUS_INTEGRATION" ]; then
DEFINE="$DEFINE -D DESKTOP_APP_DISABLE_GTK_INTEGRATION=ON -D DESKTOP_APP_DISABLE_WEBKITGTK=ON"
fi
if [ "${{ matrix.defines }}" == "DESKTOP_APP_DISABLE_GTK_INTEGRATION" ]; then
DEFINE="$DEFINE -D DESKTOP_APP_DISABLE_WEBKITGTK=ON"
fi
echo Define from matrix: $DEFINE
echo "ARTIFACT_NAME=Telegram_${{ matrix.defines }}" >> $GITHUB_ENV
else

View File

@@ -2,7 +2,7 @@ name: 'Lock Threads'
on:
schedule:
- cron: '0 * * * *'
- cron: '0 0 * * *'
jobs:
lock:

View File

@@ -449,10 +449,11 @@ jobs:
git clone git://code.qt.io/qt/qt5.git qt_$QT
cd qt_$QT
perl init-repository --module-subset=qtbase,qtimageformats
perl init-repository --module-subset=qtbase,qtimageformats,qtsvg
git checkout v5.15.2
git submodule update qtbase
git submodule update qtimageformats
git submodule update qtsvg
cd qtbase
find ../../patches/qtbase_$QT -type f -print0 | sort -z | xargs -0 git apply
cd ..

View File

@@ -52,28 +52,34 @@ jobs:
- ""
env:
SDK: "10.0.18362.0"
VC: "call vcvars32.bat && cd Libraries"
GIT: "https://github.com"
QT: "5_15_2"
QT_VER: "5.15.2"
OPENSSL_VER: "1_1_1"
UPLOAD_ARTIFACT: "false"
ONLY_CACHE: "false"
MANUAL_CACHING: "2"
MANUAL_CACHING: "0"
DOC_PATH: "docs/building-win.md"
AUTO_CACHING: "1"
defaults:
run:
shell: cmd
working-directory: Libraries
steps:
- name: Get repository name.
shell: bash
working-directory: ${{ github.workspace }}
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
- uses: ilammy/msvc-dev-cmd@v1.9.0
name: x86 Native Tools Command Prompt.
with:
arch: win32
- name: Set up environment paths.
shell: bash
working-directory: ${{ github.workspace }}
run: |
echo "C:\\Strawberry\\perl\\bin\\" >> $GITHUB_PATH
echo "C:\\Program Files\\NASM\\" >> $GITHUB_PATH
@@ -84,6 +90,8 @@ jobs:
p=`pwd | sed 's#^/[d]#d:#g' |sed 's#/#\\\\#g'`
echo "LibrariesPath=$p" >> $GITHUB_ENV
echo "QT=${QT_VER//./_}" >> $GITHUB_ENV
- name: Save msbuild version.
run: |
call vcvars32.bat
@@ -97,6 +105,7 @@ jobs:
- name: Generate cache key.
shell: bash
working-directory: ${{ github.workspace }}
run: |
curl -o $LibrariesPath/tg_owt-version.json https://api.github.com/repos/desktop-app/tg_owt/git/refs/heads/master
curl -o $LibrariesPath/tg_angle-version.json https://api.github.com/repos/desktop-app/tg_angle/git/refs/heads/master
@@ -108,7 +117,10 @@ jobs:
echo "CACHE_KEY=`md5sum CACHE_KEY.txt | awk '{ print $1 }'`" >> $GITHUB_ENV
- name: Choco installs.
run: choco install --no-progress -y nasm yasm jom ninja
run: |
choco install --allow-empty-checksums --no-progress -y yasm
choco install --no-progress -y nasm jom ninja
python -m pip install pywin32
- name: NuGet sources.
run: |
@@ -117,6 +129,7 @@ jobs:
- name: Patches.
shell: bash
working-directory: ${{ github.workspace }}
run: |
echo "Find necessary commit from doc."
checkoutCommit=$(grep -A 1 "cd patches" $REPO_NAME/$DOC_PATH | sed -n 2p)
@@ -139,8 +152,6 @@ jobs:
- name: LZMA.
run: |
%VC%
git clone %GIT%/telegramdesktop/lzma.git
cd lzma
cd C\Util\LzmaLib
@@ -155,8 +166,6 @@ jobs:
- name: OpenSSL.
if: steps.cache-openssl.outputs.cache-hit != 'true'
run: |
%VC%
git clone %GIT%/openssl/openssl.git openssl_%OPENSSL_VER%
cd openssl_%OPENSSL_VER%
git checkout OpenSSL_%OPENSSL_VER%-stable
@@ -180,8 +189,6 @@ jobs:
- name: Zlib.
run: |
%VC%
git clone %GIT%/telegramdesktop/zlib.git
cd zlib
git checkout tdesktop
@@ -191,8 +198,6 @@ jobs:
- name: MozJPEG.
shell: cmd
run: |
%VC%
git clone -b v4.0.1-rc2 %GIT%/mozilla/mozjpeg.git
cd mozjpeg
cmake . ^
@@ -211,8 +216,6 @@ jobs:
- name: OpenAL Soft.
if: steps.cache-openal.outputs.cache-hit != 'true'
run: |
%VC%
git clone -b openal-soft-1.21.0 --depth=1 %GIT%/kcat/openal-soft.git
cd openal-soft\build
cmake .. ^
@@ -236,8 +239,6 @@ jobs:
GYP_MSVS_VERSION: 2019
if: steps.cache-breakpad.outputs.cache-hit != 'true'
run: |
cd %LibrariesPath%
git clone %GIT%/telegramdesktop/gyp.git
cd gyp
SET PATH=%PY2%;%cd%;%PATH%
@@ -271,8 +272,6 @@ jobs:
- name: Opus.
if: steps.cache-opus.outputs.cache-hit != 'true'
run: |
%VC%
git clone %GIT%/telegramdesktop/opus.git
cd opus
git checkout tdesktop
@@ -281,10 +280,7 @@ jobs:
msbuild -m opus.sln /property:Configuration=Release /property:Platform="Win32"
- name: Rnnoise.
shell: cmd
run: |
%VC%
git clone %GIT%/desktop-app/rnnoise.git
mkdir rnnoise\out
cd rnnoise\out
@@ -300,7 +296,6 @@ jobs:
- name: FFmpeg.
if: steps.cache-ffmpeg.outputs.cache-hit != 'true'
run: |
%VC%
choco install --no-progress -y msys2
git clone %GIT%/FFmpeg/FFmpeg.git ffmpeg
@@ -321,8 +316,6 @@ jobs:
- name: Angle.
if: steps.cache-angle.outputs.cache-hit != 'true'
run: |
%VC%
git clone --recursive %GIT%/desktop-app/tg_angle.git
mkdir tg_angle\out\Debug
cd tg_angle\out\Debug
@@ -348,14 +341,13 @@ jobs:
- name: Configure Qt 5.15.2.
if: steps.cache-qt.outputs.cache-hit != 'true'
run: |
%VC%
git clone git://code.qt.io/qt/qt5.git qt_%QT%
cd qt_%QT%
perl init-repository --module-subset=qtbase,qtimageformats
perl init-repository --module-subset=qtbase,qtimageformats,qtsvg
git checkout v%QT_VER%
git submodule update qtbase
git submodule update qtimageformats
git submodule update qtsvg
cd qtbase
for /r %%i in (..\..\patches\qtbase_%QT%\*) do git apply %%i
cd ..
@@ -401,7 +393,6 @@ jobs:
- name: Qt 5.15.2 build.
if: steps.cache-qt.outputs.cache-hit != 'true'
run: |
%VC%
cd qt_%QT%
jom -j%NUMBER_OF_PROCESSORS%
@@ -419,8 +410,6 @@ jobs:
- name: WebRTC.
if: steps.cache-webrtc.outputs.cache-hit != 'true'
run: |
%VC%
git clone --recursive %GIT%/desktop-app/tg_owt.git
mkdir tg_owt\out\Debug
cd tg_owt\out\Debug
@@ -457,10 +446,12 @@ jobs:
echo "TDESKTOP_BUILD_DEFINE=$DEFINE" >> $GITHUB_ENV
- name: Free up some disk space.
working-directory: ${{ github.workspace }}
run: del /S *.pdb
- name: Telegram Desktop build.
if: env.ONLY_CACHE == 'false'
working-directory: ${{ github.workspace }}
run: |
cd %REPO_NAME%\Telegram
@@ -478,6 +469,7 @@ jobs:
- name: Move artifact.
if: env.UPLOAD_ARTIFACT == 'true'
working-directory: ${{ github.workspace }}
run: |
cd %REPO_NAME%\out\Debug
mkdir artifact

3
.gitmodules vendored
View File

@@ -91,3 +91,6 @@
[submodule "Telegram/lib_waylandshells"]
path = Telegram/lib_waylandshells
url = https://github.com/desktop-app/lib_waylandshells.git
[submodule "Telegram/ThirdParty/jemalloc"]
path = Telegram/ThirdParty/jemalloc
url = https://github.com/jemalloc/jemalloc

View File

@@ -91,12 +91,16 @@ PRIVATE
api/api_attached_stickers.h
api/api_authorizations.cpp
api/api_authorizations.h
api/api_blocked_peers.cpp
api/api_blocked_peers.h
api/api_bot.cpp
api/api_bot.h
api/api_chat_filters.cpp
api/api_chat_filters.h
api/api_chat_invite.cpp
api/api_chat_invite.h
api/api_cloud_password.cpp
api/api_cloud_password.h
api/api_common.h
api/api_editing.cpp
api/api_editing.h
@@ -124,6 +128,8 @@ PRIVATE
api/api_toggling_media.h
api/api_updates.cpp
api/api_updates.h
api/api_user_privacy.cpp
api/api_user_privacy.h
boxes/filters/edit_filter_box.cpp
boxes/filters/edit_filter_box.h
boxes/filters/edit_filter_chats_list.cpp
@@ -220,6 +226,7 @@ PRIVATE
calls/group/calls_choose_join_as.h
calls/group/calls_group_call.cpp
calls/group/calls_group_call.h
calls/group/calls_group_common.cpp
calls/group/calls_group_common.h
calls/group/calls_group_invite_controller.cpp
calls/group/calls_group_invite_controller.h
@@ -265,6 +272,8 @@ PRIVATE
calls/calls_video_bubble.h
calls/calls_video_incoming.cpp
calls/calls_video_incoming.h
chat_helpers/bot_command.cpp
chat_helpers/bot_command.h
chat_helpers/bot_keyboard.cpp
chat_helpers/bot_keyboard.h
chat_helpers/emoji_keywords.cpp
@@ -676,8 +685,6 @@ PRIVATE
inline_bots/inline_bot_send_data.h
inline_bots/inline_results_inner.cpp
inline_bots/inline_results_inner.h
inline_bots/inline_results_mosaic_layout.cpp
inline_bots/inline_results_mosaic_layout.h
inline_bots/inline_results_widget.cpp
inline_bots/inline_results_widget.h
intro/intro_code.cpp
@@ -704,8 +711,10 @@ PRIVATE
lang/lang_numbers_animation.h
lang/lang_translator.cpp
lang/lang_translator.h
layout/layout_utils.cpp
layout/layout_utils.h
layout/layout_document_generic_preview.cpp
layout/layout_document_generic_preview.h
layout/layout_item_base.cpp
layout/layout_item_base.h
main/main_account.cpp
main/main_account.h
main/main_app_config.cpp
@@ -819,8 +828,6 @@ PRIVATE
overview/overview_layout.cpp
overview/overview_layout.h
overview/overview_layout_delegate.h
overview/overview_mosaic_layout.cpp
overview/overview_mosaic_layout.h
passport/passport_encryption.cpp
passport/passport_encryption.h
passport/passport_form_controller.cpp
@@ -873,7 +880,6 @@ PRIVATE
platform/linux/notifications_manager_linux.h
platform/linux/specific_linux.cpp
platform/linux/specific_linux.h
platform/linux/window_title_linux.h
platform/mac/file_utilities_mac.mm
platform/mac/file_utilities_mac.h
platform/mac/launcher_mac.mm
@@ -888,7 +894,6 @@ PRIVATE
platform/mac/specific_mac_p.mm
platform/mac/specific_mac_p.h
platform/mac/window_title_mac.mm
platform/mac/window_title_mac.h
platform/mac/touchbar/items/mac_formatter_item.h
platform/mac/touchbar/items/mac_formatter_item.mm
platform/mac/touchbar/items/mac_pinned_chats_item.h
@@ -919,8 +924,6 @@ PRIVATE
platform/win/notifications_manager_win.h
platform/win/specific_win.cpp
platform/win/specific_win.h
platform/win/window_title_win.cpp
platform/win/window_title_win.h
platform/win/windows_app_user_model_id.cpp
platform/win/windows_app_user_model_id.h
platform/win/windows_dlls.cpp
@@ -1094,13 +1097,11 @@ PRIVATE
window/window_outdated_bar.h
window/window_peer_menu.cpp
window/window_peer_menu.h
window/window_section_common.h
window/window_session_controller.cpp
window/window_session_controller.h
window/window_slide_animation.cpp
window/window_slide_animation.h
window/window_title_qt.cpp
window/window_title_qt.h
window/window_title.h
window/window_top_bar_wrap.h
window/themes/window_theme.cpp
window/themes/window_theme.h
@@ -1127,8 +1128,6 @@ PRIVATE
config.h
facades.cpp
facades.h
layout.cpp
layout.h
logs.cpp
logs.h
main.cpp
@@ -1141,13 +1140,6 @@ PRIVATE
stdafx.h
)
if (NOT LINUX)
remove_target_sources(Telegram ${src_loc}
window/window_title_qt.cpp
window/window_title_qt.h
)
endif()
if (DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
remove_target_sources(Telegram ${src_loc}
platform/linux/linux_xdp_file_dialog.cpp
@@ -1306,8 +1298,6 @@ else()
if (NOT DESKTOP_APP_DISABLE_X11_INTEGRATION)
target_link_libraries(Telegram PRIVATE X11)
endif()
target_link_libraries(Telegram PRIVATE rt)
endif()
endif()
@@ -1325,10 +1315,6 @@ if (build_macstore)
COMMAND mkdir -p $<TARGET_FILE_DIR:Telegram>/../Frameworks
COMMAND cp -a ${libs_loc}/breakpad/src/client/mac/build/Release/Breakpad.framework $<TARGET_FILE_DIR:Telegram>/../Frameworks/Breakpad.framework
)
elseif (build_osx)
set(bundle_identifier "com.tdesktop.Telegram$<$<CONFIG:Debug>:DebugOsx>")
set(bundle_entitlements "Telegram.entitlements")
set(output_name "Telegram")
else()
set(bundle_identifier "com.tdesktop.Telegram$<$<CONFIG:Debug>:Debug>")
set(bundle_entitlements "Telegram.entitlements")
@@ -1446,10 +1432,6 @@ if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT
endif()
endif()
if (LINUX)
target_link_options(Updater PRIVATE -static-libstdc++)
endif()
if (DESKTOP_APP_SPECIAL_TARGET)
add_executable(Packer)
init_target(Packer)

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 878 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1010 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,34 +0,0 @@
#!/usr/bin/env bash
set -e
FullExecPath=$PWD
pushd `dirname $0` > /dev/null
FullScriptPath=`pwd`
popd > /dev/null
if [ ! -d "$FullScriptPath/../../../../DesktopPrivate" ]; then
echo ""
echo "This script is for building the production version of Telegram Desktop."
echo ""
echo "For building custom versions please visit the build instructions page at:"
echo "https://github.com/telegramdesktop/tdesktop/#build-instructions"
exit
fi
Error () {
cd $FullExecPath
echo "$1"
exit 1
}
cd $FullScriptPath/../../../../
while IFS='' read -r line || [[ -n "$line" ]]; do
tx pull -f -l $line --minimum-perc=100
done < tdesktop/Telegram/Resources/langs/list
cd translations/telegram-desktop.langstrings/
for file in *.strings; do
iconv -f "UTF-16LE" -t "UTF-8" "$file" > "../../tdesktop/Telegram/Resources/langs/lang_$file.tmp"
awk '{ if (NR==1) sub(/^\xef\xbb\xbf/,""); sub(/
/,""); print }' "../../tdesktop/Telegram/Resources/langs/lang_$file.tmp" > "../../tdesktop/Telegram/Resources/langs/lang_$file"
rm "../../tdesktop/Telegram/Resources/langs/lang_$file.tmp"
done

View File

@@ -82,6 +82,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_box_ok" = "OK";
"lng_box_done" = "Done";
"lng_box_yes" = "Yes";
"lng_box_no" = "No";
"lng_cancel" = "Cancel";
"lng_continue" = "Continue";
@@ -575,6 +577,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_passcode_autolock_minutes#other" = "{count} minutes";
"lng_passcode_autolock_hours#one" = "{count} hour";
"lng_passcode_autolock_hours#other" = "{count} hours";
"lng_passcode_autolock_hours_minutes" = "{hours_count}h {minutes_count}m";
"lng_passcode_enter_old" = "Enter current passcode";
"lng_passcode_enter_first" = "Enter a passcode";
"lng_passcode_enter_new" = "Enter new passcode";
@@ -596,6 +599,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_cloud_password_remove" = "Remove cloud password";
"lng_cloud_password_set" = "Enable two-step verification";
"lng_cloud_password_edit" = "Change cloud password";
"lng_cloud_password_reset_in" = "Reset password in";
"lng_cloud_password_reset_ready" = "Reset password";
"lng_cloud_password_reset_cancel" = "Cancel password reset";
"lng_cloud_password_enter_old" = "Enter current password";
"lng_cloud_password_enter_first" = "Enter a password";
"lng_cloud_password_enter_new" = "Enter new password";
@@ -618,6 +624,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_cloud_password_passport_losing" = "Warning! All data saved in your Telegram Passport will be lost!";
"lng_cloud_password_resend" = "Resend code";
"lng_cloud_password_resent" = "Code was resent.";
"lng_cloud_password_reset_title" = "Reset password";
"lng_cloud_password_reset_no_email" = "Since you didn't provide a recovery email when setting up your password, your remaining options are either to remember your password or wait 7 days until your password is reset.";
"lng_cloud_password_reset_with_email" = "If you don't have access to your recovery email, your remaining options are either to remember your password or wait 7 days until your password resets.";
"lng_cloud_password_reset_ok" = "Reset";
"lng_cloud_password_reset_cancel_title" = "Cancel reset";
"lng_cloud_password_reset_cancel_sure" = "Cancel the password reset process? If you request a new reset later, it will take another 7 days.";
"lng_cloud_password_reset_later" = "You recently requested a password reset that was cancelled. Please wait {duration} before making a new request.";
"lng_connection_auto_connecting" = "Default (connecting...)";
"lng_connection_auto" = "Default ({transport} used)";
@@ -1005,8 +1018,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_messages_ttl_title" = "Auto-delete messages";
"lng_manage_messages_ttl_never" = "Off";
"lng_manage_messages_ttl_after1" = "24 hours";
"lng_manage_messages_ttl_after2" = "7 days";
"lng_manage_messages_ttl_after1" = "1 day";
"lng_manage_messages_ttl_after2" = "1 week";
"lng_manage_messages_ttl_after3" = "1 month";
"lng_ttl_edit_about" = "Automatically delete new messages after a certain period of time for you and {user}.";
"lng_ttl_edit_about_group" = "Automatically delete new messages sent in this chat after a certain period of time.";
@@ -1015,8 +1029,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_ttl_about_tooltip" = "New messages in this chat will be automatically deleted in {duration}.";
"lng_ttl_about_tooltip_channel" = "New messages in this chat will be automatically deleted in {duration}.";
"lng_ttl_about_tooltip_off" = "Auto-delete is now disabled.";
"lng_ttl_about_duration1" = "24 hours";
"lng_ttl_about_duration2" = "7 days";
"lng_ttl_about_duration1" = "1 day";
"lng_ttl_about_duration2" = "1 week";
"lng_ttl_about_duration3" = "1 month";
"lng_report_title" = "Report channel";
"lng_report_group_title" = "Report group";
@@ -1626,6 +1641,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_mark_read_sure" = "Are you sure you want to mark all chats from this folder as read?";
"lng_context_mark_read_all" = "Mark all chats as read";
"lng_context_mark_read_all_sure" = "Are you sure you want to mark all chats as read?";
"lng_context_mark_read_mentions_all" = "Mark all mentions as read";
"lng_context_archive_expand" = "Expand";
"lng_context_archive_collapse" = "Collapse";
"lng_context_archive_to_menu" = "Move to main menu";
@@ -2009,6 +2025,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_call_start_video" = "Start Video";
"lng_call_stop_video" = "Stop Video";
"lng_call_screencast" = "Screencast";
"lng_call_end_call" = "End Call";
"lng_call_mute_audio" = "Mute";
"lng_call_unmute_audio" = "Unmute";
@@ -2088,6 +2105,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_noise_suppression" = "Enable Noise Suppression";
"lng_group_call_limit#one" = "Video is only available\nfor the first {count} member";
"lng_group_call_limit#other" = "Video is only available\nfor the first {count} members";
"lng_group_call_over_limit#one" = "The voice chat is over {count} member.\nNew participants only have access to audio stream.";
"lng_group_call_over_limit#other" = "The voice chat is over {count} members.\nNew participants only have access to audio stream.";
"lng_group_call_video_paused" = "Video is paused";
"lng_group_call_share_speaker" = "Users with this link can speak";
"lng_group_call_copy_speaker_link" = "Copy Speaker Link";

View File

@@ -1 +0,0 @@
de,es,it,ko,nl,pt_BR

View File

@@ -1,29 +0,0 @@
#!/usr/bin/env bash
set -e
FullExecPath=$PWD
pushd `dirname $0` > /dev/null
FullScriptPath=`pwd`
popd > /dev/null
if [ ! -d "$FullScriptPath/../../../../DesktopPrivate" ]; then
echo ""
echo "This script is for building the production version of Telegram Desktop."
echo ""
echo "For building custom versions please visit the build instructions page at:"
echo "https://github.com/telegramdesktop/tdesktop/#build-instructions"
exit
fi
Error () {
cd $FullExecPath
echo "$1"
exit 1
}
cd $FullScriptPath/../../../../
while IFS='' read -r line || [[ -n "$line" ]]; do
tx pull -f -l $line
done < tdesktop/Telegram/Resources/langs/list
tx push -s
cd $FullExecPath

View File

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

View File

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

View File

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

View File

@@ -496,11 +496,7 @@ int main(int argc, char *argv[])
#elif defined Q_OS_MAC
QString outName(QString("tmacupd%1").arg(AlphaVersion ? AlphaVersion : version));
#elif defined Q_OS_UNIX
#ifndef _LP64
QString outName(QString("tlinux32upd%1").arg(AlphaVersion ? AlphaVersion : version));
#else
QString outName(QString("tlinuxupd%1").arg(AlphaVersion ? AlphaVersion : version));
#endif
#else
#error Unknown platform!
#endif

View File

@@ -384,8 +384,6 @@ int main(int argc, char *argv[]) {
bool writeprotected = false;
bool tosettings = false;
bool startintray = false;
bool testmode = false;
bool externalupdater = false;
bool customWorkingDir = false;
char *key = 0;
@@ -399,10 +397,6 @@ int main(int argc, char *argv[]) {
debug = _debug = true;
} else if (equal(argv[i], "-startintray")) {
startintray = true;
} else if (equal(argv[i], "-testmode")) {
testmode = true;
} else if (equal(argv[i], "-externalupdater")) {
externalupdater = true;
} else if (equal(argv[i], "-tosettings")) {
tosettings = true;
} else if (equal(argv[i], "-workdir_custom")) {
@@ -503,8 +497,6 @@ int main(int argc, char *argv[]) {
if (autostart) push("-autostart");
if (debug) push("-debug");
if (startintray) push("-startintray");
if (testmode) push("-testmode");
if (externalupdater) push("-externalupdater");
if (tosettings) push("-tosettings");
if (key) {
push("-key");

View File

@@ -90,7 +90,7 @@ int main(int argc, const char * argv[]) {
openLog();
pid_t procId = 0;
BOOL update = YES, toSettings = NO, autoStart = NO, startInTray = NO, testMode = NO, freeType = NO, externalUpdater = NO;
BOOL update = YES, toSettings = NO, autoStart = NO, startInTray = NO, freeType = NO;
BOOL customWorkingDir = NO;
NSString *key = nil;
for (int i = 0; i < argc; ++i) {
@@ -114,12 +114,8 @@ int main(int argc, const char * argv[]) {
_debug = YES;
} else if ([@"-startintray" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
startInTray = YES;
} else if ([@"-testmode" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
testMode = YES;
} else if ([@"-freetype" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
freeType = YES;
} else if ([@"-externalupdater" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
externalUpdater = YES;
} else if ([@"-workdir_custom" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
customWorkingDir = YES;
} else if ([@"-key" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
@@ -256,9 +252,7 @@ int main(int argc, const char * argv[]) {
if (toSettings) [args addObject:@"-tosettings"];
if (_debug) [args addObject:@"-debug"];
if (startInTray) [args addObject:@"-startintray"];
if (testMode) [args addObject:@"-testmode"];
if (freeType) [args addObject:@"-freetype"];
if (externalUpdater) [args addObject:@"-externalupdater"];
if (autoStart) [args addObject:@"-autostart"];
if (key) {
[args addObject:@"-key"];

View File

@@ -343,7 +343,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
LPWSTR *args;
int argsCount;
bool needupdate = false, autostart = false, debug = false, writeprotected = false, startintray = false, testmode = false, freetype = false, externalupdater = false;
bool needupdate = false, autostart = false, debug = false, writeprotected = false, startintray = false, freetype = false;
args = CommandLineToArgvW(GetCommandLine(), &argsCount);
if (args) {
for (int i = 1; i < argsCount; ++i) {
@@ -357,12 +357,8 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
openLog();
} else if (equal(args[i], L"-startintray")) {
startintray = true;
} else if (equal(args[i], L"-testmode")) {
testmode = true;
} else if (equal(args[i], L"-freetype")) {
freetype = true;
} else if (equal(args[i], L"-externalupdater")) {
externalupdater = true;
} else if (equal(args[i], L"-writeprotected") && ++i < argsCount) {
writeLog(std::wstring(L"Argument: ") + args[i]);
writeprotected = true;
@@ -432,9 +428,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
if (autostart) targs += L" -autostart";
if (debug) targs += L" -debug";
if (startintray) targs += L" -startintray";
if (testmode) targs += L" -testmode";
if (freetype) targs += L" -freetype";
if (externalupdater) targs += L" -externalupdater";
if (!customWorkingDir.empty()) {
targs += L" -workdir \"" + customWorkingDir + L"\"";
}

View File

@@ -0,0 +1,173 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "api/api_blocked_peers.h"
#include "apiwrap.h"
#include "base/unixtime.h"
#include "data/data_changes.h"
#include "data/data_peer.h"
#include "data/data_peer_id.h"
#include "data/data_session.h"
#include "main/main_session.h"
namespace Api {
namespace {
constexpr auto kBlockedFirstSlice = 16;
constexpr auto kBlockedPerPage = 40;
BlockedPeers::Slice TLToSlice(
const MTPcontacts_Blocked &blocked,
Data::Session &owner) {
const auto create = [&](int count, const QVector<MTPPeerBlocked> &list) {
auto slice = BlockedPeers::Slice();
slice.total = std::max(count, list.size());
slice.list.reserve(list.size());
for (const auto &contact : list) {
contact.match([&](const MTPDpeerBlocked &data) {
slice.list.push_back({
.id = peerFromMTP(data.vpeer_id()),
.date = data.vdate().v,
});
});
}
return slice;
};
return blocked.match([&](const MTPDcontacts_blockedSlice &data) {
owner.processUsers(data.vusers());
owner.processChats(data.vchats());
return create(data.vcount().v, data.vblocked().v);
}, [&](const MTPDcontacts_blocked &data) {
owner.processUsers(data.vusers());
owner.processChats(data.vchats());
return create(0, data.vblocked().v);
});
}
} // namespace
BlockedPeers::BlockedPeers(not_null<ApiWrap*> api)
: _session(&api->session())
, _api(&api->instance()) {
}
bool BlockedPeers::Slice::Item::operator==(const Item &other) const {
return (id == other.id) && (date == other.date);
}
bool BlockedPeers::Slice::Item::operator!=(const Item &other) const {
return !(*this == other);
}
bool BlockedPeers::Slice::operator==(const BlockedPeers::Slice &other) const {
return (total == other.total) && (list == other.list);
}
bool BlockedPeers::Slice::operator!=(const BlockedPeers::Slice &other) const {
return !(*this == other);
}
void BlockedPeers::block(not_null<PeerData*> peer) {
if (peer->isBlocked()) {
_session->changes().peerUpdated(
peer,
Data::PeerUpdate::Flag::IsBlocked);
} else if (_blockRequests.find(peer) == end(_blockRequests)) {
const auto requestId = _api.request(MTPcontacts_Block(
peer->input
)).done([=](const MTPBool &result) {
_blockRequests.erase(peer);
peer->setIsBlocked(true);
if (_slice) {
_slice->list.insert(
_slice->list.begin(),
{ peer->id, base::unixtime::now() });
++_slice->total;
_changes.fire_copy(*_slice);
}
}).fail([=](const MTP::Error &error) {
_blockRequests.erase(peer);
}).send();
_blockRequests.emplace(peer, requestId);
}
}
void BlockedPeers::unblock(not_null<PeerData*> peer, Fn<void()> onDone) {
if (!peer->isBlocked()) {
_session->changes().peerUpdated(
peer,
Data::PeerUpdate::Flag::IsBlocked);
return;
} else if (_blockRequests.find(peer) != end(_blockRequests)) {
return;
}
const auto requestId = _api.request(MTPcontacts_Unblock(
peer->input
)).done([=](const MTPBool &result) {
_blockRequests.erase(peer);
peer->setIsBlocked(false);
if (_slice) {
auto &list = _slice->list;
for (auto i = list.begin(); i != list.end(); ++i) {
if (i->id == peer->id) {
list.erase(i);
break;
}
}
if (_slice->total > list.size()) {
--_slice->total;
}
_changes.fire_copy(*_slice);
}
if (onDone) {
onDone();
}
}).fail([=](const MTP::Error &error) {
_blockRequests.erase(peer);
}).send();
_blockRequests.emplace(peer, requestId);
}
void BlockedPeers::reload() {
if (_requestId) {
return;
}
request(0, [=](Slice &&slice) {
if (!_slice || *_slice != slice) {
_slice = slice;
_changes.fire(std::move(slice));
}
});
}
auto BlockedPeers::slice() -> rpl::producer<BlockedPeers::Slice> {
if (!_slice) {
reload();
}
return _slice
? _changes.events_starting_with_copy(*_slice)
: (_changes.events() | rpl::type_erased());
}
void BlockedPeers::request(int offset, Fn<void(BlockedPeers::Slice)> onDone) {
if (_requestId) {
return;
}
_requestId = _api.request(MTPcontacts_GetBlocked(
MTP_int(offset),
MTP_int(offset ? kBlockedPerPage : kBlockedFirstSlice)
)).done([=](const MTPcontacts_Blocked &result) {
_requestId = 0;
onDone(TLToSlice(result, _session->data()));
}).fail([=](const MTP::Error &error) {
_requestId = 0;
}).send();
}
} // namespace Api

View File

@@ -0,0 +1,60 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "mtproto/sender.h"
class ApiWrap;
namespace Main {
class Session;
} // namespace Main
namespace Api {
class BlockedPeers final {
public:
struct Slice {
struct Item {
PeerId id;
TimeId date = 0;
bool operator==(const Item &other) const;
bool operator!=(const Item &other) const;
};
QVector<Item> list;
int total = 0;
bool operator==(const Slice &other) const;
bool operator!=(const Slice &other) const;
};
explicit BlockedPeers(not_null<ApiWrap*> api);
void reload();
rpl::producer<Slice> slice();
void request(int offset, Fn<void(Slice)> onDone);
void block(not_null<PeerData*> peer);
void unblock(not_null<PeerData*> peer, Fn<void()> onDone = nullptr);
private:
const not_null<Main::Session*> _session;
MTP::Sender _api;
base::flat_map<not_null<PeerData*>, mtpRequestId> _blockRequests;
mtpRequestId _requestId = 0;
std::optional<Slice> _slice;
rpl::event_stream<Slice> _changes;
};
} // namespace Api

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_bot.h"
#include "apiwrap.h"
#include "api/api_cloud_password.h"
#include "core/core_cloud_password.h"
#include "api/api_send_progress.h"
#include "boxes/confirm_box.h"
@@ -168,7 +169,7 @@ void SendBotCallbackDataWithPassword(
if (!button || button->requestId) {
return;
}
api->reloadPasswordState();
api->cloudPassword().reload();
SendBotCallbackData(item, row, column, MTP_inputCheckPasswordEmpty(), [=](const MTP::Error &error) {
auto box = PrePasswordErrorBox(
error,
@@ -181,7 +182,7 @@ void SendBotCallbackDataWithPassword(
} else {
auto lifetime = std::make_shared<rpl::lifetime>();
button->requestId = -1;
api->passwordState(
api->cloudPassword().state(
) | rpl::take(
1
) | rpl::start_with_next([=](const Core::CloudPasswordState &state) mutable {

View File

@@ -112,6 +112,11 @@ ConfirmInviteBox::ConfirmInviteBox(
? tr::lng_channel_invite_private(tr::now)
: (!_participants.empty() && _participants.size() < count)
? tr::lng_group_invite_members(tr::now, lt_count, count)
: (count > 0 && _isChannel)
? tr::lng_chat_status_subscribers(
tr::now,
lt_count_decimal,
count)
: (count > 0)
? tr::lng_chat_status_members(tr::now, lt_count_decimal, count)
: _isChannel

View File

@@ -0,0 +1,112 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "api/api_cloud_password.h"
#include "base/openssl_help.h"
#include "core/core_cloud_password.h"
#include "apiwrap.h"
namespace Api {
// #TODO Add ability to set recovery email separately.
CloudPassword::CloudPassword(not_null<ApiWrap*> api)
: _api(&api->instance()) {
}
void CloudPassword::reload() {
if (_requestId) {
return;
}
_requestId = _api.request(MTPaccount_GetPassword(
)).done([=](const MTPaccount_Password &result) {
_requestId = 0;
result.match([&](const MTPDaccount_password &data) {
openssl::AddRandomSeed(bytes::make_span(data.vsecure_random().v));
if (_state) {
*_state = Core::ParseCloudPasswordState(data);
} else {
_state = std::make_unique<Core::CloudPasswordState>(
Core::ParseCloudPasswordState(data));
}
_stateChanges.fire_copy(*_state);
});
}).fail([=](const MTP::Error &error) {
_requestId = 0;
}).send();
}
void CloudPassword::clearUnconfirmedPassword() {
_requestId = _api.request(MTPaccount_CancelPasswordEmail(
)).done([=](const MTPBool &result) {
_requestId = 0;
reload();
}).fail([=](const MTP::Error &error) {
_requestId = 0;
reload();
}).send();
}
rpl::producer<Core::CloudPasswordState> CloudPassword::state() const {
return _state
? _stateChanges.events_starting_with_copy(*_state)
: (_stateChanges.events() | rpl::type_erased());
}
auto CloudPassword::stateCurrent() const
-> std::optional<Core::CloudPasswordState> {
return _state
? base::make_optional(*_state)
: std::nullopt;
}
auto CloudPassword::resetPassword()
-> rpl::producer<CloudPassword::ResetRetryDate, QString> {
return [=](auto consumer) {
_api.request(MTPaccount_ResetPassword(
)).done([=](const MTPaccount_ResetPasswordResult &result) {
result.match([&](const MTPDaccount_resetPasswordOk &data) {
reload();
}, [&](const MTPDaccount_resetPasswordRequestedWait &data) {
if (!_state) {
reload();
return;
}
const auto until = data.vuntil_date().v;
if (_state->pendingResetDate != until) {
_state->pendingResetDate = until;
_stateChanges.fire_copy(*_state);
}
}, [&](const MTPDaccount_resetPasswordFailedWait &data) {
consumer.put_next_copy(data.vretry_date().v);
});
consumer.put_done();
}).fail([=](const MTP::Error &error) {
consumer.put_error_copy(error.type());
}).send();
return rpl::lifetime();
};
}
auto CloudPassword::cancelResetPassword()
-> rpl::producer<rpl::no_value, QString> {
return [=](auto consumer) {
_api.request(MTPaccount_DeclinePasswordReset(
)).done([=] {
reload();
consumer.put_done();
}).fail([=](const MTP::Error &error) {
consumer.put_error_copy(error.type());
}).send();
return rpl::lifetime();
};
}
} // namespace Api

View File

@@ -0,0 +1,45 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "mtproto/sender.h"
namespace Core {
struct CloudPasswordState;
} // namespace Core
class ApiWrap;
namespace Main {
class Session;
} // namespace Main
namespace Api {
class CloudPassword final {
public:
using ResetRetryDate = int;
explicit CloudPassword(not_null<ApiWrap*> api);
void reload();
void clearUnconfirmedPassword();
rpl::producer<Core::CloudPasswordState> state() const;
std::optional<Core::CloudPasswordState> stateCurrent() const;
rpl::producer<ResetRetryDate, QString> resetPassword();
rpl::producer<rpl::no_value, QString> cancelResetPassword();
private:
MTP::Sender _api;
mtpRequestId _requestId = 0;
std::unique_ptr<Core::CloudPasswordState> _state;
rpl::event_stream<Core::CloudPasswordState> _stateChanges;
};
} // namespace Api

View File

@@ -32,7 +32,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/file_upload.h"
#include "mainwidget.h"
#include "apiwrap.h"
#include "app.h"
namespace Api {
namespace {
@@ -40,22 +39,22 @@ namespace {
void InnerFillMessagePostFlags(
const Api::SendOptions &options,
not_null<PeerData*> peer,
MTPDmessage::Flags &flags) {
MessageFlags &flags) {
const auto anonymousPost = peer->amAnonymous();
if (!anonymousPost) {
flags |= MTPDmessage::Flag::f_from_id;
flags |= MessageFlag::HasFromId;
return;
} else if (peer->asMegagroup()) {
return;
}
flags |= MTPDmessage::Flag::f_post;
flags |= MessageFlag::Post;
// Don't display views and author of a new post when it's scheduled.
if (options.scheduled) {
return;
}
flags |= MTPDmessage::Flag::f_views;
flags |= MessageFlag::HasViews;
if (peer->asChannel()->addsSignature()) {
flags |= MTPDmessage::Flag::f_post_author;
flags |= MessageFlag::HasPostAuthor;
}
}
@@ -79,11 +78,10 @@ void SendExistingMedia(
session->data().nextLocalMessageId());
const auto randomId = openssl::RandomValue<uint64>();
auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media;
auto clientFlags = NewMessageClientFlags();
auto flags = NewMessageFlags(peer);
auto sendFlags = MTPmessages_SendMedia::Flags(0);
if (message.action.replyTo) {
flags |= MTPDmessage::Flag::f_reply_to;
flags |= MessageFlag::HasReplyInfo;
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
}
const auto anonymousPost = peer->amAnonymous();
@@ -111,19 +109,19 @@ void SendExistingMedia(
const auto captionText = caption.text;
if (message.action.options.scheduled) {
flags |= MTPDmessage::Flag::f_from_scheduled;
flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
} else {
clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry;
flags |= MessageFlag::LocalHistoryEntry;
}
session->data().registerMessageRandomId(randomId, newId);
const auto viaBotId = UserId();
history->addNewLocalMessage(
newId.msg,
flags,
clientFlags,
0,
viaBotId,
replyTo,
HistoryItem::NewMessageDate(message.action.options.scheduled),
messageFromId,
@@ -253,11 +251,10 @@ bool SendDice(Api::MessageToSend &message) {
const auto randomId = openssl::RandomValue<uint64>();
auto &histories = history->owner().histories();
auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media;
auto clientFlags = NewMessageClientFlags();
auto flags = NewMessageFlags(peer);
auto sendFlags = MTPmessages_SendMedia::Flags(0);
if (message.action.replyTo) {
flags |= MTPDmessage::Flag::f_reply_to;
flags |= MessageFlag::HasReplyInfo;
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
}
const auto replyHeader = NewMessageReplyHeader(message.action);
@@ -272,42 +269,26 @@ bool SendDice(Api::MessageToSend &message) {
const auto replyTo = message.action.replyTo;
if (message.action.options.scheduled) {
flags |= MTPDmessage::Flag::f_from_scheduled;
flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
} else {
clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry;
flags |= MessageFlag::LocalHistoryEntry;
}
session->data().registerMessageRandomId(randomId, newId);
const auto views = 1;
const auto forwards = 0;
history->addNewMessage(
MTP_message(
MTP_flags(flags),
MTP_int(newId.msg),
peerToMTP(messageFromId),
peerToMTP(history->peer->id),
MTPMessageFwdHeader(),
MTPint(), // via_bot_id
replyHeader,
MTP_int(HistoryItem::NewMessageDate(
message.action.options.scheduled)),
MTP_string(),
MTP_messageMediaDice(MTP_int(0), MTP_string(emoji)),
MTPReplyMarkup(),
MTP_vector<MTPMessageEntity>(),
MTP_int(views),
MTP_int(forwards),
MTPMessageReplies(),
MTPint(), // edit_date
MTP_string(messagePostAuthor),
MTPlong(),
//MTPMessageReactions(),
MTPVector<MTPRestrictionReason>(),
MTPint()), // ttl_period
clientFlags,
NewMessageType::Unread);
const auto viaBotId = UserId();
history->addNewLocalMessage(
newId.msg,
flags,
viaBotId,
message.action.replyTo,
HistoryItem::NewMessageDate(message.action.options.scheduled),
messageFromId,
messagePostAuthor,
TextWithEntities(),
MTP_messageMediaDice(MTP_int(0), MTP_string(emoji)),
MTPReplyMarkup());
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
@@ -338,14 +319,15 @@ bool SendDice(Api::MessageToSend &message) {
void FillMessagePostFlags(
const Api::SendAction &action,
not_null<PeerData*> peer,
MTPDmessage::Flags &flags) {
MessageFlags &flags) {
InnerFillMessagePostFlags(action.options, peer, flags);
}
void SendConfirmedFile(
not_null<Main::Session*> session,
const std::shared_ptr<FileLoadResult> &file) {
const auto isEditing = file->to.replaceMediaOf != 0;
const auto isEditing = (file->type != SendMediaType::Audio)
&& (file->to.replaceMediaOf != 0);
const auto channelId = peerToChannel(file->to.peer);
const auto newId = FullMsgId(
@@ -396,29 +378,24 @@ void SendConfirmedFile(
}
}
auto flags = (isEditing ? MTPDmessage::Flags() : NewMessageFlags(peer))
| MTPDmessage::Flag::f_entities
| MTPDmessage::Flag::f_media;
auto clientFlags = NewMessageClientFlags();
auto flags = isEditing ? MessageFlags() : NewMessageFlags(peer);
if (file->to.replyTo) {
flags |= MTPDmessage::Flag::f_reply_to;
flags |= MessageFlag::HasReplyInfo;
}
const auto replyHeader = NewMessageReplyHeader(action);
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, file->to.options);
Api::FillMessagePostFlags(action, peer, flags);
FillMessagePostFlags(action, peer, flags);
if (silentPost) {
flags |= MTPDmessage::Flag::f_silent;
}
if (groupId) {
flags |= MTPDmessage::Flag::f_grouped_id;
flags |= MessageFlag::Silent;
}
if (file->to.options.scheduled) {
flags |= MTPDmessage::Flag::f_from_scheduled;
flags |= MessageFlag::IsOrWasScheduled;
// Scheduled messages have no the 'edited' badge.
flags |= MTPDmessage::Flag::f_edit_hide;
flags |= MessageFlag::HideEdited;
} else {
clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry;
flags |= MessageFlag::LocalHistoryEntry;
}
const auto messageFromId = anonymousPost ? 0 : session->userPeerId();
@@ -426,17 +403,37 @@ void SendConfirmedFile(
? session->user()->name
: QString();
const auto views = 1;
const auto forwards = 0;
if (file->type == SendMediaType::Photo) {
const auto photoFlags = MTPDmessageMediaPhoto::Flag::f_photo | 0;
const auto photo = MTP_messageMediaPhoto(
MTP_flags(photoFlags),
file->photo,
MTPint());
const auto media = [&] {
if (file->type == SendMediaType::Photo) {
return MTP_messageMediaPhoto(
MTP_flags(MTPDmessageMediaPhoto::Flag::f_photo),
file->photo,
MTPint());
} else if (file->type == SendMediaType::File) {
return MTP_messageMediaDocument(
MTP_flags(MTPDmessageMediaDocument::Flag::f_document),
file->document,
MTPint());
} else if (file->type == SendMediaType::Audio) {
return MTP_messageMediaDocument(
MTP_flags(MTPDmessageMediaDocument::Flag::f_document),
file->document,
MTPint());
} else {
Unexpected("Type in sendFilesConfirmed.");
}
}();
const auto mtpMessage = MTP_message(
MTP_flags(flags),
if (itemToEdit) {
itemToEdit->savePreviousMedia();
itemToEdit->applyEdition(MTP_message(
MTP_flags(MTPDmessage::Flag::f_media
| ((flags & MessageFlag::HideEdited)
? MTPDmessage::Flag::f_edit_hide
: MTPDmessage::Flag())
| (localEntities.v.isEmpty()
? MTPDmessage::Flag()
: MTPDmessage::Flag::f_entities)),
MTP_int(newId.msg),
peerToMTP(messageFromId),
peerToMTP(file->to.peer),
@@ -445,105 +442,32 @@ void SendConfirmedFile(
replyHeader,
MTP_int(HistoryItem::NewMessageDate(file->to.options.scheduled)),
MTP_string(caption.text),
photo,
media,
MTPReplyMarkup(),
localEntities,
MTP_int(views),
MTP_int(forwards),
MTPint(), // views
MTPint(), // forwards
MTPMessageReplies(),
MTPint(), // edit_date
MTP_string(messagePostAuthor),
MTP_long(groupId),
//MTPMessageReactions(),
MTPVector<MTPRestrictionReason>(),
MTPint()); // ttl_period
if (itemToEdit) {
itemToEdit->savePreviousMedia();
itemToEdit->applyEdition(mtpMessage.c_message());
} else {
history->addNewMessage(
mtpMessage,
clientFlags,
NewMessageType::Unread);
}
} else if (file->type == SendMediaType::File) {
const auto documentFlags = MTPDmessageMediaDocument::Flag::f_document | 0;
const auto document = MTP_messageMediaDocument(
MTP_flags(documentFlags),
file->document,
MTPint());
const auto mtpMessage = MTP_message(
MTP_flags(flags),
MTP_int(newId.msg),
peerToMTP(messageFromId),
peerToMTP(file->to.peer),
MTPMessageFwdHeader(),
MTPint(),
replyHeader,
MTP_int(HistoryItem::NewMessageDate(file->to.options.scheduled)),
MTP_string(caption.text),
document,
MTPReplyMarkup(),
localEntities,
MTP_int(views),
MTP_int(forwards),
MTPMessageReplies(),
MTPint(), // edit_date
MTP_string(messagePostAuthor),
MTP_long(groupId),
//MTPMessageReactions(),
MTPVector<MTPRestrictionReason>(),
MTPint()); // ttl_period
if (itemToEdit) {
itemToEdit->savePreviousMedia();
itemToEdit->applyEdition(mtpMessage.c_message());
} else {
history->addNewMessage(
mtpMessage,
clientFlags,
NewMessageType::Unread);
}
} else if (file->type == SendMediaType::Audio) {
if (!peer->isChannel() || peer->isMegagroup()) {
flags |= MTPDmessage::Flag::f_media_unread;
}
const auto documentFlags = MTPDmessageMediaDocument::Flag::f_document | 0;
const auto document = MTP_messageMediaDocument(
MTP_flags(documentFlags),
file->document,
MTPint());
history->addNewMessage(
MTP_message(
MTP_flags(flags),
MTP_int(newId.msg),
peerToMTP(messageFromId),
peerToMTP(file->to.peer),
MTPMessageFwdHeader(),
MTPint(),
replyHeader,
MTP_int(
HistoryItem::NewMessageDate(file->to.options.scheduled)),
MTP_string(caption.text),
document,
MTPReplyMarkup(),
localEntities,
MTP_int(views),
MTP_int(forwards),
MTPMessageReplies(),
MTPint(), // edit_date
MTP_string(messagePostAuthor),
MTP_long(groupId),
//MTPMessageReactions(),
MTPVector<MTPRestrictionReason>(),
MTPint()), // ttl_period
clientFlags,
NewMessageType::Unread);
// Voices can't be edited.
MTPint()).c_message());
} else {
Unexpected("Type in sendFilesConfirmed.");
const auto viaBotId = UserId();
history->addNewLocalMessage(
newId.msg,
flags,
viaBotId,
file->to.replyTo,
HistoryItem::NewMessageDate(file->to.options.scheduled),
messageFromId,
messagePostAuthor,
caption,
media,
MTPReplyMarkup(),
groupId);
}
if (isEditing) {

View File

@@ -30,7 +30,7 @@ bool SendDice(Api::MessageToSend &message);
void FillMessagePostFlags(
const SendAction &action,
not_null<PeerData*> peer,
MTPDmessage::Flags &flags);
MessageFlags &flags);
void SendConfirmedFile(
not_null<Main::Session*> session,

View File

@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_authorizations.h"
#include "api/api_text_entities.h"
#include "api/api_user_privacy.h"
#include "main/main_session.h"
#include "main/main_account.h"
#include "mtproto/mtp_instance.h"
@@ -41,7 +42,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_controller.h"
#include "boxes/confirm_box.h"
#include "apiwrap.h"
#include "app.h" // App::formatPhone
#include "ui/text/format_values.h" // Ui::FormatPhone
#include "app.h" // App::quitting
namespace Api {
namespace {
@@ -1054,7 +1056,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
//MTPMessageReactions(),
MTPVector<MTPRestrictionReason>(),
MTP_int(d.vttl_period().value_or_empty())),
MTPDmessage_ClientFlags(),
MessageFlags(),
NewMessageType::Unread);
} break;
@@ -1085,7 +1087,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
//MTPMessageReactions(),
MTPVector<MTPRestrictionReason>(),
MTP_int(d.vttl_period().value_or_empty())),
MTPDmessage_ClientFlags(),
MessageFlags(),
NewMessageType::Unread);
} break;
@@ -1114,7 +1116,7 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) {
if (needToAdd) {
_session->data().addNewMessage(
d.vmessage(),
MTPDmessage_ClientFlags(),
MessageFlags(),
NewMessageType::Unread);
}
} break;
@@ -1208,7 +1210,7 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) {
if (needToAdd) {
_session->data().addNewMessage(
d.vmessage(),
MTPDmessage_ClientFlags(),
MessageFlags(),
NewMessageType::Unread);
}
} break;
@@ -1844,7 +1846,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|| user->isSelf()
|| user->phone().isEmpty())
? QString()
: App::formatPhone(user->phone())),
: Ui::FormatPhone(user->phone())),
user->username);
session().changes().peerUpdated(
@@ -1953,13 +1955,10 @@ void Updates::feedUpdate(const MTPUpdate &update) {
}
return true;
};
if (const auto key = ApiWrap::Privacy::KeyFromMTP(d.vkey().type())) {
if (allLoaded()) {
session().api().handlePrivacyChange(*key, d.vrules());
} else {
session().api().reloadPrivacy(*key);
}
}
session().api().userPrivacy().apply(
d.vkey().type(),
d.vrules(),
allLoaded());
} break;
case mtpc_updatePinnedDialogs: {

View File

@@ -0,0 +1,303 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "api/api_user_privacy.h"
#include "apiwrap.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "data/data_peer.h"
#include "data/data_peer_id.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "main/main_session.h"
namespace Api {
namespace {
constexpr auto kMaxRules = 3; // Allow users, disallow users, Option.
using TLInputRules = MTPVector<MTPInputPrivacyRule>;
using TLRules = MTPVector<MTPPrivacyRule>;
TLInputRules RulesToTL(const UserPrivacy::Rule &rule) {
const auto collectInputUsers = [](const auto &peers) {
auto result = QVector<MTPInputUser>();
result.reserve(peers.size());
for (const auto peer : peers) {
if (const auto user = peer->asUser()) {
result.push_back(user->inputUser);
}
}
return result;
};
const auto collectInputChats = [](const auto &peers) {
auto result = QVector<MTPint>(); // #TODO ids
result.reserve(peers.size());
for (const auto peer : peers) {
if (!peer->isUser()) {
result.push_back(peerToBareMTPInt(peer->id));
}
}
return result;
};
auto result = QVector<MTPInputPrivacyRule>();
result.reserve(kMaxRules);
if (!rule.ignoreAlways) {
const auto users = collectInputUsers(rule.always);
const auto chats = collectInputChats(rule.always);
if (!users.empty()) {
result.push_back(
MTP_inputPrivacyValueAllowUsers(
MTP_vector<MTPInputUser>(users)));
}
if (!chats.empty()) {
result.push_back(
MTP_inputPrivacyValueAllowChatParticipants(
MTP_vector<MTPint>(chats)));
}
}
if (!rule.ignoreNever) {
const auto users = collectInputUsers(rule.never);
const auto chats = collectInputChats(rule.never);
if (!users.empty()) {
result.push_back(
MTP_inputPrivacyValueDisallowUsers(
MTP_vector<MTPInputUser>(users)));
}
if (!chats.empty()) {
result.push_back(
MTP_inputPrivacyValueDisallowChatParticipants(
MTP_vector<MTPint>(chats)));
}
}
result.push_back([&] {
using Option = UserPrivacy::Option;
switch (rule.option) {
case Option::Everyone: return MTP_inputPrivacyValueAllowAll();
case Option::Contacts: return MTP_inputPrivacyValueAllowContacts();
case Option::Nobody: return MTP_inputPrivacyValueDisallowAll();
}
Unexpected("Option value in Api::UserPrivacy::RulesToTL.");
}());
return MTP_vector<MTPInputPrivacyRule>(std::move(result));
}
UserPrivacy::Rule TLToRules(const TLRules &rules, Data::Session &owner) {
// This is simplified version of privacy rules interpretation.
// But it should be fine for all the apps
// that use the same subset of features.
using Option = UserPrivacy::Option;
auto result = UserPrivacy::Rule();
auto optionSet = false;
const auto setOption = [&](Option option) {
if (optionSet) return;
optionSet = true;
result.option = option;
};
auto &always = result.always;
auto &never = result.never;
const auto feed = [&](const MTPPrivacyRule &rule) {
rule.match([&](const MTPDprivacyValueAllowAll &) {
setOption(Option::Everyone);
}, [&](const MTPDprivacyValueAllowContacts &) {
setOption(Option::Contacts);
}, [&](const MTPDprivacyValueAllowUsers &data) {
const auto &users = data.vusers().v;
always.reserve(always.size() + users.size());
for (const auto userId : users) {
const auto user = owner.user(UserId(userId.v));
if (!base::contains(never, user)
&& !base::contains(always, user)) {
always.emplace_back(user);
}
}
}, [&](const MTPDprivacyValueAllowChatParticipants &data) {
const auto &chats = data.vchats().v;
always.reserve(always.size() + chats.size());
for (const auto &chatId : chats) {
const auto chat = owner.chatLoaded(chatId);
const auto peer = chat
? static_cast<PeerData*>(chat)
: owner.channelLoaded(chatId);
if (peer
&& !base::contains(never, peer)
&& !base::contains(always, peer)) {
always.emplace_back(peer);
}
}
}, [&](const MTPDprivacyValueDisallowContacts &) {
// Not supported
}, [&](const MTPDprivacyValueDisallowAll &) {
setOption(Option::Nobody);
}, [&](const MTPDprivacyValueDisallowUsers &data) {
const auto &users = data.vusers().v;
never.reserve(never.size() + users.size());
for (const auto userId : users) {
const auto user = owner.user(UserId(userId.v));
if (!base::contains(always, user)
&& !base::contains(never, user)) {
never.emplace_back(user);
}
}
}, [&](const MTPDprivacyValueDisallowChatParticipants &data) {
const auto &chats = data.vchats().v;
never.reserve(never.size() + chats.size());
for (const auto &chatId : chats) {
const auto chat = owner.chatLoaded(chatId);
const auto peer = chat
? static_cast<PeerData*>(chat)
: owner.channelLoaded(chatId);
if (peer
&& !base::contains(always, peer)
&& !base::contains(never, peer)) {
never.emplace_back(peer);
}
}
});
};
for (const auto &rule : rules.v) {
feed(rule);
}
feed(MTP_privacyValueDisallowAll()); // Disallow by default.
return result;
}
MTPInputPrivacyKey KeyToTL(UserPrivacy::Key key) {
using Key = UserPrivacy::Key;
switch (key) {
case Key::Calls: return MTP_inputPrivacyKeyPhoneCall();
case Key::Invites: return MTP_inputPrivacyKeyChatInvite();
case Key::PhoneNumber: return MTP_inputPrivacyKeyPhoneNumber();
case Key::AddedByPhone:
return MTP_inputPrivacyKeyAddedByPhone();
case Key::LastSeen:
return MTP_inputPrivacyKeyStatusTimestamp();
case Key::CallsPeer2Peer:
return MTP_inputPrivacyKeyPhoneP2P();
case Key::Forwards:
return MTP_inputPrivacyKeyForwards();
case Key::ProfilePhoto:
return MTP_inputPrivacyKeyProfilePhoto();
}
Unexpected("Key in Api::UserPrivacy::KetToTL.");
}
std::optional<UserPrivacy::Key> TLToKey(mtpTypeId type) {
using Key = UserPrivacy::Key;
switch (type) {
case mtpc_privacyKeyPhoneNumber:
case mtpc_inputPrivacyKeyPhoneNumber: return Key::PhoneNumber;
case mtpc_privacyKeyAddedByPhone:
case mtpc_inputPrivacyKeyAddedByPhone: return Key::AddedByPhone;
case mtpc_privacyKeyStatusTimestamp:
case mtpc_inputPrivacyKeyStatusTimestamp: return Key::LastSeen;
case mtpc_privacyKeyChatInvite:
case mtpc_inputPrivacyKeyChatInvite: return Key::Invites;
case mtpc_privacyKeyPhoneCall:
case mtpc_inputPrivacyKeyPhoneCall: return Key::Calls;
case mtpc_privacyKeyPhoneP2P:
case mtpc_inputPrivacyKeyPhoneP2P: return Key::CallsPeer2Peer;
case mtpc_privacyKeyForwards:
case mtpc_inputPrivacyKeyForwards: return Key::Forwards;
case mtpc_privacyKeyProfilePhoto:
case mtpc_inputPrivacyKeyProfilePhoto: return Key::ProfilePhoto;
}
return std::nullopt;
}
} // namespace
UserPrivacy::UserPrivacy(not_null<ApiWrap*> api)
: _session(&api->session())
, _api(&api->instance()) {
}
void UserPrivacy::save(
Key key,
const UserPrivacy::Rule &rule) {
const auto tlKey = KeyToTL(key);
const auto keyTypeId = tlKey.type();
const auto it = _privacySaveRequests.find(keyTypeId);
if (it != _privacySaveRequests.cend()) {
_api.request(it->second).cancel();
_privacySaveRequests.erase(it);
}
const auto requestId = _api.request(MTPaccount_SetPrivacy(
tlKey,
RulesToTL(rule)
)).done([=](const MTPaccount_PrivacyRules &result) {
result.match([&](const MTPDaccount_privacyRules &data) {
_session->data().processUsers(data.vusers());
_session->data().processChats(data.vchats());
_privacySaveRequests.remove(keyTypeId);
apply(keyTypeId, data.vrules(), true);
});
}).fail([=](const MTP::Error &error) {
_privacySaveRequests.remove(keyTypeId);
}).send();
_privacySaveRequests.emplace(keyTypeId, requestId);
}
void UserPrivacy::apply(
mtpTypeId type,
const TLRules &rules,
bool allLoaded) {
if (const auto key = TLToKey(type)) {
if (!allLoaded) {
reload(*key);
return;
}
pushPrivacy(*key, rules);
if ((*key) == Key::LastSeen) {
_session->api().updatePrivacyLastSeens();
}
}
}
void UserPrivacy::reload(Key key) {
if (_privacyRequestIds.contains(key)) {
return;
}
const auto requestId = _api.request(MTPaccount_GetPrivacy(
KeyToTL(key)
)).done([=](const MTPaccount_PrivacyRules &result) {
_privacyRequestIds.erase(key);
result.match([&](const MTPDaccount_privacyRules &data) {
_session->data().processUsers(data.vusers());
_session->data().processChats(data.vchats());
pushPrivacy(key, data.vrules());
});
}).fail([=](const MTP::Error &error) {
_privacyRequestIds.erase(key);
}).send();
_privacyRequestIds.emplace(key, requestId);
}
void UserPrivacy::pushPrivacy(Key key, const TLRules &rules) {
const auto &saved = (_privacyValues[key] =
TLToRules(rules, _session->data()));
const auto i = _privacyChanges.find(key);
if (i != end(_privacyChanges)) {
i->second.fire_copy(saved);
}
}
auto UserPrivacy::value(Key key) -> rpl::producer<UserPrivacy::Rule> {
if (const auto i = _privacyValues.find(key); i != end(_privacyValues)) {
return _privacyChanges[key].events_starting_with_copy(i->second);
} else {
return _privacyChanges[key].events();
}
}
} // namespace Api

View File

@@ -0,0 +1,73 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "mtproto/sender.h"
class ApiWrap;
namespace Main {
class Session;
} // namespace Main
namespace Api {
class UserPrivacy final {
public:
enum class Key {
PhoneNumber,
AddedByPhone,
LastSeen,
Calls,
Invites,
CallsPeer2Peer,
Forwards,
ProfilePhoto,
};
enum class Option {
Everyone,
Contacts,
Nobody,
};
struct Rule {
Option option = Option::Everyone;
std::vector<not_null<PeerData*>> always;
std::vector<not_null<PeerData*>> never;
bool ignoreAlways = false;
bool ignoreNever = false;
};
explicit UserPrivacy(not_null<ApiWrap*> api);
void save(
Key key,
const UserPrivacy::Rule &rule);
void apply(
mtpTypeId type,
const MTPVector<MTPPrivacyRule> &rules,
bool allLoaded);
void reload(Key key);
rpl::producer<Rule> value(Key key);
private:
const not_null<Main::Session*> _session;
void pushPrivacy(Key key, const MTPVector<MTPPrivacyRule> &rules);
base::flat_map<mtpTypeId, mtpRequestId> _privacySaveRequests;
base::flat_map<Key, mtpRequestId> _privacyRequestIds;
base::flat_map<Key, Rule> _privacyValues;
std::map<Key, rpl::event_stream<Rule>> _privacyChanges;
MTP::Sender _api;
};
} // namespace Api

View File

@@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_authorizations.h"
#include "api/api_attached_stickers.h"
#include "api/api_blocked_peers.h"
#include "api/api_cloud_password.h"
#include "api/api_hash.h"
#include "api/api_invite_links.h"
#include "api/api_media.h"
@@ -18,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_sensitive_content.h"
#include "api/api_global_privacy.h"
#include "api/api_updates.h"
#include "api/api_user_privacy.h"
#include "data/stickers/data_stickers.h"
#include "data/data_drafts.h"
#include "data/data_changes.h"
@@ -42,7 +45,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/dialogs_key.h"
#include "core/core_cloud_password.h"
#include "core/application.h"
#include "base/openssl_help.h"
#include "base/unixtime.h"
#include "base/qt_adapters.h"
#include "base/call_delayed.h"
@@ -78,7 +80,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/storage_media_prepare.h"
#include "storage/storage_account.h"
#include "facades.h"
#include "app.h"
#include "app.h" // App::quitting
namespace {
@@ -108,7 +110,6 @@ constexpr auto kStickersByEmojiInvalidateTimeout = crl::time(60 * 60 * 1000);
constexpr auto kNotifySettingSaveTimeout = crl::time(1000);
constexpr auto kDialogsFirstLoad = 20;
constexpr auto kDialogsPerPage = 500;
constexpr auto kBlockedFirstSlice = 16;
using PhotoFileLocationId = Data::PhotoFileLocationId;
using DocumentFileLocationId = Data::DocumentFileLocationId;
@@ -120,65 +121,6 @@ using UpdatedFileReferences = Data::UpdatedFileReferences;
} // namespace
MTPInputPrivacyKey ApiWrap::Privacy::Input(Key key) {
switch (key) {
case Privacy::Key::Calls: return MTP_inputPrivacyKeyPhoneCall();
case Privacy::Key::Invites: return MTP_inputPrivacyKeyChatInvite();
case Privacy::Key::PhoneNumber: return MTP_inputPrivacyKeyPhoneNumber();
case Privacy::Key::AddedByPhone:
return MTP_inputPrivacyKeyAddedByPhone();
case Privacy::Key::LastSeen:
return MTP_inputPrivacyKeyStatusTimestamp();
case Privacy::Key::CallsPeer2Peer:
return MTP_inputPrivacyKeyPhoneP2P();
case Privacy::Key::Forwards:
return MTP_inputPrivacyKeyForwards();
case Privacy::Key::ProfilePhoto:
return MTP_inputPrivacyKeyProfilePhoto();
}
Unexpected("Key in ApiWrap::Privacy::Input.");
}
std::optional<ApiWrap::Privacy::Key> ApiWrap::Privacy::KeyFromMTP(
mtpTypeId type) {
using Key = Privacy::Key;
switch (type) {
case mtpc_privacyKeyPhoneNumber:
case mtpc_inputPrivacyKeyPhoneNumber: return Key::PhoneNumber;
case mtpc_privacyKeyAddedByPhone:
case mtpc_inputPrivacyKeyAddedByPhone: return Key::AddedByPhone;
case mtpc_privacyKeyStatusTimestamp:
case mtpc_inputPrivacyKeyStatusTimestamp: return Key::LastSeen;
case mtpc_privacyKeyChatInvite:
case mtpc_inputPrivacyKeyChatInvite: return Key::Invites;
case mtpc_privacyKeyPhoneCall:
case mtpc_inputPrivacyKeyPhoneCall: return Key::Calls;
case mtpc_privacyKeyPhoneP2P:
case mtpc_inputPrivacyKeyPhoneP2P: return Key::CallsPeer2Peer;
case mtpc_privacyKeyForwards:
case mtpc_inputPrivacyKeyForwards: return Key::Forwards;
case mtpc_privacyKeyProfilePhoto:
case mtpc_inputPrivacyKeyProfilePhoto: return Key::ProfilePhoto;
}
return std::nullopt;
}
bool ApiWrap::BlockedPeersSlice::Item::operator==(const Item &other) const {
return (peer == other.peer) && (date == other.date);
}
bool ApiWrap::BlockedPeersSlice::Item::operator!=(const Item &other) const {
return !(*this == other);
}
bool ApiWrap::BlockedPeersSlice::operator==(const BlockedPeersSlice &other) const {
return (total == other.total) && (list == other.list);
}
bool ApiWrap::BlockedPeersSlice::operator!=(const BlockedPeersSlice &other) const {
return !(*this == other);
}
ApiWrap::ApiWrap(not_null<Main::Session*> session)
: MTP::Sender(&session->account().mtp())
, _session(session)
@@ -192,9 +134,12 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
, _updateNotifySettingsTimer([=] { sendNotifySettingsUpdates(); })
, _authorizations(std::make_unique<Api::Authorizations>(this))
, _attachedStickers(std::make_unique<Api::AttachedStickers>(this))
, _blockedPeers(std::make_unique<Api::BlockedPeers>(this))
, _cloudPassword(std::make_unique<Api::CloudPassword>(this))
, _selfDestruct(std::make_unique<Api::SelfDestruct>(this))
, _sensitiveContent(std::make_unique<Api::SensitiveContent>(this))
, _globalPrivacy(std::make_unique<Api::GlobalPrivacy>(this))
, _userPrivacy(std::make_unique<Api::UserPrivacy>(this))
, _inviteLinks(std::make_unique<Api::InviteLinks>(this)) {
crl::on_main(session, [=] {
// You can't use _session->lifetime() in the constructor,
@@ -2175,68 +2120,6 @@ void ApiWrap::leaveChannel(not_null<ChannelData*> channel) {
}
}
void ApiWrap::blockPeer(not_null<PeerData*> peer) {
if (peer->isBlocked()) {
session().changes().peerUpdated(
peer,
Data::PeerUpdate::Flag::IsBlocked);
} else if (_blockRequests.find(peer) == end(_blockRequests)) {
const auto requestId = request(MTPcontacts_Block(
peer->input
)).done([=](const MTPBool &result) {
_blockRequests.erase(peer);
peer->setIsBlocked(true);
if (_blockedPeersSlice) {
_blockedPeersSlice->list.insert(
_blockedPeersSlice->list.begin(),
{ peer, base::unixtime::now() });
++_blockedPeersSlice->total;
_blockedPeersChanges.fire_copy(*_blockedPeersSlice);
}
}).fail([=](const MTP::Error &error) {
_blockRequests.erase(peer);
}).send();
_blockRequests.emplace(peer, requestId);
}
}
void ApiWrap::unblockPeer(not_null<PeerData*> peer, Fn<void()> onDone) {
if (!peer->isBlocked()) {
session().changes().peerUpdated(
peer,
Data::PeerUpdate::Flag::IsBlocked);
return;
} else if (_blockRequests.find(peer) != end(_blockRequests)) {
return;
}
const auto requestId = request(MTPcontacts_Unblock(
peer->input
)).done([=](const MTPBool &result) {
_blockRequests.erase(peer);
peer->setIsBlocked(false);
if (_blockedPeersSlice) {
auto &list = _blockedPeersSlice->list;
for (auto i = list.begin(); i != list.end(); ++i) {
if (i->peer == peer) {
list.erase(i);
break;
}
}
if (_blockedPeersSlice->total > list.size()) {
--_blockedPeersSlice->total;
}
_blockedPeersChanges.fire_copy(*_blockedPeersSlice);
}
if (onDone) {
onDone();
}
}).fail([=](const MTP::Error &error) {
_blockRequests.erase(peer);
}).send();
_blockRequests.emplace(peer, requestId);
}
void ApiWrap::requestNotifySettings(const MTPInputNotifyPeer &peer) {
const auto key = [&] {
switch (peer.type()) {
@@ -2309,45 +2192,7 @@ void ApiWrap::saveDraftToCloudDelayed(not_null<History*> history) {
}
}
void ApiWrap::savePrivacy(
const MTPInputPrivacyKey &key,
QVector<MTPInputPrivacyRule> &&rules) {
const auto keyTypeId = key.type();
const auto it = _privacySaveRequests.find(keyTypeId);
if (it != _privacySaveRequests.cend()) {
request(it->second).cancel();
_privacySaveRequests.erase(it);
}
const auto requestId = request(MTPaccount_SetPrivacy(
key,
MTP_vector<MTPInputPrivacyRule>(std::move(rules))
)).done([=](const MTPaccount_PrivacyRules &result) {
result.match([&](const MTPDaccount_privacyRules &data) {
_session->data().processUsers(data.vusers());
_session->data().processChats(data.vchats());
_privacySaveRequests.remove(keyTypeId);
if (const auto key = Privacy::KeyFromMTP(keyTypeId)) {
handlePrivacyChange(*key, data.vrules());
}
});
}).fail([=](const MTP::Error &error) {
_privacySaveRequests.remove(keyTypeId);
}).send();
_privacySaveRequests.emplace(keyTypeId, requestId);
}
void ApiWrap::handlePrivacyChange(
Privacy::Key key,
const MTPVector<MTPPrivacyRule> &rules) {
pushPrivacy(key, rules.v);
if (key == Privacy::Key::LastSeen) {
updatePrivacyLastSeens(rules.v);
}
}
void ApiWrap::updatePrivacyLastSeens(const QVector<MTPPrivacyRule> &rules) {
void ApiWrap::updatePrivacyLastSeens() {
const auto now = base::unixtime::now();
_session->data().enumerateUsers([&](UserData *user) {
if (user->isSelf() || !user->isFullLoaded()) {
@@ -3783,18 +3628,17 @@ void ApiWrap::forwardMessages(
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, action.options);
auto flags = MTPDmessage::Flags(0);
auto clientFlags = MTPDmessage_ClientFlags();
auto flags = MessageFlags();
auto sendFlags = MTPmessages_ForwardMessages::Flags(0);
FillMessagePostFlags(action, peer, flags);
if (silentPost) {
sendFlags |= MTPmessages_ForwardMessages::Flag::f_silent;
}
if (action.options.scheduled) {
flags |= MTPDmessage::Flag::f_from_scheduled;
flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_ForwardMessages::Flag::f_schedule_date;
} else {
clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry;
flags |= MessageFlag::LocalHistoryEntry;
}
auto forwardFrom = items.front()->history()->peer;
@@ -3861,7 +3705,6 @@ void ApiWrap::forwardMessages(
history->addNewLocalMessage(
newId.msg,
flags,
clientFlags,
HistoryItem::NewMessageDate(action.options.scheduled),
messageFromId,
messagePostAuthor,
@@ -3926,61 +3769,44 @@ void ApiWrap::sendSharedContact(
_session->data().nextLocalMessageId());
const auto anonymousPost = peer->amAnonymous();
auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media;
auto clientFlags = NewMessageClientFlags();
auto flags = NewMessageFlags(peer);
if (action.replyTo) {
flags |= MTPDmessage::Flag::f_reply_to;
flags |= MessageFlag::HasReplyInfo;
}
const auto replyHeader = NewMessageReplyHeader(action);
FillMessagePostFlags(action, peer, flags);
if (action.options.scheduled) {
flags |= MTPDmessage::Flag::f_from_scheduled;
flags |= MessageFlag::IsOrWasScheduled;
} else {
clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry;
flags |= MessageFlag::LocalHistoryEntry;
}
const auto messageFromId = anonymousPost ? 0 : _session->userPeerId();
const auto messagePostAuthor = peer->isBroadcast()
? _session->user()->name
: QString();
const auto vcard = QString();
const auto views = 1;
const auto forwards = 0;
const auto item = history->addNewMessage(
MTP_message(
MTP_flags(flags),
MTP_int(newId.msg),
peerToMTP(messageFromId),
peerToMTP(peer->id),
MTPMessageFwdHeader(),
MTPint(), // via_bot_id
replyHeader,
MTP_int(HistoryItem::NewMessageDate(action.options.scheduled)),
MTP_string(),
MTP_messageMediaContact(
MTP_string(phone),
MTP_string(firstName),
MTP_string(lastName),
MTP_string(vcard),
MTP_int(userId.bare)), // #TODO ids
MTPReplyMarkup(),
MTPVector<MTPMessageEntity>(),
MTP_int(views),
MTP_int(forwards),
MTPMessageReplies(),
MTPint(), // edit_date
MTP_string(messagePostAuthor),
MTPlong(),
//MTPMessageReactions(),
MTPVector<MTPRestrictionReason>(),
MTPint()), // ttl_period
clientFlags,
NewMessageType::Unread);
const auto viaBotId = UserId();
const auto item = history->addNewLocalMessage(
newId.msg,
flags,
viaBotId,
action.replyTo,
HistoryItem::NewMessageDate(action.options.scheduled),
messageFromId,
messagePostAuthor,
TextWithEntities(),
MTP_messageMediaContact(
MTP_string(phone),
MTP_string(firstName),
MTP_string(lastName),
MTP_string(), // vcard
MTP_int(userId.bare)), // #TODO ids
MTPReplyMarkup());
const auto media = MTP_inputMediaContact(
MTP_string(phone),
MTP_string(firstName),
MTP_string(lastName),
MTP_string(vcard));
MTP_string()); // vcard
sendMedia(item, media, action.options);
_session->data().sendHistoryChangeNotifications();
@@ -4181,11 +4007,10 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
_session->data().registerMessageSentData(randomId, peer->id, sending.text);
MTPstring msgText(MTP_string(sending.text));
auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_entities;
auto clientFlags = NewMessageClientFlags();
auto flags = NewMessageFlags(peer);
auto sendFlags = MTPmessages_SendMessage::Flags(0);
if (action.replyTo) {
flags |= MTPDmessage::Flag::f_reply_to;
flags |= MessageFlag::HasReplyInfo;
sendFlags |= MTPmessages_SendMessage::Flag::f_reply_to_msg_id;
}
const auto replyHeader = NewMessageReplyHeader(action);
@@ -4198,7 +4023,6 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
MTP_webPagePending(
MTP_long(page->id),
MTP_int(page->pendingTill)));
flags |= MTPDmessage::Flag::f_media;
}
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, action.options);
@@ -4206,10 +4030,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
if (silentPost) {
sendFlags |= MTPmessages_SendMessage::Flag::f_silent;
}
auto localEntities = Api::EntitiesToMTP(
_session,
sending.entities);
auto sentEntities = Api::EntitiesToMTP(
const auto sentEntities = Api::EntitiesToMTP(
_session,
sending.entities,
Api::ConvertOption::SkipLocal);
@@ -4227,39 +4048,23 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
? _session->user()->name
: QString();
if (action.options.scheduled) {
flags |= MTPDmessage::Flag::f_from_scheduled;
flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date;
} else {
clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry;
flags |= MessageFlag::LocalHistoryEntry;
}
const auto views = 1;
const auto forwards = 0;
lastMessage = history->addNewMessage(
MTP_message(
MTP_flags(flags),
MTP_int(newId.msg),
peerToMTP(messageFromId),
peerToMTP(peer->id),
MTPMessageFwdHeader(),
MTPint(), // via_bot_id
replyHeader,
MTP_int(
HistoryItem::NewMessageDate(action.options.scheduled)),
msgText,
media,
MTPReplyMarkup(),
localEntities,
MTP_int(views),
MTP_int(forwards),
MTPMessageReplies(),
MTPint(), // edit_date
MTP_string(messagePostAuthor),
MTPlong(),
//MTPMessageReactions(),
MTPVector<MTPRestrictionReason>(),
MTPint()), // ttl_period
clientFlags,
NewMessageType::Unread);
const auto viaBotId = UserId();
lastMessage = history->addNewLocalMessage(
newId.msg,
flags,
viaBotId,
action.replyTo,
HistoryItem::NewMessageDate(action.options.scheduled),
messageFromId,
messagePostAuthor,
sending,
media,
MTPReplyMarkup());
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
history->sendRequestId = request(MTPmessages_SendMessage(
MTP_flags(sendFlags),
@@ -4346,11 +4151,10 @@ void ApiWrap::sendInlineResult(
_session->data().nextLocalMessageId());
const auto randomId = openssl::RandomValue<uint64>();
auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media;
auto clientFlags = NewMessageClientFlags();
auto flags = NewMessageFlags(peer);
auto sendFlags = MTPmessages_SendInlineBotResult::Flag::f_clear_draft | 0;
if (action.replyTo) {
flags |= MTPDmessage::Flag::f_reply_to;
flags |= MessageFlag::HasReplyInfo;
sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_reply_to_msg_id;
}
const auto anonymousPost = peer->amAnonymous();
@@ -4360,13 +4164,13 @@ void ApiWrap::sendInlineResult(
sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_silent;
}
if (bot) {
flags |= MTPDmessage::Flag::f_via_bot_id;
flags |= MessageFlag::HasViaBot;
}
if (action.options.scheduled) {
flags |= MTPDmessage::Flag::f_from_scheduled;
flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_schedule_date;
} else {
clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry;
flags |= MessageFlag::LocalHistoryEntry;
}
const auto messageFromId = anonymousPost ? 0 : _session->userPeerId();
@@ -4379,10 +4183,9 @@ void ApiWrap::sendInlineResult(
data->addToHistory(
history,
flags,
clientFlags,
newId.msg,
messageFromId,
MTP_int(HistoryItem::NewMessageDate(action.options.scheduled)),
HistoryItem::NewMessageDate(action.options.scheduled),
bot ? peerToUser(bot->id) : 0,
action.replyTo,
messagePostAuthor);
@@ -4784,52 +4587,6 @@ void ApiWrap::clearPeerPhoto(not_null<PhotoData*> photo) {
}
}
void ApiWrap::reloadPasswordState() {
if (_passwordRequestId) {
return;
}
_passwordRequestId = request(MTPaccount_GetPassword(
)).done([=](const MTPaccount_Password &result) {
_passwordRequestId = 0;
result.match([&](const MTPDaccount_password &data) {
openssl::AddRandomSeed(bytes::make_span(data.vsecure_random().v));
if (_passwordState) {
*_passwordState = Core::ParseCloudPasswordState(data);
} else {
_passwordState = std::make_unique<Core::CloudPasswordState>(
Core::ParseCloudPasswordState(data));
}
_passwordStateChanges.fire_copy(*_passwordState);
});
}).fail([=](const MTP::Error &error) {
_passwordRequestId = 0;
}).send();
}
void ApiWrap::clearUnconfirmedPassword() {
_passwordRequestId = request(MTPaccount_CancelPasswordEmail(
)).done([=](const MTPBool &result) {
_passwordRequestId = 0;
reloadPasswordState();
}).fail([=](const MTP::Error &error) {
_passwordRequestId = 0;
reloadPasswordState();
}).send();
}
rpl::producer<Core::CloudPasswordState> ApiWrap::passwordState() const {
return _passwordState
? _passwordStateChanges.events_starting_with_copy(*_passwordState)
: (_passwordStateChanges.events() | rpl::type_erased());
}
auto ApiWrap::passwordStateCurrent() const
-> std::optional<Core::CloudPasswordState> {
return _passwordState
? base::make_optional(*_passwordState)
: std::nullopt;
}
void ApiWrap::reloadContactSignupSilent() {
if (_contactSignupSilentRequestId) {
return;
@@ -4903,176 +4660,6 @@ void ApiWrap::saveSelfBio(const QString &text, FnMut<void()> done) {
}).send();
}
void ApiWrap::reloadPrivacy(Privacy::Key key) {
if (_privacyRequestIds.contains(key)) {
return;
}
const auto requestId = request(MTPaccount_GetPrivacy(
Privacy::Input(key)
)).done([=](const MTPaccount_PrivacyRules &result) {
_privacyRequestIds.erase(key);
result.match([&](const MTPDaccount_privacyRules &data) {
_session->data().processUsers(data.vusers());
_session->data().processChats(data.vchats());
pushPrivacy(key, data.vrules().v);
});
}).fail([=](const MTP::Error &error) {
_privacyRequestIds.erase(key);
}).send();
_privacyRequestIds.emplace(key, requestId);
}
auto ApiWrap::parsePrivacy(const QVector<MTPPrivacyRule> &rules)
-> Privacy {
using Option = Privacy::Option;
// This is simplified version of privacy rules interpretation.
// But it should be fine for all the apps
// that use the same subset of features.
auto result = Privacy();
auto optionSet = false;
const auto SetOption = [&](Option option) {
if (optionSet) return;
optionSet = true;
result.option = option;
};
auto &always = result.always;
auto &never = result.never;
const auto Feed = [&](const MTPPrivacyRule &rule) {
rule.match([&](const MTPDprivacyValueAllowAll &) {
SetOption(Option::Everyone);
}, [&](const MTPDprivacyValueAllowContacts &) {
SetOption(Option::Contacts);
}, [&](const MTPDprivacyValueAllowUsers &data) {
const auto &users = data.vusers().v;
always.reserve(always.size() + users.size());
for (const auto userId : users) {
const auto user = _session->data().user(UserId(userId.v));
if (!base::contains(never, user)
&& !base::contains(always, user)) {
always.emplace_back(user);
}
}
}, [&](const MTPDprivacyValueAllowChatParticipants &data) {
const auto &chats = data.vchats().v;
always.reserve(always.size() + chats.size());
for (const auto &chatId : chats) {
const auto chat = _session->data().chatLoaded(chatId);
const auto peer = chat
? static_cast<PeerData*>(chat)
: _session->data().channelLoaded(chatId);
if (peer
&& !base::contains(never, peer)
&& !base::contains(always, peer)) {
always.emplace_back(peer);
}
}
}, [&](const MTPDprivacyValueDisallowContacts &) {
// not supported
}, [&](const MTPDprivacyValueDisallowAll &) {
SetOption(Option::Nobody);
}, [&](const MTPDprivacyValueDisallowUsers &data) {
const auto &users = data.vusers().v;
never.reserve(never.size() + users.size());
for (const auto userId : users) {
const auto user = _session->data().user(UserId(userId.v));
if (!base::contains(always, user)
&& !base::contains(never, user)) {
never.emplace_back(user);
}
}
}, [&](const MTPDprivacyValueDisallowChatParticipants &data) {
const auto &chats = data.vchats().v;
never.reserve(never.size() + chats.size());
for (const auto &chatId : chats) {
const auto chat = _session->data().chatLoaded(chatId);
const auto peer = chat
? static_cast<PeerData*>(chat)
: _session->data().channelLoaded(chatId);
if (peer
&& !base::contains(always, peer)
&& !base::contains(never, peer)) {
never.emplace_back(peer);
}
}
});
};
for (const auto &rule : rules) {
Feed(rule);
}
Feed(MTP_privacyValueDisallowAll()); // disallow by default.
return result;
}
void ApiWrap::pushPrivacy(
Privacy::Key key,
const QVector<MTPPrivacyRule> &rules) {
const auto &saved = (_privacyValues[key] = parsePrivacy(rules));
const auto i = _privacyChanges.find(key);
if (i != end(_privacyChanges)) {
i->second.fire_copy(saved);
}
}
auto ApiWrap::privacyValue(Privacy::Key key) -> rpl::producer<Privacy> {
if (const auto i = _privacyValues.find(key); i != end(_privacyValues)) {
return _privacyChanges[key].events_starting_with_copy(i->second);
} else {
return _privacyChanges[key].events();
}
}
void ApiWrap::reloadBlockedPeers() {
if (_blockedPeersRequestId) {
return;
}
_blockedPeersRequestId = request(MTPcontacts_GetBlocked(
MTP_int(0),
MTP_int(kBlockedFirstSlice)
)).done([=](const MTPcontacts_Blocked &result) {
_blockedPeersRequestId = 0;
const auto push = [&](
int count,
const QVector<MTPPeerBlocked> &list) {
auto slice = BlockedPeersSlice();
slice.total = std::max(count, list.size());
slice.list.reserve(list.size());
for (const auto &contact : list) {
contact.match([&](const MTPDpeerBlocked &data) {
const auto peer = _session->data().peerLoaded(
peerFromMTP(data.vpeer_id()));
if (peer) {
peer->setIsBlocked(true);
slice.list.push_back({ peer, data.vdate().v });
}
});
}
if (!_blockedPeersSlice || *_blockedPeersSlice != slice) {
_blockedPeersSlice = slice;
_blockedPeersChanges.fire(std::move(slice));
}
};
result.match([&](const MTPDcontacts_blockedSlice &data) {
_session->data().processUsers(data.vusers());
push(data.vcount().v, data.vblocked().v);
}, [&](const MTPDcontacts_blocked &data) {
_session->data().processUsers(data.vusers());
push(0, data.vblocked().v);
});
}).fail([=](const MTP::Error &error) {
_blockedPeersRequestId = 0;
}).send();
}
auto ApiWrap::blockedPeersSlice() -> rpl::producer<BlockedPeersSlice> {
if (!_blockedPeersSlice) {
reloadBlockedPeers();
}
return _blockedPeersSlice
? _blockedPeersChanges.events_starting_with_copy(*_blockedPeersSlice)
: (_blockedPeersChanges.events() | rpl::type_erased());
}
Api::Authorizations &ApiWrap::authorizations() {
return *_authorizations;
}
@@ -5081,6 +4668,14 @@ Api::AttachedStickers &ApiWrap::attachedStickers() {
return *_attachedStickers;
}
Api::BlockedPeers &ApiWrap::blockedPeers() {
return *_blockedPeers;
}
Api::CloudPassword &ApiWrap::cloudPassword() {
return *_cloudPassword;
}
Api::SelfDestruct &ApiWrap::selfDestruct() {
return *_selfDestruct;
}
@@ -5093,6 +4688,10 @@ Api::GlobalPrivacy &ApiWrap::globalPrivacy() {
return *_globalPrivacy;
}
Api::UserPrivacy &ApiWrap::userPrivacy() {
return *_userPrivacy;
}
Api::InviteLinks &ApiWrap::inviteLinks() {
return *_inviteLinks;
}

View File

@@ -45,10 +45,6 @@ namespace Dialogs {
class Key;
} // namespace Dialogs
namespace Core {
struct CloudPasswordState;
} // namespace Core
namespace Ui {
struct PreparedList;
} // namespace Ui
@@ -58,9 +54,12 @@ namespace Api {
class Updates;
class Authorizations;
class AttachedStickers;
class BlockedPeers;
class CloudPassword;
class SelfDestruct;
class SensitiveContent;
class GlobalPrivacy;
class UserPrivacy;
class InviteLinks;
namespace details {
@@ -113,46 +112,6 @@ public:
using SendAction = Api::SendAction;
using MessageToSend = Api::MessageToSend;
struct Privacy {
enum class Key {
PhoneNumber,
AddedByPhone,
LastSeen,
Calls,
Invites,
CallsPeer2Peer,
Forwards,
ProfilePhoto,
};
enum class Option {
Everyone,
Contacts,
Nobody,
};
Option option = Option::Everyone;
std::vector<not_null<PeerData*>> always;
std::vector<not_null<PeerData*>> never;
static MTPInputPrivacyKey Input(Key key);
static std::optional<Key> KeyFromMTP(mtpTypeId type);
};
struct BlockedPeersSlice {
struct Item {
PeerData *peer = nullptr;
TimeId date = 0;
bool operator==(const Item &other) const;
bool operator!=(const Item &other) const;
};
QVector<Item> list;
int total = 0;
bool operator==(const BlockedPeersSlice &other) const;
bool operator!=(const BlockedPeersSlice &other) const;
};
explicit ApiWrap(not_null<Main::Session*> session);
~ApiWrap();
@@ -295,19 +254,10 @@ public:
void joinChannel(not_null<ChannelData*> channel);
void leaveChannel(not_null<ChannelData*> channel);
void blockPeer(not_null<PeerData*> peer);
void unblockPeer(not_null<PeerData*> peer, Fn<void()> onDone = nullptr);
void requestNotifySettings(const MTPInputNotifyPeer &peer);
void updateNotifySettingsDelayed(not_null<const PeerData*> peer);
void saveDraftToCloudDelayed(not_null<History*> history);
void savePrivacy(
const MTPInputPrivacyKey &key,
QVector<MTPInputPrivacyRule> &&rules);
void handlePrivacyChange(
Privacy::Key key,
const MTPVector<MTPPrivacyRule> &rules);
static int OnlineTillFromStatus(
const MTPUserStatus &status,
int currentOnlineTill);
@@ -433,11 +383,6 @@ public:
void uploadPeerPhoto(not_null<PeerData*> peer, QImage &&image);
void clearPeerPhoto(not_null<PhotoData*> photo);
void reloadPasswordState();
void clearUnconfirmedPassword();
rpl::producer<Core::CloudPasswordState> passwordState() const;
std::optional<Core::CloudPasswordState> passwordStateCurrent() const;
void reloadContactSignupSilent();
rpl::producer<bool> contactSignupSilent() const;
std::optional<bool> contactSignupSilentCurrent() const;
@@ -445,17 +390,14 @@ public:
void saveSelfBio(const QString &text, FnMut<void()> done);
void reloadPrivacy(Privacy::Key key);
rpl::producer<Privacy> privacyValue(Privacy::Key key);
void reloadBlockedPeers();
rpl::producer<BlockedPeersSlice> blockedPeersSlice();
[[nodiscard]] Api::Authorizations &authorizations();
[[nodiscard]] Api::AttachedStickers &attachedStickers();
[[nodiscard]] Api::BlockedPeers &blockedPeers();
[[nodiscard]] Api::CloudPassword &cloudPassword();
[[nodiscard]] Api::SelfDestruct &selfDestruct();
[[nodiscard]] Api::SensitiveContent &sensitiveContent();
[[nodiscard]] Api::GlobalPrivacy &globalPrivacy();
[[nodiscard]] Api::UserPrivacy &userPrivacy();
[[nodiscard]] Api::InviteLinks &inviteLinks();
void createPoll(
@@ -469,6 +411,8 @@ public:
void closePoll(not_null<HistoryItem*> item);
void reloadPollResults(not_null<HistoryItem*> item);
void updatePrivacyLastSeens();
private:
struct MessageDataRequest {
using Callbacks = QList<RequestMessageDataCallback>;
@@ -625,12 +569,6 @@ private:
void photoUploadReady(const FullMsgId &msgId, const MTPInputFile &file);
Privacy parsePrivacy(const QVector<MTPPrivacyRule> &rules);
void pushPrivacy(
Privacy::Key key,
const QVector<MTPPrivacyRule> &rules);
void updatePrivacyLastSeens(const QVector<MTPPrivacyRule> &rules);
void migrateDone(
not_null<PeerData*> peer,
not_null<ChannelData*> channel);
@@ -675,7 +613,6 @@ private:
QMap<uint64, QPair<uint64, mtpRequestId> > _stickerSetRequests;
QMap<ChannelData*, mtpRequestId> _channelAmInRequests;
base::flat_map<not_null<PeerData*>, mtpRequestId> _blockRequests;
base::flat_map<PeerId, mtpRequestId> _notifySettingRequests;
base::flat_map<not_null<History*>, mtpRequestId> _draftsSaveRequestIds;
base::Timer _draftsSaveTimer;
@@ -700,8 +637,6 @@ private:
base::flat_map<not_null<EmojiPtr>, StickersByEmoji> _stickersByEmoji;
base::flat_map<mtpTypeId, mtpRequestId> _privacySaveRequests;
mtpRequestId _contactsRequestId = 0;
mtpRequestId _contactsStatusesRequestId = 0;
@@ -764,27 +699,18 @@ private:
base::flat_map<FullMsgId, not_null<PeerData*>> _peerPhotoUploads;
mtpRequestId _passwordRequestId = 0;
std::unique_ptr<Core::CloudPasswordState> _passwordState;
rpl::event_stream<Core::CloudPasswordState> _passwordStateChanges;
mtpRequestId _saveBioRequestId = 0;
FnMut<void()> _saveBioDone;
QString _saveBioText;
base::flat_map<Privacy::Key, mtpRequestId> _privacyRequestIds;
base::flat_map<Privacy::Key, Privacy> _privacyValues;
std::map<Privacy::Key, rpl::event_stream<Privacy>> _privacyChanges;
mtpRequestId _blockedPeersRequestId = 0;
std::optional<BlockedPeersSlice> _blockedPeersSlice;
rpl::event_stream<BlockedPeersSlice> _blockedPeersChanges;
const std::unique_ptr<Api::Authorizations> _authorizations;
const std::unique_ptr<Api::AttachedStickers> _attachedStickers;
const std::unique_ptr<Api::BlockedPeers> _blockedPeers;
const std::unique_ptr<Api::CloudPassword> _cloudPassword;
const std::unique_ptr<Api::SelfDestruct> _selfDestruct;
const std::unique_ptr<Api::SensitiveContent> _sensitiveContent;
const std::unique_ptr<Api::GlobalPrivacy> _globalPrivacy;
const std::unique_ptr<Api::UserPrivacy> _userPrivacy;
const std::unique_ptr<Api::InviteLinks> _inviteLinks;
base::flat_map<FullMsgId, mtpRequestId> _pollVotesRequestIds;

View File

@@ -7,53 +7,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "app.h"
#include "lang/lang_keys.h"
#include "boxes/confirm_box.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_user.h"
#include "data/data_abstract_structure.h"
#include "data/data_media_types.h"
#include "data/data_session.h"
#include "data/data_document.h"
#include "history/history.h"
#include "history/history_location_manager.h"
#include "history/history_item_components.h"
#include "history/view/history_view_element.h"
#include "media/audio/media_audio.h"
#include "ui/image/image.h"
#include "ui/cached_round_corners.h"
#include "inline_bots/inline_bot_layout_item.h"
#include "core/crash_reports.h"
#include "core/update_checker.h"
#include "core/sandbox.h"
#include "core/application.h"
#include "window/notifications_manager.h"
#include "window/window_controller.h"
#include "platform/platform_notifications_manager.h"
#include "storage/file_upload.h"
#include "storage/localstorage.h"
#include "storage/storage_facade.h"
#include "storage/storage_shared_media.h"
#include "mainwindow.h"
#include "mainwidget.h"
#include "apiwrap.h"
#include "numbers.h"
#include "main/main_session.h"
#include "styles/style_boxes.h"
#include "styles/style_overview.h"
#include "styles/style_media_view.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h"
#include <QtCore/QBuffer>
#include <QtGui/QFontDatabase>
#ifdef OS_MAC_OLD
#include <libexif/exif-data.h>
#endif // OS_MAC_OLD
namespace {
constexpr auto kImageAreaLimit = 12'032 * 9'024;
@@ -70,41 +32,6 @@ HistoryView::Element *hoveredItem = nullptr,
namespace App {
QString formatPhone(QString phone) {
if (phone.isEmpty()) return QString();
if (phone.at(0) == '0') return phone;
QString number = phone;
for (const QChar *ch = phone.constData(), *e = ch + phone.size(); ch != e; ++ch) {
if (ch->unicode() < '0' || ch->unicode() > '9') {
number = phone.replace(QRegularExpression(qsl("[^\\d]")), QString());
}
}
QVector<int> groups = phoneNumberParse(number);
if (groups.isEmpty()) return '+' + number;
QString result;
result.reserve(number.size() + groups.size() + 1);
result.append('+');
int32 sum = 0;
for (int32 i = 0, l = groups.size(); i < l; ++i) {
result.append(number.midRef(sum, groups.at(i)));
sum += groups.at(i);
if (sum < number.size()) result.append(' ');
}
if (sum < number.size()) result.append(number.midRef(sum));
return result;
}
void initMedia() {
Ui::StartCachedCorners();
}
void deinitMedia() {
Ui::FinishCachedCorners();
Data::clearGlobalStructures();
}
void hoveredItem(HistoryView::Element *item) {
::hoveredItem = item;
}
@@ -195,80 +122,4 @@ namespace App {
App::quit();
}
QImage readImage(QByteArray data, QByteArray *format, bool opaque, bool *animated) {
if (data.isEmpty()) {
return QImage();
}
QByteArray tmpFormat;
QImage result;
QBuffer buffer(&data);
if (!format) {
format = &tmpFormat;
}
{
QImageReader reader(&buffer, *format);
#ifndef OS_MAC_OLD
reader.setAutoTransform(true);
#endif // OS_MAC_OLD
if (animated) *animated = reader.supportsAnimation() && reader.imageCount() > 1;
if (!reader.canRead()) {
return QImage();
}
const auto imageSize = reader.size();
if (imageSize.width() * imageSize.height() > kImageAreaLimit) {
return QImage();
}
QByteArray fmt = reader.format();
if (!fmt.isEmpty()) *format = fmt;
if (!reader.read(&result)) {
return QImage();
}
fmt = reader.format();
if (!fmt.isEmpty()) *format = fmt;
}
buffer.seek(0);
auto fmt = QString::fromUtf8(*format).toLower();
if (fmt == "jpg" || fmt == "jpeg") {
#ifdef OS_MAC_OLD
if (auto exifData = exif_data_new_from_data((const uchar*)(data.constData()), data.size())) {
auto byteOrder = exif_data_get_byte_order(exifData);
if (auto exifEntry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION)) {
auto orientationFix = [exifEntry, byteOrder] {
auto orientation = exif_get_short(exifEntry->data, byteOrder);
switch (orientation) {
case 2: return QTransform(-1, 0, 0, 1, 0, 0);
case 3: return QTransform(-1, 0, 0, -1, 0, 0);
case 4: return QTransform(1, 0, 0, -1, 0, 0);
case 5: return QTransform(0, -1, -1, 0, 0, 0);
case 6: return QTransform(0, 1, -1, 0, 0, 0);
case 7: return QTransform(0, 1, 1, 0, 0, 0);
case 8: return QTransform(0, -1, 1, 0, 0, 0);
}
return QTransform();
};
result = result.transformed(orientationFix());
}
exif_data_free(exifData);
}
#endif // OS_MAC_OLD
} else if (opaque) {
result = Images::prepareOpaque(std::move(result));
}
return result;
}
QImage readImage(const QString &file, QByteArray *format, bool opaque, bool *animated, QByteArray *content) {
QFile f(file);
if (f.size() > kImageSizeLimit || !f.open(QIODevice::ReadOnly)) {
if (animated) *animated = false;
return QImage();
}
auto imageBytes = f.readAll();
auto result = readImage(imageBytes, format, opaque, animated);
if (content && !result.isNull()) {
*content = imageBytes;
}
return result;
}
}

View File

@@ -7,15 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "data/data_types.h"
namespace HistoryView {
class Element;
} // namespace HistoryView
namespace App {
QString formatPhone(QString phone);
void hoveredItem(HistoryView::Element *item);
HistoryView::Element *hoveredItem();
void pressedItem(HistoryView::Element *item);
@@ -28,9 +24,6 @@ namespace App {
HistoryView::Element *mousedItem();
void clearMousedItems();
void initMedia();
void deinitMedia();
enum LaunchState {
Launched = 0,
QuitRequested = 1,
@@ -42,8 +35,4 @@ namespace App {
void setLaunchState(LaunchState state);
void restart();
constexpr auto kImageSizeLimit = 64 * 1024 * 1024; // Open images up to 64mb jpg/png/gif
QImage readImage(QByteArray data, QByteArray *format = nullptr, bool opaque = true, bool *animated = nullptr);
QImage readImage(const QString &file, QByteArray *format = nullptr, bool opaque = true, bool *animated = nullptr, QByteArray *content = 0);
};

View File

@@ -93,13 +93,9 @@ void AboutBox::showVersionHistory() {
url += qsl("win/%1.zip");
} else if (Platform::IsWindows64Bit()) {
url += qsl("win64/%1.zip");
} else if (Platform::IsOSXBuild()) {
url += qsl("osx/%1.zip");
} else if (Platform::IsMac()) {
url += qsl("mac/%1.zip");
} else if (Platform::IsLinux32Bit()) {
url += qsl("linux32/%1.tar.xz");
} else if (Platform::IsLinux64Bit()) {
} else if (Platform::IsLinux()) {
url += qsl("linux/%1.tar.xz");
} else {
Unexpected("Platform value.");

View File

@@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwidget.h"
#include "mainwindow.h"
#include "app.h"
namespace Ui {
namespace internal {

View File

@@ -7,41 +7,139 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/auto_lock_box.h"
#include "lang/lang_keys.h"
#include "storage/localstorage.h"
#include "core/application.h"
#include "mainwindow.h"
#include "lang/lang_keys.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/time_input.h"
#include "ui/ui_utility.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
AutoLockBox::AutoLockBox(QWidget*, not_null<Main::Session*> session)
: _session(session) {
namespace {
constexpr auto kCustom = std::numeric_limits<int>::max();
constexpr auto kOptions = { 60, 300, 3600, 18000, kCustom };
constexpr auto kDefaultCustom = "10:00"_cs;
auto TimeString(int seconds) {
const auto hours = seconds / 3600;
const auto minutes = (seconds - hours * 3600) / 60;
return QString("%1:%2").arg(hours).arg(minutes, 2, 10, QLatin1Char('0'));
}
} // namespace
AutoLockBox::AutoLockBox(QWidget*) {
}
void AutoLockBox::prepare() {
setTitle(tr::lng_passcode_autolock());
addButton(tr::lng_box_ok(), [this] { closeBox(); });
addButton(tr::lng_box_ok(), [=] { closeBox(); });
auto options = { 60, 300, 3600, 18000 };
const auto currentTime = Core::App().settings().autoLock();
auto group = std::make_shared<Ui::RadiobuttonGroup>(
Core::App().settings().autoLock());
const auto group = std::make_shared<Ui::RadiobuttonGroup>(
ranges::contains(kOptions, currentTime) ? currentTime : kCustom);
const auto x = st::boxPadding.left() + st::boxOptionListPadding.left();
auto y = st::boxOptionListPadding.top() + st::autolockButton.margin.top();
auto count = int(options.size());
const auto count = int(kOptions.size());
_options.reserve(count);
for (auto seconds : options) {
_options.emplace_back(this, group, seconds, (seconds % 3600) ? tr::lng_passcode_autolock_minutes(tr::now, lt_count, seconds / 60) : tr::lng_passcode_autolock_hours(tr::now, lt_count, seconds / 3600), st::autolockButton);
_options.back()->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), y);
for (const auto seconds : kOptions) {
const auto text = [&] {
if (seconds == kCustom) {
return QString();
}
const auto minutes = (seconds % 3600);
return (minutes
? tr::lng_passcode_autolock_minutes
: tr::lng_passcode_autolock_hours)(
tr::now,
lt_count,
minutes ? (seconds / 60) : (seconds / 3600));
}();
_options.emplace_back(
this,
group,
seconds,
text,
st::autolockButton);
_options.back()->moveToLeft(x, y);
y += _options.back()->heightNoMargins() + st::boxOptionListSkip;
}
group->setChangedCallback([this](int value) { durationChanged(value); });
setDimensions(st::autolockWidth, st::boxOptionListPadding.top() + count * _options.back()->heightNoMargins() + (count - 1) * st::boxOptionListSkip + st::boxOptionListPadding.bottom() + st::boxPadding.bottom());
const auto timeInput = [&] {
const auto &last = _options.back();
const auto &st = st::autolockButton;
const auto textLeft = st.checkPosition.x()
+ last->checkRect().width()
+ st.textPosition.x();
const auto textTop = st.margin.top() + st.textPosition.y();
const auto timeInput = Ui::CreateChild<Ui::TimeInput>(
this,
(group->value() == kCustom)
? TimeString(currentTime)
: kDefaultCustom.utf8(),
st::autolockTimeField,
st::autolockDateField,
st::scheduleTimeSeparator,
st::scheduleTimeSeparatorPadding);
timeInput->resizeToWidth(st::autolockTimeWidth);
timeInput->moveToLeft(last->x() + textLeft, last->y() + textTop);
return timeInput;
}();
const auto collect = [=] {
const auto timeValue = timeInput->valueCurrent().split(':');
if (timeValue.size() != 2) {
return 0;
}
return timeValue[0].toInt() * 3600 + timeValue[1].toInt() * 60;
};
timeInput->focuses(
) | rpl::start_with_next([=] {
group->setValue(kCustom);
}, lifetime());
group->setChangedCallback([=](int value) {
if (value != kCustom) {
durationChanged(value);
} else {
timeInput->setFocusFast();
}
});
rpl::merge(
boxClosing() | rpl::filter([=] { return group->value() == kCustom; }),
timeInput->submitRequests()
) | rpl::start_with_next([=] {
if (const auto result = collect()) {
durationChanged(result);
} else {
timeInput->showError();
}
}, lifetime());
const auto timeInputBottom = timeInput->y() + timeInput->height();
setDimensions(
st::autolockWidth,
st::boxOptionListPadding.top()
+ (timeInputBottom - _options.back()->bottomNoMargins())
+ count * _options.back()->heightNoMargins()
+ (count - 1) * st::boxOptionListSkip
+ st::boxOptionListPadding.bottom()
+ st::boxPadding.bottom());
}
void AutoLockBox::durationChanged(int seconds) {
if (Core::App().settings().autoLock() == seconds) {
closeBox();
return;
}
Core::App().settings().setAutoLock(seconds);
Core::App().saveSettingsDelayed();

View File

@@ -9,17 +9,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/abstract_box.h"
namespace Main {
class Session;
} // namespace Main
namespace Ui {
class Radiobutton;
} // namespace Ui
class AutoLockBox : public Ui::BoxContent {
public:
AutoLockBox(QWidget*, not_null<Main::Session*> session);
AutoLockBox(QWidget*);
protected:
void prepare() override;
@@ -27,8 +23,6 @@ protected:
private:
void durationChanged(int seconds);
const not_null<Main::Session*> _session;
std::vector<object_ptr<Ui::Radiobutton>> _options;
};

View File

@@ -255,7 +255,7 @@ void BackgroundBox::Inner::updatePapers() {
_papers = _session->data().wallpapers(
) | ranges::views::filter([](const Data::WallPaper &paper) {
return !paper.isPattern() || paper.backgroundColor().has_value();
return !paper.isPattern() || !paper.backgroundColors().empty();
}) | ranges::views::transform([](const Data::WallPaper &paper) {
return Paper{ paper };
}) | ranges::to_vector;
@@ -326,8 +326,18 @@ void BackgroundBox::Inner::validatePaperThumbnail(
paper.dataMedia = document->createMediaView();
paper.dataMedia->thumbnailWanted(paper.data.fileOrigin());
}
}
if (!paper.dataMedia || !paper.dataMedia->thumbnail()) {
if (!paper.dataMedia->thumbnail()) {
return;
}
} else if (!paper.data.backgroundColors().empty()) {
paper.thumbnail = Ui::PixmapFromImage(
Data::GenerateWallPaper(
st::backgroundSize * cIntRetinaFactor(),
paper.data.backgroundColors(),
paper.data.gradientRotation()));
paper.thumbnail.setDevicePixelRatio(cRetinaFactor());
return;
} else {
return;
}
}
@@ -336,12 +346,11 @@ void BackgroundBox::Inner::validatePaperThumbnail(
: paper.dataMedia->thumbnail();
auto original = thumbnail->original();
if (paper.data.isPattern()) {
const auto color = *paper.data.backgroundColor();
original = Data::PreparePatternImage(
std::move(original),
color,
Data::PatternColor(color),
paper.data.patternIntensity());
paper.data.backgroundColors(),
paper.data.gradientRotation(),
paper.data.patternOpacity());
}
paper.thumbnail = Ui::PixmapFromImage(TakeMiddleSample(
original,

View File

@@ -279,35 +279,36 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
});
}
AdminLog::OwnedItem GenerateTextItem(
[[nodiscard]] AdminLog::OwnedItem GenerateTextItem(
not_null<HistoryView::ElementDelegate*> delegate,
not_null<History*> history,
const QString &text,
bool out) {
Expects(history->peer->isUser());
using Flag = MTPDmessage::Flag;
static auto id = ServerMaxMsgId + (ServerMaxMsgId / 3);
const auto flags = Flag::f_entities
| Flag::f_from_id
| (out ? Flag::f_out : Flag(0));
const auto clientFlags = MTPDmessage_ClientFlag::f_fake_history_item;
const auto replyTo = 0;
const auto viaBotId = UserId(0);
const auto flags = MessageFlag::FakeHistoryItem
| MessageFlag::HasFromId
| (out ? MessageFlag::Outgoing : MessageFlag(0));
const auto replyTo = MsgId();
const auto viaBotId = UserId();
const auto groupedId = uint64();
const auto item = history->makeMessage(
++id,
flags,
clientFlags,
replyTo,
viaBotId,
base::unixtime::now(),
out ? history->session().userId() : peerToUser(history->peer->id),
QString(),
TextWithEntities{ TextUtilities::Clean(text) });
TextWithEntities{ TextUtilities::Clean(text) },
MTP_messageMediaEmpty(),
MTPReplyMarkup(),
groupedId);
return AdminLog::OwnedItem(delegate, item);
}
QImage PrepareScaledNonPattern(
[[nodiscard]] QImage PrepareScaledNonPattern(
const QImage &image,
Images::Option blur) {
const auto size = st::boxWideWidth;
@@ -330,66 +331,31 @@ QImage PrepareScaledNonPattern(
size);
}
QImage ColorizePattern(QImage image, QColor color) {
if (image.format() != QImage::Format_ARGB32_Premultiplied) {
image = std::move(image).convertToFormat(
QImage::Format_ARGB32_Premultiplied);
}
// Similar to style::colorizeImage.
// But style::colorizeImage takes pattern with all pixels having the
// same components value, from (0, 0, 0, 0) to (255, 255, 255, 255).
//
// While in patterns we have different value ranges, usually they are
// from (0, 0, 0, 0) to (0, 0, 0, 255), so we should use only 'alpha'.
const auto width = image.width();
const auto height = image.height();
const auto pattern = anim::shifted(color);
constexpr auto resultIntsPerPixel = 1;
const auto resultIntsPerLine = (image.bytesPerLine() >> 2);
const auto resultIntsAdded = resultIntsPerLine - width * resultIntsPerPixel;
auto resultInts = reinterpret_cast<uint32*>(image.bits());
Assert(resultIntsAdded >= 0);
Assert(image.depth() == static_cast<int>((resultIntsPerPixel * sizeof(uint32)) << 3));
Assert(image.bytesPerLine() == (resultIntsPerLine << 2));
const auto maskBytesPerPixel = (image.depth() >> 3);
const auto maskBytesPerLine = image.bytesPerLine();
const auto maskBytesAdded = maskBytesPerLine - width * maskBytesPerPixel;
// We want to read the last byte of four available.
// This is the difference with style::colorizeImage.
auto maskBytes = image.constBits() + (maskBytesPerPixel - 1);
Assert(maskBytesAdded >= 0);
Assert(image.depth() == (maskBytesPerPixel << 3));
for (auto y = 0; y != height; ++y) {
for (auto x = 0; x != width; ++x) {
auto maskOpacity = static_cast<anim::ShiftedMultiplier>(*maskBytes) + 1;
*resultInts = anim::unshifted(pattern * maskOpacity);
maskBytes += maskBytesPerPixel;
resultInts += resultIntsPerPixel;
}
maskBytes += maskBytesAdded;
resultInts += resultIntsAdded;
}
return image;
}
QImage PrepareScaledFromFull(
[[nodiscard]] QImage PrepareScaledFromFull(
const QImage &image,
std::optional<QColor> patternBackground,
bool isPattern,
const std::vector<QColor> &background,
int gradientRotation,
float64 patternOpacity,
Images::Option blur = Images::Option(0)) {
auto result = PrepareScaledNonPattern(image, blur);
if (patternBackground) {
result = ColorizePattern(
if (isPattern) {
result = Data::PreparePatternImage(
std::move(result),
Data::PatternColor(*patternBackground));
background,
gradientRotation,
patternOpacity);
}
return std::move(result).convertToFormat(
QImage::Format_ARGB32_Premultiplied);
}
[[nodiscard]] QImage BlackImage(QSize size) {
auto result = QImage(size, QImage::Format_ARGB32_Premultiplied);
result.fill(Qt::black);
return result;
}
} // namespace
BackgroundPreviewBox::BackgroundPreviewBox(
@@ -414,12 +380,28 @@ BackgroundPreviewBox::BackgroundPreviewBox(
if (_media) {
_media->thumbnailWanted(_paper.fileOrigin());
}
generateBackground();
_controller->session().downloaderTaskFinished(
) | rpl::start_with_next([=] {
update();
}, lifetime());
}
void BackgroundPreviewBox::generateBackground() {
if (_paper.backgroundColors().empty()) {
return;
}
const auto size = QSize(st::boxWideWidth, st::boxWideWidth)
* cIntRetinaFactor();
_generated = Ui::PixmapFromImage((_paper.patternOpacity() >= 0.)
? Data::GenerateWallPaper(
size,
_paper.backgroundColors(),
_paper.gradientRotation())
: BlackImage(size));
_generated.setDevicePixelRatio(cRetinaFactor());
}
not_null<HistoryView::ElementDelegate*> BackgroundPreviewBox::delegate() {
return static_cast<HistoryView::ElementDelegate*>(this);
}
@@ -432,7 +414,7 @@ void BackgroundPreviewBox::prepare() {
if (_paper.hasShareUrl()) {
addLeftButton(tr::lng_background_share(), [=] { share(); });
}
updateServiceBg(_paper.backgroundColor());
updateServiceBg(_paper.backgroundColors());
_paper.loadDocument();
const auto document = _paper.document();
@@ -520,21 +502,23 @@ void BackgroundPreviewBox::paintEvent(QPaintEvent *e) {
Painter p(this);
const auto ms = crl::now();
const auto color = _paper.backgroundColor();
if (color) {
p.fillRect(e->rect(), *color);
if (_scaled.isNull()) {
setScaledFromThumb();
}
if (!color || _paper.isPattern()) {
if (!_scaled.isNull() || setScaledFromThumb()) {
paintImage(p);
paintRadial(p);
} else if (!color) {
p.fillRect(e->rect(), st::boxBg);
return;
} else {
// Progress of pattern loading.
paintRadial(p);
}
if (!_generated.isNull()
&& (_scaled.isNull()
|| (_fadeOutThumbnail.isNull() && _fadeIn.animating()))) {
p.drawPixmap(0, 0, _generated);
}
if (!_scaled.isNull()) {
paintImage(p);
paintRadial(p);
} else if (_generated.isNull()) {
p.fillRect(e->rect(), st::boxBg);
return;
} else {
// Progress of pattern loading.
paintRadial(p);
}
paintTexts(p, ms);
}
@@ -542,10 +526,6 @@ void BackgroundPreviewBox::paintEvent(QPaintEvent *e) {
void BackgroundPreviewBox::paintImage(Painter &p) {
Expects(!_scaled.isNull());
const auto master = _paper.isPattern()
? std::clamp(_paper.patternIntensity() / 100., 0., 1.)
: 1.;
const auto factor = cIntRetinaFactor();
const auto size = st::boxWideWidth;
const auto from = QRect(
@@ -562,7 +542,7 @@ void BackgroundPreviewBox::paintImage(Painter &p) {
const auto &pixmap = (!_blurred.isNull() && _paper.isBlurred())
? _blurred
: _scaled;
p.setOpacity(master * fade);
p.setOpacity(fade);
p.drawPixmap(rect(), pixmap, from);
checkBlurAnimationStart();
}
@@ -609,11 +589,18 @@ QRect BackgroundPreviewBox::radialRect() const {
void BackgroundPreviewBox::paintTexts(Painter &p, crl::time ms) {
const auto height1 = _text1->height();
const auto height2 = _text2->height();
const auto context = HistoryView::PaintContext{
.bubblesPattern = nullptr, // #TODO bubbles
.viewport = rect(),
.clip = rect(),
.selection = TextSelection(),
.now = ms,
};
p.translate(0, textsTop());
paintDate(p);
_text1->draw(p, rect(), TextSelection(), ms);
_text1->draw(p, context);
p.translate(0, height1);
_text2->draw(p, rect(), TextSelection(), ms);
_text2->draw(p, context);
p.translate(0, height2);
}
@@ -654,7 +641,10 @@ void BackgroundPreviewBox::radialAnimationCallback(crl::time now) {
checkLoadedDocument();
}
bool BackgroundPreviewBox::setScaledFromThumb() {
void BackgroundPreviewBox::setScaledFromThumb() {
if (!_scaled.isNull()) {
return;
}
const auto localThumbnail = _paper.localThumbnail();
const auto thumbnail = localThumbnail
? localThumbnail
@@ -662,13 +652,16 @@ bool BackgroundPreviewBox::setScaledFromThumb() {
? _media->thumbnail()
: nullptr;
if (!thumbnail) {
return false;
return;
} else if (_paper.isPattern() && _paper.document() != nullptr) {
return false;
return;
}
auto scaled = PrepareScaledFromFull(
thumbnail->original(),
patternBackgroundColor(),
_paper.isPattern(),
_paper.backgroundColors(),
_paper.gradientRotation(),
_paper.patternOpacity(),
_paper.document() ? Images::Option::Blurred : Images::Option(0));
auto blurred = (_paper.document() || _paper.isPattern())
? QImage()
@@ -676,13 +669,12 @@ bool BackgroundPreviewBox::setScaledFromThumb() {
Data::PrepareBlurredBackground(thumbnail->original()),
Images::Option(0));
setScaledFromImage(std::move(scaled), std::move(blurred));
return true;
}
void BackgroundPreviewBox::setScaledFromImage(
QImage &&image,
QImage &&blurred) {
updateServiceBg(Window::Theme::CountAverageColor(image));
updateServiceBg({ Window::Theme::CountAverageColor(image) });
if (!_full.isNull()) {
startFadeInFrom(std::move(_scaled));
}
@@ -709,16 +701,20 @@ void BackgroundPreviewBox::checkBlurAnimationStart() {
startFadeInFrom(_paper.isBlurred() ? _scaled : _blurred);
}
void BackgroundPreviewBox::updateServiceBg(std::optional<QColor> background) {
if (background) {
_serviceBg = Window::Theme::AdjustedColor(
st::msgServiceBg->c,
*background);
void BackgroundPreviewBox::updateServiceBg(const std::vector<QColor> &bg) {
const auto count = int(bg.size());
if (!count) {
return;
}
}
std::optional<QColor> BackgroundPreviewBox::patternBackgroundColor() const {
return _paper.isPattern() ? _paper.backgroundColor() : std::nullopt;
auto red = 0, green = 0, blue = 0;
for (const auto &color : bg) {
red += color.red();
green += color.green();
blue += color.blue();
}
_serviceBg = Window::Theme::AdjustedColor(
st::msgServiceBg->c,
QColor(red / count, green / count, blue / count));
}
void BackgroundPreviewBox::checkLoadedDocument() {
@@ -736,15 +732,23 @@ void BackgroundPreviewBox::checkLoadedDocument() {
crl::async([
this,
image = std::move(image),
patternBackground = patternBackgroundColor(),
isPattern = _paper.isPattern(),
background = _paper.backgroundColors(),
gradientRotation = _paper.gradientRotation(),
patternOpacity = _paper.patternOpacity(),
guard = _generating.make_guard()
]() mutable {
auto scaled = PrepareScaledFromFull(image, patternBackground);
auto blurred = patternBackground
? QImage()
: PrepareScaledNonPattern(
auto scaled = PrepareScaledFromFull(
image,
isPattern,
background,
gradientRotation,
patternOpacity);
auto blurred = !isPattern
? PrepareScaledNonPattern(
Data::PrepareBlurredBackground(image),
Images::Option(0));
Images::Option(0))
: QImage();
crl::on_main(std::move(guard), [
this,
image = std::move(image),
@@ -767,7 +771,7 @@ bool BackgroundPreviewBox::Start(
not_null<Window::SessionController*> controller,
const QString &slug,
const QMap<QString, QString> &params) {
if (const auto paper = Data::WallPaper::FromColorSlug(slug)) {
if (const auto paper = Data::WallPaper::FromColorsSlug(slug)) {
controller->show(Box<BackgroundPreviewBox>(
controller,
paper->withUrlParams(params)));

View File

@@ -56,11 +56,11 @@ private:
void radialAnimationCallback(crl::time now);
QRect radialRect() const;
void generateBackground();
void checkLoadedDocument();
bool setScaledFromThumb();
void setScaledFromThumb();
void setScaledFromImage(QImage &&image, QImage &&blurred);
void updateServiceBg(std::optional<QColor> background);
std::optional<QColor> patternBackgroundColor() const;
void updateServiceBg(const std::vector<QColor> &bg);
void paintImage(Painter &p);
void paintRadial(Painter &p);
void paintTexts(Painter &p, crl::time ms);
@@ -76,7 +76,7 @@ private:
Data::WallPaper _paper;
std::shared_ptr<Data::DocumentMedia> _media;
QImage _full;
QPixmap _scaled, _blurred, _fadeOutThumbnail;
QPixmap _generated, _scaled, _blurred, _fadeOutThumbnail;
Ui::Animations::Simple _fadeIn;
Ui::RadialAnimation _radial;
base::binary_guard _generating;

View File

@@ -324,7 +324,7 @@ passcodePadding: margins(0px, 0px, 0px, 5px);
passcodeTextLine: 28px;
passcodeLittleSkip: 5px;
passcodeAboutSkip: 7px;
passcodeSkip: 20px;
passcodeSkip: 23px;
newGroupAboutFg: windowSubTextFg;
newGroupPadding: margins(4px, 6px, 4px, 3px);
@@ -965,3 +965,11 @@ scheduleTimeSeparatorPadding: margins(2px, 0px, 2px, 0px);
boxAttentionDividerLabel: FlatLabel(boxDividerLabel) {
textFg: boxTextFgError;
}
autolockDateField: InputField(scheduleDateField) {
heightMin: 22px;
}
autolockTimeField: InputField(scheduleTimeField) {
heightMin: 20px;
}
autolockTimeWidth: 52px;

View File

@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/input_fields.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/toast/toast.h"
#include "ui/text/format_values.h" // Ui::FormatPhone
#include "ui/text/text_utilities.h"
#include "ui/special_fields.h"
#include "boxes/confirm_phone_box.h"
@@ -21,7 +22,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "mtproto/sender.h"
#include "apiwrap.h"
#include "app.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
@@ -235,7 +235,7 @@ void ChangePhoneBox::EnterPhone::sendPhoneFail(const MTP::Error &error, const QS
tr::lng_change_phone_occupied(
tr::now,
lt_phone,
App::formatPhone(phoneNumber)),
Ui::FormatPhone(phoneNumber)),
tr::lng_box_ok(tr::now)));
} else {
showError(Lang::Hard::ServerError());
@@ -271,7 +271,7 @@ void ChangePhoneBox::EnterCode::prepare() {
auto descriptionText = tr::lng_change_phone_code_description(
tr::now,
lt_phone,
Ui::Text::Bold(App::formatPhone(_phone)),
Ui::Text::Bold(Ui::FormatPhone(_phone)),
Ui::Text::WithEntities);
auto description = object_ptr<Ui::FlatLabel>(this, rpl::single(descriptionText), st::changePhoneLabel);
description->moveToLeft(st::boxPadding.left(), 0);

View File

@@ -41,7 +41,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "mtproto/mtproto_config.h"
#include "facades.h" // Ui::showChatsList
#include "app.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/labels.h"
#include "ui/text/format_values.h" // Ui::FormatPhone
#include "ui/text/text_utilities.h"
#include "core/click_handler_types.h" // UrlClickHandler
#include "base/qthelp_url.h" // qthelp::url_encode
@@ -18,7 +19,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "mainwidget.h"
#include "numbers.h"
#include "app.h"
#include "lang/lang_keys.h"
#include "mtproto/facade.h"
#include "styles/style_layers.h"
@@ -296,7 +296,7 @@ void ConfirmPhoneBox::prepare() {
this,
tr::lng_confirm_phone_about(
lt_phone,
rpl::single(Ui::Text::Bold(App::formatPhone(_phone))),
rpl::single(Ui::Text::Bold(Ui::FormatPhone(_phone))),
Ui::Text::WithEntities),
st::confirmPhoneAboutLabel);
@@ -347,7 +347,7 @@ void ConfirmPhoneBox::sendCode() {
void ConfirmPhoneBox::confirmDone(const MTPBool &result) {
_sendCodeRequestId = 0;
Ui::show(Box<InformBox>(tr::lng_confirm_phone_success(tr::now, lt_phone, App::formatPhone(_phone))));
Ui::show(Box<InformBox>(tr::lng_confirm_phone_success(tr::now, lt_phone, Ui::FormatPhone(_phone))));
}
void ConfirmPhoneBox::confirmFail(const MTP::Error &error) {

View File

@@ -67,10 +67,7 @@ auto ListFromMimeData(not_null<const QMimeData*> data) {
if (result.error == Error::None) {
return result;
} else if (data->hasImage()) {
auto image = Platform::GetImageFromClipboard();
if (image.isNull()) {
image = qvariant_cast<QImage>(data->imageData());
}
auto image = qvariant_cast<QImage>(data->imageData());
if (!image.isNull()) {
return Storage::PrepareMediaFromImage(
std::move(image),

View File

@@ -170,63 +170,6 @@ void EditPrivacyBox::editExceptions(
Ui::LayerOption::KeepOther);
}
QVector<MTPInputPrivacyRule> EditPrivacyBox::collectResult() {
const auto collectInputUsers = [](const auto &peers) {
auto result = QVector<MTPInputUser>();
result.reserve(peers.size());
for (const auto peer : peers) {
if (const auto user = peer->asUser()) {
result.push_back(user->inputUser);
}
}
return result;
};
const auto collectInputChats = [](const auto &peers) {
auto result = QVector<MTPint>(); // #TODO ids
result.reserve(peers.size());
for (const auto peer : peers) {
if (!peer->isUser()) {
result.push_back(peerToBareMTPInt(peer->id));
}
}
return result;
};
constexpr auto kMaxRules = 3; // allow users, disallow users, option
auto result = QVector<MTPInputPrivacyRule>();
result.reserve(kMaxRules);
if (showExceptionLink(Exception::Always)) {
const auto users = collectInputUsers(_value.always);
const auto chats = collectInputChats(_value.always);
if (!users.empty()) {
result.push_back(MTP_inputPrivacyValueAllowUsers(MTP_vector<MTPInputUser>(users)));
}
if (!chats.empty()) {
result.push_back(MTP_inputPrivacyValueAllowChatParticipants(MTP_vector<MTPint>(chats)));
}
}
if (showExceptionLink(Exception::Never)) {
const auto users = collectInputUsers(_value.never);
const auto chats = collectInputChats(_value.never);
if (!users.empty()) {
result.push_back(MTP_inputPrivacyValueDisallowUsers(MTP_vector<MTPInputUser>(users)));
}
if (!chats.empty()) {
result.push_back(MTP_inputPrivacyValueDisallowChatParticipants(MTP_vector<MTPint>(chats)));
}
}
result.push_back([&] {
switch (_value.option) {
case Option::Everyone: return MTP_inputPrivacyValueAllowAll();
case Option::Contacts: return MTP_inputPrivacyValueAllowContacts();
case Option::Nobody: return MTP_inputPrivacyValueDisallowAll();
}
Unexpected("Option value in EditPrivacyBox::collectResult.");
}());
return result;
}
std::vector<not_null<PeerData*>> &EditPrivacyBox::exceptions(Exception exception) {
switch (exception) {
case Exception::Always: return _value.always;
@@ -379,10 +322,13 @@ void EditPrivacyBox::setupContent() {
const auto someAreDisallowed = (_value.option != Option::Everyone)
|| !_value.never.empty();
_controller->confirmSave(someAreDisallowed, crl::guard(this, [=] {
_value.ignoreAlways = !showExceptionLink(Exception::Always);
_value.ignoreNever = !showExceptionLink(Exception::Never);
_controller->saveAdditional();
_window->session().api().savePrivacy(
_controller->apiKey(),
collectResult());
_window->session().api().userPrivacy().save(
_controller->key(),
_value);
closeBox();
}));
});

View File

@@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/abstract_box.h"
#include "mtproto/sender.h"
#include "apiwrap.h"
#include "api/api_user_privacy.h"
namespace Ui {
class VerticalLayout;
@@ -31,15 +31,14 @@ class EditPrivacyBox;
class EditPrivacyController {
public:
using Key = ApiWrap::Privacy::Key;
using Option = ApiWrap::Privacy::Option;
using Key = Api::UserPrivacy::Key;
using Option = Api::UserPrivacy::Option;
enum class Exception {
Always,
Never,
};
[[nodiscard]] virtual Key key() = 0;
[[nodiscard]] virtual MTPInputPrivacyKey apiKey() = 0;
[[nodiscard]] virtual rpl::producer<QString> title() = 0;
[[nodiscard]] virtual bool hasOption(Option option) {
@@ -102,8 +101,8 @@ private:
class EditPrivacyBox : public Ui::BoxContent {
public:
using Value = ApiWrap::Privacy;
using Option = Value::Option;
using Value = Api::UserPrivacy::Rule;
using Option = Api::UserPrivacy::Option;
using Exception = EditPrivacyController::Exception;
EditPrivacyBox(
@@ -124,7 +123,6 @@ protected:
private:
bool showExceptionLink(Exception exception) const;
void setupContent();
QVector<MTPInputPrivacyRule> collectResult();
Ui::FlatLabel *addLabel(
not_null<Ui::VerticalLayout*> container,

View File

@@ -14,7 +14,6 @@ Copyright (C) 2017, Nicholas Guriev <guriev-ns@ya.ru>
#include "ui/special_buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/labels.h"
#include "app.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"

View File

@@ -11,8 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "boxes/confirm_box.h"
#include "boxes/confirm_phone_box.h"
#include "base/unixtime.h"
#include "mainwindow.h"
#include "apiwrap.h"
#include "api/api_cloud_password.h"
#include "main/main_session.h"
#include "main/main_domain.h"
#include "core/application.h"
@@ -42,7 +44,7 @@ enum class PasswordErrorType {
void SetCloudPassword(
not_null<Ui::GenericBox*> box,
not_null<Main::Session*> session) {
session->api().passwordState(
session->api().cloudPassword().state(
) | rpl::start_with_next([=] {
using namespace Settings;
const auto weak = Ui::MakeWeak(box);
@@ -95,6 +97,60 @@ void TransferPasswordError(
}
}
void StartPendingReset(
not_null<Main::Session*> session,
not_null<Ui::BoxContent*> context,
Fn<void()> close) {
const auto weak = Ui::MakeWeak(context.get());
auto lifetime = std::make_shared<rpl::lifetime>();
auto finish = [=](const QString &message) mutable {
if (const auto strong = weak.data()) {
if (!message.isEmpty()) {
strong->getDelegate()->show(Box<InformBox>(message));
}
strong->closeBox();
}
close();
if (lifetime) {
base::take(lifetime)->destroy();
}
};
session->api().cloudPassword().resetPassword(
) | rpl::start_with_next_error_done([=](
Api::CloudPassword::ResetRetryDate retryDate) {
constexpr auto kMinute = 60;
constexpr auto kHour = 3600;
constexpr auto kDay = 86400;
const auto left = std::max(
retryDate - base::unixtime::now(),
kMinute);
const auto days = (left / kDay);
const auto hours = (left / kHour);
const auto minutes = (left / kMinute);
const auto duration = days
? tr::lng_group_call_duration_days(tr::now, lt_count, days)
: hours
? tr::lng_group_call_duration_hours(tr::now, lt_count, hours)
: tr::lng_group_call_duration_minutes(
tr::now,
lt_count,
minutes);
if (const auto strong = weak.data()) {
strong->getDelegate()->show(Box<InformBox>(
tr::lng_cloud_password_reset_later(
tr::now,
lt_duration,
duration)));
}
}, [=](const QString &error) mutable {
finish("Error: " + error);
}, [=]() mutable {
finish({});
}, *lifetime);
}
} // namespace
PasscodeBox::CloudFields PasscodeBox::CloudFields::From(
@@ -106,6 +162,7 @@ PasscodeBox::CloudFields PasscodeBox::CloudFields::From(
result.hasRecovery = current.hasRecovery;
result.notEmptyPassport = current.notEmptyPassport;
result.hint = current.hint;
result.pendingResetDate = current.pendingResetDate;
return result;
}
@@ -132,20 +189,33 @@ PasscodeBox::PasscodeBox(
PasscodeBox::PasscodeBox(
QWidget*,
not_null<Main::Session*> session,
not_null<MTP::Instance*> mtp,
Main::Session *session,
const CloudFields &fields)
: _session(session)
, _api(&_session->mtp())
, _api(mtp)
, _turningOff(fields.turningOff)
, _cloudPwd(true)
, _cloudFields(fields)
, _about(st::boxWidth - st::boxPadding.left() * 1.5)
, _oldPasscode(this, st::defaultInputField, tr::lng_cloud_password_enter_old())
, _newPasscode(this, st::defaultInputField, fields.curRequest ? tr::lng_cloud_password_enter_new() : tr::lng_cloud_password_enter_first())
, _newPasscode(
this,
st::defaultInputField,
(fields.curRequest
? tr::lng_cloud_password_enter_new()
: tr::lng_cloud_password_enter_first()))
, _reenterPasscode(this, st::defaultInputField, tr::lng_cloud_password_confirm_new())
, _passwordHint(this, st::defaultInputField, fields.curRequest ? tr::lng_cloud_password_change_hint() : tr::lng_cloud_password_hint())
, _passwordHint(
this,
st::defaultInputField,
(fields.curRequest
? tr::lng_cloud_password_change_hint()
: tr::lng_cloud_password_hint()))
, _recoverEmail(this, st::defaultInputField, tr::lng_cloud_password_email())
, _recover(this, tr::lng_signin_recover(tr::now)) {
, _recover(this, tr::lng_signin_recover(tr::now))
, _showRecoverLink(_cloudFields.hasRecovery || !_cloudFields.pendingResetDate) {
Expects(session != nullptr || !fields.fromRecoveryCode.isEmpty());
Expects(!_turningOff || _cloudFields.curRequest);
if (!_cloudFields.hint.isEmpty()) {
@@ -155,6 +225,13 @@ PasscodeBox::PasscodeBox(
}
}
PasscodeBox::PasscodeBox(
QWidget*,
not_null<Main::Session*> session,
const CloudFields &fields)
: PasscodeBox(nullptr, &session->mtp(), session, fields) {
}
rpl::producer<QByteArray> PasscodeBox::newPasswordSet() const {
return _newPasswordSet.events();
}
@@ -167,6 +244,10 @@ rpl::producer<> PasscodeBox::clearUnconfirmedPassword() const {
return _clearUnconfirmedPassword.events();
}
rpl::producer<MTPauth_Authorization> PasscodeBox::newAuthorization() const {
return _newAuthorization.events();
}
bool PasscodeBox::currentlyHave() const {
return _cloudPwd
? (!!_cloudFields.curRequest)
@@ -203,20 +284,22 @@ void PasscodeBox::prepare() {
: _cloudPwd
? tr::lng_cloud_password_remove()
: tr::lng_passcode_remove());
setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_cloudFields.hasRecovery && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom());
setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_showRecoverLink && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom());
} else {
if (currentlyHave()) {
_oldPasscode->show();
setTitle(_cloudPwd
? tr::lng_cloud_password_change()
: tr::lng_passcode_change());
setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_cloudFields.hasRecovery && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + _newPasscode->height() + st::passcodeLittleSkip + _reenterPasscode->height() + st::passcodeSkip + (_cloudPwd ? _passwordHint->height() + st::passcodeLittleSkip : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom());
setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_showRecoverLink && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + _newPasscode->height() + st::passcodeLittleSkip + _reenterPasscode->height() + st::passcodeSkip + (_cloudPwd ? _passwordHint->height() + st::passcodeLittleSkip : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom());
} else {
_oldPasscode->hide();
setTitle(_cloudPwd
? tr::lng_cloud_password_create()
? (_cloudFields.fromRecoveryCode.isEmpty()
? tr::lng_cloud_password_create()
: tr::lng_cloud_password_change())
: tr::lng_passcode_create());
setDimensions(st::boxWidth, st::passcodePadding.top() + _newPasscode->height() + st::passcodeLittleSkip + _reenterPasscode->height() + st::passcodeSkip + (_cloudPwd ? _passwordHint->height() + st::passcodeLittleSkip : 0) + st::passcodeAboutSkip + _aboutHeight + (_cloudPwd ? (st::passcodeLittleSkip + _recoverEmail->height() + st::passcodeSkip) : st::passcodePadding.bottom()));
setDimensions(st::boxWidth, st::passcodePadding.top() + _newPasscode->height() + st::passcodeLittleSkip + _reenterPasscode->height() + st::passcodeSkip + (_cloudPwd ? _passwordHint->height() + st::passcodeLittleSkip : 0) + st::passcodeAboutSkip + _aboutHeight + ((_cloudPwd && _cloudFields.fromRecoveryCode.isEmpty()) ? (st::passcodeLittleSkip + _recoverEmail->height() + st::passcodeSkip) : st::passcodePadding.bottom()));
}
}
@@ -237,11 +320,16 @@ void PasscodeBox::prepare() {
const auto has = currentlyHave();
_oldPasscode->setVisible(onlyCheck || has);
_recover->setVisible((onlyCheck || has) && _cloudPwd && _cloudFields.hasRecovery);
_recover->setVisible((onlyCheck || has)
&& _cloudPwd
&& _showRecoverLink);
_newPasscode->setVisible(!onlyCheck);
_reenterPasscode->setVisible(!onlyCheck);
_passwordHint->setVisible(!onlyCheck && _cloudPwd);
_recoverEmail->setVisible(!onlyCheck && _cloudPwd && !has);
_recoverEmail->setVisible(!onlyCheck
&& _cloudPwd
&& !has
&& _cloudFields.fromRecoveryCode.isEmpty());
}
void PasscodeBox::submit() {
@@ -285,7 +373,7 @@ void PasscodeBox::paintEvent(QPaintEvent *e) {
Painter p(this);
int32 w = st::boxWidth - st::boxPadding.left() * 1.5;
int32 abouty = (_passwordHint->isHidden() ? ((_reenterPasscode->isHidden() ? (_oldPasscode->y() + (_cloudFields.hasRecovery && !_hintText.isEmpty() ? st::passcodeTextLine : 0)) : _reenterPasscode->y()) + st::passcodeSkip) : _passwordHint->y()) + _oldPasscode->height() + st::passcodeLittleSkip + st::passcodeAboutSkip;
int32 abouty = (_passwordHint->isHidden() ? ((_reenterPasscode->isHidden() ? (_oldPasscode->y() + (_showRecoverLink && !_hintText.isEmpty() ? st::passcodeTextLine : 0)) : _reenterPasscode->y()) + st::passcodeSkip) : _passwordHint->y()) + _oldPasscode->height() + st::passcodeLittleSkip + st::passcodeAboutSkip;
p.setPen(st::boxTextFg);
_about.drawLeft(p, st::boxPadding.left(), abouty, w, width());
@@ -317,7 +405,7 @@ void PasscodeBox::resizeEvent(QResizeEvent *e) {
_oldPasscode->resize(w, _oldPasscode->height());
_oldPasscode->moveToLeft(st::boxPadding.left(), st::passcodePadding.top());
_newPasscode->resize(w, _newPasscode->height());
_newPasscode->moveToLeft(st::boxPadding.left(), _oldPasscode->y() + ((_turningOff || has) ? (_oldPasscode->height() + st::passcodeTextLine + ((_cloudFields.hasRecovery && !_hintText.isEmpty()) ? st::passcodeTextLine : 0)) : 0));
_newPasscode->moveToLeft(st::boxPadding.left(), _oldPasscode->y() + ((_turningOff || has) ? (_oldPasscode->height() + st::passcodeTextLine + ((_showRecoverLink && !_hintText.isEmpty()) ? st::passcodeTextLine : 0)) : 0));
_reenterPasscode->resize(w, _reenterPasscode->height());
_reenterPasscode->moveToLeft(st::boxPadding.left(), _newPasscode->y() + _newPasscode->height() + st::passcodeLittleSkip);
_passwordHint->resize(w, _passwordHint->height());
@@ -340,21 +428,44 @@ void PasscodeBox::setInnerFocus() {
}
}
void PasscodeBox::recoverPasswordDone(
const QByteArray &newPasswordBytes,
const MTPauth_Authorization &result) {
if (_replacedBy) {
_replacedBy->closeBox();
}
_setRequest = 0;
const auto weak = Ui::MakeWeak(this);
_newAuthorization.fire_copy(result);
if (weak) {
_newPasswordSet.fire_copy(newPasswordBytes);
if (weak) {
getDelegate()->show(Box<InformBox>(
tr::lng_cloud_password_updated(tr::now)));
if (weak) {
closeBox();
}
}
}
}
void PasscodeBox::setPasswordDone(const QByteArray &newPasswordBytes) {
if (_replacedBy) {
_replacedBy->closeBox();
}
_setRequest = 0;
_newPasswordSet.fire_copy(newPasswordBytes);
const auto weak = Ui::MakeWeak(this);
const auto text = _reenterPasscode->isHidden()
? tr::lng_cloud_password_removed(tr::now)
: _oldPasscode->isHidden()
? tr::lng_cloud_password_was_set(tr::now)
: tr::lng_cloud_password_updated(tr::now);
getDelegate()->show(Box<InformBox>(text));
_newPasswordSet.fire_copy(newPasswordBytes);
if (weak) {
closeBox();
const auto text = _reenterPasscode->isHidden()
? tr::lng_cloud_password_removed(tr::now)
: _oldPasscode->isHidden()
? tr::lng_cloud_password_was_set(tr::now)
: tr::lng_cloud_password_updated(tr::now);
getDelegate()->show(Box<InformBox>(text));
if (weak) {
closeBox();
}
}
}
@@ -379,7 +490,7 @@ void PasscodeBox::setPasswordFail(const QString &type) {
_oldPasscode->setFocus();
_oldPasscode->showError();
_oldError = tr::lng_flood_error(tr::now);
if (_cloudFields.hasRecovery && _hintText.isEmpty()) {
if (_showRecoverLink && _hintText.isEmpty()) {
_recover->hide();
}
update();
@@ -739,25 +850,44 @@ void PasscodeBox::setNewCloudPassword(const QString &newPassword) {
}
const auto hint = _passwordHint->getLastText();
const auto email = _recoverEmail->getLastText().trimmed();
const auto flags = MTPDaccount_passwordInputSettings::Flag::f_new_algo
| MTPDaccount_passwordInputSettings::Flag::f_new_password_hash
| MTPDaccount_passwordInputSettings::Flag::f_hint
| MTPDaccount_passwordInputSettings::Flag::f_email;
using Flag = MTPDaccount_passwordInputSettings::Flag;
const auto flags = Flag::f_new_algo
| Flag::f_new_password_hash
| Flag::f_hint
| (_cloudFields.fromRecoveryCode.isEmpty() ? Flag::f_email : Flag(0));
_checkPasswordCallback = nullptr;
_setRequest = _api.request(MTPaccount_UpdatePasswordSettings(
MTP_inputCheckPasswordEmpty(),
MTP_account_passwordInputSettings(
MTP_flags(flags),
Core::PrepareCloudPasswordAlgo(_cloudFields.newAlgo),
MTP_bytes(newPasswordHash.modpow),
MTP_string(hint),
MTP_string(email),
MTPSecureSecretSettings())
)).done([=](const MTPBool &result) {
setPasswordDone(newPasswordBytes);
}).fail([=](const MTP::Error &error) {
setPasswordFail(newPasswordBytes, email, error);
}).handleFloodErrors().send();
const auto settings = MTP_account_passwordInputSettings(
MTP_flags(flags),
Core::PrepareCloudPasswordAlgo(_cloudFields.newAlgo),
MTP_bytes(newPasswordHash.modpow),
MTP_string(hint),
MTP_string(email),
MTPSecureSecretSettings());
if (_cloudFields.fromRecoveryCode.isEmpty()) {
_setRequest = _api.request(MTPaccount_UpdatePasswordSettings(
MTP_inputCheckPasswordEmpty(),
settings
)).done([=](const MTPBool &result) {
setPasswordDone(newPasswordBytes);
}).fail([=](const MTP::Error &error) {
setPasswordFail(newPasswordBytes, email, error);
}).handleFloodErrors().send();
} else {
_setRequest = _api.request(MTPauth_RecoverPassword(
MTP_flags(MTPauth_RecoverPassword::Flag::f_new_settings),
MTP_string(_cloudFields.fromRecoveryCode),
settings
)).done([=](const MTPauth_Authorization &result) {
recoverPasswordDone(newPasswordBytes, result);
}).fail([=](const MTP::Error &error) {
if (MTP::IsFloodError(error)) {
_newError = tr::lng_flood_error(tr::now);
update();
}
setPasswordFail(newPasswordBytes, email, error);
}).handleFloodErrors().send();
}
}
void PasscodeBox::changeCloudPassword(
@@ -913,7 +1043,7 @@ void PasscodeBox::badOldPasscode() {
_oldError = _cloudPwd
? tr::lng_cloud_password_wrong(tr::now)
: tr::lng_passcode_wrong(tr::now);
if (_cloudFields.hasRecovery && _hintText.isEmpty()) {
if (_showRecoverLink && _hintText.isEmpty()) {
_recover->hide();
}
update();
@@ -922,7 +1052,7 @@ void PasscodeBox::badOldPasscode() {
void PasscodeBox::oldChanged() {
if (!_oldError.isEmpty()) {
_oldError = QString();
if (_cloudFields.hasRecovery && _hintText.isEmpty()) {
if (_showRecoverLink && _hintText.isEmpty()) {
_recover->show();
}
update();
@@ -944,7 +1074,22 @@ void PasscodeBox::emailChanged() {
}
void PasscodeBox::recoverByEmail() {
if (_pattern.isEmpty()) {
if (!_cloudFields.hasRecovery) {
Assert(_session != nullptr);
const auto session = _session;
const auto confirmBox = std::make_shared<QPointer<BoxContent>>();
const auto reset = crl::guard(this, [=] {
StartPendingReset(session, this, [=] {
if (const auto box = *confirmBox) {
box->closeBox();
}
});
});
*confirmBox = getDelegate()->show(Box<ConfirmBox>(
tr::lng_cloud_password_reset_no_email(tr::now),
tr::lng_cloud_password_reset_ok(tr::now),
reset));
} else if (_pattern.isEmpty()) {
_pattern = "-";
_api.request(MTPauth_RequestPasswordRecovery(
)).done([=](const MTPauth_PasswordRecovery &result) {
@@ -962,16 +1107,19 @@ void PasscodeBox::recoverExpired() {
}
void PasscodeBox::recover() {
if (_pattern == "-") return;
if (_pattern == "-" || !_session) {
return;
}
const auto weak = Ui::MakeWeak(this);
const auto box = getDelegate()->show(Box<RecoverBox>(
&_api.instance(),
_session,
_pattern,
_cloudFields.notEmptyPassport));
_cloudFields,
[weak] { if (weak) { weak->closeBox(); } }));
box->passwordCleared(
) | rpl::map_to(
QByteArray()
box->newPasswordSet(
) | rpl::start_to_stream(_newPasswordSet, lifetime());
box->recoveryExpired(
@@ -994,17 +1142,44 @@ void PasscodeBox::recoverStartFail(const MTP::Error &error) {
RecoverBox::RecoverBox(
QWidget*,
not_null<Main::Session*> session,
not_null<MTP::Instance*> mtp,
Main::Session *session,
const QString &pattern,
bool notEmptyPassport)
: _api(&session->mtp())
const PasscodeBox::CloudFields &fields,
Fn<void()> closeParent)
: _session(session)
, _api(mtp)
, _pattern(st::normalFont->elided(tr::lng_signin_recover_hint(tr::now, lt_recover_email, pattern), st::boxWidth - st::boxPadding.left() * 1.5))
, _notEmptyPassport(notEmptyPassport)
, _recoverCode(this, st::defaultInputField, tr::lng_signin_code()) {
, _cloudFields(fields)
, _recoverCode(this, st::defaultInputField, tr::lng_signin_code())
, _noEmailAccess(this, tr::lng_signin_try_password(tr::now))
, _closeParent(std::move(closeParent)) {
if (_cloudFields.pendingResetDate != 0 || !session) {
_noEmailAccess.destroy();
} else {
_noEmailAccess->setClickedCallback([=] {
const auto confirmBox = std::make_shared<QPointer<BoxContent>>();
const auto reset = crl::guard(this, [=] {
const auto closeParent = _closeParent;
StartPendingReset(session, this, [=] {
if (closeParent) {
closeParent();
}
if (const auto box = *confirmBox) {
box->closeBox();
}
});
});
*confirmBox = getDelegate()->show(Box<ConfirmBox>(
tr::lng_cloud_password_reset_with_email(tr::now),
tr::lng_cloud_password_reset_ok(tr::now),
reset));
});
}
}
rpl::producer<> RecoverBox::passwordCleared() const {
return _passwordCleared.events();
rpl::producer<QByteArray> RecoverBox::newPasswordSet() const {
return _newPasswordSet.events();
}
rpl::producer<> RecoverBox::recoveryExpired() const {
@@ -1017,7 +1192,13 @@ void RecoverBox::prepare() {
addButton(tr::lng_passcode_submit(), [=] { submit(); });
addButton(tr::lng_cancel(), [=] { closeBox(); });
setDimensions(st::boxWidth, st::passcodePadding.top() + st::passcodePadding.bottom() + st::passcodeTextLine + _recoverCode->height() + st::passcodeTextLine);
setDimensions(
st::boxWidth,
(st::passcodePadding.top()
+ st::passcodePadding.bottom()
+ st::passcodeTextLine
+ _recoverCode->height()
+ st::passcodeTextLine));
connect(_recoverCode, &Ui::InputField::changed, [=] { codeChanged(); });
connect(_recoverCode, &Ui::InputField::submitted, [=] { submit(); });
@@ -1044,6 +1225,9 @@ void RecoverBox::resizeEvent(QResizeEvent *e) {
_recoverCode->resize(st::boxWidth - st::boxPadding.left() - st::boxPadding.right(), _recoverCode->height());
_recoverCode->moveToLeft(st::boxPadding.left(), st::passcodePadding.top() + st::passcodePadding.bottom() + st::passcodeTextLine);
if (_noEmailAccess) {
_noEmailAccess->moveToLeft(st::boxPadding.left(), _recoverCode->y() + _recoverCode->height() + (st::passcodeTextLine - _noEmailAccess->height()) / 2);
}
}
void RecoverBox::setInnerFocus() {
@@ -1061,17 +1245,29 @@ void RecoverBox::submit() {
}
const auto send = crl::guard(this, [=] {
_submitRequest = _api.request(MTPauth_RecoverPassword(
MTP_flags(0),
MTP_string(code),
MTPaccount_PasswordInputSettings()
)).done([=](const MTPauth_Authorization &result) {
codeSubmitDone(result);
}).fail([=](const MTP::Error &error) {
codeSubmitFail(error);
}).handleFloodErrors().send();
if (_cloudFields.turningOff) {
// From "Disable cloud password".
_submitRequest = _api.request(MTPauth_RecoverPassword(
MTP_flags(0),
MTP_string(code),
MTPaccount_PasswordInputSettings()
)).done([=](const MTPauth_Authorization &result) {
proceedToClear();
}).fail([=](const MTP::Error &error) {
checkSubmitFail(error);
}).handleFloodErrors().send();
} else {
// From "Change cloud password".
_submitRequest = _api.request(MTPauth_CheckRecoveryPassword(
MTP_string(code)
)).done([=](const MTPBool &result) {
proceedToChange(code);
}).fail([=](const MTP::Error &error) {
checkSubmitFail(error);
}).handleFloodErrors().send();
}
});
if (_notEmptyPassport) {
if (_cloudFields.notEmptyPassport) {
const auto confirmed = [=](Fn<void()> &&close) {
send();
close();
@@ -1085,25 +1281,61 @@ void RecoverBox::submit() {
}
}
void RecoverBox::codeChanged() {
_error = QString();
void RecoverBox::setError(const QString &error) {
_error = error;
if (_noEmailAccess) {
_noEmailAccess->setVisible(error.isEmpty());
}
update();
}
void RecoverBox::codeSubmitDone(const MTPauth_Authorization &result) {
_submitRequest = 0;
void RecoverBox::codeChanged() {
setError(QString());
}
_passwordCleared.fire({});
void RecoverBox::proceedToClear() {
_submitRequest = 0;
_newPasswordSet.fire({});
getDelegate()->show(
Box<InformBox>(tr::lng_cloud_password_removed(tr::now)),
Ui::LayerOption::CloseOther);
}
void RecoverBox::codeSubmitFail(const MTP::Error &error) {
void RecoverBox::proceedToChange(const QString &code) {
Expects(!_cloudFields.turningOff);
_submitRequest = 0;
auto fields = _cloudFields;
fields.fromRecoveryCode = code;
fields.hasRecovery = false;
// we could've been turning off, no need to force new password then
// like if (_cloudFields.turningOff) { just RecoverPassword else Check }
fields.curRequest = {};
auto box = Box<PasscodeBox>(_session, fields);
box->boxClosing(
) | rpl::start_with_next([=] {
const auto weak = Ui::MakeWeak(this);
if (const auto onstack = _closeParent) {
onstack();
}
if (weak) {
weak->closeBox();
}
}, lifetime());
box->newPasswordSet(
) | rpl::start_with_next([=](QByteArray &&password) {
_newPasswordSet.fire(std::move(password));
}, lifetime());
getDelegate()->show(std::move(box));
}
void RecoverBox::checkSubmitFail(const MTP::Error &error) {
if (MTP::IsFloodError(error)) {
_submitRequest = 0;
_error = tr::lng_flood_error(tr::now);
update();
setError(tr::lng_flood_error(tr::now));
_recoverCode->showError();
return;
}
@@ -1111,7 +1343,7 @@ void RecoverBox::codeSubmitFail(const MTP::Error &error) {
const QString &err = error.type();
if (err == qstr("PASSWORD_EMPTY")) {
_passwordCleared.fire({});
_newPasswordSet.fire(QByteArray());
getDelegate()->show(
Box<InformBox>(tr::lng_cloud_password_removed(tr::now)),
Ui::LayerOption::CloseOther);
@@ -1121,18 +1353,14 @@ void RecoverBox::codeSubmitFail(const MTP::Error &error) {
_recoveryExpired.fire({});
closeBox();
} else if (err == qstr("CODE_INVALID")) {
_error = tr::lng_signin_wrong_code(tr::now);
update();
setError(tr::lng_signin_wrong_code(tr::now));
_recoverCode->selectAll();
_recoverCode->setFocus();
_recoverCode->showError();
} else {
if (Logs::DebugEnabled()) { // internal server error
_error = err + ": " + error.description();
} else {
_error = Lang::Hard::ServerError();
}
update();
setError(Logs::DebugEnabled() // internal server error
? (err + ": " + error.description())
: Lang::Hard::ServerError());
_recoverCode->setFocus();
}
}

View File

@@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/sender.h"
#include "core/core_cloud_password.h"
namespace MTP {
class Instance;
} // namespace MTP
namespace Main {
class Session;
} // namespace Main
@@ -35,10 +39,12 @@ public:
Core::CloudPasswordCheckRequest curRequest;
Core::CloudPasswordAlgo newAlgo;
bool hasRecovery = false;
QString fromRecoveryCode;
bool notEmptyPassport = false;
QString hint;
Core::SecureSecretAlgo newSecureSecretAlgo;
bool turningOff = false;
TimeId pendingResetDate = 0;
// Check cloud password for some action.
Fn<void(const Core::CloudPasswordResult &)> customCheckCallback;
@@ -46,6 +52,11 @@ public:
std::optional<QString> customDescription;
rpl::producer<QString> customSubmitButton;
};
PasscodeBox(
QWidget*,
not_null<MTP::Instance*> mtp,
Main::Session *session,
const CloudFields &fields);
PasscodeBox(
QWidget*,
not_null<Main::Session*> session,
@@ -55,6 +66,8 @@ public:
rpl::producer<> passwordReloadNeeded() const;
rpl::producer<> clearUnconfirmedPassword() const;
rpl::producer<MTPauth_Authorization> newAuthorization() const;
bool handleCustomCheckError(const MTP::Error &error);
bool handleCustomCheckError(const QString &type);
@@ -82,6 +95,9 @@ private:
bool onlyCheckCurrent() const;
void setPasswordDone(const QByteArray &newPasswordBytes);
void recoverPasswordDone(
const QByteArray &newPasswordBytes,
const MTPauth_Authorization &result);
void setPasswordFail(const MTP::Error &error);
void setPasswordFail(const QString &type);
void setPasswordFail(
@@ -131,7 +147,7 @@ private:
void passwordChecked();
void serverError();
const not_null<Main::Session*> _session;
Main::Session *_session = nullptr;
MTP::Sender _api;
QString _pattern;
@@ -157,10 +173,12 @@ private:
object_ptr<Ui::InputField> _passwordHint;
object_ptr<Ui::InputField> _recoverEmail;
object_ptr<Ui::LinkButton> _recover;
bool _showRecoverLink = false;
QString _oldError, _newError, _emailError;
rpl::event_stream<QByteArray> _newPasswordSet;
rpl::event_stream<MTPauth_Authorization> _newAuthorization;
rpl::event_stream<> _passwordReloadNeeded;
rpl::event_stream<> _clearUnconfirmedPassword;
@@ -170,12 +188,14 @@ class RecoverBox final : public Ui::BoxContent {
public:
RecoverBox(
QWidget*,
not_null<Main::Session*> session,
not_null<MTP::Instance*> mtp,
Main::Session *session,
const QString &pattern,
bool notEmptyPassport);
const PasscodeBox::CloudFields &fields,
Fn<void()> closeParent = nullptr);
rpl::producer<> passwordCleared() const;
rpl::producer<> recoveryExpired() const;
[[nodiscard]] rpl::producer<QByteArray> newPasswordSet() const;
[[nodiscard]] rpl::producer<> recoveryExpired() const;
//void reloadPassword();
//void recoveryExpired();
@@ -190,20 +210,26 @@ protected:
private:
void submit();
void codeChanged();
void codeSubmitDone(const MTPauth_Authorization &result);
void codeSubmitFail(const MTP::Error &error);
void proceedToClear();
void proceedToChange(const QString &code);
void checkSubmitFail(const MTP::Error &error);
void setError(const QString &error);
Main::Session *_session = nullptr;
MTP::Sender _api;
mtpRequestId _submitRequest = 0;
QString _pattern;
bool _notEmptyPassport = false;
PasscodeBox::CloudFields _cloudFields;
object_ptr<Ui::InputField> _recoverCode;
object_ptr<Ui::LinkButton> _noEmailAccess;
Fn<void()> _closeParent;
QString _error;
rpl::event_stream<> _passwordCleared;
rpl::event_stream<QByteArray> _newPasswordSet;
rpl::event_stream<> _recoveryExpired;
};

View File

@@ -30,7 +30,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/profile/info_profile_icon.h"
#include "apiwrap.h"
#include "facades.h" // Ui::showPeerHistory
#include "app.h"
#include "styles/style_boxes.h"
namespace {

View File

@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/vertical_layout.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/input_fields.h"
#include "ui/text/format_values.h" // Ui::FormatPhone
#include "ui/text/text_utilities.h"
#include "info/profile/info_profile_cover.h"
#include "lang/lang_keys.h"
@@ -19,7 +20,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/toast/toast.h"
#include "main/main_session.h"
#include "apiwrap.h"
#include "app.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_info.h"
@@ -145,7 +145,7 @@ void Controller::setupCover() {
_window,
(_phone.isEmpty()
? tr::lng_contact_mobile_hidden()
: rpl::single(App::formatPhone(_phone)))),
: rpl::single(Ui::FormatPhone(_phone)))),
style::margins())->setAttribute(Qt::WA_TransparentForMouseEvents);
}

View File

@@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "base/qt_adapters.h"
#include "apiwrap.h"
#include "api/api_cloud_password.h"
#include "main/main_session.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
@@ -444,7 +445,7 @@ void EditAdminBox::transferOwnership() {
? peer()->asChannel()->inputChannel
: MTP_inputChannelEmpty();
const auto api = &peer()->session().api();
api->reloadPasswordState();
api->cloudPassword().reload();
_checkTransferRequestId = api->request(MTPchannels_EditCreator(
channel,
MTP_inputUserEmpty(),
@@ -495,7 +496,7 @@ void EditAdminBox::transferOwnershipChecked() {
}
void EditAdminBox::requestTransferPassword(not_null<ChannelData*> channel) {
peer()->session().api().passwordState(
peer()->session().api().cloudPassword().state(
) | rpl::take(
1
) | rpl::start_with_next([=](const Core::CloudPasswordState &state) {

View File

@@ -47,7 +47,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h"
#include "info/profile/info_profile_icon.h"
#include "app.h"
#include "apiwrap.h"
#include "api/api_invite_links.h"
#include "facades.h"

View File

@@ -27,7 +27,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "mainwindow.h"
#include "apiwrap.h"
#include "app.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_info.h"

View File

@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/send_files_box.h"
#include "platform/platform_specific.h"
#include "lang/lang_keys.h"
#include "storage/localstorage.h"
#include "storage/storage_media_prepare.h"
@@ -52,7 +51,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h"
#include "core/core_settings.h"
#include "facades.h" // App::LambdaDelayed.
#include "app.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
@@ -800,10 +798,7 @@ bool SendFilesBox::addFiles(not_null<const QMimeData*> data) {
if (result.error == Ui::PreparedList::Error::None) {
return result;
} else if (data->hasImage()) {
auto image = Platform::GetImageFromClipboard();
if (image.isNull()) {
image = qvariant_cast<QImage>(data->imageData());
}
auto image = qvariant_cast<QImage>(data->imageData());
if (!image.isNull()) {
return Storage::PrepareMediaFromImage(
std::move(image),

View File

@@ -19,7 +19,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "apiwrap.h"
#include "app.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"

View File

@@ -173,6 +173,24 @@ callCameraUnmute: CallButton(callMicrophoneUnmute) {
}
}
}
callScreencastOn: CallButton(callMicrophoneMute) {
button: IconButton(callButton) {
icon: icon {{ "calls/calls_present", callIconFg }};
iconPosition: point(-1px, 22px);
ripple: RippleAnimation(defaultRippleAnimation) {
color: callMuteRipple;
}
}
}
callScreencastOff: CallButton(callMicrophoneUnmute) {
button: IconButton(callButton) {
icon: icon {{ "calls/calls_present", callIconFgActive }};
iconPosition: point(-1px, 22px);
ripple: RippleAnimation(defaultRippleAnimation) {
color: callIconActiveRipple;
}
}
}
callBottomShadowSize: 124px;
CallMuteButton {
@@ -724,6 +742,12 @@ groupCallTitleLabel: FlatLabel(groupCallSubtitleLabel) {
linkFontOver: font(semibold 14px);
}
}
groupCallVideoLimitLabel: FlatLabel(defaultFlatLabel) {
align: align(top);
textFg: groupCallMemberNotJoinedStatus;
style: semiboldTextStyle;
minWidth: 96px;
}
groupCallAddButtonPosition: point(10px, 7px);
groupCallMembersWidthMax: 480px;
groupCallRecordingMark: 6px;
@@ -1215,6 +1239,7 @@ groupCallNarrowColoredCrossLine: CrossLineAnimation(groupCallNarrowInactiveCross
groupCallNarrowRaisedHand: icon {{ "calls/video_mini_speak", groupCallMemberInactiveStatus }};
groupCallNarrowCameraIcon: icon {{ "calls/video_mini_video", groupCallMemberNotJoinedStatus }};
groupCallNarrowScreenIcon: icon {{ "calls/video_mini_screencast", groupCallMemberNotJoinedStatus }};
groupCallNarrowInvitedIcon: icon {{ "calls/video_mini_invited", groupCallMemberNotJoinedStatus }};
groupCallNarrowIconPosition: point(-4px, 2px);
groupCallNarrowIconSkip: 15px;
groupCallOutline: 2px;

View File

@@ -26,7 +26,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/confirm_box.h"
#include "base/unixtime.h"
#include "api/api_updates.h"
#include "app.h"
#include "apiwrap.h"
#include "styles/style_layers.h" // st::boxLabel.
#include "styles/style_calls.h"
@@ -398,7 +397,7 @@ void BoxController::receivedCalls(const QVector<MTPMessage> &result) {
if (const auto peer = session().data().peerLoaded(peerId)) {
const auto item = session().data().addNewMessage(
message,
MTPDmessage_ClientFlags(),
MessageFlags(),
NewMessageType::Existing);
insertRow(item, InsertWay::Append);
} else {

View File

@@ -290,16 +290,8 @@ void Call::startIncoming() {
}).send();
}
void Call::switchVideoOutgoing() {
const auto video = _videoOutgoing->state() == Webrtc::VideoState::Active;
_delegate->callRequestPermissionsOrFail(crl::guard(this, [=] {
videoOutgoing()->setState(StartVideoState(!video));
}), true);
}
void Call::answer() {
const auto video = _videoOutgoing->state() == Webrtc::VideoState::Active;
const auto video = isSharingVideo();
_delegate->callRequestPermissionsOrFail(crl::guard(this, [=] {
actuallyAnswer();
}), video);
@@ -366,7 +358,9 @@ void Call::setupOutgoingVideo() {
}
_videoOutgoing->stateValue(
) | rpl::start_with_next([=](Webrtc::VideoState state) {
if (state != Webrtc::VideoState::Inactive && !hasDevices()) {
if (state != Webrtc::VideoState::Inactive
&& !hasDevices()
&& !_videoCaptureIsScreencast) {
_errors.fire({ ErrorType::NoCamera });
_videoOutgoing->setState(Webrtc::VideoState::Inactive);
} else if (_state.current() != State::Established
@@ -383,7 +377,9 @@ void Call::setupOutgoingVideo() {
// Paused not supported right now.
Assert(state == Webrtc::VideoState::Active);
if (!_videoCapture) {
_videoCapture = _delegate->callGetVideoCapture();
_videoCapture = _delegate->callGetVideoCapture(
_videoCaptureDeviceId,
_videoCaptureIsScreencast);
_videoCapture->setOutput(_videoOutgoing->sink());
}
if (_instance) {
@@ -740,7 +736,7 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
.receiveTimeout = serverConfig.callPacketTimeoutMs / 1000.,
.dataSaving = tgcalls::DataSaving::Never,
.enableP2P = call.is_p2p_allowed(),
.enableAEC = !Platform::IsMac10_7OrGreater(),
.enableAEC = false,
.enableNS = true,
.enableAGC = true,
.enableVolumeControl = true,
@@ -986,9 +982,15 @@ void Call::setCurrentAudioDevice(bool input, const QString &deviceId) {
}
}
void Call::setCurrentVideoDevice(const QString &deviceId) {
if (_videoCapture) {
_videoCapture->switchToDevice(deviceId.toStdString());
void Call::setCurrentCameraDevice(const QString &deviceId) {
if (!_videoCaptureIsScreencast) {
_videoCaptureDeviceId = deviceId;
if (_videoCapture) {
_videoCapture->switchToDevice(deviceId.toStdString(), false);
if (_instance) {
_instance->sendVideoDeviceUpdated();
}
}
}
}
@@ -1008,6 +1010,77 @@ void Call::setAudioDuckingEnabled(bool enabled) {
}
}
bool Call::isSharingVideo() const {
return (_videoOutgoing->state() != Webrtc::VideoState::Inactive);
}
bool Call::isSharingCamera() const {
return !_videoCaptureIsScreencast && isSharingVideo();
}
bool Call::isSharingScreen() const {
return _videoCaptureIsScreencast && isSharingVideo();
}
QString Call::cameraSharingDeviceId() const {
return isSharingCamera() ? _videoCaptureDeviceId : QString();
}
QString Call::screenSharingDeviceId() const {
return isSharingScreen() ? _videoCaptureDeviceId : QString();
}
void Call::toggleCameraSharing(bool enabled) {
if (isSharingCamera() == enabled) {
return;
} else if (!enabled) {
if (_videoCapture) {
_videoCapture->setState(tgcalls::VideoState::Inactive);
}
_videoOutgoing->setState(Webrtc::VideoState::Inactive);
_videoCaptureDeviceId = QString();
return;
}
_delegate->callRequestPermissionsOrFail(crl::guard(this, [=] {
toggleScreenSharing(std::nullopt);
const auto deviceId = Core::App().settings().callVideoInputDeviceId();
_videoCaptureDeviceId = deviceId;
if (_videoCapture) {
_videoCapture->switchToDevice(deviceId.toStdString(), false);
if (_instance) {
_instance->sendVideoDeviceUpdated();
}
}
_videoOutgoing->setState(Webrtc::VideoState::Active);
}), true);
}
void Call::toggleScreenSharing(std::optional<QString> uniqueId) {
if (!uniqueId) {
if (isSharingScreen()) {
if (_videoCapture) {
_videoCapture->setState(tgcalls::VideoState::Inactive);
}
_videoOutgoing->setState(Webrtc::VideoState::Inactive);
}
_videoCaptureDeviceId = QString();
_videoCaptureIsScreencast = false;
return;
} else if (screenSharingDeviceId() == *uniqueId) {
return;
}
toggleCameraSharing(false);
_videoCaptureIsScreencast = true;
_videoCaptureDeviceId = *uniqueId;
if (_videoCapture) {
_videoCapture->switchToDevice(uniqueId->toStdString(), true);
if (_instance) {
_instance->sendVideoDeviceUpdated();
}
}
_videoOutgoing->setState(Webrtc::VideoState::Active);
}
void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) {
Expects(type != FinishType::None);

View File

@@ -77,8 +77,10 @@ public:
Fn<void()> onSuccess,
bool video) = 0;
virtual auto callGetVideoCapture()
-> std::shared_ptr<tgcalls::VideoCaptureInterface> = 0;
virtual auto callGetVideoCapture(
const QString &deviceId,
bool isScreenCapture)
-> std::shared_ptr<tgcalls::VideoCaptureInterface> = 0;
virtual ~Delegate() = default;
@@ -174,7 +176,6 @@ public:
crl::time getDurationMs() const;
float64 getWaitingSoundPeakValue() const;
void switchVideoOutgoing();
void answer();
void hangup();
void redial();
@@ -185,10 +186,22 @@ public:
QString getDebugLog() const;
void setCurrentAudioDevice(bool input, const QString &deviceId);
void setCurrentVideoDevice(const QString &deviceId);
//void setAudioVolume(bool input, float level);
void setAudioDuckingEnabled(bool enabled);
void setCurrentCameraDevice(const QString &deviceId);
[[nodiscard]] QString videoDeviceId() const {
return _videoCaptureDeviceId;
}
[[nodiscard]] bool isSharingVideo() const;
[[nodiscard]] bool isSharingCamera() const;
[[nodiscard]] bool isSharingScreen() const;
[[nodiscard]] QString cameraSharingDeviceId() const;
[[nodiscard]] QString screenSharingDeviceId() const;
void toggleCameraSharing(bool enabled);
void toggleScreenSharing(std::optional<QString> uniqueId);
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;
}
@@ -268,6 +281,8 @@ private:
std::unique_ptr<tgcalls::Instance> _instance;
std::shared_ptr<tgcalls::VideoCaptureInterface> _videoCapture;
QString _videoCaptureDeviceId;
bool _videoCaptureIsScreencast = false;
const std::unique_ptr<Webrtc::VideoTrack> _videoIncoming;
const std::unique_ptr<Webrtc::VideoTrack> _videoOutgoing;

View File

@@ -34,7 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwidget.h"
#include "mtproto/mtproto_config.h"
#include "boxes/rate_call_box.h"
#include "app.h"
#include "app.h" // App::quitting
#include <tgcalls/VideoCaptureInterface.h>
#include <tgcalls/StaticThreads.h>
@@ -64,8 +64,10 @@ public:
Fn<void()> onSuccess,
bool video) override;
void callPlaySound(CallSound sound) override;
auto callGetVideoCapture()
-> std::shared_ptr<tgcalls::VideoCaptureInterface> override;
auto callGetVideoCapture(
const QString &deviceId,
bool isScreenCapture)
-> std::shared_ptr<tgcalls::VideoCaptureInterface> override;
void groupCallFinished(not_null<GroupCall*> call) override;
void groupCallFailed(not_null<GroupCall*> call) override;
@@ -123,9 +125,11 @@ void Instance::Delegate::callPlaySound(CallSound sound) {
}());
}
auto Instance::Delegate::callGetVideoCapture()
auto Instance::Delegate::callGetVideoCapture(
const QString &deviceId,
bool isScreenCapture)
-> std::shared_ptr<tgcalls::VideoCaptureInterface> {
return _instance->getVideoCapture();
return _instance->getVideoCapture(deviceId, isScreenCapture);
}
void Instance::Delegate::groupCallFinished(not_null<GroupCall*> call) {
@@ -159,7 +163,7 @@ void Instance::Delegate::groupCallPlaySound(GroupCallSound sound) {
auto Instance::Delegate::groupCallGetVideoCapture(const QString &deviceId)
-> std::shared_ptr<tgcalls::VideoCaptureInterface> {
return _instance->getVideoCapture(deviceId);
return _instance->getVideoCapture(deviceId, false);
}
FnMut<void()> Instance::Delegate::groupCallAddAsyncWaiter() {
@@ -699,18 +703,25 @@ void Instance::requestPermissionOrFail(Platform::PermissionType type, Fn<void()>
}
std::shared_ptr<tgcalls::VideoCaptureInterface> Instance::getVideoCapture(
QString deviceId) {
if (deviceId.isEmpty()) {
deviceId = Core::App().settings().callVideoInputDeviceId();
}
std::optional<QString> deviceId,
bool isScreenCapture) {
if (auto result = _videoCapture.lock()) {
result->switchToDevice(deviceId.toStdString());
if (deviceId) {
result->switchToDevice(
(deviceId->isEmpty()
? Core::App().settings().callVideoInputDeviceId()
: *deviceId).toStdString(),
isScreenCapture);
}
return result;
}
const auto startDeviceId = (deviceId && !deviceId->isEmpty())
? *deviceId
: Core::App().settings().callVideoInputDeviceId();
auto result = std::shared_ptr<tgcalls::VideoCaptureInterface>(
tgcalls::VideoCaptureInterface::Create(
tgcalls::StaticThreads::getThreads(),
deviceId.toStdString()));
startDeviceId.toStdString()));
_videoCapture = result;
return result;
}

View File

@@ -75,7 +75,9 @@ public:
bool activateCurrentCall(const QString &joinHash = QString());
bool minimizeCurrentActiveCall();
bool closeCurrentActiveCall();
[[nodiscard]] auto getVideoCapture(QString deviceId = QString())
[[nodiscard]] auto getVideoCapture(
std::optional<QString> deviceId = std::nullopt,
bool isScreenCapture = false)
-> std::shared_ptr<tgcalls::VideoCaptureInterface>;
void requestPermissionsOrFail(Fn<void()> onSuccess, bool video = true);

View File

@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo_media.h"
#include "data/data_cloud_file.h"
#include "data/data_changes.h"
#include "calls/group/calls_group_common.h"
#include "calls/calls_emoji_fingerprint.h"
#include "calls/calls_signal_bars.h"
#include "calls/calls_userpic.h"
@@ -24,7 +25,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/shadow.h"
#include "ui/widgets/window.h"
#include "ui/widgets/rp_window.h"
#include "ui/layers/layer_manager.h"
#include "ui/layers/generic_box.h"
#include "ui/image/image.h"
#include "ui/text/format_values.h"
#include "ui/wrap/fade_wrap.h"
@@ -42,8 +45,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/platform_specific.h"
#include "base/platform/base_platform_info.h"
#include "window/main_window.h"
#include "app.h"
#include "webrtc/webrtc_video_track.h"
#include "webrtc/webrtc_media_devices.h"
#include "styles/style_calls.h"
#include "styles/style_chat.h"
@@ -57,6 +60,7 @@ namespace Calls {
Panel::Panel(not_null<Call*> call)
: _call(call)
, _user(call->user())
, _layerBg(std::make_unique<Ui::LayerManager>(widget()))
#ifndef Q_OS_MAC
, _controls(std::make_unique<Ui::Platform::TitleControls>(
widget(),
@@ -67,10 +71,14 @@ Panel::Panel(not_null<Call*> call)
, _answerHangupRedial(widget(), st::callAnswer, &st::callHangup)
, _decline(widget(), object_ptr<Ui::CallButton>(widget(), st::callHangup))
, _cancel(widget(), object_ptr<Ui::CallButton>(widget(), st::callCancel))
, _screencast(widget(), st::callScreencastOn, &st::callScreencastOff)
, _camera(widget(), st::callCameraMute, &st::callCameraUnmute)
, _mute(widget(), st::callMicrophoneMute, &st::callMicrophoneUnmute)
, _name(widget(), st::callName)
, _status(widget(), st::callStatus) {
_layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox);
_layerBg->setHideByBackgroundClick(true);
_decline->setDuration(st::callPanelDuration);
_decline->entity()->setText(tr::lng_call_decline());
_cancel->setDuration(st::callPanelDuration);
@@ -152,9 +160,13 @@ void Panel::initWindow() {
_answerHangupRedial->height()).contains(widgetPoint)
|| (!_outgoingPreviewInBody
&& _outgoingVideoBubble->geometry().contains(widgetPoint));
return inControls
? Flag::None
: (Flag::Move | Flag::FullScreen);
if (inControls) {
return Flag::None | Flag(0);
}
const auto shown = _layerBg->topShownLayer();
return (!shown || !shown->geometry().contains(widgetPoint))
? (Flag::Move | Flag::FullScreen)
: Flag::None;
});
// Don't do that, it looks awful :(
@@ -205,9 +217,28 @@ void Panel::initControls() {
_call->setMuted(!_call->muted());
}
});
_screencast->setClickedCallback([=] {
if (!_call) {
return;
} else if (!Webrtc::DesktopCaptureAllowed()) {
if (auto box = Group::ScreenSharingPrivacyRequestBox()) {
_layerBg->showBox(std::move(box));
}
} else if (const auto source = Webrtc::UniqueDesktopCaptureSource()) {
if (_call->isSharingScreen()) {
_call->toggleScreenSharing(std::nullopt);
} else {
chooseSourceAccepted(*source, false);
}
} else {
Group::Ui::DesktopCapture::ChooseSource(this);
}
});
_camera->setClickedCallback([=] {
if (_call) {
_call->switchVideoOutgoing();
if (!_call) {
return;
} else {
_call->toggleCameraSharing(!_call->isSharingCamera());
}
});
@@ -218,7 +249,8 @@ void Panel::initControls() {
});
_updateOuterRippleTimer.setCallback([this] {
if (_call) {
_answerHangupRedial->setOuterValue(_call->getWaitingSoundPeakValue());
_answerHangupRedial->setOuterValue(
_call->getWaitingSoundPeakValue());
} else {
_answerHangupRedial->setOuterValue(0.);
_updateOuterRippleTimer.cancel();
@@ -260,6 +292,40 @@ void Panel::setIncomingSize(QSize size) {
showControls();
}
QWidget *Panel::chooseSourceParent() {
return window().get();
}
QString Panel::chooseSourceActiveDeviceId() {
return _call->screenSharingDeviceId();
}
bool Panel::chooseSourceActiveWithAudio() {
return false;// _call->screenSharingWithAudio();
}
bool Panel::chooseSourceWithAudioSupported() {
//#ifdef Q_OS_WIN
// return true;
//#else // Q_OS_WIN
return false;
//#endif // Q_OS_WIN
}
rpl::lifetime &Panel::chooseSourceInstanceLifetime() {
return lifetime();
}
void Panel::chooseSourceAccepted(
const QString &deviceId,
bool withAudio) {
_call->toggleScreenSharing(deviceId/*, withAudio*/);
}
void Panel::chooseSourceStop() {
_call->toggleScreenSharing(std::nullopt);
}
void Panel::refreshIncomingGeometry() {
Expects(_call != nullptr);
Expects(_incoming != nullptr);
@@ -332,12 +398,19 @@ void Panel::reinitWithCall(Call *call) {
}, _callLifetime);
_call->videoOutgoing()->stateValue(
) | rpl::start_with_next([=](Webrtc::VideoState state) {
const auto active = (state == Webrtc::VideoState::Active);
_camera->setProgress(active ? 0. : 1.);
_camera->setText(active
? tr::lng_call_stop_video()
: tr::lng_call_start_video());
) | rpl::start_with_next([=] {
{
const auto active = _call->isSharingCamera();
_camera->setProgress(active ? 0. : 1.);
_camera->setText(active
? tr::lng_call_stop_video()
: tr::lng_call_start_video());
}
{
const auto active = _call->isSharingScreen();
_screencast->setProgress(active ? 0. : 1.);
_screencast->setText(tr::lng_call_screencast());
}
}, _callLifetime);
_call->stateValue(
@@ -646,9 +719,11 @@ void Panel::updateControlsGeometry() {
updateOutgoingVideoBubbleGeometry();
}
auto bothWidth = _answerHangupRedial->width() + st::callCancel.button.width;
_decline->moveToLeft((widget()->width() - bothWidth) / 2, _buttonsTop);
_cancel->moveToLeft((widget()->width() - bothWidth) / 2, _buttonsTop);
auto threeWidth = _answerHangupRedial->width()
+ st::callCancel.button.width
- _screencast->width();
_decline->moveToLeft((widget()->width() - threeWidth) / 2, _buttonsTop);
_cancel->moveToLeft((widget()->width() - threeWidth) / 2, _buttonsTop);
updateHangupGeometry();
}
@@ -670,16 +745,19 @@ void Panel::updateOutgoingVideoBubbleGeometry() {
}
void Panel::updateHangupGeometry() {
auto singleWidth = _answerHangupRedial->width();
auto bothWidth = singleWidth + st::callCancel.button.width;
auto rightFrom = (widget()->width() - bothWidth) / 2;
auto rightTo = (widget()->width() - singleWidth) / 2;
auto twoWidth = _answerHangupRedial->width() + _screencast->width();
auto threeWidth = twoWidth + st::callCancel.button.width;
auto rightFrom = (widget()->width() - threeWidth) / 2;
auto rightTo = (widget()->width() - twoWidth) / 2;
auto hangupProgress = _hangupShownProgress.value(_hangupShown ? 1. : 0.);
auto hangupRight = anim::interpolate(rightFrom, rightTo, hangupProgress);
_answerHangupRedial->moveToRight(hangupRight, _buttonsTop);
_answerHangupRedial->setProgress(hangupProgress);
_mute->moveToRight(hangupRight - _mute->width(), _buttonsTop);
_camera->moveToLeft(hangupRight - _mute->width(), _buttonsTop);
_screencast->moveToLeft(hangupRight - _mute->width(), _buttonsTop);
_camera->moveToLeft(
hangupRight - _mute->width() + _screencast->width(),
_buttonsTop);
}
void Panel::updateStatusGeometry() {
@@ -709,7 +787,7 @@ void Panel::handleClose() {
}
}
not_null<Ui::Window*> Panel::window() const {
not_null<Ui::RpWindow*> Panel::window() const {
return _window.window();
}

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h"
#include "base/object_ptr.h"
#include "calls/calls_call.h"
#include "calls/group/ui/desktop_capture_choose_source.h"
#include "ui/effects/animations.h"
#include "ui/gl/gl_window.h"
#include "ui/rp_widget.h"
@@ -25,12 +26,13 @@ class CloudImageView;
namespace Ui {
class IconButton;
class CallButton;
class LayerManager;
class FlatLabel;
template <typename Widget>
class FadeWrap;
template <typename Widget>
class PaddingWrap;
class Window;
class RpWindow;
namespace GL {
enum class Backend;
} // namespace GL
@@ -50,7 +52,7 @@ class Userpic;
class SignalBars;
class VideoBubble;
class Panel final {
class Panel final : private Group::Ui::DesktopCapture::ChooseSourceDelegate {
public:
Panel(not_null<Call*> call);
~Panel();
@@ -61,7 +63,17 @@ public:
void replaceCall(not_null<Call*> call);
void closeBeforeDestroy();
rpl::lifetime &lifetime();
QWidget *chooseSourceParent() override;
QString chooseSourceActiveDeviceId() override;
bool chooseSourceActiveWithAudio() override;
bool chooseSourceWithAudioSupported() override;
rpl::lifetime &chooseSourceInstanceLifetime() override;
void chooseSourceAccepted(
const QString &deviceId,
bool withAudio) override;
void chooseSourceStop() override;
[[nodiscard]] rpl::lifetime &lifetime();
private:
class Incoming;
@@ -73,7 +85,7 @@ private:
Redial,
};
[[nodiscard]] not_null<Ui::Window*> window() const;
[[nodiscard]] not_null<Ui::RpWindow*> window() const;
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
void paint(QRect clip);
@@ -110,6 +122,7 @@ private:
not_null<UserData*> _user;
Ui::GL::Window _window;
const std::unique_ptr<Ui::LayerManager> _layerBg;
std::unique_ptr<Incoming> _incoming;
#ifndef Q_OS_MAC
@@ -128,6 +141,7 @@ private:
bool _outgoingPreviewInBody = false;
std::optional<AnswerHangupRedialState> _answerHangupRedialState;
Ui::Animations::Simple _hangupShownProgress;
object_ptr<Ui::CallButton> _screencast;
object_ptr<Ui::CallButton> _camera;
object_ptr<Ui::CallButton> _mute;
object_ptr<Ui::FlatLabel> _name;

View File

@@ -32,7 +32,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "boxes/abstract_box.h"
#include "base/timer.h"
#include "app.h"
#include "styles/style_calls.h"
#include "styles/style_chat.h" // style::GroupCallUserpics
#include "styles/style_layers.h"

View File

@@ -654,7 +654,7 @@ void GroupCall::toggleScreenSharing(
_screenWithAudio = withAudio;
_screenState = Webrtc::VideoState::Active;
if (changed && wasSharing && isSharingScreen()) {
_screenCapture->switchToDevice(uniqueId->toStdString());
_screenCapture->switchToDevice(uniqueId->toStdString(), true);
}
if (_screenInstance) {
_screenInstance->setIsMuted(!withAudio);
@@ -1715,13 +1715,13 @@ void GroupCall::handlePossibleCreateOrJoinResponse(
void GroupCall::handlePossibleCreateOrJoinResponse(
const MTPDgroupCall &data) {
setScheduledDate(data.vschedule_date().value_or_empty());
if (_acceptFields) {
if (!_instance && !_id) {
const auto input = MTP_inputGroupCall(
data.vid(),
data.vaccess_hash());
const auto scheduleDate = data.vschedule_date().value_or_empty();
setScheduledDate(scheduleDate);
if (const auto chat = _peer->asChat()) {
chat->setGroupCall(input, scheduleDate);
} else if (const auto group = _peer->asChannel()) {
@@ -1735,6 +1735,7 @@ void GroupCall::handlePossibleCreateOrJoinResponse(
} else if (_id != data.vid().v || !_instance) {
return;
}
setScheduledDate(data.vschedule_date().value_or_empty());
if (const auto streamDcId = data.vstream_dc_id()) {
_broadcastDcId = MTP::BareDcId(streamDcId->v);
}
@@ -1951,7 +1952,7 @@ void GroupCall::setupMediaDevices() {
) | rpl::start_with_next([=](QString id) {
_cameraInputId = id;
if (_cameraCapture) {
_cameraCapture->switchToDevice(id.toStdString());
_cameraCapture->switchToDevice(id.toStdString(), false);
}
}, _lifetime);
}
@@ -2064,7 +2065,9 @@ void GroupCall::setupOutgoingVideo() {
});
});
} else {
_cameraCapture->switchToDevice(_cameraInputId.toStdString());
_cameraCapture->switchToDevice(
_cameraInputId.toStdString(),
false);
}
if (_instance) {
_instance->setVideoCapture(_cameraCapture);
@@ -2131,7 +2134,8 @@ void GroupCall::setupOutgoingVideo() {
});
} else {
_screenCapture->switchToDevice(
_screenDeviceId.toStdString());
_screenDeviceId.toStdString(),
true);
}
if (_screenInstance) {
_screenInstance->setVideoCapture(_screenCapture);
@@ -2779,6 +2783,8 @@ void GroupCall::checkLastSpoke() {
|| muted() == MuteState::Active
|| muted() == MuteState::PushToTalk) {
real->applyLastSpoke(ssrc, when, now);
} else {
real->applyLastSpoke(ssrc, { crl::time(), crl::time() }, now);
}
}
_lastSpoke = std::move(list);
@@ -3049,10 +3055,6 @@ void GroupCall::setCurrentAudioDevice(bool input, const QString &deviceId) {
}
}
void GroupCall::setCurrentVideoDevice(const QString &deviceId) {
_mediaDevices->switchToVideoInput(deviceId);
}
void GroupCall::toggleMute(const Group::MuteRequest &data) {
if (data.locallyOnly) {
applyParticipantLocally(data.peer, data.mute, std::nullopt);

View File

@@ -370,7 +370,6 @@ public:
}
void setCurrentAudioDevice(bool input, const QString &deviceId);
void setCurrentVideoDevice(const QString &deviceId);
[[nodiscard]] bool isSharingScreen() const;
[[nodiscard]] rpl::producer<bool> isSharingScreenValue() const;
[[nodiscard]] bool isScreenPaused() const;

View File

@@ -0,0 +1,53 @@
/*
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 "calls/group/calls_group_common.h"
#include "base/platform/base_platform_info.h"
#include "ui/widgets/labels.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "lang/lang_keys.h"
#include "styles/style_layers.h"
#include "styles/style_calls.h"
namespace Calls::Group {
object_ptr<Ui::GenericBox> ScreenSharingPrivacyRequestBox() {
#ifdef Q_OS_MAC
if (!Platform::IsMac10_15OrGreater()) {
return { nullptr };
}
return Box([=](not_null<Ui::GenericBox*> box) {
box->addRow(
object_ptr<Ui::FlatLabel>(
box.get(),
rpl::combine(
tr::lng_group_call_mac_screencast_access(),
tr::lng_group_call_mac_recording()
) | rpl::map([](QString a, QString b) {
auto result = Ui::Text::RichLangValue(a);
result.append("\n\n").append(Ui::Text::RichLangValue(b));
return result;
}),
st::groupCallBoxLabel),
style::margins(
st::boxRowPadding.left(),
st::boxPadding.top(),
st::boxRowPadding.right(),
st::boxPadding.bottom()));
box->addButton(tr::lng_group_call_mac_settings(), [=] {
Platform::OpenDesktopCapturePrivacySettings();
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
});
#else // Q_OS_MAC
return { nullptr };
#endif // Q_OS_MAC
}
} // namespace Calls::Group

View File

@@ -7,8 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/object_ptr.h"
class UserData;
namespace Ui {
class GenericBox;
} // namespace Ui
namespace Calls::Group {
constexpr auto kDefaultVolume = 10000;
@@ -78,4 +84,6 @@ constexpr inline bool is_flag_type(StickedTooltip) {
}
using StickedTooltips = base::flags<StickedTooltip>;
[[nodiscard]] object_ptr<Ui::GenericBox> ScreenSharingPrivacyRequestBox();
} // namespace Calls::Group

View File

@@ -46,9 +46,16 @@ constexpr auto kUserpicBlurRadius = 8;
using Row = MembersRow;
[[nodiscard]] int VideoParticipantsLimit(not_null<Main::Session*> session) {
return int(session->account().appConfig().get<double>(
"groupcall_video_participants_max",
30.));
}
void SetupVideoPlaceholder(
not_null<Ui::RpWidget*> widget,
not_null<PeerData*> chat) {
not_null<PeerData*> chat,
int limit) {
struct State {
QImage blurred;
QImage rounded;
@@ -128,9 +135,6 @@ void SetupVideoPlaceholder(
size.width());
const auto skip = st::groupCallVideoLargeSkip;
const auto limit = chat->session().account().appConfig().get<double>(
"groupcall_video_participants_max",
30.);
p.setPen(st::groupCallVideoTextFg);
const auto text = QRect(
skip,
@@ -145,6 +149,22 @@ void SetupVideoPlaceholder(
}, widget->lifetime());
}
void SetupVideoAboutLimit(
not_null<Ui::RpWidget*> widget,
not_null<Main::Session*> session,
int limit) {
const auto label = Ui::CreateChild<Ui::FlatLabel>(
widget.get(),
tr::lng_group_call_over_limit(lt_count, rpl::single(limit * 1.)),
st::groupCallVideoLimitLabel);
widget->widthValue(
) | rpl::start_with_next([=](int width) {
label->resizeToWidth(width);
label->moveToLeft(0, st::normalFont->height / 3);
widget->resize(width, label->height() + st::normalFont->height);
}, label->lifetime());
}
} // namespace
class Members::Controller final
@@ -226,7 +246,20 @@ private:
not_null<Row*> row,
const std::optional<Data::GroupCallParticipant> &was,
const Data::GroupCallParticipant *participant);
void updateRowInSoundingMap(
not_null<Row*> row,
bool wasSounding,
uint32 wasSsrc,
uint32 wasAdditionalSsrc,
const Data::GroupCallParticipant *participant);
void updateRowInSoundingMap(
not_null<Row*> row,
bool wasSounding,
uint32 wasSsrc,
bool nowSounding,
uint32 nowSsrc);
void removeRow(not_null<Row*> row);
void removeRowFromSoundingMap(not_null<Row*> row);
void updateRowLevel(not_null<Row*> row, float level);
void checkRowPosition(not_null<Row*> row);
[[nodiscard]] bool needToReorder(not_null<Row*> row) const;
@@ -784,14 +817,50 @@ void Members::Controller::updateRow(
: 0;
row->setSkipLevelUpdate(_skipRowLevelUpdate);
row->updateState(participant);
const auto wasNoSounding = _soundingRowBySsrc.empty();
updateRowInSoundingMap(
row,
wasSounding,
wasSsrc,
wasAdditionalSsrc,
participant);
const auto nowNoSounding = _soundingRowBySsrc.empty();
if (wasNoSounding && !nowNoSounding) {
_soundingAnimation.start();
} else if (nowNoSounding && !wasNoSounding) {
_soundingAnimation.stop();
}
delegate()->peerListUpdateRow(row);
}
void Members::Controller::updateRowInSoundingMap(
not_null<Row*> row,
bool wasSounding,
uint32 wasSsrc,
uint32 wasAdditionalSsrc,
const Data::GroupCallParticipant *participant) {
const auto nowSounding = row->sounding();
const auto nowSsrc = participant ? participant->ssrc : 0;
const auto nowAdditionalSsrc = participant
? GetAdditionalAudioSsrc(participant->videoParams)
: 0;
updateRowInSoundingMap(row, wasSounding, wasSsrc, nowSounding, nowSsrc);
updateRowInSoundingMap(
row,
wasSounding,
wasAdditionalSsrc,
nowSounding,
nowAdditionalSsrc);
}
const auto wasNoSounding = _soundingRowBySsrc.empty();
void Members::Controller::updateRowInSoundingMap(
not_null<Row*> row,
bool wasSounding,
uint32 wasSsrc,
bool nowSounding,
uint32 nowSsrc) {
if (wasSsrc == nowSsrc) {
if (nowSsrc && nowSounding != wasSounding) {
if (nowSounding) {
@@ -806,32 +875,14 @@ void Members::Controller::updateRow(
_soundingRowBySsrc.emplace(nowSsrc, row);
}
}
if (wasAdditionalSsrc == nowAdditionalSsrc) {
if (nowAdditionalSsrc && nowSounding != wasSounding) {
if (nowSounding) {
_soundingRowBySsrc.emplace(nowAdditionalSsrc, row);
} else {
_soundingRowBySsrc.remove(nowAdditionalSsrc);
}
}
} else {
_soundingRowBySsrc.remove(wasAdditionalSsrc);
if (nowSounding && nowAdditionalSsrc) {
_soundingRowBySsrc.emplace(nowAdditionalSsrc, row);
}
}
const auto nowNoSounding = _soundingRowBySsrc.empty();
if (wasNoSounding && !nowNoSounding) {
_soundingAnimation.start();
} else if (nowNoSounding && !wasNoSounding) {
_soundingAnimation.stop();
}
delegate()->peerListUpdateRow(row);
}
void Members::Controller::removeRow(not_null<Row*> row) {
removeRowFromSoundingMap(row);
delegate()->peerListRemoveRow(row);
}
void Members::Controller::removeRowFromSoundingMap(not_null<Row*> row) {
// There may be 0, 1 or 2 entries for a row.
for (auto i = begin(_soundingRowBySsrc); i != end(_soundingRowBySsrc);) {
if (i->second == row) {
@@ -840,7 +891,6 @@ void Members::Controller::removeRow(not_null<Row*> row) {
++i;
}
}
delegate()->peerListRemoveRow(row);
}
void Members::Controller::updateRowLevel(
@@ -924,18 +974,22 @@ void Members::Controller::prepareRows(not_null<Data::GroupCall*> real) {
auto changed = false;
auto count = delegate()->peerListFullRowsCount();
for (auto i = 0; i != count;) {
auto row = delegate()->peerListRowAt(i);
auto participantPeer = row->peer();
if (isMe(participantPeer)) {
const auto row = static_cast<Row*>(
delegate()->peerListRowAt(i).get());
removeRowFromSoundingMap(row);
const auto participantPeer = row->peer();
const auto me = isMe(participantPeer);
if (me) {
foundMe = true;
++i;
continue;
}
if (real->participantByPeer(participantPeer)) {
if (const auto found = real->participantByPeer(participantPeer)) {
updateRowInSoundingMap(row, false, 0, 0, found);
++i;
} else if (me) {
++i;
} else {
changed = true;
removeRow(static_cast<Row*>(row.get()));
removeRow(row);
--count;
}
}
@@ -1036,12 +1090,16 @@ void Members::Controller::rowPaintIcon(
return;
}
const auto narrow = (state.style == MembersRowStyle::Narrow);
if (!narrow && state.invited) {
st::groupCallMemberInvited.paintInCenter(
p,
QRect(
rect.topLeft() + st::groupCallMemberInvitedPosition,
st::groupCallMemberInvited.size()));
if (state.invited) {
if (narrow) {
st::groupCallNarrowInvitedIcon.paintInCenter(p, rect);
} else {
st::groupCallMemberInvited.paintInCenter(
p,
QRect(
rect.topLeft() + st::groupCallMemberInvitedPosition,
st::groupCallMemberInvited.size()));
}
return;
}
const auto video = (state.style == MembersRowStyle::Video);
@@ -1601,6 +1659,7 @@ Members::Members(
object_ptr<Ui::VerticalLayout>(_scroll.data())))
, _videoWrap(_layout->add(object_ptr<Ui::RpWidget>(_layout.get())))
, _videoPlaceholder(std::make_unique<Ui::RpWidget>(_videoWrap.get()))
, _videoAboutLimit(std::make_unique<Ui::RpWidget>(_videoWrap.get()))
, _viewport(
std::make_unique<Viewport>(
_videoWrap.get(),
@@ -1838,6 +1897,7 @@ void Members::trackViewportGeometry() {
_scroll->scrollTopValue(
) | rpl::skip(1) | rpl::start_with_next(move, _viewport->lifetime());
const auto videoLimit = VideoParticipantsLimit(&_call->peer()->session());
rpl::combine(
_layout->widthValue(),
_call->hasNotShownVideoValue()
@@ -1846,15 +1906,52 @@ void Members::trackViewportGeometry() {
_videoPlaceholder->setGeometry(0, 0, width, height);
}, _videoPlaceholder->lifetime());
SetupVideoPlaceholder(_videoPlaceholder.get(), _call->peer());
SetupVideoPlaceholder(_videoPlaceholder.get(), _call->peer(), videoLimit);
_layout->widthValue(
) | rpl::start_with_next([=](int width) {
_videoAboutLimit->resizeToWidth(width);
}, _videoAboutLimit->lifetime());
using namespace rpl::mappers;
auto aboutLimitRelevant = fullCountValue(
) | rpl::map(
_1 > videoLimit
) | rpl::distinct_until_changed();
auto aboutLimitShown = rpl::combine(
std::move(aboutLimitRelevant),
_call->canManageValue(),
_1 && _2);
SetupVideoAboutLimit(
_videoAboutLimit.get(),
&_call->peer()->session(),
videoLimit);
rpl::combine(
_videoPlaceholder->heightValue(),
_viewport->fullHeightValue()
) | rpl::start_with_next([=](int placeholder, int viewport) {
_viewport->fullHeightValue(),
_videoAboutLimit->heightValue(),
std::move(aboutLimitShown)
) | rpl::start_with_next([=](
int placeholder,
int viewport,
int aboutLimit,
bool aboutLimitShown) {
if (placeholder > 0 || viewport <= 0 || !aboutLimitShown) {
aboutLimitShown = false;
}
// This call may update _videoAboutLimit->height() :(
_videoAboutLimit->setVisible(aboutLimitShown);
_videoAboutLimit->move(0, viewport);
_videoWrap->resize(
_videoWrap->width(),
std::max(placeholder, viewport));
std::max(
placeholder,
(viewport
+ (aboutLimitShown ? _videoAboutLimit->height() : 0))));
if (viewport > 0) {
move();
resize();

View File

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

View File

@@ -25,7 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/dropdown_menu.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/tooltip.h"
#include "ui/widgets/window.h"
#include "ui/widgets/rp_window.h"
#include "ui/chat/group_call_bar.h"
#include "ui/layers/layer_manager.h"
#include "ui/layers/generic_box.h"
@@ -48,7 +48,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "base/qt_signal_producer.h"
#include "base/timer_rpl.h"
#include "app.h"
#include "apiwrap.h" // api().kickParticipant.
#include "webrtc/webrtc_video_track.h"
#include "webrtc/webrtc_media_devices.h" // UniqueDesktopCaptureSource.
@@ -70,10 +69,6 @@ constexpr auto kRecordingOpacity = 0.6;
constexpr auto kStartNoConfirmation = TimeId(10);
constexpr auto kControlsBackgroundOpacity = 0.8;
constexpr auto kOverrideActiveColorBgAlpha = 172;
constexpr auto kMicrophoneTooltipAfterLoudCount = 3;
constexpr auto kDropLoudAfterQuietCount = 5;
constexpr auto kMicrophoneTooltipLevelThreshold = 0.2;
constexpr auto kMicrophoneTooltipCheckInterval = crl::time(500);
} // namespace
@@ -87,49 +82,6 @@ struct Panel::ControlsBackgroundNarrow {
Ui::RpWidget blocker;
};
class Panel::MicLevelTester final {
public:
explicit MicLevelTester(Fn<void()> show);
[[nodiscard]] bool showTooltip() const;
private:
void check();
Fn<void()> _show;
base::Timer _timer;
Webrtc::AudioInputTester _tester;
int _loudCount = 0;
int _quietCount = 0;
};
Panel::MicLevelTester::MicLevelTester(Fn<void()> show)
: _show(std::move(show))
, _timer([=] { check(); })
, _tester(
Core::App().settings().callAudioBackend(),
Core::App().settings().callInputDeviceId()) {
_timer.callEach(kMicrophoneTooltipCheckInterval);
}
bool Panel::MicLevelTester::showTooltip() const {
return (_loudCount >= kMicrophoneTooltipAfterLoudCount);
}
void Panel::MicLevelTester::check() {
const auto level = _tester.getAndResetLevel();
if (level >= kMicrophoneTooltipLevelThreshold) {
_quietCount = 0;
if (++_loudCount >= kMicrophoneTooltipAfterLoudCount) {
_show();
}
} else if (_loudCount > 0 && ++_quietCount >= kDropLoudAfterQuietCount) {
_quietCount = 0;
_loudCount = 0;
}
}
Panel::Panel(not_null<GroupCall*> call)
: _call(call)
, _peer(call->peer())
@@ -315,11 +267,16 @@ void Panel::initWindow() {
0,
widget()->width(),
st::groupCallMembersTop);
return (titleRect.contains(widgetPoint)
const auto moveable = (titleRect.contains(widgetPoint)
&& (!_menuToggle || !_menuToggle->geometry().contains(widgetPoint))
&& (!_menu || !_menu->geometry().contains(widgetPoint))
&& (!_recordingMark || !_recordingMark->geometry().contains(widgetPoint))
&& (!_joinAsToggle || !_joinAsToggle->geometry().contains(widgetPoint)))
&& (!_joinAsToggle || !_joinAsToggle->geometry().contains(widgetPoint)));
if (!moveable) {
return (Flag::None | Flag(0));
}
const auto shown = _layerBg->topShownLayer();
return (!shown || !shown->geometry().contains(widgetPoint))
? (Flag::Move | Flag::Maximize)
: Flag::None;
});
@@ -1155,35 +1112,9 @@ void Panel::refreshTopButton() {
}
void Panel::screenSharingPrivacyRequest() {
#ifdef Q_OS_MAC
if (!Platform::IsMac10_15OrGreater()) {
return;
if (auto box = ScreenSharingPrivacyRequestBox()) {
_layerBg->showBox(std::move(box));
}
const auto requestInputMonitoring = Platform::IsMac10_15OrGreater();
_layerBg->showBox(Box([=](not_null<Ui::GenericBox*> box) {
box->addRow(
object_ptr<Ui::FlatLabel>(
box.get(),
rpl::combine(
tr::lng_group_call_mac_screencast_access(),
tr::lng_group_call_mac_recording()
) | rpl::map([](QString a, QString b) {
auto result = Ui::Text::RichLangValue(a);
result.append("\n\n").append(Ui::Text::RichLangValue(b));
return result;
}),
st::groupCallBoxLabel),
style::margins(
st::boxRowPadding.left(),
st::boxPadding.top(),
st::boxRowPadding.right(),
st::boxPadding.bottom()));
box->addButton(tr::lng_group_call_mac_settings(), [=] {
Platform::OpenDesktopCapturePrivacySettings();
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}));
#endif // Q_OS_MAC
}
void Panel::chooseShareScreenSource() {
@@ -2221,7 +2152,7 @@ bool Panel::handleClose() {
return false;
}
not_null<Ui::Window*> Panel::window() const {
not_null<Ui::RpWindow*> Panel::window() const {
return _window.window();
}

View File

@@ -63,6 +63,7 @@ class Members;
class Viewport;
enum class PanelMode;
enum class StickedTooltip;
class MicLevelTester;
class Panel final : private Ui::DesktopCapture::ChooseSourceDelegate {
public:
@@ -94,9 +95,8 @@ private:
Activated,
Discarded,
};
class MicLevelTester;
[[nodiscard]] not_null<Ui::Window*> window() const;
[[nodiscard]] not_null<Ui::RpWindow*> window() const;
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
[[nodiscard]] PanelMode mode() const;

View File

@@ -53,6 +53,10 @@ namespace Calls::Group {
namespace {
constexpr auto kDelaysCount = 201;
constexpr auto kMicrophoneTooltipAfterLoudCount = 3;
constexpr auto kDropLoudAfterQuietCount = 5;
constexpr auto kMicrophoneTooltipLevelThreshold = 0.2;
constexpr auto kMicrophoneTooltipCheckInterval = crl::time(500);
#ifdef Q_OS_MAC
constexpr auto kCheckAccessibilityInterval = crl::time(500);
@@ -735,4 +739,31 @@ std::pair<Fn<void()>, rpl::lifetime> ShareInviteLinkAction(
return { std::move(callback), std::move(lifetime) };
}
MicLevelTester::MicLevelTester(Fn<void()> show)
: _show(std::move(show))
, _timer([=] { check(); })
, _tester(
std::make_unique<Webrtc::AudioInputTester>(
Core::App().settings().callAudioBackend(),
Core::App().settings().callInputDeviceId())) {
_timer.callEach(kMicrophoneTooltipCheckInterval);
}
bool MicLevelTester::showTooltip() const {
return (_loudCount >= kMicrophoneTooltipAfterLoudCount);
}
void MicLevelTester::check() {
const auto level = _tester->getAndResetLevel();
if (level >= kMicrophoneTooltipLevelThreshold) {
_quietCount = 0;
if (++_loudCount >= kMicrophoneTooltipAfterLoudCount) {
_show();
}
} else if (_loudCount > 0 && ++_quietCount >= kDropLoudAfterQuietCount) {
_quietCount = 0;
_loudCount = 0;
}
}
} // namespace Calls::Group

View File

@@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/layers/generic_box.h"
namespace Webrtc {
class AudioInputTester;
} // namespace Webrtc
namespace Calls {
class GroupCall;
} // namespace Calls
@@ -24,4 +28,21 @@ void SettingsBox(
Fn<void(object_ptr<Ui::BoxContent>)> showBox,
Fn<void(QString)> showToast);
class MicLevelTester final {
public:
explicit MicLevelTester(Fn<void()> show);
[[nodiscard]] bool showTooltip() const;
private:
void check();
Fn<void()> _show;
base::Timer _timer;
std::unique_ptr<Webrtc::AudioInputTester> _tester;
int _loudCount = 0;
int _quietCount = 0;
};
} // namespace Calls::Group

View File

@@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/group/ui/desktop_capture_choose_source.h"
#include "ui/widgets/window.h"
#include "ui/widgets/rp_window.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h"
@@ -104,7 +104,7 @@ private:
std::unique_ptr<ChooseSourceProcess>> &Map();
const not_null<ChooseSourceDelegate*> _delegate;
const std::unique_ptr<Ui::Window> _window;
const std::unique_ptr<RpWindow> _window;
const std::unique_ptr<ScrollArea> _scroll;
const not_null<RpWidget*> _inner;
const not_null<RpWidget*> _bottom;
@@ -250,7 +250,7 @@ rpl::lifetime &Source::lifetime() {
ChooseSourceProcess::ChooseSourceProcess(
not_null<ChooseSourceDelegate*> delegate)
: _delegate(delegate)
, _window(std::make_unique<Ui::Window>())
, _window(std::make_unique<RpWindow>())
, _scroll(std::make_unique<ScrollArea>(_window->body()))
, _inner(_scroll->setOwnedWidget(object_ptr<RpWidget>(_scroll.get())))
, _bottom(CreateChild<RpWidget>(_window->body().get()))
@@ -423,7 +423,7 @@ void ChooseSourceProcess::setupPanel() {
+ rows * st::desktopCaptureSourceSize.height()
+ (rows - 1) * skips.height()
+ margins.bottom();
_inner->resize(width, std::max(height, innerHeight));
_inner->resize(width, innerHeight);
}, _inner->lifetime());
if (const auto parent = _delegate->chooseSourceParent()) {

View File

@@ -0,0 +1,49 @@
/*
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 "chat_helpers/bot_command.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_peer.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "history/history_item.h"
namespace Bot {
QString WrapCommandInChat(
not_null<PeerData*> peer,
const QString &command,
const FullMsgId &context) {
auto result = command;
if (const auto item = peer->owner().message(context)) {
if (const auto user = item->fromOriginal()->asUser()) {
return WrapCommandInChat(peer, command, user);
}
}
return result;
}
QString WrapCommandInChat(
not_null<PeerData*> peer,
const QString &command,
not_null<UserData*> bot) {
if (!bot->isBot() || bot->username.isEmpty()) {
return command;
}
const auto botStatus = peer->isChat()
? peer->asChat()->botStatus
: peer->isMegagroup()
? peer->asChannel()->mgInfo->botStatus
: -1;
return ((command.indexOf('@') < 2) && (botStatus == 0 || botStatus == 2))
? command + '@' + bot->username
: command;
}
} // namespace Bot

View File

@@ -0,0 +1,31 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
class PeerData;
class UserData;
namespace Bot {
struct SendCommandRequest {
not_null<PeerData*> peer;
QString command;
FullMsgId context;
int replyTo = 0;
};
[[nodiscard]] QString WrapCommandInChat(
not_null<PeerData*> peer,
const QString &command,
const FullMsgId &context);
[[nodiscard]] QString WrapCommandInChat(
not_null<PeerData*> peer,
const QString &command,
not_null<UserData*> bot);
} // namespace Bot

View File

@@ -7,11 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "chat_helpers/bot_keyboard.h"
#include "core/click_handler_types.h"
#include "history/history.h"
#include "history/history_item_components.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "ui/cached_round_corners.h"
#include "facades.h"
#include "styles/style_widgets.h"
@@ -98,9 +100,11 @@ int Style::minButtonWidth(HistoryMessageMarkupButton::Type type) const {
} // namespace
BotKeyboard::BotKeyboard(not_null<Main::Session*> session, QWidget *parent)
BotKeyboard::BotKeyboard(
not_null<Window::SessionController*> controller,
QWidget *parent)
: TWidget(parent)
, _session(session)
, _controller(controller)
, _st(&st::botKbButton) {
setGeometry(0, 0, _st->margin, st::botKbScroll.deltat);
_height = st::botKbScroll.deltat;
@@ -137,7 +141,12 @@ void BotKeyboard::mouseReleaseEvent(QMouseEvent *e) {
updateSelected();
if (ClickHandlerPtr activated = ClickHandler::unpressed()) {
ActivateClickHandler(window(), activated, e->button());
ActivateClickHandler(window(), activated, {
e->button(),
QVariant::fromValue(ClickHandlerContext{
.sessionWindow = base::make_weak(_controller.get()),
})
});
}
}
@@ -151,32 +160,50 @@ void BotKeyboard::leaveEventHook(QEvent *e) {
}
bool BotKeyboard::moderateKeyActivate(int key) {
if (const auto item = _session->data().message(_wasForMsgId)) {
const auto &data = _controller->session().data();
const auto botCommand = [](int key) {
if (key == Qt::Key_Q || key == Qt::Key_6) {
return u"/translate"_q;
} else if (key == Qt::Key_W || key == Qt::Key_5) {
return u"/eng"_q;
} else if (key == Qt::Key_3) {
return u"/pattern"_q;
} else if (key == Qt::Key_4) {
return u"/abuse"_q;
} else if (key == Qt::Key_0 || key == Qt::Key_E || key == Qt::Key_9) {
return u"/undo"_q;
} else if (key == Qt::Key_Plus
|| key == Qt::Key_QuoteLeft
|| key == Qt::Key_7) {
return u"/next"_q;
} else if (key == Qt::Key_Period
|| key == Qt::Key_S
|| key == Qt::Key_8) {
return u"/stats"_q;
}
return QString();
};
if (const auto item = data.message(_wasForMsgId)) {
if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
if (key >= Qt::Key_1 && key <= Qt::Key_2) {
const auto index = int(key - Qt::Key_1);
if (!markup->rows.empty()
&& index >= 0
&& index < int(markup->rows.front().size())) {
App::activateBotCommand(item, 0, index);
App::activateBotCommand(_controller, item, 0, index);
return true;
}
} else if (const auto user = item->history()->peer->asUser()) {
if (user->isBot() && item->from() == user) {
if (key == Qt::Key_Q || key == Qt::Key_6) {
App::sendBotCommand(user, user, qsl("/translate"));
} else if (key == Qt::Key_W || key == Qt::Key_5) {
App::sendBotCommand(user, user, qsl("/eng"));
} else if (key == Qt::Key_3) {
App::sendBotCommand(user, user, qsl("/pattern"));
} else if (key == Qt::Key_4) {
App::sendBotCommand(user, user, qsl("/abuse"));
} else if (key == Qt::Key_0 || key == Qt::Key_E || key == Qt::Key_9) {
App::sendBotCommand(user, user, qsl("/undo"));
} else if (key == Qt::Key_Plus || key == Qt::Key_QuoteLeft || key == Qt::Key_7) {
App::sendBotCommand(user, user, qsl("/next"));
} else if (key == Qt::Key_Period || key == Qt::Key_S || key == Qt::Key_8) {
App::sendBotCommand(user, user, qsl("/stats"));
const auto command = botCommand(key);
if (!command.isEmpty()) {
_sendCommandRequests.fire({
.peer = user,
.command = command,
.context = item->fullId(),
});
}
return true;
}
@@ -215,9 +242,9 @@ bool BotKeyboard::updateMarkup(HistoryItem *to, bool force) {
_wasForMsgId = FullMsgId(to->channelId(), to->id);
auto markupFlags = to->replyKeyboardFlags();
_forceReply = markupFlags & MTPDreplyKeyboardMarkup_ClientFlag::f_force_reply;
_maximizeSize = !(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_resize);
_singleUse = _forceReply || (markupFlags & MTPDreplyKeyboardMarkup::Flag::f_single_use);
_forceReply = markupFlags & ReplyMarkupFlag::ForceReply;
_maximizeSize = !(markupFlags & ReplyMarkupFlag::Resize);
_singleUse = _forceReply || (markupFlags & ReplyMarkupFlag::SingleUse);
if (const auto markup = to->Get<HistoryMessageReplyMarkup>()) {
_placeholder = markup->placeholder;
@@ -317,4 +344,9 @@ void BotKeyboard::updateSelected() {
}
}
auto BotKeyboard::sendCommandRequests() const
-> rpl::producer<Bot::SendCommandRequest> {
return _sendCommandRequests.events();
}
BotKeyboard::~BotKeyboard() = default;

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "ui/widgets/tooltip.h"
#include "chat_helpers/bot_command.h"
class ReplyKeyboard;
@@ -15,16 +16,18 @@ namespace style {
struct BotKeyboardButton;
} // namespace style
namespace Main {
class Session;
} // namespace Main
namespace Window {
class SessionController;
} // namespace Window
class BotKeyboard
: public TWidget
, public Ui::AbstractTooltipShower
, public ClickHandlerHost {
public:
BotKeyboard(not_null<Main::Session*> session, QWidget *parent);
BotKeyboard(
not_null<Window::SessionController*> controller,
QWidget *parent);
bool moderateKeyActivate(int index);
@@ -60,6 +63,8 @@ public:
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
rpl::producer<Bot::SendCommandRequest> sendCommandRequests() const;
~BotKeyboard();
protected:
@@ -78,7 +83,7 @@ private:
void updateStyle(int newWidth);
void clearSelection();
const not_null<Main::Session*> _session;
const not_null<Window::SessionController*> _controller;
FullMsgId _wasForMsgId;
QString _placeholder;
int _height = 0;
@@ -90,6 +95,8 @@ private:
QPoint _lastMousePos;
std::unique_ptr<ReplyKeyboard> _impl;
rpl::event_stream<Bot::SendCommandRequest> _sendCommandRequests;
const style::BotKeyboardButton *_st = nullptr;
};

View File

@@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/ui_utility.h"
#include "ui/cached_round_corners.h"
#include "lang/lang_keys.h"
#include "layout/layout_utils.h"
#include "layout/layout_position.h"
#include "emoji_suggestions_data.h"
#include "emoji_suggestions_helper.h"
#include "main/main_session.h"

View File

@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document_media.h"
#include "data/stickers/data_stickers.h"
#include "chat_helpers/send_context_menu.h" // SendMenu::FillSendMenu
#include "core/click_handler_types.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/popup_menu.h"
@@ -27,12 +28,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "inline_bots/inline_bot_result.h"
#include "storage/localstorage.h"
#include "lang/lang_keys.h"
#include "layout/layout_utils.h"
#include "layout/layout_position.h"
#include "mainwindow.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "history/view/history_view_cursor_state.h"
#include "app.h"
#include "storage/storage_account.h" // Account::writeSavedGifs
#include "styles/style_chat_helpers.h"
@@ -202,6 +202,11 @@ GifsListWidget::GifsListWidget(
) | rpl::start_with_next([=](const QSize &s) {
_mosaic.setFullWidth(s.width());
}, lifetime());
_mosaic.setOffset(
st::inlineResultsLeft - st::roundRadiusSmall,
st::stickerPanPadding);
_mosaic.setRightSkip(st::inlineResultsSkip);
}
rpl::producer<TabbedSelector::FileChosen> GifsListWidget::fileChosen() const {
@@ -337,12 +342,15 @@ void GifsListWidget::paintInlineItems(Painter &p, QRect clip) {
using namespace InlineBots::Layout;
PaintContext context(crl::now(), false, gifPaused, false);
_mosaic.paint(
p,
st::stickerPanPadding,
st::inlineResultsLeft - st::roundRadiusSmall,
clip,
context);
auto paintItem = [&](not_null<const ItemBase*> item, QPoint point) {
p.translate(point.x(), point.y());
item->paint(
p,
clip.translated(-point),
&context);
p.translate(-point.x(), -point.y());
};
_mosaic.paint(std::move(paintItem), clip);
}
void GifsListWidget::mousePressEvent(QMouseEvent *e) {
@@ -407,7 +415,12 @@ void GifsListWidget::mouseReleaseEvent(QMouseEvent *e) {
if (dynamic_cast<InlineBots::Layout::SendClickHandler*>(activated.get())) {
selectInlineResult(_selected, {});
} else {
ActivateClickHandler(window(), activated, e->button());
ActivateClickHandler(window(), activated, {
e->button(),
QVariant::fromValue(ClickHandlerContext{
.sessionWindow = base::make_weak(controller().get()),
})
});
}
}
@@ -520,7 +533,7 @@ void GifsListWidget::refreshSavedGifs() {
return layoutPrepareSavedGif(gif);
}) | ranges::views::filter([](const LayoutItem *item) {
return item != nullptr;
}) | ranges::to_vector;
}) | ranges::to<std::vector<not_null<LayoutItem*>>>;
_mosaic.addItems(layouts);
}
@@ -610,7 +623,9 @@ void GifsListWidget::deleteUnusedInlineLayouts() {
}
void GifsListWidget::preloadImages() {
_mosaic.preloadImages();
_mosaic.forEach([](not_null<const LayoutItem*> item) {
item->preload();
});
}
void GifsListWidget::switchToSavedGifs() {
@@ -645,10 +660,11 @@ int GifsListWidget::refreshInlineRows(const InlineCacheEntry *entry, bool result
return layoutPrepareInlineResult(r.get());
}) | ranges::views::filter([](const LayoutItem *item) {
return item != nullptr;
}) | ranges::to_vector;
}) | ranges::to<std::vector<not_null<LayoutItem*>>>;
_mosaic.addItems(resultLayouts);
added = resultLayouts.size();
preloadImages();
}
resizeToWidth(width());
@@ -661,7 +677,11 @@ int GifsListWidget::refreshInlineRows(const InlineCacheEntry *entry, bool result
}
int GifsListWidget::validateExistingInlineRows(const InlineResults &results) {
const auto until = _mosaic.validateExistingRows(results);
const auto until = _mosaic.validateExistingRows([&](
not_null<const LayoutItem*> item,
int untilIndex) {
return item->getResult() != results[untilIndex].get();
}, results.size());
if (_mosaic.empty()) {
_inlineWithThumb = false;
@@ -853,10 +873,12 @@ void GifsListWidget::updateSelected() {
}
const auto p = mapFromGlobal(_lastMousePos);
const auto sx = (rtl() ? width() - p.x() : p.x())
- (st::inlineResultsLeft - st::roundRadiusSmall);
const auto sy = p.y() - st::stickerPanPadding;
const auto &[link, item, selected] = _mosaic.findByPoint({ sx, sy });
const auto sx = rtl() ? (width() - p.x()) : p.x();
const auto sy = p.y();
const auto &[index, exact, relative] = _mosaic.findByPoint({ sx, sy });
const auto selected = exact ? index : -1;
const auto item = exact ? _mosaic.itemAt(selected).get() : nullptr;
const auto link = exact ? item->getState(relative, {}).link : nullptr;
if (_selected != selected) {
if (const auto s = _mosaic.maybeItemAt(_selected)) {

View File

@@ -10,8 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/tabbed_selector.h"
#include "base/timer.h"
#include "inline_bots/inline_bot_layout_item.h"
#include "inline_bots/inline_results_mosaic_layout.h"
#include "app.h"
#include "layout/layout_mosaic.h"
#include <QtCore/QTimer>
@@ -167,7 +166,7 @@ private:
Footer *_footer = nullptr;
InlineBots::Layout::MosaicLayout _mosaic;
Mosaic::Layout::MosaicLayout<LayoutItem> _mosaic;
int _selected = -1;
int _pressed = -1;

View File

@@ -14,6 +14,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_schedule_box.h"
#include "lang/lang_keys.h"
#include "ui/widgets/popup_menu.h"
#include "data/data_peer.h"
#include "main/main_session.h"
#include "apiwrap.h"
#include <QtWidgets/QApplication>
@@ -133,4 +136,45 @@ void SetupMenuAndShortcuts(
}, button->lifetime());
}
void SetupUnreadMentionsMenu(
not_null<Ui::RpWidget*> button,
Fn<PeerData*()> currentPeer) {
struct State {
base::unique_qptr<Ui::PopupMenu> menu;
base::flat_set<not_null<PeerData*>> sentForPeers;
};
const auto state = std::make_shared<State>();
const auto showMenu = [=] {
const auto peer = currentPeer();
if (!peer) {
return;
}
state->menu = base::make_unique_q<Ui::PopupMenu>(button);
const auto text = tr::lng_context_mark_read_mentions_all(tr::now);
state->menu->addAction(text, [=] {
if (!state->sentForPeers.emplace(peer).second) {
return;
}
peer->session().api().request(MTPmessages_ReadMentions(
peer->input
)).done([=](const MTPmessages_AffectedHistory &result) {
state->sentForPeers.remove(peer);
peer->session().api().applyAffectedHistory(peer, result);
}).fail([=](const MTP::Error &error) {
state->sentForPeers.remove(peer);
}).send();
});
state->menu->popup(QCursor::pos());
};
base::install_event_filter(button, [=](not_null<QEvent*> e) {
if (e->type() == QEvent::ContextMenu) {
showMenu();
return base::EventFilterResult::Cancel;
}
return base::EventFilterResult::Continue;
});
}
} // namespace SendMenu

View File

@@ -50,4 +50,8 @@ void SetupMenuAndShortcuts(
Fn<void()> silent,
Fn<void()> schedule);
void SetupUnreadMentionsMenu(
not_null<Ui::RpWidget*> button,
Fn<PeerData*()> currentPeer);
} // namespace SendMenu

View File

@@ -21,7 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h"
#include "base/call_delayed.h"
#include "apiwrap.h"
#include "app.h"
#include "styles/style_chat.h"
#include <QtCore/QBuffer>

View File

@@ -14,7 +14,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h"
#include "mainwindow.h"
#include "core/application.h"
#include "app.h"
#include "styles/style_chat_helpers.h"
namespace ChatHelpers {

View File

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

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "core/application.h"
#include "data/data_abstract_structure.h"
#include "data/data_photo.h"
#include "data/data_document.h"
#include "data/data_session.h"
@@ -64,6 +65,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_options.h"
#include "ui/emoji_config.h"
#include "ui/effects/animations.h"
#include "ui/cached_round_corners.h"
#include "storage/serialize_common.h"
#include "storage/storage_domain.h"
#include "storage/storage_databases.h"
@@ -172,7 +174,8 @@ Application::~Application() {
Ui::Emoji::Clear();
Media::Clip::Finish();
App::deinitMedia();
Ui::FinishCachedCorners();
Data::clearGlobalStructures();
Window::Theme::Uninitialize();
@@ -217,6 +220,7 @@ void Application::run() {
style::startManager(cScale());
Ui::InitTextOptions();
Ui::StartCachedCorners();
Ui::Emoji::Init();
startEmojiImageLoader();
startSystemDarkModeViewer();
@@ -262,14 +266,13 @@ void Application::run() {
// Depend on activeWindow() for now :(
startShortcuts();
App::initMedia();
startDomain();
_window->widget()->show();
const auto currentGeometry = _window->widget()->geometry();
_mediaView = std::make_unique<Media::View::OverlayWidget>();
_window->widget()->setGeometry(currentGeometry);
_window->widget()->Ui::RpWidget::setGeometry(currentGeometry);
DEBUG_LOG(("Application Info: showing."));
_window->finishFirstShow();
@@ -392,15 +395,6 @@ bool Application::hideMediaView() {
return false;
}
PeerData *Application::ui_getPeerForMouseAction() {
if (_mediaView && !_mediaView->isHidden()) {
return _mediaView->ui_getPeerForMouseAction();
} else if (const auto m = App::main()) { // multi good
return m->ui_getPeerForMouseAction();
}
return nullptr;
}
bool Application::eventFilter(QObject *object, QEvent *e) {
switch (e->type()) {
case QEvent::KeyPress:

View File

@@ -143,7 +143,6 @@ public:
// Media view interface.
void checkMediaViewActivation();
bool hideMediaView();
[[nodiscard]] PeerData *ui_getPeerForMouseAction();
[[nodiscard]] QPoint getPointForCallPanelCenter() const;
[[nodiscard]] QImage logo() const {

View File

@@ -88,6 +88,21 @@ std::map<int, const char*> BetaLogs() {
"- Fix Direct3D acceleration on basic Windows 7 setup.\n"
},
{
2009004,
"- Choose one from dozens of new gorgeous animated backgrounds"
" in Chat Settings > Chat background.\n"
},
{
2009005,
"- Tile chat background patterns horizontally.\n"
"- Fix a rare crash in spellchecker on Windows.\n"
"- Fix animated chat backgrounds in Saved Messages.\n"
"- Fix \"Sorry, group is inaccessible\" message in scheduled voice chats.\n",
},
};
};

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