Merge remote-tracking branch 'tdesktop/dev' into dev
|
|
@ -1,86 +0,0 @@
|
||||||
@echo off
|
|
||||||
|
|
||||||
IF "%BUILD_DIR%"=="" SET BUILD_DIR=C:\TBuild
|
|
||||||
SET LIB_DIR=%BUILD_DIR%\Libraries
|
|
||||||
SET SRC_DIR=%BUILD_DIR%\tdesktop
|
|
||||||
SET QT_VERSION=5_6_2
|
|
||||||
|
|
||||||
call:configureBuild
|
|
||||||
call:getDependencies
|
|
||||||
call:setupGYP
|
|
||||||
cd %SRC_DIR%
|
|
||||||
|
|
||||||
echo Finished!
|
|
||||||
|
|
||||||
GOTO:EOF
|
|
||||||
|
|
||||||
:: FUNCTIONS
|
|
||||||
:logInfo
|
|
||||||
echo [INFO] %~1
|
|
||||||
GOTO:EOF
|
|
||||||
|
|
||||||
:logError
|
|
||||||
echo [ERROR] %~1
|
|
||||||
GOTO:EOF
|
|
||||||
|
|
||||||
:getDependencies
|
|
||||||
call:logInfo "Clone dependencies repository"
|
|
||||||
git clone -q --depth 1 --branch master https://github.com/telegramdesktop/dependencies_windows.git %LIB_DIR%
|
|
||||||
cd %LIB_DIR%
|
|
||||||
|
|
||||||
git clone --depth 1 --branch 0.9.1 https://github.com/ericniebler/range-v3
|
|
||||||
|
|
||||||
if exist prepare.bat (
|
|
||||||
call prepare.bat
|
|
||||||
) else (
|
|
||||||
call:logError "Error cloning dependencies, trying again"
|
|
||||||
rmdir %LIB_DIR% /S /Q
|
|
||||||
call:getDependencies
|
|
||||||
)
|
|
||||||
GOTO:EOF
|
|
||||||
|
|
||||||
:setupGYP
|
|
||||||
call:logInfo "Setup GYP/Ninja and generate VS solution"
|
|
||||||
cd %LIB_DIR%
|
|
||||||
git clone https://github.com/telegramdesktop/gyp.git
|
|
||||||
cd gyp
|
|
||||||
git checkout tdesktop
|
|
||||||
SET PATH=%PATH%;%BUILD_DIR%\Libraries\gyp;%BUILD_DIR%\Libraries\ninja;
|
|
||||||
cd %SRC_DIR%
|
|
||||||
git submodule init
|
|
||||||
git submodule update
|
|
||||||
cd %SRC_DIR%\Telegram
|
|
||||||
call gyp\refresh.bat --api-id 17349 --api-hash 344583e45741c457fe1862106095a5eb --ci-build
|
|
||||||
GOTO:EOF
|
|
||||||
|
|
||||||
:configureBuild
|
|
||||||
call:logInfo "Configuring build"
|
|
||||||
call:logInfo "Build version: %BUILD_VERSION%"
|
|
||||||
set TDESKTOP_BUILD_DEFINES=
|
|
||||||
|
|
||||||
echo %BUILD_VERSION% | findstr /C:"disable_register_custom_scheme">nul && (
|
|
||||||
set TDESKTOP_BUILD_DEFINES=%TDESKTOP_BUILD_DEFINES%,TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME
|
|
||||||
)
|
|
||||||
|
|
||||||
echo %BUILD_VERSION% | findstr /C:"disable_crash_reports">nul && (
|
|
||||||
set TDESKTOP_BUILD_DEFINES=%TDESKTOP_BUILD_DEFINES%,DESKTOP_APP_DISABLE_CRASH_REPORTS
|
|
||||||
)
|
|
||||||
|
|
||||||
echo %BUILD_VERSION% | findstr /C:"disable_network_proxy">nul && (
|
|
||||||
set TDESKTOP_BUILD_DEFINES=%TDESKTOP_BUILD_DEFINES%,TDESKTOP_DISABLE_NETWORK_PROXY
|
|
||||||
)
|
|
||||||
|
|
||||||
echo %BUILD_VERSION% | findstr /C:"disable_desktop_file_generation">nul && (
|
|
||||||
set TDESKTOP_BUILD_DEFINES=%TDESKTOP_BUILD_DEFINES%,TDESKTOP_DISABLE_DESKTOP_FILE_GENERATION
|
|
||||||
)
|
|
||||||
|
|
||||||
echo %BUILD_VERSION% | findstr /C:"disable_gtk_integration">nul && (
|
|
||||||
set TDESKTOP_BUILD_DEFINES=%TDESKTOP_BUILD_DEFINES%,TDESKTOP_DISABLE_GTK_INTEGRATION
|
|
||||||
)
|
|
||||||
|
|
||||||
if not "%TDESKTOP_BUILD_DEFINES%" == "" (
|
|
||||||
set "TDESKTOP_BUILD_DEFINES=%TDESKTOP_BUILD_DEFINES:~1%"
|
|
||||||
)
|
|
||||||
|
|
||||||
call:logInfo "Build Defines: %TDESKTOP_BUILD_DEFINES%"
|
|
||||||
GOTO:EOF
|
|
||||||
2
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
|
|
@ -8,7 +8,7 @@ about: Report errors or unexpected behavior.
|
||||||
Thanks for reporting issues of Telegram Desktop!
|
Thanks for reporting issues of Telegram Desktop!
|
||||||
|
|
||||||
To make it easier for us to help you please enter detailed information below.
|
To make it easier for us to help you please enter detailed information below.
|
||||||
-->
|
-->
|
||||||
### Steps to reproduce
|
### Steps to reproduce
|
||||||
1.
|
1.
|
||||||
2.
|
2.
|
||||||
|
|
|
||||||
103
.github/workflows/issue_closer.yml
vendored
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
name: Issue closer.
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: opened
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
comment:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Get the latest version.
|
||||||
|
run: |
|
||||||
|
tag=$(git ls-remote --tags git://github.com/$GITHUB_REPOSITORY | cut -f 2 | tail -n1)
|
||||||
|
echo $tag
|
||||||
|
echo ::set-env name=LATEST_TAG::$tag
|
||||||
|
|
||||||
|
- name: Check a version from an issue.
|
||||||
|
uses: actions/github-script@0.4.0
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
script: |
|
||||||
|
let errorStr = "Version not found.";
|
||||||
|
|
||||||
|
let item1 = "Version of Telegram Desktop";
|
||||||
|
let item2 = "Used theme";
|
||||||
|
let body = context.payload.issue.body;
|
||||||
|
|
||||||
|
console.log("Body of issue:\n" + body);
|
||||||
|
let index1 = body.indexOf(item1) + item1.length;
|
||||||
|
let index2 = body.indexOf(item2);
|
||||||
|
index2 = (index2 == -1) ? Number.MAX_SAFE_INTEGER : index2;
|
||||||
|
|
||||||
|
console.log("Index 1: " + index1);
|
||||||
|
console.log("Index 2: " + index2);
|
||||||
|
|
||||||
|
if (index1 == -1) {
|
||||||
|
console.log(errorStr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseVersion(str) {
|
||||||
|
let pattern = /[0-9]\.[0-9][0-9.]{0,}/g;
|
||||||
|
return str.match(pattern);
|
||||||
|
}
|
||||||
|
function firstNum(version) {
|
||||||
|
return version[0].split(".")[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
let issueVer = parseVersion(body.substring(index1, index2));
|
||||||
|
|
||||||
|
if (issueVer == undefined) {
|
||||||
|
console.log(errorStr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("Version from issue: " + issueVer[0]);
|
||||||
|
|
||||||
|
let latestVer = parseVersion(process.env.LATEST_TAG);
|
||||||
|
|
||||||
|
if (latestVer == undefined) {
|
||||||
|
console.log(errorStr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("Version from tags: " + latestVer[0]);
|
||||||
|
|
||||||
|
let issueNum = firstNum(issueVer);
|
||||||
|
let latestNum = firstNum(latestVer);
|
||||||
|
|
||||||
|
if (issueNum <= latestNum && issueNum < 5) {
|
||||||
|
console.log("Seems the version of this issue is fine!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let message = `
|
||||||
|
Sorry, but according to the version you specify in this issue, \
|
||||||
|
you are using the [Telegram for macOS](https://macos.telegram.org), \
|
||||||
|
not the [Telegram Desktop](https://desktop.telegram.org).
|
||||||
|
You can report your issue to [the group](https://t.me/macswift) \
|
||||||
|
or to [the repository of Telegram for macOS](https://github.com/overtake/TelegramSwift).
|
||||||
|
|
||||||
|
If I made a mistake and closed your issue wrongly, please reopen it. Thanks!
|
||||||
|
`;
|
||||||
|
|
||||||
|
let params = {
|
||||||
|
owner: context.issue.owner,
|
||||||
|
repo: context.issue.repo,
|
||||||
|
issue_number: context.issue.number
|
||||||
|
};
|
||||||
|
|
||||||
|
github.issues.createComment({
|
||||||
|
...params,
|
||||||
|
body: message
|
||||||
|
});
|
||||||
|
|
||||||
|
github.issues.addLabels({
|
||||||
|
...params,
|
||||||
|
labels: ['TG macOS Swift']
|
||||||
|
});
|
||||||
|
|
||||||
|
github.issues.update({
|
||||||
|
...params,
|
||||||
|
state: 'closed'
|
||||||
|
});
|
||||||
|
|
||||||
4
.github/workflows/mac.yml
vendored
|
|
@ -102,7 +102,7 @@ jobs:
|
||||||
|
|
||||||
git clone $GIT/desktop-app/zlib.git
|
git clone $GIT/desktop-app/zlib.git
|
||||||
cd zlib
|
cd zlib
|
||||||
CFLAGS="$MIN_MAC $UNGUARDED" LDFLAGS="$MIN_MAC" ./configure
|
CFLAGS="$MIN_MAC $UNGUARDED" LDFLAGS="$MIN_MAC" ./configure --prefix=$PREFIX
|
||||||
make -j$(nproc)
|
make -j$(nproc)
|
||||||
sudo make install
|
sudo make install
|
||||||
|
|
||||||
|
|
@ -429,4 +429,4 @@ jobs:
|
||||||
name: Upload artifact.
|
name: Upload artifact.
|
||||||
with:
|
with:
|
||||||
name: Telegram
|
name: Telegram
|
||||||
path: $REPO_NAME\out\Debug\artifact\
|
path: ${{ env.REPO_NAME }}/out/Debug/artifact/
|
||||||
|
|
|
||||||
11
.github/workflows/win.yml
vendored
|
|
@ -23,7 +23,6 @@ jobs:
|
||||||
env:
|
env:
|
||||||
SDK: "10.0.18362.0"
|
SDK: "10.0.18362.0"
|
||||||
VC: "call vcvars32.bat && cd Libraries"
|
VC: "call vcvars32.bat && cd Libraries"
|
||||||
PY2: 'C:\hostedtoolcache\windows\Python\2.7.16\x64'
|
|
||||||
GIT: "https://github.com"
|
GIT: "https://github.com"
|
||||||
QT: "5_12_5"
|
QT: "5_12_5"
|
||||||
OPENSSL_VER: "1_1_1"
|
OPENSSL_VER: "1_1_1"
|
||||||
|
|
@ -186,6 +185,16 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
cd %LibrariesPath%
|
cd %LibrariesPath%
|
||||||
|
|
||||||
|
echo Find any version of Python 2.
|
||||||
|
for /D %%a in (C:\hostedtoolcache\windows\Python\2.*) do (
|
||||||
|
SET PY2=%%a\x64
|
||||||
|
)
|
||||||
|
IF [%PY2%] == [] (
|
||||||
|
echo Python 2 is not found.
|
||||||
|
exit 1
|
||||||
|
)
|
||||||
|
echo Found %PY2%.
|
||||||
|
|
||||||
git clone %GIT%/telegramdesktop/gyp.git
|
git clone %GIT%/telegramdesktop/gyp.git
|
||||||
cd gyp
|
cd gyp
|
||||||
SET PATH=%PY2%;%cd%;%PATH%
|
SET PATH=%PY2%;%cd%;%PATH%
|
||||||
|
|
|
||||||
3
.gitmodules
vendored
|
|
@ -28,9 +28,6 @@
|
||||||
[submodule "Telegram/lib_base"]
|
[submodule "Telegram/lib_base"]
|
||||||
path = Telegram/lib_base
|
path = Telegram/lib_base
|
||||||
url = https://github.com/desktop-app/lib_base.git
|
url = https://github.com/desktop-app/lib_base.git
|
||||||
[submodule "Telegram/gyp/helpers"]
|
|
||||||
path = Telegram/gyp/helpers
|
|
||||||
url = https://github.com/desktop-app/gyp_helpers.git
|
|
||||||
[submodule "Telegram/codegen"]
|
[submodule "Telegram/codegen"]
|
||||||
path = Telegram/codegen
|
path = Telegram/codegen
|
||||||
url = https://github.com/desktop-app/codegen.git
|
url = https://github.com/desktop-app/codegen.git
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,7 @@ buildRange() {
|
||||||
rm -rf *
|
rm -rf *
|
||||||
|
|
||||||
cd "$EXTERNAL"
|
cd "$EXTERNAL"
|
||||||
git clone --depth 1 --branch 0.9.1 https://github.com/ericniebler/range-v3
|
git clone --depth 1 --branch 0.10.0 https://github.com/ericniebler/range-v3
|
||||||
|
|
||||||
cd "$EXTERNAL/range-v3"
|
cd "$EXTERNAL/range-v3"
|
||||||
cp -r * "$RANGE_PATH/"
|
cp -r * "$RANGE_PATH/"
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ travisStartFold() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "travis_fold:start:$NAME"
|
echo "travis_fold:start:$NAME"
|
||||||
sameLineInfoMessage "$TITLE"
|
sameLineInfoMessage "$TITLE"
|
||||||
|
|
||||||
TRAVIS_LAST_FOLD="$NAME"
|
TRAVIS_LAST_FOLD="$NAME"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -524,6 +524,10 @@ PRIVATE
|
||||||
info/media/info_media_widget.h
|
info/media/info_media_widget.h
|
||||||
info/members/info_members_widget.cpp
|
info/members/info_members_widget.cpp
|
||||||
info/members/info_members_widget.h
|
info/members/info_members_widget.h
|
||||||
|
info/polls/info_polls_results_inner_widget.cpp
|
||||||
|
info/polls/info_polls_results_inner_widget.h
|
||||||
|
info/polls/info_polls_results_widget.cpp
|
||||||
|
info/polls/info_polls_results_widget.h
|
||||||
info/profile/info_profile_actions.cpp
|
info/profile/info_profile_actions.cpp
|
||||||
info/profile/info_profile_actions.h
|
info/profile/info_profile_actions.h
|
||||||
info/profile/info_profile_cover.cpp
|
info/profile/info_profile_cover.cpp
|
||||||
|
|
@ -1096,7 +1100,11 @@ elseif (build_osx)
|
||||||
else()
|
else()
|
||||||
set(bundle_identifier "com.tdesktop.Telegram$<$<CONFIG:Debug>:Debug>")
|
set(bundle_identifier "com.tdesktop.Telegram$<$<CONFIG:Debug>:Debug>")
|
||||||
set(bundle_entitlements "Telegram.entitlements")
|
set(bundle_entitlements "Telegram.entitlements")
|
||||||
set(output_name "Telegram")
|
if (LINUX AND DESKTOP_APP_USE_PACKAGED)
|
||||||
|
set(output_name "telegram-desktop")
|
||||||
|
else()
|
||||||
|
set(output_name "Telegram")
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set_target_properties(Telegram PROPERTIES
|
set_target_properties(Telegram PROPERTIES
|
||||||
|
|
@ -1193,3 +1201,17 @@ if ((NOT disable_autoupdate OR NOT LINUX) AND NOT build_macstore AND NOT build_w
|
||||||
set_target_properties(Packer PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
|
set_target_properties(Packer PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (LINUX AND DESKTOP_APP_USE_PACKAGED)
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
install(TARGETS Telegram RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||||
|
install(FILES "Resources/art/icon16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "telegram.png")
|
||||||
|
install(FILES "Resources/art/icon32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "telegram.png")
|
||||||
|
install(FILES "Resources/art/icon48.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48/apps" RENAME "telegram.png")
|
||||||
|
install(FILES "Resources/art/icon64.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/64x64/apps" RENAME "telegram.png")
|
||||||
|
install(FILES "Resources/art/icon128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "telegram.png")
|
||||||
|
install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "telegram.png")
|
||||||
|
install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "telegram.png")
|
||||||
|
install(FILES "../lib/xdg/telegramdesktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications" RENAME "${TDESKTOP_LAUNCHER_BASENAME}.desktop")
|
||||||
|
install(FILES "../lib/xdg/telegramdesktop.appdata.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo" RENAME "${TDESKTOP_LAUNCHER_BASENAME}.appdata.xml")
|
||||||
|
endif()
|
||||||
|
|
|
||||||
BIN
Telegram/Resources/icons/poll_choice_right.png
Normal file
|
After Width: | Height: | Size: 166 B |
BIN
Telegram/Resources/icons/poll_choice_right@2x.png
Normal file
|
After Width: | Height: | Size: 219 B |
BIN
Telegram/Resources/icons/poll_choice_right@3x.png
Normal file
|
After Width: | Height: | Size: 294 B |
BIN
Telegram/Resources/icons/poll_choice_wrong.png
Normal file
|
After Width: | Height: | Size: 157 B |
BIN
Telegram/Resources/icons/poll_choice_wrong@2x.png
Normal file
|
After Width: | Height: | Size: 213 B |
BIN
Telegram/Resources/icons/poll_choice_wrong@3x.png
Normal file
|
After Width: | Height: | Size: 260 B |
BIN
Telegram/Resources/icons/poll_select_check.png
Normal file
|
After Width: | Height: | Size: 249 B |
BIN
Telegram/Resources/icons/poll_select_check@2x.png
Normal file
|
After Width: | Height: | Size: 364 B |
BIN
Telegram/Resources/icons/poll_select_check@3x.png
Normal file
|
After Width: | Height: | Size: 556 B |
|
|
@ -158,6 +158,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_deleted_message" = "Deleted message";
|
"lng_deleted_message" = "Deleted message";
|
||||||
"lng_pinned_message" = "Pinned message";
|
"lng_pinned_message" = "Pinned message";
|
||||||
"lng_pinned_poll" = "Pinned poll";
|
"lng_pinned_poll" = "Pinned poll";
|
||||||
|
"lng_pinned_quiz" = "Pinned quiz";
|
||||||
"lng_pinned_unpin_sure" = "Would you like to unpin this message?";
|
"lng_pinned_unpin_sure" = "Would you like to unpin this message?";
|
||||||
"lng_pinned_pin_sure" = "Would you like to pin this message?";
|
"lng_pinned_pin_sure" = "Would you like to pin this message?";
|
||||||
"lng_pinned_pin" = "Pin";
|
"lng_pinned_pin" = "Pin";
|
||||||
|
|
@ -1294,6 +1295,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
"lng_scheduled_messages" = "Scheduled Messages";
|
"lng_scheduled_messages" = "Scheduled Messages";
|
||||||
"lng_reminder_messages" = "Reminders";
|
"lng_reminder_messages" = "Reminders";
|
||||||
|
"lng_scheduled_date" = "Scheduled for {date}";
|
||||||
|
"lng_scheduled_date_until_online" = "Scheduled until online";
|
||||||
|
"lng_scheduled_send_until_online" = "Send when online";
|
||||||
"lng_scheduled_send_now" = "Send message now?";
|
"lng_scheduled_send_now" = "Send message now?";
|
||||||
"lng_scheduled_send_now_many#one" = "Send {count} message now?";
|
"lng_scheduled_send_now_many#one" = "Send {count} message now?";
|
||||||
"lng_scheduled_send_now_many#other" = "Send {count} messages now?";
|
"lng_scheduled_send_now_many#other" = "Send {count} messages now?";
|
||||||
|
|
@ -1453,6 +1457,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_send_album" = "Send as an album";
|
"lng_send_album" = "Send as an album";
|
||||||
"lng_send_photo" = "Send as a photo";
|
"lng_send_photo" = "Send as a photo";
|
||||||
"lng_send_file" = "Send as a file";
|
"lng_send_file" = "Send as a file";
|
||||||
|
"lng_send_media_invalid_files" = "Sorry, no valid files found.";
|
||||||
|
|
||||||
"lng_forward_choose" = "Choose recipient...";
|
"lng_forward_choose" = "Choose recipient...";
|
||||||
"lng_forward_cant" = "Sorry, no way to forward here :(";
|
"lng_forward_cant" = "Sorry, no way to forward here :(";
|
||||||
|
|
@ -1797,6 +1802,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_restricted_send_inline_all" = "Posting inline content isn't allowed in this group.";
|
"lng_restricted_send_inline_all" = "Posting inline content isn't allowed in this group.";
|
||||||
"lng_restricted_send_polls_all" = "Posting polls isn't allowed in this group.";
|
"lng_restricted_send_polls_all" = "Posting polls isn't allowed in this group.";
|
||||||
|
|
||||||
|
"lng_restricted_send_public_polls" = "Sorry, public polls can't be forwarded to channels.";
|
||||||
|
|
||||||
"lng_exceptions_list_title" = "Exceptions";
|
"lng_exceptions_list_title" = "Exceptions";
|
||||||
"lng_removed_list_title" = "Removed users";
|
"lng_removed_list_title" = "Removed users";
|
||||||
|
|
||||||
|
|
@ -2166,10 +2173,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_launch_exe_dont_ask" = "Don't ask me again";
|
"lng_launch_exe_dont_ask" = "Don't ask me again";
|
||||||
|
|
||||||
"lng_polls_anonymous" = "Anonymous Poll";
|
"lng_polls_anonymous" = "Anonymous Poll";
|
||||||
|
"lng_polls_public" = "Poll";
|
||||||
|
"lng_polls_anonymous_quiz" = "Anonymous Quiz";
|
||||||
|
"lng_polls_public_quiz" = "Quiz";
|
||||||
"lng_polls_closed" = "Final results";
|
"lng_polls_closed" = "Final results";
|
||||||
"lng_polls_votes_count#one" = "{count} vote";
|
"lng_polls_votes_count#one" = "{count} vote";
|
||||||
"lng_polls_votes_count#other" = "{count} votes";
|
"lng_polls_votes_count#other" = "{count} votes";
|
||||||
"lng_polls_votes_none" = "No votes";
|
"lng_polls_votes_none" = "No votes";
|
||||||
|
"lng_polls_answers_count#one" = "{count} answer";
|
||||||
|
"lng_polls_answers_count#other" = "{count} answers";
|
||||||
|
"lng_polls_answers_none" = "No answers";
|
||||||
|
"lng_polls_submit_votes" = "Vote";
|
||||||
|
"lng_polls_view_results" = "View results";
|
||||||
"lng_polls_retract" = "Retract vote";
|
"lng_polls_retract" = "Retract vote";
|
||||||
"lng_polls_stop" = "Stop poll";
|
"lng_polls_stop" = "Stop poll";
|
||||||
"lng_polls_stop_warning" = "If you stop this poll now, nobody will be able to vote in it anymore. This action cannot be undone.";
|
"lng_polls_stop_warning" = "If you stop this poll now, nobody will be able to vote in it anymore. This action cannot be undone.";
|
||||||
|
|
@ -2183,8 +2198,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_polls_create_limit#one" = "You can add {count} more option.";
|
"lng_polls_create_limit#one" = "You can add {count} more option.";
|
||||||
"lng_polls_create_limit#other" = "You can add {count} more options.";
|
"lng_polls_create_limit#other" = "You can add {count} more options.";
|
||||||
"lng_polls_create_maximum" = "You have added the maximum number of options.";
|
"lng_polls_create_maximum" = "You have added the maximum number of options.";
|
||||||
|
"lng_polls_create_settings" = "Settings";
|
||||||
|
"lng_polls_create_hint" = "Tap to select the right option";
|
||||||
|
"lng_polls_create_anonymous" = "Anonymous Votes";
|
||||||
|
"lng_polls_create_multiple_choice" = "Multiple Answers";
|
||||||
|
"lng_polls_create_quiz_mode" = "Quiz Mode";
|
||||||
"lng_polls_create_button" = "Create";
|
"lng_polls_create_button" = "Create";
|
||||||
|
|
||||||
|
"lng_polls_poll_results_title" = "Poll results";
|
||||||
|
"lng_polls_quiz_results_title" = "Quiz results";
|
||||||
|
"lng_polls_show_more#one" = "Show more ({count})";
|
||||||
|
"lng_polls_show_more#other" = "Show more ({count})";
|
||||||
|
"lng_polls_votes_collapse" = "Collapse";
|
||||||
|
|
||||||
"lng_outdated_title" = "PLEASE UPDATE YOUR OPERATING SYSTEM.";
|
"lng_outdated_title" = "PLEASE UPDATE YOUR OPERATING SYSTEM.";
|
||||||
"lng_outdated_soon" = "Otherwise, Telegram Desktop will stop updating on {date}.";
|
"lng_outdated_soon" = "Otherwise, Telegram Desktop will stop updating on {date}.";
|
||||||
"lng_outdated_now" = "So that Telegram Desktop can update to newer versions.";
|
"lng_outdated_now" = "So that Telegram Desktop can update to newer versions.";
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int =
|
||||||
inputMediaGame#d33f43f3 id:InputGame = InputMedia;
|
inputMediaGame#d33f43f3 id:InputGame = InputMedia;
|
||||||
inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia;
|
inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia;
|
||||||
inputMediaGeoLive#ce4e82fd flags:# stopped:flags.0?true geo_point:InputGeoPoint period:flags.1?int = InputMedia;
|
inputMediaGeoLive#ce4e82fd flags:# stopped:flags.0?true geo_point:InputGeoPoint period:flags.1?int = InputMedia;
|
||||||
inputMediaPoll#6b3765b poll:Poll = InputMedia;
|
inputMediaPoll#abe9ca25 flags:# poll:Poll correct_answers:flags.0?Vector<bytes> = InputMedia;
|
||||||
|
|
||||||
inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
|
inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
|
||||||
inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto;
|
inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto;
|
||||||
|
|
@ -351,6 +351,7 @@ updateDeleteScheduledMessages#90866cee peer:Peer messages:Vector<int> = Update;
|
||||||
updateTheme#8216fba3 theme:Theme = Update;
|
updateTheme#8216fba3 theme:Theme = Update;
|
||||||
updateGeoLiveViewed#871fb939 peer:Peer msg_id:int = Update;
|
updateGeoLiveViewed#871fb939 peer:Peer msg_id:int = Update;
|
||||||
updateLoginToken#564fe691 = Update;
|
updateLoginToken#564fe691 = Update;
|
||||||
|
updateMessagePollVote#42f88f2c poll_id:long user_id:int options:Vector<bytes> = Update;
|
||||||
|
|
||||||
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
|
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
|
||||||
|
|
||||||
|
|
@ -546,6 +547,7 @@ keyboardButtonGame#50f41ccf text:string = KeyboardButton;
|
||||||
keyboardButtonBuy#afd93fbb text:string = KeyboardButton;
|
keyboardButtonBuy#afd93fbb text:string = KeyboardButton;
|
||||||
keyboardButtonUrlAuth#10b78d29 flags:# text:string fwd_text:flags.0?string url:string button_id:int = KeyboardButton;
|
keyboardButtonUrlAuth#10b78d29 flags:# text:string fwd_text:flags.0?string url:string button_id:int = KeyboardButton;
|
||||||
inputKeyboardButtonUrlAuth#d02e7fd4 flags:# request_write_access:flags.0?true text:string fwd_text:flags.1?string url:string bot:InputUser = KeyboardButton;
|
inputKeyboardButtonUrlAuth#d02e7fd4 flags:# request_write_access:flags.0?true text:string fwd_text:flags.1?string url:string bot:InputUser = KeyboardButton;
|
||||||
|
keyboardButtonRequestPoll#bbc7515d flags:# quiz:flags.0?Bool text:string = KeyboardButton;
|
||||||
|
|
||||||
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
|
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
|
||||||
|
|
||||||
|
|
@ -1015,11 +1017,11 @@ help.userInfo#1eb3758 message:string entities:Vector<MessageEntity> author:strin
|
||||||
|
|
||||||
pollAnswer#6ca9c2e9 text:string option:bytes = PollAnswer;
|
pollAnswer#6ca9c2e9 text:string option:bytes = PollAnswer;
|
||||||
|
|
||||||
poll#d5529d06 id:long flags:# closed:flags.0?true question:string answers:Vector<PollAnswer> = Poll;
|
poll#d5529d06 id:long flags:# closed:flags.0?true public_voters:flags.1?true multiple_choice:flags.2?true quiz:flags.3?true question:string answers:Vector<PollAnswer> = Poll;
|
||||||
|
|
||||||
pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true option:bytes voters:int = PollAnswerVoters;
|
pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true correct:flags.1?true option:bytes voters:int = PollAnswerVoters;
|
||||||
|
|
||||||
pollResults#5755785a flags:# min:flags.0?true results:flags.1?Vector<PollAnswerVoters> total_voters:flags.2?int = PollResults;
|
pollResults#c87024a2 flags:# min:flags.0?true results:flags.1?Vector<PollAnswerVoters> total_voters:flags.2?int recent_voters:flags.3?Vector<int> = PollResults;
|
||||||
|
|
||||||
chatOnlines#f041e250 onlines:int = ChatOnlines;
|
chatOnlines#f041e250 onlines:int = ChatOnlines;
|
||||||
|
|
||||||
|
|
@ -1077,16 +1079,11 @@ restrictionReason#d072acb4 platform:string reason:string text:string = Restricti
|
||||||
inputTheme#3c5693e9 id:long access_hash:long = InputTheme;
|
inputTheme#3c5693e9 id:long access_hash:long = InputTheme;
|
||||||
inputThemeSlug#f5890df1 slug:string = InputTheme;
|
inputThemeSlug#f5890df1 slug:string = InputTheme;
|
||||||
|
|
||||||
themeDocumentNotModified#483d270c = Theme;
|
|
||||||
theme#28f1114 flags:# creator:flags.0?true default:flags.1?true id:long access_hash:long slug:string title:string document:flags.2?Document settings:flags.3?ThemeSettings installs_count:int = Theme;
|
theme#28f1114 flags:# creator:flags.0?true default:flags.1?true id:long access_hash:long slug:string title:string document:flags.2?Document settings:flags.3?ThemeSettings installs_count:int = Theme;
|
||||||
|
|
||||||
account.themesNotModified#f41eb622 = account.Themes;
|
account.themesNotModified#f41eb622 = account.Themes;
|
||||||
account.themes#7f676421 hash:int themes:Vector<Theme> = account.Themes;
|
account.themes#7f676421 hash:int themes:Vector<Theme> = account.Themes;
|
||||||
|
|
||||||
wallet.liteResponse#764386d7 response:bytes = wallet.LiteResponse;
|
|
||||||
|
|
||||||
wallet.secretSalt#dd484d64 salt:bytes = wallet.KeySecretSalt;
|
|
||||||
|
|
||||||
auth.loginToken#629f1980 expires:int token:bytes = auth.LoginToken;
|
auth.loginToken#629f1980 expires:int token:bytes = auth.LoginToken;
|
||||||
auth.loginTokenMigrateTo#68e9916 dc_id:int token:bytes = auth.LoginToken;
|
auth.loginTokenMigrateTo#68e9916 dc_id:int token:bytes = auth.LoginToken;
|
||||||
auth.loginTokenSuccess#390d5c5e authorization:auth.Authorization = auth.LoginToken;
|
auth.loginTokenSuccess#390d5c5e authorization:auth.Authorization = auth.LoginToken;
|
||||||
|
|
@ -1107,6 +1104,12 @@ themeSettings#9c14984a flags:# base_theme:BaseTheme accent_color:int message_top
|
||||||
|
|
||||||
webPageAttributeTheme#54b56617 flags:# documents:flags.0?Vector<Document> settings:flags.1?ThemeSettings = WebPageAttribute;
|
webPageAttributeTheme#54b56617 flags:# documents:flags.0?Vector<Document> settings:flags.1?ThemeSettings = WebPageAttribute;
|
||||||
|
|
||||||
|
messageUserVote#a28e5559 user_id:int option:bytes date:int = MessageUserVote;
|
||||||
|
messageUserVoteInputOption#36377430 user_id:int date:int = MessageUserVote;
|
||||||
|
messageUserVoteMultiple#e8fe0de user_id:int options:Vector<bytes> date:int = MessageUserVote;
|
||||||
|
|
||||||
|
messages.votesList#823f649 flags:# count:int votes:Vector<MessageUserVote> users:Vector<User> next_offset:flags.0?string = messages.VotesList;
|
||||||
|
|
||||||
---functions---
|
---functions---
|
||||||
|
|
||||||
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
|
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
|
||||||
|
|
@ -1343,6 +1346,7 @@ messages.getScheduledHistory#e2c2685b peer:InputPeer hash:int = messages.Message
|
||||||
messages.getScheduledMessages#bdbb0464 peer:InputPeer id:Vector<int> = messages.Messages;
|
messages.getScheduledMessages#bdbb0464 peer:InputPeer id:Vector<int> = messages.Messages;
|
||||||
messages.sendScheduledMessages#bd38850a peer:InputPeer id:Vector<int> = Updates;
|
messages.sendScheduledMessages#bd38850a peer:InputPeer id:Vector<int> = Updates;
|
||||||
messages.deleteScheduledMessages#59ae2b16 peer:InputPeer id:Vector<int> = Updates;
|
messages.deleteScheduledMessages#59ae2b16 peer:InputPeer id:Vector<int> = Updates;
|
||||||
|
messages.getPollVotes#b86e380e flags:# peer:InputPeer id:int option:flags.0?bytes offset:flags.1?string limit:int = messages.VotesList;
|
||||||
|
|
||||||
updates.getState#edd4882a = updates.State;
|
updates.getState#edd4882a = updates.State;
|
||||||
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
|
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
|
||||||
|
|
@ -1354,7 +1358,7 @@ photos.deletePhotos#87cf7f2f id:Vector<InputPhoto> = Vector<long>;
|
||||||
photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos;
|
photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos;
|
||||||
|
|
||||||
upload.saveFilePart#b304a621 file_id:long file_part:int bytes:bytes = Bool;
|
upload.saveFilePart#b304a621 file_id:long file_part:int bytes:bytes = Bool;
|
||||||
upload.getFile#b15a9afc flags:# precise:flags.0?true location:InputFileLocation offset:int limit:int = upload.File;
|
upload.getFile#b15a9afc flags:# precise:flags.0?true cdn_supported:flags.1?true location:InputFileLocation offset:int limit:int = upload.File;
|
||||||
upload.saveBigFilePart#de7b673d file_id:long file_part:int file_total_parts:int bytes:bytes = Bool;
|
upload.saveBigFilePart#de7b673d file_id:long file_part:int file_total_parts:int bytes:bytes = Bool;
|
||||||
upload.getWebFile#24e6818d location:InputWebFileLocation offset:int limit:int = upload.WebFile;
|
upload.getWebFile#24e6818d location:InputWebFileLocation offset:int limit:int = upload.WebFile;
|
||||||
upload.getCdnFile#2000bcc3 file_token:bytes offset:int limit:int = upload.CdnFile;
|
upload.getCdnFile#2000bcc3 file_token:bytes offset:int limit:int = upload.CdnFile;
|
||||||
|
|
@ -1451,7 +1455,4 @@ langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLangua
|
||||||
folders.editPeerFolders#6847d0ab folder_peers:Vector<InputFolderPeer> = Updates;
|
folders.editPeerFolders#6847d0ab folder_peers:Vector<InputFolderPeer> = Updates;
|
||||||
folders.deleteFolder#1c295881 folder_id:int = Updates;
|
folders.deleteFolder#1c295881 folder_id:int = Updates;
|
||||||
|
|
||||||
wallet.sendLiteRequest#e2c9d33e body:bytes = wallet.LiteResponse;
|
// LAYER 109
|
||||||
wallet.getKeySecretSalt#b57f346 revoke:Bool = wallet.KeySecretSalt;
|
|
||||||
|
|
||||||
// LAYER 108
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||||
ProcessorArchitecture="ARCHITECTURE"
|
ProcessorArchitecture="ARCHITECTURE"
|
||||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||||
Version="1.9.4.0" />
|
Version="1.9.7.0" />
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>Telegram Desktop</DisplayName>
|
<DisplayName>Telegram Desktop</DisplayName>
|
||||||
<PublisherDisplayName>Telegram FZ-LLC</PublisherDisplayName>
|
<PublisherDisplayName>Telegram FZ-LLC</PublisherDisplayName>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
<application>
|
<application>
|
||||||
<!-- Windows 10 -->
|
<!-- Windows 10 -->
|
||||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||||
<!-- Windows 8.1 -->
|
<!-- Windows 8.1 -->
|
||||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||||
<!-- Windows Vista -->
|
<!-- Windows Vista -->
|
||||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||||
<!-- Windows 7 -->
|
<!-- Windows 7 -->
|
||||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||||
<!-- Windows 8 -->
|
<!-- Windows 8 -->
|
||||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||||
</application>
|
</application>
|
||||||
</compatibility>
|
</compatibility>
|
||||||
</assembly>
|
</assembly>
|
||||||
|
|
@ -21,6 +21,7 @@ struct SendOptions {
|
||||||
enum class SendType {
|
enum class SendType {
|
||||||
Normal,
|
Normal,
|
||||||
Scheduled,
|
Scheduled,
|
||||||
|
ScheduledToUser, // For "Send when online".
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SendAction {
|
struct SendAction {
|
||||||
|
|
|
||||||
|
|
@ -5842,12 +5842,24 @@ void ApiWrap::createPoll(
|
||||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto inputFlags = data.quiz()
|
||||||
|
? MTPDinputMediaPoll::Flag::f_correct_answers
|
||||||
|
: MTPDinputMediaPoll::Flag(0);
|
||||||
|
auto correct = QVector<MTPbytes>();
|
||||||
|
for (const auto &answer : data.answers) {
|
||||||
|
if (answer.correct) {
|
||||||
|
correct.push_back(MTP_bytes(answer.option));
|
||||||
|
}
|
||||||
|
}
|
||||||
const auto replyTo = action.replyTo;
|
const auto replyTo = action.replyTo;
|
||||||
history->sendRequestId = request(MTPmessages_SendMedia(
|
history->sendRequestId = request(MTPmessages_SendMedia(
|
||||||
MTP_flags(sendFlags),
|
MTP_flags(sendFlags),
|
||||||
peer->input,
|
peer->input,
|
||||||
MTP_int(replyTo),
|
MTP_int(replyTo),
|
||||||
MTP_inputMediaPoll(PollDataToMTP(&data)),
|
MTP_inputMediaPoll(
|
||||||
|
MTP_flags(inputFlags),
|
||||||
|
PollDataToMTP(&data),
|
||||||
|
MTP_vector<MTPbytes>(correct)),
|
||||||
MTP_string(),
|
MTP_string(),
|
||||||
MTP_long(rand_value<uint64>()),
|
MTP_long(rand_value<uint64>()),
|
||||||
MTPReplyMarkup(),
|
MTPReplyMarkup(),
|
||||||
|
|
@ -5879,13 +5891,13 @@ void ApiWrap::sendPollVotes(
|
||||||
const auto hideSending = [=] {
|
const auto hideSending = [=] {
|
||||||
if (showSending) {
|
if (showSending) {
|
||||||
if (const auto item = _session->data().message(itemId)) {
|
if (const auto item = _session->data().message(itemId)) {
|
||||||
poll->sendingVote = QByteArray();
|
poll->sendingVotes.clear();
|
||||||
_session->data().requestItemRepaint(item);
|
_session->data().requestItemRepaint(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (showSending) {
|
if (showSending) {
|
||||||
poll->sendingVote = options.front();
|
poll->sendingVotes = options;
|
||||||
_session->data().requestItemRepaint(item);
|
_session->data().requestItemRepaint(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5921,12 +5933,24 @@ void ApiWrap::closePoll(not_null<HistoryItem*> item) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto inputFlags = poll->quiz()
|
||||||
|
? MTPDinputMediaPoll::Flag::f_correct_answers
|
||||||
|
: MTPDinputMediaPoll::Flag(0);
|
||||||
|
auto correct = QVector<MTPbytes>();
|
||||||
|
for (const auto &answer : poll->answers) {
|
||||||
|
if (answer.correct) {
|
||||||
|
correct.push_back(MTP_bytes(answer.option));
|
||||||
|
}
|
||||||
|
}
|
||||||
const auto requestId = request(MTPmessages_EditMessage(
|
const auto requestId = request(MTPmessages_EditMessage(
|
||||||
MTP_flags(MTPmessages_EditMessage::Flag::f_media),
|
MTP_flags(MTPmessages_EditMessage::Flag::f_media),
|
||||||
item->history()->peer->input,
|
item->history()->peer->input,
|
||||||
MTP_int(item->id),
|
MTP_int(item->id),
|
||||||
MTPstring(),
|
MTPstring(),
|
||||||
MTP_inputMediaPoll(PollDataToMTP(poll)),
|
MTP_inputMediaPoll(
|
||||||
|
MTP_flags(inputFlags),
|
||||||
|
PollDataToMTP(poll, true),
|
||||||
|
MTP_vector<MTPbytes>(correct)),
|
||||||
MTPReplyMarkup(),
|
MTPReplyMarkup(),
|
||||||
MTPVector<MTPMessageEntity>(),
|
MTPVector<MTPMessageEntity>(),
|
||||||
MTP_int(0) // schedule_date
|
MTP_int(0) // schedule_date
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "numbers.h"
|
#include "numbers.h"
|
||||||
#include "observer_peer.h"
|
#include "observer_peer.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
#include "styles/style_boxes.h"
|
||||||
#include "styles/style_overview.h"
|
#include "styles/style_overview.h"
|
||||||
#include "styles/style_mediaview.h"
|
#include "styles/style_mediaview.h"
|
||||||
#include "styles/style_chat_helpers.h"
|
#include "styles/style_chat_helpers.h"
|
||||||
|
|
@ -180,6 +181,8 @@ namespace App {
|
||||||
prepareCorners(MessageInSelectedCorners, st::historyMessageRadius, st::msgInBgSelected, &st::msgInShadowSelected);
|
prepareCorners(MessageInSelectedCorners, st::historyMessageRadius, st::msgInBgSelected, &st::msgInShadowSelected);
|
||||||
prepareCorners(MessageOutCorners, st::historyMessageRadius, st::msgOutBg, &st::msgOutShadow);
|
prepareCorners(MessageOutCorners, st::historyMessageRadius, st::msgOutBg, &st::msgOutShadow);
|
||||||
prepareCorners(MessageOutSelectedCorners, st::historyMessageRadius, st::msgOutBgSelected, &st::msgOutShadowSelected);
|
prepareCorners(MessageOutSelectedCorners, st::historyMessageRadius, st::msgOutBgSelected, &st::msgOutShadowSelected);
|
||||||
|
|
||||||
|
prepareCorners(SendFilesBoxAlbumGroupCorners, st::sendBoxAlbumGroupRadius, st::callFingerprintBg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void createCorners() {
|
void createCorners() {
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,8 @@ enum RoundCorners : int {
|
||||||
MessageOutCorners,
|
MessageOutCorners,
|
||||||
MessageOutSelectedCorners,
|
MessageOutSelectedCorners,
|
||||||
|
|
||||||
|
SendFilesBoxAlbumGroupCorners,
|
||||||
|
|
||||||
RoundCornersCount
|
RoundCornersCount
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -514,6 +514,29 @@ editMediaButton: IconButton {
|
||||||
ripple: defaultRippleAnimation;
|
ripple: defaultRippleAnimation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendFilesBox
|
||||||
|
|
||||||
|
sendBoxAlbumGroupEditInternalSkip: 9px;
|
||||||
|
sendBoxAlbumGroupSkipRight: 6px;
|
||||||
|
sendBoxAlbumGroupSkipTop: 6px;
|
||||||
|
sendBoxAlbumGroupRadius: 12px;
|
||||||
|
sendBoxAlbumGroupHeight: 25px;
|
||||||
|
|
||||||
|
sendBoxAlbumGroupEditButtonIcon: editMediaButtonIconPhoto;
|
||||||
|
sendBoxAlbumGroupEditButtonIconPosition: point(4px, -1px);
|
||||||
|
|
||||||
|
sendBoxAlbumGroupButtonFile: IconButton(editMediaButton) {
|
||||||
|
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||||
|
color: windowBgRipple;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendBoxAlbumGroupDeleteButtonIconPosition: point(-3px, 0px);
|
||||||
|
sendBoxAlbumGroupDeleteButtonIcon: icon {{ "history_file_cancel", msgServiceFg}};
|
||||||
|
sendBoxAlbumGroupDeleteButtonIconFile: icon {{ "history_file_cancel", menuIconFg, point(6px, 6px) }};
|
||||||
|
|
||||||
|
// End of SendFilesBox
|
||||||
|
|
||||||
calendarTitleHeight: boxTitleHeight;
|
calendarTitleHeight: boxTitleHeight;
|
||||||
calendarPrevious: IconButton {
|
calendarPrevious: IconButton {
|
||||||
width: calendarTitleHeight;
|
width: calendarTitleHeight;
|
||||||
|
|
@ -812,15 +835,15 @@ createPollField: InputField(defaultInputField) {
|
||||||
}
|
}
|
||||||
createPollFieldPadding: margins(22px, 5px, 22px, 5px);
|
createPollFieldPadding: margins(22px, 5px, 22px, 5px);
|
||||||
createPollOptionField: InputField(createPollField) {
|
createPollOptionField: InputField(createPollField) {
|
||||||
textMargins: margins(22px, 8px, 40px, 8px);
|
textMargins: margins(22px, 11px, 40px, 11px);
|
||||||
placeholderMargins: margins(2px, 0px, 2px, 0px);
|
placeholderMargins: margins(2px, 0px, 2px, 0px);
|
||||||
heightMax: 64px;
|
heightMax: 68px;
|
||||||
}
|
}
|
||||||
createPollLimitLabel: FlatLabel(defaultFlatLabel) {
|
createPollLimitLabel: FlatLabel(defaultFlatLabel) {
|
||||||
minWidth: 274px;
|
minWidth: 274px;
|
||||||
align: align(topleft);
|
align: align(topleft);
|
||||||
}
|
}
|
||||||
createPollLimitPadding: margins(22px, 10px, 22px, 5px);
|
createPollLimitPadding: margins(22px, 10px, 22px, 16px);
|
||||||
createPollOptionRemove: CrossButton {
|
createPollOptionRemove: CrossButton {
|
||||||
width: 22px;
|
width: 22px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
|
|
@ -841,7 +864,7 @@ createPollOptionRemove: CrossButton {
|
||||||
color: windowBgOver;
|
color: windowBgOver;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
createPollOptionRemovePosition: point(10px, 7px);
|
createPollOptionRemovePosition: point(11px, 9px);
|
||||||
createPollWarning: FlatLabel(defaultFlatLabel) {
|
createPollWarning: FlatLabel(defaultFlatLabel) {
|
||||||
textFg: windowSubTextFg;
|
textFg: windowSubTextFg;
|
||||||
palette: TextPalette(defaultTextPalette) {
|
palette: TextPalette(defaultTextPalette) {
|
||||||
|
|
@ -849,6 +872,8 @@ createPollWarning: FlatLabel(defaultFlatLabel) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
createPollWarningPosition: point(16px, 6px);
|
createPollWarningPosition: point(16px, 6px);
|
||||||
|
createPollCheckboxMargin: margins(23px, 10px, 23px, 10px);
|
||||||
|
createPollFieldTitlePadding: margins(22px, 7px, 10px, 6px);
|
||||||
|
|
||||||
callSettingsButton: IconButton {
|
callSettingsButton: IconButton {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
|
|
@ -922,6 +947,33 @@ customBadgeField: InputField(defaultInputField) {
|
||||||
heightMin: 32px;
|
heightMin: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pollResultsQuestion: FlatLabel(defaultFlatLabel) {
|
||||||
|
minWidth: 320px;
|
||||||
|
textFg: windowBoldFg;
|
||||||
|
style: TextStyle(defaultTextStyle) {
|
||||||
|
font: font(16px semibold);
|
||||||
|
linkFont: font(16px semibold);
|
||||||
|
linkFontOver: font(16px semibold underline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pollResultsVotesCount: FlatLabel(defaultFlatLabel) {
|
||||||
|
textFg: windowSubTextFg;
|
||||||
|
}
|
||||||
|
pollResultsHeaderPadding: margins(22px, 22px, 22px, 8px);
|
||||||
|
pollResultsShowMore: SettingsButton {
|
||||||
|
textFg: lightButtonFg;
|
||||||
|
textFgOver: lightButtonFgOver;
|
||||||
|
textBg: windowBg;
|
||||||
|
textBgOver: windowBgOver;
|
||||||
|
|
||||||
|
font: semiboldFont;
|
||||||
|
|
||||||
|
height: 20px;
|
||||||
|
padding: margins(71px, 10px, 8px, 8px);
|
||||||
|
|
||||||
|
ripple: defaultRippleAnimation;
|
||||||
|
}
|
||||||
|
|
||||||
fontsBoxTextStyle: TextStyle(defaultTextStyle) {
|
fontsBoxTextStyle: TextStyle(defaultTextStyle) {
|
||||||
font: font(13px);
|
font: font(13px);
|
||||||
linkFont: font(13px);
|
linkFont: font(13px);
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/toast/toast.h"
|
#include "ui/toast/toast.h"
|
||||||
#include "ui/wrap/vertical_layout.h"
|
#include "ui/wrap/vertical_layout.h"
|
||||||
#include "ui/wrap/slide_wrap.h"
|
#include "ui/wrap/slide_wrap.h"
|
||||||
|
#include "ui/wrap/fade_wrap.h"
|
||||||
#include "ui/widgets/input_fields.h"
|
#include "ui/widgets/input_fields.h"
|
||||||
#include "ui/widgets/shadow.h"
|
#include "ui/widgets/shadow.h"
|
||||||
#include "ui/widgets/labels.h"
|
#include "ui/widgets/labels.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
|
#include "ui/widgets/checkbox.h"
|
||||||
|
#include "ui/toast/toast.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||||
#include "chat_helpers/message_field.h"
|
#include "chat_helpers/message_field.h"
|
||||||
|
|
@ -42,13 +45,16 @@ public:
|
||||||
Options(
|
Options(
|
||||||
not_null<QWidget*> outer,
|
not_null<QWidget*> outer,
|
||||||
not_null<Ui::VerticalLayout*> container,
|
not_null<Ui::VerticalLayout*> container,
|
||||||
not_null<Main::Session*> session);
|
not_null<Main::Session*> session,
|
||||||
|
bool chooseCorrectEnabled);
|
||||||
|
|
||||||
[[nodiscard]] bool isValid() const;
|
[[nodiscard]] bool isValid() const;
|
||||||
[[nodiscard]] rpl::producer<bool> isValidChanged() const;
|
[[nodiscard]] rpl::producer<bool> isValidChanged() const;
|
||||||
[[nodiscard]] std::vector<PollAnswer> toPollAnswers() const;
|
[[nodiscard]] std::vector<PollAnswer> toPollAnswers() const;
|
||||||
void focusFirst();
|
void focusFirst();
|
||||||
|
|
||||||
|
void enableChooseCorrect(bool enabled);
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<int> usedCount() const;
|
[[nodiscard]] rpl::producer<int> usedCount() const;
|
||||||
[[nodiscard]] rpl::producer<not_null<QWidget*>> scrollToWidget() const;
|
[[nodiscard]] rpl::producer<not_null<QWidget*>> scrollToWidget() const;
|
||||||
[[nodiscard]] rpl::producer<> backspaceInFront() const;
|
[[nodiscard]] rpl::producer<> backspaceInFront() const;
|
||||||
|
|
@ -56,23 +62,31 @@ public:
|
||||||
private:
|
private:
|
||||||
class Option {
|
class Option {
|
||||||
public:
|
public:
|
||||||
static Option Create(
|
Option(
|
||||||
not_null<QWidget*> outer,
|
not_null<QWidget*> outer,
|
||||||
not_null<Ui::VerticalLayout*> container,
|
not_null<Ui::VerticalLayout*> container,
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
int position);
|
int position,
|
||||||
|
std::shared_ptr<Ui::RadiobuttonGroup> group);
|
||||||
|
|
||||||
|
Option(const Option &other) = delete;
|
||||||
|
Option &operator=(const Option &other) = delete;
|
||||||
|
|
||||||
void toggleRemoveAlways(bool toggled);
|
void toggleRemoveAlways(bool toggled);
|
||||||
|
void enableChooseCorrect(
|
||||||
|
std::shared_ptr<Ui::RadiobuttonGroup> group);
|
||||||
|
|
||||||
void show(anim::type animated);
|
void show(anim::type animated);
|
||||||
void destroy(FnMut<void()> done);
|
void destroy(FnMut<void()> done);
|
||||||
|
|
||||||
//[[nodisacrd]] bool hasShadow() const;
|
[[nodisacrd]] bool hasShadow() const;
|
||||||
//void destroyShadow();
|
void createShadow();
|
||||||
|
void destroyShadow();
|
||||||
|
|
||||||
[[nodiscard]] bool isEmpty() const;
|
[[nodiscard]] bool isEmpty() const;
|
||||||
[[nodiscard]] bool isGood() const;
|
[[nodiscard]] bool isGood() const;
|
||||||
[[nodiscard]] bool isTooLong() const;
|
[[nodiscard]] bool isTooLong() const;
|
||||||
|
[[nodiscard]] bool isCorrect() const;
|
||||||
[[nodiscard]] bool hasFocus() const;
|
[[nodiscard]] bool hasFocus() const;
|
||||||
void setFocus() const;
|
void setFocus() const;
|
||||||
void clearValue();
|
void clearValue();
|
||||||
|
|
@ -86,29 +100,18 @@ private:
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<Qt::MouseButton> removeClicks() const;
|
[[nodiscard]] rpl::producer<Qt::MouseButton> removeClicks() const;
|
||||||
|
|
||||||
inline bool operator<(const Option &other) const {
|
|
||||||
return field() < other.field();
|
|
||||||
}
|
|
||||||
|
|
||||||
friend inline bool operator<(
|
|
||||||
const Option &option,
|
|
||||||
Ui::InputField *field) {
|
|
||||||
return option.field() < field;
|
|
||||||
}
|
|
||||||
friend inline bool operator<(
|
|
||||||
Ui::InputField *field,
|
|
||||||
const Option &option) {
|
|
||||||
return field < option.field();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Option() = default;
|
|
||||||
|
|
||||||
void createShadow();
|
|
||||||
void createRemove();
|
void createRemove();
|
||||||
void createWarning();
|
void createWarning();
|
||||||
|
void toggleCorrectSpace(bool visible);
|
||||||
|
void updateFieldGeometry();
|
||||||
|
|
||||||
base::unique_qptr<Ui::SlideWrap<Ui::InputField>> _field;
|
base::unique_qptr<Ui::SlideWrap<Ui::RpWidget>> _wrap;
|
||||||
|
not_null<Ui::RpWidget*> _content;
|
||||||
|
base::unique_qptr<Ui::FadeWrapScaled<Ui::Radiobutton>> _correct;
|
||||||
|
Ui::Animations::Simple _correctShown;
|
||||||
|
bool _hasCorrect = false;
|
||||||
|
Ui::InputField *_field = nullptr;
|
||||||
base::unique_qptr<Ui::PlainShadow> _shadow;
|
base::unique_qptr<Ui::PlainShadow> _shadow;
|
||||||
base::unique_qptr<Ui::CrossButton> _remove;
|
base::unique_qptr<Ui::CrossButton> _remove;
|
||||||
rpl::variable<bool> *_removeAlways = nullptr;
|
rpl::variable<bool> *_removeAlways = nullptr;
|
||||||
|
|
@ -116,23 +119,26 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] bool full() const;
|
[[nodiscard]] bool full() const;
|
||||||
//[[nodiscard]] bool correctShadows() const;
|
[[nodiscard]] bool correctShadows() const;
|
||||||
//void fixShadows();
|
void fixShadows();
|
||||||
void removeEmptyTail();
|
void removeEmptyTail();
|
||||||
void addEmptyOption();
|
void addEmptyOption();
|
||||||
void checkLastOption();
|
void checkLastOption();
|
||||||
void validateState();
|
void validateState();
|
||||||
void fixAfterErase();
|
void fixAfterErase();
|
||||||
void destroy(Option &&option);
|
void destroy(std::unique_ptr<Option> option);
|
||||||
void removeDestroyed(not_null<Ui::InputField*> field);
|
void removeDestroyed(not_null<Option*> field);
|
||||||
int findField(not_null<Ui::InputField*> field) const;
|
int findField(not_null<Ui::InputField*> field) const;
|
||||||
|
[[nodiscard]] auto createChooseCorrectGroup()
|
||||||
|
-> std::shared_ptr<Ui::RadiobuttonGroup>;
|
||||||
|
|
||||||
not_null<QWidget*> _outer;
|
not_null<QWidget*> _outer;
|
||||||
not_null<Ui::VerticalLayout*> _container;
|
not_null<Ui::VerticalLayout*> _container;
|
||||||
const not_null<Main::Session*> _session;
|
const not_null<Main::Session*> _session;
|
||||||
|
std::shared_ptr<Ui::RadiobuttonGroup> _chooseCorrectGroup;
|
||||||
int _position = 0;
|
int _position = 0;
|
||||||
std::vector<Option> _list;
|
std::vector<std::unique_ptr<Option>> _list;
|
||||||
std::set<Option, std::less<>> _destroyed;
|
std::vector<std::unique_ptr<Option>> _destroyed;
|
||||||
rpl::variable<bool> _valid = false;
|
rpl::variable<bool> _valid = false;
|
||||||
rpl::variable<int> _usedCount = 0;
|
rpl::variable<int> _usedCount = 0;
|
||||||
rpl::event_stream<not_null<QWidget*>> _scrollToWidget;
|
rpl::event_stream<not_null<QWidget*>> _scrollToWidget;
|
||||||
|
|
@ -187,58 +193,85 @@ void FocusAtEnd(not_null<Ui::InputField*> field) {
|
||||||
field->ensureCursorVisible();
|
field->ensureCursorVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
Options::Option Options::Option::Create(
|
Options::Option::Option(
|
||||||
not_null<QWidget*> outer,
|
not_null<QWidget*> outer,
|
||||||
not_null<Ui::VerticalLayout*> container,
|
not_null<Ui::VerticalLayout*> container,
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
int position) {
|
int position,
|
||||||
auto result = Option();
|
std::shared_ptr<Ui::RadiobuttonGroup> group)
|
||||||
const auto field = container->insert(
|
: _wrap(container->insert(
|
||||||
position,
|
position,
|
||||||
object_ptr<Ui::SlideWrap<Ui::InputField>>(
|
object_ptr<Ui::SlideWrap<Ui::RpWidget>>(
|
||||||
container,
|
container,
|
||||||
object_ptr<Ui::InputField>(
|
object_ptr<Ui::RpWidget>(container))))
|
||||||
container,
|
, _content(_wrap->entity())
|
||||||
st::createPollOptionField,
|
, _field(
|
||||||
Ui::InputField::Mode::NoNewlines,
|
Ui::CreateChild<Ui::InputField>(
|
||||||
tr::lng_polls_create_option_add())));
|
_content.get(),
|
||||||
InitField(outer, field->entity(), session);
|
st::createPollOptionField,
|
||||||
field->entity()->setMaxLength(kOptionLimit + kErrorLimit);
|
Ui::InputField::Mode::NoNewlines,
|
||||||
result._field.reset(field);
|
tr::lng_polls_create_option_add())) {
|
||||||
|
InitField(outer, _field, session);
|
||||||
|
_field->setMaxLength(kOptionLimit + kErrorLimit);
|
||||||
|
_field->show();
|
||||||
|
|
||||||
result.createShadow();
|
_wrap->hide(anim::type::instant);
|
||||||
result.createRemove();
|
|
||||||
result.createWarning();
|
_content->widthValue(
|
||||||
return result;
|
) | rpl::start_with_next([=] {
|
||||||
|
updateFieldGeometry();
|
||||||
|
}, _field->lifetime());
|
||||||
|
|
||||||
|
_field->heightValue(
|
||||||
|
) | rpl::start_with_next([=](int height) {
|
||||||
|
_content->resize(_content->width(), height);
|
||||||
|
}, _field->lifetime());
|
||||||
|
|
||||||
|
QObject::connect(_field, &Ui::InputField::changed, [=] {
|
||||||
|
Ui::PostponeCall(crl::guard(_field, [=] {
|
||||||
|
if (_hasCorrect) {
|
||||||
|
_correct->toggle(isGood(), anim::type::normal);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
createShadow();
|
||||||
|
createRemove();
|
||||||
|
createWarning();
|
||||||
|
enableChooseCorrect(group);
|
||||||
|
_correctShown.stop();
|
||||||
|
if (_correct) {
|
||||||
|
_correct->finishAnimating();
|
||||||
|
}
|
||||||
|
updateFieldGeometry();
|
||||||
}
|
}
|
||||||
|
|
||||||
//bool Options::Option::hasShadow() const {
|
bool Options::Option::hasShadow() const {
|
||||||
// return (_shadow != nullptr);
|
return (_shadow != nullptr);
|
||||||
//}
|
}
|
||||||
|
|
||||||
void Options::Option::createShadow() {
|
void Options::Option::createShadow() {
|
||||||
Expects(_field != nullptr);
|
Expects(_content != nullptr);
|
||||||
|
|
||||||
if (_shadow) {
|
if (_shadow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto value = Ui::CreateChild<Ui::PlainShadow>(field().get());
|
_shadow.reset(Ui::CreateChild<Ui::PlainShadow>(field().get()));
|
||||||
value->show();
|
_shadow->show();
|
||||||
field()->sizeValue(
|
field()->sizeValue(
|
||||||
) | rpl::start_with_next([=](QSize size) {
|
) | rpl::start_with_next([=](QSize size) {
|
||||||
const auto left = st::createPollFieldPadding.left();
|
const auto left = st::createPollFieldPadding.left();
|
||||||
value->setGeometry(
|
_shadow->setGeometry(
|
||||||
left,
|
left,
|
||||||
size.height() - st::lineWidth,
|
size.height() - st::lineWidth,
|
||||||
size.width() - left,
|
size.width() - left,
|
||||||
st::lineWidth);
|
st::lineWidth);
|
||||||
}, value->lifetime());
|
}, _shadow->lifetime());
|
||||||
_shadow.reset(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//void Options::Option::destroyShadow() {
|
void Options::Option::destroyShadow() {
|
||||||
// _shadow = nullptr;
|
_shadow = nullptr;
|
||||||
//}
|
}
|
||||||
|
|
||||||
void Options::Option::createRemove() {
|
void Options::Option::createRemove() {
|
||||||
using namespace rpl::mappers;
|
using namespace rpl::mappers;
|
||||||
|
|
@ -313,6 +346,10 @@ bool Options::Option::isTooLong() const {
|
||||||
return (field()->getLastText().size() > kOptionLimit);
|
return (field()->getLastText().size() > kOptionLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Options::Option::isCorrect() const {
|
||||||
|
return isGood() && _correct && _correct->entity()->Checkbox::checked();
|
||||||
|
}
|
||||||
|
|
||||||
bool Options::Option::hasFocus() const {
|
bool Options::Option::hasFocus() const {
|
||||||
return field()->hasFocus();
|
return field()->hasFocus();
|
||||||
}
|
}
|
||||||
|
|
@ -333,8 +370,66 @@ void Options::Option::toggleRemoveAlways(bool toggled) {
|
||||||
*_removeAlways = toggled;
|
*_removeAlways = toggled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Options::Option::enableChooseCorrect(
|
||||||
|
std::shared_ptr<Ui::RadiobuttonGroup> group) {
|
||||||
|
if (!group) {
|
||||||
|
if (_correct) {
|
||||||
|
_hasCorrect = false;
|
||||||
|
_correct->hide(anim::type::normal);
|
||||||
|
toggleCorrectSpace(false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
static auto Index = 0;
|
||||||
|
const auto button = Ui::CreateChild<Ui::FadeWrapScaled<Ui::Radiobutton>>(
|
||||||
|
_content.get(),
|
||||||
|
object_ptr<Ui::Radiobutton>(
|
||||||
|
_content.get(),
|
||||||
|
group,
|
||||||
|
++Index,
|
||||||
|
QString(),
|
||||||
|
st::defaultCheckbox));
|
||||||
|
button->entity()->resize(
|
||||||
|
button->entity()->height(),
|
||||||
|
button->entity()->height());
|
||||||
|
button->hide(anim::type::instant);
|
||||||
|
_content->sizeValue(
|
||||||
|
) | rpl::start_with_next([=](QSize size) {
|
||||||
|
const auto left = st::createPollFieldPadding.left();
|
||||||
|
button->moveToLeft(
|
||||||
|
left,
|
||||||
|
(size.height() - button->heightNoMargins()) / 2);
|
||||||
|
}, button->lifetime());
|
||||||
|
_correct.reset(button);
|
||||||
|
_hasCorrect = true;
|
||||||
|
if (isGood()) {
|
||||||
|
_correct->show(anim::type::normal);
|
||||||
|
} else {
|
||||||
|
_correct->hide(anim::type::instant);
|
||||||
|
}
|
||||||
|
toggleCorrectSpace(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Options::Option::toggleCorrectSpace(bool visible) {
|
||||||
|
_correctShown.start(
|
||||||
|
[=] { updateFieldGeometry(); },
|
||||||
|
visible ? 0. : 1.,
|
||||||
|
visible ? 1. : 0.,
|
||||||
|
st::fadeWrapDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Options::Option::updateFieldGeometry() {
|
||||||
|
const auto shown = _correctShown.value(_hasCorrect ? 1. : 0.);
|
||||||
|
const auto skip = st::defaultRadio.diameter
|
||||||
|
+ st::defaultCheckbox.textPosition.x();
|
||||||
|
const auto left = anim::interpolate(0, skip, shown);
|
||||||
|
const auto width = _content->width() - left;
|
||||||
|
_field->resizeToWidth(_content->width() - left);
|
||||||
|
_field->moveToLeft(left, 0);
|
||||||
|
}
|
||||||
|
|
||||||
not_null<Ui::InputField*> Options::Option::field() const {
|
not_null<Ui::InputField*> Options::Option::field() const {
|
||||||
return _field->entity();
|
return _field;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Options::Option::removePlaceholder() const {
|
void Options::Option::removePlaceholder() const {
|
||||||
|
|
@ -344,10 +439,12 @@ void Options::Option::removePlaceholder() const {
|
||||||
PollAnswer Options::Option::toPollAnswer(int index) const {
|
PollAnswer Options::Option::toPollAnswer(int index) const {
|
||||||
Expects(index >= 0 && index < kMaxOptionsCount);
|
Expects(index >= 0 && index < kMaxOptionsCount);
|
||||||
|
|
||||||
return PollAnswer{
|
auto result = PollAnswer{
|
||||||
field()->getLastText().trimmed(),
|
field()->getLastText().trimmed(),
|
||||||
QByteArray(1, ('0' + index))
|
QByteArray(1, ('0' + index))
|
||||||
};
|
};
|
||||||
|
result.correct = _correct ? _correct->entity()->Checkbox::checked() : false;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<Qt::MouseButton> Options::Option::removeClicks() const {
|
rpl::producer<Qt::MouseButton> Options::Option::removeClicks() const {
|
||||||
|
|
@ -357,10 +454,14 @@ rpl::producer<Qt::MouseButton> Options::Option::removeClicks() const {
|
||||||
Options::Options(
|
Options::Options(
|
||||||
not_null<QWidget*> outer,
|
not_null<QWidget*> outer,
|
||||||
not_null<Ui::VerticalLayout*> container,
|
not_null<Ui::VerticalLayout*> container,
|
||||||
not_null<Main::Session*> session)
|
not_null<Main::Session*> session,
|
||||||
|
bool chooseCorrectEnabled)
|
||||||
: _outer(outer)
|
: _outer(outer)
|
||||||
, _container(container)
|
, _container(container)
|
||||||
, _session(session)
|
, _session(session)
|
||||||
|
, _chooseCorrectGroup(chooseCorrectEnabled
|
||||||
|
? createChooseCorrectGroup()
|
||||||
|
: nullptr)
|
||||||
, _position(_container->count()) {
|
, _position(_container->count()) {
|
||||||
checkLastOption();
|
checkLastOption();
|
||||||
}
|
}
|
||||||
|
|
@ -390,19 +491,18 @@ rpl::producer<> Options::backspaceInFront() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Options::Option::show(anim::type animated) {
|
void Options::Option::show(anim::type animated) {
|
||||||
_field->hide(anim::type::instant);
|
_wrap->show(animated);
|
||||||
_field->show(animated);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Options::Option::destroy(FnMut<void()> done) {
|
void Options::Option::destroy(FnMut<void()> done) {
|
||||||
if (anim::Disabled() || _field->isHidden()) {
|
if (anim::Disabled() || _wrap->isHidden()) {
|
||||||
Ui::PostponeCall(std::move(done));
|
Ui::PostponeCall(std::move(done));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_field->hide(anim::type::normal);
|
_wrap->hide(anim::type::normal);
|
||||||
base::call_delayed(
|
base::call_delayed(
|
||||||
st::slideWrapDuration * 2,
|
st::slideWrapDuration * 2,
|
||||||
_field.get(),
|
_content.get(),
|
||||||
std::move(done));
|
std::move(done));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -410,8 +510,8 @@ std::vector<PollAnswer> Options::toPollAnswers() const {
|
||||||
auto result = std::vector<PollAnswer>();
|
auto result = std::vector<PollAnswer>();
|
||||||
result.reserve(_list.size());
|
result.reserve(_list.size());
|
||||||
auto counter = int(0);
|
auto counter = int(0);
|
||||||
const auto makeAnswer = [&](const Option &option) {
|
const auto makeAnswer = [&](const std::unique_ptr<Option> &option) {
|
||||||
return option.toPollAnswer(counter++);
|
return option->toPollAnswer(counter++);
|
||||||
};
|
};
|
||||||
ranges::copy(
|
ranges::copy(
|
||||||
_list
|
_list
|
||||||
|
|
@ -424,29 +524,45 @@ std::vector<PollAnswer> Options::toPollAnswers() const {
|
||||||
void Options::focusFirst() {
|
void Options::focusFirst() {
|
||||||
Expects(!_list.empty());
|
Expects(!_list.empty());
|
||||||
|
|
||||||
_list.front().setFocus();
|
_list.front()->setFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Ui::RadiobuttonGroup> Options::createChooseCorrectGroup() {
|
||||||
|
auto result = std::make_shared<Ui::RadiobuttonGroup>(0);
|
||||||
|
result->setChangedCallback([=](int) {
|
||||||
|
validateState();
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Options::enableChooseCorrect(bool enabled) {
|
||||||
|
_chooseCorrectGroup = enabled
|
||||||
|
? createChooseCorrectGroup()
|
||||||
|
: nullptr;
|
||||||
|
validateState();
|
||||||
|
for (auto &option : _list) {
|
||||||
|
option->enableChooseCorrect(_chooseCorrectGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Options::correctShadows() const {
|
||||||
|
// Last one should be without shadow.
|
||||||
|
const auto noShadow = ranges::find(
|
||||||
|
_list,
|
||||||
|
true,
|
||||||
|
ranges::not_fn(&Option::hasShadow));
|
||||||
|
return (noShadow == end(_list) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Options::fixShadows() {
|
||||||
|
if (correctShadows()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (auto &option : _list) {
|
||||||
|
option->createShadow();
|
||||||
|
}
|
||||||
|
_list.back()->destroyShadow();
|
||||||
}
|
}
|
||||||
//
|
|
||||||
//bool Options::correctShadows() const {
|
|
||||||
// // Last one should be without shadow if all options were used.
|
|
||||||
// const auto noShadow = ranges::find(
|
|
||||||
// _list,
|
|
||||||
// true,
|
|
||||||
// ranges::not_fn(&Option::hasShadow));
|
|
||||||
// return (noShadow == end(_list) - (full() ? 1 : 0));
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//void Options::fixShadows() {
|
|
||||||
// if (correctShadows()) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// for (auto &option : _list) {
|
|
||||||
// option.createShadow();
|
|
||||||
// }
|
|
||||||
// if (full()) {
|
|
||||||
// _list.back().destroyShadow();
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
void Options::removeEmptyTail() {
|
void Options::removeEmptyTail() {
|
||||||
// Only one option at the end of options list can be empty.
|
// Only one option at the end of options list can be empty.
|
||||||
|
|
@ -465,7 +581,7 @@ void Options::removeEmptyTail() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (focusLast) {
|
if (focusLast) {
|
||||||
emptyItem->setFocus();
|
(*emptyItem)->setFocus();
|
||||||
}
|
}
|
||||||
for (auto i = emptyItem + 1; i != end; ++i) {
|
for (auto i = emptyItem + 1; i != end; ++i) {
|
||||||
destroy(std::move(*i));
|
destroy(std::move(*i));
|
||||||
|
|
@ -474,44 +590,46 @@ void Options::removeEmptyTail() {
|
||||||
fixAfterErase();
|
fixAfterErase();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Options::destroy(Option &&option) {
|
void Options::destroy(std::unique_ptr<Option> option) {
|
||||||
const auto field = option.field();
|
const auto value = option.get();
|
||||||
option.destroy([=] { removeDestroyed(field); });
|
option->destroy([=] { removeDestroyed(value); });
|
||||||
_destroyed.emplace(std::move(option));
|
_destroyed.push_back(std::move(option));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Options::fixAfterErase() {
|
void Options::fixAfterErase() {
|
||||||
Expects(!_list.empty());
|
Expects(!_list.empty());
|
||||||
|
|
||||||
const auto last = _list.end() - 1;
|
const auto last = _list.end() - 1;
|
||||||
last->setPlaceholder();
|
(*last)->setPlaceholder();
|
||||||
last->toggleRemoveAlways(false);
|
(*last)->toggleRemoveAlways(false);
|
||||||
if (last != begin(_list)) {
|
if (last != begin(_list)) {
|
||||||
(last - 1)->setPlaceholder();
|
(*(last - 1))->setPlaceholder();
|
||||||
(last - 1)->toggleRemoveAlways(false);
|
(*(last - 1))->toggleRemoveAlways(false);
|
||||||
}
|
}
|
||||||
|
fixShadows();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Options::addEmptyOption() {
|
void Options::addEmptyOption() {
|
||||||
if (full()) {
|
if (full()) {
|
||||||
return;
|
return;
|
||||||
} else if (!_list.empty() && _list.back().isEmpty()) {
|
} else if (!_list.empty() && _list.back()->isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_list.size() > 1) {
|
if (_list.size() > 1) {
|
||||||
(_list.end() - 2)->removePlaceholder();
|
(*(_list.end() - 2))->removePlaceholder();
|
||||||
(_list.end() - 2)->toggleRemoveAlways(true);
|
(*(_list.end() - 2))->toggleRemoveAlways(true);
|
||||||
}
|
}
|
||||||
_list.push_back(Option::Create(
|
_list.push_back(std::make_unique<Option>(
|
||||||
_outer,
|
_outer,
|
||||||
_container,
|
_container,
|
||||||
_session,
|
_session,
|
||||||
_position + _list.size() + _destroyed.size()));
|
_position + _list.size() + _destroyed.size(),
|
||||||
const auto field = _list.back().field();
|
_chooseCorrectGroup));
|
||||||
|
const auto field = _list.back()->field();
|
||||||
QObject::connect(field, &Ui::InputField::submitted, [=] {
|
QObject::connect(field, &Ui::InputField::submitted, [=] {
|
||||||
const auto index = findField(field);
|
const auto index = findField(field);
|
||||||
if (_list[index].isGood() && index + 1 < _list.size()) {
|
if (_list[index]->isGood() && index + 1 < _list.size()) {
|
||||||
_list[index + 1].setFocus();
|
_list[index + 1]->setFocus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
QObject::connect(field, &Ui::InputField::changed, [=] {
|
QObject::connect(field, &Ui::InputField::changed, [=] {
|
||||||
|
|
@ -534,25 +652,25 @@ void Options::addEmptyOption() {
|
||||||
|
|
||||||
const auto index = findField(field);
|
const auto index = findField(field);
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
_list[index - 1].setFocus();
|
_list[index - 1]->setFocus();
|
||||||
} else {
|
} else {
|
||||||
_backspaceInFront.fire({});
|
_backspaceInFront.fire({});
|
||||||
}
|
}
|
||||||
return base::EventFilterResult::Cancel;
|
return base::EventFilterResult::Cancel;
|
||||||
});
|
});
|
||||||
|
|
||||||
_list.back().removeClicks(
|
_list.back()->removeClicks(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::take(1) | rpl::start_with_next([=] {
|
||||||
Ui::PostponeCall(crl::guard(field, [=] {
|
Ui::PostponeCall(crl::guard(field, [=] {
|
||||||
Expects(!_list.empty());
|
Expects(!_list.empty());
|
||||||
|
|
||||||
const auto item = begin(_list) + findField(field);
|
const auto item = begin(_list) + findField(field);
|
||||||
if (item == _list.end() - 1) {
|
if (item == _list.end() - 1) {
|
||||||
item->clearValue();
|
(*item)->clearValue();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (item->hasFocus()) {
|
if ((*item)->hasFocus()) {
|
||||||
(item + 1)->setFocus();
|
(*(item + 1))->setFocus();
|
||||||
}
|
}
|
||||||
destroy(std::move(*item));
|
destroy(std::move(*item));
|
||||||
_list.erase(item);
|
_list.erase(item);
|
||||||
|
|
@ -561,21 +679,28 @@ void Options::addEmptyOption() {
|
||||||
}));
|
}));
|
||||||
}, field->lifetime());
|
}, field->lifetime());
|
||||||
|
|
||||||
_list.back().show((_list.size() == 1)
|
_list.back()->show((_list.size() == 1)
|
||||||
? anim::type::instant
|
? anim::type::instant
|
||||||
: anim::type::normal);
|
: anim::type::normal);
|
||||||
//fixShadows();
|
fixShadows();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Options::removeDestroyed(not_null<Ui::InputField*> field) {
|
void Options::removeDestroyed(not_null<Option*> option) {
|
||||||
_destroyed.erase(_destroyed.find(field));
|
const auto i = ranges::find(
|
||||||
|
_destroyed,
|
||||||
|
option.get(),
|
||||||
|
&std::unique_ptr<Option>::get);
|
||||||
|
Assert(i != end(_destroyed));
|
||||||
|
_destroyed.erase(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Options::validateState() {
|
void Options::validateState() {
|
||||||
checkLastOption();
|
checkLastOption();
|
||||||
_valid = (ranges::count_if(_list, &Option::isGood) > 1)
|
_valid = (ranges::count_if(_list, &Option::isGood) > 1)
|
||||||
&& (ranges::find_if(_list, &Option::isTooLong) == end(_list));
|
&& (ranges::find_if(_list, &Option::isTooLong) == end(_list))
|
||||||
const auto lastEmpty = !_list.empty() && _list.back().isEmpty();
|
&& (!_chooseCorrectGroup
|
||||||
|
|| ranges::find_if(_list, &Option::isCorrect) != end(_list));
|
||||||
|
const auto lastEmpty = !_list.empty() && _list.back()->isEmpty();
|
||||||
_usedCount = _list.size() - (lastEmpty ? 1 : 0);
|
_usedCount = _list.size() - (lastEmpty ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -599,8 +724,12 @@ void Options::checkLastOption() {
|
||||||
CreatePollBox::CreatePollBox(
|
CreatePollBox::CreatePollBox(
|
||||||
QWidget*,
|
QWidget*,
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
|
PollData::Flags chosen,
|
||||||
|
PollData::Flags disabled,
|
||||||
Api::SendType sendType)
|
Api::SendType sendType)
|
||||||
: _session(session)
|
: _session(session)
|
||||||
|
, _chosen(chosen)
|
||||||
|
, _disabled(disabled)
|
||||||
, _sendType(sendType) {
|
, _sendType(sendType) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -668,11 +797,17 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||||
const auto question = setupQuestion(container);
|
const auto question = setupQuestion(container);
|
||||||
AddDivider(container);
|
AddDivider(container);
|
||||||
AddSkip(container);
|
AddSkip(container);
|
||||||
AddSubsectionTitle(container, tr::lng_polls_create_options());
|
container->add(
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
container,
|
||||||
|
tr::lng_polls_create_options(),
|
||||||
|
st::settingsSubsectionTitle),
|
||||||
|
st::createPollFieldTitlePadding);
|
||||||
const auto options = lifetime().make_state<Options>(
|
const auto options = lifetime().make_state<Options>(
|
||||||
getDelegate()->outerContainer(),
|
getDelegate()->outerContainer(),
|
||||||
container,
|
container,
|
||||||
_session);
|
_session,
|
||||||
|
(_chosen & PollData::Flag::Quiz));
|
||||||
auto limit = options->usedCount() | rpl::after_next([=](int count) {
|
auto limit = options->usedCount() | rpl::after_next([=](int count) {
|
||||||
setCloseByEscape(!count);
|
setCloseByEscape(!count);
|
||||||
setCloseByOutsideClick(!count);
|
setCloseByOutsideClick(!count);
|
||||||
|
|
@ -684,11 +819,68 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||||
container->resizeToWidth(container->widthNoMargins());
|
container->resizeToWidth(container->widthNoMargins());
|
||||||
});
|
});
|
||||||
container->add(
|
container->add(
|
||||||
object_ptr<Ui::FlatLabel>(
|
object_ptr<Ui::DividerLabel>(
|
||||||
container,
|
container,
|
||||||
std::move(limit),
|
object_ptr<Ui::FlatLabel>(
|
||||||
st::createPollLimitLabel),
|
container,
|
||||||
st::createPollLimitPadding);
|
std::move(limit),
|
||||||
|
st::boxDividerLabel),
|
||||||
|
st::createPollLimitPadding));
|
||||||
|
|
||||||
|
AddSkip(container);
|
||||||
|
AddSubsectionTitle(container, tr::lng_polls_create_settings());
|
||||||
|
|
||||||
|
const auto anonymous = (!(_disabled & PollData::Flag::PublicVotes))
|
||||||
|
? container->add(
|
||||||
|
object_ptr<Ui::Checkbox>(
|
||||||
|
container,
|
||||||
|
tr::lng_polls_create_anonymous(tr::now),
|
||||||
|
!(_chosen & PollData::Flag::PublicVotes),
|
||||||
|
st::defaultCheckbox),
|
||||||
|
st::createPollCheckboxMargin)
|
||||||
|
: nullptr;
|
||||||
|
const auto hasMultiple = !(_chosen & PollData::Flag::Quiz)
|
||||||
|
|| !(_disabled & PollData::Flag::Quiz);
|
||||||
|
const auto multiple = hasMultiple
|
||||||
|
? container->add(
|
||||||
|
object_ptr<Ui::Checkbox>(
|
||||||
|
container,
|
||||||
|
tr::lng_polls_create_multiple_choice(tr::now),
|
||||||
|
(_chosen & PollData::Flag::MultiChoice),
|
||||||
|
st::defaultCheckbox),
|
||||||
|
st::createPollCheckboxMargin)
|
||||||
|
: nullptr;
|
||||||
|
const auto quiz = container->add(
|
||||||
|
object_ptr<Ui::Checkbox>(
|
||||||
|
container,
|
||||||
|
tr::lng_polls_create_quiz_mode(tr::now),
|
||||||
|
(_chosen & PollData::Flag::Quiz),
|
||||||
|
st::defaultCheckbox),
|
||||||
|
st::createPollCheckboxMargin);
|
||||||
|
quiz->setDisabled(_disabled & PollData::Flag::Quiz);
|
||||||
|
if (multiple) {
|
||||||
|
multiple->setDisabled((_disabled & PollData::Flag::MultiChoice)
|
||||||
|
|| (_chosen & PollData::Flag::Quiz));
|
||||||
|
multiple->events(
|
||||||
|
) | rpl::filter([=](not_null<QEvent*> e) {
|
||||||
|
return (e->type() == QEvent::MouseButtonPress) && quiz->checked();
|
||||||
|
}) | rpl::start_with_next([=] {
|
||||||
|
Ui::Toast::Show("Quiz has only one right answer.");
|
||||||
|
}, multiple->lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace rpl::mappers;
|
||||||
|
quiz->checkedChanges(
|
||||||
|
) | rpl::start_with_next([=](bool checked) {
|
||||||
|
if (multiple) {
|
||||||
|
if (checked && multiple->checked()) {
|
||||||
|
multiple->setChecked(false);
|
||||||
|
}
|
||||||
|
multiple->setDisabled(checked
|
||||||
|
|| (_disabled & PollData::Flag::MultiChoice));
|
||||||
|
}
|
||||||
|
options->enableChooseCorrect(checked);
|
||||||
|
}, quiz->lifetime());
|
||||||
|
|
||||||
const auto isValidQuestion = [=] {
|
const auto isValidQuestion = [=] {
|
||||||
const auto text = question->getLastText().trimmed();
|
const auto text = question->getLastText().trimmed();
|
||||||
|
|
@ -706,9 +898,16 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto collectResult = [=] {
|
const auto collectResult = [=] {
|
||||||
auto result = PollData(id);
|
using Flag = PollData::Flag;
|
||||||
|
auto result = PollData(&_session->data(), id);
|
||||||
result.question = question->getLastText().trimmed();
|
result.question = question->getLastText().trimmed();
|
||||||
result.answers = options->toPollAnswers();
|
result.answers = options->toPollAnswers();
|
||||||
|
const auto publicVotes = (anonymous && !anonymous->checked());
|
||||||
|
const auto multiChoice = (multiple && multiple->checked());
|
||||||
|
result.setFlags(Flag(0)
|
||||||
|
| (publicVotes ? Flag::PublicVotes : Flag(0))
|
||||||
|
| (multiChoice ? Flag::MultiChoice : Flag(0))
|
||||||
|
| (quiz->checked() ? Flag::Quiz : Flag(0)));
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
const auto send = [=](Api::SendOptions options) {
|
const auto send = [=](Api::SendOptions options) {
|
||||||
|
|
@ -743,7 +942,7 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||||
tr::lng_polls_create_button(),
|
tr::lng_polls_create_button(),
|
||||||
[=] { send({}); });
|
[=] { send({}); });
|
||||||
if (_sendType == Api::SendType::Normal) {
|
if (_sendType == Api::SendType::Normal) {
|
||||||
SetupSendMenu(
|
SetupSendMenuAndShortcuts(
|
||||||
submit.data(),
|
submit.data(),
|
||||||
[=] { return SendMenuType::Scheduled; },
|
[=] { return SendMenuType::Scheduled; },
|
||||||
sendSilent,
|
sendSilent,
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ public:
|
||||||
CreatePollBox(
|
CreatePollBox(
|
||||||
QWidget*,
|
QWidget*,
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
|
PollData::Flags chosen,
|
||||||
|
PollData::Flags disabled,
|
||||||
Api::SendType sendType);
|
Api::SendType sendType);
|
||||||
|
|
||||||
rpl::producer<Result> submitRequests() const;
|
rpl::producer<Result> submitRequests() const;
|
||||||
|
|
@ -47,6 +49,8 @@ private:
|
||||||
not_null<Ui::VerticalLayout*> container);
|
not_null<Ui::VerticalLayout*> container);
|
||||||
|
|
||||||
const not_null<Main::Session*> _session;
|
const not_null<Main::Session*> _session;
|
||||||
|
const PollData::Flags _chosen = PollData::Flags();
|
||||||
|
const PollData::Flags _disabled = PollData::Flags();
|
||||||
const Api::SendType _sendType = Api::SendType();
|
const Api::SendType _sendType = Api::SendType();
|
||||||
Fn<void()> _setInnerFocus;
|
Fn<void()> _setInnerFocus;
|
||||||
Fn<rpl::producer<bool>()> _dataIsValidValue;
|
Fn<rpl::producer<bool>()> _dataIsValidValue;
|
||||||
|
|
|
||||||
|
|
@ -488,97 +488,30 @@ void EditCaptionBox::updateEditMediaButton() {
|
||||||
|
|
||||||
void EditCaptionBox::createEditMediaButton() {
|
void EditCaptionBox::createEditMediaButton() {
|
||||||
const auto callback = [=](FileDialog::OpenResult &&result) {
|
const auto callback = [=](FileDialog::OpenResult &&result) {
|
||||||
if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
|
auto showBoxErrorCallback = [](tr::phrase<> t) {
|
||||||
return;
|
Ui::show(Box<InformBox>(t(tr::now)), Ui::LayerOption::KeepOther);
|
||||||
}
|
|
||||||
|
|
||||||
const auto isValidFile = [](QString mimeType) {
|
|
||||||
if (mimeType == qstr("image/webp")) {
|
|
||||||
Ui::show(
|
|
||||||
Box<InformBox>(tr::lng_edit_media_invalid_file(tr::now)),
|
|
||||||
Ui::LayerOption::KeepOther);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!result.remoteContent.isEmpty()) {
|
auto list = Storage::PreparedList::PreparedFileFromFilesDialog(
|
||||||
|
std::move(result),
|
||||||
|
_isAlbum,
|
||||||
|
std::move(showBoxErrorCallback),
|
||||||
|
st::sendMediaPreviewSize);
|
||||||
|
|
||||||
auto list = Storage::PrepareMediaFromImage(
|
if (list) {
|
||||||
QImage(),
|
_preparedList = std::move(*list);
|
||||||
std::move(result.remoteContent),
|
updateEditPreview();
|
||||||
st::sendMediaPreviewSize);
|
|
||||||
|
|
||||||
if (!isValidFile(list.files.front().mime)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_isAlbum) {
|
|
||||||
const auto albumMimes = {
|
|
||||||
"image/jpeg",
|
|
||||||
"image/png",
|
|
||||||
"video/mp4",
|
|
||||||
};
|
|
||||||
const auto file = &list.files.front();
|
|
||||||
if (ranges::find(albumMimes, file->mime) == end(albumMimes)
|
|
||||||
|| file->type == Storage::PreparedFile::AlbumType::None) {
|
|
||||||
Ui::show(
|
|
||||||
Box<InformBox>(tr::lng_edit_media_album_error(tr::now)),
|
|
||||||
Ui::LayerOption::KeepOther);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_preparedList = std::move(list);
|
|
||||||
} else if (!result.paths.isEmpty()) {
|
|
||||||
auto list = Storage::PrepareMediaList(
|
|
||||||
QStringList(result.paths.front()),
|
|
||||||
st::sendMediaPreviewSize);
|
|
||||||
|
|
||||||
// Don't rewrite _preparedList if new list is not valid for album.
|
|
||||||
if (_isAlbum) {
|
|
||||||
using Info = FileMediaInformation;
|
|
||||||
|
|
||||||
const auto media = &list.files.front().information->media;
|
|
||||||
const auto valid = media->match([&](const Info::Image &data) {
|
|
||||||
return Storage::ValidateThumbDimensions(
|
|
||||||
data.data.width(),
|
|
||||||
data.data.height())
|
|
||||||
&& !data.animated;
|
|
||||||
}, [&](Info::Video &data) {
|
|
||||||
data.isGifv = false;
|
|
||||||
return true;
|
|
||||||
}, [](auto &&other) {
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
if (!valid) {
|
|
||||||
Ui::show(
|
|
||||||
Box<InformBox>(tr::lng_edit_media_album_error(tr::now)),
|
|
||||||
Ui::LayerOption::KeepOther);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const auto info = QFileInfo(result.paths.front());
|
|
||||||
if (!isValidFile(Core::MimeTypeForFile(info).name())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_preparedList = std::move(list);
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateEditPreview();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto buttonCallback = [=] {
|
const auto buttonCallback = [=] {
|
||||||
const auto filters = _isAlbum
|
const auto filters = _isAlbum
|
||||||
? QStringList(qsl("Image and Video Files (*.png *.jpg *.mp4)"))
|
? FileDialog::AlbumFilesFilter()
|
||||||
: QStringList(FileDialog::AllFilesFilter());
|
: FileDialog::AllFilesFilter();
|
||||||
FileDialog::GetOpenPath(
|
FileDialog::GetOpenPath(
|
||||||
this,
|
this,
|
||||||
tr::lng_choose_file(tr::now),
|
tr::lng_choose_file(tr::now),
|
||||||
filters.join(qsl(";;")),
|
filters,
|
||||||
crl::guard(this, callback));
|
crl::guard(this, callback));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||||
#include "chat_helpers/tabbed_panel.h"
|
#include "chat_helpers/tabbed_panel.h"
|
||||||
#include "chat_helpers/tabbed_selector.h"
|
#include "chat_helpers/tabbed_selector.h"
|
||||||
|
#include "confirm_box.h"
|
||||||
#include "history/view/history_view_schedule_box.h"
|
#include "history/view/history_view_schedule_box.h"
|
||||||
#include "core/file_utilities.h"
|
#include "core/file_utilities.h"
|
||||||
#include "core/mime_type.h"
|
#include "core/mime_type.h"
|
||||||
|
|
@ -48,8 +49,102 @@ namespace {
|
||||||
constexpr auto kMinPreviewWidth = 20;
|
constexpr auto kMinPreviewWidth = 20;
|
||||||
constexpr auto kShrinkDuration = crl::time(150);
|
constexpr auto kShrinkDuration = crl::time(150);
|
||||||
constexpr auto kDragDuration = crl::time(200);
|
constexpr auto kDragDuration = crl::time(200);
|
||||||
const auto kStickerMimeString = qstr("image/webp");
|
|
||||||
const auto kAnimatedStickerMimeString = qstr("application/x-tgsticker");
|
enum class ButtonType {
|
||||||
|
Edit,
|
||||||
|
Delete,
|
||||||
|
None,
|
||||||
|
};
|
||||||
|
|
||||||
|
inline bool CanAddUrls(const QList<QUrl> &urls) {
|
||||||
|
return !urls.isEmpty() && ranges::find_if(
|
||||||
|
urls,
|
||||||
|
[](const QUrl &url) { return !url.isLocalFile(); }
|
||||||
|
) == urls.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IsFirstAlbumItem(const Storage::PreparedList &list) {
|
||||||
|
using AlbumType = Storage::PreparedFile::AlbumType;
|
||||||
|
return (list.files.size() > 0)
|
||||||
|
&& (list.files.front().type != AlbumType::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IsSingleItem(const Storage::PreparedList &list) {
|
||||||
|
return list.files.size() == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect PaintAlbumThumbButtons(
|
||||||
|
Painter &p,
|
||||||
|
QPoint point,
|
||||||
|
int outerWidth,
|
||||||
|
float64 shrinkProgress) {
|
||||||
|
|
||||||
|
const auto skipInternal = st::sendBoxAlbumGroupEditInternalSkip;
|
||||||
|
const auto size = st::sendBoxAlbumGroupHeight;
|
||||||
|
const auto skipRight = st::sendBoxAlbumGroupSkipRight;
|
||||||
|
const auto skipTop = st::sendBoxAlbumGroupSkipTop;
|
||||||
|
const auto groupWidth = size * 2 + skipInternal;
|
||||||
|
|
||||||
|
// If the width is tiny, it would be better to not display the buttons.
|
||||||
|
if (groupWidth > outerWidth) {
|
||||||
|
return QRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the width is too small,
|
||||||
|
// it would be better to display the buttons in the center.
|
||||||
|
const auto groupX = point.x() + ((groupWidth + skipRight * 2 > outerWidth)
|
||||||
|
? (outerWidth - groupWidth) / 2
|
||||||
|
: outerWidth - skipRight - groupWidth);
|
||||||
|
const auto groupY = point.y() + skipTop;
|
||||||
|
const auto deleteLeft = skipInternal + size;
|
||||||
|
|
||||||
|
p.setOpacity(1.0 - shrinkProgress);
|
||||||
|
|
||||||
|
QRect groupRect(groupX, groupY, groupWidth, size);
|
||||||
|
App::roundRect(
|
||||||
|
p,
|
||||||
|
groupRect,
|
||||||
|
st::callFingerprintBg,
|
||||||
|
SendFilesBoxAlbumGroupCorners);
|
||||||
|
|
||||||
|
const auto editP = st::sendBoxAlbumGroupEditButtonIconPosition;
|
||||||
|
const auto deleteP = st::sendBoxAlbumGroupDeleteButtonIconPosition;
|
||||||
|
|
||||||
|
st::sendBoxAlbumGroupEditButtonIcon.paintInCenter(
|
||||||
|
p,
|
||||||
|
QRect(groupX + editP.x(), groupY + editP.y(), size, size));
|
||||||
|
st::sendBoxAlbumGroupDeleteButtonIcon.paintInCenter(
|
||||||
|
p,
|
||||||
|
QRect(
|
||||||
|
groupX + deleteLeft + deleteP.x(),
|
||||||
|
groupY + deleteP.y(),
|
||||||
|
size,
|
||||||
|
size));
|
||||||
|
p.setOpacity(1);
|
||||||
|
|
||||||
|
return groupRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileDialogCallback(
|
||||||
|
FileDialog::OpenResult &&result,
|
||||||
|
bool isAlbum,
|
||||||
|
Fn<void(Storage::PreparedList)> callback) {
|
||||||
|
auto showBoxErrorCallback = [](tr::phrase<> text) {
|
||||||
|
Ui::show(Box<InformBox>(text(tr::now)), Ui::LayerOption::KeepOther);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto list = Storage::PreparedList::PreparedFileFromFilesDialog(
|
||||||
|
std::move(result),
|
||||||
|
isAlbum,
|
||||||
|
std::move(showBoxErrorCallback),
|
||||||
|
st::sendMediaPreviewSize);
|
||||||
|
|
||||||
|
if (!list) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(std::move(*list));
|
||||||
|
}
|
||||||
|
|
||||||
class SingleMediaPreview : public Ui::RpWidget {
|
class SingleMediaPreview : public Ui::RpWidget {
|
||||||
public:
|
public:
|
||||||
|
|
@ -123,7 +218,10 @@ class AlbumThumb {
|
||||||
public:
|
public:
|
||||||
AlbumThumb(
|
AlbumThumb(
|
||||||
const Storage::PreparedFile &file,
|
const Storage::PreparedFile &file,
|
||||||
const Ui::GroupMediaLayout &layout);
|
const Ui::GroupMediaLayout &layout,
|
||||||
|
QWidget *parent,
|
||||||
|
Fn<void()> editCallback,
|
||||||
|
Fn<void()> deleteCallback);
|
||||||
|
|
||||||
void moveToLayout(const Ui::GroupMediaLayout &layout);
|
void moveToLayout(const Ui::GroupMediaLayout &layout);
|
||||||
void animateLayoutToInitial();
|
void animateLayoutToInitial();
|
||||||
|
|
@ -141,6 +239,8 @@ public:
|
||||||
void paintFile(Painter &p, int left, int top, int outerWidth);
|
void paintFile(Painter &p, int left, int top, int outerWidth);
|
||||||
|
|
||||||
bool containsPoint(QPoint position) const;
|
bool containsPoint(QPoint position) const;
|
||||||
|
bool buttonsContainPoint(QPoint position) const;
|
||||||
|
ButtonType buttonTypeFromPoint(QPoint position) const;
|
||||||
int distanceTo(QPoint position) const;
|
int distanceTo(QPoint position) const;
|
||||||
bool isPointAfter(QPoint position) const;
|
bool isPointAfter(QPoint position) const;
|
||||||
void moveInAlbum(QPoint to);
|
void moveInAlbum(QPoint to);
|
||||||
|
|
@ -148,6 +248,8 @@ public:
|
||||||
void suggestMove(float64 delta, Fn<void()> callback);
|
void suggestMove(float64 delta, Fn<void()> callback);
|
||||||
void finishAnimations();
|
void finishAnimations();
|
||||||
|
|
||||||
|
void updateFileRow(int row);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QRect countRealGeometry() const;
|
QRect countRealGeometry() const;
|
||||||
QRect countCurrentGeometry(float64 progress) const;
|
QRect countCurrentGeometry(float64 progress) const;
|
||||||
|
|
@ -173,11 +275,19 @@ private:
|
||||||
Ui::Animations::Simple _suggestedMoveAnimation;
|
Ui::Animations::Simple _suggestedMoveAnimation;
|
||||||
int _lastShrinkValue = 0;
|
int _lastShrinkValue = 0;
|
||||||
|
|
||||||
|
QRect _lastRectOfButtons;
|
||||||
|
|
||||||
|
object_ptr<Ui::IconButton> _editMedia = nullptr;
|
||||||
|
object_ptr<Ui::IconButton> _deleteMedia = nullptr;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
AlbumThumb::AlbumThumb(
|
AlbumThumb::AlbumThumb(
|
||||||
const Storage::PreparedFile &file,
|
const Storage::PreparedFile &file,
|
||||||
const Ui::GroupMediaLayout &layout)
|
const Ui::GroupMediaLayout &layout,
|
||||||
|
QWidget *parent,
|
||||||
|
Fn<void()> editCallback,
|
||||||
|
Fn<void()> deleteCallback)
|
||||||
: _layout(layout)
|
: _layout(layout)
|
||||||
, _fullPreview(file.preview)
|
, _fullPreview(file.preview)
|
||||||
, _shrinkSize(int(std::ceil(st::historyMessageRadius / 1.4)))
|
, _shrinkSize(int(std::ceil(st::historyMessageRadius / 1.4)))
|
||||||
|
|
@ -218,7 +328,11 @@ AlbumThumb::AlbumThumb(
|
||||||
|
|
||||||
const auto availableFileWidth = st::sendMediaPreviewSize
|
const auto availableFileWidth = st::sendMediaPreviewSize
|
||||||
- st::sendMediaFileThumbSkip
|
- st::sendMediaFileThumbSkip
|
||||||
- st::sendMediaFileThumbSize;
|
- st::sendMediaFileThumbSize
|
||||||
|
// Right buttons.
|
||||||
|
- st::sendBoxAlbumGroupButtonFile.width * 2
|
||||||
|
- st::sendBoxAlbumGroupEditInternalSkip
|
||||||
|
- st::sendBoxAlbumGroupSkipRight;
|
||||||
const auto filepath = file.path;
|
const auto filepath = file.path;
|
||||||
if (filepath.isEmpty()) {
|
if (filepath.isEmpty()) {
|
||||||
_name = filedialogDefaultName(
|
_name = filedialogDefaultName(
|
||||||
|
|
@ -245,6 +359,45 @@ AlbumThumb::AlbumThumb(
|
||||||
_nameWidth = st::semiboldFont->width(_name);
|
_nameWidth = st::semiboldFont->width(_name);
|
||||||
}
|
}
|
||||||
_statusWidth = st::normalFont->width(_status);
|
_statusWidth = st::normalFont->width(_status);
|
||||||
|
|
||||||
|
_editMedia.create(parent, st::sendBoxAlbumGroupButtonFile);
|
||||||
|
_deleteMedia.create(parent, st::sendBoxAlbumGroupButtonFile);
|
||||||
|
|
||||||
|
const auto duration = st::historyAttach.ripple.hideDuration;
|
||||||
|
_editMedia->setClickedCallback(App::LambdaDelayed(
|
||||||
|
duration,
|
||||||
|
parent,
|
||||||
|
std::move(editCallback)));
|
||||||
|
_deleteMedia->setClickedCallback(App::LambdaDelayed(
|
||||||
|
duration,
|
||||||
|
parent,
|
||||||
|
std::move(deleteCallback)));
|
||||||
|
|
||||||
|
_editMedia->setIconOverride(&st::editMediaButtonIconFile);
|
||||||
|
_deleteMedia->setIconOverride(&st::sendBoxAlbumGroupDeleteButtonIconFile);
|
||||||
|
|
||||||
|
updateFileRow(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlbumThumb::updateFileRow(int row) {
|
||||||
|
if (row < 0) {
|
||||||
|
_editMedia->hide();
|
||||||
|
_deleteMedia->hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_editMedia->show();
|
||||||
|
_deleteMedia->show();
|
||||||
|
|
||||||
|
const auto fileHeight = st::sendMediaFileThumbSize
|
||||||
|
+ st::sendMediaFileThumbSkip;
|
||||||
|
|
||||||
|
const auto top = row * fileHeight + st::sendBoxAlbumGroupSkipTop;
|
||||||
|
const auto size = st::editMediaButtonSize;
|
||||||
|
|
||||||
|
auto right = st::sendBoxAlbumGroupSkipRight + size;
|
||||||
|
_deleteMedia->moveToRight(right, top);
|
||||||
|
right += st::sendBoxAlbumGroupEditInternalSkip + size;
|
||||||
|
_editMedia->moveToRight(right, top);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumThumb::resetLayoutAnimation() {
|
void AlbumThumb::resetLayoutAnimation() {
|
||||||
|
|
@ -337,6 +490,12 @@ void AlbumThumb::paintInAlbum(
|
||||||
}
|
}
|
||||||
st::historyFileThumbPlay.paintInCenter(p, inner);
|
st::historyFileThumbPlay.paintInCenter(p, inner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_lastRectOfButtons = PaintAlbumThumbButtons(
|
||||||
|
p,
|
||||||
|
{ x, y },
|
||||||
|
geometry.width(),
|
||||||
|
shrinkProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumThumb::prepareCache(QSize size, int shrink) {
|
void AlbumThumb::prepareCache(QSize size, int shrink) {
|
||||||
|
|
@ -490,6 +649,12 @@ void AlbumThumb::paintPhoto(Painter &p, int left, int top, int outerWidth) {
|
||||||
top,
|
top,
|
||||||
outerWidth,
|
outerWidth,
|
||||||
_photo);
|
_photo);
|
||||||
|
|
||||||
|
_lastRectOfButtons = PaintAlbumThumbButtons(
|
||||||
|
p,
|
||||||
|
{ left, top },
|
||||||
|
st::sendMediaPreviewSize,
|
||||||
|
0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumThumb::paintFile(Painter &p, int left, int top, int outerWidth) {
|
void AlbumThumb::paintFile(Painter &p, int left, int top, int outerWidth) {
|
||||||
|
|
@ -520,6 +685,19 @@ bool AlbumThumb::containsPoint(QPoint position) const {
|
||||||
return _layout.geometry.contains(position);
|
return _layout.geometry.contains(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AlbumThumb::buttonsContainPoint(QPoint position) const {
|
||||||
|
return _lastRectOfButtons.contains(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonType AlbumThumb::buttonTypeFromPoint(QPoint position) const {
|
||||||
|
if (!buttonsContainPoint(position)) {
|
||||||
|
return ButtonType::None;
|
||||||
|
}
|
||||||
|
return (position.x() < _lastRectOfButtons.center().x())
|
||||||
|
? ButtonType::Edit
|
||||||
|
: ButtonType::Delete;
|
||||||
|
}
|
||||||
|
|
||||||
int AlbumThumb::distanceTo(QPoint position) const {
|
int AlbumThumb::distanceTo(QPoint position) const {
|
||||||
const auto delta = (_layout.geometry.center() - position);
|
const auto delta = (_layout.geometry.center() - position);
|
||||||
return QPoint::dotProduct(delta, delta);
|
return QPoint::dotProduct(delta, delta);
|
||||||
|
|
@ -601,14 +779,12 @@ SingleMediaPreview *SingleMediaPreview::Create(
|
||||||
preview.height())) {
|
preview.height())) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
const auto sticker = (file.information->filemime == kStickerMimeString)
|
|
||||||
|| (file.information->filemime == kAnimatedStickerMimeString);
|
|
||||||
return Ui::CreateChild<SingleMediaPreview>(
|
return Ui::CreateChild<SingleMediaPreview>(
|
||||||
parent,
|
parent,
|
||||||
controller,
|
controller,
|
||||||
preview,
|
preview,
|
||||||
animated,
|
animated,
|
||||||
sticker,
|
Core::IsMimeSticker(file.information->filemime),
|
||||||
animationPreview ? file.path : QString());
|
animationPreview ? file.path : QString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -960,6 +1136,14 @@ public:
|
||||||
void setSendWay(SendFilesWay way);
|
void setSendWay(SendFilesWay way);
|
||||||
std::vector<int> takeOrder();
|
std::vector<int> takeOrder();
|
||||||
|
|
||||||
|
auto thumbDeleted() {
|
||||||
|
return _thumbDeleted.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto thumbChanged() {
|
||||||
|
return _thumbChanged.events();
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent *e) override;
|
void paintEvent(QPaintEvent *e) override;
|
||||||
void mousePressEvent(QMouseEvent *e) override;
|
void mousePressEvent(QMouseEvent *e) override;
|
||||||
|
|
@ -974,6 +1158,15 @@ private:
|
||||||
void prepareThumbs();
|
void prepareThumbs();
|
||||||
void updateSizeAnimated(const std::vector<Ui::GroupMediaLayout> &layout);
|
void updateSizeAnimated(const std::vector<Ui::GroupMediaLayout> &layout);
|
||||||
void updateSize();
|
void updateSize();
|
||||||
|
void updateFileRows();
|
||||||
|
|
||||||
|
int thumbIndex(AlbumThumb *thumb);
|
||||||
|
AlbumThumb *thumbUnderCursor();
|
||||||
|
void deleteThumbByIndex(int index);
|
||||||
|
void changeThumbByIndex(int index);
|
||||||
|
void thumbButtonsCallback(
|
||||||
|
not_null<AlbumThumb*> thumb,
|
||||||
|
ButtonType type);
|
||||||
|
|
||||||
void paintAlbum(Painter &p) const;
|
void paintAlbum(Painter &p) const;
|
||||||
void paintPhotos(Painter &p, QRect clip) const;
|
void paintPhotos(Painter &p, QRect clip) const;
|
||||||
|
|
@ -1003,6 +1196,9 @@ private:
|
||||||
AlbumThumb *_paintedAbove = nullptr;
|
AlbumThumb *_paintedAbove = nullptr;
|
||||||
QPoint _draggedStartPosition;
|
QPoint _draggedStartPosition;
|
||||||
|
|
||||||
|
rpl::event_stream<int> _thumbDeleted;
|
||||||
|
rpl::event_stream<int> _thumbChanged;
|
||||||
|
|
||||||
mutable Ui::Animations::Simple _thumbsHeightAnimation;
|
mutable Ui::Animations::Simple _thumbsHeightAnimation;
|
||||||
mutable Ui::Animations::Simple _shrinkAnimation;
|
mutable Ui::Animations::Simple _shrinkAnimation;
|
||||||
mutable Ui::Animations::Simple _finishDragAnimation;
|
mutable Ui::Animations::Simple _finishDragAnimation;
|
||||||
|
|
@ -1019,6 +1215,7 @@ SendFilesBox::AlbumPreview::AlbumPreview(
|
||||||
setMouseTracking(true);
|
setMouseTracking(true);
|
||||||
prepareThumbs();
|
prepareThumbs();
|
||||||
updateSize();
|
updateSize();
|
||||||
|
updateFileRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SendFilesBox::AlbumPreview::setSendWay(SendFilesWay way) {
|
void SendFilesBox::AlbumPreview::setSendWay(SendFilesWay way) {
|
||||||
|
|
@ -1027,9 +1224,18 @@ void SendFilesBox::AlbumPreview::setSendWay(SendFilesWay way) {
|
||||||
_sendWay = way;
|
_sendWay = way;
|
||||||
}
|
}
|
||||||
updateSize();
|
updateSize();
|
||||||
|
updateFileRows();
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SendFilesBox::AlbumPreview::updateFileRows() {
|
||||||
|
Expects(_order.size() == _thumbs.size());
|
||||||
|
const auto isFile = (_sendWay == SendFilesWay::Files);
|
||||||
|
for (auto i = 0; i < _order.size(); i++) {
|
||||||
|
_thumbs[i]->updateFileRow(isFile ? _order[i] : -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<int> SendFilesBox::AlbumPreview::takeOrder() {
|
std::vector<int> SendFilesBox::AlbumPreview::takeOrder() {
|
||||||
auto reordered = std::vector<std::unique_ptr<AlbumThumb>>();
|
auto reordered = std::vector<std::unique_ptr<AlbumThumb>>();
|
||||||
reordered.reserve(_thumbs.size());
|
reordered.reserve(_thumbs.size());
|
||||||
|
|
@ -1071,7 +1277,10 @@ void SendFilesBox::AlbumPreview::prepareThumbs() {
|
||||||
for (auto i = 0; i != count; ++i) {
|
for (auto i = 0; i != count; ++i) {
|
||||||
_thumbs.push_back(std::make_unique<AlbumThumb>(
|
_thumbs.push_back(std::make_unique<AlbumThumb>(
|
||||||
_list.files[i],
|
_list.files[i],
|
||||||
layout[i]));
|
layout[i],
|
||||||
|
this,
|
||||||
|
[=] { changeThumbByIndex(thumbIndex(thumbUnderCursor())); },
|
||||||
|
[=] { deleteThumbByIndex(thumbIndex(thumbUnderCursor())); }));
|
||||||
}
|
}
|
||||||
_thumbsHeight = countLayoutHeight(layout);
|
_thumbsHeight = countLayoutHeight(layout);
|
||||||
_photosHeight = ranges::accumulate(ranges::view::all(
|
_photosHeight = ranges::accumulate(ranges::view::all(
|
||||||
|
|
@ -1094,9 +1303,27 @@ int SendFilesBox::AlbumPreview::contentTop() const {
|
||||||
|
|
||||||
AlbumThumb *SendFilesBox::AlbumPreview::findThumb(QPoint position) const {
|
AlbumThumb *SendFilesBox::AlbumPreview::findThumb(QPoint position) const {
|
||||||
position -= QPoint(contentLeft(), contentTop());
|
position -= QPoint(contentLeft(), contentTop());
|
||||||
const auto i = ranges::find_if(_thumbs, [&](const auto &thumb) {
|
|
||||||
return thumb->containsPoint(position);
|
auto top = 0;
|
||||||
});
|
const auto isPhotosWay = (_sendWay == SendFilesWay::Photos);
|
||||||
|
const auto skip = isPhotosWay
|
||||||
|
? st::sendMediaPreviewPhotoSkip
|
||||||
|
: st::sendMediaFileThumbSkip;
|
||||||
|
auto find = [&](const auto &thumb) {
|
||||||
|
if (_sendWay == SendFilesWay::Album) {
|
||||||
|
return thumb->containsPoint(position);
|
||||||
|
} else if (isPhotosWay || _sendWay == SendFilesWay::Files) {
|
||||||
|
const auto bottom = top + (isPhotosWay
|
||||||
|
? thumb->photoHeight()
|
||||||
|
: st::sendMediaFileThumbSize);
|
||||||
|
const auto isUnderTop = (position.y() > top);
|
||||||
|
top = bottom + skip;
|
||||||
|
return isUnderTop && (position.y() < bottom);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto i = ranges::find_if(_thumbs, std::move(find));
|
||||||
return (i == _thumbs.end()) ? nullptr : i->get();
|
return (i == _thumbs.end()) ? nullptr : i->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1277,6 +1504,56 @@ void SendFilesBox::AlbumPreview::paintFiles(Painter &p, QRect clip) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int SendFilesBox::AlbumPreview::thumbIndex(AlbumThumb *thumb) {
|
||||||
|
if (!thumb) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
const auto thumbIt = ranges::find_if(_thumbs, [&](auto &t) {
|
||||||
|
return t.get() == thumb;
|
||||||
|
});
|
||||||
|
Expects(thumbIt != _thumbs.end());
|
||||||
|
return std::distance(_thumbs.begin(), thumbIt);
|
||||||
|
}
|
||||||
|
|
||||||
|
AlbumThumb *SendFilesBox::AlbumPreview::thumbUnderCursor() {
|
||||||
|
return findThumb(mapFromGlobal(QCursor::pos()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendFilesBox::AlbumPreview::deleteThumbByIndex(int index) {
|
||||||
|
if (index < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto orderIt = ranges::find(_order, index);
|
||||||
|
Expects(orderIt != _order.end());
|
||||||
|
|
||||||
|
_order.erase(orderIt);
|
||||||
|
ranges::for_each(_order, [=](auto &i) {
|
||||||
|
if (i > index) {
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_thumbDeleted.fire(std::move(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendFilesBox::AlbumPreview::changeThumbByIndex(int index) {
|
||||||
|
if (index < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_thumbChanged.fire(std::move(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendFilesBox::AlbumPreview::thumbButtonsCallback(
|
||||||
|
not_null<AlbumThumb*> thumb,
|
||||||
|
ButtonType type) {
|
||||||
|
const auto index = thumbIndex(thumb);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case ButtonType::None: return;
|
||||||
|
case ButtonType::Edit: changeThumbByIndex(index); break;
|
||||||
|
case ButtonType::Delete: deleteThumbByIndex(index); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SendFilesBox::AlbumPreview::mousePressEvent(QMouseEvent *e) {
|
void SendFilesBox::AlbumPreview::mousePressEvent(QMouseEvent *e) {
|
||||||
if (_finishDragAnimation.animating()) {
|
if (_finishDragAnimation.animating()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -1284,6 +1561,10 @@ void SendFilesBox::AlbumPreview::mousePressEvent(QMouseEvent *e) {
|
||||||
const auto position = e->pos();
|
const auto position = e->pos();
|
||||||
cancelDrag();
|
cancelDrag();
|
||||||
if (const auto thumb = findThumb(position)) {
|
if (const auto thumb = findThumb(position)) {
|
||||||
|
if (thumb->buttonsContainPoint(e->pos())) {
|
||||||
|
thumbButtonsCallback(thumb, thumb->buttonTypeFromPoint(e->pos()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
_paintedAbove = _suggestedThumb = _draggedThumb = thumb;
|
_paintedAbove = _suggestedThumb = _draggedThumb = thumb;
|
||||||
_draggedStartPosition = position;
|
_draggedStartPosition = position;
|
||||||
_shrinkAnimation.start([=] { update(); }, 0., 1., kShrinkDuration);
|
_shrinkAnimation.start([=] { update(); }, 0., 1., kShrinkDuration);
|
||||||
|
|
@ -1291,19 +1572,26 @@ void SendFilesBox::AlbumPreview::mousePressEvent(QMouseEvent *e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SendFilesBox::AlbumPreview::mouseMoveEvent(QMouseEvent *e) {
|
void SendFilesBox::AlbumPreview::mouseMoveEvent(QMouseEvent *e) {
|
||||||
if (_sendWay != SendFilesWay::Album) {
|
if (_sendWay == SendFilesWay::Files) {
|
||||||
applyCursor(style::cur_default);
|
applyCursor(style::cur_default);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_draggedThumb) {
|
const auto isAlbum = (_sendWay == SendFilesWay::Album);
|
||||||
|
if (isAlbum && _draggedThumb) {
|
||||||
const auto position = e->pos();
|
const auto position = e->pos();
|
||||||
_draggedThumb->moveInAlbum(position - _draggedStartPosition);
|
_draggedThumb->moveInAlbum(position - _draggedStartPosition);
|
||||||
updateSuggestedDrag(_draggedThumb->center());
|
updateSuggestedDrag(_draggedThumb->center());
|
||||||
update();
|
update();
|
||||||
} else {
|
} else {
|
||||||
const auto cursor = findThumb(e->pos())
|
const auto thumb = findThumb(e->pos());
|
||||||
|
const auto regularCursor = isAlbum
|
||||||
? style::cur_sizeall
|
? style::cur_sizeall
|
||||||
: style::cur_default;
|
: style::cur_default;
|
||||||
|
const auto cursor = thumb
|
||||||
|
? (thumb->buttonsContainPoint(e->pos())
|
||||||
|
? style::cur_pointer
|
||||||
|
: regularCursor)
|
||||||
|
: style::cur_default;
|
||||||
applyCursor(cursor);
|
applyCursor(cursor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1405,7 +1693,8 @@ void SendFilesBox::initPreview(rpl::producer<int> desiredPreviewHeight) {
|
||||||
) | rpl::start_with_next([=](int height) {
|
) | rpl::start_with_next([=](int height) {
|
||||||
setDimensions(
|
setDimensions(
|
||||||
st::boxWideWidth,
|
st::boxWideWidth,
|
||||||
std::min(st::sendMediaPreviewHeightMax, height));
|
std::min(st::sendMediaPreviewHeightMax, height),
|
||||||
|
true);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
if (_preview) {
|
if (_preview) {
|
||||||
|
|
@ -1414,7 +1703,7 @@ void SendFilesBox::initPreview(rpl::producer<int> desiredPreviewHeight) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SendFilesBox::prepareSingleFilePreview() {
|
void SendFilesBox::prepareSingleFilePreview() {
|
||||||
Expects(_list.files.size() == 1);
|
Expects(IsSingleItem(_list));
|
||||||
|
|
||||||
const auto &file = _list.files[0];
|
const auto &file = _list.files[0];
|
||||||
const auto media = SingleMediaPreview::Create(this, _controller, file);
|
const auto media = SingleMediaPreview::Create(this, _controller, file);
|
||||||
|
|
@ -1442,11 +1731,74 @@ void SendFilesBox::prepareAlbumPreview() {
|
||||||
this,
|
this,
|
||||||
_list,
|
_list,
|
||||||
_sendWay->value()));
|
_sendWay->value()));
|
||||||
|
|
||||||
|
addThumbButtonHandlers(wrap);
|
||||||
|
|
||||||
_preview = wrap;
|
_preview = wrap;
|
||||||
_albumPreview->show();
|
_albumPreview->show();
|
||||||
setupShadows(wrap, _albumPreview);
|
setupShadows(wrap, _albumPreview);
|
||||||
|
|
||||||
initPreview(_albumPreview->desiredHeightValue());
|
initPreview(_albumPreview->desiredHeightValue());
|
||||||
|
|
||||||
|
crl::on_main([=] {
|
||||||
|
wrap->scrollToY(_lastScrollTop);
|
||||||
|
_lastScrollTop = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendFilesBox::addThumbButtonHandlers(not_null<Ui::ScrollArea*> wrap) {
|
||||||
|
_albumPreview->thumbDeleted(
|
||||||
|
) | rpl::start_with_next([=](auto index) {
|
||||||
|
_lastScrollTop = wrap->scrollTop();
|
||||||
|
|
||||||
|
_list.files.erase(_list.files.begin() + index);
|
||||||
|
applyAlbumOrder();
|
||||||
|
|
||||||
|
if (_preview) {
|
||||||
|
_preview->deleteLater();
|
||||||
|
}
|
||||||
|
_albumPreview = nullptr;
|
||||||
|
|
||||||
|
if (IsSingleItem(_list)) {
|
||||||
|
_list.albumIsPossible = false;
|
||||||
|
if (_sendWay->value() == SendFilesWay::Album) {
|
||||||
|
_sendWay->setValue(SendFilesWay::Photos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_compressConfirm = _compressConfirmInitial;
|
||||||
|
refreshAllAfterAlbumChanges();
|
||||||
|
|
||||||
|
}, _albumPreview->lifetime());
|
||||||
|
|
||||||
|
_albumPreview->thumbChanged(
|
||||||
|
) | rpl::start_with_next([=](auto index) {
|
||||||
|
_lastScrollTop = wrap->scrollTop();
|
||||||
|
|
||||||
|
const auto callback = [=](FileDialog::OpenResult &&result) {
|
||||||
|
FileDialogCallback(
|
||||||
|
std::move(result),
|
||||||
|
true,
|
||||||
|
[=] (auto list) {
|
||||||
|
_list.files[index] = std::move(list.files.front());
|
||||||
|
applyAlbumOrder();
|
||||||
|
|
||||||
|
if (_preview) {
|
||||||
|
_preview->deleteLater();
|
||||||
|
}
|
||||||
|
_albumPreview = nullptr;
|
||||||
|
|
||||||
|
refreshAllAfterAlbumChanges();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
FileDialog::GetOpenPath(
|
||||||
|
this,
|
||||||
|
tr::lng_choose_file(tr::now),
|
||||||
|
FileDialog::AlbumFilesFilter(),
|
||||||
|
crl::guard(this, callback));
|
||||||
|
|
||||||
|
}, _albumPreview->lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SendFilesBox::setupShadows(
|
void SendFilesBox::setupShadows(
|
||||||
|
|
@ -1482,7 +1834,7 @@ void SendFilesBox::setupShadows(
|
||||||
void SendFilesBox::prepare() {
|
void SendFilesBox::prepare() {
|
||||||
_send = addButton(tr::lng_send_button(), [=] { send({}); });
|
_send = addButton(tr::lng_send_button(), [=] { send({}); });
|
||||||
if (_sendType == Api::SendType::Normal) {
|
if (_sendType == Api::SendType::Normal) {
|
||||||
SetupSendMenu(
|
SetupSendMenuAndShortcuts(
|
||||||
_send,
|
_send,
|
||||||
[=] { return _sendMenuType; },
|
[=] { return _sendMenuType; },
|
||||||
[=] { sendSilent(); },
|
[=] { sendSilent(); },
|
||||||
|
|
@ -1497,6 +1849,49 @@ void SendFilesBox::prepare() {
|
||||||
_cancelledCallback();
|
_cancelledCallback();
|
||||||
}
|
}
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
|
const auto title = tr::lng_stickers_featured_add(tr::now) + qsl("...");
|
||||||
|
_addFileToAlbum = addLeftButton(
|
||||||
|
rpl::single(title),
|
||||||
|
App::LambdaDelayed(st::historyAttach.ripple.hideDuration, this, [=] {
|
||||||
|
openDialogToAddFileToAlbum();
|
||||||
|
}));
|
||||||
|
|
||||||
|
updateLeftButtonVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendFilesBox::updateLeftButtonVisibility() {
|
||||||
|
const auto isAlbum = _list.albumIsPossible
|
||||||
|
&& (_list.files.size() < Storage::MaxAlbumItems());
|
||||||
|
if (isAlbum || (IsSingleItem(_list) && IsFirstAlbumItem(_list))) {
|
||||||
|
_addFileToAlbum->show();
|
||||||
|
} else {
|
||||||
|
_addFileToAlbum->hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendFilesBox::refreshAllAfterAlbumChanges() {
|
||||||
|
refreshAlbumMediaCount();
|
||||||
|
preparePreview();
|
||||||
|
captionResized();
|
||||||
|
updateLeftButtonVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendFilesBox::openDialogToAddFileToAlbum() {
|
||||||
|
const auto callback = [=](FileDialog::OpenResult &&result) {
|
||||||
|
FileDialogCallback(
|
||||||
|
std::move(result),
|
||||||
|
true,
|
||||||
|
[=] (auto list) {
|
||||||
|
addFiles(std::move(list));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
FileDialog::GetOpenPaths(
|
||||||
|
this,
|
||||||
|
tr::lng_choose_file(tr::now),
|
||||||
|
FileDialog::AlbumFilesFilter(),
|
||||||
|
crl::guard(this, callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SendFilesBox::initSendWay() {
|
void SendFilesBox::initSendWay() {
|
||||||
|
|
@ -1574,7 +1969,7 @@ void SendFilesBox::refreshAlbumMediaCount() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SendFilesBox::preparePreview() {
|
void SendFilesBox::preparePreview() {
|
||||||
if (_list.files.size() == 1) {
|
if (IsSingleItem(_list)) {
|
||||||
prepareSingleFilePreview();
|
prepareSingleFilePreview();
|
||||||
} else {
|
} else {
|
||||||
if (_list.albumIsPossible) {
|
if (_list.albumIsPossible) {
|
||||||
|
|
@ -1611,7 +2006,7 @@ void SendFilesBox::setupSendWayControls() {
|
||||||
addRadio(_sendAlbum, SendFilesWay::Album, tr::lng_send_album(tr::now));
|
addRadio(_sendAlbum, SendFilesWay::Album, tr::lng_send_album(tr::now));
|
||||||
}
|
}
|
||||||
if (!_list.albumIsPossible || _albumPhotosCount > 0) {
|
if (!_list.albumIsPossible || _albumPhotosCount > 0) {
|
||||||
addRadio(_sendPhotos, SendFilesWay::Photos, (_list.files.size() == 1)
|
addRadio(_sendPhotos, SendFilesWay::Photos, IsSingleItem(_list)
|
||||||
? tr::lng_send_photo(tr::now)
|
? tr::lng_send_photo(tr::now)
|
||||||
: (_albumVideosCount > 0)
|
: (_albumVideosCount > 0)
|
||||||
? tr::lng_send_separate_photos_videos(tr::now)
|
? tr::lng_send_separate_photos_videos(tr::now)
|
||||||
|
|
@ -1619,7 +2014,7 @@ void SendFilesBox::setupSendWayControls() {
|
||||||
? tr::lng_send_separate_photos(tr::now)
|
? tr::lng_send_separate_photos(tr::now)
|
||||||
: tr::lng_send_photos(tr::now, lt_count, _list.files.size())));
|
: tr::lng_send_photos(tr::now, lt_count, _list.files.size())));
|
||||||
}
|
}
|
||||||
addRadio(_sendFiles, SendFilesWay::Files, (_list.files.size() == 1)
|
addRadio(_sendFiles, SendFilesWay::Files, (IsSingleItem(_list))
|
||||||
? tr::lng_send_file(tr::now)
|
? tr::lng_send_file(tr::now)
|
||||||
: tr::lng_send_files(tr::now, lt_count, _list.files.size()));
|
: tr::lng_send_files(tr::now, lt_count, _list.files.size()));
|
||||||
}
|
}
|
||||||
|
|
@ -1746,16 +2141,9 @@ void SendFilesBox::captionResized() {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SendFilesBox::canAddUrls(const QList<QUrl> &urls) const {
|
|
||||||
return !urls.isEmpty() && ranges::find_if(
|
|
||||||
urls,
|
|
||||||
[](const QUrl &url) { return !url.isLocalFile(); }
|
|
||||||
) == urls.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SendFilesBox::canAddFiles(not_null<const QMimeData*> data) const {
|
bool SendFilesBox::canAddFiles(not_null<const QMimeData*> data) const {
|
||||||
const auto urls = data->hasUrls() ? data->urls() : QList<QUrl>();
|
const auto urls = data->hasUrls() ? data->urls() : QList<QUrl>();
|
||||||
auto filesCount = canAddUrls(urls) ? urls.size() : 0;
|
auto filesCount = CanAddUrls(urls) ? urls.size() : 0;
|
||||||
if (!filesCount && data->hasImage()) {
|
if (!filesCount && data->hasImage()) {
|
||||||
++filesCount;
|
++filesCount;
|
||||||
}
|
}
|
||||||
|
|
@ -1764,8 +2152,7 @@ bool SendFilesBox::canAddFiles(not_null<const QMimeData*> data) const {
|
||||||
return false;
|
return false;
|
||||||
} else if (_list.files.size() > 1 && !_albumPreview) {
|
} else if (_list.files.size() > 1 && !_albumPreview) {
|
||||||
return false;
|
return false;
|
||||||
} else if (_list.files.front().type
|
} else if (!IsFirstAlbumItem(_list)) {
|
||||||
== Storage::PreparedFile::AlbumType::None) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -1774,7 +2161,7 @@ bool SendFilesBox::canAddFiles(not_null<const QMimeData*> data) const {
|
||||||
bool SendFilesBox::addFiles(not_null<const QMimeData*> data) {
|
bool SendFilesBox::addFiles(not_null<const QMimeData*> data) {
|
||||||
auto list = [&] {
|
auto list = [&] {
|
||||||
const auto urls = data->hasUrls() ? data->urls() : QList<QUrl>();
|
const auto urls = data->hasUrls() ? data->urls() : QList<QUrl>();
|
||||||
auto result = canAddUrls(urls)
|
auto result = CanAddUrls(urls)
|
||||||
? Storage::PrepareMediaList(urls, st::sendMediaPreviewSize)
|
? Storage::PrepareMediaList(urls, st::sendMediaPreviewSize)
|
||||||
: Storage::PreparedList(
|
: Storage::PreparedList(
|
||||||
Storage::PreparedList::Error::EmptyFile,
|
Storage::PreparedList::Error::EmptyFile,
|
||||||
|
|
@ -1792,35 +2179,35 @@ bool SendFilesBox::addFiles(not_null<const QMimeData*> data) {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}();
|
}();
|
||||||
if (_list.files.size() + list.files.size() > Storage::MaxAlbumItems()) {
|
return addFiles(std::move(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SendFilesBox::addFiles(Storage::PreparedList list) {
|
||||||
|
const auto sumFiles = _list.files.size() + list.files.size();
|
||||||
|
const auto cutToAlbumSize = (sumFiles > Storage::MaxAlbumItems());
|
||||||
|
if (list.error != Storage::PreparedList::Error::None) {
|
||||||
return false;
|
return false;
|
||||||
} else if (list.error != Storage::PreparedList::Error::None) {
|
} else if (!IsSingleItem(list) && !list.albumIsPossible) {
|
||||||
return false;
|
return false;
|
||||||
} else if (list.files.size() != 1 && !list.albumIsPossible) {
|
} else if (!IsFirstAlbumItem(list)) {
|
||||||
return false;
|
|
||||||
} else if (list.files.front().type
|
|
||||||
== Storage::PreparedFile::AlbumType::None) {
|
|
||||||
return false;
|
return false;
|
||||||
} else if (_list.files.size() > 1 && !_albumPreview) {
|
} else if (_list.files.size() > 1 && !_albumPreview) {
|
||||||
return false;
|
return false;
|
||||||
} else if (_list.files.front().type
|
} else if (!IsFirstAlbumItem(_list)) {
|
||||||
== Storage::PreparedFile::AlbumType::None) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
applyAlbumOrder();
|
applyAlbumOrder();
|
||||||
delete base::take(_preview);
|
delete base::take(_preview);
|
||||||
_albumPreview = nullptr;
|
_albumPreview = nullptr;
|
||||||
|
|
||||||
if (_list.files.size() == 1
|
if (IsSingleItem(_list)
|
||||||
&& _sendWay->value() == SendFilesWay::Photos) {
|
&& _sendWay->value() == SendFilesWay::Photos) {
|
||||||
_sendWay->setValue(SendFilesWay::Album);
|
_sendWay->setValue(SendFilesWay::Album);
|
||||||
}
|
}
|
||||||
_list.mergeToEnd(std::move(list));
|
_list.mergeToEnd(std::move(list), cutToAlbumSize);
|
||||||
|
|
||||||
_compressConfirm = _compressConfirmInitial;
|
_compressConfirm = _compressConfirmInitial;
|
||||||
refreshAlbumMediaCount();
|
refreshAllAfterAlbumChanges();
|
||||||
preparePreview();
|
|
||||||
captionResized();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1858,7 +2245,9 @@ void SendFilesBox::updateBoxSize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SendFilesBox::keyPressEvent(QKeyEvent *e) {
|
void SendFilesBox::keyPressEvent(QKeyEvent *e) {
|
||||||
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
|
if (e->matches(QKeySequence::Open) && !_addFileToAlbum->isHidden()) {
|
||||||
|
openDialogToAddFileToAlbum();
|
||||||
|
} else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
|
||||||
const auto modifiers = e->modifiers();
|
const auto modifiers = e->modifiers();
|
||||||
const auto ctrl = modifiers.testFlag(Qt::ControlModifier)
|
const auto ctrl = modifiers.testFlag(Qt::ControlModifier)
|
||||||
|| modifiers.testFlag(Qt::MetaModifier);
|
|| modifiers.testFlag(Qt::MetaModifier);
|
||||||
|
|
@ -1937,7 +2326,9 @@ void SendFilesBox::setInnerFocus() {
|
||||||
void SendFilesBox::send(
|
void SendFilesBox::send(
|
||||||
Api::SendOptions options,
|
Api::SendOptions options,
|
||||||
bool ctrlShiftEnter) {
|
bool ctrlShiftEnter) {
|
||||||
if (_sendType == Api::SendType::Scheduled && !options.scheduled) {
|
if ((_sendType == Api::SendType::Scheduled
|
||||||
|
|| _sendType == Api::SendType::ScheduledToUser)
|
||||||
|
&& !options.scheduled) {
|
||||||
return sendScheduled();
|
return sendScheduled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1982,9 +2373,12 @@ void SendFilesBox::sendSilent() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SendFilesBox::sendScheduled() {
|
void SendFilesBox::sendScheduled() {
|
||||||
|
const auto type = (_sendType == Api::SendType::ScheduledToUser)
|
||||||
|
? SendMenuType::ScheduledToUser
|
||||||
|
: _sendMenuType;
|
||||||
const auto callback = [=](Api::SendOptions options) { send(options); };
|
const auto callback = [=](Api::SendOptions options) { send(options); };
|
||||||
Ui::show(
|
Ui::show(
|
||||||
HistoryView::PrepareScheduleBox(this, _sendMenuType, callback),
|
HistoryView::PrepareScheduleBox(this, type, callback),
|
||||||
Ui::LayerOption::KeepOther);
|
Ui::LayerOption::KeepOther);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -120,9 +120,15 @@ private:
|
||||||
void updateControlsGeometry();
|
void updateControlsGeometry();
|
||||||
void updateCaptionPlaceholder();
|
void updateCaptionPlaceholder();
|
||||||
|
|
||||||
|
void addThumbButtonHandlers(not_null<Ui::ScrollArea*> wrap);
|
||||||
|
|
||||||
bool canAddFiles(not_null<const QMimeData*> data) const;
|
bool canAddFiles(not_null<const QMimeData*> data) const;
|
||||||
bool canAddUrls(const QList<QUrl> &urls) const;
|
|
||||||
bool addFiles(not_null<const QMimeData*> data);
|
bool addFiles(not_null<const QMimeData*> data);
|
||||||
|
bool addFiles(Storage::PreparedList list);
|
||||||
|
|
||||||
|
void openDialogToAddFileToAlbum();
|
||||||
|
void updateLeftButtonVisibility();
|
||||||
|
void refreshAllAfterAlbumChanges();
|
||||||
|
|
||||||
const not_null<Window::SessionController*> _controller;
|
const not_null<Window::SessionController*> _controller;
|
||||||
const Api::SendType _sendType = Api::SendType();
|
const Api::SendType _sendType = Api::SendType();
|
||||||
|
|
@ -163,6 +169,9 @@ private:
|
||||||
int _albumVideosCount = 0;
|
int _albumVideosCount = 0;
|
||||||
int _albumPhotosCount = 0;
|
int _albumPhotosCount = 0;
|
||||||
|
|
||||||
|
int _lastScrollTop = 0;
|
||||||
|
|
||||||
QPointer<Ui::RoundButton> _send;
|
QPointer<Ui::RoundButton> _send;
|
||||||
|
QPointer<Ui::RoundButton> _addFileToAlbum;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -413,7 +413,9 @@ void ShareBox::keyPressEvent(QKeyEvent *e) {
|
||||||
|
|
||||||
SendMenuType ShareBox::sendMenuType() const {
|
SendMenuType ShareBox::sendMenuType() const {
|
||||||
const auto selected = _inner->selected();
|
const auto selected = _inner->selected();
|
||||||
return (selected.size() == 1 && selected.front()->isSelf())
|
return ranges::all_of(selected, HistoryView::CanScheduleUntilOnline)
|
||||||
|
? SendMenuType::ScheduledToUser
|
||||||
|
: (selected.size() == 1 && selected.front()->isSelf())
|
||||||
? SendMenuType::Reminder
|
? SendMenuType::Reminder
|
||||||
: SendMenuType::Scheduled;
|
: SendMenuType::Scheduled;
|
||||||
}
|
}
|
||||||
|
|
@ -424,7 +426,7 @@ void ShareBox::createButtons() {
|
||||||
const auto send = addButton(tr::lng_share_confirm(), [=] {
|
const auto send = addButton(tr::lng_share_confirm(), [=] {
|
||||||
submit({});
|
submit({});
|
||||||
});
|
});
|
||||||
SetupSendMenu(
|
SetupSendMenuAndShortcuts(
|
||||||
send,
|
send,
|
||||||
[=] { return sendMenuType(); },
|
[=] { return sendMenuType(); },
|
||||||
[=] { submitSilent(); },
|
[=] { submitSilent(); },
|
||||||
|
|
|
||||||
|
|
@ -435,7 +435,9 @@ void EmojiKeywords::LangPack::applyDifference(
|
||||||
LangPackData &&result) {
|
LangPackData &&result) {
|
||||||
applyData(std::move(result));
|
applyData(std::move(result));
|
||||||
});
|
});
|
||||||
crl::async([=, callback = std::move(callback)]() mutable {
|
crl::async([=,
|
||||||
|
copy = std::move(copy),
|
||||||
|
callback = std::move(callback)]() mutable {
|
||||||
ApplyDifference(copy, keywords, version);
|
ApplyDifference(copy, keywords, version);
|
||||||
WriteLocalCache(id, copy);
|
WriteLocalCache(id, copy);
|
||||||
crl::on_main([
|
crl::on_main([
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "base/qthelp_url.h"
|
#include "base/qthelp_url.h"
|
||||||
#include "base/event_filter.h"
|
#include "base/event_filter.h"
|
||||||
#include "boxes/abstract_box.h"
|
#include "boxes/abstract_box.h"
|
||||||
|
#include "core/shortcuts.h"
|
||||||
#include "ui/wrap/vertical_layout.h"
|
#include "ui/wrap/vertical_layout.h"
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "ui/ui_utility.h"
|
#include "ui/ui_utility.h"
|
||||||
|
|
@ -33,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include <QtGui/QGuiApplication>
|
#include <QtGui/QGuiApplication>
|
||||||
#include <QtGui/QTextBlock>
|
#include <QtGui/QTextBlock>
|
||||||
#include <QtGui/QClipboard>
|
#include <QtGui/QClipboard>
|
||||||
|
#include <QtWidgets/QApplication>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
|
@ -666,7 +668,7 @@ void MessageLinksParser::apply(
|
||||||
_list = std::move(parsed);
|
_list = std::move(parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetupSendMenu(
|
void SetupSendMenuAndShortcuts(
|
||||||
not_null<Ui::RpWidget*> button,
|
not_null<Ui::RpWidget*> button,
|
||||||
Fn<SendMenuType()> type,
|
Fn<SendMenuType()> type,
|
||||||
Fn<void()> silent,
|
Fn<void()> silent,
|
||||||
|
|
@ -688,9 +690,9 @@ void SetupSendMenu(
|
||||||
}
|
}
|
||||||
if (schedule && now != SendMenuType::SilentOnly) {
|
if (schedule && now != SendMenuType::SilentOnly) {
|
||||||
(*menu)->addAction(
|
(*menu)->addAction(
|
||||||
(now == SendMenuType::Scheduled
|
(now == SendMenuType::Reminder
|
||||||
? tr::lng_schedule_message(tr::now)
|
? tr::lng_reminder_message(tr::now)
|
||||||
: tr::lng_reminder_message(tr::now)),
|
: tr::lng_schedule_message(tr::now)),
|
||||||
schedule);
|
schedule);
|
||||||
}
|
}
|
||||||
(*menu)->popup(QCursor::pos());
|
(*menu)->popup(QCursor::pos());
|
||||||
|
|
@ -702,4 +704,46 @@ void SetupSendMenu(
|
||||||
}
|
}
|
||||||
return base::EventFilterResult::Continue;
|
return base::EventFilterResult::Continue;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Shortcuts::Requests(
|
||||||
|
) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
|
||||||
|
using Command = Shortcuts::Command;
|
||||||
|
|
||||||
|
const auto now = type();
|
||||||
|
if (now == SendMenuType::Disabled
|
||||||
|
|| (!silent && now == SendMenuType::SilentOnly)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(silent
|
||||||
|
&& (now != SendMenuType::Reminder)
|
||||||
|
&& request->check(Command::SendSilentMessage)
|
||||||
|
&& request->handle([=] {
|
||||||
|
silent();
|
||||||
|
return true;
|
||||||
|
}))
|
||||||
|
||
|
||||||
|
(schedule
|
||||||
|
&& (now != SendMenuType::SilentOnly)
|
||||||
|
&& request->check(Command::ScheduleMessage)
|
||||||
|
&& request->handle([=] {
|
||||||
|
schedule();
|
||||||
|
return true;
|
||||||
|
}))
|
||||||
|
||
|
||||||
|
(request->check(Command::JustSendMessage) && request->handle([=] {
|
||||||
|
const auto post = [&](QEvent::Type type) {
|
||||||
|
QApplication::postEvent(
|
||||||
|
button,
|
||||||
|
new QMouseEvent(
|
||||||
|
type,
|
||||||
|
QPointF(0, 0),
|
||||||
|
Qt::LeftButton,
|
||||||
|
Qt::LeftButton,
|
||||||
|
Qt::NoModifier));
|
||||||
|
};
|
||||||
|
post(QEvent::MouseButtonPress);
|
||||||
|
post(QEvent::MouseButtonRelease);
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
|
}, button->lifetime());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,10 +99,11 @@ enum class SendMenuType {
|
||||||
Disabled,
|
Disabled,
|
||||||
SilentOnly,
|
SilentOnly,
|
||||||
Scheduled,
|
Scheduled,
|
||||||
|
ScheduledToUser, // For "Send when online".
|
||||||
Reminder,
|
Reminder,
|
||||||
};
|
};
|
||||||
|
|
||||||
void SetupSendMenu(
|
void SetupSendMenuAndShortcuts(
|
||||||
not_null<Ui::RpWidget*> button,
|
not_null<Ui::RpWidget*> button,
|
||||||
Fn<SendMenuType()> type,
|
Fn<SendMenuType()> type,
|
||||||
Fn<void()> silent,
|
Fn<void()> silent,
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,6 @@ enum {
|
||||||
|
|
||||||
StickerMaxSize = 2048, // 2048x2048 is a max image size for sticker
|
StickerMaxSize = 2048, // 2048x2048 is a max image size for sticker
|
||||||
|
|
||||||
MaxZoomLevel = 7, // x8
|
|
||||||
ZoomToScreenLevel = 1024, // just constant
|
ZoomToScreenLevel = 1024, // just constant
|
||||||
|
|
||||||
PreloadHeightsCount = 3, // when 3 screens to scroll left make a preload request
|
PreloadHeightsCount = 3, // when 3 screens to scroll left make a preload request
|
||||||
|
|
|
||||||
|
|
@ -295,6 +295,10 @@ QString AllFilesFilter() {
|
||||||
#endif // Q_OS_WIN
|
#endif // Q_OS_WIN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString AlbumFilesFilter() {
|
||||||
|
return qsl("Image and Video Files (*.png *.jpg *.mp4 *.jpeg)");
|
||||||
|
}
|
||||||
|
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
void InitLastPathDefault() {
|
void InitLastPathDefault() {
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ void GetFolder(
|
||||||
Fn<void()> failed = Fn<void()>());
|
Fn<void()> failed = Fn<void()>());
|
||||||
|
|
||||||
QString AllFilesFilter();
|
QString AllFilesFilter();
|
||||||
|
QString AlbumFilesFilter();
|
||||||
|
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -248,10 +248,8 @@ void Launcher::init() {
|
||||||
|
|
||||||
QApplication::setApplicationName(qsl("KotatogramDesktop"));
|
QApplication::setApplicationName(qsl("KotatogramDesktop"));
|
||||||
|
|
||||||
#ifdef TDESKTOP_LAUNCHER_FILENAME
|
#if defined(Q_OS_LINUX) && QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
|
||||||
QApplication::setDesktopFileName(qsl(MACRO_TO_STRING(TDESKTOP_LAUNCHER_FILENAME)));
|
QApplication::setDesktopFileName(qsl(MACRO_TO_STRING(TDESKTOP_LAUNCHER_BASENAME)) + ".desktop");
|
||||||
#elif defined(Q_OS_LINUX) && QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
|
|
||||||
QApplication::setDesktopFileName(qsl("kotatogramdesktop.desktop"));
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef OS_MAC_OLD
|
#ifndef OS_MAC_OLD
|
||||||
|
|
|
||||||
|
|
@ -102,4 +102,9 @@ MimeType MimeTypeForData(const QByteArray &data) {
|
||||||
return MimeType(QMimeDatabase().mimeTypeForData(data));
|
return MimeType(QMimeDatabase().mimeTypeForData(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsMimeSticker(const QString &mime) {
|
||||||
|
return mime == qsl("image/webp")
|
||||||
|
|| mime == qsl("application/x-tgsticker");
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
||||||
|
|
@ -39,4 +39,6 @@ MimeType MimeTypeForName(const QString &mime);
|
||||||
MimeType MimeTypeForFile(const QFileInfo &file);
|
MimeType MimeTypeForFile(const QFileInfo &file);
|
||||||
MimeType MimeTypeForData(const QByteArray &data);
|
MimeType MimeTypeForData(const QByteArray &data);
|
||||||
|
|
||||||
|
bool IsMimeSticker(const QString &mime);
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
||||||
|
|
@ -98,12 +98,7 @@ int Sandbox::start() {
|
||||||
const auto d = QFile::encodeName(QDir(cWorkingDir()).absolutePath());
|
const auto d = QFile::encodeName(QDir(cWorkingDir()).absolutePath());
|
||||||
char h[33] = { 0 };
|
char h[33] = { 0 };
|
||||||
hashMd5Hex(d.constData(), d.size(), h);
|
hashMd5Hex(d.constData(), d.size(), h);
|
||||||
#ifndef OS_MAC_STORE
|
_localServerName = Platform::SingleInstanceLocalServerName(h);
|
||||||
_localServerName = psServerPrefix() + h + '-' + cGUIDStr();
|
|
||||||
#else // OS_MAC_STORE
|
|
||||||
h[4] = 0; // use only first 4 chars
|
|
||||||
_localServerName = psServerPrefix() + h;
|
|
||||||
#endif // OS_MAC_STORE
|
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
&_localSocket,
|
&_localSocket,
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,12 @@ const auto CommandByName = base::flat_map<QString, Command>{
|
||||||
|
|
||||||
{ qsl("show_archive") , Command::ShowArchive },
|
{ qsl("show_archive") , Command::ShowArchive },
|
||||||
|
|
||||||
|
// Shortcuts that have no default values.
|
||||||
|
{ qsl("message") , Command::JustSendMessage },
|
||||||
|
{ qsl("message_silently") , Command::SendSilentMessage },
|
||||||
|
{ qsl("message_scheduled"), Command::ScheduleMessage },
|
||||||
|
//
|
||||||
|
|
||||||
{ qsl("save_draft") , Command::SaveDraft },
|
{ qsl("save_draft") , Command::SaveDraft },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,10 @@ enum class Command {
|
||||||
|
|
||||||
ShowArchive,
|
ShowArchive,
|
||||||
|
|
||||||
|
JustSendMessage,
|
||||||
|
SendSilentMessage,
|
||||||
|
ScheduleMessage,
|
||||||
|
|
||||||
SupportReloadTemplates,
|
SupportReloadTemplates,
|
||||||
SupportToggleMuted,
|
SupportToggleMuted,
|
||||||
SupportScrollToCurrent,
|
SupportScrollToCurrent,
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#define TDESKTOP_ALPHA_VERSION (0ULL)
|
#define TDESKTOP_ALPHA_VERSION (0ULL)
|
||||||
#endif // TDESKTOP_ALLOW_CLOSED_ALPHA
|
#endif // TDESKTOP_ALLOW_CLOSED_ALPHA
|
||||||
|
|
||||||
constexpr auto AppVersion = 1009004;
|
constexpr auto AppVersion = 1009007;
|
||||||
constexpr auto AppVersionStr = "1.9.4";
|
constexpr auto AppVersionStr = "1.9.7";
|
||||||
constexpr auto AppBetaVersion = false;
|
constexpr auto AppBetaVersion = false;
|
||||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||||
constexpr auto AppKotatoVersion = 1001003;
|
constexpr auto AppKotatoVersion = 1001003;
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,6 @@ void CloudThemes::applyUpdate(const MTPTheme &theme) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
applyFromDocument(cloud);
|
applyFromDocument(cloud);
|
||||||
}, [&](const MTPDthemeDocumentNotModified &data) {
|
|
||||||
});
|
});
|
||||||
scheduleReload();
|
scheduleReload();
|
||||||
}
|
}
|
||||||
|
|
@ -160,8 +159,6 @@ void CloudThemes::resolve(
|
||||||
void CloudThemes::showPreview(const MTPTheme &data) {
|
void CloudThemes::showPreview(const MTPTheme &data) {
|
||||||
data.match([&](const MTPDtheme &data) {
|
data.match([&](const MTPDtheme &data) {
|
||||||
showPreview(CloudTheme::Parse(_session, data));
|
showPreview(CloudTheme::Parse(_session, data));
|
||||||
}, [&](const MTPDthemeDocumentNotModified &data) {
|
|
||||||
LOG(("API Error: Unexpected themeDocumentNotModified."));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -269,8 +266,6 @@ void CloudThemes::parseThemes(const QVector<MTPTheme> &list) {
|
||||||
for (const auto &theme : list) {
|
for (const auto &theme : list) {
|
||||||
theme.match([&](const MTPDtheme &data) {
|
theme.match([&](const MTPDtheme &data) {
|
||||||
_list.push_back(CloudTheme::Parse(_session, data));
|
_list.push_back(CloudTheme::Parse(_session, data));
|
||||||
}, [&](const MTPDthemeDocumentNotModified &data) {
|
|
||||||
LOG(("API Error: Unexpected themeDocumentNotModified."));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
checkCurrentTheme();
|
checkCurrentTheme();
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,6 @@ struct FileReferenceAccumulator {
|
||||||
if (const auto document = data.vdocument()) {
|
if (const auto document = data.vdocument()) {
|
||||||
push(*document);
|
push(*document);
|
||||||
}
|
}
|
||||||
}, [&](const MTPDthemeDocumentNotModified &data) {
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
void push(const MTPWebPageAttribute &data) {
|
void push(const MTPWebPageAttribute &data) {
|
||||||
|
|
|
||||||
|
|
@ -1302,6 +1302,9 @@ TextForMimeData MediaPoll::clipboardText() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MediaPoll::errorTextForForward(not_null<PeerData*> peer) const {
|
QString MediaPoll::errorTextForForward(not_null<PeerData*> peer) const {
|
||||||
|
if (_poll->publicVotes() && peer->isChannel() && !peer->isMegagroup()) {
|
||||||
|
return tr::lng_restricted_send_public_polls(tr::now);
|
||||||
|
}
|
||||||
return Data::RestrictionError(
|
return Data::RestrictionError(
|
||||||
peer,
|
peer,
|
||||||
ChatRestriction::f_send_polls
|
ChatRestriction::f_send_polls
|
||||||
|
|
|
||||||
|
|
@ -754,6 +754,17 @@ int PeerData::slowmodeSecondsLeft() const {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PeerData::canSendPolls() const {
|
||||||
|
if (const auto user = asUser()) {
|
||||||
|
return user->isBot();
|
||||||
|
} else if (const auto chat = asChat()) {
|
||||||
|
return chat->canSendPolls();
|
||||||
|
} else if (const auto channel = asChannel()) {
|
||||||
|
return channel->canSendPolls();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
|
||||||
std::vector<ChatRestrictions> ListOfRestrictions() {
|
std::vector<ChatRestrictions> ListOfRestrictions() {
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,7 @@ public:
|
||||||
[[nodiscard]] bool canRevokeFullHistory() const;
|
[[nodiscard]] bool canRevokeFullHistory() const;
|
||||||
[[nodiscard]] bool slowmodeApplied() const;
|
[[nodiscard]] bool slowmodeApplied() const;
|
||||||
[[nodiscard]] int slowmodeSecondsLeft() const;
|
[[nodiscard]] int slowmodeSecondsLeft() const;
|
||||||
|
[[nodiscard]] bool canSendPolls() const;
|
||||||
|
|
||||||
[[nodiscard]] UserData *asUser();
|
[[nodiscard]] UserData *asUser();
|
||||||
[[nodiscard]] const UserData *asUser() const;
|
[[nodiscard]] const UserData *asUser() const;
|
||||||
|
|
@ -366,7 +367,7 @@ private:
|
||||||
|
|
||||||
static constexpr auto kUnknownPhotoId = PhotoId(0xFFFFFFFFFFFFFFFFULL);
|
static constexpr auto kUnknownPhotoId = PhotoId(0xFFFFFFFFFFFFFFFFULL);
|
||||||
|
|
||||||
not_null<Data::Session*> _owner;
|
const not_null<Data::Session*> _owner;
|
||||||
|
|
||||||
ImagePtr _userpic;
|
ImagePtr _userpic;
|
||||||
PhotoId _userpicPhotoId = kUnknownPhotoId;
|
PhotoId _userpicPhotoId = kUnknownPhotoId;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_poll.h"
|
#include "data/data_poll.h"
|
||||||
|
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
@ -34,14 +36,19 @@ PollAnswer *AnswerByOption(
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
PollData::PollData(PollId id) : id(id) {
|
PollData::PollData(not_null<Data::Session*> owner, PollId id)
|
||||||
|
: id(id)
|
||||||
|
, _owner(owner) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PollData::applyChanges(const MTPDpoll &poll) {
|
bool PollData::applyChanges(const MTPDpoll &poll) {
|
||||||
Expects(poll.vid().v == id);
|
Expects(poll.vid().v == id);
|
||||||
|
|
||||||
const auto newQuestion = qs(poll.vquestion());
|
const auto newQuestion = qs(poll.vquestion());
|
||||||
const auto newClosed = poll.is_closed();
|
const auto newFlags = (poll.is_closed() ? Flag::Closed : Flag(0))
|
||||||
|
| (poll.is_public_voters() ? Flag::PublicVotes : Flag(0))
|
||||||
|
| (poll.is_multiple_choice() ? Flag::MultiChoice : Flag(0))
|
||||||
|
| (poll.is_quiz() ? Flag::Quiz : Flag(0));
|
||||||
auto newAnswers = ranges::view::all(
|
auto newAnswers = ranges::view::all(
|
||||||
poll.vanswers().v
|
poll.vanswers().v
|
||||||
) | ranges::view::transform([](const MTPPollAnswer &data) {
|
) | ranges::view::transform([](const MTPPollAnswer &data) {
|
||||||
|
|
@ -56,14 +63,14 @@ bool PollData::applyChanges(const MTPDpoll &poll) {
|
||||||
) | ranges::to_vector;
|
) | ranges::to_vector;
|
||||||
|
|
||||||
const auto changed1 = (question != newQuestion)
|
const auto changed1 = (question != newQuestion)
|
||||||
|| (closed != newClosed);
|
|| (_flags != newFlags);
|
||||||
const auto changed2 = (answers != newAnswers);
|
const auto changed2 = (answers != newAnswers);
|
||||||
if (!changed1 && !changed2) {
|
if (!changed1 && !changed2) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (changed1) {
|
if (changed1) {
|
||||||
question = newQuestion;
|
question = newQuestion;
|
||||||
closed = newClosed;
|
_flags = newFlags;
|
||||||
}
|
}
|
||||||
if (changed2) {
|
if (changed2) {
|
||||||
std::swap(answers, newAnswers);
|
std::swap(answers, newAnswers);
|
||||||
|
|
@ -71,6 +78,7 @@ bool PollData::applyChanges(const MTPDpoll &poll) {
|
||||||
if (const auto current = answerByOption(old.option)) {
|
if (const auto current = answerByOption(old.option)) {
|
||||||
current->votes = old.votes;
|
current->votes = old.votes;
|
||||||
current->chosen = old.chosen;
|
current->chosen = old.chosen;
|
||||||
|
current->correct = old.correct;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -92,6 +100,29 @@ bool PollData::applyResults(const MTPPollResults &results) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (const auto recent = results.vrecent_voters()) {
|
||||||
|
const auto recentChanged = !ranges::equal(
|
||||||
|
recentVoters,
|
||||||
|
recent->v,
|
||||||
|
ranges::equal_to(),
|
||||||
|
&UserData::id,
|
||||||
|
&MTPint::v);
|
||||||
|
if (recentChanged) {
|
||||||
|
changed = true;
|
||||||
|
recentVoters = ranges::view::all(
|
||||||
|
recent->v
|
||||||
|
) | ranges::view::transform([&](MTPint userId) {
|
||||||
|
const auto user = _owner->user(userId.v);
|
||||||
|
return (user->loadedStatus != PeerData::NotLoaded)
|
||||||
|
? user.get()
|
||||||
|
: nullptr;
|
||||||
|
}) | ranges::view::filter([](UserData *user) {
|
||||||
|
return user != nullptr;
|
||||||
|
}) | ranges::view::transform([](UserData *user) {
|
||||||
|
return not_null<UserData*>(user);
|
||||||
|
}) | ranges::to_vector;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!changed) {
|
if (!changed) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -104,11 +135,11 @@ bool PollData::applyResults(const MTPPollResults &results) {
|
||||||
void PollData::checkResultsReload(not_null<HistoryItem*> item, crl::time now) {
|
void PollData::checkResultsReload(not_null<HistoryItem*> item, crl::time now) {
|
||||||
if (lastResultsUpdate && lastResultsUpdate + kShortPollTimeout > now) {
|
if (lastResultsUpdate && lastResultsUpdate + kShortPollTimeout > now) {
|
||||||
return;
|
return;
|
||||||
} else if (closed) {
|
} else if (closed()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lastResultsUpdate = now;
|
lastResultsUpdate = now;
|
||||||
Auth().api().reloadPollResults(item);
|
_owner->session().api().reloadPollResults(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
PollAnswer *PollData::answerByOption(const QByteArray &option) {
|
PollAnswer *PollData::answerByOption(const QByteArray &option) {
|
||||||
|
|
@ -137,18 +168,50 @@ bool PollData::applyResultToAnswers(
|
||||||
answer->chosen = voters.is_chosen();
|
answer->chosen = voters.is_chosen();
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
if (answer->correct != voters.is_correct()) {
|
||||||
|
answer->correct = voters.is_correct();
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
} else if (const auto existing = answerByOption(option)) {
|
} else if (const auto existing = answerByOption(option)) {
|
||||||
answer->chosen = existing->chosen;
|
answer->chosen = existing->chosen;
|
||||||
|
answer->correct = existing->correct;
|
||||||
}
|
}
|
||||||
return changed;
|
return changed;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PollData::setFlags(Flags flags) {
|
||||||
|
if (_flags != flags) {
|
||||||
|
_flags = flags;
|
||||||
|
++version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PollData::Flags PollData::flags() const {
|
||||||
|
return _flags;
|
||||||
|
}
|
||||||
|
|
||||||
bool PollData::voted() const {
|
bool PollData::voted() const {
|
||||||
return ranges::find(answers, true, &PollAnswer::chosen) != end(answers);
|
return ranges::find(answers, true, &PollAnswer::chosen) != end(answers);
|
||||||
}
|
}
|
||||||
|
|
||||||
MTPPoll PollDataToMTP(not_null<const PollData*> poll) {
|
bool PollData::closed() const {
|
||||||
|
return (_flags & Flag::Closed);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PollData::publicVotes() const {
|
||||||
|
return (_flags & Flag::PublicVotes);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PollData::multiChoice() const {
|
||||||
|
return (_flags & Flag::MultiChoice);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PollData::quiz() const {
|
||||||
|
return (_flags & Flag::Quiz);
|
||||||
|
}
|
||||||
|
|
||||||
|
MTPPoll PollDataToMTP(not_null<const PollData*> poll, bool close) {
|
||||||
const auto convert = [](const PollAnswer &answer) {
|
const auto convert = [](const PollAnswer &answer) {
|
||||||
return MTP_pollAnswer(
|
return MTP_pollAnswer(
|
||||||
MTP_string(answer.text),
|
MTP_string(answer.text),
|
||||||
|
|
@ -160,9 +223,14 @@ MTPPoll PollDataToMTP(not_null<const PollData*> poll) {
|
||||||
poll->answers,
|
poll->answers,
|
||||||
ranges::back_inserter(answers),
|
ranges::back_inserter(answers),
|
||||||
convert);
|
convert);
|
||||||
|
using Flag = MTPDpoll::Flag;
|
||||||
|
const auto flags = ((poll->closed() || close) ? Flag::f_closed : Flag(0))
|
||||||
|
| (poll->multiChoice() ? Flag::f_multiple_choice : Flag(0))
|
||||||
|
| (poll->publicVotes() ? Flag::f_public_voters : Flag(0))
|
||||||
|
| (poll->quiz() ? Flag::f_quiz : Flag(0));
|
||||||
return MTP_poll(
|
return MTP_poll(
|
||||||
MTP_long(poll->id),
|
MTP_long(poll->id),
|
||||||
MTP_flags(MTPDpoll::Flag::f_closed),
|
MTP_flags(flags),
|
||||||
MTP_string(poll->question),
|
MTP_string(poll->question),
|
||||||
MTP_vector<MTPPollAnswer>(answers));
|
MTP_vector<MTPPollAnswer>(answers));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
class Session;
|
||||||
|
} // namespace Data
|
||||||
|
|
||||||
struct PollAnswer {
|
struct PollAnswer {
|
||||||
QString text;
|
QString text;
|
||||||
QByteArray option;
|
QByteArray option;
|
||||||
int votes = 0;
|
int votes = 0;
|
||||||
bool chosen = false;
|
bool chosen = false;
|
||||||
|
bool correct = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline bool operator==(const PollAnswer &a, const PollAnswer &b) {
|
inline bool operator==(const PollAnswer &a, const PollAnswer &b) {
|
||||||
|
|
@ -24,23 +29,39 @@ inline bool operator!=(const PollAnswer &a, const PollAnswer &b) {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PollData {
|
struct PollData {
|
||||||
explicit PollData(PollId id);
|
PollData(not_null<Data::Session*> owner, PollId id);
|
||||||
|
|
||||||
|
enum class Flag {
|
||||||
|
Closed = 0x01,
|
||||||
|
PublicVotes = 0x02,
|
||||||
|
MultiChoice = 0x04,
|
||||||
|
Quiz = 0x08,
|
||||||
|
};
|
||||||
|
friend inline constexpr bool is_flag_type(Flag) { return true; };
|
||||||
|
using Flags = base::flags<Flag>;
|
||||||
|
|
||||||
bool applyChanges(const MTPDpoll &poll);
|
bool applyChanges(const MTPDpoll &poll);
|
||||||
bool applyResults(const MTPPollResults &results);
|
bool applyResults(const MTPPollResults &results);
|
||||||
void checkResultsReload(not_null<HistoryItem*> item, crl::time now);
|
void checkResultsReload(not_null<HistoryItem*> item, crl::time now);
|
||||||
|
|
||||||
PollAnswer *answerByOption(const QByteArray &option);
|
[[nodiscard]] PollAnswer *answerByOption(const QByteArray &option);
|
||||||
const PollAnswer *answerByOption(const QByteArray &option) const;
|
[[nodiscard]] const PollAnswer *answerByOption(
|
||||||
|
const QByteArray &option) const;
|
||||||
|
|
||||||
bool voted() const;
|
void setFlags(Flags flags);
|
||||||
|
[[nodiscard]] Flags flags() const;
|
||||||
|
[[nodiscard]] bool voted() const;
|
||||||
|
[[nodiscard]] bool closed() const;
|
||||||
|
[[nodiscard]] bool publicVotes() const;
|
||||||
|
[[nodiscard]] bool multiChoice() const;
|
||||||
|
[[nodiscard]] bool quiz() const;
|
||||||
|
|
||||||
PollId id = 0;
|
PollId id = 0;
|
||||||
QString question;
|
QString question;
|
||||||
std::vector<PollAnswer> answers;
|
std::vector<PollAnswer> answers;
|
||||||
|
std::vector<not_null<UserData*>> recentVoters;
|
||||||
int totalVoters = 0;
|
int totalVoters = 0;
|
||||||
bool closed = false;
|
std::vector<QByteArray> sendingVotes;
|
||||||
QByteArray sendingVote;
|
|
||||||
crl::time lastResultsUpdate = 0;
|
crl::time lastResultsUpdate = 0;
|
||||||
|
|
||||||
int version = 0;
|
int version = 0;
|
||||||
|
|
@ -52,6 +73,9 @@ private:
|
||||||
const MTPPollAnswerVoters &result,
|
const MTPPollAnswerVoters &result,
|
||||||
bool isMinResults);
|
bool isMinResults);
|
||||||
|
|
||||||
|
not_null<Data::Session*> _owner;
|
||||||
|
Flags _flags = Flags();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
MTPPoll PollDataToMTP(not_null<const PollData*> poll);
|
MTPPoll PollDataToMTP(not_null<const PollData*> poll, bool close = false);
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,8 @@ public:
|
||||||
[[nodiscard]] rpl::producer<> updates(not_null<History*> history);
|
[[nodiscard]] rpl::producer<> updates(not_null<History*> history);
|
||||||
[[nodiscard]] Data::MessagesSlice list(not_null<History*> history);
|
[[nodiscard]] Data::MessagesSlice list(not_null<History*> history);
|
||||||
|
|
||||||
|
static constexpr auto kScheduledUntilOnlineTimestamp = TimeId(0x7FFFFFFE);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using OwnedItem = std::unique_ptr<HistoryItem, HistoryItem::Destroyer>;
|
using OwnedItem = std::unique_ptr<HistoryItem, HistoryItem::Destroyer>;
|
||||||
struct List {
|
struct List {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
#include "api/api_text_entities.h"
|
#include "api/api_text_entities.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
|
#include "core/mime_type.h" // Core::IsMimeSticker
|
||||||
#include "core/crash_reports.h" // CrashReports::SetAnnotation
|
#include "core/crash_reports.h" // CrashReports::SetAnnotation
|
||||||
#include "ui/image/image.h"
|
#include "ui/image/image.h"
|
||||||
#include "ui/image/image_source.h" // Images::LocalFileSource
|
#include "ui/image/image_source.h" // Images::LocalFileSource
|
||||||
|
|
@ -2390,8 +2391,7 @@ not_null<DocumentData*> Session::processDocument(
|
||||||
case mtpc_document: {
|
case mtpc_document: {
|
||||||
const auto &fields = data.c_document();
|
const auto &fields = data.c_document();
|
||||||
const auto mime = qs(fields.vmime_type());
|
const auto mime = qs(fields.vmime_type());
|
||||||
const auto format = (mime == qstr("image/webp")
|
const auto format = Core::IsMimeSticker(mime)
|
||||||
|| mime == qstr("application/x-tgsticker"))
|
|
||||||
? "WEBP"
|
? "WEBP"
|
||||||
: "JPG";
|
: "JPG";
|
||||||
return document(
|
return document(
|
||||||
|
|
@ -2895,7 +2895,7 @@ void Session::gameApplyFields(
|
||||||
not_null<PollData*> Session::poll(PollId id) {
|
not_null<PollData*> Session::poll(PollId id) {
|
||||||
auto i = _polls.find(id);
|
auto i = _polls.find(id);
|
||||||
if (i == _polls.cend()) {
|
if (i == _polls.cend()) {
|
||||||
i = _polls.emplace(id, std::make_unique<PollData>(id)).first;
|
i = _polls.emplace(id, std::make_unique<PollData>(this, id)).first;
|
||||||
}
|
}
|
||||||
return i->second.get();
|
return i->second.get();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "media/clip/media_clip_reader.h"
|
#include "media/clip/media_clip_reader.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
|
#include "window/window_peer_menu.h"
|
||||||
#include "history/history_item_components.h"
|
#include "history/history_item_components.h"
|
||||||
#include "base/platform/base_platform_info.h"
|
#include "base/platform/base_platform_info.h"
|
||||||
#include "data/data_peer.h"
|
#include "data/data_peer.h"
|
||||||
|
|
@ -115,6 +116,19 @@ void activateBotCommand(
|
||||||
}));
|
}));
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
|
case ButtonType::RequestPoll: {
|
||||||
|
hideSingleUseKeyboard(msg);
|
||||||
|
auto chosen = PollData::Flags();
|
||||||
|
auto disabled = PollData::Flags();
|
||||||
|
if (!button->data.isEmpty()) {
|
||||||
|
disabled |= PollData::Flag::Quiz;
|
||||||
|
if (button->data[0]) {
|
||||||
|
chosen |= PollData::Flag::Quiz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Window::PeerMenuCreatePoll(msg->history()->peer, chosen, disabled);
|
||||||
|
} break;
|
||||||
|
|
||||||
case ButtonType::SwitchInlineSame:
|
case ButtonType::SwitchInlineSame:
|
||||||
case ButtonType::SwitchInline: {
|
case ButtonType::SwitchInline: {
|
||||||
if (auto m = App::main()) {
|
if (auto m = App::main()) {
|
||||||
|
|
|
||||||
|
|
@ -577,6 +577,11 @@ bool InnerWidget::elementIntersectsRange(
|
||||||
void InnerWidget::elementStartStickerLoop(not_null<const Element*> view) {
|
void InnerWidget::elementStartStickerLoop(not_null<const Element*> view) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InnerWidget::elementShowPollResults(
|
||||||
|
not_null<PollData*> poll,
|
||||||
|
FullMsgId context) {
|
||||||
|
}
|
||||||
|
|
||||||
void InnerWidget::saveState(not_null<SectionMemento*> memento) {
|
void InnerWidget::saveState(not_null<SectionMemento*> memento) {
|
||||||
memento->setFilter(std::move(_filter));
|
memento->setFilter(std::move(_filter));
|
||||||
memento->setAdmins(std::move(_admins));
|
memento->setAdmins(std::move(_admins));
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,9 @@ public:
|
||||||
int till) override;
|
int till) override;
|
||||||
void elementStartStickerLoop(
|
void elementStartStickerLoop(
|
||||||
not_null<const HistoryView::Element*> view) override;
|
not_null<const HistoryView::Element*> view) override;
|
||||||
|
void elementShowPollResults(
|
||||||
|
not_null<PollData*> poll,
|
||||||
|
FullMsgId context) override;
|
||||||
|
|
||||||
~InnerWidget();
|
~InnerWidget();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,8 @@ historyReceivedIcon: icon {{ "history_received", historyOutIconFg, point(2px, 4p
|
||||||
historyReceivedSelectedIcon: icon {{ "history_received", historyOutIconFgSelected, point(2px, 4px) }};
|
historyReceivedSelectedIcon: icon {{ "history_received", historyOutIconFgSelected, point(2px, 4px) }};
|
||||||
historyReceivedInvertedIcon: icon {{ "history_received", historyIconFgInverted, point(2px, 4px) }};
|
historyReceivedInvertedIcon: icon {{ "history_received", historyIconFgInverted, point(2px, 4px) }};
|
||||||
|
|
||||||
|
historyScheduledUntilOnlineStateSpace: 17px;
|
||||||
|
|
||||||
historyViewsSpace: 11px;
|
historyViewsSpace: 11px;
|
||||||
historyViewsWidth: 20px;
|
historyViewsWidth: 20px;
|
||||||
historyViewsTop: -15px;
|
historyViewsTop: -15px;
|
||||||
|
|
@ -539,9 +541,9 @@ historyPollQuestionStyle: TextStyle(defaultTextStyle) {
|
||||||
}
|
}
|
||||||
historyPollAnswerStyle: defaultTextStyle;
|
historyPollAnswerStyle: defaultTextStyle;
|
||||||
historyPollQuestionTop: 7px;
|
historyPollQuestionTop: 7px;
|
||||||
historyPollSubtitleSkip: 2px;
|
historyPollSubtitleSkip: 4px;
|
||||||
historyPollAnswerPadding: margins(31px, 10px, 0px, 10px);
|
historyPollAnswerPadding: margins(31px, 10px, 0px, 10px);
|
||||||
historyPollAnswersSkip: 3px;
|
historyPollAnswersSkip: 2px;
|
||||||
historyPollPercentFont: semiboldFont;
|
historyPollPercentFont: semiboldFont;
|
||||||
historyPollPercentSkip: 6px;
|
historyPollPercentSkip: 6px;
|
||||||
historyPollPercentTop: 0px;
|
historyPollPercentTop: 0px;
|
||||||
|
|
@ -575,6 +577,17 @@ historyPollRippleOut: RippleAnimation(defaultRippleAnimation) {
|
||||||
color: msgWaveformOutInactive;
|
color: msgWaveformOutInactive;
|
||||||
}
|
}
|
||||||
historyPollRippleOpacity: 0.3;
|
historyPollRippleOpacity: 0.3;
|
||||||
|
historyPollRecentVotersSkip: 4px;
|
||||||
|
historyPollRecentVoterSize: 18px;
|
||||||
|
historyPollRecentVoterSkip: 13px;
|
||||||
|
historyPollBottomButtonSkip: 15px;
|
||||||
|
historyPollBottomButtonTop: 4px;
|
||||||
|
historyPollChoiceRight: icon {{ "poll_choice_right", activeButtonFg }};
|
||||||
|
historyPollChoiceWrong: icon {{ "poll_choice_wrong", activeButtonFg }};
|
||||||
|
historyPollOutChosen: icon {{ "poll_select_check", historyFileOutIconFg }};
|
||||||
|
historyPollOutChosenSelected: icon {{ "poll_select_check", historyFileOutIconFgSelected }};
|
||||||
|
historyPollInChosen: icon {{ "poll_select_check", historyFileInIconFg }};
|
||||||
|
historyPollInChosenSelected: icon {{ "poll_select_check", historyFileInIconFgSelected }};
|
||||||
|
|
||||||
boxAttachEmoji: IconButton(historyAttachEmoji) {
|
boxAttachEmoji: IconButton(historyAttachEmoji) {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/inactive_press.h"
|
#include "ui/inactive_press.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "window/window_peer_menu.h"
|
#include "window/window_peer_menu.h"
|
||||||
|
#include "window/window_controller.h"
|
||||||
#include "boxes/confirm_box.h"
|
#include "boxes/confirm_box.h"
|
||||||
#include "boxes/report_box.h"
|
#include "boxes/report_box.h"
|
||||||
#include "boxes/sticker_set_box.h"
|
#include "boxes/sticker_set_box.h"
|
||||||
|
|
@ -1719,8 +1720,8 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||||
}
|
}
|
||||||
if (const auto media = item->media()) {
|
if (const auto media = item->media()) {
|
||||||
if (const auto poll = media->poll()) {
|
if (const auto poll = media->poll()) {
|
||||||
if (!poll->closed) {
|
if (!poll->closed()) {
|
||||||
if (poll->voted()) {
|
if (poll->voted() && !poll->quiz()) {
|
||||||
_menu->addAction(tr::lng_polls_retract(tr::now), [=] {
|
_menu->addAction(tr::lng_polls_retract(tr::now), [=] {
|
||||||
session().api().sendPollVotes(itemId, {});
|
session().api().sendPollVotes(itemId, {});
|
||||||
});
|
});
|
||||||
|
|
@ -2418,6 +2419,12 @@ void HistoryInner::elementStartStickerLoop(
|
||||||
_animatedStickersPlayed.emplace(view->data());
|
_animatedStickersPlayed.emplace(view->data());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HistoryInner::elementShowPollResults(
|
||||||
|
not_null<PollData*> poll,
|
||||||
|
FullMsgId context) {
|
||||||
|
_controller->showPollResults(poll, context);
|
||||||
|
}
|
||||||
|
|
||||||
auto HistoryInner::getSelectionState() const
|
auto HistoryInner::getSelectionState() const
|
||||||
-> HistoryView::TopBarWidget::SelectedState {
|
-> HistoryView::TopBarWidget::SelectedState {
|
||||||
auto result = HistoryView::TopBarWidget::SelectedState {};
|
auto result = HistoryView::TopBarWidget::SelectedState {};
|
||||||
|
|
@ -3278,6 +3285,13 @@ not_null<HistoryView::ElementDelegate*> HistoryInner::ElementDelegate() {
|
||||||
Instance->elementStartStickerLoop(view);
|
Instance->elementStartStickerLoop(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void elementShowPollResults(
|
||||||
|
not_null<PollData*> poll,
|
||||||
|
FullMsgId context) override {
|
||||||
|
if (Instance) {
|
||||||
|
Instance->elementShowPollResults(poll, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,9 @@ public:
|
||||||
int from,
|
int from,
|
||||||
int till) const;
|
int till) const;
|
||||||
void elementStartStickerLoop(not_null<const Element*> view);
|
void elementStartStickerLoop(not_null<const Element*> view);
|
||||||
|
void elementShowPollResults(
|
||||||
|
not_null<PollData*> poll,
|
||||||
|
FullMsgId context);
|
||||||
|
|
||||||
void updateBotInfo(bool recount = true);
|
void updateBotInfo(bool recount = true);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "core/crash_reports.h"
|
#include "core/crash_reports.h"
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
|
#include "data/data_scheduled_messages.h" // kScheduledUntilOnlineTimestamp
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_messages.h"
|
#include "data/data_messages.h"
|
||||||
#include "data/data_media_types.h"
|
#include "data/data_media_types.h"
|
||||||
|
|
@ -862,6 +863,21 @@ QDateTime ItemDateTime(not_null<const HistoryItem*> item) {
|
||||||
return base::unixtime::parse(item->date());
|
return base::unixtime::parse(item->date());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ItemDateText(not_null<const HistoryItem*> item, bool isUntilOnline) {
|
||||||
|
const auto dateText = langDayOfMonthFull(ItemDateTime(item).date());
|
||||||
|
return !item->isScheduled()
|
||||||
|
? dateText
|
||||||
|
: isUntilOnline
|
||||||
|
? tr::lng_scheduled_date_until_online(tr::now)
|
||||||
|
: tr::lng_scheduled_date(tr::now, lt_date, dateText);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsItemScheduledUntilOnline(not_null<const HistoryItem*> item) {
|
||||||
|
return item->isScheduled()
|
||||||
|
&& (item->date() ==
|
||||||
|
Data::ScheduledMessages::kScheduledUntilOnlineTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
ClickHandlerPtr goToMessageClickHandler(
|
ClickHandlerPtr goToMessageClickHandler(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
FullMsgId returnToId) {
|
FullMsgId returnToId) {
|
||||||
|
|
|
||||||
|
|
@ -374,6 +374,8 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
QDateTime ItemDateTime(not_null<const HistoryItem*> item);
|
QDateTime ItemDateTime(not_null<const HistoryItem*> item);
|
||||||
|
QString ItemDateText(not_null<const HistoryItem*> item, bool isUntilOnline);
|
||||||
|
bool IsItemScheduledUntilOnline(not_null<const HistoryItem*> item);
|
||||||
|
|
||||||
ClickHandlerPtr goToMessageClickHandler(
|
ClickHandlerPtr goToMessageClickHandler(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
|
|
|
||||||
|
|
@ -899,6 +899,21 @@ void HistoryMessageReplyMarkup::createFromButtonRows(
|
||||||
}, [&](const MTPDinputKeyboardButtonUrlAuth &data) {
|
}, [&](const MTPDinputKeyboardButtonUrlAuth &data) {
|
||||||
LOG(("API Error: inputKeyboardButtonUrlAuth received."));
|
LOG(("API Error: inputKeyboardButtonUrlAuth received."));
|
||||||
// Should not get those for the users.
|
// Should not get those for the users.
|
||||||
|
}, [&](const MTPDkeyboardButtonRequestPoll &data) {
|
||||||
|
const auto quiz = [&] {
|
||||||
|
if (!data.vquiz()) {
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
return data.vquiz()->match([&](const MTPDboolTrue&) {
|
||||||
|
return QByteArray(1, 1);
|
||||||
|
}, [&](const MTPDboolFalse&) {
|
||||||
|
return QByteArray(1, 0);
|
||||||
|
});
|
||||||
|
}();
|
||||||
|
row.emplace_back(
|
||||||
|
Type::RequestPoll,
|
||||||
|
qs(data.vtext()),
|
||||||
|
quiz);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!row.empty()) {
|
if (!row.empty()) {
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,7 @@ struct HistoryMessageMarkupButton {
|
||||||
Callback,
|
Callback,
|
||||||
RequestPhone,
|
RequestPhone,
|
||||||
RequestLocation,
|
RequestLocation,
|
||||||
|
RequestPoll,
|
||||||
SwitchInline,
|
SwitchInline,
|
||||||
SwitchInlineSame,
|
SwitchInlineSame,
|
||||||
Game,
|
Game,
|
||||||
|
|
|
||||||
|
|
@ -314,7 +314,7 @@ HistoryWidget::HistoryWidget(
|
||||||
_fieldBarCancel->addClickHandler([=] { cancelFieldAreaState(); });
|
_fieldBarCancel->addClickHandler([=] { cancelFieldAreaState(); });
|
||||||
_send->addClickHandler([=] { sendButtonClicked(); });
|
_send->addClickHandler([=] { sendButtonClicked(); });
|
||||||
|
|
||||||
SetupSendMenu(
|
SetupSendMenuAndShortcuts(
|
||||||
_send,
|
_send,
|
||||||
[=] { return sendButtonMenuType(); },
|
[=] { return sendButtonMenuType(); },
|
||||||
[=] { sendSilent(); },
|
[=] { sendSilent(); },
|
||||||
|
|
@ -3066,6 +3066,8 @@ SendMenuType HistoryWidget::sendMenuType() const {
|
||||||
? SendMenuType::Disabled
|
? SendMenuType::Disabled
|
||||||
: _peer->isSelf()
|
: _peer->isSelf()
|
||||||
? SendMenuType::Reminder
|
? SendMenuType::Reminder
|
||||||
|
: HistoryView::CanScheduleUntilOnline(_peer)
|
||||||
|
? SendMenuType::ScheduledToUser
|
||||||
: SendMenuType::Scheduled;
|
: SendMenuType::Scheduled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -6900,7 +6902,13 @@ void HistoryWidget::drawPinnedBar(Painter &p) {
|
||||||
}
|
}
|
||||||
p.setPen(st::historyReplyNameFg);
|
p.setPen(st::historyReplyNameFg);
|
||||||
p.setFont(st::msgServiceNameFont);
|
p.setFont(st::msgServiceNameFont);
|
||||||
p.drawText(left, top + st::msgServiceNameFont->ascent, (media && media->poll()) ? tr::lng_pinned_poll(tr::now) : tr::lng_pinned_message(tr::now));
|
const auto poll = media ? media->poll() : nullptr;
|
||||||
|
const auto pinnedHeader = !poll
|
||||||
|
? tr::lng_pinned_message(tr::now)
|
||||||
|
: poll->quiz()
|
||||||
|
? tr::lng_pinned_quiz(tr::now)
|
||||||
|
: tr::lng_pinned_poll(tr::now);
|
||||||
|
p.drawText(left, top + st::msgServiceNameFont->ascent, pinnedHeader);
|
||||||
|
|
||||||
p.setPen(st::historyComposeAreaFg);
|
p.setPen(st::historyComposeAreaFg);
|
||||||
p.setTextPalette(st::historyComposeAreaPalette);
|
p.setTextPalette(st::historyComposeAreaPalette);
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,11 @@ void SimpleElementDelegate::elementStartStickerLoop(
|
||||||
not_null<const Element*> view) {
|
not_null<const Element*> view) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SimpleElementDelegate::elementShowPollResults(
|
||||||
|
not_null<PollData*> poll,
|
||||||
|
FullMsgId context) {
|
||||||
|
}
|
||||||
|
|
||||||
TextSelection UnshiftItemSelection(
|
TextSelection UnshiftItemSelection(
|
||||||
TextSelection selection,
|
TextSelection selection,
|
||||||
uint16 byLength) {
|
uint16 byLength) {
|
||||||
|
|
@ -180,8 +185,8 @@ void UnreadBar::paint(Painter &p, int y, int w) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DateBadge::init(const QDateTime &date) {
|
void DateBadge::init(const QString &date) {
|
||||||
text = langDayOfMonthFull(date.date());
|
text = date;
|
||||||
width = st::msgServiceFont->width(text);
|
width = st::msgServiceFont->width(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,7 +207,8 @@ Element::Element(
|
||||||
not_null<HistoryItem*> data)
|
not_null<HistoryItem*> data)
|
||||||
: _delegate(delegate)
|
: _delegate(delegate)
|
||||||
, _data(data)
|
, _data(data)
|
||||||
, _dateTime(ItemDateTime(data))
|
, _isScheduledUntilOnline(IsItemScheduledUntilOnline(data))
|
||||||
|
, _dateTime(_isScheduledUntilOnline ? QDateTime() : ItemDateTime(data))
|
||||||
, _context(delegate->elementContext()) {
|
, _context(delegate->elementContext()) {
|
||||||
history()->owner().registerItemView(this);
|
history()->owner().registerItemView(this);
|
||||||
refreshMedia();
|
refreshMedia();
|
||||||
|
|
@ -504,7 +510,7 @@ void Element::setDisplayDate(bool displayDate) {
|
||||||
const auto item = data();
|
const auto item = data();
|
||||||
if (displayDate && !Has<DateBadge>()) {
|
if (displayDate && !Has<DateBadge>()) {
|
||||||
AddComponents(DateBadge::Bit());
|
AddComponents(DateBadge::Bit());
|
||||||
Get<DateBadge>()->init(dateTime());
|
Get<DateBadge>()->init(ItemDateText(item, _isScheduledUntilOnline));
|
||||||
setPendingResize();
|
setPendingResize();
|
||||||
} else if (!displayDate && Has<DateBadge>()) {
|
} else if (!displayDate && Has<DateBadge>()) {
|
||||||
RemoveComponents(DateBadge::Bit());
|
RemoveComponents(DateBadge::Bit());
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,9 @@ public:
|
||||||
int from,
|
int from,
|
||||||
int till) = 0;
|
int till) = 0;
|
||||||
virtual void elementStartStickerLoop(not_null<const Element*> view) = 0;
|
virtual void elementStartStickerLoop(not_null<const Element*> view) = 0;
|
||||||
|
virtual void elementShowPollResults(
|
||||||
|
not_null<PollData*> poll,
|
||||||
|
FullMsgId context) = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -71,6 +74,9 @@ public:
|
||||||
int from,
|
int from,
|
||||||
int till) override;
|
int till) override;
|
||||||
void elementStartStickerLoop(not_null<const Element*> view) override;
|
void elementStartStickerLoop(not_null<const Element*> view) override;
|
||||||
|
void elementShowPollResults(
|
||||||
|
not_null<PollData*> poll,
|
||||||
|
FullMsgId context) override;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -116,7 +122,7 @@ struct UnreadBar : public RuntimeComponent<UnreadBar, Element> {
|
||||||
// Any HistoryView::Element can have this Component for
|
// Any HistoryView::Element can have this Component for
|
||||||
// displaying the day mark above the message.
|
// displaying the day mark above the message.
|
||||||
struct DateBadge : public RuntimeComponent<DateBadge, Element> {
|
struct DateBadge : public RuntimeComponent<DateBadge, Element> {
|
||||||
void init(const QDateTime &date);
|
void init(const QString &date);
|
||||||
|
|
||||||
int height() const;
|
int height() const;
|
||||||
void paint(Painter &p, int y, int w) const;
|
void paint(Painter &p, int y, int w) const;
|
||||||
|
|
@ -305,6 +311,7 @@ private:
|
||||||
const not_null<ElementDelegate*> _delegate;
|
const not_null<ElementDelegate*> _delegate;
|
||||||
const not_null<HistoryItem*> _data;
|
const not_null<HistoryItem*> _data;
|
||||||
std::unique_ptr<Media> _media;
|
std::unique_ptr<Media> _media;
|
||||||
|
bool _isScheduledUntilOnline = false;
|
||||||
const QDateTime _dateTime;
|
const QDateTime _dateTime;
|
||||||
|
|
||||||
int _y = 0;
|
int _y = 0;
|
||||||
|
|
|
||||||
|
|
@ -1153,6 +1153,11 @@ bool ListWidget::elementIntersectsRange(
|
||||||
void ListWidget::elementStartStickerLoop(not_null<const Element*> view) {
|
void ListWidget::elementStartStickerLoop(not_null<const Element*> view) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ListWidget::elementShowPollResults(
|
||||||
|
not_null<PollData*> poll,
|
||||||
|
FullMsgId context) {
|
||||||
|
}
|
||||||
|
|
||||||
void ListWidget::saveState(not_null<ListMemento*> memento) {
|
void ListWidget::saveState(not_null<ListMemento*> memento) {
|
||||||
memento->setAroundPosition(_aroundPosition);
|
memento->setAroundPosition(_aroundPosition);
|
||||||
auto state = countScrollState();
|
auto state = countScrollState();
|
||||||
|
|
@ -1386,7 +1391,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
|
||||||
} else {
|
} else {
|
||||||
ServiceMessagePainter::paintDate(
|
ServiceMessagePainter::paintDate(
|
||||||
p,
|
p,
|
||||||
view->dateTime(),
|
ItemDateText(view->data(), IsItemScheduledUntilOnline(view->data())),
|
||||||
dateY,
|
dateY,
|
||||||
width);
|
width);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,9 @@ public:
|
||||||
int from,
|
int from,
|
||||||
int till) override;
|
int till) override;
|
||||||
void elementStartStickerLoop(not_null<const Element*> view) override;
|
void elementStartStickerLoop(not_null<const Element*> view) override;
|
||||||
|
void elementShowPollResults(
|
||||||
|
not_null<PollData*> poll,
|
||||||
|
FullMsgId context) override;
|
||||||
|
|
||||||
~ListWidget();
|
~ListWidget();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1269,7 +1269,12 @@ int Message::infoWidth() const {
|
||||||
result += st::historySendStateSpace;
|
result += st::historySendStateSpace;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasOutLayout()) {
|
|
||||||
|
// When message is scheduled until online, time is not displayed,
|
||||||
|
// so message should have less space.
|
||||||
|
if (!item->_timeWidth) {
|
||||||
|
result += st::historyScheduledUntilOnlineStateSpace;
|
||||||
|
} else if (hasOutLayout()) {
|
||||||
result += st::historySendStateSpace;
|
result += st::historySendStateSpace;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
|
||||||
|
|
@ -8,17 +8,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/history_view_schedule_box.h"
|
#include "history/view/history_view_schedule_box.h"
|
||||||
|
|
||||||
#include "api/api_common.h"
|
#include "api/api_common.h"
|
||||||
|
#include "data/data_peer.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
|
#include "data/data_scheduled_messages.h" // kScheduledUntilOnlineTimestamp
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
#include "boxes/calendar_box.h"
|
#include "boxes/calendar_box.h"
|
||||||
#include "ui/widgets/input_fields.h"
|
#include "ui/widgets/input_fields.h"
|
||||||
#include "ui/widgets/labels.h"
|
#include "ui/widgets/labels.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "ui/wrap/padding_wrap.h"
|
#include "ui/wrap/padding_wrap.h"
|
||||||
#include "chat_helpers/message_field.h"
|
#include "chat_helpers/message_field.h"
|
||||||
|
#include "styles/style_info.h"
|
||||||
#include "styles/style_layers.h"
|
#include "styles/style_layers.h"
|
||||||
#include "styles/style_history.h"
|
#include "styles/style_history.h"
|
||||||
|
|
||||||
|
#include <QGuiApplication>
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
|
@ -535,20 +542,41 @@ void TimeInput::startBorderAnimation() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FillSendUntilOnlineMenu(
|
||||||
|
not_null<Ui::IconButton*> button,
|
||||||
|
Fn<void()> callback) {
|
||||||
|
const auto menu = std::make_shared<base::unique_qptr<Ui::PopupMenu>>();
|
||||||
|
button->setClickedCallback([=] {
|
||||||
|
*menu = base::make_unique_q<Ui::PopupMenu>(button);
|
||||||
|
(*menu)->addAction(
|
||||||
|
tr::lng_scheduled_send_until_online(tr::now),
|
||||||
|
std::move(callback));
|
||||||
|
(*menu)->popup(QCursor::pos());
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TimeId DefaultScheduleTime() {
|
TimeId DefaultScheduleTime() {
|
||||||
return base::unixtime::now() + 600;
|
return base::unixtime::now() + 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CanScheduleUntilOnline(not_null<PeerData*> peer) {
|
||||||
|
return !peer->isSelf()
|
||||||
|
&& peer->isUser()
|
||||||
|
&& !peer->asUser()->isBot()
|
||||||
|
&& (peer->asUser()->onlineTill > 0);
|
||||||
|
}
|
||||||
|
|
||||||
void ScheduleBox(
|
void ScheduleBox(
|
||||||
not_null<Ui::GenericBox*> box,
|
not_null<Ui::GenericBox*> box,
|
||||||
SendMenuType type,
|
SendMenuType type,
|
||||||
FnMut<void(Api::SendOptions)> done,
|
FnMut<void(Api::SendOptions)> done,
|
||||||
TimeId time) {
|
TimeId time) {
|
||||||
box->setTitle((type == SendMenuType::Scheduled)
|
box->setTitle((type == SendMenuType::Reminder)
|
||||||
? tr::lng_schedule_title()
|
? tr::lng_remind_title()
|
||||||
: tr::lng_remind_title());
|
: tr::lng_schedule_title());
|
||||||
box->setWidth(st::boxWideWidth);
|
box->setWidth(st::boxWideWidth);
|
||||||
|
|
||||||
const auto date = Ui::CreateChild<rpl::variable<QDate>>(
|
const auto date = Ui::CreateChild<rpl::variable<QDate>>(
|
||||||
|
|
@ -637,10 +665,15 @@ void ScheduleBox(
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
const auto save = [=](bool silent) {
|
const auto save = [=](bool silent, bool untilOnline = false) {
|
||||||
|
// Pro tip: Hold Ctrl key to send a silent scheduled message!
|
||||||
|
auto ctrl =
|
||||||
|
(QGuiApplication::keyboardModifiers() == Qt::ControlModifier);
|
||||||
auto result = Api::SendOptions();
|
auto result = Api::SendOptions();
|
||||||
result.silent = silent;
|
result.silent = silent || ctrl;
|
||||||
result.scheduled = collect();
|
result.scheduled = untilOnline
|
||||||
|
? Data::ScheduledMessages::kScheduledUntilOnlineTimestamp
|
||||||
|
: collect();
|
||||||
if (!result.scheduled) {
|
if (!result.scheduled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -658,12 +691,20 @@ void ScheduleBox(
|
||||||
const auto submit = box->addButton(tr::lng_settings_save(), [=] {
|
const auto submit = box->addButton(tr::lng_settings_save(), [=] {
|
||||||
save(false);
|
save(false);
|
||||||
});
|
});
|
||||||
SetupSendMenu(
|
SetupSendMenuAndShortcuts(
|
||||||
submit.data(),
|
submit.data(),
|
||||||
[=] { return SendMenuType::SilentOnly; },
|
[=] { return SendMenuType::SilentOnly; },
|
||||||
[=] { save(true); },
|
[=] { save(true); },
|
||||||
nullptr);
|
nullptr);
|
||||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||||
|
|
||||||
|
if (type == SendMenuType::ScheduledToUser) {
|
||||||
|
const auto sendUntilOnline = box->addTopButton(st::infoTopBarMenu);
|
||||||
|
FillSendUntilOnlineMenu(
|
||||||
|
sendUntilOnline.data(),
|
||||||
|
[=] { save(false, true); });
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ enum class SendMenuType;
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
[[nodiscard]] TimeId DefaultScheduleTime();
|
[[nodiscard]] TimeId DefaultScheduleTime();
|
||||||
|
[[nodiscard]] bool CanScheduleUntilOnline(not_null<PeerData*> peer);
|
||||||
void ScheduleBox(
|
void ScheduleBox(
|
||||||
not_null<Ui::GenericBox*> box,
|
not_null<Ui::GenericBox*> box,
|
||||||
SendMenuType type,
|
SendMenuType type,
|
||||||
|
|
|
||||||
|
|
@ -240,7 +240,9 @@ bool ScheduledWidget::confirmSendingFiles(
|
||||||
text,
|
text,
|
||||||
boxCompressConfirm,
|
boxCompressConfirm,
|
||||||
_history->peer->slowmodeApplied() ? SendLimit::One : SendLimit::Many,
|
_history->peer->slowmodeApplied() ? SendLimit::One : SendLimit::Many,
|
||||||
Api::SendType::Scheduled,
|
CanScheduleUntilOnline(_history->peer)
|
||||||
|
? Api::SendType::ScheduledToUser
|
||||||
|
: Api::SendType::Scheduled,
|
||||||
SendMenuType::Disabled);
|
SendMenuType::Disabled);
|
||||||
//_field->setTextWithTags({});
|
//_field->setTextWithTags({});
|
||||||
|
|
||||||
|
|
@ -545,6 +547,8 @@ void ScheduledWidget::sendInlineResult(
|
||||||
SendMenuType ScheduledWidget::sendMenuType() const {
|
SendMenuType ScheduledWidget::sendMenuType() const {
|
||||||
return _history->peer->isSelf()
|
return _history->peer->isSelf()
|
||||||
? SendMenuType::Reminder
|
? SendMenuType::Reminder
|
||||||
|
: HistoryView::CanScheduleUntilOnline(_history->peer)
|
||||||
|
? SendMenuType::ScheduledToUser
|
||||||
: SendMenuType::Scheduled;
|
: SendMenuType::Scheduled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -199,6 +199,10 @@ void ServiceMessagePainter::paintDate(Painter &p, const QDateTime &date, int y,
|
||||||
paintPreparedDate(p, dateText, dateTextWidth, y, w);
|
paintPreparedDate(p, dateText, dateTextWidth, y, w);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ServiceMessagePainter::paintDate(Painter &p, const QString &dateText, int y, int w) {
|
||||||
|
paintPreparedDate(p, dateText, st::msgServiceFont->width(dateText), y, w);
|
||||||
|
}
|
||||||
|
|
||||||
void ServiceMessagePainter::paintDate(Painter &p, const QString &dateText, int dateTextWidth, int y, int w) {
|
void ServiceMessagePainter::paintDate(Painter &p, const QString &dateText, int dateTextWidth, int y, int w) {
|
||||||
paintPreparedDate(p, dateText, dateTextWidth, y, w);
|
paintPreparedDate(p, dateText, dateTextWidth, y, w);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ struct PaintContext {
|
||||||
class ServiceMessagePainter {
|
class ServiceMessagePainter {
|
||||||
public:
|
public:
|
||||||
static void paintDate(Painter &p, const QDateTime &date, int y, int w);
|
static void paintDate(Painter &p, const QDateTime &date, int y, int w);
|
||||||
|
static void paintDate(Painter &p, const QString &dateText, int y, int w);
|
||||||
static void paintDate(Painter &p, const QString &dateText, int dateTextWidth, int y, int w);
|
static void paintDate(Painter &p, const QString &dateText, int dateTextWidth, int y, int w);
|
||||||
|
|
||||||
static void paintBubble(Painter &p, int x, int y, int w, int h);
|
static void paintBubble(Painter &p, int x, int y, int w, int h);
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,35 @@ namespace {
|
||||||
|
|
||||||
constexpr auto kAudioVoiceMsgUpdateView = crl::time(100);
|
constexpr auto kAudioVoiceMsgUpdateView = crl::time(100);
|
||||||
|
|
||||||
|
[[nodiscard]] QString CleanTagSymbols(const QString &value) {
|
||||||
|
auto result = QString();
|
||||||
|
const auto begin = value.begin(), end = value.end();
|
||||||
|
auto from = begin;
|
||||||
|
for (auto ch = begin; ch != end; ++ch) {
|
||||||
|
if (ch->isHighSurrogate()
|
||||||
|
&& (ch + 1) != end
|
||||||
|
&& (ch + 1)->isLowSurrogate()
|
||||||
|
&& QChar::surrogateToUcs4(
|
||||||
|
ch->unicode(),
|
||||||
|
(ch + 1)->unicode()) >= 0xe0000) {
|
||||||
|
if (ch > from) {
|
||||||
|
if (result.isEmpty()) {
|
||||||
|
result.reserve(value.size());
|
||||||
|
}
|
||||||
|
result.append(from, ch - from);
|
||||||
|
}
|
||||||
|
++ch;
|
||||||
|
from = ch + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (from == begin) {
|
||||||
|
return value;
|
||||||
|
} else if (end > from) {
|
||||||
|
result.append(from, end - from);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Document::Document(
|
Document::Document(
|
||||||
|
|
@ -104,7 +133,8 @@ void Document::createComponents(bool caption) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Document::fillNamedFromData(HistoryDocumentNamed *named) {
|
void Document::fillNamedFromData(HistoryDocumentNamed *named) {
|
||||||
const auto nameString = named->_name = _data->composeNameString();
|
const auto nameString = named->_name = CleanTagSymbols(
|
||||||
|
_data->composeNameString());
|
||||||
named->_namew = st::semiboldFont->width(nameString);
|
named->_namew = st::semiboldFont->width(nameString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -263,7 +263,7 @@ bool Gif::downloadInCorner() const {
|
||||||
&& (_data->loading() || !autoplayEnabled())
|
&& (_data->loading() || !autoplayEnabled())
|
||||||
&& _data->canBeStreamed()
|
&& _data->canBeStreamed()
|
||||||
&& !_data->inappPlaybackFailed()
|
&& !_data->inappPlaybackFailed()
|
||||||
&& IsServerMsgId(_parent->data()->id);
|
&& !_parent->data()->isSending();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Gif::autoplayEnabled() const {
|
bool Gif::autoplayEnabled() const {
|
||||||
|
|
@ -431,19 +431,22 @@ void Gif::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms
|
||||||
} else {
|
} else {
|
||||||
p.drawPixmap(rthumb.topLeft(), normal->pixBlurredSingle(_realParent->fullId(), _thumbw, _thumbh, usew, painth, roundRadius, roundCorners));
|
p.drawPixmap(rthumb.topLeft(), normal->pixBlurredSingle(_realParent->fullId(), _thumbw, _thumbh, usew, painth, roundRadius, roundCorners));
|
||||||
}
|
}
|
||||||
} else if (const auto blurred = _data->thumbnailInline()) {
|
} else {
|
||||||
p.drawPixmap(rthumb.topLeft(), blurred->pixBlurredSingle(_realParent->fullId(), _thumbw, _thumbh, usew, painth, roundRadius, roundCorners));
|
_data->loadThumbnail(_realParent->fullId());
|
||||||
} else if (!isRound) {
|
if (const auto blurred = _data->thumbnailInline()) {
|
||||||
const auto roundTop = (roundCorners & RectPart::TopLeft);
|
p.drawPixmap(rthumb.topLeft(), blurred->pixBlurredSingle(_realParent->fullId(), _thumbw, _thumbh, usew, painth, roundRadius, roundCorners));
|
||||||
const auto roundBottom = (roundCorners & RectPart::BottomLeft);
|
} else if (!isRound) {
|
||||||
const auto margin = inWebPage
|
const auto roundTop = (roundCorners & RectPart::TopLeft);
|
||||||
? st::buttonRadius
|
const auto roundBottom = (roundCorners & RectPart::BottomLeft);
|
||||||
: st::historyMessageRadius;
|
const auto margin = inWebPage
|
||||||
const auto parts = roundCorners
|
? st::buttonRadius
|
||||||
| RectPart::NoTopBottom
|
: st::historyMessageRadius;
|
||||||
| (roundTop ? RectPart::Top : RectPart::None)
|
const auto parts = roundCorners
|
||||||
| (roundBottom ? RectPart::Bottom : RectPart::None);
|
| RectPart::NoTopBottom
|
||||||
App::roundRect(p, rthumb.marginsAdded({ 0, roundTop ? 0 : margin, 0, roundBottom ? 0 : margin }), st::imageBg, roundRadius, parts);
|
| (roundTop ? RectPart::Top : RectPart::None)
|
||||||
|
| (roundBottom ? RectPart::Bottom : RectPart::None);
|
||||||
|
App::roundRect(p, rthumb.marginsAdded({ 0, roundTop ? 0 : margin, 0, roundBottom ? 0 : margin }), st::imageBg, roundRadius, parts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -807,7 +810,7 @@ TextState Gif::textState(QPoint point, StateRequest request) const {
|
||||||
if (QRect(usex + paintx, painty, usew, painth).contains(point)) {
|
if (QRect(usex + paintx, painty, usew, painth).contains(point)) {
|
||||||
result.link = _data->uploading()
|
result.link = _data->uploading()
|
||||||
? _cancell
|
? _cancell
|
||||||
: !IsServerMsgId(_realParent->id)
|
: _realParent->isSending()
|
||||||
? nullptr
|
? nullptr
|
||||||
: (_data->loaded() || _data->canBePlayed())
|
: (_data->loaded() || _data->canBePlayed())
|
||||||
? _openl
|
? _openl
|
||||||
|
|
@ -1098,7 +1101,7 @@ TextState Gif::getStateGrouped(
|
||||||
}
|
}
|
||||||
return TextState(_parent, _data->uploading()
|
return TextState(_parent, _data->uploading()
|
||||||
? _cancell
|
? _cancell
|
||||||
: !IsServerMsgId(_realParent->id)
|
: _realParent->isSending()
|
||||||
? nullptr
|
? nullptr
|
||||||
: (_data->loaded() || _data->canBePlayed())
|
: (_data->loaded() || _data->canBePlayed())
|
||||||
? _openl
|
? _openl
|
||||||
|
|
@ -1279,6 +1282,7 @@ void Gif::parentTextUpdated() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gif::refreshParentId(not_null<HistoryItem*> realParent) {
|
void Gif::refreshParentId(not_null<HistoryItem*> realParent) {
|
||||||
|
File::refreshParentId(realParent);
|
||||||
if (_parent->media() == this) {
|
if (_parent->media() == this) {
|
||||||
refreshCaption();
|
refreshCaption();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/history_view_cursor_state.h"
|
#include "history/view/history_view_cursor_state.h"
|
||||||
#include "calls/calls_instance.h"
|
#include "calls/calls_instance.h"
|
||||||
#include "ui/text_options.h"
|
#include "ui/text_options.h"
|
||||||
|
#include "ui/text/text_utilities.h"
|
||||||
#include "ui/effects/animations.h"
|
#include "ui/effects/animations.h"
|
||||||
#include "ui/effects/radial_animation.h"
|
#include "ui/effects/radial_animation.h"
|
||||||
#include "ui/effects/ripple_animation.h"
|
#include "ui/effects/ripple_animation.h"
|
||||||
#include "data/data_media_types.h"
|
#include "data/data_media_types.h"
|
||||||
#include "data/data_poll.h"
|
#include "data/data_poll.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "layout.h"
|
#include "layout.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
|
@ -29,6 +31,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kShowRecentVotersCount = 3;
|
||||||
|
|
||||||
struct PercentCounterItem {
|
struct PercentCounterItem {
|
||||||
int index = 0;
|
int index = 0;
|
||||||
int percent = 0;
|
int percent = 0;
|
||||||
|
|
@ -112,6 +116,8 @@ struct Poll::AnswerAnimation {
|
||||||
anim::value percent;
|
anim::value percent;
|
||||||
anim::value filling;
|
anim::value filling;
|
||||||
anim::value opacity;
|
anim::value opacity;
|
||||||
|
bool chosen = false;
|
||||||
|
bool correct = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Poll::AnswersAnimation {
|
struct Poll::AnswersAnimation {
|
||||||
|
|
@ -132,7 +138,7 @@ struct Poll::SendingAnimation {
|
||||||
struct Poll::Answer {
|
struct Poll::Answer {
|
||||||
Answer();
|
Answer();
|
||||||
|
|
||||||
void fillText(const PollAnswer &original);
|
void fillData(not_null<PollData*> poll, const PollAnswer &original);
|
||||||
|
|
||||||
Ui::Text::String text;
|
Ui::Text::String text;
|
||||||
QByteArray option;
|
QByteArray option;
|
||||||
|
|
@ -142,7 +148,10 @@ struct Poll::Answer {
|
||||||
float64 filling = 0.;
|
float64 filling = 0.;
|
||||||
QString votesPercentString;
|
QString votesPercentString;
|
||||||
bool chosen = false;
|
bool chosen = false;
|
||||||
|
bool correct = false;
|
||||||
|
bool selected = false;
|
||||||
ClickHandlerPtr handler;
|
ClickHandlerPtr handler;
|
||||||
|
Ui::Animations::Simple selectedAnimation;
|
||||||
mutable std::unique_ptr<Ui::RippleAnimation> ripple;
|
mutable std::unique_ptr<Ui::RippleAnimation> ripple;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -159,7 +168,11 @@ Poll::SendingAnimation::SendingAnimation(
|
||||||
Poll::Answer::Answer() : text(st::msgMinWidth / 2) {
|
Poll::Answer::Answer() : text(st::msgMinWidth / 2) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Poll::Answer::fillText(const PollAnswer &original) {
|
void Poll::Answer::fillData(
|
||||||
|
not_null<PollData*> poll,
|
||||||
|
const PollAnswer &original) {
|
||||||
|
chosen = original.chosen;
|
||||||
|
correct = poll->quiz() ? original.correct : chosen;
|
||||||
if (!text.isEmpty() && text.toString() == original.text) {
|
if (!text.isEmpty() && text.toString() == original.text) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -174,7 +187,15 @@ Poll::Poll(
|
||||||
not_null<PollData*> poll)
|
not_null<PollData*> poll)
|
||||||
: Media(parent)
|
: Media(parent)
|
||||||
, _poll(poll)
|
, _poll(poll)
|
||||||
, _question(st::msgMinWidth / 2) {
|
, _question(st::msgMinWidth / 2)
|
||||||
|
, _showResultsLink(
|
||||||
|
std::make_shared<LambdaClickHandler>(crl::guard(
|
||||||
|
this,
|
||||||
|
[=] { showResults(); })))
|
||||||
|
, _sendVotesLink(
|
||||||
|
std::make_shared<LambdaClickHandler>(crl::guard(
|
||||||
|
this,
|
||||||
|
[=] { sendMultiOptions(); }))) {
|
||||||
history()->owner().registerPollView(_poll, _parent);
|
history()->owner().registerPollView(_poll, _parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,13 +223,17 @@ QSize Poll::countOptimalSize() {
|
||||||
+ st::historyPollAnswerPadding.bottom();
|
+ st::historyPollAnswerPadding.bottom();
|
||||||
}), 0);
|
}), 0);
|
||||||
|
|
||||||
|
const auto bottomButtonHeight = inlineFooter()
|
||||||
|
? 0
|
||||||
|
: st::historyPollBottomButtonSkip;
|
||||||
auto minHeight = st::historyPollQuestionTop
|
auto minHeight = st::historyPollQuestionTop
|
||||||
+ _question.minHeight()
|
+ _question.minHeight()
|
||||||
+ st::historyPollSubtitleSkip
|
+ st::historyPollSubtitleSkip
|
||||||
+ st::msgDateFont->height
|
+ st::msgDateFont->height
|
||||||
+ st::historyPollAnswersSkip
|
+ st::historyPollAnswersSkip
|
||||||
+ answersHeight
|
+ answersHeight
|
||||||
+ st::msgPadding.bottom()
|
+ st::historyPollTotalVotesSkip
|
||||||
|
+ bottomButtonHeight
|
||||||
+ st::msgDateFont->height
|
+ st::msgDateFont->height
|
||||||
+ st::msgPadding.bottom();
|
+ st::msgPadding.bottom();
|
||||||
if (!isBubbleTop()) {
|
if (!isBubbleTop()) {
|
||||||
|
|
@ -218,13 +243,28 @@ QSize Poll::countOptimalSize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Poll::showVotes() const {
|
bool Poll::showVotes() const {
|
||||||
return _voted || _closed;
|
return _voted || (_flags & PollData::Flag::Closed);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Poll::canVote() const {
|
bool Poll::canVote() const {
|
||||||
return !showVotes() && IsServerMsgId(_parent->data()->id);
|
return !showVotes() && IsServerMsgId(_parent->data()->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Poll::canSendVotes() const {
|
||||||
|
return canVote() && _hasSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Poll::showVotersCount() const {
|
||||||
|
return showVotes()
|
||||||
|
? (!_totalVotes || !(_flags & PollData::Flag::PublicVotes))
|
||||||
|
: !(_flags & PollData::Flag::MultiChoice);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Poll::inlineFooter() const {
|
||||||
|
return !(_flags
|
||||||
|
& (PollData::Flag::PublicVotes | PollData::Flag::MultiChoice));
|
||||||
|
}
|
||||||
|
|
||||||
int Poll::countAnswerTop(
|
int Poll::countAnswerTop(
|
||||||
const Answer &answer,
|
const Answer &answer,
|
||||||
int innerWidth) const {
|
int innerWidth) const {
|
||||||
|
|
@ -278,6 +318,9 @@ QSize Poll::countCurrentSize(int newWidth) {
|
||||||
return countAnswerHeight(answer, innerWidth);
|
return countAnswerHeight(answer, innerWidth);
|
||||||
}), 0);
|
}), 0);
|
||||||
|
|
||||||
|
const auto bottomButtonHeight = inlineFooter()
|
||||||
|
? 0
|
||||||
|
: st::historyPollBottomButtonSkip;
|
||||||
auto newHeight = st::historyPollQuestionTop
|
auto newHeight = st::historyPollQuestionTop
|
||||||
+ _question.countHeight(innerWidth)
|
+ _question.countHeight(innerWidth)
|
||||||
+ st::historyPollSubtitleSkip
|
+ st::historyPollSubtitleSkip
|
||||||
|
|
@ -285,6 +328,7 @@ QSize Poll::countCurrentSize(int newWidth) {
|
||||||
+ st::historyPollAnswersSkip
|
+ st::historyPollAnswersSkip
|
||||||
+ answersHeight
|
+ answersHeight
|
||||||
+ st::historyPollTotalVotesSkip
|
+ st::historyPollTotalVotesSkip
|
||||||
|
+ bottomButtonHeight
|
||||||
+ st::msgDateFont->height
|
+ st::msgDateFont->height
|
||||||
+ st::msgPadding.bottom();
|
+ st::msgPadding.bottom();
|
||||||
if (!isBubbleTop()) {
|
if (!isBubbleTop()) {
|
||||||
|
|
@ -309,13 +353,22 @@ void Poll::updateTexts() {
|
||||||
_poll->question,
|
_poll->question,
|
||||||
options);
|
options);
|
||||||
}
|
}
|
||||||
if (_closed != _poll->closed || _subtitle.isEmpty()) {
|
if (_flags != _poll->flags() || _subtitle.isEmpty()) {
|
||||||
_closed = _poll->closed;
|
using Flag = PollData::Flag;
|
||||||
|
_flags = _poll->flags();
|
||||||
_subtitle.setText(
|
_subtitle.setText(
|
||||||
st::msgDateTextStyle,
|
st::msgDateTextStyle,
|
||||||
_closed ? tr::lng_polls_closed(tr::now) : tr::lng_polls_anonymous(tr::now));
|
((_flags & Flag::Closed)
|
||||||
|
? tr::lng_polls_closed(tr::now)
|
||||||
|
: (_flags & Flag::Quiz)
|
||||||
|
? ((_flags & Flag::PublicVotes)
|
||||||
|
? tr::lng_polls_public_quiz(tr::now)
|
||||||
|
: tr::lng_polls_anonymous_quiz(tr::now))
|
||||||
|
: ((_flags & Flag::PublicVotes)
|
||||||
|
? tr::lng_polls_public(tr::now)
|
||||||
|
: tr::lng_polls_anonymous(tr::now))));
|
||||||
}
|
}
|
||||||
|
updateRecentVoters();
|
||||||
updateAnswers();
|
updateAnswers();
|
||||||
updateVotes();
|
updateVotes();
|
||||||
|
|
||||||
|
|
@ -324,6 +377,16 @@ void Poll::updateTexts() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Poll::updateRecentVoters() {
|
||||||
|
auto &&sliced = ranges::view::all(
|
||||||
|
_poll->recentVoters
|
||||||
|
) | ranges::view::take(kShowRecentVotersCount);
|
||||||
|
const auto changed = !ranges::equal(_recentVoters, sliced);
|
||||||
|
if (changed) {
|
||||||
|
_recentVoters = sliced | ranges::to_vector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Poll::updateAnswers() {
|
void Poll::updateAnswers() {
|
||||||
const auto changed = !ranges::equal(
|
const auto changed = !ranges::equal(
|
||||||
_answers,
|
_answers,
|
||||||
|
|
@ -334,16 +397,16 @@ void Poll::updateAnswers() {
|
||||||
if (!changed) {
|
if (!changed) {
|
||||||
auto &&answers = ranges::view::zip(_answers, _poll->answers);
|
auto &&answers = ranges::view::zip(_answers, _poll->answers);
|
||||||
for (auto &&[answer, original] : answers) {
|
for (auto &&[answer, original] : answers) {
|
||||||
answer.fillText(original);
|
answer.fillData(_poll, original);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_answers = ranges::view::all(
|
_answers = ranges::view::all(
|
||||||
_poll->answers
|
_poll->answers
|
||||||
) | ranges::view::transform([](const PollAnswer &answer) {
|
) | ranges::view::transform([&](const PollAnswer &answer) {
|
||||||
auto result = Answer();
|
auto result = Answer();
|
||||||
result.option = answer.option;
|
result.option = answer.option;
|
||||||
result.fillText(answer);
|
result.fillData(_poll, answer);
|
||||||
return result;
|
return result;
|
||||||
}) | ranges::to_vector;
|
}) | ranges::to_vector;
|
||||||
|
|
||||||
|
|
@ -355,12 +418,66 @@ void Poll::updateAnswers() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ClickHandlerPtr Poll::createAnswerClickHandler(
|
ClickHandlerPtr Poll::createAnswerClickHandler(
|
||||||
const Answer &answer) const {
|
const Answer &answer) {
|
||||||
const auto option = answer.option;
|
const auto option = answer.option;
|
||||||
const auto itemId = _parent->data()->fullId();
|
if (_flags & PollData::Flag::MultiChoice) {
|
||||||
return std::make_shared<LambdaClickHandler>([=] {
|
return std::make_shared<LambdaClickHandler>(crl::guard(this, [=] {
|
||||||
history()->session().api().sendPollVotes(itemId, { option });
|
toggleMultiOption(option);
|
||||||
});
|
}));
|
||||||
|
}
|
||||||
|
return std::make_shared<LambdaClickHandler>(crl::guard(this, [=] {
|
||||||
|
history()->session().api().sendPollVotes(
|
||||||
|
_parent->data()->fullId(),
|
||||||
|
{ option });
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Poll::toggleMultiOption(const QByteArray &option) {
|
||||||
|
const auto i = ranges::find(
|
||||||
|
_answers,
|
||||||
|
option,
|
||||||
|
&Answer::option);
|
||||||
|
if (i != end(_answers)) {
|
||||||
|
const auto selected = i->selected;
|
||||||
|
i->selected = !selected;
|
||||||
|
i->selectedAnimation.start(
|
||||||
|
[=] { history()->owner().requestViewRepaint(_parent); },
|
||||||
|
selected ? 1. : 0.,
|
||||||
|
selected ? 0. : 1.,
|
||||||
|
st::defaultCheck.duration);
|
||||||
|
if (selected) {
|
||||||
|
const auto j = ranges::find(
|
||||||
|
_answers,
|
||||||
|
true,
|
||||||
|
&Answer::selected);
|
||||||
|
_hasSelected = (j != end(_answers));
|
||||||
|
} else {
|
||||||
|
_hasSelected = true;
|
||||||
|
}
|
||||||
|
history()->owner().requestViewRepaint(_parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Poll::sendMultiOptions() {
|
||||||
|
auto chosen = _answers | ranges::view::filter(
|
||||||
|
&Answer::selected
|
||||||
|
) | ranges::view::transform(
|
||||||
|
&Answer::option
|
||||||
|
) | ranges::to_vector;
|
||||||
|
if (!chosen.empty()) {
|
||||||
|
for (auto &answer : _answers) {
|
||||||
|
answer.selected = false;
|
||||||
|
}
|
||||||
|
history()->session().api().sendPollVotes(
|
||||||
|
_parent->data()->fullId(),
|
||||||
|
std::move(chosen));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Poll::showResults() {
|
||||||
|
_parent->delegate()->elementShowPollResults(
|
||||||
|
_poll,
|
||||||
|
_parent->data()->fullId());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Poll::updateVotes() {
|
void Poll::updateVotes() {
|
||||||
|
|
@ -370,21 +487,23 @@ void Poll::updateVotes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Poll::checkSendingAnimation() const {
|
void Poll::checkSendingAnimation() const {
|
||||||
const auto &sending = _poll->sendingVote;
|
const auto &sending = _poll->sendingVotes;
|
||||||
if (sending.isEmpty() == !_sendingAnimation) {
|
const auto sendingRadial = (sending.size() == 1)
|
||||||
|
&& !(_flags & PollData::Flag::MultiChoice);
|
||||||
|
if (sendingRadial == (_sendingAnimation != nullptr)) {
|
||||||
if (_sendingAnimation) {
|
if (_sendingAnimation) {
|
||||||
_sendingAnimation->option = sending;
|
_sendingAnimation->option = sending.front();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (sending.isEmpty()) {
|
if (!sendingRadial) {
|
||||||
if (!_answersAnimation) {
|
if (!_answersAnimation) {
|
||||||
_sendingAnimation = nullptr;
|
_sendingAnimation = nullptr;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_sendingAnimation = std::make_unique<SendingAnimation>(
|
_sendingAnimation = std::make_unique<SendingAnimation>(
|
||||||
sending,
|
sending.front(),
|
||||||
[=] { radialAnimationCallback(); });
|
[=] { radialAnimationCallback(); });
|
||||||
_sendingAnimation->animation.start();
|
_sendingAnimation->animation.start();
|
||||||
}
|
}
|
||||||
|
|
@ -394,9 +513,17 @@ void Poll::updateTotalVotes() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_totalVotes = _poll->totalVoters;
|
_totalVotes = _poll->totalVoters;
|
||||||
|
const auto quiz = _poll->quiz();
|
||||||
const auto string = !_totalVotes
|
const auto string = !_totalVotes
|
||||||
? tr::lng_polls_votes_none(tr::now)
|
? (quiz
|
||||||
: tr::lng_polls_votes_count(tr::now, lt_count_short, _totalVotes);
|
? tr::lng_polls_answers_none
|
||||||
|
: tr::lng_polls_votes_none)(tr::now)
|
||||||
|
: (quiz
|
||||||
|
? tr::lng_polls_answers_count
|
||||||
|
: tr::lng_polls_votes_count)(
|
||||||
|
tr::now,
|
||||||
|
lt_count_short,
|
||||||
|
_totalVotes);
|
||||||
_totalVotesLabel.setText(st::msgDateTextStyle, string);
|
_totalVotesLabel.setText(st::msgDateTextStyle, string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -485,6 +612,7 @@ void Poll::draw(Painter &p, const QRect &r, TextSelection selection, crl::time m
|
||||||
|
|
||||||
p.setPen(regular);
|
p.setPen(regular);
|
||||||
_subtitle.drawLeftElided(p, padding.left(), tshift, paintw, width());
|
_subtitle.drawLeftElided(p, padding.left(), tshift, paintw, width());
|
||||||
|
paintRecentVoters(p, padding.left() + _subtitle.maxWidth(), tshift, selection);
|
||||||
tshift += st::msgDateFont->height + st::historyPollAnswersSkip;
|
tshift += st::msgDateFont->height + st::historyPollAnswersSkip;
|
||||||
|
|
||||||
const auto progress = _answersAnimation
|
const auto progress = _answersAnimation
|
||||||
|
|
@ -517,23 +645,80 @@ void Poll::draw(Painter &p, const QRect &r, TextSelection selection, crl::time m
|
||||||
selection);
|
selection);
|
||||||
tshift += height;
|
tshift += height;
|
||||||
}
|
}
|
||||||
if (!_totalVotesLabel.isEmpty()) {
|
if (!inlineFooter()) {
|
||||||
|
paintBottom(p, padding.left(), tshift, paintw, selection);
|
||||||
|
} else if (!_totalVotesLabel.isEmpty()) {
|
||||||
tshift += st::msgPadding.bottom();
|
tshift += st::msgPadding.bottom();
|
||||||
|
paintInlineFooter(p, padding.left(), tshift, paintw, selection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Poll::paintInlineFooter(
|
||||||
|
Painter &p,
|
||||||
|
int left,
|
||||||
|
int top,
|
||||||
|
int paintw,
|
||||||
|
TextSelection selection) const {
|
||||||
|
const auto selected = (selection == FullSelection);
|
||||||
|
const auto outbg = _parent->hasOutLayout();
|
||||||
|
const auto ®ular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg);
|
||||||
|
p.setPen(regular);
|
||||||
|
_totalVotesLabel.drawLeftElided(
|
||||||
|
p,
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
std::min(
|
||||||
|
_totalVotesLabel.maxWidth(),
|
||||||
|
paintw - _parent->infoWidth()),
|
||||||
|
width());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Poll::paintBottom(
|
||||||
|
Painter &p,
|
||||||
|
int left,
|
||||||
|
int top,
|
||||||
|
int paintw,
|
||||||
|
TextSelection selection) const {
|
||||||
|
const auto stringtop = top + st::msgPadding.bottom() + st::historyPollBottomButtonTop;
|
||||||
|
const auto selected = (selection == FullSelection);
|
||||||
|
const auto outbg = _parent->hasOutLayout();
|
||||||
|
const auto ®ular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg);
|
||||||
|
if (showVotersCount()) {
|
||||||
p.setPen(regular);
|
p.setPen(regular);
|
||||||
_totalVotesLabel.drawLeftElided(
|
_totalVotesLabel.draw(p, left, stringtop, paintw, style::al_top);
|
||||||
p,
|
} else {
|
||||||
padding.left(),
|
const auto link = showVotes()
|
||||||
tshift,
|
? _showResultsLink
|
||||||
std::min(
|
: canSendVotes()
|
||||||
_totalVotesLabel.maxWidth(),
|
? _sendVotesLink
|
||||||
paintw - _parent->infoWidth()),
|
: nullptr;
|
||||||
width());
|
if (_linkRipple) {
|
||||||
|
const auto linkHeight = bottomButtonHeight();
|
||||||
|
p.setOpacity(st::historyPollRippleOpacity);
|
||||||
|
_linkRipple->paint(p, left - st::msgPadding.left(), height() - linkHeight, width());
|
||||||
|
if (_linkRipple->empty()) {
|
||||||
|
_linkRipple.reset();
|
||||||
|
}
|
||||||
|
p.setOpacity(1.);
|
||||||
|
}
|
||||||
|
p.setFont(st::semiboldFont);
|
||||||
|
if (!link) {
|
||||||
|
p.setPen(regular);
|
||||||
|
} else {
|
||||||
|
p.setPen(outbg ? (selected ? st::msgFileThumbLinkOutFgSelected : st::msgFileThumbLinkOutFg) : (selected ? st::msgFileThumbLinkInFgSelected : st::msgFileThumbLinkInFg));
|
||||||
|
}
|
||||||
|
const auto string = showVotes()
|
||||||
|
? tr::lng_polls_view_results(tr::now, Ui::Text::Upper)
|
||||||
|
: tr::lng_polls_submit_votes(tr::now, Ui::Text::Upper);
|
||||||
|
const auto stringw = st::semiboldFont->width(string);
|
||||||
|
p.drawTextLeft(left + (paintw - stringw) / 2, stringtop, width(), string, stringw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Poll::resetAnswersAnimation() const {
|
void Poll::resetAnswersAnimation() const {
|
||||||
_answersAnimation = nullptr;
|
_answersAnimation = nullptr;
|
||||||
if (_poll->sendingVote.isEmpty()) {
|
if (_poll->sendingVotes.size() != 1
|
||||||
|
|| (_flags & PollData::Flag::MultiChoice)) {
|
||||||
_sendingAnimation = nullptr;
|
_sendingAnimation = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -544,6 +729,36 @@ void Poll::radialAnimationCallback() const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Poll::paintRecentVoters(
|
||||||
|
Painter &p,
|
||||||
|
int left,
|
||||||
|
int top,
|
||||||
|
TextSelection selection) const {
|
||||||
|
const auto count = int(_recentVoters.size());
|
||||||
|
if (!count) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto x = left
|
||||||
|
+ st::historyPollRecentVotersSkip
|
||||||
|
+ (count - 1) * st::historyPollRecentVoterSkip;
|
||||||
|
auto y = top;
|
||||||
|
const auto size = st::historyPollRecentVoterSize;
|
||||||
|
const auto outbg = _parent->hasOutLayout();
|
||||||
|
const auto selected = (selection == FullSelection);
|
||||||
|
auto pen = (selected
|
||||||
|
? (outbg ? st::msgOutBgSelected : st::msgInBgSelected)
|
||||||
|
: (outbg ? st::msgOutBg : st::msgInBg))->p;
|
||||||
|
pen.setWidth(st::lineWidth);
|
||||||
|
for (const auto &recent : _recentVoters) {
|
||||||
|
recent->paintUserpic(p, x, y, size);
|
||||||
|
p.setPen(pen);
|
||||||
|
p.setBrush(Qt::NoBrush);
|
||||||
|
PainterHighQualityEnabler hq(p);
|
||||||
|
p.drawEllipse(x, y, size, size);
|
||||||
|
x -= st::historyPollRecentVoterSkip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int Poll::paintAnswer(
|
int Poll::paintAnswer(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
const Answer &answer,
|
const Answer &answer,
|
||||||
|
|
@ -592,6 +807,8 @@ int Poll::paintAnswer(
|
||||||
p.setOpacity(sqrt(opacity));
|
p.setOpacity(sqrt(opacity));
|
||||||
paintFilling(
|
paintFilling(
|
||||||
p,
|
p,
|
||||||
|
animation->chosen,
|
||||||
|
animation->correct,
|
||||||
animation->filling.current(),
|
animation->filling.current(),
|
||||||
left,
|
left,
|
||||||
top,
|
top,
|
||||||
|
|
@ -613,6 +830,8 @@ int Poll::paintAnswer(
|
||||||
selection);
|
selection);
|
||||||
paintFilling(
|
paintFilling(
|
||||||
p,
|
p,
|
||||||
|
answer.chosen,
|
||||||
|
answer.correct,
|
||||||
answer.filling,
|
answer.filling,
|
||||||
left,
|
left,
|
||||||
top,
|
top,
|
||||||
|
|
@ -644,9 +863,13 @@ void Poll::paintRadio(
|
||||||
const auto over = ClickHandler::showAsActive(answer.handler);
|
const auto over = ClickHandler::showAsActive(answer.handler);
|
||||||
const auto ®ular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg);
|
const auto ®ular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg);
|
||||||
|
|
||||||
p.setBrush(Qt::NoBrush);
|
const auto checkmark = answer.selectedAnimation.value(answer.selected ? 1. : 0.);
|
||||||
|
|
||||||
const auto o = p.opacity();
|
const auto o = p.opacity();
|
||||||
p.setOpacity(o * (over ? st::historyPollRadioOpacityOver : st::historyPollRadioOpacity));
|
if (checkmark < 1.) {
|
||||||
|
p.setBrush(Qt::NoBrush);
|
||||||
|
p.setOpacity(o * (over ? st::historyPollRadioOpacityOver : st::historyPollRadioOpacity));
|
||||||
|
}
|
||||||
|
|
||||||
const auto rect = QRectF(left, top, st.diameter, st.diameter).marginsRemoved(QMarginsF(st.thickness / 2., st.thickness / 2., st.thickness / 2., st.thickness / 2.));
|
const auto rect = QRectF(left, top, st.diameter, st.diameter).marginsRemoved(QMarginsF(st.thickness / 2., st.thickness / 2., st.thickness / 2., st.thickness / 2.));
|
||||||
if (_sendingAnimation && _sendingAnimation->option == answer.option) {
|
if (_sendingAnimation && _sendingAnimation->option == answer.option) {
|
||||||
|
|
@ -665,10 +888,24 @@ void Poll::paintRadio(
|
||||||
state.arcLength);
|
state.arcLength);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
auto pen = regular->p;
|
if (checkmark < 1.) {
|
||||||
pen.setWidth(st.thickness);
|
auto pen = regular->p;
|
||||||
p.setPen(pen);
|
pen.setWidth(st.thickness);
|
||||||
p.drawEllipse(rect);
|
p.setPen(pen);
|
||||||
|
p.drawEllipse(rect);
|
||||||
|
}
|
||||||
|
if (checkmark > 0.) {
|
||||||
|
const auto removeFull = (st.diameter / 2 - st.thickness);
|
||||||
|
const auto removeNow = removeFull * (1. - checkmark);
|
||||||
|
const auto color = outbg ? (selected ? st::msgFileThumbLinkOutFgSelected : st::msgFileThumbLinkOutFg) : (selected ? st::msgFileThumbLinkInFgSelected : st::msgFileThumbLinkInFg);
|
||||||
|
auto pen = color->p;
|
||||||
|
pen.setWidth(st.thickness);
|
||||||
|
p.setPen(pen);
|
||||||
|
p.setBrush(color);
|
||||||
|
p.drawEllipse(rect.marginsRemoved({ removeNow, removeNow, removeNow, removeNow }));
|
||||||
|
const auto &icon = outbg ? (selected ? st::historyPollOutChosenSelected : st::historyPollOutChosen) : (selected ? st::historyPollInChosenSelected : st::historyPollInChosen);
|
||||||
|
icon.paint(p, left + (st.diameter - icon.width()) / 2, top + (st.diameter - icon.height()) / 2, width());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p.setOpacity(o);
|
p.setOpacity(o);
|
||||||
|
|
@ -696,6 +933,8 @@ void Poll::paintPercent(
|
||||||
|
|
||||||
void Poll::paintFilling(
|
void Poll::paintFilling(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
|
bool chosen,
|
||||||
|
bool correct,
|
||||||
float64 filling,
|
float64 filling,
|
||||||
int left,
|
int left,
|
||||||
int top,
|
int top,
|
||||||
|
|
@ -712,15 +951,36 @@ void Poll::paintFilling(
|
||||||
|
|
||||||
top += st::historyPollAnswerPadding.top();
|
top += st::historyPollAnswerPadding.top();
|
||||||
|
|
||||||
const auto bar = outbg ? (selected ? st::msgWaveformOutActiveSelected : st::msgWaveformOutActive) : (selected ? st::msgWaveformInActiveSelected : st::msgWaveformInActive);
|
|
||||||
PainterHighQualityEnabler hq(p);
|
PainterHighQualityEnabler hq(p);
|
||||||
p.setPen(Qt::NoPen);
|
p.setPen(Qt::NoPen);
|
||||||
p.setBrush(bar);
|
const auto thickness = st::historyPollFillingHeight;
|
||||||
const auto max = awidth - st::historyPollFillingRight;
|
const auto max = awidth - st::historyPollFillingRight;
|
||||||
const auto size = anim::interpolate(st::historyPollFillingMin, max, filling);
|
const auto size = anim::interpolate(st::historyPollFillingMin, max, filling);
|
||||||
const auto radius = st::historyPollFillingRadius;
|
const auto radius = st::historyPollFillingRadius;
|
||||||
const auto ftop = bottom - st::historyPollFillingBottom - st::historyPollFillingHeight;
|
const auto ftop = bottom - st::historyPollFillingBottom - thickness;
|
||||||
p.drawRoundedRect(aleft, ftop, size, st::historyPollFillingHeight, radius, radius);
|
|
||||||
|
if (chosen && !correct) {
|
||||||
|
p.setBrush(st::boxTextFgError);
|
||||||
|
} else {
|
||||||
|
const auto bar = outbg ? (selected ? st::msgWaveformOutActiveSelected : st::msgWaveformOutActive) : (selected ? st::msgWaveformInActiveSelected : st::msgWaveformInActive);
|
||||||
|
p.setBrush(bar);
|
||||||
|
}
|
||||||
|
auto barleft = aleft;
|
||||||
|
auto barwidth = size;
|
||||||
|
if (chosen || correct) {
|
||||||
|
const auto &icon = (chosen && !correct)
|
||||||
|
? st::historyPollChoiceWrong
|
||||||
|
: st::historyPollChoiceRight;
|
||||||
|
const auto cleft = aleft - st::historyPollPercentSkip - icon.width();
|
||||||
|
const auto ctop = ftop - (icon.height() - thickness) / 2;
|
||||||
|
p.drawEllipse(cleft, ctop, icon.width(), icon.height());
|
||||||
|
icon.paint(p, cleft, ctop, width);
|
||||||
|
//barleft += icon.width() - radius;
|
||||||
|
//barwidth -= icon.width() - radius;
|
||||||
|
}
|
||||||
|
if (barwidth > 0) {
|
||||||
|
p.drawRoundedRect(barleft, ftop, barwidth, thickness, radius, radius);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Poll::answerVotesChanged() const {
|
bool Poll::answerVotesChanged() const {
|
||||||
|
|
@ -748,6 +1008,8 @@ void Poll::saveStateInAnimation() const {
|
||||||
result.percent = show ? float64(answer.votesPercent) : 0.;
|
result.percent = show ? float64(answer.votesPercent) : 0.;
|
||||||
result.filling = show ? answer.filling : 0.;
|
result.filling = show ? answer.filling : 0.;
|
||||||
result.opacity = show ? 1. : 0.;
|
result.opacity = show ? 1. : 0.;
|
||||||
|
result.chosen = answer.chosen;
|
||||||
|
result.correct = answer.correct;
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
ranges::transform(
|
ranges::transform(
|
||||||
|
|
@ -761,7 +1023,7 @@ bool Poll::checkAnimationStart() const {
|
||||||
// Skip initial changes.
|
// Skip initial changes.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto result = (showVotes() != (_poll->voted() || _poll->closed))
|
const auto result = (showVotes() != (_poll->voted() || _poll->closed()))
|
||||||
|| answerVotesChanged();
|
|| answerVotesChanged();
|
||||||
if (result) {
|
if (result) {
|
||||||
saveStateInAnimation();
|
saveStateInAnimation();
|
||||||
|
|
@ -780,6 +1042,8 @@ void Poll::startAnswersAnimation() const {
|
||||||
data.percent.start(show ? float64(answer.votesPercent) : 0.);
|
data.percent.start(show ? float64(answer.votesPercent) : 0.);
|
||||||
data.filling.start(show ? answer.filling : 0.);
|
data.filling.start(show ? answer.filling : 0.);
|
||||||
data.opacity.start(show ? 1. : 0.);
|
data.opacity.start(show ? 1. : 0.);
|
||||||
|
data.chosen = data.chosen || answer.chosen;
|
||||||
|
data.correct = data.correct || answer.correct;
|
||||||
}
|
}
|
||||||
_answersAnimation->progress.start(
|
_answersAnimation->progress.start(
|
||||||
[=] { history()->owner().requestViewRepaint(_parent); },
|
[=] { history()->owner().requestViewRepaint(_parent); },
|
||||||
|
|
@ -790,7 +1054,7 @@ void Poll::startAnswersAnimation() const {
|
||||||
|
|
||||||
TextState Poll::textState(QPoint point, StateRequest request) const {
|
TextState Poll::textState(QPoint point, StateRequest request) const {
|
||||||
auto result = TextState(_parent);
|
auto result = TextState(_parent);
|
||||||
if (!_poll->sendingVote.isEmpty()) {
|
if (!_poll->sendingVotes.empty()) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -819,15 +1083,39 @@ TextState Poll::textState(QPoint point, StateRequest request) const {
|
||||||
result.customTooltip = true;
|
result.customTooltip = true;
|
||||||
using Flag = Ui::Text::StateRequest::Flag;
|
using Flag = Ui::Text::StateRequest::Flag;
|
||||||
if (request.flags & Flag::LookupCustomTooltip) {
|
if (request.flags & Flag::LookupCustomTooltip) {
|
||||||
|
const auto quiz = _poll->quiz();
|
||||||
result.customTooltipText = answer.votes
|
result.customTooltipText = answer.votes
|
||||||
? tr::lng_polls_votes_count(tr::now, lt_count_decimal, answer.votes)
|
? (quiz
|
||||||
: tr::lng_polls_votes_none(tr::now);
|
? tr::lng_polls_answers_count
|
||||||
|
: tr::lng_polls_votes_count)(
|
||||||
|
tr::now,
|
||||||
|
lt_count_decimal,
|
||||||
|
answer.votes)
|
||||||
|
: (quiz
|
||||||
|
? tr::lng_polls_answers_none
|
||||||
|
: tr::lng_polls_votes_none)(tr::now);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
tshift += height;
|
tshift += height;
|
||||||
}
|
}
|
||||||
|
if (!showVotersCount()) {
|
||||||
|
const auto link = showVotes()
|
||||||
|
? _showResultsLink
|
||||||
|
: canSendVotes()
|
||||||
|
? _sendVotesLink
|
||||||
|
: nullptr;
|
||||||
|
if (link) {
|
||||||
|
const auto linkHeight = bottomButtonHeight();
|
||||||
|
const auto linkTop = height() - linkHeight;
|
||||||
|
if (QRect(0, linkTop, width(), linkHeight).contains(point)) {
|
||||||
|
_lastLinkPoint = point;
|
||||||
|
result.link = link;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -842,6 +1130,8 @@ void Poll::clickHandlerPressedChanged(
|
||||||
&Answer::handler);
|
&Answer::handler);
|
||||||
if (i != end(_answers)) {
|
if (i != end(_answers)) {
|
||||||
toggleRipple(*i, pressed);
|
toggleRipple(*i, pressed);
|
||||||
|
} else if (handler == _sendVotesLink || handler == _showResultsLink) {
|
||||||
|
toggleLinkRipple(pressed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -864,10 +1154,55 @@ void Poll::toggleRipple(Answer &answer, bool pressed) {
|
||||||
}
|
}
|
||||||
const auto top = countAnswerTop(answer, innerWidth);
|
const auto top = countAnswerTop(answer, innerWidth);
|
||||||
answer.ripple->add(_lastLinkPoint - QPoint(0, top));
|
answer.ripple->add(_lastLinkPoint - QPoint(0, top));
|
||||||
} else {
|
} else if (answer.ripple) {
|
||||||
if (answer.ripple) {
|
answer.ripple->lastStop();
|
||||||
answer.ripple->lastStop();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int Poll::bottomButtonHeight() const {
|
||||||
|
const auto skip = st::historyPollChoiceRight.height()
|
||||||
|
- st::historyPollFillingBottom
|
||||||
|
- st::historyPollFillingHeight
|
||||||
|
- (st::historyPollChoiceRight.height() - st::historyPollFillingHeight) / 2;
|
||||||
|
return st::historyPollTotalVotesSkip
|
||||||
|
- skip
|
||||||
|
+ st::historyPollBottomButtonSkip
|
||||||
|
+ st::msgDateFont->height
|
||||||
|
+ st::msgPadding.bottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Poll::toggleLinkRipple(bool pressed) {
|
||||||
|
if (pressed) {
|
||||||
|
const auto linkWidth = width();
|
||||||
|
const auto linkHeight = bottomButtonHeight();
|
||||||
|
if (!_linkRipple) {
|
||||||
|
const auto drawMask = [&](QPainter &p) {
|
||||||
|
const auto radius = st::historyMessageRadius;
|
||||||
|
p.drawRoundedRect(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
linkWidth,
|
||||||
|
linkHeight,
|
||||||
|
radius,
|
||||||
|
radius);
|
||||||
|
p.fillRect(0, 0, linkWidth, radius * 2, Qt::white);
|
||||||
|
};
|
||||||
|
auto mask = isBubbleBottom()
|
||||||
|
? Ui::RippleAnimation::maskByDrawer(
|
||||||
|
QSize(linkWidth, linkHeight),
|
||||||
|
false,
|
||||||
|
drawMask)
|
||||||
|
: Ui::RippleAnimation::rectMask({ linkWidth, linkHeight });
|
||||||
|
_linkRipple = std::make_unique<Ui::RippleAnimation>(
|
||||||
|
(_parent->hasOutLayout()
|
||||||
|
? st::historyPollRippleOut
|
||||||
|
: st::historyPollRippleIn),
|
||||||
|
std::move(mask),
|
||||||
|
[=] { history()->owner().requestViewRepaint(_parent); });
|
||||||
}
|
}
|
||||||
|
_linkRipple->add(_lastLinkPoint - QPoint(0, height() - linkHeight));
|
||||||
|
} else if (_linkRipple) {
|
||||||
|
_linkRipple->lastStop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "history/view/media/history_view_media.h"
|
#include "history/view/media/history_view_media.h"
|
||||||
|
#include "data/data_poll.h"
|
||||||
|
#include "base/weak_ptr.h"
|
||||||
|
|
||||||
struct PollAnswer;
|
namespace Ui {
|
||||||
|
class RippleAnimation;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
class Poll : public Media {
|
class Poll : public Media, public base::has_weak_ptr {
|
||||||
public:
|
public:
|
||||||
Poll(
|
Poll(
|
||||||
not_null<Element*> parent,
|
not_null<Element*> parent,
|
||||||
|
|
@ -53,6 +57,7 @@ private:
|
||||||
|
|
||||||
[[nodiscard]] bool showVotes() const;
|
[[nodiscard]] bool showVotes() const;
|
||||||
[[nodiscard]] bool canVote() const;
|
[[nodiscard]] bool canVote() const;
|
||||||
|
[[nodiscard]] bool canSendVotes() const;
|
||||||
|
|
||||||
[[nodiscard]] int countAnswerTop(
|
[[nodiscard]] int countAnswerTop(
|
||||||
const Answer &answer,
|
const Answer &answer,
|
||||||
|
|
@ -61,11 +66,14 @@ private:
|
||||||
const Answer &answer,
|
const Answer &answer,
|
||||||
int innerWidth) const;
|
int innerWidth) const;
|
||||||
[[nodiscard]] ClickHandlerPtr createAnswerClickHandler(
|
[[nodiscard]] ClickHandlerPtr createAnswerClickHandler(
|
||||||
const Answer &answer) const;
|
const Answer &answer);
|
||||||
void updateTexts();
|
void updateTexts();
|
||||||
|
void updateRecentVoters();
|
||||||
void updateAnswers();
|
void updateAnswers();
|
||||||
void updateVotes();
|
void updateVotes();
|
||||||
void updateTotalVotes();
|
void updateTotalVotes();
|
||||||
|
bool showVotersCount() const;
|
||||||
|
bool inlineFooter() const;
|
||||||
void updateAnswerVotes();
|
void updateAnswerVotes();
|
||||||
void updateAnswerVotesFromOriginal(
|
void updateAnswerVotesFromOriginal(
|
||||||
Answer &answer,
|
Answer &answer,
|
||||||
|
|
@ -74,6 +82,11 @@ private:
|
||||||
int maxVotes);
|
int maxVotes);
|
||||||
void checkSendingAnimation() const;
|
void checkSendingAnimation() const;
|
||||||
|
|
||||||
|
void paintRecentVoters(
|
||||||
|
Painter &p,
|
||||||
|
int left,
|
||||||
|
int top,
|
||||||
|
TextSelection selection) const;
|
||||||
int paintAnswer(
|
int paintAnswer(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
const Answer &answer,
|
const Answer &answer,
|
||||||
|
|
@ -99,12 +112,26 @@ private:
|
||||||
TextSelection selection) const;
|
TextSelection selection) const;
|
||||||
void paintFilling(
|
void paintFilling(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
|
bool chosen,
|
||||||
|
bool correct,
|
||||||
float64 filling,
|
float64 filling,
|
||||||
int left,
|
int left,
|
||||||
int top,
|
int top,
|
||||||
int width,
|
int width,
|
||||||
int height,
|
int height,
|
||||||
TextSelection selection) const;
|
TextSelection selection) const;
|
||||||
|
void paintInlineFooter(
|
||||||
|
Painter &p,
|
||||||
|
int left,
|
||||||
|
int top,
|
||||||
|
int paintw,
|
||||||
|
TextSelection selection) const;
|
||||||
|
void paintBottom(
|
||||||
|
Painter &p,
|
||||||
|
int left,
|
||||||
|
int top,
|
||||||
|
int paintw,
|
||||||
|
TextSelection selection) const;
|
||||||
|
|
||||||
bool checkAnimationStart() const;
|
bool checkAnimationStart() const;
|
||||||
bool answerVotesChanged() const;
|
bool answerVotesChanged() const;
|
||||||
|
|
@ -114,17 +141,30 @@ private:
|
||||||
void radialAnimationCallback() const;
|
void radialAnimationCallback() const;
|
||||||
|
|
||||||
void toggleRipple(Answer &answer, bool pressed);
|
void toggleRipple(Answer &answer, bool pressed);
|
||||||
|
void toggleLinkRipple(bool pressed);
|
||||||
|
void toggleMultiOption(const QByteArray &option);
|
||||||
|
void sendMultiOptions();
|
||||||
|
void showResults();
|
||||||
|
|
||||||
not_null<PollData*> _poll;
|
[[nodiscard]] int bottomButtonHeight() const;
|
||||||
|
|
||||||
|
const not_null<PollData*> _poll;
|
||||||
int _pollVersion = 0;
|
int _pollVersion = 0;
|
||||||
int _totalVotes = 0;
|
int _totalVotes = 0;
|
||||||
bool _voted = false;
|
bool _voted = false;
|
||||||
bool _closed = false;
|
PollData::Flags _flags = PollData::Flags();
|
||||||
|
|
||||||
Ui::Text::String _question;
|
Ui::Text::String _question;
|
||||||
Ui::Text::String _subtitle;
|
Ui::Text::String _subtitle;
|
||||||
|
std::vector<not_null<UserData*>> _recentVoters;
|
||||||
|
QImage _recentVotersImage;
|
||||||
|
|
||||||
std::vector<Answer> _answers;
|
std::vector<Answer> _answers;
|
||||||
Ui::Text::String _totalVotesLabel;
|
Ui::Text::String _totalVotesLabel;
|
||||||
|
ClickHandlerPtr _showResultsLink;
|
||||||
|
ClickHandlerPtr _sendVotesLink;
|
||||||
|
mutable std::unique_ptr<Ui::RippleAnimation> _linkRipple;
|
||||||
|
bool _hasSelected = false;
|
||||||
|
|
||||||
mutable std::unique_ptr<AnswersAnimation> _answersAnimation;
|
mutable std::unique_ptr<AnswersAnimation> _answersAnimation;
|
||||||
mutable std::unique_ptr<SendingAnimation> _sendingAnimation;
|
mutable std::unique_ptr<SendingAnimation> _sendingAnimation;
|
||||||
|
|
|
||||||
|
|
@ -266,6 +266,8 @@ Key ContentMemento::key() const {
|
||||||
return Key(Auth().data().peer(peerId));
|
return Key(Auth().data().peer(peerId));
|
||||||
//} else if (const auto feed = this->feed()) { // #feed
|
//} else if (const auto feed = this->feed()) { // #feed
|
||||||
// return Key(feed);
|
// return Key(feed);
|
||||||
|
} else if (const auto poll = this->poll()) {
|
||||||
|
return Key(poll, pollContextId());
|
||||||
} else {
|
} else {
|
||||||
return Settings::Tag{ settingsSelf() };
|
return Settings::Tag{ settingsSelf() };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,10 @@ public:
|
||||||
//explicit ContentMemento(not_null<Data::Feed*> feed) : _feed(feed) { // #feed
|
//explicit ContentMemento(not_null<Data::Feed*> feed) : _feed(feed) { // #feed
|
||||||
//}
|
//}
|
||||||
explicit ContentMemento(Settings::Tag settings);
|
explicit ContentMemento(Settings::Tag settings);
|
||||||
|
ContentMemento(not_null<PollData*> poll, FullMsgId contextId)
|
||||||
|
: _poll(poll)
|
||||||
|
, _pollContextId(contextId) {
|
||||||
|
}
|
||||||
|
|
||||||
virtual object_ptr<ContentWidget> createWidget(
|
virtual object_ptr<ContentWidget> createWidget(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
|
|
@ -141,6 +145,12 @@ public:
|
||||||
UserData *settingsSelf() const {
|
UserData *settingsSelf() const {
|
||||||
return _settingsSelf;
|
return _settingsSelf;
|
||||||
}
|
}
|
||||||
|
PollData *poll() const {
|
||||||
|
return _poll;
|
||||||
|
}
|
||||||
|
FullMsgId pollContextId() const {
|
||||||
|
return _pollContextId;
|
||||||
|
}
|
||||||
Key key() const;
|
Key key() const;
|
||||||
|
|
||||||
virtual Section section() const = 0;
|
virtual Section section() const = 0;
|
||||||
|
|
@ -177,6 +187,9 @@ private:
|
||||||
const PeerId _migratedPeerId = 0;
|
const PeerId _migratedPeerId = 0;
|
||||||
//Data::Feed * const _feed = nullptr; // #feed
|
//Data::Feed * const _feed = nullptr; // #feed
|
||||||
UserData * const _settingsSelf = nullptr;
|
UserData * const _settingsSelf = nullptr;
|
||||||
|
PollData * const _poll = nullptr;
|
||||||
|
const FullMsgId _pollContextId;
|
||||||
|
|
||||||
int _scrollTop = 0;
|
int _scrollTop = 0;
|
||||||
QString _searchFieldQuery;
|
QString _searchFieldQuery;
|
||||||
bool _searchEnabledByContent = false;
|
bool _searchEnabledByContent = false;
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_media_types.h"
|
||||||
|
#include "history/history_item.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
|
|
||||||
|
|
@ -46,6 +48,10 @@ Key::Key(not_null<PeerData*> peer) : _value(peer) {
|
||||||
Key::Key(Settings::Tag settings) : _value(settings) {
|
Key::Key(Settings::Tag settings) : _value(settings) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Key::Key(not_null<PollData*> poll, FullMsgId contextId)
|
||||||
|
: _value(PollKey{ poll, contextId }) {
|
||||||
|
}
|
||||||
|
|
||||||
PeerData *Key::peer() const {
|
PeerData *Key::peer() const {
|
||||||
if (const auto peer = base::get_if<not_null<PeerData*>>(&_value)) {
|
if (const auto peer = base::get_if<not_null<PeerData*>>(&_value)) {
|
||||||
return *peer;
|
return *peer;
|
||||||
|
|
@ -67,6 +73,20 @@ UserData *Key::settingsSelf() const {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PollData *Key::poll() const {
|
||||||
|
if (const auto data = base::get_if<PollKey>(&_value)) {
|
||||||
|
return data->poll;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
FullMsgId Key::pollContextId() const {
|
||||||
|
if (const auto data = base::get_if<PollKey>(&_value)) {
|
||||||
|
return data->contextId;
|
||||||
|
}
|
||||||
|
return FullMsgId();
|
||||||
|
}
|
||||||
|
|
||||||
rpl::producer<SparseIdsMergedSlice> AbstractController::mediaSource(
|
rpl::producer<SparseIdsMergedSlice> AbstractController::mediaSource(
|
||||||
SparseIdsMergedSlice::UniversalMsgId aroundId,
|
SparseIdsMergedSlice::UniversalMsgId aroundId,
|
||||||
int limitBefore,
|
int limitBefore,
|
||||||
|
|
@ -106,6 +126,15 @@ PeerId AbstractController::migratedPeerId() const {
|
||||||
return PeerId(0);
|
return PeerId(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PollData *AbstractController::poll() const {
|
||||||
|
if (const auto item = session().data().message(pollContextId())) {
|
||||||
|
if (const auto media = item->media()) {
|
||||||
|
return media->poll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void AbstractController::showSection(
|
void AbstractController::showSection(
|
||||||
Window::SectionMemento &&memento,
|
Window::SectionMemento &&memento,
|
||||||
const Window::SectionShow ¶ms) {
|
const Window::SectionShow ¶ms) {
|
||||||
|
|
|
||||||
|
|
@ -33,16 +33,24 @@ public:
|
||||||
Key(not_null<PeerData*> peer);
|
Key(not_null<PeerData*> peer);
|
||||||
//Key(not_null<Data::Feed*> feed); // #feed
|
//Key(not_null<Data::Feed*> feed); // #feed
|
||||||
Key(Settings::Tag settings);
|
Key(Settings::Tag settings);
|
||||||
|
Key(not_null<PollData*> poll, FullMsgId contextId);
|
||||||
|
|
||||||
PeerData *peer() const;
|
PeerData *peer() const;
|
||||||
//Data::Feed *feed() const; // #feed
|
//Data::Feed *feed() const; // #feed
|
||||||
UserData *settingsSelf() const;
|
UserData *settingsSelf() const;
|
||||||
|
PollData *poll() const;
|
||||||
|
FullMsgId pollContextId() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct PollKey {
|
||||||
|
not_null<PollData*> poll;
|
||||||
|
FullMsgId contextId;
|
||||||
|
};
|
||||||
base::variant<
|
base::variant<
|
||||||
not_null<PeerData*>,
|
not_null<PeerData*>,
|
||||||
//not_null<Data::Feed*>, // #feed
|
//not_null<Data::Feed*>, // #feed
|
||||||
Settings::Tag> _value;
|
Settings::Tag,
|
||||||
|
PollKey> _value;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -60,6 +68,7 @@ public:
|
||||||
Members,
|
Members,
|
||||||
//Channels, // #feed
|
//Channels, // #feed
|
||||||
Settings,
|
Settings,
|
||||||
|
PollResults,
|
||||||
};
|
};
|
||||||
using SettingsType = ::Settings::Type;
|
using SettingsType = ::Settings::Type;
|
||||||
using MediaType = Storage::SharedMediaType;
|
using MediaType = Storage::SharedMediaType;
|
||||||
|
|
@ -113,6 +122,10 @@ public:
|
||||||
UserData *settingsSelf() const {
|
UserData *settingsSelf() const {
|
||||||
return key().settingsSelf();
|
return key().settingsSelf();
|
||||||
}
|
}
|
||||||
|
PollData *poll() const;
|
||||||
|
FullMsgId pollContextId() const {
|
||||||
|
return key().pollContextId();
|
||||||
|
}
|
||||||
|
|
||||||
virtual void setSearchEnabledByContent(bool enabled) {
|
virtual void setSearchEnabledByContent(bool enabled) {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "info/common_groups/info_common_groups_widget.h"
|
#include "info/common_groups/info_common_groups_widget.h"
|
||||||
//#include "info/feed/info_feed_profile_widget.h" // #feed
|
//#include "info/feed/info_feed_profile_widget.h" // #feed
|
||||||
#include "info/settings/info_settings_widget.h"
|
#include "info/settings/info_settings_widget.h"
|
||||||
|
#include "info/polls/info_polls_results_widget.h"
|
||||||
#include "info/info_section_widget.h"
|
#include "info/info_section_widget.h"
|
||||||
#include "info/info_layer_widget.h"
|
#include "info/info_layer_widget.h"
|
||||||
#include "info/info_controller.h"
|
#include "info/info_controller.h"
|
||||||
|
|
@ -42,6 +43,10 @@ Memento::Memento(Settings::Tag settings, Section section)
|
||||||
: Memento(DefaultStack(settings, section)) {
|
: Memento(DefaultStack(settings, section)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Memento::Memento(not_null<PollData*> poll, FullMsgId contextId)
|
||||||
|
: Memento(DefaultStack(poll, contextId)) {
|
||||||
|
}
|
||||||
|
|
||||||
Memento::Memento(std::vector<std::unique_ptr<ContentMemento>> stack)
|
Memento::Memento(std::vector<std::unique_ptr<ContentMemento>> stack)
|
||||||
: _stack(std::move(stack)) {
|
: _stack(std::move(stack)) {
|
||||||
}
|
}
|
||||||
|
|
@ -72,6 +77,14 @@ std::vector<std::unique_ptr<ContentMemento>> Memento::DefaultStack(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<ContentMemento>> Memento::DefaultStack(
|
||||||
|
not_null<PollData*> poll,
|
||||||
|
FullMsgId contextId) {
|
||||||
|
auto result = std::vector<std::unique_ptr<ContentMemento>>();
|
||||||
|
result.push_back(std::make_unique<Polls::Memento>(poll, contextId));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
Section Memento::DefaultSection(not_null<PeerData*> peer) {
|
Section Memento::DefaultSection(not_null<PeerData*> peer) {
|
||||||
if (peer->isSelf()) {
|
if (peer->isSelf()) {
|
||||||
return Section(Section::MediaType::Photo);
|
return Section(Section::MediaType::Photo);
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ public:
|
||||||
Memento(PeerId peerId, Section section);
|
Memento(PeerId peerId, Section section);
|
||||||
//Memento(not_null<Data::Feed*> feed, Section section); // #feed
|
//Memento(not_null<Data::Feed*> feed, Section section); // #feed
|
||||||
Memento(Settings::Tag settings, Section section);
|
Memento(Settings::Tag settings, Section section);
|
||||||
|
Memento(not_null<PollData*> poll, FullMsgId contextId);
|
||||||
explicit Memento(std::vector<std::unique_ptr<ContentMemento>> stack);
|
explicit Memento(std::vector<std::unique_ptr<ContentMemento>> stack);
|
||||||
|
|
||||||
object_ptr<Window::SectionWidget> createWidget(
|
object_ptr<Window::SectionWidget> createWidget(
|
||||||
|
|
@ -76,10 +77,13 @@ private:
|
||||||
static std::vector<std::unique_ptr<ContentMemento>> DefaultStack(
|
static std::vector<std::unique_ptr<ContentMemento>> DefaultStack(
|
||||||
Settings::Tag settings,
|
Settings::Tag settings,
|
||||||
Section section);
|
Section section);
|
||||||
|
static std::vector<std::unique_ptr<ContentMemento>> DefaultStack(
|
||||||
|
not_null<PollData*> poll,
|
||||||
|
FullMsgId contextId);
|
||||||
|
|
||||||
//static std::unique_ptr<ContentMemento> DefaultContent( // #feed
|
//static std::unique_ptr<ContentMemento> DefaultContent( // #feed
|
||||||
// not_null<Data::Feed*> feed,
|
// not_null<Data::Feed*> feed,
|
||||||
// Section section);
|
// Section section);
|
||||||
|
|
||||||
static std::unique_ptr<ContentMemento> DefaultContent(
|
static std::unique_ptr<ContentMemento> DefaultContent(
|
||||||
PeerId peerId,
|
PeerId peerId,
|
||||||
Section section);
|
Section section);
|
||||||
|
|
|
||||||
|
|
@ -635,6 +635,11 @@ rpl::producer<QString> TitleValue(
|
||||||
return tr::ktg_settings_kotato();
|
return tr::ktg_settings_kotato();
|
||||||
}
|
}
|
||||||
Unexpected("Bad settings type in Info::TitleValue()");
|
Unexpected("Bad settings type in Info::TitleValue()");
|
||||||
|
|
||||||
|
case Section::Type::PollResults:
|
||||||
|
return key.poll()->quiz()
|
||||||
|
? tr::lng_polls_quiz_results_title()
|
||||||
|
: tr::lng_polls_poll_results_title();
|
||||||
}
|
}
|
||||||
Unexpected("Bad section type in Info::TitleValue()");
|
Unexpected("Bad section type in Info::TitleValue()");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -217,7 +217,7 @@ Dialogs::RowDescriptor WrapWidget::activeChat() const {
|
||||||
return Dialogs::RowDescriptor(peer->owner().history(peer), FullMsgId());
|
return Dialogs::RowDescriptor(peer->owner().history(peer), FullMsgId());
|
||||||
//} else if (const auto feed = key().feed()) { // #feed
|
//} else if (const auto feed = key().feed()) { // #feed
|
||||||
// return Dialogs::RowDescriptor(feed, FullMsgId());
|
// return Dialogs::RowDescriptor(feed, FullMsgId());
|
||||||
} else if (key().settingsSelf()) {
|
} else if (key().settingsSelf() || key().poll()) {
|
||||||
return Dialogs::RowDescriptor();
|
return Dialogs::RowDescriptor();
|
||||||
}
|
}
|
||||||
Unexpected("Owner in WrapWidget::activeChat().");
|
Unexpected("Owner in WrapWidget::activeChat().");
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,725 @@
|
||||||
|
/*
|
||||||
|
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 "info/polls/info_polls_results_inner_widget.h"
|
||||||
|
|
||||||
|
#include "info/polls/info_polls_results_widget.h"
|
||||||
|
#include "info/info_controller.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "data/data_poll.h"
|
||||||
|
#include "data/data_peer.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "ui/widgets/labels.h"
|
||||||
|
#include "ui/widgets/buttons.h"
|
||||||
|
#include "ui/wrap/vertical_layout.h"
|
||||||
|
#include "ui/wrap/padding_wrap.h"
|
||||||
|
#include "ui/wrap/slide_wrap.h"
|
||||||
|
#include "ui/text/text_utilities.h"
|
||||||
|
#include "boxes/peer_list_box.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "history/history.h"
|
||||||
|
#include "history/history_item.h"
|
||||||
|
#include "apiwrap.h"
|
||||||
|
#include "styles/style_layers.h"
|
||||||
|
#include "styles/style_boxes.h"
|
||||||
|
#include "styles/style_info.h"
|
||||||
|
|
||||||
|
namespace Info {
|
||||||
|
namespace Polls {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kFirstPage = 15;
|
||||||
|
constexpr auto kPerPage = 50;
|
||||||
|
constexpr auto kLeavePreloaded = 5;
|
||||||
|
|
||||||
|
class PeerListDummy final : public Ui::RpWidget {
|
||||||
|
public:
|
||||||
|
PeerListDummy(QWidget *parent, int count, const style::PeerList &st);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const style::PeerList &_st;
|
||||||
|
int _count = 0;
|
||||||
|
|
||||||
|
std::vector<Ui::Animations::Simple> _animations;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class ListDelegate final : public PeerListContentDelegate {
|
||||||
|
public:
|
||||||
|
void peerListSetTitle(rpl::producer<QString> title) override;
|
||||||
|
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
|
||||||
|
bool peerListIsRowSelected(not_null<PeerData*> peer) override;
|
||||||
|
int peerListSelectedRowsCount() override;
|
||||||
|
std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override;
|
||||||
|
void peerListScrollToTop() override;
|
||||||
|
void peerListAddSelectedRowInBunch(
|
||||||
|
not_null<PeerData*> peer) override;
|
||||||
|
void peerListFinishSelectedRowsBunch() override;
|
||||||
|
void peerListSetDescription(
|
||||||
|
object_ptr<Ui::FlatLabel> description) override;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
PeerListDummy::PeerListDummy(
|
||||||
|
QWidget *parent,
|
||||||
|
int count,
|
||||||
|
const style::PeerList &st)
|
||||||
|
: _st(st)
|
||||||
|
, _count(count) {
|
||||||
|
resize(width(), _count * _st.item.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PeerListDummy::paintEvent(QPaintEvent *e) {
|
||||||
|
QPainter p(this);
|
||||||
|
|
||||||
|
PainterHighQualityEnabler hq(p);
|
||||||
|
|
||||||
|
const auto fill = e->rect();
|
||||||
|
const auto bottom = fill.top() + fill.height();
|
||||||
|
const auto from = floorclamp(fill.top(), _st.item.height, 0, _count);
|
||||||
|
const auto till = ceilclamp(bottom, _st.item.height, 0, _count);
|
||||||
|
p.translate(0, _st.item.height * from);
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
for (auto i = from; i != till; ++i) {
|
||||||
|
p.setBrush(st::windowBgOver);
|
||||||
|
p.drawEllipse(
|
||||||
|
_st.item.photoPosition.x(),
|
||||||
|
_st.item.photoPosition.y(),
|
||||||
|
_st.item.photoSize,
|
||||||
|
_st.item.photoSize);
|
||||||
|
|
||||||
|
const auto small = int(1.5 * _st.item.photoSize);
|
||||||
|
const auto large = 2 * small;
|
||||||
|
const auto second = (i % 2) ? large : small;
|
||||||
|
const auto height = _st.item.nameStyle.font->height / 2;
|
||||||
|
const auto radius = height / 2;
|
||||||
|
const auto left = _st.item.namePosition.x();
|
||||||
|
const auto top = _st.item.namePosition.y()
|
||||||
|
+ (_st.item.nameStyle.font->height - height) / 2;
|
||||||
|
const auto skip = _st.item.namePosition.x()
|
||||||
|
- _st.item.photoPosition.x()
|
||||||
|
- _st.item.photoSize;
|
||||||
|
const auto next = left + small + skip;
|
||||||
|
p.drawRoundedRect(left, top, small, height, radius, radius);
|
||||||
|
p.drawRoundedRect(next, top, second, height, radius, radius);
|
||||||
|
|
||||||
|
p.translate(0, _st.item.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListDelegate::peerListSetTitle(rpl::producer<QString> title) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListDelegate::peerListSetAdditionalTitle(rpl::producer<QString> title) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ListDelegate::peerListIsRowSelected(not_null<PeerData*> peer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ListDelegate::peerListSelectedRowsCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ListDelegate::peerListCollectSelectedRows()
|
||||||
|
-> std::vector<not_null<PeerData*>> {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListDelegate::peerListScrollToTop() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListDelegate::peerListAddSelectedRowInBunch(not_null<PeerData*> peer) {
|
||||||
|
Unexpected("Item selection in Info::Profile::Members.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListDelegate::peerListFinishSelectedRowsBunch() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListDelegate::peerListSetDescription(
|
||||||
|
object_ptr<Ui::FlatLabel> description) {
|
||||||
|
description.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class ListController final : public PeerListController {
|
||||||
|
public:
|
||||||
|
ListController(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
not_null<PollData*> poll,
|
||||||
|
FullMsgId context,
|
||||||
|
QByteArray option);
|
||||||
|
|
||||||
|
Main::Session &session() const override;
|
||||||
|
void prepare() override;
|
||||||
|
void rowClicked(not_null<PeerListRow*> row) override;
|
||||||
|
void loadMoreRows() override;
|
||||||
|
|
||||||
|
void allowLoadMore();
|
||||||
|
void collapse();
|
||||||
|
|
||||||
|
[[nodiscard]] auto showPeerInfoRequests() const
|
||||||
|
-> rpl::producer<not_null<PeerData*>>;
|
||||||
|
[[nodiscard]] rpl::producer<int> scrollToRequests() const;
|
||||||
|
[[nodiscard]] rpl::producer<int> count() const;
|
||||||
|
[[nodiscard]] rpl::producer<int> fullCount() const;
|
||||||
|
[[nodiscard]] rpl::producer<int> loadMoreCount() const;
|
||||||
|
|
||||||
|
std::unique_ptr<PeerListState> saveState() const override;
|
||||||
|
void restoreState(std::unique_ptr<PeerListState> state) override;
|
||||||
|
|
||||||
|
std::unique_ptr<PeerListRow> createRestoredRow(
|
||||||
|
not_null<PeerData*> peer) override;
|
||||||
|
|
||||||
|
void scrollTo(int y);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct SavedState : SavedStateBase {
|
||||||
|
QString offset;
|
||||||
|
QString loadForOffset;
|
||||||
|
int leftToLoad = 0;
|
||||||
|
int fullCount = 0;
|
||||||
|
std::vector<not_null<UserData*>> preloaded;
|
||||||
|
bool wasLoading = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool appendRow(not_null<UserData*> user);
|
||||||
|
std::unique_ptr<PeerListRow> createRow(not_null<UserData*> user) const;
|
||||||
|
void addPreloaded();
|
||||||
|
bool addPreloadedPage();
|
||||||
|
void preloadedAdded();
|
||||||
|
|
||||||
|
const not_null<Main::Session*> _session;
|
||||||
|
const not_null<PollData*> _poll;
|
||||||
|
const FullMsgId _context;
|
||||||
|
const QByteArray _option;
|
||||||
|
|
||||||
|
MTP::Sender _api;
|
||||||
|
|
||||||
|
QString _offset;
|
||||||
|
mtpRequestId _loadRequestId = 0;
|
||||||
|
QString _loadForOffset;
|
||||||
|
std::vector<not_null<UserData*>> _preloaded;
|
||||||
|
rpl::variable<int> _count = 0;
|
||||||
|
rpl::variable<int> _fullCount;
|
||||||
|
rpl::variable<int> _leftToLoad;
|
||||||
|
|
||||||
|
rpl::event_stream<not_null<PeerData*>> _showPeerInfoRequests;
|
||||||
|
rpl::event_stream<int> _scrollToRequests;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
ListController::ListController(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
not_null<PollData*> poll,
|
||||||
|
FullMsgId context,
|
||||||
|
QByteArray option)
|
||||||
|
: _session(session)
|
||||||
|
, _poll(poll)
|
||||||
|
, _context(context)
|
||||||
|
, _option(option)
|
||||||
|
, _api(_session->api().instance()) {
|
||||||
|
const auto i = ranges::find(poll->answers, option, &PollAnswer::option);
|
||||||
|
Assert(i != poll->answers.end());
|
||||||
|
_fullCount = i->votes;
|
||||||
|
_leftToLoad = i->votes;
|
||||||
|
}
|
||||||
|
|
||||||
|
Main::Session &ListController::session() const {
|
||||||
|
return *_session;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListController::prepare() {
|
||||||
|
delegate()->peerListRefreshRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListController::loadMoreRows() {
|
||||||
|
if (_loadRequestId
|
||||||
|
|| !_leftToLoad.current()
|
||||||
|
|| (!_offset.isEmpty() && _loadForOffset != _offset)
|
||||||
|
|| !_preloaded.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto item = session().data().message(_context);
|
||||||
|
if (!item || !IsServerMsgId(item->id)) {
|
||||||
|
_leftToLoad = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using Flag = MTPmessages_GetPollVotes::Flag;
|
||||||
|
const auto flags = Flag::f_option
|
||||||
|
| (_offset.isEmpty() ? Flag(0) : Flag::f_offset);
|
||||||
|
const auto limit = _offset.isEmpty() ? kFirstPage : kPerPage;
|
||||||
|
_loadRequestId = _api.request(MTPmessages_GetPollVotes(
|
||||||
|
MTP_flags(flags),
|
||||||
|
item->history()->peer->input,
|
||||||
|
MTP_int(item->id),
|
||||||
|
MTP_bytes(_option),
|
||||||
|
MTP_string(_offset),
|
||||||
|
MTP_int(limit)
|
||||||
|
)).done([=](const MTPmessages_VotesList &result) {
|
||||||
|
const auto count = result.match([&](
|
||||||
|
const MTPDmessages_votesList &data) {
|
||||||
|
_offset = data.vnext_offset().value_or_empty();
|
||||||
|
auto &owner = session().data();
|
||||||
|
owner.processUsers(data.vusers());
|
||||||
|
auto add = limit - kLeavePreloaded;
|
||||||
|
for (const auto &vote : data.vvotes().v) {
|
||||||
|
vote.match([&](const auto &data) {
|
||||||
|
const auto user = owner.user(data.vuser_id().v);
|
||||||
|
if (user->loadedStatus != PeerData::NotLoaded) {
|
||||||
|
if (add) {
|
||||||
|
appendRow(user);
|
||||||
|
--add;
|
||||||
|
} else {
|
||||||
|
_preloaded.push_back(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return data.vcount().v;
|
||||||
|
});
|
||||||
|
if (_offset.isEmpty()) {
|
||||||
|
addPreloaded();
|
||||||
|
_fullCount = delegate()->peerListFullRowsCount();
|
||||||
|
_leftToLoad = 0;
|
||||||
|
} else {
|
||||||
|
_count = delegate()->peerListFullRowsCount();
|
||||||
|
_fullCount = count;
|
||||||
|
_leftToLoad = count - delegate()->peerListFullRowsCount();
|
||||||
|
delegate()->peerListRefreshRows();
|
||||||
|
}
|
||||||
|
_loadRequestId = 0;
|
||||||
|
}).fail([=](const RPCError &error) {
|
||||||
|
_loadRequestId = 0;
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListController::allowLoadMore() {
|
||||||
|
if (!addPreloadedPage()) {
|
||||||
|
_loadForOffset = _offset;
|
||||||
|
addPreloaded();
|
||||||
|
loadMoreRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListController::collapse() {
|
||||||
|
const auto count = delegate()->peerListFullRowsCount();
|
||||||
|
if (count <= kFirstPage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto remove = count - (kFirstPage - kLeavePreloaded);
|
||||||
|
ranges::action::reverse(_preloaded);
|
||||||
|
_preloaded.reserve(_preloaded.size() + remove);
|
||||||
|
for (auto i = 0; i != remove; ++i) {
|
||||||
|
const auto row = delegate()->peerListRowAt(count - i - 1);
|
||||||
|
_preloaded.push_back(row->peer()->asUser());
|
||||||
|
delegate()->peerListRemoveRow(row);
|
||||||
|
}
|
||||||
|
ranges::action::reverse(_preloaded);
|
||||||
|
|
||||||
|
delegate()->peerListRefreshRows();
|
||||||
|
const auto now = count - remove;
|
||||||
|
_count = now;
|
||||||
|
_leftToLoad = _fullCount.current() - now;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListController::addPreloaded() {
|
||||||
|
for (const auto user : base::take(_preloaded)) {
|
||||||
|
appendRow(user);
|
||||||
|
}
|
||||||
|
preloadedAdded();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ListController::addPreloadedPage() {
|
||||||
|
if (_preloaded.size() < kPerPage + kLeavePreloaded) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto from = begin(_preloaded);
|
||||||
|
const auto till = from + kPerPage;
|
||||||
|
for (auto i = from; i != till; ++i) {
|
||||||
|
appendRow(*i);
|
||||||
|
}
|
||||||
|
_preloaded.erase(from, till);
|
||||||
|
preloadedAdded();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListController::preloadedAdded() {
|
||||||
|
_count = delegate()->peerListFullRowsCount();
|
||||||
|
_leftToLoad = _fullCount.current() - _count.current();
|
||||||
|
delegate()->peerListRefreshRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ListController::showPeerInfoRequests() const
|
||||||
|
-> rpl::producer<not_null<PeerData*>> {
|
||||||
|
return _showPeerInfoRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<int> ListController::scrollToRequests() const {
|
||||||
|
return _scrollToRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<int> ListController::count() const {
|
||||||
|
return _count.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<int> ListController::fullCount() const {
|
||||||
|
return _fullCount.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<int> ListController::loadMoreCount() const {
|
||||||
|
const auto initial = (_fullCount.current() <= kFirstPage)
|
||||||
|
? _fullCount.current()
|
||||||
|
: (kFirstPage - kLeavePreloaded);
|
||||||
|
return rpl::combine(
|
||||||
|
_count.value(),
|
||||||
|
_leftToLoad.value()
|
||||||
|
) | rpl::map([=](int count, int leftToLoad) {
|
||||||
|
return (count > 0) ? leftToLoad : (leftToLoad - initial);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ListController::saveState() const -> std::unique_ptr<PeerListState> {
|
||||||
|
auto result = PeerListController::saveState();
|
||||||
|
|
||||||
|
auto my = std::make_unique<SavedState>();
|
||||||
|
my->offset = _offset;
|
||||||
|
my->fullCount = _fullCount.current();
|
||||||
|
my->leftToLoad = _leftToLoad.current();
|
||||||
|
my->preloaded = _preloaded;
|
||||||
|
my->wasLoading = (_loadRequestId != 0);
|
||||||
|
my->loadForOffset = _loadForOffset;
|
||||||
|
result->controllerState = std::move(my);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListController::restoreState(std::unique_ptr<PeerListState> state) {
|
||||||
|
auto typeErasedState = state
|
||||||
|
? state->controllerState.get()
|
||||||
|
: nullptr;
|
||||||
|
if (const auto my = dynamic_cast<SavedState*>(typeErasedState)) {
|
||||||
|
if (const auto requestId = base::take(_loadRequestId)) {
|
||||||
|
_api.request(requestId).cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
_offset = my->offset;
|
||||||
|
_loadForOffset = my->loadForOffset;
|
||||||
|
_preloaded = std::move(my->preloaded);
|
||||||
|
_count = int(state->list.size());
|
||||||
|
_fullCount = my->fullCount;
|
||||||
|
_leftToLoad = my->leftToLoad;
|
||||||
|
if (my->wasLoading) {
|
||||||
|
loadMoreRows();
|
||||||
|
}
|
||||||
|
PeerListController::restoreState(std::move(state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<PeerListRow> ListController::createRestoredRow(
|
||||||
|
not_null<PeerData*> peer) {
|
||||||
|
if (const auto user = peer->asUser()) {
|
||||||
|
return createRow(user);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListController::rowClicked(not_null<PeerListRow*> row) {
|
||||||
|
_showPeerInfoRequests.fire(row->peer());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ListController::appendRow(not_null<UserData*> user) {
|
||||||
|
if (delegate()->peerListFindRow(user->id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
delegate()->peerListAppendRow(createRow(user));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<PeerListRow> ListController::createRow(
|
||||||
|
not_null<UserData*> user) const {
|
||||||
|
auto row = std::make_unique<PeerListRow>(user);
|
||||||
|
row->setCustomStatus(QString());
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListController::scrollTo(int y) {
|
||||||
|
_scrollToRequests.fire_copy(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
ListController *CreateAnswerRows(
|
||||||
|
not_null<Ui::VerticalLayout*> container,
|
||||||
|
rpl::producer<int> visibleTop,
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
not_null<PollData*> poll,
|
||||||
|
FullMsgId context,
|
||||||
|
const PollAnswer &answer) {
|
||||||
|
using namespace rpl::mappers;
|
||||||
|
|
||||||
|
if (!answer.votes) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto delegate = container->lifetime().make_state<ListDelegate>();
|
||||||
|
const auto controller = container->lifetime().make_state<ListController>(
|
||||||
|
session,
|
||||||
|
poll,
|
||||||
|
context,
|
||||||
|
answer.option);
|
||||||
|
|
||||||
|
const auto percent = answer.votes * 100 / poll->totalVoters;
|
||||||
|
const auto phrase = poll->quiz()
|
||||||
|
? tr::lng_polls_answers_count
|
||||||
|
: tr::lng_polls_votes_count;
|
||||||
|
const auto sampleText = phrase(
|
||||||
|
tr::now,
|
||||||
|
lt_count_decimal,
|
||||||
|
answer.votes);
|
||||||
|
const auto &font = st::boxDividerLabel.style.font;
|
||||||
|
const auto sampleWidth = font->width(sampleText);
|
||||||
|
const auto rightSkip = sampleWidth + font->spacew * 4;
|
||||||
|
const auto headerWrap = container->add(
|
||||||
|
object_ptr<Ui::RpWidget>(
|
||||||
|
container));
|
||||||
|
|
||||||
|
container->add(object_ptr<Ui::FixedHeightWidget>(
|
||||||
|
container,
|
||||||
|
st::boxLittleSkip));
|
||||||
|
|
||||||
|
const auto content = container->add(object_ptr<PeerListContent>(
|
||||||
|
container,
|
||||||
|
controller,
|
||||||
|
st::infoCommonGroupsList));
|
||||||
|
delegate->setContent(content);
|
||||||
|
controller->setDelegate(delegate);
|
||||||
|
|
||||||
|
const auto count = (answer.votes <= kFirstPage)
|
||||||
|
? answer.votes
|
||||||
|
: (kFirstPage - kLeavePreloaded);
|
||||||
|
const auto placeholder = container->add(object_ptr<PeerListDummy>(
|
||||||
|
container,
|
||||||
|
count,
|
||||||
|
st::infoCommonGroupsList));
|
||||||
|
|
||||||
|
controller->count(
|
||||||
|
) | rpl::filter(_1 > 0) | rpl::start_with_next([=] {
|
||||||
|
delete placeholder;
|
||||||
|
}, placeholder->lifetime());
|
||||||
|
|
||||||
|
const auto header = Ui::CreateChild<Ui::DividerLabel>(
|
||||||
|
container.get(),
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
container,
|
||||||
|
(answer.text
|
||||||
|
+ QString::fromUtf8(" \xe2\x80\x94 ")
|
||||||
|
+ QString::number(percent)
|
||||||
|
+ "%"),
|
||||||
|
st::boxDividerLabel),
|
||||||
|
style::margins(
|
||||||
|
st::pollResultsHeaderPadding.left(),
|
||||||
|
st::pollResultsHeaderPadding.top(),
|
||||||
|
st::pollResultsHeaderPadding.right() + rightSkip,
|
||||||
|
st::pollResultsHeaderPadding.bottom()));
|
||||||
|
|
||||||
|
const auto votes = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
|
header,
|
||||||
|
phrase(
|
||||||
|
lt_count_decimal,
|
||||||
|
controller->fullCount() | rpl::map(_1 + 0.)),
|
||||||
|
st::pollResultsVotesCount);
|
||||||
|
const auto collapse = Ui::CreateChild<Ui::LinkButton>(
|
||||||
|
header,
|
||||||
|
tr::lng_polls_votes_collapse(tr::now),
|
||||||
|
st::defaultLinkButton);
|
||||||
|
collapse->setClickedCallback([=] {
|
||||||
|
controller->scrollTo(headerWrap->y());
|
||||||
|
controller->collapse();
|
||||||
|
});
|
||||||
|
rpl::combine(
|
||||||
|
controller->fullCount(),
|
||||||
|
controller->count()
|
||||||
|
) | rpl::start_with_next([=](int fullCount, int count) {
|
||||||
|
const auto many = (fullCount > kFirstPage)
|
||||||
|
&& (count > kFirstPage - kLeavePreloaded);
|
||||||
|
collapse->setVisible(many);
|
||||||
|
votes->setVisible(!many);
|
||||||
|
}, collapse->lifetime());
|
||||||
|
|
||||||
|
headerWrap->widthValue(
|
||||||
|
) | rpl::start_with_next([=](int width) {
|
||||||
|
header->resizeToWidth(width);
|
||||||
|
votes->moveToRight(
|
||||||
|
st::pollResultsHeaderPadding.right(),
|
||||||
|
st::pollResultsHeaderPadding.top(),
|
||||||
|
width);
|
||||||
|
collapse->moveToRight(
|
||||||
|
st::pollResultsHeaderPadding.right(),
|
||||||
|
st::pollResultsHeaderPadding.top(),
|
||||||
|
width);
|
||||||
|
}, header->lifetime());
|
||||||
|
|
||||||
|
header->heightValue(
|
||||||
|
) | rpl::start_with_next([=](int height) {
|
||||||
|
headerWrap->resize(headerWrap->width(), height);
|
||||||
|
}, header->lifetime());
|
||||||
|
|
||||||
|
const auto more = container->add(
|
||||||
|
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
||||||
|
container,
|
||||||
|
object_ptr<Ui::SettingsButton>(
|
||||||
|
container,
|
||||||
|
tr::lng_polls_show_more(
|
||||||
|
lt_count_decimal,
|
||||||
|
controller->loadMoreCount() | rpl::map(_1 + 0.),
|
||||||
|
Ui::Text::Upper),
|
||||||
|
st::pollResultsShowMore)));
|
||||||
|
more->entity()->setClickedCallback([=] {
|
||||||
|
controller->allowLoadMore();
|
||||||
|
});
|
||||||
|
controller->loadMoreCount(
|
||||||
|
) | rpl::map(_1 > 0) | rpl::start_with_next([=](bool visible) {
|
||||||
|
more->toggle(visible, anim::type::instant);
|
||||||
|
}, more->lifetime());
|
||||||
|
|
||||||
|
container->add(object_ptr<Ui::FixedHeightWidget>(
|
||||||
|
container,
|
||||||
|
st::boxLittleSkip));
|
||||||
|
|
||||||
|
rpl::combine(
|
||||||
|
std::move(visibleTop),
|
||||||
|
headerWrap->geometryValue(),
|
||||||
|
more->topValue()
|
||||||
|
) | rpl::filter([=](int, QRect headerRect, int moreTop) {
|
||||||
|
return moreTop >= headerRect.y() + headerRect.height();
|
||||||
|
}) | rpl::start_with_next([=](
|
||||||
|
int visibleTop,
|
||||||
|
QRect headerRect,
|
||||||
|
int moreTop) {
|
||||||
|
const auto skip = st::pollResultsHeaderPadding.top()
|
||||||
|
- st::pollResultsHeaderPadding.bottom();
|
||||||
|
const auto top = std::clamp(
|
||||||
|
visibleTop - skip,
|
||||||
|
headerRect.y(),
|
||||||
|
moreTop - headerRect.height());
|
||||||
|
header->move(0, top);
|
||||||
|
}, header->lifetime());
|
||||||
|
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
InnerWidget::InnerWidget(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Controller*> controller,
|
||||||
|
not_null<PollData*> poll,
|
||||||
|
FullMsgId contextId)
|
||||||
|
: RpWidget(parent)
|
||||||
|
, _controller(controller)
|
||||||
|
, _poll(poll)
|
||||||
|
, _contextId(contextId)
|
||||||
|
, _content(this) {
|
||||||
|
setupContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InnerWidget::visibleTopBottomUpdated(
|
||||||
|
int visibleTop,
|
||||||
|
int visibleBottom) {
|
||||||
|
setChildVisibleTopBottom(_content, visibleTop, visibleBottom);
|
||||||
|
_visibleTop = visibleTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InnerWidget::saveState(not_null<Memento*> memento) {
|
||||||
|
auto states = base::flat_map<
|
||||||
|
QByteArray,
|
||||||
|
std::unique_ptr<PeerListState>>();
|
||||||
|
for (const auto &[option, controller] : _sections) {
|
||||||
|
states[option] = controller->saveState();
|
||||||
|
}
|
||||||
|
memento->setListStates(std::move(states));
|
||||||
|
}
|
||||||
|
|
||||||
|
void InnerWidget::restoreState(not_null<Memento*> memento) {
|
||||||
|
auto states = memento->listStates();
|
||||||
|
for (const auto &[option, controller] : _sections) {
|
||||||
|
const auto i = states.find(option);
|
||||||
|
if (i != end(states)) {
|
||||||
|
controller->restoreState(std::move(i->second));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int InnerWidget::desiredHeight() const {
|
||||||
|
auto desired = 0;
|
||||||
|
//auto count = qMax(_user->commonChatsCount(), 1);
|
||||||
|
//desired += qMax(count, _list->fullRowsCount())
|
||||||
|
// * st::infoCommonGroupsList.item.height;
|
||||||
|
return qMax(height(), desired);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InnerWidget::setupContent() {
|
||||||
|
const auto quiz = _poll->quiz();
|
||||||
|
_content->add(
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
_content,
|
||||||
|
_poll->question,
|
||||||
|
st::pollResultsQuestion),
|
||||||
|
style::margins{
|
||||||
|
st::boxRowPadding.left(),
|
||||||
|
0,
|
||||||
|
st::boxRowPadding.right(),
|
||||||
|
st::boxMediumSkip });
|
||||||
|
for (const auto &answer : _poll->answers) {
|
||||||
|
const auto session = &_controller->parentController()->session();
|
||||||
|
const auto controller = CreateAnswerRows(
|
||||||
|
_content,
|
||||||
|
_visibleTop.value(),
|
||||||
|
session,
|
||||||
|
_poll,
|
||||||
|
_contextId,
|
||||||
|
answer);
|
||||||
|
if (!controller) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
controller->showPeerInfoRequests(
|
||||||
|
) | rpl::start_to_stream(
|
||||||
|
_showPeerInfoRequests,
|
||||||
|
lifetime());
|
||||||
|
controller->scrollToRequests(
|
||||||
|
) | rpl::start_with_next([=](int y) {
|
||||||
|
_scrollToRequests.fire({ y, -1 });
|
||||||
|
}, lifetime());
|
||||||
|
_sections.emplace(answer.option, controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
widthValue(
|
||||||
|
) | rpl::start_with_next([=](int newWidth) {
|
||||||
|
_content->resizeToWidth(newWidth);
|
||||||
|
}, _content->lifetime());
|
||||||
|
|
||||||
|
_content->heightValue(
|
||||||
|
) | rpl::start_with_next([=](int height) {
|
||||||
|
resize(width(), height);
|
||||||
|
}, _content->lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<Ui::ScrollToRequest> InnerWidget::scrollToRequests() const {
|
||||||
|
return _scrollToRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InnerWidget::showPeerInfoRequests() const
|
||||||
|
-> rpl::producer<not_null<PeerData*>> {
|
||||||
|
return _showPeerInfoRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Polls
|
||||||
|
} // namespace Info
|
||||||
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/rp_widget.h"
|
||||||
|
#include "ui/widgets/scroll_area.h"
|
||||||
|
#include "base/object_ptr.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class VerticalLayout;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Info {
|
||||||
|
|
||||||
|
class Controller;
|
||||||
|
|
||||||
|
namespace Polls {
|
||||||
|
|
||||||
|
class Memento;
|
||||||
|
class ListController;
|
||||||
|
|
||||||
|
class InnerWidget final : public Ui::RpWidget {
|
||||||
|
public:
|
||||||
|
InnerWidget(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Controller*> controller,
|
||||||
|
not_null<PollData*> poll,
|
||||||
|
FullMsgId contextId);
|
||||||
|
|
||||||
|
[[nodiscard]] not_null<PollData*> poll() const {
|
||||||
|
return _poll;
|
||||||
|
}
|
||||||
|
[[nodiscard]] FullMsgId contextId() const {
|
||||||
|
return _contextId;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto scrollToRequests() const
|
||||||
|
-> rpl::producer<Ui::ScrollToRequest>;
|
||||||
|
|
||||||
|
[[nodiscard]] auto showPeerInfoRequests() const
|
||||||
|
-> rpl::producer<not_null<PeerData*>>;
|
||||||
|
|
||||||
|
[[nodiscard]] int desiredHeight() const;
|
||||||
|
|
||||||
|
void saveState(not_null<Memento*> memento);
|
||||||
|
void restoreState(not_null<Memento*> memento);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void visibleTopBottomUpdated(
|
||||||
|
int visibleTop,
|
||||||
|
int visibleBottom) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupContent();
|
||||||
|
|
||||||
|
not_null<Controller*> _controller;
|
||||||
|
not_null<PollData*> _poll;
|
||||||
|
FullMsgId _contextId;
|
||||||
|
object_ptr<Ui::VerticalLayout> _content;
|
||||||
|
base::flat_map<QByteArray, not_null<ListController*>> _sections;
|
||||||
|
|
||||||
|
rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
|
||||||
|
rpl::event_stream<not_null<PeerData*>> _showPeerInfoRequests;
|
||||||
|
rpl::variable<int> _visibleTop = 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Polls
|
||||||
|
} // namespace Info
|
||||||
111
Telegram/SourceFiles/info/polls/info_polls_results_widget.cpp
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
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 "info/polls/info_polls_results_widget.h"
|
||||||
|
|
||||||
|
#include "info/polls/info_polls_results_inner_widget.h"
|
||||||
|
#include "boxes/peer_list_box.h"
|
||||||
|
|
||||||
|
namespace Info {
|
||||||
|
namespace Polls {
|
||||||
|
|
||||||
|
Memento::Memento(not_null<PollData*> poll, FullMsgId contextId)
|
||||||
|
: ContentMemento(poll, contextId) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Section Memento::section() const {
|
||||||
|
return Section(Section::Type::PollResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Memento::setListStates(base::flat_map<
|
||||||
|
QByteArray,
|
||||||
|
std::unique_ptr<PeerListState>> states) {
|
||||||
|
_listStates = std::move(states);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Memento::listStates()
|
||||||
|
-> base::flat_map<QByteArray, std::unique_ptr<PeerListState>> {
|
||||||
|
return std::move(_listStates);
|
||||||
|
}
|
||||||
|
|
||||||
|
object_ptr<ContentWidget> Memento::createWidget(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Controller*> controller,
|
||||||
|
const QRect &geometry) {
|
||||||
|
auto result = object_ptr<Widget>(parent, controller);
|
||||||
|
result->setInternalState(geometry, this);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Memento::~Memento() = default;
|
||||||
|
|
||||||
|
Widget::Widget(QWidget *parent, not_null<Controller*> controller)
|
||||||
|
: ContentWidget(parent, controller)
|
||||||
|
, _inner(setInnerWidget(
|
||||||
|
object_ptr<InnerWidget>(
|
||||||
|
this,
|
||||||
|
controller,
|
||||||
|
controller->poll(),
|
||||||
|
controller->pollContextId()))) {
|
||||||
|
_inner->showPeerInfoRequests(
|
||||||
|
) | rpl::start_with_next([=](not_null<PeerData*> peer) {
|
||||||
|
controller->showPeerInfo(peer);
|
||||||
|
}, _inner->lifetime());
|
||||||
|
_inner->scrollToRequests(
|
||||||
|
) | rpl::start_with_next([=](const Ui::ScrollToRequest &request) {
|
||||||
|
scrollTo(request);
|
||||||
|
}, _inner->lifetime());
|
||||||
|
|
||||||
|
controller->setCanSaveChanges(rpl::single(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
not_null<PollData*> Widget::poll() const {
|
||||||
|
return _inner->poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
FullMsgId Widget::contextId() const {
|
||||||
|
return _inner->contextId();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Widget::showInternal(not_null<ContentMemento*> memento) {
|
||||||
|
//if (const auto myMemento = dynamic_cast<Memento*>(memento.get())) {
|
||||||
|
// Assert(myMemento->self() == self());
|
||||||
|
|
||||||
|
// if (_inner->showInternal(myMemento)) {
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Widget::setInternalState(
|
||||||
|
const QRect &geometry,
|
||||||
|
not_null<Memento*> memento) {
|
||||||
|
setGeometry(geometry);
|
||||||
|
Ui::SendPendingMoveResizeEvents(this);
|
||||||
|
restoreState(memento);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ContentMemento> Widget::doCreateMemento() {
|
||||||
|
auto result = std::make_unique<Memento>(poll(), contextId());
|
||||||
|
saveState(result.get());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Widget::saveState(not_null<Memento*> memento) {
|
||||||
|
memento->setScrollTop(scrollTopSave());
|
||||||
|
_inner->saveState(memento);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Widget::restoreState(not_null<Memento*> memento) {
|
||||||
|
_inner->restoreState(memento);
|
||||||
|
auto scrollTop = memento->scrollTop();
|
||||||
|
scrollTopRestore(memento->scrollTop());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Polls
|
||||||
|
} // namespace Info
|
||||||
70
Telegram/SourceFiles/info/polls/info_polls_results_widget.h
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
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 "info/info_content_widget.h"
|
||||||
|
#include "info/info_controller.h"
|
||||||
|
|
||||||
|
struct PeerListState;
|
||||||
|
|
||||||
|
namespace Info {
|
||||||
|
namespace Polls {
|
||||||
|
|
||||||
|
class InnerWidget;
|
||||||
|
|
||||||
|
class Memento final : public ContentMemento {
|
||||||
|
public:
|
||||||
|
Memento(not_null<PollData*> poll, FullMsgId contextId);
|
||||||
|
~Memento();
|
||||||
|
|
||||||
|
object_ptr<ContentWidget> createWidget(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Controller*> controller,
|
||||||
|
const QRect &geometry) override;
|
||||||
|
|
||||||
|
Section section() const override;
|
||||||
|
|
||||||
|
void setListStates(base::flat_map<
|
||||||
|
QByteArray,
|
||||||
|
std::unique_ptr<PeerListState>> states);
|
||||||
|
auto listStates()
|
||||||
|
-> base::flat_map<QByteArray, std::unique_ptr<PeerListState>>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
base::flat_map<
|
||||||
|
QByteArray,
|
||||||
|
std::unique_ptr<PeerListState>> _listStates;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class Widget final : public ContentWidget {
|
||||||
|
public:
|
||||||
|
Widget(QWidget *parent, not_null<Controller*> controller);
|
||||||
|
|
||||||
|
[[nodiscard]] not_null<PollData*> poll() const;
|
||||||
|
[[nodiscard]] FullMsgId contextId() const;
|
||||||
|
|
||||||
|
bool showInternal(
|
||||||
|
not_null<ContentMemento*> memento) override;
|
||||||
|
|
||||||
|
void setInternalState(
|
||||||
|
const QRect &geometry,
|
||||||
|
not_null<Memento*> memento);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void saveState(not_null<Memento*> memento);
|
||||||
|
void restoreState(not_null<Memento*> memento);
|
||||||
|
|
||||||
|
std::unique_ptr<ContentMemento> doCreateMemento() override;
|
||||||
|
|
||||||
|
not_null<InnerWidget*> _inner;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Settings
|
||||||
|
} // namespace Info
|
||||||
|
|
@ -20,7 +20,6 @@ namespace Settings {
|
||||||
using Type = Section::SettingsType;
|
using Type = Section::SettingsType;
|
||||||
|
|
||||||
struct Tag;
|
struct Tag;
|
||||||
class InnerWidget;
|
|
||||||
|
|
||||||
class Memento final : public ContentMemento {
|
class Memento final : public ContentMemento {
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
|
|
@ -309,6 +309,7 @@ void File::Context::readNextPacket() {
|
||||||
if (i->second.size() == kMaxQueuedPackets) {
|
if (i->second.size() == kMaxQueuedPackets) {
|
||||||
processQueuedPackets(SleepPolicy::Allowed);
|
processQueuedPackets(SleepPolicy::Allowed);
|
||||||
}
|
}
|
||||||
|
Assert(i->second.size() < kMaxQueuedPackets);
|
||||||
} else {
|
} else {
|
||||||
// Still trying to read by drain.
|
// Still trying to read by drain.
|
||||||
Assert(result.is<FFmpeg::AvErrorWrap>());
|
Assert(result.is<FFmpeg::AvErrorWrap>());
|
||||||
|
|
|
||||||
|
|
@ -396,6 +396,8 @@ bool Player::fileProcessPackets(
|
||||||
videoReceivedTill(till);
|
videoReceivedTill(till);
|
||||||
});
|
});
|
||||||
_video->process(base::take(list));
|
_video->process(base::take(list));
|
||||||
|
} else {
|
||||||
|
list.clear(); // Free non-needed packets.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fileReadMore();
|
return fileReadMore();
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ namespace View {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kPreloadCount = 4;
|
constexpr auto kPreloadCount = 4;
|
||||||
|
constexpr auto kMaxZoomLevel = 7; // x8
|
||||||
|
|
||||||
constexpr auto kOverlayLoaderPriority = 2;
|
constexpr auto kOverlayLoaderPriority = 2;
|
||||||
|
|
||||||
|
|
@ -897,13 +898,13 @@ bool OverlayWidget::radialAnimationCallback(crl::time now) {
|
||||||
void OverlayWidget::zoomIn() {
|
void OverlayWidget::zoomIn() {
|
||||||
int32 newZoom = _zoom;
|
int32 newZoom = _zoom;
|
||||||
if (newZoom == ZoomToScreenLevel) {
|
if (newZoom == ZoomToScreenLevel) {
|
||||||
if (qCeil(_zoomToScreen) <= MaxZoomLevel) {
|
if (qCeil(_zoomToScreen) <= kMaxZoomLevel) {
|
||||||
newZoom = qCeil(_zoomToScreen);
|
newZoom = qCeil(_zoomToScreen);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (newZoom < _zoomToScreen && (newZoom + 1 > _zoomToScreen || (_zoomToScreen > MaxZoomLevel && newZoom == MaxZoomLevel))) {
|
if (newZoom < _zoomToScreen && (newZoom + 1 > _zoomToScreen || (_zoomToScreen > kMaxZoomLevel && newZoom == kMaxZoomLevel))) {
|
||||||
newZoom = ZoomToScreenLevel;
|
newZoom = ZoomToScreenLevel;
|
||||||
} else if (newZoom < MaxZoomLevel) {
|
} else if (newZoom < kMaxZoomLevel) {
|
||||||
++newZoom;
|
++newZoom;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -913,13 +914,13 @@ void OverlayWidget::zoomIn() {
|
||||||
void OverlayWidget::zoomOut() {
|
void OverlayWidget::zoomOut() {
|
||||||
int32 newZoom = _zoom;
|
int32 newZoom = _zoom;
|
||||||
if (newZoom == ZoomToScreenLevel) {
|
if (newZoom == ZoomToScreenLevel) {
|
||||||
if (qFloor(_zoomToScreen) >= -MaxZoomLevel) {
|
if (qFloor(_zoomToScreen) >= -kMaxZoomLevel) {
|
||||||
newZoom = qFloor(_zoomToScreen);
|
newZoom = qFloor(_zoomToScreen);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (newZoom > _zoomToScreen && (newZoom - 1 < _zoomToScreen || (_zoomToScreen < -MaxZoomLevel && newZoom == -MaxZoomLevel))) {
|
if (newZoom > _zoomToScreen && (newZoom - 1 < _zoomToScreen || (_zoomToScreen < -kMaxZoomLevel && newZoom == -kMaxZoomLevel))) {
|
||||||
newZoom = ZoomToScreenLevel;
|
newZoom = ZoomToScreenLevel;
|
||||||
} else if (newZoom > -MaxZoomLevel) {
|
} else if (newZoom > -kMaxZoomLevel) {
|
||||||
--newZoom;
|
--newZoom;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -929,7 +930,7 @@ void OverlayWidget::zoomOut() {
|
||||||
void OverlayWidget::zoomReset() {
|
void OverlayWidget::zoomReset() {
|
||||||
int32 newZoom = _zoom;
|
int32 newZoom = _zoom;
|
||||||
if (_zoom == 0) {
|
if (_zoom == 0) {
|
||||||
if (qFloor(_zoomToScreen) == qCeil(_zoomToScreen) && qRound(_zoomToScreen) >= -MaxZoomLevel && qRound(_zoomToScreen) <= MaxZoomLevel) {
|
if (qFloor(_zoomToScreen) == qCeil(_zoomToScreen) && qRound(_zoomToScreen) >= -kMaxZoomLevel && qRound(_zoomToScreen) <= kMaxZoomLevel) {
|
||||||
newZoom = qRound(_zoomToScreen);
|
newZoom = qRound(_zoomToScreen);
|
||||||
} else {
|
} else {
|
||||||
newZoom = ZoomToScreenLevel;
|
newZoom = ZoomToScreenLevel;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "platform/linux/linux_desktop_environment.h"
|
#include "platform/linux/linux_desktop_environment.h"
|
||||||
|
|
||||||
|
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
|
||||||
#include <QDBusInterface>
|
#include <QDBusInterface>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Platform {
|
namespace Platform {
|
||||||
namespace DesktopEnvironment {
|
namespace DesktopEnvironment {
|
||||||
|
|
@ -117,11 +119,11 @@ bool TryQtTrayIcon() {
|
||||||
|
|
||||||
bool PreferAppIndicatorTrayIcon() {
|
bool PreferAppIndicatorTrayIcon() {
|
||||||
return IsXFCE() || IsUnity() || IsUbuntu() ||
|
return IsXFCE() || IsUnity() || IsUbuntu() ||
|
||||||
(IsGnome() && QDBusInterface("org.kde.StatusNotifierWatcher", "/").isValid());
|
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
|
||||||
}
|
(IsGnome() && QDBusInterface("org.kde.StatusNotifierWatcher", "/").isValid());
|
||||||
|
#else
|
||||||
bool TryUnityCounter() {
|
IsGnome();
|
||||||
return IsUnity() || IsPantheon() || IsUbuntu() || IsKDE5();
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace DesktopEnvironment
|
} // namespace DesktopEnvironment
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,6 @@ inline bool IsAwesome() {
|
||||||
|
|
||||||
bool TryQtTrayIcon();
|
bool TryQtTrayIcon();
|
||||||
bool PreferAppIndicatorTrayIcon();
|
bool PreferAppIndicatorTrayIcon();
|
||||||
bool TryUnityCounter();
|
|
||||||
|
|
||||||
} // namespace DesktopEnvironment
|
} // namespace DesktopEnvironment
|
||||||
} // namespace Platform
|
} // namespace Platform
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "facades.h"
|
#include "facades.h"
|
||||||
#include "app.h"
|
#include "app.h"
|
||||||
|
|
||||||
|
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
|
||||||
#include <QtDBus>
|
#include <QtDBus>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <QtWidgets/QMenu>
|
#include <QtWidgets/QMenu>
|
||||||
#include <QtWidgets/QAction>
|
#include <QtWidgets/QAction>
|
||||||
|
|
||||||
|
|
@ -43,7 +46,10 @@ bool _trayIconMuted = true;
|
||||||
int32 _trayIconCount = 0;
|
int32 _trayIconCount = 0;
|
||||||
QImage _trayIconImageBack, _trayIconImage;
|
QImage _trayIconImageBack, _trayIconImage;
|
||||||
QString _desktopFile;
|
QString _desktopFile;
|
||||||
|
|
||||||
|
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
|
||||||
QString _dbusPath = "/";
|
QString _dbusPath = "/";
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||||
void _trayIconPopup(GtkStatusIcon *status_icon, guint button, guint32 activate_time, gpointer popup_menu) {
|
void _trayIconPopup(GtkStatusIcon *status_icon, guint button, guint32 activate_time, gpointer popup_menu) {
|
||||||
|
|
@ -331,6 +337,7 @@ void MainWindow::updateIconCounters() {
|
||||||
|
|
||||||
const auto counter = Core::App().unreadBadge();
|
const auto counter = Core::App().unreadBadge();
|
||||||
|
|
||||||
|
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
|
||||||
if (useUnityCount) {
|
if (useUnityCount) {
|
||||||
QVariantMap dbusUnityProperties;
|
QVariantMap dbusUnityProperties;
|
||||||
if (counter > 0) {
|
if (counter > 0) {
|
||||||
|
|
@ -345,6 +352,7 @@ void MainWindow::updateIconCounters() {
|
||||||
signal << dbusUnityProperties;
|
signal << dbusUnityProperties;
|
||||||
QDBusConnection::sessionBus().send(signal);
|
QDBusConnection::sessionBus().send(signal);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (noQtTrayIcon) {
|
if (noQtTrayIcon) {
|
||||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||||
|
|
@ -534,14 +542,12 @@ void MainWindow::psCreateTrayIcon() {
|
||||||
void MainWindow::psFirstShow() {
|
void MainWindow::psFirstShow() {
|
||||||
psCreateTrayIcon();
|
psCreateTrayIcon();
|
||||||
|
|
||||||
|
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
|
||||||
if (QDBusInterface("com.canonical.Unity", "/").isValid()) {
|
if (QDBusInterface("com.canonical.Unity", "/").isValid()) {
|
||||||
auto snapName = QString::fromLatin1(qgetenv("SNAP_NAME"));
|
auto snapName = QString::fromLatin1(qgetenv("SNAP_NAME"));
|
||||||
if(snapName.isEmpty()) {
|
if(snapName.isEmpty()) {
|
||||||
std::vector<QString> possibleDesktopFiles = {
|
std::vector<QString> possibleDesktopFiles = {
|
||||||
#ifdef TDESKTOP_LAUNCHER_FILENAME
|
MACRO_TO_STRING(TDESKTOP_LAUNCHER_BASENAME) ".desktop",
|
||||||
MACRO_TO_STRING(TDESKTOP_LAUNCHER_FILENAME),
|
|
||||||
#endif // TDESKTOP_LAUNCHER_FILENAME
|
|
||||||
"kotatogramdesktop.desktop",
|
|
||||||
"Kotatogram.desktop"
|
"Kotatogram.desktop"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -565,6 +571,7 @@ void MainWindow::psFirstShow() {
|
||||||
} else {
|
} else {
|
||||||
LOG(("Not using Unity Launcher count."));
|
LOG(("Not using Unity Launcher count."));
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
bool showShadows = true;
|
bool showShadows = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "facades.h"
|
#include "facades.h"
|
||||||
|
|
||||||
#include <QtCore/QBuffer>
|
#include <QtCore/QBuffer>
|
||||||
|
|
||||||
|
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
|
||||||
#include <QtDBus/QDBusConnection>
|
#include <QtDBus/QDBusConnection>
|
||||||
#include <QtDBus/QDBusReply>
|
#include <QtDBus/QDBusReply>
|
||||||
#include <QtDBus/QDBusMetaType>
|
#include <QtDBus/QDBusMetaType>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Platform {
|
namespace Platform {
|
||||||
namespace Notifications {
|
namespace Notifications {
|
||||||
|
|
||||||
|
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kService = str_const("org.freedesktop.Notifications");
|
constexpr auto kService = str_const("org.freedesktop.Notifications");
|
||||||
|
|
@ -52,19 +57,19 @@ std::vector<QString> GetServerInformation(
|
||||||
return serverInformation;
|
return serverInformation;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<QString> GetCapabilities(
|
QStringList GetCapabilities(
|
||||||
const std::shared_ptr<QDBusInterface> ¬ificationInterface) {
|
const std::shared_ptr<QDBusInterface> ¬ificationInterface) {
|
||||||
QDBusReply<QStringList> capabilitiesReply = notificationInterface
|
QDBusReply<QStringList> capabilitiesReply = notificationInterface
|
||||||
->call(qsl("GetCapabilities"));
|
->call(qsl("GetCapabilities"));
|
||||||
|
|
||||||
if (capabilitiesReply.isValid()) {
|
if (capabilitiesReply.isValid()) {
|
||||||
return capabilitiesReply.value().toVector().toStdVector();
|
return capabilitiesReply.value();
|
||||||
} else {
|
} else {
|
||||||
LOG(("Native notification error: %1")
|
LOG(("Native notification error: %1")
|
||||||
.arg(capabilitiesReply.error().message()));
|
.arg(capabilitiesReply.error().message()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::vector<QString>();
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
QVersionNumber ParseSpecificationVersion(
|
QVersionNumber ParseSpecificationVersion(
|
||||||
|
|
@ -92,9 +97,8 @@ NotificationData::NotificationData(
|
||||||
, _peerId(peerId)
|
, _peerId(peerId)
|
||||||
, _msgId(msgId) {
|
, _msgId(msgId) {
|
||||||
auto capabilities = GetCapabilities(_notificationInterface);
|
auto capabilities = GetCapabilities(_notificationInterface);
|
||||||
auto capabilitiesEnd = capabilities.end();
|
|
||||||
|
|
||||||
if (ranges::find(capabilities, qsl("body-markup")) != capabilitiesEnd) {
|
if (capabilities.contains(qsl("body-markup"))) {
|
||||||
_body = subtitle.isEmpty()
|
_body = subtitle.isEmpty()
|
||||||
? msg.toHtmlEscaped()
|
? msg.toHtmlEscaped()
|
||||||
: qsl("<b>%1</b>\n%2").arg(subtitle.toHtmlEscaped())
|
: qsl("<b>%1</b>\n%2").arg(subtitle.toHtmlEscaped())
|
||||||
|
|
@ -105,24 +109,33 @@ NotificationData::NotificationData(
|
||||||
: qsl("%1\n%2").arg(subtitle).arg(msg);
|
: qsl("%1\n%2").arg(subtitle).arg(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ranges::find(capabilities, qsl("actions")) != capabilitiesEnd) {
|
if (capabilities.contains(qsl("actions"))) {
|
||||||
_actions << qsl("default") << QString();
|
_actions << qsl("default") << QString();
|
||||||
|
|
||||||
// icon name according to https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
|
|
||||||
_actions << qsl("mail-reply-sender")
|
|
||||||
<< tr::lng_notification_reply(tr::now);
|
|
||||||
|
|
||||||
connect(_notificationInterface.get(),
|
connect(_notificationInterface.get(),
|
||||||
SIGNAL(ActionInvoked(uint, QString)),
|
SIGNAL(ActionInvoked(uint, QString)),
|
||||||
this, SLOT(notificationClicked(uint)));
|
this, SLOT(notificationClicked(uint)));
|
||||||
|
|
||||||
|
if (capabilities.contains(qsl("inline-reply"))) {
|
||||||
|
_actions << qsl("inline-reply")
|
||||||
|
<< tr::lng_notification_reply(tr::now);
|
||||||
|
|
||||||
|
connect(_notificationInterface.get(),
|
||||||
|
SIGNAL(NotificationReplied(uint,QString)),
|
||||||
|
this, SLOT(notificationReplied(uint,QString)));
|
||||||
|
} else {
|
||||||
|
// icon name according to https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
|
||||||
|
_actions << qsl("mail-reply-sender")
|
||||||
|
<< tr::lng_notification_reply(tr::now);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ranges::find(capabilities, qsl("action-icons")) != capabilitiesEnd) {
|
if (capabilities.contains(qsl("action-icons"))) {
|
||||||
_hints["action-icons"] = true;
|
_hints["action-icons"] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// suppress system sound if telegram sound activated, otherwise use system sound
|
// suppress system sound if telegram sound activated, otherwise use system sound
|
||||||
if (ranges::find(capabilities, qsl("sound")) != capabilitiesEnd) {
|
if (capabilities.contains(qsl("sound"))) {
|
||||||
if (Global::SoundNotify()) {
|
if (Global::SoundNotify()) {
|
||||||
_hints["suppress-sound"] = true;
|
_hints["suppress-sound"] = true;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -131,20 +144,14 @@ NotificationData::NotificationData(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ranges::find(capabilities, qsl("x-canonical-append"))
|
if (capabilities.contains(qsl("x-canonical-append"))) {
|
||||||
!= capabilitiesEnd) {
|
|
||||||
_hints["x-canonical-append"] = qsl("true");
|
_hints["x-canonical-append"] = qsl("true");
|
||||||
}
|
}
|
||||||
|
|
||||||
_hints["category"] = qsl("im.received");
|
_hints["category"] = qsl("im.received");
|
||||||
|
|
||||||
#ifdef TDESKTOP_LAUNCHER_FILENAME
|
|
||||||
_hints["desktop-entry"] =
|
_hints["desktop-entry"] =
|
||||||
qsl(MACRO_TO_STRING(TDESKTOP_LAUNCHER_FILENAME))
|
qsl(MACRO_TO_STRING(TDESKTOP_LAUNCHER_BASENAME));
|
||||||
.remove(QRegExp(qsl("\\.desktop$"), Qt::CaseInsensitive));
|
|
||||||
#else
|
|
||||||
_hints["desktop-entry"] = qsl("kotatogramdesktop");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
connect(_notificationInterface.get(),
|
connect(_notificationInterface.get(),
|
||||||
SIGNAL(NotificationClosed(uint, uint)),
|
SIGNAL(NotificationClosed(uint, uint)),
|
||||||
|
|
@ -238,6 +245,15 @@ void NotificationData::notificationClicked(uint id) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NotificationData::notificationReplied(uint id, const QString &text) {
|
||||||
|
if (id == _notificationId) {
|
||||||
|
const auto manager = _manager;
|
||||||
|
crl::on_main(manager, [=] {
|
||||||
|
manager->notificationReplied(_peerId, _msgId, { text, {} });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QDBusArgument &operator<<(QDBusArgument &argument,
|
QDBusArgument &operator<<(QDBusArgument &argument,
|
||||||
const NotificationData::ImageData &imageData) {
|
const NotificationData::ImageData &imageData) {
|
||||||
argument.beginStructure();
|
argument.beginStructure();
|
||||||
|
|
@ -265,24 +281,32 @@ const QDBusArgument &operator>>(const QDBusArgument &argument,
|
||||||
argument.endStructure();
|
argument.endStructure();
|
||||||
return argument;
|
return argument;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
bool Supported() {
|
bool Supported() {
|
||||||
|
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
|
||||||
static auto Available = QDBusInterface(
|
static auto Available = QDBusInterface(
|
||||||
str_const_toString(kService),
|
str_const_toString(kService),
|
||||||
str_const_toString(kObjectPath),
|
str_const_toString(kObjectPath),
|
||||||
str_const_toString(kInterface)).isValid();
|
str_const_toString(kInterface)).isValid();
|
||||||
|
|
||||||
return Available;
|
return Available;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Window::Notifications::Manager> Create(
|
std::unique_ptr<Window::Notifications::Manager> Create(
|
||||||
Window::Notifications::System *system) {
|
Window::Notifications::System *system) {
|
||||||
|
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
|
||||||
if (Global::NativeNotifications() && Supported()) {
|
if (Global::NativeNotifications() && Supported()) {
|
||||||
return std::make_unique<Manager>(system);
|
return std::make_unique<Manager>(system);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
|
||||||
Manager::Private::Private(Manager *manager, Type type)
|
Manager::Private::Private(Manager *manager, Type type)
|
||||||
: _cachedUserpics(type)
|
: _cachedUserpics(type)
|
||||||
, _manager(manager)
|
, _manager(manager)
|
||||||
|
|
@ -303,14 +327,7 @@ Manager::Private::Private(Manager *manager, Type type)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!capabilities.empty()) {
|
if (!capabilities.empty()) {
|
||||||
const auto capabilitiesString = std::accumulate(
|
const auto capabilitiesString = capabilities.join(", ");
|
||||||
capabilities.begin(),
|
|
||||||
capabilities.end(),
|
|
||||||
QString{},
|
|
||||||
[](auto &s, auto &p) {
|
|
||||||
return s + (p + qstr(", "));
|
|
||||||
}).chopped(2);
|
|
||||||
|
|
||||||
LOG(("Notification daemon capabilities: %1").arg(capabilitiesString));
|
LOG(("Notification daemon capabilities: %1").arg(capabilitiesString));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -431,6 +448,7 @@ void Manager::doClearAllFast() {
|
||||||
void Manager::doClearFromHistory(not_null<History*> history) {
|
void Manager::doClearFromHistory(not_null<History*> history) {
|
||||||
_private->clearFromHistory(history);
|
_private->clearFromHistory(history);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace Notifications
|
} // namespace Notifications
|
||||||
} // namespace Platform
|
} // namespace Platform
|
||||||
|
|
|
||||||