Compare commits
217 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f26a24f78 | ||
|
|
6abd4bae58 | ||
|
|
f8844750f5 | ||
|
|
de984d44ac | ||
|
|
babcefeb23 | ||
|
|
71a55290ab | ||
|
|
8ef0b88633 | ||
|
|
f9c3415aa7 | ||
|
|
deb7d5914f | ||
|
|
469e4394bd | ||
|
|
1bcf9dda0a | ||
|
|
02fafde09d | ||
|
|
de5e1b3452 | ||
|
|
4381db691b | ||
|
|
bd879262c2 | ||
|
|
da88b4c475 | ||
|
|
276a18cddd | ||
|
|
6eb54e55a5 | ||
|
|
e7fa330215 | ||
|
|
27ed160a40 | ||
|
|
180b614c86 | ||
|
|
e2e6b64632 | ||
|
|
be46aacbe5 | ||
|
|
3bd46f3415 | ||
|
|
7e6e2960bd | ||
|
|
9df4377450 | ||
|
|
294ab035f0 | ||
|
|
f96554e271 | ||
|
|
2c1fdbe55b | ||
|
|
b5bce29514 | ||
|
|
97557b85d9 | ||
|
|
8a5cc70d9b | ||
|
|
d5075891b2 | ||
|
|
4df778f4e9 | ||
|
|
3cda66f1f3 | ||
|
|
e0991d9376 | ||
|
|
8c94742070 | ||
|
|
419b2b02bc | ||
|
|
b4998527dd | ||
|
|
8ac8598b20 | ||
|
|
f7ddf8b024 | ||
|
|
18f1563829 | ||
|
|
20ba35c0a7 | ||
|
|
de4764c1c7 | ||
|
|
31bb8a3ac5 | ||
|
|
e0156abe7c | ||
|
|
7d02e92843 | ||
|
|
80aef75ea5 | ||
|
|
c940062787 | ||
|
|
4b3b9ad140 | ||
|
|
1bb2efc346 | ||
|
|
0b699b45b1 | ||
|
|
3c0487db81 | ||
|
|
f62869e4df | ||
|
|
fce459f612 | ||
|
|
6a003114db | ||
|
|
39ed5cab0d | ||
|
|
4b67c8f4bb | ||
|
|
65604add65 | ||
|
|
3e69f65d1c | ||
|
|
0b26dbbc9e | ||
|
|
152d943f3d | ||
|
|
7fffd1d318 | ||
|
|
5d875341f4 | ||
|
|
61dbd4c4c2 | ||
|
|
5f42011c7b | ||
|
|
7e56174ba9 | ||
|
|
0a83b3f58c | ||
|
|
71fec23311 | ||
|
|
2ba3035a13 | ||
|
|
7a653a8e1b | ||
|
|
a93b32fb53 | ||
|
|
d82cc350c0 | ||
|
|
5997a7d48a | ||
|
|
7641fb6712 | ||
|
|
dfaf9b9d43 | ||
|
|
6147b0eec0 | ||
|
|
2eefef3649 | ||
|
|
1905a67e6c | ||
|
|
38ab18f6fb | ||
|
|
ddcc20c8a7 | ||
|
|
f3c6763058 | ||
|
|
91a11d2e74 | ||
|
|
fb880481a4 | ||
|
|
9e6703f02f | ||
|
|
da5435d1cb | ||
|
|
db0726364b | ||
|
|
944b2de852 | ||
|
|
53927502af | ||
|
|
828d8ea051 | ||
|
|
f2e345b39f | ||
|
|
6f625a899c | ||
|
|
28bc8c3bf3 | ||
|
|
ef30949943 | ||
|
|
4f888bc418 | ||
|
|
34caf6967e | ||
|
|
a728c783d9 | ||
|
|
934d232653 | ||
|
|
4fbc7771c9 | ||
|
|
571ab422bf | ||
|
|
9592f4de6e | ||
|
|
c126d99fd0 | ||
|
|
11c8a272ec | ||
|
|
f7170b8c50 | ||
|
|
9ce28f4cb7 | ||
|
|
522a457d98 | ||
|
|
9e9ffaa27b | ||
|
|
9ab4a8ab6e | ||
|
|
5c784081c2 | ||
|
|
9c84c0afe9 | ||
|
|
accc5e8b10 | ||
|
|
1e0c71a411 | ||
|
|
663687884b | ||
|
|
ade2bdcafd | ||
|
|
9e01e64f63 | ||
|
|
7cc0fd879d | ||
|
|
42bd835800 | ||
|
|
ef7b5cb8e7 | ||
|
|
2651d79b63 | ||
|
|
ddc12c3f71 | ||
|
|
9c3c2e9fa7 | ||
|
|
564f9ac38d | ||
|
|
1b49bd0843 | ||
|
|
831d79d912 | ||
|
|
d61f809cca | ||
|
|
130176fd7e | ||
|
|
1ea9be9877 | ||
|
|
e3b56aa05b | ||
|
|
4f4195c88a | ||
|
|
1d9a8af174 | ||
|
|
8b1e7e1fa9 | ||
|
|
eeb0b62bfa | ||
|
|
0a114bbe4a | ||
|
|
474458e4c3 | ||
|
|
c6b73631d8 | ||
|
|
bce318c48e | ||
|
|
9e4ac1c835 | ||
|
|
53933db077 | ||
|
|
4156d8e908 | ||
|
|
6491d83085 | ||
|
|
6259d30e2b | ||
|
|
3ee7a0dc16 | ||
|
|
43d8723e35 | ||
|
|
d59ab17ac5 | ||
|
|
d26cda097f | ||
|
|
6daa58b596 | ||
|
|
d0920d9eb9 | ||
|
|
fbd353501b | ||
|
|
9876aa64e0 | ||
|
|
b8ccf7b3d4 | ||
|
|
c15b5914b2 | ||
|
|
73aebb8890 | ||
|
|
f9270710be | ||
|
|
0b63abf5b4 | ||
|
|
51ae4af83e | ||
|
|
4832982988 | ||
|
|
b4d49715dc | ||
|
|
0de7b4eb39 | ||
|
|
3e88451ea8 | ||
|
|
5468fde492 | ||
|
|
f17e13cad0 | ||
|
|
f511449273 | ||
|
|
bbe65e212a | ||
|
|
5ebd0df15f | ||
|
|
66a34bcb89 | ||
|
|
acad1d4175 | ||
|
|
716fade52a | ||
|
|
3b9312d9ac | ||
|
|
f87d072c79 | ||
|
|
b61724019a | ||
|
|
3ffdb1ee56 | ||
|
|
5fbf280e4a | ||
|
|
f787e0fa1d | ||
|
|
0d33b92d24 | ||
|
|
8e5d0c66db | ||
|
|
6e8ac60399 | ||
|
|
f8bd80109c | ||
|
|
de89d349ad | ||
|
|
dbc9beaa19 | ||
|
|
1f171c4ed1 | ||
|
|
2e03888505 | ||
|
|
ab5eafbe68 | ||
|
|
73014a33fe | ||
|
|
7bdbe0ef77 | ||
|
|
d4dbad4649 | ||
|
|
24b23bbb5a | ||
|
|
abab44a02b | ||
|
|
b9c07e644f | ||
|
|
ce9c3b4ef8 | ||
|
|
13862bd561 | ||
|
|
856d38df49 | ||
|
|
27a5e13107 | ||
|
|
a71c24f803 | ||
|
|
758ec52b91 | ||
|
|
96418bb9f1 | ||
|
|
f750d94b2d | ||
|
|
6eb9695e1e | ||
|
|
33bbad8053 | ||
|
|
3ea34461b2 | ||
|
|
b6a202b721 | ||
|
|
4d8cb022c5 | ||
|
|
3046318de5 | ||
|
|
c351598f13 | ||
|
|
02084be583 | ||
|
|
e117d08b2c | ||
|
|
58783170cd | ||
|
|
9ba2426c02 | ||
|
|
2cbaa7b03d | ||
|
|
836a0f3a73 | ||
|
|
e9977f551f | ||
|
|
78abe362cd | ||
|
|
47c2922d55 | ||
|
|
690f3fecbb | ||
|
|
d9242db7b3 | ||
|
|
a9b5e22b37 | ||
|
|
7b8f1704dc | ||
|
|
de88ddf42b |
7
.github/workflows/mac_packaged.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
||||
run: |
|
||||
brew update
|
||||
brew upgrade || true
|
||||
brew install ada-url autoconf automake boost cmake ffmpeg@6 jpeg-xl libavif libheif libtool openal-soft openh264 openssl opus ninja pkg-config python qt yasm xz
|
||||
brew install ada-url autoconf automake boost cmake ffmpeg@6 jpeg-xl libavif libheif libtool openal-soft openh264 openssl opus ninja pkg-config python qtbase qtimageformats qtsvg xz
|
||||
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
||||
|
||||
xcodebuild -version > CACHE_KEY.txt
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
fi
|
||||
echo "CACHE_KEY=`md5 -q CACHE_KEY.txt`" >> $GITHUB_ENV
|
||||
|
||||
echo "MACOSX_DEPLOYMENT_TARGET=$(grep 'set(QT_SUPPORTED_MIN_MACOS_VERSION' /opt/homebrew/Cellar/qt/6.9.2/lib/cmake/Qt6/Qt6ConfigExtras.cmake | sed -E 's/^.*"(.*)"\)$/\1/')" >> $GITHUB_ENV
|
||||
echo "MACOSX_DEPLOYMENT_TARGET=$(grep 'set(QT_SUPPORTED_MIN_MACOS_VERSION' /opt/homebrew/Cellar/qtbase/*/lib/cmake/Qt6/Qt6ConfigExtras.cmake | sed -E 's/^.*"(.*)"\)$/\1/')" >> $GITHUB_ENV
|
||||
echo "LibrariesPath=`pwd`" >> $GITHUB_ENV
|
||||
|
||||
curl -o tg_owt-version.json https://api.github.com/repos/desktop-app/tg_owt/git/refs/heads/master
|
||||
@@ -115,7 +115,8 @@ jobs:
|
||||
cmake -Bbuild -GNinja . \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DCMAKE_C_FLAGS_DEBUG="" \
|
||||
-DCMAKE_CXX_FLAGS_DEBUG=""
|
||||
-DCMAKE_CXX_FLAGS_DEBUG="" \
|
||||
-DCMAKE_DISABLE_FIND_PACKAGE_absl=ON
|
||||
|
||||
cmake --build build --parallel
|
||||
|
||||
|
||||
@@ -1044,6 +1044,8 @@ PRIVATE
|
||||
info/polls/info_polls_results_widget.h
|
||||
info/profile/info_profile_actions.cpp
|
||||
info/profile/info_profile_actions.h
|
||||
info/profile/info_profile_badge_tooltip.cpp
|
||||
info/profile/info_profile_badge_tooltip.h
|
||||
info/profile/info_profile_badge.cpp
|
||||
info/profile/info_profile_badge.h
|
||||
info/profile/info_profile_cover.cpp
|
||||
@@ -1058,8 +1060,10 @@ PRIVATE
|
||||
info/profile/info_profile_members_controllers.h
|
||||
info/profile/info_profile_phone_menu.cpp
|
||||
info/profile/info_profile_phone_menu.h
|
||||
info/profile/info_profile_text.cpp
|
||||
info/profile/info_profile_text.h
|
||||
info/profile/info_profile_status_label.cpp
|
||||
info/profile/info_profile_status_label.h
|
||||
info/profile/info_profile_top_bar.cpp
|
||||
info/profile/info_profile_top_bar.h
|
||||
info/profile/info_profile_values.cpp
|
||||
info/profile/info_profile_values.h
|
||||
info/profile/info_profile_widget.cpp
|
||||
@@ -1669,8 +1673,12 @@ PRIVATE
|
||||
ui/item_text_options.cpp
|
||||
ui/item_text_options.h
|
||||
ui/resize_area.h
|
||||
ui/top_background_gradient.cpp
|
||||
ui/top_background_gradient.h
|
||||
ui/unread_badge.cpp
|
||||
ui/unread_badge.h
|
||||
ui/peer/video_userpic_player.cpp
|
||||
ui/peer/video_userpic_player.h
|
||||
window/main_window.cpp
|
||||
window/main_window.h
|
||||
window/notifications_manager.cpp
|
||||
|
||||
BIN
Telegram/Resources/animations/my_gifts_empty.tgs
Normal file
BIN
Telegram/Resources/animations/profile/profile_muting.tgs
Normal file
BIN
Telegram/Resources/animations/profile/profile_unmuting.tgs
Normal file
3
Telegram/Resources/icons/profile/call.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M19.035,15.184L16.561,14.901C15.786,14.771 14.725,15.259 13.376,16.365C13.315,16.416 13.142,16.533 12.95,16.54C12.764,16.547 12.56,16.444 12.493,16.408C10.162,15.134 8.734,13.687 7.467,11.306C7.435,11.247 7.356,11.086 7.38,10.938C7.402,10.802 7.524,10.676 7.567,10.624C8.595,9.364 9.108,8.299 9.108,7.429L8.826,4.974C8.709,3.99 7.881,3.25 6.887,3.25L5.202,3.25C4.101,3.25 3.185,4.166 3.254,5.267C3.77,13.586 10.424,20.23 18.733,20.746C19.834,20.815 20.75,19.899 20.75,18.798L20.75,17.113C20.76,16.129 20.019,15.301 19.035,15.184Z" fill="#FFFFFF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 649 B |
3
Telegram/Resources/icons/profile/gift.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M11.157,13.843L11.157,21.158L7.506,21.158C6.265,21.158 5.26,20.152 5.26,18.911L5.26,13.843L11.157,13.843ZM18.741,13.829L18.74,18.911C18.74,20.152 17.735,21.158 16.494,21.158L12.842,21.158L12.842,13.843L18.6,13.843C18.648,13.843 18.695,13.838 18.741,13.829ZM14.727,3.208C15.308,3.208 15.832,3.33 16.301,3.575C16.77,3.819 17.144,4.158 17.423,4.591C17.702,5.025 17.841,5.519 17.841,6.075C17.841,6.454 17.768,6.802 17.622,7.119C17.492,7.402 17.319,7.652 17.104,7.871L17.037,7.935L19.302,7.935C19.923,7.935 20.426,8.438 20.426,9.058L20.426,11.313C20.426,11.933 19.923,12.436 19.302,12.436L18.74,12.436L18.741,12.45C18.695,12.441 18.648,12.436 18.6,12.436L12.842,12.436L12.842,7.951L11.16,7.951L11.16,6.729C11.16,6.118 10.977,5.631 10.611,5.268C10.245,4.905 9.804,4.723 9.287,4.723C8.8,4.723 8.403,4.853 8.097,5.114C7.79,5.375 7.637,5.736 7.637,6.197C7.637,6.668 7.827,7.078 8.207,7.427C8.521,7.715 8.94,7.885 9.464,7.935L11.157,7.935L11.157,12.436L4.698,12.436C4.077,12.436 3.574,11.933 3.574,11.313L3.574,9.058C3.574,8.438 4.077,7.935 4.698,7.935L6.849,7.935C6.603,7.702 6.408,7.43 6.265,7.119C6.119,6.802 6.046,6.454 6.046,6.075C6.046,5.519 6.185,5.025 6.464,4.591C6.743,4.158 7.118,3.819 7.59,3.575C8.061,3.33 8.585,3.208 9.16,3.208C9.8,3.208 10.369,3.371 10.868,3.698C11.367,4.025 11.727,4.487 11.948,5.085C12.169,4.487 12.527,4.025 13.023,3.698C13.519,3.371 14.087,3.208 14.727,3.208ZM14.608,4.723C14.087,4.723 13.643,4.905 13.276,5.268C12.91,5.631 12.843,6.118 12.843,6.729L12.842,7.935L14.423,7.935C14.909,7.888 15.305,7.739 15.61,7.487L15.679,7.427C16.059,7.078 16.249,6.668 16.249,6.197C16.249,5.736 16.096,5.375 15.79,5.114C15.484,4.853 15.09,4.723 14.608,4.723Z" fill="#FFFFFF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
3
Telegram/Resources/icons/profile/join.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M17.176,12.972C19.682,12.972 21.713,15.004 21.713,17.509C21.713,20.015 19.682,22.046 17.176,22.046C14.67,22.046 12.639,20.015 12.639,17.509C12.639,15.004 14.67,12.972 17.176,12.972ZM11.343,12.729C11.998,12.729 12.619,12.78 13.209,12.881C11.869,14.011 11.019,15.701 11.019,17.59C11.019,18.624 11.273,19.598 11.723,20.453L5.218,20.453C4.358,20.453 3.759,20.317 3.422,20.046C3.085,19.774 2.917,19.391 2.917,18.895C2.917,18.245 3.113,17.562 3.505,16.845C3.897,16.128 4.462,15.459 5.2,14.836C5.938,14.214 6.824,13.707 7.86,13.316C8.895,12.925 10.056,12.729 11.343,12.729ZM17.176,14.593C16.848,14.593 16.582,14.858 16.582,15.186L16.582,16.915L14.853,16.915C14.525,16.915 14.259,17.181 14.259,17.509C14.259,17.837 14.525,18.103 14.853,18.103L16.582,18.103L16.582,19.832C16.582,20.16 16.848,20.426 17.176,20.426C17.504,20.426 17.77,20.16 17.77,19.832L17.77,18.103L19.499,18.103C19.827,18.103 20.093,17.837 20.093,17.509C20.093,17.181 19.827,16.915 19.499,16.915L17.77,16.915L17.77,15.186C17.77,14.858 17.504,14.593 17.176,14.593ZM11.343,10.974C12.117,10.974 12.822,10.784 13.459,10.405C14.096,10.025 14.605,9.514 14.985,8.872C15.365,8.229 15.556,7.507 15.556,6.706C15.556,5.934 15.364,5.233 14.98,4.605C14.596,3.976 14.085,3.476 13.446,3.105C12.807,2.733 12.106,2.548 11.343,2.548C10.579,2.548 9.878,2.735 9.239,3.111C8.601,3.486 8.089,3.989 7.705,4.621C7.322,5.253 7.13,5.953 7.13,6.722C7.133,7.513 7.325,8.229 7.705,8.872C8.085,9.514 8.595,10.025 9.234,10.405C9.873,10.784 10.576,10.974 11.343,10.974Z" fill="#FFFFFF" fill-rule="evenodd"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
3
Telegram/Resources/icons/profile/leave.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M12.329,3.574C13.94,3.574 15.245,4.88 15.245,6.491L15.245,7.463C15.245,8 14.81,8.435 14.273,8.435C13.736,8.435 13.301,8 13.301,7.463L13.301,6.491C13.301,5.954 12.866,5.519 12.329,5.519L6.171,5.519C5.634,5.519 5.199,5.954 5.199,6.491L5.199,17.509C5.199,18.046 5.634,18.481 6.171,18.481L12.329,18.481C12.866,18.481 13.301,18.046 13.301,17.509L13.301,16.537C13.301,16 13.736,15.565 14.273,15.565C14.81,15.565 15.245,16 15.245,16.537L15.245,17.509C15.245,19.12 13.94,20.426 12.329,20.426L6.171,20.426C4.56,20.426 3.255,19.12 3.255,17.509L3.255,6.491C3.255,4.88 4.56,3.574 6.171,3.574L12.329,3.574ZM19.045,7.945L21.927,11.13C22.374,11.624 22.374,12.376 21.927,12.87L19.045,16.055C18.685,16.453 18.07,16.484 17.672,16.124C17.274,15.763 17.243,15.149 17.603,14.751L19.212,12.972L10.06,12.972C9.523,12.972 9.088,12.537 9.088,12C9.088,11.463 9.523,11.028 10.06,11.028L19.212,11.028L17.603,9.249C17.257,8.867 17.272,8.285 17.626,7.921L17.672,7.876C18.07,7.516 18.685,7.547 19.045,7.945Z" fill="#FFFFFF" fill-rule="nonzero"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
3
Telegram/Resources/icons/profile/message.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M12.009,2.759C17.307,2.759 21.602,6.688 21.602,11.533C21.602,16.379 17.307,20.307 12.009,20.307C10.566,20.307 9.197,20.016 7.969,19.494C7.602,19.784 7.273,20.004 6.98,20.154C6.092,20.609 5.496,20.772 4.144,20.914C3.778,20.952 3.534,20.625 3.819,20.34C4.452,19.708 4.793,18.577 4.966,17.49C3.384,15.925 2.417,13.833 2.417,11.533C2.417,6.688 6.711,2.759 12.009,2.759Z" fill="#FFFFFF" fill-rule="evenodd"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 505 B |
3
Telegram/Resources/icons/profile/mute.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M11.922,2.528C12.663,2.528 13.264,3.129 13.264,3.87L13.264,4.492C16.232,5.216 18.32,7.681 18.32,10.805L18.32,14.987C18.32,15.133 18.373,15.273 18.47,15.382L19.678,16.735C20.342,17.382 19.668,18.491 18.731,18.491L5.261,18.491C4.324,18.491 3.661,17.382 4.324,16.735L5.532,15.382C5.629,15.273 5.682,15.133 5.682,14.987L5.682,10.805C5.682,7.735 7.675,5.317 10.556,4.538L10.556,3.87C10.556,3.151 11.121,2.564 11.831,2.529L11.922,2.528ZM12.001,21.472C13.159,21.472 14.107,20.558 14.107,19.525L9.895,19.525C9.895,20.558 10.832,21.472 12.001,21.472Z" fill="#FFFFFF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 661 B |
BIN
Telegram/Resources/icons/profile/profile_more.png
Normal file
|
After Width: | Height: | Size: 352 B |
BIN
Telegram/Resources/icons/profile/profile_more@2x.png
Normal file
|
After Width: | Height: | Size: 442 B |
BIN
Telegram/Resources/icons/profile/profile_more@3x.png
Normal file
|
After Width: | Height: | Size: 818 B |
3
Telegram/Resources/icons/profile/report.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M12,2.833C17.063,2.833 21.167,6.937 21.167,12C21.167,17.063 17.063,21.167 12,21.167C6.937,21.167 2.833,17.063 2.833,12C2.833,6.937 6.937,2.833 12,2.833ZM12,14.444C11.347,14.444 10.813,14.957 10.779,15.602L10.778,15.689C10.778,16.364 11.325,16.911 12,16.911C12.653,16.911 13.187,16.398 13.221,15.754L13.222,15.667C13.222,14.992 12.675,14.444 12,14.444ZM12,7.111C11.404,7.111 10.922,7.594 10.922,8.19L10.922,11.995C10.922,12.591 11.404,13.074 12,13.074C12.596,13.074 13.078,12.591 13.078,11.995L13.078,8.19C13.078,7.594 12.596,7.111 12,7.111Z" fill="#FFFFFF" fill-rule="evenodd"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 680 B |
3
Telegram/Resources/icons/profile/unmute.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M14.107,19.525C14.107,20.558 13.159,21.472 12.001,21.472C10.832,21.472 9.895,20.558 9.895,19.525L14.107,19.525ZM3.505,3.471L20.618,19.654C20.962,19.979 20.977,20.52 20.652,20.864C20.327,21.207 19.786,21.222 19.443,20.898L2.329,4.714C1.986,4.389 1.971,3.848 2.295,3.505C2.62,3.161 3.162,3.146 3.505,3.471ZM5.718,10.105L14.585,18.491L5.261,18.491C4.324,18.491 3.661,17.382 4.324,16.735L5.532,15.382C5.629,15.273 5.682,15.133 5.682,14.987L5.682,10.805C5.682,10.568 5.694,10.334 5.718,10.105ZM11.922,2.528C12.663,2.528 13.264,3.129 13.264,3.87L13.264,4.492C16.232,5.216 18.32,7.681 18.32,10.805L18.32,14.987C18.32,15.133 18.373,15.273 18.47,15.382L18.781,15.73L8.174,5.699C8.862,5.176 9.666,4.779 10.556,4.538L10.556,3.87C10.556,3.151 11.121,2.564 11.831,2.529L11.922,2.528Z" fill="#FFFFFF" fill-rule="nonzero"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 910 B |
@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_menu_my_stories" = "My Stories";
|
||||
"lng_menu_my_groups" = "My Groups";
|
||||
"lng_menu_my_channels" = "My Channels";
|
||||
"lng_open_menu" = "Open navigation menu";
|
||||
|
||||
"lng_disable_notifications_from_tray" = "Disable notifications";
|
||||
"lng_enable_notifications_from_tray" = "Enable notifications";
|
||||
@@ -127,6 +128,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_cancel" = "Cancel";
|
||||
"lng_continue" = "Continue";
|
||||
"lng_close" = "Close";
|
||||
"lng_minimize_window" = "Minimize";
|
||||
"lng_maximize_window" = "Maximize";
|
||||
"lng_restore_window" = "Restore";
|
||||
"lng_go_back" = "Go back";
|
||||
"lng_connecting" = "Connecting...";
|
||||
"lng_reconnecting#one" = "Reconnect in {count} s...";
|
||||
"lng_reconnecting#other" = "Reconnect in {count} s...";
|
||||
@@ -388,6 +393,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_country_ph" = "Search";
|
||||
"lng_country_none" = "Country not found";
|
||||
"lng_country_select" = "Select Country";
|
||||
"lng_phone_number" = "Phone number";
|
||||
|
||||
"lng_code_ph" = "Code";
|
||||
"lng_code_desc" = "We've sent an activation code to your phone.\nPlease enter it below.";
|
||||
@@ -970,6 +976,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_font_family" = "Font family";
|
||||
|
||||
"lng_settings_color_title" = "Color preview";
|
||||
"lng_settings_color_tab_profile" = "Profile";
|
||||
"lng_settings_color_tab_name" = "Name";
|
||||
"lng_settings_color_reply" = "Reply to your message";
|
||||
"lng_settings_color_reply_channel" = "Reply to your channel message";
|
||||
"lng_settings_color_text" = "Your name and replies to your messages will be shown in the selected color.";
|
||||
@@ -984,10 +992,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_color_emoji_off" = "Off";
|
||||
"lng_settings_color_emoji_about" = "Make replies to your messages stand out by adding custom patterns to them.";
|
||||
"lng_settings_color_emoji_about_channel" = "Select an icon to create a custom pattern for replies to your messages.";
|
||||
"lng_settings_color_subscribe" = "Subscribe to {link} to choose a custom color for your name.";
|
||||
"lng_settings_color_changed" = "Your name color has been updated!";
|
||||
"lng_settings_color_changed_channel" = "Your channel color has been updated!";
|
||||
"lng_settings_color_changed_profile" = "Your profile style has been updated!";
|
||||
"lng_settings_color_changed_profile_channel" = "Your channel profile style has been updated!";
|
||||
"lng_settings_color_apply" = "Apply Style";
|
||||
"lng_settings_color_profile_emoji" = "Add icons to Profile";
|
||||
"lng_settings_color_profile_emoji_channel" = "Profile Logo";
|
||||
"lng_settings_color_reset" = "Reset Profile Color";
|
||||
"lng_settings_color_profile_about" = "You can change the color of your name and customize replies to you. {link}";
|
||||
"lng_settings_color_profile_about_link" = "Change {emoji}";
|
||||
"lng_settings_color_choose_channel" = "Choose a color and a logo for your channel's profile";
|
||||
"lng_settings_color_choose_group" = "Choose a color and a logo for the group's profile";
|
||||
"lng_settings_color_group_boost_footer#one" = "The group has **{count}** boost. {link}";
|
||||
"lng_settings_color_group_boost_footer#other" = "The group has **{count}** boosts. {link}";
|
||||
"lng_settings_color_group_boost_footer_link" = "What are boosts?";
|
||||
|
||||
"lng_suggest_hide_new_title" = "Hide new chats?";
|
||||
"lng_suggest_hide_new_about" = "You are receiving lots of new chats from users who are not in your Contact List.\n\nDo you want to have such chats **automatically muted** and **archived**?";
|
||||
@@ -1640,6 +1659,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_profile_open_app_short" = "Open";
|
||||
"lng_profile_open_app_about" = "By launching this mini app, you agree to the {terms}.";
|
||||
"lng_profile_open_app_terms" = "Terms of Service for Mini Apps";
|
||||
"lng_profile_open_photo" = "Open Photo";
|
||||
"lng_profile_bot_permissions_title" = "Allow access to";
|
||||
"lng_profile_bot_emoji_status_access" = "Emoji Status";
|
||||
"lng_info_add_as_contact" = "Add to contacts";
|
||||
@@ -1662,6 +1682,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_profile_changed_photo_title" = "Photo updated";
|
||||
"lng_profile_changed_photo_about" = "You can change it in {link}.";
|
||||
"lng_profile_changed_photo_link" = "Settings";
|
||||
|
||||
"lng_profile_action_short_message" = "Message";
|
||||
"lng_profile_action_short_mute" = "Mute";
|
||||
"lng_profile_action_short_unmute" = "Unmute";
|
||||
"lng_profile_action_short_call" = "Call";
|
||||
"lng_profile_action_short_discuss" = "Discuss";
|
||||
"lng_profile_action_short_gift" = "Gift";
|
||||
"lng_profile_action_short_join" = "Join";
|
||||
"lng_profile_action_short_report" = "Report";
|
||||
"lng_profile_action_short_leave" = "Leave";
|
||||
"lng_profile_action_short_more" = "More";
|
||||
|
||||
"lng_media_type_photos" = "Photos";
|
||||
"lng_media_type_gifs" = "GIFs";
|
||||
"lng_media_type_videos" = "Videos";
|
||||
@@ -2736,6 +2768,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_premium_summary_about_filter_tags" = "Display folder names for each chat in the chat list.";
|
||||
"lng_premium_summary_subtitle_todo_lists" = "Checklists";
|
||||
"lng_premium_summary_about_todo_lists" = "Plan, assign, and complete tasks - seamlessly and efficiently.";
|
||||
"lng_premium_summary_subtitle_peer_colors" = "Name and Profile Colors";
|
||||
"lng_premium_summary_about_peer_colors" = "Choose a color and logo for your profile and replies to your messages.";
|
||||
"lng_premium_summary_bottom_subtitle" = "About Telegram Premium";
|
||||
"lng_premium_summary_bottom_about" = "While the free version of Telegram already gives its users more than any other messaging application, **Telegram Premium** pushes its capabilities even further.\n\n**Telegram Premium** is a paid option, because most Premium Features require additional expenses from Telegram to third parties such as data center providers and server manufacturers. Contributions from **Telegram Premium** users allow us to cover such costs and also help Telegram stay free for everyone.";
|
||||
"lng_premium_summary_button" = "Subscribe for {cost} per month";
|
||||
@@ -3615,6 +3649,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_stars_your_finished" = "none left";
|
||||
"lng_gift_stars_tabs_all" = "All Gifts";
|
||||
"lng_gift_stars_tabs_my" = "My Gifts";
|
||||
"lng_gift_stars_tabs_my_empty" = "You don't have any gifts you can use as a profile cover.";
|
||||
"lng_gift_stars_tabs_my_empty_next" = "Browse gifts available for purchase {emoji}";
|
||||
"lng_gift_stars_tabs_collectibles" = "Collectibles";
|
||||
"lng_gift_send_title" = "Send a Gift";
|
||||
"lng_gift_send_message" = "Enter Message";
|
||||
|
||||
@@ -45,6 +45,10 @@
|
||||
<file alias="photo_suggest_icon.tgs">../../animations/photo_suggest_icon.tgs</file>
|
||||
<file alias="toast/saved_messages.tgs">../../animations/toast/saved_messages.tgs</file>
|
||||
<file alias="toast/tagged.tgs">../../animations/toast/tagged.tgs</file>
|
||||
<file alias="my_gifts_empty.tgs">../../animations/my_gifts_empty.tgs</file>
|
||||
|
||||
<file alias="profile_muting.tgs">../../animations/profile/profile_muting.tgs</file>
|
||||
<file alias="profile_unmuting.tgs">../../animations/profile/profile_unmuting.tgs</file>
|
||||
|
||||
<file alias="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file>
|
||||
<file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="6.2.2.0" />
|
||||
Version="6.2.5.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 6,2,2,0
|
||||
PRODUCTVERSION 6,2,2,0
|
||||
FILEVERSION 6,2,5,0
|
||||
PRODUCTVERSION 6,2,5,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "6.2.2.0"
|
||||
VALUE "FileVersion", "6.2.5.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "6.2.2.0"
|
||||
VALUE "ProductVersion", "6.2.5.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 6,2,2,0
|
||||
PRODUCTVERSION 6,2,2,0
|
||||
FILEVERSION 6,2,5,0
|
||||
PRODUCTVERSION 6,2,5,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "6.2.2.0"
|
||||
VALUE "FileVersion", "6.2.5.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "6.2.2.0"
|
||||
VALUE "ProductVersion", "6.2.5.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -9,7 +9,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/color_int_conversion.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
@@ -20,8 +22,9 @@ constexpr auto kRequestEach = 3600 * crl::time(1000);
|
||||
|
||||
PeerColors::PeerColors(not_null<ApiWrap*> api)
|
||||
: _api(&api->instance())
|
||||
, _timer([=] { request(); }) {
|
||||
, _timer([=] { request(); requestProfile(); }) {
|
||||
request();
|
||||
requestProfile();
|
||||
_timer.callEach(kRequestEach);
|
||||
}
|
||||
|
||||
@@ -45,6 +48,24 @@ void PeerColors::request() {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void PeerColors::requestProfile() {
|
||||
if (_profileRequestId) {
|
||||
return;
|
||||
}
|
||||
_profileRequestId = _api.request(MTPhelp_GetPeerProfileColors(
|
||||
MTP_int(_profileHash)
|
||||
)).done([=](const MTPhelp_PeerColors &result) {
|
||||
_profileRequestId = 0;
|
||||
result.match([&](const MTPDhelp_peerColors &data) {
|
||||
_profileHash = data.vhash().v;
|
||||
applyProfile(data);
|
||||
}, [](const MTPDhelp_peerColorsNotModified &) {
|
||||
});
|
||||
}).fail([=] {
|
||||
_profileRequestId = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
std::vector<uint8> PeerColors::suggested() const {
|
||||
return _suggested.current();
|
||||
}
|
||||
@@ -76,21 +97,27 @@ const base::flat_map<uint8, int> &PeerColors::requiredLevelsChannel() const {
|
||||
return _requiredLevelsChannel;
|
||||
}
|
||||
|
||||
int PeerColors::requiredGroupLevelFor(PeerId channel, uint8 index) const {
|
||||
int PeerColors::requiredLevelFor(
|
||||
PeerId channel,
|
||||
uint8 index,
|
||||
bool isMegagroup,
|
||||
bool profile) const {
|
||||
if (Data::DecideColorIndex(channel) == index) {
|
||||
return 0;
|
||||
} else if (const auto i = _requiredLevelsGroup.find(index)
|
||||
; i != end(_requiredLevelsGroup)) {
|
||||
return i->second;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int PeerColors::requiredChannelLevelFor(PeerId channel, uint8 index) const {
|
||||
if (Data::DecideColorIndex(channel) == index) {
|
||||
return 0;
|
||||
} else if (const auto i = _requiredLevelsChannel.find(index)
|
||||
; i != end(_requiredLevelsChannel)) {
|
||||
if (profile) {
|
||||
const auto it = _profileColors.find(index);
|
||||
if (it != end(_profileColors)) {
|
||||
return isMegagroup
|
||||
? it->second.requiredLevelsGroup
|
||||
: it->second.requiredLevelsChannel;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
const auto &levels = isMegagroup
|
||||
? _requiredLevelsGroup
|
||||
: _requiredLevelsChannel;
|
||||
if (const auto i = levels.find(index); i != end(levels)) {
|
||||
return i->second;
|
||||
}
|
||||
return 1;
|
||||
@@ -165,4 +192,87 @@ void PeerColors::apply(const MTPDhelp_peerColors &data) {
|
||||
_suggested = std::move(suggested);
|
||||
}
|
||||
|
||||
void PeerColors::applyProfile(const MTPDhelp_peerColors &data) {
|
||||
const auto parseColors = [](const MTPhelp_PeerColorSet &set) {
|
||||
const auto toUint = [](const MTPint &c) {
|
||||
return (uint32(1) << 24) | uint32(c.v);
|
||||
};
|
||||
return set.match([&](const MTPDhelp_peerColorSet &) {
|
||||
LOG(("API Error: peerColorSet in profile colors result!"));
|
||||
return Data::ColorProfileSet();
|
||||
}, [&](const MTPDhelp_peerColorProfileSet &data) {
|
||||
auto set = Data::ColorProfileSet();
|
||||
set.palette.reserve(data.vpalette_colors().v.size());
|
||||
set.bg.reserve(data.vbg_colors().v.size());
|
||||
set.story.reserve(data.vstory_colors().v.size());
|
||||
for (const auto &c : data.vpalette_colors().v) {
|
||||
set.palette.push_back(Ui::ColorFromSerialized(toUint(c)));
|
||||
}
|
||||
for (const auto &c : data.vbg_colors().v) {
|
||||
set.bg.push_back(Ui::ColorFromSerialized(toUint(c)));
|
||||
}
|
||||
for (const auto &c : data.vstory_colors().v) {
|
||||
set.story.push_back(Ui::ColorFromSerialized(toUint(c)));
|
||||
}
|
||||
return set;
|
||||
});
|
||||
};
|
||||
|
||||
auto suggested = std::vector<Data::ColorProfileData>();
|
||||
const auto &list = data.vcolors().v;
|
||||
suggested.reserve(list.size());
|
||||
for (const auto &color : list) {
|
||||
const auto &data = color.data();
|
||||
const auto colorIndexBare = data.vcolor_id().v;
|
||||
if (colorIndexBare < 0 || colorIndexBare >= Ui::kColorIndexCount) {
|
||||
LOG(("API Error: Bad color index: %1").arg(colorIndexBare));
|
||||
continue;
|
||||
}
|
||||
const auto colorIndex = uint8(colorIndexBare);
|
||||
auto result = ProfileColorOption();
|
||||
result.isHidden = data.is_hidden();
|
||||
if (const auto min = data.vgroup_min_level()) {
|
||||
result.requiredLevelsGroup = min->v;
|
||||
}
|
||||
if (const auto min = data.vchannel_min_level()) {
|
||||
result.requiredLevelsChannel = min->v;
|
||||
}
|
||||
if (const auto light = data.vcolors()) {
|
||||
result.data.light = parseColors(*light);
|
||||
}
|
||||
if (const auto dark = data.vdark_colors()) {
|
||||
result.data.dark = parseColors(*dark);
|
||||
}
|
||||
_profileColors[colorIndex] = std::move(result);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Data::ColorProfileSet> PeerColors::colorProfileFor(
|
||||
not_null<PeerData*> peer) const {
|
||||
if (const auto colorProfileIndex = peer->colorProfileIndex()) {
|
||||
return colorProfileFor(*colorProfileIndex);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<Data::ColorProfileSet> PeerColors::colorProfileFor(
|
||||
uint8 index) const {
|
||||
const auto i = _profileColors.find(index);
|
||||
if (i != end(_profileColors)) {
|
||||
return Window::Theme::IsNightMode()
|
||||
? i->second.data.dark
|
||||
: i->second.data.light;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<uint8> PeerColors::profileColorIndices() const {
|
||||
auto result = std::vector<uint8>();
|
||||
result.reserve(_profileColors.size());
|
||||
for (const auto &[index, option] : _profileColors) {
|
||||
result.push_back(index);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "data/data_peer_colors.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class ApiWrap;
|
||||
@@ -34,27 +35,45 @@ public:
|
||||
[[nodiscard]] auto requiredLevelsChannel() const
|
||||
-> const base::flat_map<uint8, int> &;
|
||||
|
||||
[[nodiscard]] int requiredGroupLevelFor(
|
||||
PeerId channel,
|
||||
uint8 index) const;
|
||||
[[nodiscard]] int requiredChannelLevelFor(
|
||||
[[nodiscard]] int requiredLevelFor(
|
||||
PeerId channel,
|
||||
uint8 index,
|
||||
bool isMegagroup,
|
||||
bool profile) const;
|
||||
|
||||
[[nodiscard]] std::optional<Data::ColorProfileSet> colorProfileFor(
|
||||
not_null<PeerData*> peer) const;
|
||||
[[nodiscard]] std::optional<Data::ColorProfileSet> colorProfileFor(
|
||||
uint8 index) const;
|
||||
|
||||
[[nodiscard]] std::vector<uint8> profileColorIndices() const;
|
||||
|
||||
private:
|
||||
struct ProfileColorOption {
|
||||
Data::ColorProfileData data;
|
||||
int requiredLevelsChannel = 0;
|
||||
int requiredLevelsGroup = 0;
|
||||
bool isHidden = false;
|
||||
};
|
||||
|
||||
void request();
|
||||
void requestProfile();
|
||||
void apply(const MTPDhelp_peerColors &data);
|
||||
void applyProfile(const MTPDhelp_peerColors &data);
|
||||
|
||||
MTP::Sender _api;
|
||||
int32 _hash = 0;
|
||||
int32 _profileHash = 0;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
mtpRequestId _profileRequestId = 0;
|
||||
base::Timer _timer;
|
||||
rpl::variable<std::vector<uint8>> _suggested;
|
||||
base::flat_map<uint8, int> _requiredLevelsGroup;
|
||||
base::flat_map<uint8, int> _requiredLevelsChannel;
|
||||
rpl::event_stream<> _colorIndicesChanged;
|
||||
std::unique_ptr<Ui::ColorIndicesCompressed> _colorIndicesCurrent;
|
||||
base::flat_map<uint8, ProfileColorOption> _profileColors;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -525,6 +525,14 @@ void EditCaptionBox::rebuildPreview() {
|
||||
_content->modifyRequests(
|
||||
) | rpl::start_to_stream(_photoEditorOpens, _content->lifetime());
|
||||
|
||||
_content->editCoverRequests() | rpl::start_with_next([=] {
|
||||
setupEditCoverHandler();
|
||||
}, _content->lifetime());
|
||||
|
||||
_content->clearCoverRequests() | rpl::start_with_next([=] {
|
||||
setupClearCoverHandler();
|
||||
}, _content->lifetime());
|
||||
|
||||
_content->heightValue(
|
||||
) | rpl::start_to_stream(_contentHeight, _content->lifetime());
|
||||
|
||||
@@ -740,6 +748,89 @@ void EditCaptionBox::setupPhotoEditorEventHandler() {
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void EditCaptionBox::setupEditCoverHandler() {
|
||||
if (_preparedList.files.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto &file = _preparedList.files.front();
|
||||
if (!file.isVideoFile()) {
|
||||
return;
|
||||
}
|
||||
const auto show = _controller->uiShow();
|
||||
const auto replace = [=](Ui::PreparedList list) {
|
||||
if (list.files.empty()) {
|
||||
return;
|
||||
}
|
||||
auto &entry = _preparedList.files.front();
|
||||
const auto video = entry.information
|
||||
? std::get_if<Ui::PreparedFileInformation::Video>(
|
||||
&entry.information->media)
|
||||
: nullptr;
|
||||
if (!video) {
|
||||
return;
|
||||
}
|
||||
auto old = std::shared_ptr<Ui::PreparedFile>(
|
||||
std::move(entry.videoCover));
|
||||
entry.videoCover = std::make_unique<Ui::PreparedFile>(
|
||||
std::move(list.files.front()));
|
||||
Editor::OpenWithPreparedFile(
|
||||
this,
|
||||
show,
|
||||
entry.videoCover.get(),
|
||||
st::sendMediaPreviewSize,
|
||||
crl::guard(this, [=](bool ok) {
|
||||
if (!ok) {
|
||||
_preparedList.files.front().videoCover = old
|
||||
? std::make_unique<Ui::PreparedFile>(
|
||||
std::move(*old))
|
||||
: nullptr;
|
||||
}
|
||||
rebuildPreview();
|
||||
}),
|
||||
video->thumbnail.size());
|
||||
};
|
||||
const auto checkResult = [=](const Ui::PreparedList &list) {
|
||||
if (list.files.empty()) {
|
||||
return true;
|
||||
}
|
||||
if (list.files.front().type != Ui::PreparedFile::Type::Photo) {
|
||||
show->showToast(tr::lng_choose_cover_bad(tr::now));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const auto callback = [=](FileDialog::OpenResult &&result) {
|
||||
const auto premium = show->session().premium();
|
||||
const auto showError = [=](tr::phrase<> t) {
|
||||
show->showToast(t(tr::now));
|
||||
};
|
||||
auto list = Storage::PreparedFileFromFilesDialog(
|
||||
std::move(result),
|
||||
checkResult,
|
||||
showError,
|
||||
st::sendMediaPreviewSize,
|
||||
premium);
|
||||
if (list) {
|
||||
replace(std::move(*list));
|
||||
}
|
||||
};
|
||||
|
||||
FileDialog::GetOpenPath(
|
||||
this,
|
||||
tr::lng_choose_cover(tr::now),
|
||||
FileDialog::ImagesFilter(),
|
||||
crl::guard(this, callback));
|
||||
}
|
||||
|
||||
void EditCaptionBox::setupClearCoverHandler() {
|
||||
if (_preparedList.files.empty()) {
|
||||
return;
|
||||
}
|
||||
auto &entry = _preparedList.files.front();
|
||||
entry.videoCover = nullptr;
|
||||
rebuildPreview();
|
||||
}
|
||||
|
||||
void EditCaptionBox::setupDragArea() {
|
||||
auto enterFilter = [=](not_null<const QMimeData*> data) {
|
||||
return !_isAllowedEditMedia
|
||||
|
||||
@@ -87,6 +87,8 @@ private:
|
||||
void rebuildPreview();
|
||||
void setupEditEventHandler();
|
||||
void setupPhotoEditorEventHandler();
|
||||
void setupEditCoverHandler();
|
||||
void setupClearCoverHandler();
|
||||
void setupField();
|
||||
void setupFieldAutocomplete();
|
||||
void setupControls();
|
||||
|
||||
@@ -449,25 +449,25 @@ void Controller::setupNotesField() {
|
||||
};
|
||||
const auto limitState = _notesField->lifetime().make_state<LimitState>();
|
||||
|
||||
const auto checkCharsLimitation = [=] {
|
||||
const auto checkCharsLimitation = [=, w = _notesField->window()] {
|
||||
const auto limit = Data::PremiumLimits(
|
||||
&_user->session()).contactNoteLengthCurrent();
|
||||
const auto remove = Ui::ComputeFieldCharacterCount(_notesField)
|
||||
- limit;
|
||||
if (!limitState->charsLimitation) {
|
||||
const auto border = _notesField->st().borderActive;
|
||||
limitState->charsLimitation = base::make_unique_q<Limit>(
|
||||
_box->verticalLayout(),
|
||||
emojiButton,
|
||||
style::al_top,
|
||||
QMargins{ 0, -st::lineWidth, 0, 0 });
|
||||
_notesField->heightValue(
|
||||
) | rpl::start_with_next([=](int height) {
|
||||
const auto &st = _notesField->st();
|
||||
const auto hasMultipleLines = height >
|
||||
(st.textMargins.top()
|
||||
+ st.style.font->height
|
||||
+ st.textMargins.bottom() * 2);
|
||||
limitState->charsLimitation->setVisible(hasMultipleLines);
|
||||
QMargins{ 0, -border - _notesField->st().border, 0, 0 });
|
||||
rpl::combine(
|
||||
limitState->charsLimitation->geometryValue(),
|
||||
_notesField->geometryValue()
|
||||
) | rpl::start_with_next([=](QRect limit, QRect field) {
|
||||
limitState->charsLimitation->setVisible(
|
||||
(w->mapToGlobal(limit.bottomLeft()).y() - border)
|
||||
< w->mapToGlobal(field.bottomLeft()).y());
|
||||
limitState->charsLimitation->raise();
|
||||
}, limitState->charsLimitation->lifetime());
|
||||
}
|
||||
@@ -484,7 +484,10 @@ void Controller::setupNotesField() {
|
||||
}
|
||||
|
||||
void Controller::setupPhotoButtons() {
|
||||
const auto iconSize = st::restoreUserpicIcon.size;
|
||||
if (!_user->isContact()) {
|
||||
return;
|
||||
}
|
||||
const auto iconPlaceholder = st::restoreUserpicIcon.size * 2;
|
||||
auto nameValue = _firstNameField
|
||||
? rpl::merge(
|
||||
rpl::single(_firstNameField->getLastText().trimmed()),
|
||||
@@ -515,7 +518,8 @@ void Controller::setupPhotoButtons() {
|
||||
.sessionWindow = base::make_weak(_window),
|
||||
}));
|
||||
});
|
||||
suggestBirthdayWrap->toggleOn(rpl::single(!_user->birthday().valid()));
|
||||
suggestBirthdayWrap->toggleOn(rpl::single(!_user->birthday().valid()
|
||||
&& !_user->starsPerMessageChecked()));
|
||||
|
||||
_suggestIcon = Ui::MakeAnimatedIcon({
|
||||
.generator = [] {
|
||||
@@ -524,7 +528,7 @@ void Controller::setupPhotoButtons() {
|
||||
QByteArray(),
|
||||
u":/animations/photo_suggest_icon.tgs"_q));
|
||||
},
|
||||
.sizeOverride = iconSize * style::DevicePixelRatio(),
|
||||
.sizeOverride = iconPlaceholder,
|
||||
.colorized = true,
|
||||
});
|
||||
|
||||
@@ -535,30 +539,36 @@ void Controller::setupPhotoButtons() {
|
||||
QByteArray(),
|
||||
u":/animations/camera_outline.tgs"_q));
|
||||
},
|
||||
.sizeOverride = iconSize * style::DevicePixelRatio(),
|
||||
.sizeOverride = iconPlaceholder,
|
||||
.colorized = true,
|
||||
});
|
||||
|
||||
const auto suggestButtonWrap = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
inner,
|
||||
object_ptr<Ui::VerticalLayout>(inner)));
|
||||
suggestButtonWrap->toggleOn(
|
||||
rpl::single(!_user->starsPerMessageChecked()));
|
||||
|
||||
const auto suggestButton = Settings::AddButtonWithIcon(
|
||||
inner,
|
||||
suggestButtonWrap->entity(),
|
||||
tr::lng_suggest_photo_for(lt_user, rpl::duplicate(nameValue)),
|
||||
st::settingsButtonLight,
|
||||
{ nullptr });
|
||||
|
||||
_suggestIconWidget = Ui::CreateChild<Ui::RpWidget>(suggestButton);
|
||||
_suggestIconWidget->resize(iconSize * style::DevicePixelRatio());
|
||||
_suggestIconWidget->resize(iconPlaceholder);
|
||||
_suggestIconWidget->paintRequest() | rpl::start_with_next([=] {
|
||||
if (_suggestIcon && _suggestIcon->valid()) {
|
||||
auto p = QPainter(_suggestIconWidget);
|
||||
const auto frame = _suggestIcon->frame(st::lightButtonFg->c);
|
||||
const auto rect = _suggestIconWidget->rect();
|
||||
p.drawImage(rect, frame);
|
||||
p.drawImage(_suggestIconWidget->rect(), frame);
|
||||
}
|
||||
}, _suggestIconWidget->lifetime());
|
||||
|
||||
suggestButton->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||
_suggestIconWidget->move(
|
||||
st::settingsButtonLight.iconLeft - iconSize.width() / 2,
|
||||
st::settingsButtonLight.iconLeft - iconPlaceholder.width() / 4,
|
||||
(size.height() - _suggestIconWidget->height()) / 2);
|
||||
}, _suggestIconWidget->lifetime());
|
||||
|
||||
@@ -579,19 +589,18 @@ void Controller::setupPhotoButtons() {
|
||||
{ nullptr });
|
||||
|
||||
_cameraIconWidget = Ui::CreateChild<Ui::RpWidget>(setButton);
|
||||
_cameraIconWidget->resize(iconSize * style::DevicePixelRatio());
|
||||
_cameraIconWidget->resize(iconPlaceholder);
|
||||
_cameraIconWidget->paintRequest() | rpl::start_with_next([=] {
|
||||
if (_cameraIcon && _cameraIcon->valid()) {
|
||||
auto p = QPainter(_cameraIconWidget);
|
||||
const auto frame = _cameraIcon->frame(st::lightButtonFg->c);
|
||||
const auto rect = _cameraIconWidget->rect();
|
||||
p.drawImage(rect, frame);
|
||||
p.drawImage(_cameraIconWidget->rect(), frame);
|
||||
}
|
||||
}, _cameraIconWidget->lifetime());
|
||||
|
||||
setButton->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||
_cameraIconWidget->move(
|
||||
st::settingsButtonLight.iconLeft - iconSize.width() / 2,
|
||||
st::settingsButtonLight.iconLeft - iconPlaceholder.width() / 4,
|
||||
(size.height() - _cameraIconWidget->height()) / 2);
|
||||
}, _cameraIconWidget->lifetime());
|
||||
|
||||
@@ -634,7 +643,7 @@ void Controller::setupPhotoButtons() {
|
||||
resetButtonWrap->toggleOn(
|
||||
_user->session().changes().peerFlagsValue(
|
||||
_user,
|
||||
Data::PeerUpdate::Flag::FullInfo
|
||||
Data::PeerUpdate::Flag::FullInfo | Data::PeerUpdate::Flag::Photo
|
||||
) | rpl::map([=] {
|
||||
return _user->hasPersonalPhoto();
|
||||
}) | rpl::distinct_until_changed());
|
||||
@@ -645,8 +654,9 @@ void Controller::setupPhotoButtons() {
|
||||
tr::now,
|
||||
lt_user,
|
||||
_user->shortName()),
|
||||
.confirmed = [=] {
|
||||
.confirmed = [=](Fn<void()> close) {
|
||||
_window->session().api().peerPhoto().clearPersonal(_user);
|
||||
close();
|
||||
},
|
||||
.confirmText = tr::lng_profile_photo_reset(tr::now),
|
||||
}));
|
||||
@@ -810,11 +820,6 @@ void Controller::processChosenPhoto(QImage &&image, bool suggest) {
|
||||
Api::PeerPhoto::UserPhoto photo{
|
||||
.image = base::duplicate(image),
|
||||
};
|
||||
if (suggest && _suggestIcon && _suggestIcon->valid()) {
|
||||
_suggestIcon->animate([=] { _suggestIconWidget->update(); });
|
||||
} else if (!suggest && _cameraIcon && _cameraIcon->valid()) {
|
||||
_cameraIcon->animate([=] { _cameraIconWidget->update(); });
|
||||
}
|
||||
if (suggest) {
|
||||
_window->session().api().peerPhoto().suggest(_user, std::move(photo));
|
||||
_window->showPeerHistory(_user->id);
|
||||
@@ -831,11 +836,6 @@ void Controller::processChosenPhotoWithMarkup(
|
||||
.markupDocumentId = data.id,
|
||||
.markupColors = std::move(data.colors),
|
||||
};
|
||||
if (suggest && _suggestIcon && _suggestIcon->valid()) {
|
||||
_suggestIcon->animate([=] { _suggestIconWidget->update(); });
|
||||
} else if (!suggest && _cameraIcon && _cameraIcon->valid()) {
|
||||
_cameraIcon->animate([=] { _cameraIconWidget->update(); });
|
||||
}
|
||||
if (suggest) {
|
||||
_window->session().api().peerPhoto().suggest(_user, std::move(photo));
|
||||
_window->showPeerHistory(_user->id);
|
||||
|
||||
@@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/event_filter.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "boxes/star_gift_box.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/data_premium_limits.h"
|
||||
#include "data/data_channel.h"
|
||||
@@ -36,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/dynamic_image.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/top_background_gradient.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_credits.h"
|
||||
#include "styles/style_premium.h"
|
||||
@@ -895,9 +895,9 @@ public:
|
||||
auto p = QPainter(&_backgroundCache);
|
||||
p.setClipRect(inner);
|
||||
const auto skip = inner.width() / 3;
|
||||
Ui::PaintPoints(
|
||||
Ui::PaintBgPoints(
|
||||
p,
|
||||
Ui::PatternPointsSmall(),
|
||||
Ui::PatternBgPointsSmall(),
|
||||
_patternCache,
|
||||
_patternEmoji.get(),
|
||||
*_unique,
|
||||
|
||||
@@ -135,6 +135,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||
return tr::lng_premium_summary_subtitle_effects();
|
||||
case PremiumFeature::TodoLists:
|
||||
return tr::lng_premium_summary_subtitle_todo_lists();
|
||||
case PremiumFeature::PeerColors:
|
||||
return tr::lng_premium_summary_subtitle_peer_colors();
|
||||
|
||||
case PremiumFeature::BusinessLocation:
|
||||
return tr::lng_business_subtitle_location();
|
||||
@@ -202,6 +204,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||
return tr::lng_premium_summary_about_effects();
|
||||
case PremiumFeature::TodoLists:
|
||||
return tr::lng_premium_summary_about_todo_lists();
|
||||
case PremiumFeature::PeerColors:
|
||||
return tr::lng_premium_summary_about_peer_colors();
|
||||
|
||||
case PremiumFeature::BusinessLocation:
|
||||
return tr::lng_business_about_location();
|
||||
@@ -543,6 +547,7 @@ struct VideoPreviewDocument {
|
||||
case PremiumFeature::MessagePrivacy: return "message_privacy";
|
||||
case PremiumFeature::Effects: return "effects";
|
||||
case PremiumFeature::TodoLists: return "todo";
|
||||
case PremiumFeature::PeerColors: return "peer_colors";
|
||||
|
||||
case PremiumFeature::BusinessLocation: return "business_location";
|
||||
case PremiumFeature::BusinessHours: return "business_hours";
|
||||
|
||||
@@ -73,6 +73,7 @@ enum class PremiumFeature {
|
||||
Effects,
|
||||
FilterTags,
|
||||
TodoLists,
|
||||
PeerColors,
|
||||
|
||||
// Business features.
|
||||
BusinessLocation,
|
||||
|
||||
@@ -1734,7 +1734,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
result,
|
||||
msgIds);
|
||||
const auto showRecentForwardsToSelf = result.size() == 1
|
||||
&& result.front()->peer()->isSelf();
|
||||
&& result.front()->peer()->isSelf()
|
||||
&& history->owner().session().premium();
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
for (const auto thread : result) {
|
||||
if (!comment.text.isEmpty()) {
|
||||
|
||||
@@ -91,6 +91,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/top_background_gradient.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
@@ -571,61 +572,6 @@ auto GenerateGiftMedia(
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage CreateGradient(
|
||||
QSize size,
|
||||
const Data::UniqueGift &gift) {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
auto result = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(ratio);
|
||||
|
||||
auto p = QPainter(&result);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
auto gradient = QRadialGradient(
|
||||
QRect(QPoint(), size).center(),
|
||||
size.height() / 2);
|
||||
gradient.setStops({
|
||||
{ 0., gift.backdrop.centerColor },
|
||||
{ 1., gift.backdrop.edgeColor },
|
||||
});
|
||||
p.setBrush(gradient);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.drawRect(QRect(QPoint(), size));
|
||||
p.end();
|
||||
|
||||
const auto mask = Images::CornersMask(st::boxRadius);
|
||||
return Images::Round(std::move(result), mask, RectPart::FullTop);
|
||||
}
|
||||
|
||||
void PrepareImage(
|
||||
QImage &image,
|
||||
not_null<Text::CustomEmoji*> emoji,
|
||||
const PatternPoint &point,
|
||||
const Data::UniqueGift &gift) {
|
||||
if (!image.isNull() || !emoji->ready()) {
|
||||
return;
|
||||
}
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto size = Emoji::GetSizeNormal() / ratio;
|
||||
image = QImage(
|
||||
2 * QSize(size, size) * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
image.setDevicePixelRatio(ratio);
|
||||
image.fill(Qt::transparent);
|
||||
auto p = QPainter(&image);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setOpacity(point.opacity);
|
||||
if (point.scale < 1.) {
|
||||
p.translate(size, size);
|
||||
p.scale(point.scale, point.scale);
|
||||
p.translate(-size, -size);
|
||||
}
|
||||
const auto shift = (2 * size - (Emoji::GetSizeLarge() / ratio)) / 2;
|
||||
emoji->paint(p, {
|
||||
.textColor = gift.backdrop.patternColor,
|
||||
.position = QPoint(shift, shift),
|
||||
});
|
||||
}
|
||||
|
||||
PreviewWrap::PreviewWrap(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<PeerData*> recipient,
|
||||
@@ -3910,13 +3856,15 @@ void AddUniqueGiftCover(
|
||||
const auto pointsHeight = st::uniqueGiftSubtitleTop;
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
if (gift.gradient.size() != cover->size() * ratio) {
|
||||
gift.gradient = CreateGradient(cover->size(), *gift.gift);
|
||||
gift.gradient = Ui::CreateTopBgGradient(
|
||||
cover->size(),
|
||||
*gift.gift);
|
||||
}
|
||||
p.drawImage(0, 0, gift.gradient);
|
||||
|
||||
PaintPoints(
|
||||
Ui::PaintBgPoints(
|
||||
p,
|
||||
PatternPoints(),
|
||||
Ui::PatternBgPoints(),
|
||||
gift.emojis,
|
||||
gift.emoji.get(),
|
||||
*gift.gift,
|
||||
@@ -4021,13 +3969,15 @@ void AddWearGiftCover(
|
||||
const auto pointsHeight = st::uniqueGiftSubtitleTop;
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
if (state->gradient.size() != cover->size() * ratio) {
|
||||
state->gradient = CreateGradient(cover->size(), state->gift);
|
||||
state->gradient = Ui::CreateTopBgGradient(
|
||||
cover->size(),
|
||||
state->gift);
|
||||
}
|
||||
p.drawImage(0, 0, state->gradient);
|
||||
|
||||
PaintPoints(
|
||||
Ui::PaintBgPoints(
|
||||
p,
|
||||
PatternPoints(),
|
||||
Ui::PatternBgPoints(),
|
||||
state->emojis,
|
||||
state->emoji.get(),
|
||||
state->gift,
|
||||
@@ -5060,119 +5010,11 @@ void UpgradeBox(
|
||||
AddUniqueCloseButton(box, {});
|
||||
}
|
||||
|
||||
const std::vector<PatternPoint> &PatternPoints() {
|
||||
static const auto kSmall = 0.7;
|
||||
static const auto kFaded = 0.2;
|
||||
static const auto kLarge = 0.85;
|
||||
static const auto kOpaque = 0.3;
|
||||
static const auto result = std::vector<PatternPoint>{
|
||||
{ { 0.5, 0.066 }, kSmall, kFaded },
|
||||
|
||||
{ { 0.177, 0.168 }, kSmall, kFaded },
|
||||
{ { 0.822, 0.168 }, kSmall, kFaded },
|
||||
|
||||
{ { 0.37, 0.168 }, kLarge, kOpaque },
|
||||
{ { 0.63, 0.168 }, kLarge, kOpaque },
|
||||
|
||||
{ { 0.277, 0.308 }, kSmall, kOpaque },
|
||||
{ { 0.723, 0.308 }, kSmall, kOpaque },
|
||||
|
||||
{ { 0.13, 0.42 }, kSmall, kFaded },
|
||||
{ { 0.87, 0.42 }, kSmall, kFaded },
|
||||
|
||||
{ { 0.27, 0.533 }, kLarge, kOpaque },
|
||||
{ { 0.73, 0.533 }, kLarge, kOpaque },
|
||||
|
||||
{ { 0.2, 0.73 }, kSmall, kFaded },
|
||||
{ { 0.8, 0.73 }, kSmall, kFaded },
|
||||
|
||||
{ { 0.302, 0.825 }, kLarge, kOpaque },
|
||||
{ { 0.698, 0.825 }, kLarge, kOpaque },
|
||||
|
||||
{ { 0.5, 0.876 }, kLarge, kFaded },
|
||||
|
||||
{ { 0.144, 0.936 }, kSmall, kFaded },
|
||||
{ { 0.856, 0.936 }, kSmall, kFaded },
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
const std::vector<PatternPoint> &PatternPointsSmall() {
|
||||
static const auto kSmall = 0.45;
|
||||
static const auto kFaded = 0.2;
|
||||
static const auto kLarge = 0.55;
|
||||
static const auto kOpaque = 0.3;
|
||||
static const auto result = std::vector<PatternPoint>{
|
||||
{ { 0.5, 0.066 }, kSmall, kFaded },
|
||||
|
||||
{ { 0.177, 0.168 }, kSmall, kFaded },
|
||||
{ { 0.822, 0.168 }, kSmall, kFaded },
|
||||
|
||||
{ { 0.37, 0.168 }, kLarge, kOpaque },
|
||||
{ { 0.63, 0.168 }, kLarge, kOpaque },
|
||||
|
||||
{ { 0.277, 0.308 }, kSmall, kOpaque },
|
||||
{ { 0.723, 0.308 }, kSmall, kOpaque },
|
||||
|
||||
{ { 0.13, 0.42 }, kSmall, kFaded },
|
||||
{ { 0.87, 0.42 }, kSmall, kFaded },
|
||||
|
||||
{ { 0.27, 0.533 }, kLarge, kOpaque },
|
||||
{ { 0.73, 0.533 }, kLarge, kOpaque },
|
||||
|
||||
{ { 0.2, 0.73 }, kSmall, kFaded },
|
||||
{ { 0.8, 0.73 }, kSmall, kFaded },
|
||||
|
||||
{ { 0.302, 0.825 }, kLarge, kOpaque },
|
||||
{ { 0.698, 0.825 }, kLarge, kOpaque },
|
||||
|
||||
{ { 0.5, 0.876 }, kLarge, kFaded },
|
||||
|
||||
{ { 0.144, 0.936 }, kSmall, kFaded },
|
||||
{ { 0.856, 0.936 }, kSmall, kFaded },
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
void PaintPoints(
|
||||
QPainter &p,
|
||||
const std::vector<PatternPoint> &points,
|
||||
base::flat_map<float64, QImage> &cache,
|
||||
not_null<Text::CustomEmoji*> emoji,
|
||||
const Data::UniqueGift &gift,
|
||||
const QRect &rect,
|
||||
float64 shown) {
|
||||
const auto origin = rect.topLeft();
|
||||
const auto width = rect.width();
|
||||
const auto height = rect.height();
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto paintPoint = [&](const PatternPoint &point) {
|
||||
const auto key = (1. + point.opacity) * 10. + point.scale;
|
||||
auto &image = cache[key];
|
||||
PrepareImage(image, emoji, point, gift);
|
||||
if (!image.isNull()) {
|
||||
const auto position = origin + QPoint(
|
||||
int(point.position.x() * width),
|
||||
int(point.position.y() * height));
|
||||
if (shown < 1.) {
|
||||
p.save();
|
||||
p.translate(position);
|
||||
p.scale(shown, shown);
|
||||
p.translate(-position);
|
||||
}
|
||||
const auto size = image.size() / ratio;
|
||||
p.drawImage(
|
||||
position - QPoint(size.width() / 2, size.height() / 2),
|
||||
image);
|
||||
if (shown < 1.) {
|
||||
p.restore();
|
||||
}
|
||||
}
|
||||
};
|
||||
for (const auto &point : points) {
|
||||
paintPoint(point);
|
||||
}
|
||||
}
|
||||
|
||||
void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args) {
|
||||
const auto weak = base::make_weak(args.controller);
|
||||
|
||||
@@ -87,23 +87,6 @@ void ShowUniqueGiftSellBox(
|
||||
|
||||
void GiftReleasedByHandler(not_null<PeerData*> peer);
|
||||
|
||||
struct PatternPoint {
|
||||
QPointF position;
|
||||
float64 scale = 1.;
|
||||
float64 opacity = 1.;
|
||||
};
|
||||
[[nodiscard]] const std::vector<PatternPoint> &PatternPoints();
|
||||
[[nodiscard]] const std::vector<PatternPoint> &PatternPointsSmall();
|
||||
|
||||
void PaintPoints(
|
||||
QPainter &p,
|
||||
const std::vector<PatternPoint> &points,
|
||||
base::flat_map<float64, QImage> &cache,
|
||||
not_null<Text::CustomEmoji*> emoji,
|
||||
const Data::UniqueGift &gift,
|
||||
const QRect &rect,
|
||||
float64 shown = 1.);
|
||||
|
||||
struct StarGiftUpgradeArgs {
|
||||
not_null<Window::SessionController*> controller;
|
||||
base::required<uint64> stargiftId;
|
||||
|
||||
@@ -46,13 +46,9 @@ class Panel::Incoming::RendererGL final : public Ui::GL::Renderer {
|
||||
public:
|
||||
explicit RendererGL(not_null<Incoming*> owner);
|
||||
|
||||
void init(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions &f) override;
|
||||
void init(QOpenGLFunctions &f) override;
|
||||
|
||||
void deinit(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions *f) override;
|
||||
void deinit(QOpenGLFunctions *f) override;
|
||||
|
||||
void paint(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
@@ -100,7 +96,7 @@ public:
|
||||
explicit RendererSW(not_null<Incoming*> owner);
|
||||
|
||||
void paintFallback(
|
||||
Painter &&p,
|
||||
Painter &p,
|
||||
const QRegion &clip,
|
||||
Ui::GL::Backend backend) override;
|
||||
|
||||
@@ -123,9 +119,7 @@ Panel::Incoming::RendererGL::RendererGL(not_null<Incoming*> owner)
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void Panel::Incoming::RendererGL::init(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions &f) {
|
||||
void Panel::Incoming::RendererGL::init(QOpenGLFunctions &f) {
|
||||
constexpr auto kQuads = 2;
|
||||
constexpr auto kQuadVertices = kQuads * 4;
|
||||
constexpr auto kQuadValues = kQuadVertices * 4;
|
||||
@@ -168,9 +162,7 @@ void Panel::Incoming::RendererGL::init(
|
||||
}));
|
||||
}
|
||||
|
||||
void Panel::Incoming::RendererGL::deinit(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions *f) {
|
||||
void Panel::Incoming::RendererGL::deinit(QOpenGLFunctions *f) {
|
||||
_textures.destroy(f);
|
||||
_imageProgram = std::nullopt;
|
||||
_texturedVertexShader = nullptr;
|
||||
@@ -437,7 +429,7 @@ Panel::Incoming::RendererSW::RendererSW(not_null<Incoming*> owner)
|
||||
}
|
||||
|
||||
void Panel::Incoming::RendererSW::paintFallback(
|
||||
Painter &&p,
|
||||
Painter &p,
|
||||
const QRegion &clip,
|
||||
Ui::GL::Backend backend) {
|
||||
const auto markGuard = gsl::finally([&] {
|
||||
|
||||
@@ -2483,7 +2483,8 @@ void Panel::updateButtonsGeometry() {
|
||||
}
|
||||
|
||||
const auto wideMenuShown = _call->canManage()
|
||||
|| _call->showChooseJoinAs();
|
||||
|| _call->showChooseJoinAs()
|
||||
|| (!rtmp && messagesEnabled); // Screen share there.
|
||||
toggle(_settings, !hidden && !wideMenuShown);
|
||||
toggle(_wideMenu, !hidden && wideMenuShown);
|
||||
|
||||
|
||||
@@ -312,9 +312,7 @@ Viewport::RendererGL::RendererGL(not_null<Viewport*> owner)
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void Viewport::RendererGL::init(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions &f) {
|
||||
void Viewport::RendererGL::init(QOpenGLFunctions &f) {
|
||||
_frameBuffer.emplace();
|
||||
_frameBuffer->setUsagePattern(QOpenGLBuffer::DynamicDraw);
|
||||
_frameBuffer->create();
|
||||
@@ -389,9 +387,7 @@ void Viewport::RendererGL::ensureARGB32Program() {
|
||||
}));
|
||||
}
|
||||
|
||||
void Viewport::RendererGL::deinit(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions *f) {
|
||||
void Viewport::RendererGL::deinit(QOpenGLFunctions *f) {
|
||||
_frameBuffer = std::nullopt;
|
||||
_frameVertexShader = nullptr;
|
||||
_imageProgram = std::nullopt;
|
||||
|
||||
@@ -28,13 +28,9 @@ class Viewport::RendererGL final : public Ui::GL::Renderer {
|
||||
public:
|
||||
explicit RendererGL(not_null<Viewport*> owner);
|
||||
|
||||
void init(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions &f) override;
|
||||
void init(QOpenGLFunctions &f) override;
|
||||
|
||||
void deinit(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions *f) override;
|
||||
void deinit(QOpenGLFunctions *f) override;
|
||||
|
||||
void paint(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
|
||||
@@ -37,7 +37,7 @@ Viewport::RendererSW::RendererSW(not_null<Viewport*> owner)
|
||||
}
|
||||
|
||||
void Viewport::RendererSW::paintFallback(
|
||||
Painter &&p,
|
||||
Painter &p,
|
||||
const QRegion &clip,
|
||||
Ui::GL::Backend backend) {
|
||||
auto bg = clip;
|
||||
|
||||
@@ -20,7 +20,7 @@ public:
|
||||
explicit RendererSW(not_null<Viewport*> owner);
|
||||
|
||||
void paintFallback(
|
||||
Painter &&p,
|
||||
Painter &p,
|
||||
const QRegion &clip,
|
||||
Ui::GL::Backend backend) override;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "calls/group/calls_volume_item.h"
|
||||
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "ui/color_int_conversion.h"
|
||||
#include "ui/effects/animation_value.h"
|
||||
#include "ui/effects/cross_line.h"
|
||||
#include "ui/widgets/continuous_sliders.h"
|
||||
@@ -254,17 +255,11 @@ void MenuVolumeItem::setSliderVolume(int volume) {
|
||||
|
||||
void MenuVolumeItem::updateSliderColor(float64 value) {
|
||||
value = std::clamp(value, 0., 1.);
|
||||
const auto color = [](int rgb) {
|
||||
return QColor(
|
||||
int((rgb & 0xFF0000) >> 16),
|
||||
int((rgb & 0x00FF00) >> 8),
|
||||
int(rgb & 0x0000FF));
|
||||
};
|
||||
const auto colors = std::array<QColor, 4>{ {
|
||||
color(0xF66464),
|
||||
color(0xD0B738),
|
||||
color(0x24CD80),
|
||||
color(0x3BBCEC),
|
||||
Ui::ColorFromSerialized(0xF66464),
|
||||
Ui::ColorFromSerialized(0xD0B738),
|
||||
Ui::ColorFromSerialized(0x24CD80),
|
||||
Ui::ColorFromSerialized(0x3BBCEC),
|
||||
} };
|
||||
_slider->setActiveFgOverride((value < 0.25)
|
||||
? anim::color(colors[0], colors[1], value / 0.25)
|
||||
|
||||
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_download_manager.h"
|
||||
#include "base/battery_saving.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/concurrent_timer.h"
|
||||
#include "base/options.h"
|
||||
#include "base/qt_signal_producer.h"
|
||||
@@ -85,6 +86,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/qthelp_regex.h"
|
||||
#include "base/qthelp_url.h"
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "ui/accessible/ui_accessible_factory.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/controls/location_picker.h"
|
||||
#include "styles/style_window.h"
|
||||
@@ -103,7 +105,6 @@ namespace {
|
||||
constexpr auto kQuitPreventTimeoutMs = crl::time(1500);
|
||||
constexpr auto kAutoLockTimeoutLateMs = crl::time(3000);
|
||||
constexpr auto kClearEmojiImageSourceTimeout = 10 * crl::time(1000);
|
||||
constexpr auto kFileOpenTimeoutMs = crl::time(1000);
|
||||
|
||||
LaunchState GlobalLaunchState/* = LaunchState::Running*/;
|
||||
|
||||
@@ -168,8 +169,7 @@ Application::Application()
|
||||
, _langCloudManager(std::make_unique<Lang::CloudManager>(langpack()))
|
||||
, _emojiKeywords(std::make_unique<ChatHelpers::EmojiKeywords>())
|
||||
, _tray(std::make_unique<Tray>())
|
||||
, _autoLockTimer([=] { checkAutoLock(); })
|
||||
, _fileOpenTimer([=] { checkFileOpen(); }) {
|
||||
, _autoLockTimer([=] { checkAutoLock(); }) {
|
||||
Ui::Integration::Set(&_private->uiIntegration);
|
||||
|
||||
_platformIntegration->init();
|
||||
@@ -287,6 +287,7 @@ void Application::run() {
|
||||
QCoreApplication::instance()->installTranslator(_translator.get());
|
||||
|
||||
style::StartManager(cScale());
|
||||
Ui::Accessible::Init();
|
||||
Ui::InitTextOptions();
|
||||
Ui::StartCachedCorners();
|
||||
Ui::Emoji::Init();
|
||||
@@ -692,24 +693,26 @@ bool Application::eventFilter(QObject *object, QEvent *e) {
|
||||
|
||||
case QEvent::FileOpen: {
|
||||
if (object == QCoreApplication::instance()) {
|
||||
const auto event = static_cast<QFileOpenEvent*>(e);
|
||||
if (const auto file = event->file(); !file.isEmpty()) {
|
||||
_filesToOpen.append(file);
|
||||
_fileOpenTimer.callOnce(kFileOpenTimeoutMs);
|
||||
} else if (event->url().scheme() == u"tg"_q
|
||||
|| event->url().scheme() == u"tonsite"_q) {
|
||||
const auto url = QString::fromUtf8(
|
||||
event->url().toEncoded().trimmed());
|
||||
cSetStartUrl(url.mid(0, 8192));
|
||||
checkStartUrl();
|
||||
if (_lastActivePrimaryWindow
|
||||
&& StartUrlRequiresActivate(url)) {
|
||||
_lastActivePrimaryWindow->activate();
|
||||
}
|
||||
} else if (event->url().scheme() == u"interpret"_q) {
|
||||
_filesToOpen.append(event->url().toString());
|
||||
_fileOpenTimer.callOnce(kFileOpenTimeoutMs);
|
||||
if (_urlsToOpen.isEmpty()) {
|
||||
InvokeQueued(this, [=] {
|
||||
const auto activateRequired = ranges::any_of(
|
||||
ranges::views::all(
|
||||
_urlsToOpen
|
||||
) | ranges::views::transform([](const QUrl &url) {
|
||||
return url.toString();
|
||||
}),
|
||||
StartUrlRequiresActivate);
|
||||
cRefStartUrls() << base::take(_urlsToOpen);
|
||||
checkStartUrls();
|
||||
if (_lastActivePrimaryWindow && activateRequired) {
|
||||
_lastActivePrimaryWindow->activate();
|
||||
}
|
||||
});
|
||||
}
|
||||
const auto event = static_cast<QFileOpenEvent*>(e);
|
||||
_urlsToOpen << event->url().toString(QUrl::FullyEncoded).mid(
|
||||
0,
|
||||
8192);
|
||||
}
|
||||
} break;
|
||||
|
||||
@@ -1081,37 +1084,27 @@ bool Application::canApplyLangPackWithoutRestart() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::checkFileOpen() {
|
||||
cSetSendPaths(_filesToOpen);
|
||||
_filesToOpen.clear();
|
||||
checkSendPaths();
|
||||
}
|
||||
|
||||
void Application::checkSendPaths() {
|
||||
if (!cSendPaths().isEmpty()
|
||||
void Application::checkStartUrls() {
|
||||
if (!Core::App().passcodeLocked()) {
|
||||
cRefStartUrls() = ranges::views::all(
|
||||
cRefStartUrls()
|
||||
) | ranges::views::filter([&](const QUrl &url) {
|
||||
if (url.scheme() == u"tonsite"_q) {
|
||||
iv().showTonSite(url.toString(), {});
|
||||
return false;
|
||||
} else if (_lastActivePrimaryWindow) {
|
||||
return !openLocalUrl(url.toString(), {});
|
||||
}
|
||||
return true;
|
||||
}) | ranges::to<QList<QUrl>>;
|
||||
}
|
||||
if (!cRefStartUrls().isEmpty()
|
||||
&& _lastActivePrimaryWindow
|
||||
&& !_lastActivePrimaryWindow->locked()) {
|
||||
_lastActivePrimaryWindow->widget()->sendPaths();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::checkStartUrl() {
|
||||
if (!cStartUrl().isEmpty()) {
|
||||
const auto url = cStartUrl();
|
||||
if (!Core::App().passcodeLocked()) {
|
||||
if (url.startsWith("tonsite://", Qt::CaseInsensitive)) {
|
||||
cSetStartUrl(QString());
|
||||
iv().showTonSite(url, {});
|
||||
} else if (_lastActivePrimaryWindow) {
|
||||
cSetStartUrl(QString());
|
||||
if (!openLocalUrl(url, {})) {
|
||||
cSetStartUrl(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Application::openLocalUrl(const QString &url, QVariant context) {
|
||||
return openCustomUrl("tg://", LocalUrlHandlers(), url, context);
|
||||
}
|
||||
|
||||
@@ -270,9 +270,7 @@ public:
|
||||
}
|
||||
|
||||
// Internal links.
|
||||
void checkStartUrl();
|
||||
void checkSendPaths();
|
||||
void checkFileOpen();
|
||||
void checkStartUrls();
|
||||
bool openLocalUrl(const QString &url, QVariant context);
|
||||
bool openInternalUrl(const QString &url, QVariant context);
|
||||
[[nodiscard]] QString changelogLink() const;
|
||||
@@ -451,8 +449,7 @@ private:
|
||||
crl::time _shouldLockAt = 0;
|
||||
base::Timer _autoLockTimer;
|
||||
|
||||
QStringList _filesToOpen;
|
||||
base::Timer _fileOpenTimer;
|
||||
QList<QUrl> _urlsToOpen;
|
||||
|
||||
std::optional<base::Timer> _saveSettingsTimer;
|
||||
|
||||
|
||||
@@ -536,15 +536,18 @@ void Launcher::processArguments() {
|
||||
{ "-tosettings" , KeyFormat::NoValues },
|
||||
{ "-startintray" , KeyFormat::NoValues },
|
||||
{ "-quit" , KeyFormat::NoValues },
|
||||
{ "-sendpath" , KeyFormat::AllLeftValues },
|
||||
{ "-workdir" , KeyFormat::OneValue },
|
||||
{ "--" , KeyFormat::OneValue },
|
||||
{ "--" , KeyFormat::AllLeftValues },
|
||||
{ "-scale" , KeyFormat::OneValue },
|
||||
};
|
||||
auto parseResult = QMap<QByteArray, QStringList>();
|
||||
auto parsingKey = QByteArray();
|
||||
auto parsingFormat = KeyFormat::NoValues;
|
||||
for (const auto &argument : std::as_const(_arguments)) {
|
||||
for (auto i = _arguments.cbegin(); i != _arguments.cend(); ++i) {
|
||||
if (i == _arguments.cbegin()) {
|
||||
continue;
|
||||
}
|
||||
const auto &argument = *i;
|
||||
switch (parsingFormat) {
|
||||
case KeyFormat::OneValue: {
|
||||
parseResult[parsingKey] = QStringList(argument.mid(0, 8192));
|
||||
@@ -559,7 +562,9 @@ void Launcher::processArguments() {
|
||||
if (it != parseMap.end()) {
|
||||
parsingFormat = it->second;
|
||||
parseResult[parsingKey] = QStringList();
|
||||
continue;
|
||||
}
|
||||
parseResult["--"].push_back(argument.mid(0, 8192));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
@@ -579,12 +584,15 @@ void Launcher::processArguments() {
|
||||
gStartToSettings = parseResult.contains("-tosettings");
|
||||
gStartInTray = parseResult.contains("-startintray");
|
||||
gQuit = parseResult.contains("-quit");
|
||||
gSendPaths = parseResult.value("-sendpath", {});
|
||||
_customWorkingDir = parseResult.value("-workdir", {}).join(QString());
|
||||
if (!_customWorkingDir.isEmpty()) {
|
||||
_customWorkingDir = QDir(_customWorkingDir).absolutePath() + '/';
|
||||
}
|
||||
gStartUrl = parseResult.value("--", {}).join(QString());
|
||||
|
||||
const auto startUrls = parseResult.value("--", {});
|
||||
gStartUrls = startUrls | ranges::views::transform([&](const QString &url) {
|
||||
return QUrl::fromUserInput(url, _initialWorkingDir);
|
||||
}) | ranges::views::filter(&QUrl::isValid) | ranges::to<QList<QUrl>>;
|
||||
|
||||
const auto scaleKey = parseResult.value("-scale", {});
|
||||
if (scaleKey.size() > 0) {
|
||||
|
||||
@@ -35,47 +35,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtGui/qpa/qplatformscreen.h>
|
||||
|
||||
namespace Core {
|
||||
namespace {
|
||||
|
||||
QChar _toHex(ushort v) {
|
||||
v = v & 0x000F;
|
||||
return QChar::fromLatin1((v >= 10) ? ('a' + (v - 10)) : ('0' + v));
|
||||
}
|
||||
ushort _fromHex(QChar c) {
|
||||
return ((c.unicode() >= uchar('a')) ? (c.unicode() - uchar('a') + 10) : (c.unicode() - uchar('0'))) & 0x000F;
|
||||
}
|
||||
|
||||
QString _escapeTo7bit(const QString &str) {
|
||||
QString result;
|
||||
result.reserve(str.size() * 2);
|
||||
for (int i = 0, l = str.size(); i != l; ++i) {
|
||||
QChar ch(str.at(i));
|
||||
ushort uch(ch.unicode());
|
||||
if (uch < 32 || uch > 127 || uch == ushort(uchar('%'))) {
|
||||
result.append('%').append(_toHex(uch >> 12)).append(_toHex(uch >> 8)).append(_toHex(uch >> 4)).append(_toHex(uch));
|
||||
} else {
|
||||
result.append(ch);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString _escapeFrom7bit(const QString &str) {
|
||||
QString result;
|
||||
result.reserve(str.size());
|
||||
for (int i = 0, l = str.size(); i != l; ++i) {
|
||||
QChar ch(str.at(i));
|
||||
if (ch == QChar::fromLatin1('%') && i + 4 < l) {
|
||||
result.append(QChar(ushort((_fromHex(str.at(i + 1)) << 12) | (_fromHex(str.at(i + 2)) << 8) | (_fromHex(str.at(i + 3)) << 4) | _fromHex(str.at(i + 4)))));
|
||||
i += 4;
|
||||
} else {
|
||||
result.append(ch);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Sandbox::QuitOnStartRequested = false;
|
||||
|
||||
@@ -277,18 +236,15 @@ void Sandbox::socketConnected() {
|
||||
_secondInstance = true;
|
||||
|
||||
QString commands;
|
||||
const QStringList &lst(cSendPaths());
|
||||
for (QStringList::const_iterator i = lst.cbegin(), e = lst.cend(); i != e; ++i) {
|
||||
commands += u"SEND:"_q + _escapeTo7bit(*i) + ';';
|
||||
}
|
||||
if (qEnvironmentVariableIsSet("XDG_ACTIVATION_TOKEN")) {
|
||||
commands += u"XDG_ACTIVATION_TOKEN:"_q + _escapeTo7bit(qEnvironmentVariable("XDG_ACTIVATION_TOKEN")) + ';';
|
||||
commands += u"XDG_ACTIVATION_TOKEN:"_q + qgetenv("XDG_ACTIVATION_TOKEN").toBase64() + ';';
|
||||
}
|
||||
if (!cStartUrl().isEmpty()) {
|
||||
commands += u"OPEN:"_q + _escapeTo7bit(cStartUrl()) + ';';
|
||||
} else if (cQuit()) {
|
||||
for (const auto &url : cRefStartUrls()) {
|
||||
commands += u"OPEN:"_q + url.toString(QUrl::FullyEncoded) + ';';
|
||||
}
|
||||
if (cQuit()) {
|
||||
commands += u"CMD:quit;"_q;
|
||||
} else {
|
||||
} else if (cRefStartUrls().isEmpty()) {
|
||||
commands += u"CMD:show;"_q;
|
||||
}
|
||||
|
||||
@@ -434,11 +390,11 @@ void Sandbox::newInstanceConnected() {
|
||||
|
||||
void Sandbox::readClients() {
|
||||
// This method can be called before Application is constructed.
|
||||
QString startUrl;
|
||||
QStringList toSend;
|
||||
QList<QUrl> startUrls;
|
||||
for (LocalClients::iterator i = _localClients.begin(), e = _localClients.end(); i != e; ++i) {
|
||||
i->second.append(i->first->readAll());
|
||||
if (i->second.size()) {
|
||||
bool activationRequired = false;
|
||||
QString cmds(QString::fromLatin1(i->second));
|
||||
int32 from = 0, l = cmds.length();
|
||||
for (int32 to = cmds.indexOf(QChar(';'), from); to >= from; to = (from < l) ? cmds.indexOf(QChar(';'), from) : -1) {
|
||||
@@ -448,21 +404,13 @@ void Sandbox::readClients() {
|
||||
const auto windowId = execExternal(cmds.mid(from + 4, to - from - 4));
|
||||
const auto response = u"RES:%1_%2;"_q.arg(processId).arg(windowId).toLatin1();
|
||||
i->first->write(response.data(), response.size());
|
||||
} else if (cmd.startsWith(u"SEND:"_q)) {
|
||||
if (cSendPaths().isEmpty()) {
|
||||
toSend.append(_escapeFrom7bit(cmds.mid(from + 5, to - from - 5)));
|
||||
}
|
||||
} else if (cmd.startsWith(u"XDG_ACTIVATION_TOKEN:"_q)) {
|
||||
qputenv("XDG_ACTIVATION_TOKEN", _escapeFrom7bit(cmds.mid(from + 21, to - from - 21)).toUtf8());
|
||||
qputenv("XDG_ACTIVATION_TOKEN", QByteArray::fromBase64(cmds.mid(from + 21, to - from - 21).toLatin1()));
|
||||
} else if (cmd.startsWith(u"OPEN:"_q)) {
|
||||
startUrl = _escapeFrom7bit(cmds.mid(from + 5, to - from - 5)).mid(0, 8192);
|
||||
const auto activationRequired = StartUrlRequiresActivate(startUrl);
|
||||
const auto processId = QApplication::applicationPid();
|
||||
const auto windowId = activationRequired
|
||||
? execExternal("show")
|
||||
: 0;
|
||||
const auto response = u"RES:%1_%2;"_q.arg(processId).arg(windowId).toLatin1();
|
||||
i->first->write(response.data(), response.size());
|
||||
startUrls.append(cmds.mid(from + 5, to - from - 5).mid(0, 8192));
|
||||
if (!activationRequired) {
|
||||
activationRequired = StartUrlRequiresActivate(startUrls.back().toString());
|
||||
}
|
||||
} else {
|
||||
LOG(("Sandbox Error: unknown command %1 passed in local socket").arg(cmd.toString()));
|
||||
}
|
||||
@@ -471,21 +419,17 @@ void Sandbox::readClients() {
|
||||
if (from > 0) {
|
||||
i->second = i->second.mid(from);
|
||||
}
|
||||
const auto processId = QApplication::applicationPid();
|
||||
const auto windowId = activationRequired
|
||||
? execExternal("show")
|
||||
: 0;
|
||||
const auto response = u"RES:%1_%2;"_q.arg(processId).arg(windowId).toLatin1();
|
||||
i->first->write(response.data(), response.size());
|
||||
}
|
||||
}
|
||||
if (!toSend.isEmpty()) {
|
||||
QStringList paths(cSendPaths());
|
||||
paths.append(toSend);
|
||||
cSetSendPaths(paths);
|
||||
}
|
||||
cRefStartUrls() << base::take(startUrls);
|
||||
if (_application) {
|
||||
_application->checkSendPaths();
|
||||
}
|
||||
if (!startUrl.isEmpty()) {
|
||||
cSetStartUrl(startUrl);
|
||||
}
|
||||
if (_application) {
|
||||
_application->checkStartUrl();
|
||||
_application->checkStartUrls();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -440,6 +440,18 @@ QString UiIntegration::phraseQuoteHeaderCopy() {
|
||||
return tr::lng_code_block_header_copy(tr::now);
|
||||
}
|
||||
|
||||
QString UiIntegration::phraseMinimize() {
|
||||
return tr::lng_minimize_window(tr::now);
|
||||
}
|
||||
|
||||
QString UiIntegration::phraseMaximize() {
|
||||
return tr::lng_maximize_window(tr::now);
|
||||
}
|
||||
|
||||
QString UiIntegration::phraseRestore() {
|
||||
return tr::lng_restore_window(tr::now);
|
||||
}
|
||||
|
||||
bool OpenGLLastCheckFailed() {
|
||||
return QFile::exists(OpenGLCheckFilePath());
|
||||
}
|
||||
|
||||
@@ -92,6 +92,9 @@ public:
|
||||
QString phraseBotAllowWriteTitle() override;
|
||||
QString phraseBotAllowWriteConfirm() override;
|
||||
QString phraseQuoteHeaderCopy() override;
|
||||
QString phraseMinimize() override;
|
||||
QString phraseMaximize() override;
|
||||
QString phraseRestore() override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 6002002;
|
||||
constexpr auto AppVersionStr = "6.2.2";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppVersion = 6002005;
|
||||
constexpr auto AppVersionStr = "6.2.5";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -7,18 +7,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/components/recent_shared_media_gifts.h"
|
||||
|
||||
#include "api/api_credits.h" // InputSavedStarGiftId
|
||||
#include "api/api_premium.h"
|
||||
#include "apiwrap.h"
|
||||
#include "chat_helpers/compose/compose_show.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kReloadThreshold = 60 * crl::time(1000);
|
||||
constexpr auto kMaxGifts = 3;
|
||||
constexpr auto kMaxPinnedGifts = 6;
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -29,19 +37,42 @@ RecentSharedMediaGifts::RecentSharedMediaGifts(
|
||||
|
||||
RecentSharedMediaGifts::~RecentSharedMediaGifts() = default;
|
||||
|
||||
std::vector<Data::SavedStarGift> RecentSharedMediaGifts::filterGifts(
|
||||
const std::deque<Data::SavedStarGift> &gifts,
|
||||
bool onlyPinnedToTop) {
|
||||
auto result = std::vector<Data::SavedStarGift>();
|
||||
const auto maxCount = onlyPinnedToTop ? kMaxPinnedGifts : kMaxGifts;
|
||||
for (const auto &gift : gifts) {
|
||||
if (!onlyPinnedToTop || gift.pinned) {
|
||||
result.push_back(gift);
|
||||
if (result.size() >= maxCount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void RecentSharedMediaGifts::request(
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void(std::vector<DocumentId>)> done) {
|
||||
Fn<void(std::vector<SavedStarGift>)> done,
|
||||
bool onlyPinnedToTop) {
|
||||
const auto it = _recent.find(peer->id);
|
||||
if (it != _recent.end()) {
|
||||
auto &entry = it->second;
|
||||
if (entry.lastRequestTime
|
||||
&& entry.lastRequestTime + kReloadThreshold > crl::now()) {
|
||||
done(std::vector<DocumentId>(entry.ids.begin(), entry.ids.end()));
|
||||
done(filterGifts(entry.gifts, onlyPinnedToTop));
|
||||
return;
|
||||
}
|
||||
if (entry.requestId) {
|
||||
peer->session().api().request(entry.requestId).cancel();
|
||||
entry.pendingCallbacks.push_back([=] {
|
||||
const auto it = _recent.find(peer->id);
|
||||
if (it != _recent.end()) {
|
||||
done(filterGifts(it->second.gifts, onlyPinnedToTop));
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +82,7 @@ void RecentSharedMediaGifts::request(
|
||||
peer->input,
|
||||
MTP_int(0), // collection_id
|
||||
MTP_string(QString()),
|
||||
MTP_int(kMaxGifts)
|
||||
MTP_int(kMaxPinnedGifts)
|
||||
)).done([=](const MTPpayments_SavedStarGifts &result) {
|
||||
const auto &data = result.data();
|
||||
const auto owner = &peer->owner();
|
||||
@@ -60,22 +91,144 @@ void RecentSharedMediaGifts::request(
|
||||
auto &entry = _recent[peer->id];
|
||||
entry.lastRequestTime = crl::now();
|
||||
entry.requestId = 0;
|
||||
entry.ids = {};
|
||||
entry.gifts.clear();
|
||||
|
||||
auto conter = 0;
|
||||
for (const auto &gift : data.vgifts().v) {
|
||||
if (auto parsed = Api::FromTL(peer, gift)) {
|
||||
entry.ids.push_back(parsed->info.document->id);
|
||||
if (entry.ids.size() > kMaxGifts) {
|
||||
entry.ids.pop_front();
|
||||
}
|
||||
if (++conter >= kMaxGifts) {
|
||||
break;
|
||||
}
|
||||
entry.gifts.push_back(std::move(*parsed));
|
||||
}
|
||||
}
|
||||
done(std::vector<DocumentId>(entry.ids.begin(), entry.ids.end()));
|
||||
|
||||
done(filterGifts(entry.gifts, onlyPinnedToTop));
|
||||
for (const auto &callback : entry.pendingCallbacks) {
|
||||
callback();
|
||||
}
|
||||
entry.pendingCallbacks.clear();
|
||||
}).send();
|
||||
}
|
||||
|
||||
void RecentSharedMediaGifts::clearLastRequestTime(
|
||||
not_null<PeerData*> peer) {
|
||||
const auto it = _recent.find(peer->id);
|
||||
if (it != _recent.end()) {
|
||||
it->second.lastRequestTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void RecentSharedMediaGifts::togglePinned(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
const Data::SavedStarGiftId &manageId,
|
||||
bool pinned,
|
||||
std::shared_ptr<Data::UniqueGift> uniqueData,
|
||||
std::shared_ptr<Data::UniqueGift> replacingData) {
|
||||
const auto performToggle = [=](const std::vector<SavedStarGift> &gifts) {
|
||||
const auto limit = _session->appConfig().pinnedGiftsLimit();
|
||||
auto manageIds = std::vector<Data::SavedStarGiftId>();
|
||||
|
||||
if (pinned) {
|
||||
manageIds.push_back(manageId);
|
||||
for (const auto &gift : gifts) {
|
||||
if (gift.pinned && gift.manageId != manageId) {
|
||||
manageIds.push_back(gift.manageId);
|
||||
if (manageIds.size() >= limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const auto &gift : gifts) {
|
||||
if (gift.pinned && gift.manageId != manageId) {
|
||||
manageIds.push_back(gift.manageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto inputs = QVector<MTPInputSavedStarGift>();
|
||||
inputs.reserve(manageIds.size());
|
||||
for (const auto &id : manageIds) {
|
||||
inputs.push_back(Api::InputSavedStarGiftId(id));
|
||||
}
|
||||
|
||||
_session->api().request(MTPpayments_ToggleStarGiftsPinnedToTop(
|
||||
peer->input,
|
||||
MTP_vector<MTPInputSavedStarGift>(std::move(inputs))
|
||||
)).done([=] {
|
||||
const auto updateLocal = [=] {
|
||||
using GiftAction = Data::GiftUpdate::Action;
|
||||
_session->data().notifyGiftUpdate({
|
||||
.id = manageId,
|
||||
.action = (pinned ? GiftAction::Pin : GiftAction::Unpin),
|
||||
});
|
||||
if (pinned) {
|
||||
show->showToast({
|
||||
.title = (uniqueData
|
||||
? tr::lng_gift_pinned_done_title(
|
||||
tr::now,
|
||||
lt_gift,
|
||||
Data::UniqueGiftName(*uniqueData))
|
||||
: QString()),
|
||||
.text = (replacingData
|
||||
? tr::lng_gift_pinned_done_replaced(
|
||||
tr::now,
|
||||
lt_gift,
|
||||
TextWithEntities{
|
||||
Data::UniqueGiftName(*replacingData),
|
||||
},
|
||||
Ui::Text::WithEntities)
|
||||
: tr::lng_gift_pinned_done(
|
||||
tr::now,
|
||||
Ui::Text::WithEntities)),
|
||||
.duration = Ui::Toast::kDefaultDuration * 2,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (!pinned) {
|
||||
auto result = std::deque<SavedStarGift>();
|
||||
for (const auto &id : manageIds) {
|
||||
for (const auto &gift : gifts) {
|
||||
if (gift.manageId == id) {
|
||||
result.push_back(gift);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_recent[peer->id].gifts = std::move(result);
|
||||
updateLocal();
|
||||
} else {
|
||||
_session->api().request(MTPpayments_GetSavedStarGift(
|
||||
MTP_vector<MTPInputSavedStarGift>(
|
||||
1,
|
||||
Api::InputSavedStarGiftId(manageId))
|
||||
)).done([=](const MTPpayments_SavedStarGifts &result) {
|
||||
const auto &tlGift = result.data().vgifts().v.front();
|
||||
if (auto parsed = Api::FromTL(peer, tlGift)) {
|
||||
auto result = std::deque<SavedStarGift>();
|
||||
for (const auto &id : manageIds) {
|
||||
if (parsed->manageId == id) {
|
||||
parsed->pinned = true;
|
||||
result.push_back(*parsed);
|
||||
continue;
|
||||
}
|
||||
for (const auto &gift : gifts) {
|
||||
if (gift.manageId == id) {
|
||||
result.push_back(gift);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_recent[peer->id].gifts = std::move(result);
|
||||
updateLocal();
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
show->showToast(error.type());
|
||||
}).send();
|
||||
};
|
||||
|
||||
request(peer, performToggle, true);
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -7,6 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_star_gift.h"
|
||||
|
||||
namespace ChatHelpers {
|
||||
class Show;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
@@ -20,13 +26,29 @@ public:
|
||||
|
||||
void request(
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void(std::vector<DocumentId>)> done);
|
||||
Fn<void(std::vector<Data::SavedStarGift>)> done,
|
||||
bool onlyPinnedToTop = false);
|
||||
|
||||
void clearLastRequestTime(not_null<PeerData*> peer);
|
||||
|
||||
void togglePinned(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
const Data::SavedStarGiftId &manageId,
|
||||
bool pinned,
|
||||
std::shared_ptr<Data::UniqueGift> uniqueData,
|
||||
std::shared_ptr<Data::UniqueGift> replacingData = nullptr);
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::vector<Data::SavedStarGift> filterGifts(
|
||||
const std::deque<SavedStarGift> &gifts,
|
||||
bool onlyPinnedToTop);
|
||||
|
||||
struct Entry {
|
||||
std::deque<DocumentId> ids;
|
||||
std::deque<SavedStarGift> gifts;
|
||||
crl::time lastRequestTime = 0;
|
||||
mtpRequestId requestId = 0;
|
||||
std::vector<Fn<void()>> pendingCallbacks;
|
||||
};
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
@@ -74,55 +74,56 @@ struct PeerUpdate {
|
||||
Usernames = (1ULL << 12),
|
||||
TranslationDisabled = (1ULL << 13),
|
||||
Color = (1ULL << 14),
|
||||
BackgroundEmoji = (1ULL << 15),
|
||||
StoriesState = (1ULL << 16),
|
||||
VerifyInfo = (1ULL << 17),
|
||||
StarsPerMessage = (1ULL << 18),
|
||||
ColorProfile = (1ULL << 15),
|
||||
BackgroundEmoji = (1ULL << 16),
|
||||
StoriesState = (1ULL << 17),
|
||||
VerifyInfo = (1ULL << 18),
|
||||
StarsPerMessage = (1ULL << 19),
|
||||
|
||||
// For users
|
||||
CanShareContact = (1ULL << 19),
|
||||
IsContact = (1ULL << 20),
|
||||
PhoneNumber = (1ULL << 21),
|
||||
OnlineStatus = (1ULL << 22),
|
||||
BotCommands = (1ULL << 23),
|
||||
BotCanBeInvited = (1ULL << 24),
|
||||
BotStartToken = (1ULL << 25),
|
||||
CommonChats = (1ULL << 26),
|
||||
PeerGifts = (1ULL << 27),
|
||||
HasCalls = (1ULL << 28),
|
||||
SupportInfo = (1ULL << 29),
|
||||
IsBot = (1ULL << 30),
|
||||
EmojiStatus = (1ULL << 31),
|
||||
BusinessDetails = (1ULL << 32),
|
||||
Birthday = (1ULL << 33),
|
||||
PersonalChannel = (1ULL << 34),
|
||||
StarRefProgram = (1ULL << 35),
|
||||
PaysPerMessage = (1ULL << 36),
|
||||
GiftSettings = (1ULL << 37),
|
||||
StarsRating = (1ULL << 38),
|
||||
ContactNote = (1ULL << 39),
|
||||
CanShareContact = (1ULL << 20),
|
||||
IsContact = (1ULL << 21),
|
||||
PhoneNumber = (1ULL << 22),
|
||||
OnlineStatus = (1ULL << 23),
|
||||
BotCommands = (1ULL << 24),
|
||||
BotCanBeInvited = (1ULL << 25),
|
||||
BotStartToken = (1ULL << 26),
|
||||
CommonChats = (1ULL << 27),
|
||||
PeerGifts = (1ULL << 28),
|
||||
HasCalls = (1ULL << 29),
|
||||
SupportInfo = (1ULL << 30),
|
||||
IsBot = (1ULL << 31),
|
||||
EmojiStatus = (1ULL << 32),
|
||||
BusinessDetails = (1ULL << 33),
|
||||
Birthday = (1ULL << 34),
|
||||
PersonalChannel = (1ULL << 35),
|
||||
StarRefProgram = (1ULL << 36),
|
||||
PaysPerMessage = (1ULL << 37),
|
||||
GiftSettings = (1ULL << 38),
|
||||
StarsRating = (1ULL << 39),
|
||||
ContactNote = (1ULL << 40),
|
||||
|
||||
// For chats and channels
|
||||
InviteLinks = (1ULL << 40),
|
||||
Members = (1ULL << 41),
|
||||
Admins = (1ULL << 42),
|
||||
BannedUsers = (1ULL << 43),
|
||||
Rights = (1ULL << 44),
|
||||
PendingRequests = (1ULL << 45),
|
||||
Reactions = (1ULL << 46),
|
||||
InviteLinks = (1ULL << 41),
|
||||
Members = (1ULL << 42),
|
||||
Admins = (1ULL << 43),
|
||||
BannedUsers = (1ULL << 44),
|
||||
Rights = (1ULL << 45),
|
||||
PendingRequests = (1ULL << 46),
|
||||
Reactions = (1ULL << 47),
|
||||
|
||||
// For channels
|
||||
ChannelAmIn = (1ULL << 47),
|
||||
StickersSet = (1ULL << 48),
|
||||
EmojiSet = (1ULL << 49),
|
||||
DiscussionLink = (1ULL << 50),
|
||||
MonoforumLink = (1ULL << 51),
|
||||
ChannelLocation = (1ULL << 52),
|
||||
Slowmode = (1ULL << 53),
|
||||
GroupCall = (1ULL << 54),
|
||||
ChannelAmIn = (1ULL << 48),
|
||||
StickersSet = (1ULL << 49),
|
||||
EmojiSet = (1ULL << 50),
|
||||
DiscussionLink = (1ULL << 51),
|
||||
MonoforumLink = (1ULL << 52),
|
||||
ChannelLocation = (1ULL << 53),
|
||||
Slowmode = (1ULL << 54),
|
||||
GroupCall = (1ULL << 55),
|
||||
|
||||
// For iteration
|
||||
LastUsedBit = (1ULL << 54),
|
||||
LastUsedBit = (1ULL << 55),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
||||
@@ -1117,9 +1117,10 @@ void DocumentData::save(
|
||||
if (!toFile.isEmpty()) {
|
||||
if (!media->bytes().isEmpty()) {
|
||||
QFile f(toFile);
|
||||
f.open(QIODevice::WriteOnly);
|
||||
f.write(media->bytes());
|
||||
f.close();
|
||||
if (f.open(QIODevice::WriteOnly)) {
|
||||
f.write(media->bytes());
|
||||
f.close();
|
||||
}
|
||||
|
||||
setLocation(Core::FileLocation(toFile));
|
||||
session().local().writeFileLocation(
|
||||
|
||||
@@ -1054,6 +1054,17 @@ bool PeerData::changeColor(
|
||||
return changed1 || changed2 || changed3;
|
||||
}
|
||||
|
||||
bool PeerData::changeColorProfile(
|
||||
const tl::conditional<MTPPeerColor> &cloudColor) {
|
||||
const auto changed1 = cloudColor
|
||||
? changeColorProfileIndex(Data::ColorIndexFromColor(cloudColor))
|
||||
: clearColorProfileIndex();
|
||||
const auto changed2 = changeProfileBackgroundEmojiId(
|
||||
Data::BackgroundEmojiIdFromColor(cloudColor));
|
||||
const auto changed3 = changeColorProfileCollectible(cloudColor);
|
||||
return changed1 || changed2 || changed3;
|
||||
}
|
||||
|
||||
void PeerData::fillNames() {
|
||||
_nameWords.clear();
|
||||
_nameFirstLetters.clear();
|
||||
@@ -1410,6 +1421,66 @@ bool PeerData::changeBackgroundEmojiId(DocumentId id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PeerData::changeColorProfileCollectible(Ui::ColorCollectible data) {
|
||||
if (!_colorProfileCollectible || (*_colorProfileCollectible != data)) {
|
||||
_colorProfileCollectible = std::make_shared<Ui::ColorCollectible>(
|
||||
std::move(data));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PeerData::changeColorProfileCollectible(
|
||||
const tl::conditional<MTPPeerColor> &cloudColor) {
|
||||
if (!cloudColor) {
|
||||
return clearColorProfileCollectible();
|
||||
}
|
||||
return cloudColor->match([&](const MTPDpeerColorCollectible &data) {
|
||||
return changeColorProfileCollectible(Data::ParseColorCollectible(data));
|
||||
}, [&](const MTPDpeerColor &) {
|
||||
return clearColorProfileCollectible();
|
||||
}, [&](const MTPDinputPeerColorCollectible &) {
|
||||
return clearColorProfileCollectible();
|
||||
});
|
||||
}
|
||||
|
||||
bool PeerData::clearColorProfileCollectible() {
|
||||
if (!_colorProfileCollectible) {
|
||||
return false;
|
||||
}
|
||||
_colorProfileCollectible = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PeerData::changeColorProfileIndex(uint8 index) {
|
||||
index %= Ui::kColorIndexCount;
|
||||
if (_colorProfileIndex == index) {
|
||||
return false;
|
||||
}
|
||||
_colorProfileIndex = index;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PeerData::clearColorProfileIndex() {
|
||||
if (!_colorProfileIndex.has_value()) {
|
||||
return false;
|
||||
}
|
||||
_colorProfileIndex = std::nullopt;
|
||||
return true;
|
||||
}
|
||||
|
||||
DocumentId PeerData::profileBackgroundEmojiId() const {
|
||||
return _profileBackgroundEmojiId;
|
||||
}
|
||||
|
||||
bool PeerData::changeProfileBackgroundEmojiId(DocumentId id) {
|
||||
if (_profileBackgroundEmojiId == id) {
|
||||
return false;
|
||||
}
|
||||
_profileBackgroundEmojiId = id;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PeerData::setEmojiStatus(const MTPEmojiStatus &status) {
|
||||
const auto parsed = owner().emojiStatuses().parse(status);
|
||||
setEmojiStatus(parsed.id, parsed.until);
|
||||
|
||||
@@ -229,6 +229,22 @@ public:
|
||||
[[nodiscard]] DocumentId backgroundEmojiId() const;
|
||||
bool changeBackgroundEmojiId(DocumentId id);
|
||||
|
||||
[[nodiscard]] std::optional<uint8> colorProfileIndex() const {
|
||||
return _colorProfileIndex;
|
||||
}
|
||||
[[nodiscard]] auto colorProfileCollectible() const
|
||||
-> const std::shared_ptr<Ui::ColorCollectible> & {
|
||||
return _colorProfileCollectible;
|
||||
}
|
||||
bool changeColorProfileCollectible(Ui::ColorCollectible data);
|
||||
bool changeColorProfileCollectible(
|
||||
const tl::conditional<MTPPeerColor> &cloudColor);
|
||||
bool clearColorProfileCollectible();
|
||||
bool changeColorProfileIndex(uint8 index);
|
||||
bool clearColorProfileIndex();
|
||||
[[nodiscard]] DocumentId profileBackgroundEmojiId() const;
|
||||
bool changeProfileBackgroundEmojiId(DocumentId id);
|
||||
|
||||
void setEmojiStatus(const MTPEmojiStatus &status);
|
||||
void setEmojiStatus(EmojiStatusId emojiStatusId, TimeId until = 0);
|
||||
[[nodiscard]] EmojiStatusId emojiStatusId() const;
|
||||
@@ -504,6 +520,7 @@ public:
|
||||
bool changeColorCollectible(
|
||||
const tl::conditional<MTPPeerColor> &cloudColor);
|
||||
bool changeColor(const tl::conditional<MTPPeerColor> &cloudColor);
|
||||
bool changeColorProfile(const tl::conditional<MTPPeerColor> &cloudColor);
|
||||
|
||||
enum class BlockStatus : char {
|
||||
Unknown,
|
||||
@@ -608,6 +625,7 @@ private:
|
||||
|
||||
EmojiStatusId _emojiStatusId;
|
||||
DocumentId _backgroundEmojiId = 0;
|
||||
DocumentId _profileBackgroundEmojiId = 0;
|
||||
crl::time _lastFullUpdate = 0;
|
||||
|
||||
QString _name;
|
||||
@@ -621,12 +639,14 @@ private:
|
||||
BarSettings _barSettings = PeerBarSettings(PeerBarSetting::Unknown);
|
||||
std::unique_ptr<PeerBarDetails> _barDetails;
|
||||
std::shared_ptr<Ui::ColorCollectible> _colorCollectible;
|
||||
std::shared_ptr<Ui::ColorCollectible> _colorProfileCollectible;
|
||||
|
||||
BlockStatus _blockStatus = BlockStatus::Unknown;
|
||||
LoadedStatus _loadedStatus = LoadedStatus::Not;
|
||||
TranslationFlag _translationFlag = TranslationFlag::Unknown;
|
||||
uint8 _colorIndex : 6 = 0;
|
||||
uint8 _colorIndexCloud : 1 = 0;
|
||||
std::optional<uint8> _colorProfileIndex;
|
||||
uint8 _userpicHasVideo : 1 = 0;
|
||||
|
||||
QString _about;
|
||||
|
||||
23
Telegram/SourceFiles/data/data_peer_colors.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Data {
|
||||
|
||||
struct ColorProfileSet {
|
||||
std::vector<QColor> palette;
|
||||
std::vector<QColor> bg;
|
||||
std::vector<QColor> story;
|
||||
};
|
||||
|
||||
struct ColorProfileData {
|
||||
ColorProfileSet light;
|
||||
ColorProfileSet dark;
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
@@ -23,9 +23,7 @@ class SendActionManager final {
|
||||
public:
|
||||
struct AnimationUpdate {
|
||||
not_null<Thread*> thread;
|
||||
int left = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
QRect rect;
|
||||
bool textUpdated = false;
|
||||
};
|
||||
explicit SendActionManager();
|
||||
|
||||
@@ -792,6 +792,9 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
|
||||
_peerDecorationsUpdated.fire_copy(result);
|
||||
}
|
||||
}
|
||||
if (result->changeColorProfile(data.vprofile_color())) {
|
||||
flags |= UpdateFlag::ColorProfile;
|
||||
}
|
||||
});
|
||||
|
||||
if (minimal) {
|
||||
@@ -1106,6 +1109,9 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
||||
_peerDecorationsUpdated.fire_copy(result);
|
||||
}
|
||||
}
|
||||
if (result->changeColorProfile(data.vprofile_color())) {
|
||||
flags |= UpdateFlag::ColorProfile;
|
||||
}
|
||||
}, [&](const MTPDchannelForbidden &data) {
|
||||
const auto channel = result->asChannel();
|
||||
|
||||
|
||||
@@ -458,7 +458,7 @@ void Entry::updateChatListEntryHeight() {
|
||||
session().changes().entryUpdated(this, Data::EntryUpdate::Flag::Height);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Entry::hasChatsFilterTags(FilterId exclude) const {
|
||||
bool Entry::hasChatsFilterTags(FilterId exclude) const {
|
||||
if (!owner().chatsFilters().tagsEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -336,10 +336,9 @@ InnerWidget::InnerWidget(
|
||||
) | rpl::start_with_next([=](
|
||||
const Data::SendActionManager::AnimationUpdate &update) {
|
||||
const auto updateRect = Ui::RowPainter::SendActionAnimationRect(
|
||||
_st,
|
||||
update.left,
|
||||
update.width,
|
||||
update.height,
|
||||
update.thread,
|
||||
_filterId,
|
||||
update.rect,
|
||||
width(),
|
||||
update.textUpdated);
|
||||
updateDialogRow(
|
||||
@@ -374,9 +373,6 @@ InnerWidget::InnerWidget(
|
||||
) | rpl::start_with_next([=](bool refreshHeight) {
|
||||
if (refreshHeight) {
|
||||
_chatsFilterTags.clear();
|
||||
}
|
||||
if (refreshHeight && _filterId) {
|
||||
// Height of the main list will be refreshed in other way.
|
||||
_shownList->updateHeights(_narrowRatio);
|
||||
}
|
||||
refreshWithCollapsedRows();
|
||||
@@ -527,14 +523,25 @@ InnerWidget::InnerWidget(
|
||||
) | rpl::start_with_next([=](
|
||||
RowDescriptor previous,
|
||||
RowDescriptor next) {
|
||||
updateDialogRow(previous);
|
||||
if (const auto sublist = previous.key.sublist()) {
|
||||
updateDialogRow({ { sublist->owningHistory() }, {} });
|
||||
}
|
||||
updateDialogRow(next);
|
||||
if (const auto sublist = next.key.sublist()) {
|
||||
updateDialogRow({ { sublist->owningHistory() }, {} });
|
||||
}
|
||||
const auto update = [&](const RowDescriptor &descriptor) {
|
||||
if (const auto topic = descriptor.key.topic()) {
|
||||
if (_openedForum == topic->forum()) {
|
||||
updateDialogRow(descriptor);
|
||||
} else {
|
||||
updateDialogRow({ { topic->owningHistory() }, {} });
|
||||
}
|
||||
} else if (const auto sublist = descriptor.key.sublist()) {
|
||||
if (_savedSublists == sublist->parent()) {
|
||||
updateDialogRow(descriptor);
|
||||
} else {
|
||||
updateDialogRow({ { sublist->owningHistory() }, {} });
|
||||
}
|
||||
} else {
|
||||
updateDialogRow(descriptor);
|
||||
}
|
||||
};
|
||||
update(previous);
|
||||
update(next);
|
||||
}, lifetime());
|
||||
|
||||
_controller->activeChatsFilter(
|
||||
|
||||
@@ -317,31 +317,32 @@ Row::~Row() {
|
||||
clearTopicJumpRipple();
|
||||
}
|
||||
|
||||
void Row::recountHeight(float64 narrowRatio, FilterId filterId) {
|
||||
if (const auto history = _id.history()) {
|
||||
const auto hasTags = _id.entry()->hasChatsFilterTags(filterId);
|
||||
const style::DialogRow &Row::ComputeSt(
|
||||
not_null<const Entry*> entry,
|
||||
FilterId filterId) {
|
||||
if (const auto history = entry->asHistory()) {
|
||||
const auto hasTags = entry->hasChatsFilterTags(filterId);
|
||||
const auto wideRow = history->isForum()
|
||||
|| history->amMonoforumAdmin();
|
||||
_height = wideRow
|
||||
? anim::interpolate(
|
||||
hasTags
|
||||
? st::taggedForumDialogRow.height
|
||||
: st::forumDialogRow.height,
|
||||
st::defaultDialogRow.height,
|
||||
narrowRatio)
|
||||
return wideRow
|
||||
? (hasTags ? st::taggedForumDialogRow : st::forumDialogRow)
|
||||
: hasTags
|
||||
? anim::interpolate(
|
||||
st::taggedDialogRow.height,
|
||||
st::defaultDialogRow.height,
|
||||
narrowRatio)
|
||||
: st::defaultDialogRow.height;
|
||||
} else if (_id.folder()) {
|
||||
_height = st::defaultDialogRow.height;
|
||||
} else if (_id.topic()) {
|
||||
_height = st::forumTopicRow.height;
|
||||
} else {
|
||||
_height = st::defaultDialogRow.height;
|
||||
? st::taggedDialogRow
|
||||
: st::defaultDialogRow;
|
||||
} else if (entry->asTopic()) {
|
||||
return st::forumTopicRow;
|
||||
}
|
||||
return st::defaultDialogRow;
|
||||
}
|
||||
|
||||
void Row::recountHeight(float64 narrowRatio, FilterId filterId) {
|
||||
const auto &st = ComputeSt(_id.entry(), filterId);
|
||||
_height = ((&st == &st::defaultDialogRow) || !_id.history())
|
||||
? st::defaultDialogRow.height
|
||||
: anim::interpolate(
|
||||
st.height,
|
||||
st::defaultDialogRow.height,
|
||||
narrowRatio);
|
||||
}
|
||||
|
||||
uint64 Row::sortKey(FilterId filterId) const {
|
||||
|
||||
@@ -87,6 +87,10 @@ public:
|
||||
Row(Key key, int index, int top);
|
||||
~Row();
|
||||
|
||||
[[nodiscard]] static const style::DialogRow &ComputeSt(
|
||||
not_null<const Entry*> entry,
|
||||
FilterId filterId);
|
||||
|
||||
[[nodiscard]] int top() const {
|
||||
return _top;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_thread.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "dialogs/dialogs_list.h"
|
||||
@@ -1198,20 +1199,20 @@ void RowPainter::Paint(
|
||||
}
|
||||
|
||||
QRect RowPainter::SendActionAnimationRect(
|
||||
not_null<const style::DialogRow*> st,
|
||||
int animationLeft,
|
||||
int animationWidth,
|
||||
int animationHeight,
|
||||
not_null<const Data::Thread*> thread,
|
||||
FilterId filterId,
|
||||
QRect rect,
|
||||
int fullWidth,
|
||||
bool textUpdated) {
|
||||
const auto nameleft = st->nameLeft;
|
||||
const auto namewidth = fullWidth - nameleft - st->padding.right();
|
||||
const auto texttop = st->textTop;
|
||||
const auto &st = Row::ComputeSt(thread, filterId);
|
||||
const auto nameleft = st.nameLeft;
|
||||
const auto namewidth = fullWidth - nameleft - st.padding.right();
|
||||
const auto texttop = st.textTop;
|
||||
return QRect(
|
||||
nameleft + (textUpdated ? 0 : animationLeft),
|
||||
texttop,
|
||||
textUpdated ? namewidth : animationWidth,
|
||||
animationHeight);
|
||||
nameleft + (textUpdated ? 0 : rect.x()),
|
||||
texttop + rect.y(),
|
||||
textUpdated ? namewidth : rect.width(),
|
||||
rect.height());
|
||||
}
|
||||
|
||||
void PaintCollapsedRow(
|
||||
|
||||
@@ -22,6 +22,7 @@ extern const style::DialogRow &defaultDialogRow;
|
||||
namespace Data {
|
||||
class Forum;
|
||||
class Folder;
|
||||
class Thread;
|
||||
} // namespace Data
|
||||
|
||||
namespace Dialogs {
|
||||
@@ -95,10 +96,9 @@ public:
|
||||
not_null<const FakeRow*> row,
|
||||
const PaintContext &context);
|
||||
static QRect SendActionAnimationRect(
|
||||
not_null<const style::DialogRow*> st,
|
||||
int animationLeft,
|
||||
int animationWidth,
|
||||
int animationHeight,
|
||||
not_null<const Data::Thread*> thread,
|
||||
FilterId filterId,
|
||||
QRect rect,
|
||||
int fullWidth,
|
||||
bool textUpdated);
|
||||
};
|
||||
|
||||
@@ -23,9 +23,9 @@ QString LocationClickHandler::copyToClipboardContextItemText() const {
|
||||
}
|
||||
|
||||
void LocationClickHandler::onClick(ClickContext context) const {
|
||||
if (!psLaunchMaps(_point)) {
|
||||
File::OpenUrl(_text);
|
||||
}
|
||||
Platform::LaunchMaps(_point, [text = _text] {
|
||||
File::OpenUrl(text);
|
||||
});
|
||||
}
|
||||
|
||||
void LocationClickHandler::setup() {
|
||||
|
||||
@@ -7148,6 +7148,11 @@ void HistoryWidget::startItemRevealAnimations() {
|
||||
|
||||
void HistoryWidget::startMessageSendingAnimation(
|
||||
not_null<HistoryItem*> item) {
|
||||
if (_list->elementChatMode() == HistoryView::ElementChatMode::Default
|
||||
&& width() > st::columnMaximalWidthLeft
|
||||
&& !item->media()) {
|
||||
return;
|
||||
}
|
||||
auto &sendingAnimation = controller()->sendingAnimation();
|
||||
if (!sendingAnimation.checkExpectedType(item)) {
|
||||
return;
|
||||
|
||||
@@ -76,6 +76,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_window.h" // columnMaximalWidthLeft
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtCore/QMimeData>
|
||||
@@ -2019,6 +2020,11 @@ void ListWidget::startItemRevealAnimations() {
|
||||
|
||||
void ListWidget::startMessageSendingAnimation(
|
||||
not_null<HistoryItem*> item) {
|
||||
if (elementChatMode() == HistoryView::ElementChatMode::Default
|
||||
&& width() > st::columnMaximalWidthLeft
|
||||
&& !item->media()) {
|
||||
return;
|
||||
}
|
||||
const auto sendingAnimation = _delegate->listSendingAnimation();
|
||||
if (!sendingAnimation || !sendingAnimation->checkExpectedType(item)) {
|
||||
return;
|
||||
|
||||
@@ -903,10 +903,12 @@ void Reply::paint(
|
||||
if (namew > 0) {
|
||||
p.setPen(!inBubble
|
||||
? st->msgImgReplyBarColor()->c
|
||||
: FromNameFg(
|
||||
: (colorCollectible || colorIndexPlusOne)
|
||||
? FromNameFg(
|
||||
context,
|
||||
colorIndexPlusOne - 1,
|
||||
colorCollectible));
|
||||
colorCollectible)
|
||||
: stm->msgServiceFg->c);
|
||||
_name.drawLeftElided(
|
||||
p,
|
||||
x + st::historyReplyPadding.left() + previewSkip,
|
||||
|
||||
@@ -387,14 +387,17 @@ bool SendActionPainter::updateNeedsAnimating(crl::time now, bool force) {
|
||||
if (force
|
||||
|| sendActionChanged
|
||||
|| (sendActionResult && !anim::Disabled())) {
|
||||
const auto height = std::max(
|
||||
st::normalFont->height,
|
||||
st::dialogsMiniPreviewTop + st::dialogsMiniPreview);
|
||||
const auto left = 0;
|
||||
const auto top = Ui::Emoji::GetCustomSkipNormal();
|
||||
const auto width = _sendActionAnimation.width() + _animationLeft;
|
||||
const auto height = std::max({
|
||||
st::normalFont->height - top,
|
||||
st::dialogsMiniPreviewTop + st::dialogsMiniPreview - top,
|
||||
Ui::Emoji::GetCustomSizeNormal(),
|
||||
});
|
||||
_history->peer->owner().sendActionManager().updateAnimation({
|
||||
_topic ? ((Data::Thread*)_topic) : _history,
|
||||
0,
|
||||
_sendActionAnimation.width() + _animationLeft,
|
||||
height,
|
||||
{ left, top, width, height },
|
||||
(force || sendActionChanged)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "boxes/background_preview_box.h"
|
||||
#include "boxes/star_gift_box.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
@@ -35,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/chat/chat_theme.h"
|
||||
#include "ui/cached_round_corners.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/top_background_gradient.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "window/section_widget.h"
|
||||
#include "window/window_session_controller.h"
|
||||
@@ -707,9 +707,9 @@ void GiftThemeBox::cacheUniqueBackground(int width, int height) {
|
||||
auto p = QPainter(&_backgroundCache);
|
||||
p.setClipRect(inner);
|
||||
const auto skip = inner.width() / 3;
|
||||
Ui::PaintPoints(
|
||||
Ui::PaintBgPoints(
|
||||
p,
|
||||
Ui::PatternPointsSmall(),
|
||||
Ui::PatternBgPointsSmall(),
|
||||
_patternCache,
|
||||
_patternEmoji.get(),
|
||||
*_data.unique,
|
||||
|
||||
@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/painter.h"
|
||||
#include "ui/power_saving.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/top_background_gradient.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_credits.h"
|
||||
@@ -476,9 +477,9 @@ auto UniqueGiftBg(
|
||||
const auto top = (webpreview ? 2 : 1) * (-shift);
|
||||
const auto outer = QRect(-shift, top, doubled, doubled);
|
||||
p.setClipRect(inner);
|
||||
Ui::PaintPoints(
|
||||
Ui::PaintBgPoints(
|
||||
p,
|
||||
Ui::PatternPoints(),
|
||||
Ui::PatternBgPoints(),
|
||||
state->cache,
|
||||
state->pattern.get(),
|
||||
*gift,
|
||||
|
||||
@@ -23,6 +23,7 @@ InfoToggle {
|
||||
|
||||
InfoPeerBadge {
|
||||
verified: icon;
|
||||
verifiedCheck: icon;
|
||||
premium: icon;
|
||||
premiumFg: color;
|
||||
position: point;
|
||||
@@ -344,6 +345,133 @@ infoLayerTopBar: InfoTopBar(infoTopBar) {
|
||||
|
||||
infoLayerTopBarMenuPosition: point(40px, 37px);
|
||||
|
||||
infoTopBarColoredBack: IconButton(infoTopBarBack) {
|
||||
icon: icon {{ "info/info_back", groupCallMembersFg }};
|
||||
iconOver: icon {{ "info/info_back", groupCallMembersFg }};
|
||||
ripple: universalRippleAnimation;
|
||||
}
|
||||
infoLayerTopBarColoredBack: IconButton(infoLayerTopBarBack) {
|
||||
icon: icon {{ "info/info_back", groupCallMembersFg }};
|
||||
iconOver: icon {{ "info/info_back", groupCallMembersFg }};
|
||||
ripple: universalRippleAnimation;
|
||||
}
|
||||
infoTopBarColoredClose: IconButton(infoTopBarClose) {
|
||||
icon: icon {{ "info/info_close", groupCallMembersFg }};
|
||||
iconOver: icon {{ "info/info_close", groupCallMembersFg }};
|
||||
ripple: universalRippleAnimation;
|
||||
}
|
||||
infoTopBarColoredMenu: IconButton(infoTopBarMenu) {
|
||||
icon: icon {{ "title_menu_dots", groupCallMembersFg }};
|
||||
iconOver: icon {{ "title_menu_dots", groupCallMembersFg }};
|
||||
ripple: universalRippleAnimation;
|
||||
}
|
||||
infoLayerTopBarColoredMenu: IconButton(infoLayerTopBarMenu) {
|
||||
icon: icon {{ "title_menu_dots", groupCallMembersFg }};
|
||||
iconOver: icon {{ "title_menu_dots", groupCallMembersFg }};
|
||||
ripple: universalRippleAnimation;
|
||||
}
|
||||
infoTopBarColoredEdit: IconButton(infoTopBarEdit) {
|
||||
icon: icon {{ "menu/edit", groupCallMembersFg }};
|
||||
iconOver: icon {{ "menu/edit", groupCallMembersFg }};
|
||||
ripple: universalRippleAnimation;
|
||||
}
|
||||
infoLayerTopBarColoredEdit: IconButton(infoLayerTopBarEdit) {
|
||||
icon: icon {{ "menu/edit", groupCallMembersFg }};
|
||||
iconOver: icon {{ "menu/edit", groupCallMembersFg }};
|
||||
ripple: universalRippleAnimation;
|
||||
}
|
||||
infoTopBarBlackBack: IconButton(infoTopBarBack) {
|
||||
icon: icon {{ "info/info_back", windowBoldFg }};
|
||||
iconOver: icon {{ "info/info_back", windowBoldFg }};
|
||||
ripple: universalRippleAnimation;
|
||||
}
|
||||
infoLayerTopBarBlackBack: IconButton(infoLayerTopBarBack) {
|
||||
icon: icon {{ "info/info_back", windowBoldFg }};
|
||||
iconOver: icon {{ "info/info_back", windowBoldFg }};
|
||||
ripple: universalRippleAnimation;
|
||||
}
|
||||
infoTopBarBlackClose: IconButton(infoTopBarClose) {
|
||||
icon: icon {{ "info/info_close", windowBoldFg }};
|
||||
iconOver: icon {{ "info/info_close", windowBoldFg }};
|
||||
ripple: universalRippleAnimation;
|
||||
}
|
||||
infoTopBarBlackEdit: IconButton(infoTopBarEdit) {
|
||||
icon: icon {{ "menu/edit", windowBoldFg }};
|
||||
iconOver: icon {{ "menu/edit", windowBoldFg }};
|
||||
ripple: universalRippleAnimation;
|
||||
}
|
||||
infoLayerTopBarBlackEdit: IconButton(infoLayerTopBarEdit) {
|
||||
icon: icon {{ "menu/edit", windowBoldFg }};
|
||||
iconOver: icon {{ "menu/edit", windowBoldFg }};
|
||||
ripple: universalRippleAnimation;
|
||||
}
|
||||
|
||||
infoProfileTopBarHeightMax: 180px + infoLayerTopBarHeight;
|
||||
infoProfileTopBarTitleTop: 113px;
|
||||
infoProfileTopBarStatusTop: 134px;
|
||||
infoProfileTopBarPhotoTop: 24px;
|
||||
infoProfileTopBarPhotoBgShift: 55px;
|
||||
infoProfileTopBarPhotoBgNoActionsShift: 30px;
|
||||
infoProfileTopBarPhotoSize: 80px;
|
||||
infoProfileTopBarLastSeenSkip: 8px;
|
||||
|
||||
infoProfileTopBarGiftSize: 20px;
|
||||
|
||||
infoProfileTopBarGiftLeft: point(15px, 13px);
|
||||
infoProfileTopBarGiftTopLeft: point(4px, 4px);
|
||||
infoProfileTopBarGiftBottomLeft: point(9px, 16px);
|
||||
infoProfileTopBarGiftRight: point(50px, 13px);
|
||||
infoProfileTopBarGiftTopRight: point(30px, 4px);
|
||||
infoProfileTopBarGiftBottomRight: point(40px, 13px);
|
||||
|
||||
infoProfileTopBarActionButtonBgOpacity: 0.16;
|
||||
infoProfileTopBarActionButtonSize: 52px;
|
||||
infoProfileTopBarActionButtonIconSize: 24px;
|
||||
infoProfileTopBarActionButtonLottieSize: infoProfileTopBarActionButtonIconSize - 2px;
|
||||
infoProfileTopBarActionButtonIconTop: 6px;
|
||||
infoProfileTopBarActionButtonTextTop: infoProfileTopBarActionButtonIconSize + infoProfileTopBarActionButtonIconTop + 1px;
|
||||
infoProfileTopBarActionButtonTextSkip: 4px;
|
||||
infoProfileTopBarActionButtonFont: font(11px);
|
||||
|
||||
infoProfileTopBarActionButtonsPadding: margins(18px, 0px, 18px, 16px);
|
||||
infoProfileTopBarActionButtonsHeight: infoProfileTopBarActionButtonSize + infoProfileTopBarActionButtonsPadding.topBottom;
|
||||
infoProfileTopBarActionButtonsSpace: 10px;
|
||||
|
||||
infoProfileTopBarStep2: infoProfileTopBarHeightMax - infoLayerTopBarHeight;
|
||||
infoProfileTopBarStep1: infoProfileTopBarStep2 - infoProfileTopBarActionButtonsHeight;
|
||||
|
||||
infoProfileTopBarNoActionsHeightMax: infoProfileTopBarHeightMax - infoProfileTopBarActionButtonSize;
|
||||
|
||||
infoProfileTopBarActionMenuSkip: 10px;
|
||||
|
||||
infoProfileTopBarActionMessage: icon{{ "profile/message-22x22", windowBoldFg }};
|
||||
infoProfileTopBarActionMute: icon{{ "profile/mute-22x22", windowBoldFg }};
|
||||
infoProfileTopBarActionUnmute: icon{{ "profile/unmute-22x22", windowBoldFg }};
|
||||
infoProfileTopBarActionCall: icon{{ "profile/call-22x22", windowBoldFg }};
|
||||
infoProfileTopBarActionGift: icon{{ "profile/gift-22x22", windowBoldFg }};
|
||||
infoProfileTopBarActionJoin: icon{{ "profile/join-22x22", windowBoldFg }};
|
||||
infoProfileTopBarActionReport: icon{{ "profile/report-22x22", windowBoldFg }};
|
||||
infoProfileTopBarActionLeave: icon{{ "profile/leave-22x22", windowBoldFg }};
|
||||
infoProfileTopBarActionMore: icon{{ "profile/profile_more", windowBoldFg }};
|
||||
|
||||
infoProfileTopBarBoostFooter: FlatLabel(defaultFlatLabel) {
|
||||
textFg: windowSubTextFg;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(11px);
|
||||
}
|
||||
palette: TextPalette(defaultTextPalette) {
|
||||
linkFg: windowBoldFg;
|
||||
selectLinkFg: windowBoldFg;
|
||||
}
|
||||
}
|
||||
infoProfileTopBarBoostFooterColored: FlatLabel(infoProfileTopBarBoostFooter) {
|
||||
textFg: groupCallVideoSubTextFg;
|
||||
palette: TextPalette(defaultTextPalette) {
|
||||
linkFg: groupCallIconFg;
|
||||
selectLinkFg: groupCallIconFg;
|
||||
}
|
||||
}
|
||||
|
||||
infoMinimalWidth: 324px;
|
||||
infoDesiredWidth: 392px;
|
||||
infoMinimalLayerMargin: 48px;
|
||||
@@ -445,14 +573,12 @@ infoRatingDeductedBadge: RoundButton(customEmojiTextBadge) {
|
||||
infoProfileInaccessibleUserpic: icon {{ "info/inaccessible_userpic", historyPeerUserpicFg }};
|
||||
|
||||
infoVerifiedCheckPosition: point(4px, 2px);
|
||||
infoVerifiedCheck: icon {
|
||||
{ "profile_verified_star", profileVerifiedCheckBg },
|
||||
{ "profile_verified_check", profileVerifiedCheckFg }
|
||||
};
|
||||
infoVerifiedStar: icon {{ "profile_verified_star", profileVerifiedCheckBg }};
|
||||
infoPremiumStar: icon {{ "profile_premium", profileVerifiedCheckBg }};
|
||||
|
||||
infoPeerBadge: InfoPeerBadge {
|
||||
verified: infoVerifiedCheck;
|
||||
verified: infoVerifiedStar;
|
||||
verifiedCheck: icon {{ "profile_verified_check", profileVerifiedCheckFg }};
|
||||
premium: infoPremiumStar;
|
||||
premiumFg: profileVerifiedCheckBg;
|
||||
position: infoVerifiedCheckPosition;
|
||||
@@ -461,18 +587,23 @@ infoPeerBadge: InfoPeerBadge {
|
||||
infoBotVerifyBadge: InfoPeerBadge(infoPeerBadge) {
|
||||
position: point(-2px, 2px);
|
||||
}
|
||||
infoColoredPeerBadge: InfoPeerBadge(infoPeerBadge) {
|
||||
verified: icon {{ "profile_verified_star", groupCallMembersFg }}; // Will be colorized.
|
||||
verifiedCheck: icon {{ "profile_verified_check", groupCallMembersFg }};
|
||||
premium: icon {{ "profile_premium", groupCallMembersFg }};
|
||||
premiumFg: groupCallMembersFg;
|
||||
}
|
||||
infoColoredBotVerifyBadge: InfoPeerBadge(infoColoredPeerBadge) {
|
||||
position: point(-2px, 2px);
|
||||
}
|
||||
|
||||
infoIconFg: windowBoldFg;
|
||||
|
||||
infoProfileSkip: 7px;
|
||||
|
||||
infoProfileLabeledPadding: margins(79px, 9px, 30px, 7px);
|
||||
infoProfileLabeledUsernamePadding: margins(79px, 9px, 20px, 7px);
|
||||
infoProfileSeparatorPadding: margins(
|
||||
77px,
|
||||
infoProfileSkip,
|
||||
0px,
|
||||
infoProfileSkip);
|
||||
infoProfileLabeledPadding: margins(23px, 9px, 20px, 7px);
|
||||
infoProfileLabeledUsernamePadding: margins(23px, 9px, 20px, 7px);
|
||||
infoProfilePersonalChannelPadding: margins(79px, 9px, 20px, 7px);
|
||||
infoProfileLabeledButtonQr: IconButton(defaultIconButton) {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
@@ -482,7 +613,7 @@ infoProfileLabeledButtonQr: IconButton(defaultIconButton) {
|
||||
rippleAreaSize: 34px;
|
||||
ripple: defaultRippleAnimation;
|
||||
}
|
||||
infoProfileLabeledButtonQrRightSkip: 10px;
|
||||
infoProfileLabeledButtonQrRightSkip: -4px;
|
||||
|
||||
infoIconInformation: icon {{ "info/info_information", infoIconFg }};
|
||||
infoIconAddMember: icon {{ "info/info_add_member", infoIconFg }};
|
||||
@@ -534,7 +665,7 @@ infoPersonalChannelNameLabel: FlatLabel(infoProfileStatus) {
|
||||
style: semiboldTextStyle;
|
||||
maxHeight: 20px;
|
||||
}
|
||||
infoPersonalChannelDateSkip: 22px;
|
||||
infoPersonalChannelDateSkip: 20px;
|
||||
infoPersonalChannelDateLabel: FlatLabel(infoProfileStatus) {
|
||||
textFg: dialogsDateFg;
|
||||
style: TextStyle(semiboldTextStyle) {
|
||||
@@ -542,7 +673,7 @@ infoPersonalChannelDateLabel: FlatLabel(infoProfileStatus) {
|
||||
}
|
||||
maxHeight: 20px;
|
||||
}
|
||||
infoPersonalChannelUserpicSkip: 3px;
|
||||
infoPersonalChannelUserpicSkip: -5px;
|
||||
infoPersonalChannelUserpic: UserpicButton(defaultUserpicButton) {
|
||||
size: size(42px, 42px);
|
||||
photoSize: 42px;
|
||||
@@ -572,13 +703,11 @@ infoProfileButton: SettingsButton(defaultSettingsButton) {
|
||||
toggleOver: infoProfileToggleOver;
|
||||
toggleSkip: 20px;
|
||||
}
|
||||
infoNotificationsButton: SettingsButton(infoProfileButton) {
|
||||
padding: margins(79px, 13px, 8px, 9px);
|
||||
}
|
||||
infoMainButton: SettingsButton(infoProfileButton) {
|
||||
textFg: lightButtonFg;
|
||||
textFgOver: lightButtonFgOver;
|
||||
style: semiboldTextStyle;
|
||||
padding: margins(23px, 10px, 8px, 8px);
|
||||
}
|
||||
infoMainButtonAttention: SettingsButton(infoMainButton) {
|
||||
textFg: attentionButtonFg;
|
||||
@@ -1294,16 +1423,5 @@ collectionSubTabs: SubTabs(defaultSubTabs) {
|
||||
earnTonIconSize: 16px;
|
||||
earnTonIconMargin: margins(0px, 2px, 0px, 0px);
|
||||
|
||||
infoMusicButtonRipple: defaultRippleAnimation;
|
||||
infoMusicButtonPerformer: FlatLabel(defaultFlatLabel) {
|
||||
textFg: windowSubTextFg;
|
||||
maxHeight: 20px;
|
||||
}
|
||||
infoMusicButtonTitle: FlatLabel(defaultFlatLabel) {
|
||||
textFg: windowBoldFg;
|
||||
style: semiboldTextStyle;
|
||||
maxHeight: 20px;
|
||||
}
|
||||
infoMusicButtonPadding: margins(48px, 6px, 12px, 6px);
|
||||
infoMusicButtonBottom: 8px;
|
||||
infoMusicButtonLine: 2px;
|
||||
infoMusicButtonRipple: universalRippleAnimation;
|
||||
infoMusicButtonPadding: margins(16px, 6px, 13px, 6px);
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "info/info_flexible_scroll.h"
|
||||
#include "info/info_wrap_widget.h"
|
||||
#include "info/statistics/info_statistics_tag.h"
|
||||
#include "ui/controls/swipe_handler_data.h"
|
||||
@@ -154,6 +155,42 @@ protected:
|
||||
doSetInnerWidget(std::move(inner)));
|
||||
}
|
||||
|
||||
template <typename Widget, typename FlexibleData>
|
||||
Widget *setupFlexibleInnerWidget(
|
||||
object_ptr<Widget> inner,
|
||||
FlexibleData &flexibleScroll,
|
||||
Fn<void(Ui::RpWidget*)> customSetup = nullptr) {
|
||||
if (inner->hasFlexibleTopBar()) {
|
||||
auto filler = setInnerWidget(object_ptr<Ui::RpWidget>(this));
|
||||
filler->resize(1, 1);
|
||||
|
||||
flexibleScroll.contentHeightValue.events(
|
||||
) | rpl::start_with_next([=](int h) {
|
||||
filler->resize(filler->width(), h);
|
||||
}, filler->lifetime());
|
||||
|
||||
filler->widthValue(
|
||||
) | rpl::start_to_stream(
|
||||
flexibleScroll.fillerWidthValue,
|
||||
lifetime());
|
||||
|
||||
if (customSetup) {
|
||||
customSetup(filler);
|
||||
}
|
||||
|
||||
// ScrollArea -> PaddingWrap -> RpWidget.
|
||||
inner->setParent(filler->parentWidget()->parentWidget());
|
||||
inner->raise();
|
||||
|
||||
using InnerPtr = base::unique_qptr<Widget>;
|
||||
auto owner = filler->lifetime().make_state<InnerPtr>(
|
||||
std::move(inner.release()));
|
||||
return owner->get();
|
||||
} else {
|
||||
return setInnerWidget(std::move(inner));
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<Controller*> controller() const {
|
||||
return _controller;
|
||||
}
|
||||
|
||||
198
Telegram/SourceFiles/info/info_flexible_scroll.cpp
Normal file
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
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/info_flexible_scroll.h"
|
||||
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "styles/style_info.h"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QScrollBar>
|
||||
|
||||
namespace Info {
|
||||
|
||||
FlexibleScrollHelper::FlexibleScrollHelper(
|
||||
not_null<Ui::ScrollArea*> scroll,
|
||||
not_null<Ui::RpWidget*> inner,
|
||||
not_null<Ui::RpWidget*> pinnedToTop,
|
||||
Fn<void(QMargins)> setPaintPadding,
|
||||
Fn<void(rpl::producer<not_null<QEvent*>>&&)> setViewport,
|
||||
FlexibleScrollData &data)
|
||||
: _scroll(scroll)
|
||||
, _inner(inner)
|
||||
, _pinnedToTop(pinnedToTop)
|
||||
, _setPaintPadding(setPaintPadding)
|
||||
, _setViewport(setViewport)
|
||||
, _data(data) {
|
||||
setupScrollAnimation();
|
||||
setupScrollHandling();
|
||||
}
|
||||
|
||||
void FlexibleScrollHelper::setupScrollAnimation() {
|
||||
constexpr auto kScrollStepTime = crl::time(260);
|
||||
|
||||
const auto clearScrollState = [=] {
|
||||
_scrollAnimation.stop();
|
||||
_scrollTopFrom = 0;
|
||||
_scrollTopTo = 0;
|
||||
_timeOffset = 0;
|
||||
_lastScrollApplied = 0;
|
||||
};
|
||||
|
||||
_scrollAnimation.init([=](crl::time now) {
|
||||
const auto progress = float64(now
|
||||
- _scrollAnimation.started()
|
||||
- _timeOffset) / kScrollStepTime;
|
||||
const auto eased = anim::easeOutQuint(1.0, progress);
|
||||
const auto scrollCurrent = anim::interpolate(
|
||||
_scrollTopFrom,
|
||||
_scrollTopTo,
|
||||
std::clamp(eased, 0., 1.));
|
||||
_scroll->scrollToY(scrollCurrent);
|
||||
_lastScrollApplied = scrollCurrent;
|
||||
if (progress >= 1) {
|
||||
clearScrollState();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void FlexibleScrollHelper::setupScrollHandling() {
|
||||
const auto heightDiff = [=] {
|
||||
return _pinnedToTop->maximumHeight()
|
||||
- _pinnedToTop->minimumHeight();
|
||||
};
|
||||
|
||||
rpl::combine(
|
||||
_pinnedToTop->heightValue(),
|
||||
_inner->heightValue()
|
||||
) | rpl::start_with_next([=](int, int h) {
|
||||
_data.contentHeightValue.fire(h + heightDiff());
|
||||
}, _pinnedToTop->lifetime());
|
||||
|
||||
const auto singleStep = _scroll->verticalScrollBar()->singleStep()
|
||||
* QApplication::wheelScrollLines();
|
||||
const auto step1 = (_pinnedToTop->maximumHeight()
|
||||
< st::infoProfileTopBarHeightMax)
|
||||
? (st::infoProfileTopBarStep2 + st::lineWidth)
|
||||
: st::infoProfileTopBarStep1;
|
||||
const auto step2 = st::infoProfileTopBarStep2;
|
||||
// const auto stepDepreciation = singleStep
|
||||
// - st::infoProfileTopBarActionButtonsHeight;
|
||||
_scrollTopPrevious = _scroll->scrollTop();
|
||||
|
||||
_scroll->scrollTopValue(
|
||||
) | rpl::start_with_next([=](int top) {
|
||||
if (_applyingFakeScrollState) {
|
||||
return;
|
||||
}
|
||||
const auto diff = top - _scrollTopPrevious;
|
||||
if (std::abs(diff) == singleStep) {
|
||||
const auto previousValue = top - diff;
|
||||
const auto nextStep = (diff > 0)
|
||||
? ((previousValue == 0)
|
||||
? step1
|
||||
: (previousValue == step1)
|
||||
? step2
|
||||
: -1)
|
||||
// : ((top < step1
|
||||
// && (top + stepDepreciation != step1
|
||||
// || _scrollAnimation.animating()))
|
||||
: ((top < step1)
|
||||
? 0
|
||||
: (top < step2)
|
||||
? step1
|
||||
: -1);
|
||||
{
|
||||
_applyingFakeScrollState = true;
|
||||
_scroll->scrollToY(previousValue);
|
||||
_applyingFakeScrollState = false;
|
||||
}
|
||||
if (_scrollAnimation.animating()
|
||||
&& ((_scrollTopTo > _scrollTopFrom) != (diff > 0))) {
|
||||
auto overriddenDirection = true;
|
||||
if (_scrollTopTo > _scrollTopFrom) {
|
||||
// From going down to going up.
|
||||
if (_scrollTopTo == step1) {
|
||||
_scrollTopTo = 0;
|
||||
} else if (_scrollTopTo == step2) {
|
||||
_scrollTopTo = step1;
|
||||
} else {
|
||||
overriddenDirection = false;
|
||||
}
|
||||
} else {
|
||||
// From going up to going down.
|
||||
if (_scrollTopTo == 0) {
|
||||
_scrollTopTo = step1;
|
||||
} else if (_scrollTopTo == step1) {
|
||||
_scrollTopTo = step2;
|
||||
} else {
|
||||
overriddenDirection = false;
|
||||
}
|
||||
}
|
||||
if (overriddenDirection) {
|
||||
_timeOffset = crl::now() - _scrollAnimation.started();
|
||||
_scrollTopFrom = _lastScrollApplied
|
||||
? _lastScrollApplied
|
||||
: previousValue;
|
||||
return;
|
||||
} else {
|
||||
_scrollAnimation.stop();
|
||||
_scrollTopFrom = 0;
|
||||
_scrollTopTo = 0;
|
||||
_timeOffset = 0;
|
||||
_lastScrollApplied = 0;
|
||||
}
|
||||
}
|
||||
_scrollTopFrom = _lastScrollApplied
|
||||
? _lastScrollApplied
|
||||
: previousValue;
|
||||
if (!_scrollAnimation.animating()) {
|
||||
_scrollTopTo = ((nextStep != -1) ? nextStep : top);
|
||||
_scrollAnimation.start();
|
||||
} else {
|
||||
if (_scrollTopTo > _scrollTopFrom) {
|
||||
// Down.
|
||||
if (_scrollTopTo == step1) {
|
||||
_scrollTopTo = step2;
|
||||
} else {
|
||||
_scrollTopTo += diff;
|
||||
}
|
||||
} else {
|
||||
// Up.
|
||||
if (_scrollTopTo == step2) {
|
||||
_scrollTopTo = step1;
|
||||
} else if (_scrollTopTo == step1) {
|
||||
_scrollTopTo = 0;
|
||||
} else {
|
||||
_scrollTopTo += diff;
|
||||
}
|
||||
}
|
||||
_timeOffset = (crl::now() - _scrollAnimation.started());
|
||||
}
|
||||
return;
|
||||
}
|
||||
_scrollTopPrevious = top;
|
||||
const auto current = heightDiff() - top;
|
||||
_inner->moveToLeft(0, std::min(0, current));
|
||||
_pinnedToTop->resize(
|
||||
_pinnedToTop->width(),
|
||||
std::max(current + _pinnedToTop->minimumHeight(), 0));
|
||||
}, _inner->lifetime());
|
||||
|
||||
_data.fillerWidthValue.events(
|
||||
) | rpl::start_with_next([=](int w) {
|
||||
_inner->resizeToWidth(w);
|
||||
}, _inner->lifetime());
|
||||
|
||||
_setPaintPadding({ 0, _pinnedToTop->minimumHeight(), 0, 0 });
|
||||
_setViewport(_pinnedToTop->events(
|
||||
) | rpl::filter([](not_null<QEvent*> e) {
|
||||
return e->type() == QEvent::Wheel;
|
||||
}));
|
||||
}
|
||||
|
||||
} // namespace Info
|
||||
51
Telegram/SourceFiles/info/info_flexible_scroll.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
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/widgets/scroll_area.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
namespace Info {
|
||||
|
||||
struct FlexibleScrollData {
|
||||
rpl::event_stream<int> contentHeightValue;
|
||||
rpl::event_stream<int> fillerWidthValue;
|
||||
};
|
||||
|
||||
class FlexibleScrollHelper final {
|
||||
public:
|
||||
FlexibleScrollHelper(
|
||||
not_null<Ui::ScrollArea*> scroll,
|
||||
not_null<Ui::RpWidget*> inner,
|
||||
not_null<Ui::RpWidget*> pinnedToTop,
|
||||
Fn<void(QMargins)> setPaintPadding,
|
||||
Fn<void(rpl::producer<not_null<QEvent*>>&&)> setViewport,
|
||||
FlexibleScrollData &data);
|
||||
|
||||
private:
|
||||
void setupScrollAnimation();
|
||||
void setupScrollHandling();
|
||||
|
||||
const not_null<Ui::ScrollArea*> _scroll;
|
||||
const not_null<Ui::RpWidget*> _inner;
|
||||
const not_null<Ui::RpWidget*> _pinnedToTop;
|
||||
const Fn<void(QMargins)> _setPaintPadding;
|
||||
const Fn<void(rpl::producer<not_null<QEvent*>>&&)> _setViewport;
|
||||
FlexibleScrollData &_data;
|
||||
|
||||
Ui::Animations::Basic _scrollAnimation;
|
||||
int _scrollTopFrom = 0;
|
||||
int _scrollTopTo = 0;
|
||||
crl::time _timeOffset = 0;
|
||||
int _lastScrollApplied = 0;
|
||||
int _scrollTopPrevious = 0;
|
||||
bool _applyingFakeScrollState = false;
|
||||
};
|
||||
|
||||
} // namespace Info
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "info/profile/info_profile_widget.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "info/media/info_media_widget.h"
|
||||
#include "info/stories/info_stories_widget.h"
|
||||
#include "info/info_content_widget.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "info/info_memento.h"
|
||||
@@ -65,8 +66,13 @@ const style::InfoTopBar &TopBarStyle(Wrap wrap) {
|
||||
[[nodiscard]] bool HasCustomTopBar(not_null<const Controller*> controller) {
|
||||
const auto section = controller->section();
|
||||
return (section.type() == Section::Type::BotStarRef)
|
||||
|| (section.type() == Section::Type::Profile)
|
||||
|| ((section.type() == Section::Type::Settings)
|
||||
&& section.settingsType()->hasCustomTopBar());
|
||||
&& section.settingsType()->hasCustomTopBar())
|
||||
|| (section.type() == Section::Type::Stories
|
||||
&& controller->key().storiesAlbumId() != Stories::ArchiveId()
|
||||
&& controller->key().storiesPeer()
|
||||
&& controller->key().storiesPeer()->isSelf());
|
||||
}
|
||||
|
||||
[[nodiscard]] Fn<Ui::StringWithNumbers(int)> SelectedTitleForMedia(
|
||||
|
||||
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/components/recent_shared_media_gifts.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_saved_messages.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -371,11 +372,13 @@ not_null<Ui::SettingsButton*> AddPeerGiftsButton(
|
||||
) | rpl::start_with_next([=] {
|
||||
state->appearedLifetime.destroy();
|
||||
const auto requestDone = crl::guard(wrap, [=](
|
||||
std::vector<DocumentId> ids) {
|
||||
std::vector<Data::SavedStarGift> gifts) {
|
||||
state->emojiList.clear();
|
||||
for (const auto &id : ids) {
|
||||
for (const auto &gift : gifts) {
|
||||
state->emojiList.push_back(
|
||||
peer->owner().customEmojiManager().create(id, refresh));
|
||||
peer->owner().customEmojiManager().create(
|
||||
gift.info.document->id,
|
||||
refresh));
|
||||
}
|
||||
state->textRefreshed.fire({});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_premium.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/send_credits_box.h" // SetButtonMarkedLabel
|
||||
#include "boxes/star_gift_box.h"
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "chat_helpers/stickers_gift_box_pack.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
@@ -36,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/dynamic_thumbnails.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/top_background_gradient.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_credits.h"
|
||||
@@ -620,9 +620,9 @@ void GiftButton::cacheUniqueBackground(
|
||||
auto p = QPainter(&_uniqueBackgroundCache);
|
||||
p.setClipRect(inner);
|
||||
const auto skip = inner.width() / 3;
|
||||
Ui::PaintPoints(
|
||||
Ui::PaintBgPoints(
|
||||
p,
|
||||
Ui::PatternPointsSmall(),
|
||||
Ui::PatternBgPointsSmall(),
|
||||
_uniquePatternCache,
|
||||
_uniquePatternEmoji.get(),
|
||||
*unique,
|
||||
|
||||
@@ -766,7 +766,8 @@ void DeleteContactNote(
|
||||
std::move(
|
||||
notesText
|
||||
) | rpl::start_with_next([=, raw = notesLine.text](
|
||||
const TextWithEntities ¬e) {
|
||||
TextWithEntities note) {
|
||||
TextUtilities::ParseEntities(note, TextParseLinks);
|
||||
raw->setMarkedText(note, context);
|
||||
}, notesLine.text->lifetime());
|
||||
|
||||
@@ -809,21 +810,21 @@ void DeleteContactNote(
|
||||
}, notesLine.wrap->lifetime());
|
||||
|
||||
const auto subtextLabel = Ui::CreateChild<Ui::FlatLabel>(
|
||||
notesLine.subtext->parentWidget(),
|
||||
notesLine.wrap->entity(),
|
||||
tr::lng_info_notes_private(tr::now),
|
||||
st::infoLabel);
|
||||
subtextLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
rpl::combine(
|
||||
notesLine.subtext->geometryValue(),
|
||||
notesContainer->widthValue()
|
||||
notesLine.wrap->entity()->widthValue(),
|
||||
notesLine.subtext->geometryValue()
|
||||
) | rpl::start_with_next([=, skip = st::lineWidth * 5](
|
||||
const QRect &subtextGeometry,
|
||||
int parentWidth) {
|
||||
int width,
|
||||
const QRect &subtextGeometry) {
|
||||
subtextLabel->moveToRight(
|
||||
subtextLabel->width(),
|
||||
0,
|
||||
subtextGeometry.y() + skip,
|
||||
parentWidth);
|
||||
width);
|
||||
}, subtextLabel->lifetime());
|
||||
|
||||
notesContainer->add(std::move(notesLine.wrap));
|
||||
@@ -1161,24 +1162,8 @@ public:
|
||||
private:
|
||||
object_ptr<Ui::RpWidget> setupPersonalChannel(not_null<UserData*> user);
|
||||
object_ptr<Ui::RpWidget> setupInfo();
|
||||
object_ptr<Ui::RpWidget> setupMuteToggle();
|
||||
void setupAboutVerification();
|
||||
void setupMainApp();
|
||||
void setupBotPermissions();
|
||||
void setupMainButtons();
|
||||
Ui::MultiSlideTracker fillTopicButtons();
|
||||
Ui::MultiSlideTracker fillUserButtons(
|
||||
not_null<UserData*> user);
|
||||
Ui::MultiSlideTracker fillChannelButtons(
|
||||
not_null<ChannelData*> channel);
|
||||
Ui::MultiSlideTracker fillDiscussionButtons(
|
||||
not_null<ChannelData*> channel);
|
||||
void addShowTopicsListButton(
|
||||
Ui::MultiSlideTracker &tracker,
|
||||
not_null<Data::Forum*> forum);
|
||||
void addViewChannelButton(
|
||||
Ui::MultiSlideTracker &tracker,
|
||||
not_null<ChannelData*> channel);
|
||||
|
||||
void addReportReaction(Ui::MultiSlideTracker &tracker);
|
||||
void addReportReaction(
|
||||
@@ -1609,7 +1594,7 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
|
||||
const auto qrButton = Ui::CreateChild<Ui::IconButton>(
|
||||
usernameLine.text->parentWidget(),
|
||||
st::infoProfileLabeledButtonQr);
|
||||
const auto rightSkip = 0;// st::infoProfileLabeledButtonQrRightSkip;
|
||||
const auto rightSkip = st::infoProfileLabeledButtonQrRightSkip;
|
||||
fitLabelToButton(qrButton, usernameLine.text, rightSkip);
|
||||
fitLabelToButton(qrButton, usernameLine.subtext, rightSkip);
|
||||
qrButton->setClickedCallback([=] {
|
||||
@@ -1693,7 +1678,7 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
|
||||
const auto qr = Ui::CreateChild<Ui::IconButton>(
|
||||
linkLine.text->parentWidget(),
|
||||
st::infoProfileLabeledButtonQr);
|
||||
const auto rightSkip = 0;// st::infoProfileLabeledButtonQrRightSkip;
|
||||
const auto rightSkip = st::infoProfileLabeledButtonQrRightSkip;
|
||||
fitLabelToButton(qr, linkLine.text, rightSkip);
|
||||
fitLabelToButton(qr, linkLine.subtext, rightSkip);
|
||||
qr->setClickedCallback([=, peer = _peer] {
|
||||
@@ -1727,33 +1712,6 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
|
||||
addTranslateToMenu(about.text, AboutWithAdvancedValue(_peer));
|
||||
}
|
||||
}
|
||||
if (!_peer->isSelf()) {
|
||||
// No notifications toggle for Self => no separator.
|
||||
|
||||
const auto user = _peer->asUser();
|
||||
const auto app = user && user->botInfo && user->botInfo->hasMainApp;
|
||||
const auto padding = app
|
||||
? QMargins(
|
||||
st::infoOpenAppMargin.left(),
|
||||
st::infoProfileSeparatorPadding.top(),
|
||||
st::infoOpenAppMargin.right(),
|
||||
0)
|
||||
: st::infoProfileSeparatorPadding;
|
||||
|
||||
result->add(object_ptr<Ui::SlideWrap<>>(
|
||||
result,
|
||||
object_ptr<Ui::PlainShadow>(result),
|
||||
padding)
|
||||
)->setDuration(
|
||||
st::infoSlideDuration
|
||||
)->toggleOn(
|
||||
std::move(tracker).atLeastOneShownValue()
|
||||
);
|
||||
}
|
||||
object_ptr<FloatingIcon>(
|
||||
result,
|
||||
st::infoIconInformation,
|
||||
st::infoInformationIconPosition);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -1817,7 +1775,7 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
|
||||
std::move(text),
|
||||
st::infoLabel,
|
||||
st::infoLabeled,
|
||||
st::infoProfileLabeledPadding);
|
||||
st::infoProfilePersonalChannelPadding);
|
||||
onlyChannelWrap->entity()->add(std::move(line.wrap));
|
||||
|
||||
line.text->setClickHandlerFilter([=](
|
||||
@@ -1864,7 +1822,7 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
|
||||
not_null<HistoryItem*> item,
|
||||
anim::type animated) {
|
||||
const auto &stUserpic = st::infoPersonalChannelUserpic;
|
||||
const auto &stLabeled = st::infoProfileLabeledPadding;
|
||||
const auto &stLabeled = st::infoProfilePersonalChannelPadding;
|
||||
|
||||
messageChannelWrap->toggle(false, anim::type::instant);
|
||||
clear();
|
||||
@@ -1979,10 +1937,10 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
|
||||
rpl::single(item->history()->peer->asChannel())),
|
||||
st::infoLabel),
|
||||
QMargins(
|
||||
st::infoProfileLabeledPadding.left(),
|
||||
st::infoProfilePersonalChannelPadding.left(),
|
||||
0,
|
||||
st::infoProfileLabeledPadding.right(),
|
||||
st::infoProfileLabeledPadding.bottom()));
|
||||
st::infoProfilePersonalChannelPadding.right(),
|
||||
st::infoProfilePersonalChannelPadding.bottom()));
|
||||
}
|
||||
{
|
||||
const auto button = Ui::CreateSimpleRectButton(
|
||||
@@ -2038,77 +1996,6 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
|
||||
return result;
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> DetailsFiller::setupMuteToggle() {
|
||||
const auto peer = _peer;
|
||||
const auto topicRootId = _topic ? _topic->rootId() : MsgId();
|
||||
const auto makeThread = [=] {
|
||||
return topicRootId
|
||||
? static_cast<Data::Thread*>(peer->forumTopicFor(topicRootId))
|
||||
: peer->owner().history(peer).get();
|
||||
};
|
||||
auto result = object_ptr<Ui::SettingsButton>(
|
||||
_wrap,
|
||||
tr::lng_profile_enable_notifications(),
|
||||
st::infoNotificationsButton);
|
||||
result->toggleOn(_topic
|
||||
? NotificationsEnabledValue(_topic)
|
||||
: NotificationsEnabledValue(peer), true);
|
||||
result->setAcceptBoth();
|
||||
const auto notifySettings = &peer->owner().notifySettings();
|
||||
MuteMenu::SetupMuteMenu(
|
||||
result.data(),
|
||||
result->clicks(
|
||||
) | rpl::filter([=](Qt::MouseButton button) {
|
||||
if (button == Qt::RightButton) {
|
||||
return true;
|
||||
}
|
||||
const auto topic = topicRootId
|
||||
? peer->forumTopicFor(topicRootId)
|
||||
: nullptr;
|
||||
Assert(!topicRootId || topic != nullptr);
|
||||
const auto is = topic
|
||||
? notifySettings->isMuted(topic)
|
||||
: notifySettings->isMuted(peer);
|
||||
if (is) {
|
||||
if (topic) {
|
||||
notifySettings->update(topic, { .unmute = true });
|
||||
} else {
|
||||
notifySettings->update(peer, { .unmute = true });
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}) | rpl::to_empty,
|
||||
makeThread,
|
||||
_controller->uiShow());
|
||||
object_ptr<FloatingIcon>(
|
||||
result,
|
||||
st::infoIconNotifications,
|
||||
st::infoNotificationsIconPosition);
|
||||
return result;
|
||||
}
|
||||
|
||||
void DetailsFiller::setupAboutVerification() {
|
||||
const auto peer = _peer;
|
||||
const auto inner = _wrap->add(object_ptr<Ui::VerticalLayout>(_wrap));
|
||||
peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::VerifyInfo
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto info = peer->botVerifyDetails();
|
||||
while (inner->count()) {
|
||||
delete inner->widgetAt(0);
|
||||
}
|
||||
if (!info) {
|
||||
Ui::AddDivider(inner);
|
||||
} else if (!info->description.empty()) {
|
||||
Ui::AddDividerText(inner, rpl::single(info->description));
|
||||
}
|
||||
inner->resizeToWidth(inner->width());
|
||||
}, inner->lifetime());
|
||||
}
|
||||
|
||||
void DetailsFiller::setupMainApp() {
|
||||
const auto button = _wrap->add(
|
||||
object_ptr<Ui::RoundButton>(
|
||||
@@ -2177,33 +2064,6 @@ void DetailsFiller::setupBotPermissions() {
|
||||
AddSkip(_wrap);
|
||||
}
|
||||
|
||||
void DetailsFiller::setupMainButtons() {
|
||||
auto wrapButtons = [=](auto &&callback) {
|
||||
auto topSkip = _wrap->add(CreateSlideSkipWidget(_wrap));
|
||||
auto tracker = callback();
|
||||
topSkip->toggleOn(std::move(tracker).atLeastOneShownValue());
|
||||
};
|
||||
if (_topic) {
|
||||
wrapButtons([=] {
|
||||
return fillTopicButtons();
|
||||
});
|
||||
} else if (const auto user = _peer->asUser()) {
|
||||
wrapButtons([=] {
|
||||
return fillUserButtons(user);
|
||||
});
|
||||
} else if (const auto channel = _peer->asChannel()) {
|
||||
if (channel->isMegagroup()) {
|
||||
wrapButtons([=] {
|
||||
return fillDiscussionButtons(channel);
|
||||
});
|
||||
} else {
|
||||
wrapButtons([=] {
|
||||
return fillChannelButtons(channel);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DetailsFiller::addReportReaction(Ui::MultiSlideTracker &tracker) {
|
||||
v::match(_origin.data, [&](GroupReactionOrigin data) {
|
||||
const auto user = _peer->asUser();
|
||||
@@ -2260,159 +2120,10 @@ void DetailsFiller::addReportReaction(
|
||||
st::infoMainButtonAttention);
|
||||
}
|
||||
|
||||
Ui::MultiSlideTracker DetailsFiller::fillTopicButtons() {
|
||||
Ui::MultiSlideTracker tracker;
|
||||
addShowTopicsListButton(tracker, _topic->forum());
|
||||
return tracker;
|
||||
}
|
||||
|
||||
void DetailsFiller::addShowTopicsListButton(
|
||||
Ui::MultiSlideTracker &tracker,
|
||||
not_null<Data::Forum*> forum) {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto window = _controller->parentController();
|
||||
const auto peer = forum->peer();
|
||||
auto showTopicsVisible = rpl::combine(
|
||||
window->adaptive().oneColumnValue(),
|
||||
window->shownForum().value(),
|
||||
_1 || (_2 != forum));
|
||||
const auto callback = [=] {
|
||||
if (const auto forum = peer->forum()) {
|
||||
if (peer->useSubsectionTabs()) {
|
||||
window->searchInChat(forum->history());
|
||||
} else {
|
||||
window->showForum(forum);
|
||||
}
|
||||
}
|
||||
};
|
||||
AddMainButton(
|
||||
_wrap,
|
||||
(forum->peer()->isBot()
|
||||
? tr::lng_bot_show_threads_list()
|
||||
: tr::lng_forum_show_topics_list()),
|
||||
std::move(showTopicsVisible),
|
||||
callback,
|
||||
tracker);
|
||||
}
|
||||
|
||||
Ui::MultiSlideTracker DetailsFiller::fillUserButtons(
|
||||
not_null<UserData*> user) {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
Ui::MultiSlideTracker tracker;
|
||||
if (user->isSelf()) {
|
||||
return tracker;
|
||||
}
|
||||
auto window = _controller->parentController();
|
||||
|
||||
auto addSendMessageButton = [&] {
|
||||
auto activePeerValue = window->activeChatValue(
|
||||
) | rpl::map([](Dialogs::Key key) {
|
||||
return key.peer();
|
||||
});
|
||||
auto sendMessageVisible = rpl::combine(
|
||||
_controller->wrapValue(),
|
||||
std::move(activePeerValue),
|
||||
(_1 != Wrap::Side) || (_2 != user));
|
||||
auto sendMessage = [window, user] {
|
||||
window->showPeerHistory(
|
||||
user,
|
||||
Window::SectionShow::Way::Forward);
|
||||
};
|
||||
AddMainButton(
|
||||
_wrap,
|
||||
tr::lng_profile_send_message(),
|
||||
std::move(sendMessageVisible),
|
||||
std::move(sendMessage),
|
||||
tracker);
|
||||
};
|
||||
|
||||
if (!user->isVerifyCodes()) {
|
||||
addSendMessageButton();
|
||||
}
|
||||
if (!_sublist) {
|
||||
addReportReaction(tracker);
|
||||
}
|
||||
|
||||
return tracker;
|
||||
}
|
||||
|
||||
Ui::MultiSlideTracker DetailsFiller::fillChannelButtons(
|
||||
not_null<ChannelData*> channel) {
|
||||
Ui::MultiSlideTracker tracker;
|
||||
addViewChannelButton(tracker, channel);
|
||||
return tracker;
|
||||
}
|
||||
|
||||
void DetailsFiller::addViewChannelButton(
|
||||
Ui::MultiSlideTracker &tracker,
|
||||
not_null<ChannelData*> channel) {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
auto window = _controller->parentController();
|
||||
auto activePeerValue = window->activeChatValue(
|
||||
) | rpl::map([](Dialogs::Key key) {
|
||||
return key.peer();
|
||||
});
|
||||
auto viewChannelVisible = rpl::combine(
|
||||
_controller->wrapValue(),
|
||||
std::move(activePeerValue),
|
||||
(_1 != Wrap::Side) || (_2 != channel));
|
||||
auto viewChannel = [=] {
|
||||
window->showPeerHistory(
|
||||
channel,
|
||||
Window::SectionShow::Way::Forward);
|
||||
};
|
||||
AddMainButton(
|
||||
_wrap,
|
||||
tr::lng_profile_view_channel(),
|
||||
std::move(viewChannelVisible),
|
||||
std::move(viewChannel),
|
||||
tracker);
|
||||
}
|
||||
|
||||
Ui::MultiSlideTracker DetailsFiller::fillDiscussionButtons(
|
||||
not_null<ChannelData*> channel) {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
Ui::MultiSlideTracker tracker;
|
||||
auto window = _controller->parentController();
|
||||
auto viewDiscussionVisible = window->dialogsEntryStateValue(
|
||||
) | rpl::map([=](const Dialogs::EntryState &state) {
|
||||
const auto history = state.key.history();
|
||||
return (state.section == Dialogs::EntryState::Section::Replies)
|
||||
&& history
|
||||
&& (history->peer == channel);
|
||||
});
|
||||
auto viewDiscussion = [=] {
|
||||
window->showPeerHistory(
|
||||
channel,
|
||||
Window::SectionShow::Way::Forward);
|
||||
};
|
||||
AddMainButton(
|
||||
_wrap,
|
||||
tr::lng_profile_view_discussion(),
|
||||
std::move(viewDiscussionVisible),
|
||||
std::move(viewDiscussion),
|
||||
tracker);
|
||||
|
||||
if (const auto forum = channel->forum()) {
|
||||
if (channel->useSubsectionTabs()) {
|
||||
addShowTopicsListButton(tracker, forum);
|
||||
}
|
||||
} else if (const auto broadcast = channel->monoforumBroadcast()) {
|
||||
addViewChannelButton(tracker, broadcast);
|
||||
}
|
||||
|
||||
return tracker;
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> DetailsFiller::fill() {
|
||||
Expects(!_topic || !_topic->creating());
|
||||
|
||||
if (!_topic) {
|
||||
setupAboutVerification();
|
||||
} else {
|
||||
add(object_ptr<Ui::BoxContentDivider>(_wrap));
|
||||
}
|
||||
@@ -2430,11 +2141,13 @@ object_ptr<Ui::RpWidget> DetailsFiller::fill() {
|
||||
setupBotPermissions();
|
||||
}
|
||||
}
|
||||
if (!user->isSelf() && !_sublist) {
|
||||
auto topSkip = _wrap->add(CreateSlideSkipWidget(_wrap));
|
||||
Ui::MultiSlideTracker tracker;
|
||||
addReportReaction(tracker);
|
||||
topSkip->toggleOn(std::move(tracker).atLeastOneShownValue());
|
||||
}
|
||||
}
|
||||
if (!_sublist && !_peer->isSelf()) {
|
||||
add(setupMuteToggle());
|
||||
}
|
||||
setupMainButtons();
|
||||
add(CreateSkipWidget(_wrap));
|
||||
|
||||
return std::move(_wrap);
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "info/profile/info_profile_badge.h"
|
||||
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_emoji_statuses.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -90,11 +91,15 @@ void Badge::setContent(Content content) {
|
||||
? (Data::FrameSizeFromTag(sizeTag())
|
||||
/ style::DevicePixelRatio())
|
||||
: 0;
|
||||
const auto &style = st();
|
||||
const auto icon = (_content.badge == BadgeType::Verified)
|
||||
? &_st.verified
|
||||
? &style.verified
|
||||
: id
|
||||
? nullptr
|
||||
: &_st.premium;
|
||||
: &style.premium;
|
||||
const auto iconForeground = (_content.badge == BadgeType::Verified)
|
||||
? &style.verifiedCheck
|
||||
: nullptr;
|
||||
if (id) {
|
||||
_emojiStatus = _session->data().customEmojiManager().create(
|
||||
Data::EmojiStatusCustomId(id),
|
||||
@@ -113,7 +118,7 @@ void Badge::setContent(Content content) {
|
||||
) | rpl::start_with_next([=, check = _view.data()]{
|
||||
if (_emojiStatus) {
|
||||
auto args = Ui::Text::CustomEmoji::Context{
|
||||
.textColor = _st.premiumFg->c,
|
||||
.textColor = style.premiumFg->c,
|
||||
.now = crl::now(),
|
||||
.paused = ((_animationPaused && _animationPaused())
|
||||
|| On(PowerSaving::kEmojiStatus)),
|
||||
@@ -125,8 +130,20 @@ void Badge::setContent(Content content) {
|
||||
}
|
||||
}
|
||||
if (icon) {
|
||||
Painter p(check);
|
||||
icon->paint(p, emoji, 0, check->width());
|
||||
auto p = Painter(check);
|
||||
if (_overrideSt) {
|
||||
icon->paint(
|
||||
p,
|
||||
emoji,
|
||||
0,
|
||||
check->width(),
|
||||
_overrideSt->premiumFg->c);
|
||||
} else {
|
||||
icon->paint(p, emoji, 0, check->width());
|
||||
}
|
||||
if (iconForeground) {
|
||||
iconForeground->paint(p, emoji, 0, check->width());
|
||||
}
|
||||
}
|
||||
}, _view->lifetime());
|
||||
} break;
|
||||
@@ -179,6 +196,13 @@ void Badge::setPremiumClickCallback(Fn<void()> callback) {
|
||||
}
|
||||
}
|
||||
|
||||
void Badge::setOverrideStyle(const style::InfoPeerBadge *st) {
|
||||
const auto was = _content;
|
||||
_overrideSt = st;
|
||||
_content = {};
|
||||
setContent(was);
|
||||
}
|
||||
|
||||
rpl::producer<> Badge::updated() const {
|
||||
return _updated.events();
|
||||
}
|
||||
@@ -187,24 +211,30 @@ void Badge::move(int left, int top, int bottom) {
|
||||
if (!_view) {
|
||||
return;
|
||||
}
|
||||
const auto &style = st();
|
||||
const auto star = !_emojiStatus
|
||||
&& (_content.badge == BadgeType::Premium
|
||||
|| _content.badge == BadgeType::Verified);
|
||||
const auto fake = !_emojiStatus && !star;
|
||||
const auto skip = fake ? 0 : _st.position.x();
|
||||
const auto skip = fake ? 0 : style.position.x();
|
||||
const auto badgeLeft = left + skip;
|
||||
const auto badgeTop = top
|
||||
+ (star
|
||||
? _st.position.y()
|
||||
? style.position.y()
|
||||
: (bottom - top - _view->height()) / 2);
|
||||
_view->moveToLeft(badgeLeft, badgeTop);
|
||||
}
|
||||
|
||||
const style::InfoPeerBadge &Badge::st() const {
|
||||
return _overrideSt ? *_overrideSt : _st;
|
||||
}
|
||||
|
||||
Data::CustomEmojiSizeTag Badge::sizeTag() const {
|
||||
using SizeTag = Data::CustomEmojiSizeTag;
|
||||
return (_st.sizeTag == 2)
|
||||
const auto &style = st();
|
||||
return (style.sizeTag == 2)
|
||||
? SizeTag::Isolated
|
||||
: (_st.sizeTag == 1)
|
||||
: (style.sizeTag == 1)
|
||||
? SizeTag::Large
|
||||
: SizeTag::Normal;
|
||||
}
|
||||
@@ -237,4 +267,18 @@ rpl::producer<Badge::Content> VerifiedContentForPeer(
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<Badge::Content> BotVerifyBadgeForPeer(
|
||||
not_null<PeerData*> peer) {
|
||||
return peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::VerifyInfo
|
||||
) | rpl::map([=] {
|
||||
const auto info = peer->botVerifyDetails();
|
||||
return Badge::Content{
|
||||
.badge = info ? BadgeType::BotVerified : BadgeType::None,
|
||||
.emojiStatusId = { info ? info->iconId : DocumentId() },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Info::Profile
|
||||
|
||||
@@ -70,6 +70,7 @@ public:
|
||||
[[nodiscard]] Ui::RpWidget *widget() const;
|
||||
|
||||
void setPremiumClickCallback(Fn<void()> callback);
|
||||
void setOverrideStyle(const style::InfoPeerBadge *st);
|
||||
[[nodiscard]] rpl::producer<> updated() const;
|
||||
void move(int left, int top, int bottom);
|
||||
|
||||
@@ -77,9 +78,11 @@ public:
|
||||
|
||||
private:
|
||||
void setContent(Content content);
|
||||
[[nodiscard]] const style::InfoPeerBadge &st() const;
|
||||
|
||||
const not_null<QWidget*> _parent;
|
||||
const style::InfoPeerBadge &_st;
|
||||
const style::InfoPeerBadge *_overrideSt = nullptr;
|
||||
const not_null<Main::Session*> _session;
|
||||
EmojiStatusPanel *_emojiStatusPanel = nullptr;
|
||||
const int _customStatusLoopsLimit = 0;
|
||||
@@ -98,5 +101,7 @@ private:
|
||||
not_null<PeerData*> peer);
|
||||
[[nodiscard]] rpl::producer<Badge::Content> VerifiedContentForPeer(
|
||||
not_null<PeerData*> peer);
|
||||
[[nodiscard]] rpl::producer<Badge::Content> BotVerifyBadgeForPeer(
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
} // namespace Info::Profile
|
||||
|
||||
224
Telegram/SourceFiles/info/profile/info_profile_badge_tooltip.cpp
Normal file
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
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/profile/info_profile_badge_tooltip.h"
|
||||
|
||||
#include "data/data_emoji_statuses.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "styles/style_info.h"
|
||||
|
||||
namespace Info::Profile {
|
||||
namespace {
|
||||
|
||||
constexpr auto kGlareDurationStep = crl::time(320);
|
||||
constexpr auto kGlareTimeout = crl::time(1000);
|
||||
|
||||
} // namespace
|
||||
|
||||
BadgeTooltip::BadgeTooltip(
|
||||
not_null<QWidget*> parent,
|
||||
std::shared_ptr<Data::EmojiStatusCollectible> collectible,
|
||||
not_null<QWidget*> pointTo)
|
||||
: Ui::RpWidget(parent)
|
||||
, _st(st::infoGiftTooltip)
|
||||
, _collectible(std::move(collectible))
|
||||
, _text(_collectible->title)
|
||||
, _font(st::infoGiftTooltipFont)
|
||||
, _inner(_font->width(_text), _font->height)
|
||||
, _outer(_inner.grownBy(_st.padding))
|
||||
, _stroke(st::lineWidth)
|
||||
, _skip(2 * _stroke)
|
||||
, _full(_outer + QSize(2 * _skip, _st.arrow + 2 * _skip))
|
||||
, _glareSize(_outer.height() * 3)
|
||||
, _glareRange(_outer.width() + _glareSize)
|
||||
, _glareDuration(_glareRange * kGlareDurationStep / _glareSize)
|
||||
, _glareTimer([=] { showGlare(); }) {
|
||||
resize(_full + QSize(0, _st.shift));
|
||||
setupGeometry(pointTo);
|
||||
}
|
||||
|
||||
void BadgeTooltip::fade(bool shown) {
|
||||
if (_shown == shown) {
|
||||
return;
|
||||
}
|
||||
show();
|
||||
_shown = shown;
|
||||
_showAnimation.start([=] {
|
||||
update();
|
||||
if (!_showAnimation.animating()) {
|
||||
if (!_shown) {
|
||||
hide();
|
||||
} else {
|
||||
showGlare();
|
||||
}
|
||||
}
|
||||
}, _shown ? 0. : 1., _shown ? 1. : 0., _st.duration, anim::easeInCirc);
|
||||
}
|
||||
|
||||
void BadgeTooltip::showGlare() {
|
||||
_glareAnimation.start([=] {
|
||||
update();
|
||||
if (!_glareAnimation.animating()) {
|
||||
_glareTimer.callOnce(kGlareTimeout);
|
||||
}
|
||||
}, 0., 1., _glareDuration);
|
||||
}
|
||||
|
||||
void BadgeTooltip::finishAnimating() {
|
||||
_showAnimation.stop();
|
||||
if (!_shown) {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
void BadgeTooltip::setOpacity(float64 opacity) {
|
||||
_opacity = opacity;
|
||||
update();
|
||||
}
|
||||
|
||||
crl::time BadgeTooltip::glarePeriod() const {
|
||||
return _glareDuration + kGlareTimeout;
|
||||
}
|
||||
|
||||
void BadgeTooltip::paintEvent(QPaintEvent *e) {
|
||||
const auto glare = _glareAnimation.value(0.);
|
||||
_glareRight = anim::interpolate(0, _glareRange, glare);
|
||||
prepareImage();
|
||||
|
||||
auto p = QPainter(this);
|
||||
const auto shown = _showAnimation.value(_shown ? 1. : 0.);
|
||||
p.setOpacity(shown * _opacity);
|
||||
const auto imageHeight = _image.height() / _image.devicePixelRatio();
|
||||
const auto top = anim::interpolate(0, height() - imageHeight, shown);
|
||||
p.drawImage(0, top, _image);
|
||||
}
|
||||
|
||||
void BadgeTooltip::setupGeometry(not_null<QWidget*> pointTo) {
|
||||
auto widget = pointTo.get();
|
||||
const auto parent = parentWidget();
|
||||
|
||||
const auto refresh = [=, weak = base::make_weak(pointTo)] {
|
||||
const auto strong = weak.get();
|
||||
if (!strong) {
|
||||
hide();
|
||||
return setGeometry({});
|
||||
}
|
||||
const auto rect = Ui::MapFrom(parent, pointTo, pointTo->rect());
|
||||
const auto point = QPoint(rect.center().x(), rect.y());
|
||||
const auto left = point.x() - (width() / 2);
|
||||
const auto skip = _st.padding.left();
|
||||
setGeometry(
|
||||
std::min(std::max(left, skip), parent->width() - width() - skip),
|
||||
std::max(point.y() - height() - _st.margin.bottom(), skip),
|
||||
width(),
|
||||
height());
|
||||
const auto arrowMiddle = point.x() - x();
|
||||
if (_arrowMiddle != arrowMiddle) {
|
||||
_arrowMiddle = arrowMiddle;
|
||||
update();
|
||||
}
|
||||
};
|
||||
refresh();
|
||||
while (widget && widget != parent) {
|
||||
base::install_event_filter(this, widget, [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Resize
|
||||
|| e->type() == QEvent::Move
|
||||
|| e->type() == QEvent::ZOrderChange) {
|
||||
refresh();
|
||||
raise();
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
widget = widget->parentWidget();
|
||||
}
|
||||
}
|
||||
|
||||
void BadgeTooltip::prepareImage() {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto arrow = _st.arrow;
|
||||
const auto size = _full * ratio;
|
||||
if (_image.size() != size) {
|
||||
_image = QImage(size, QImage::Format_ARGB32_Premultiplied);
|
||||
_image.setDevicePixelRatio(ratio);
|
||||
} else if (_imageGlareRight == _glareRight
|
||||
&& _imageArrowMiddle == _arrowMiddle) {
|
||||
return;
|
||||
}
|
||||
_imageGlareRight = _glareRight;
|
||||
_imageArrowMiddle = _arrowMiddle;
|
||||
_image.fill(Qt::transparent);
|
||||
|
||||
const auto gfrom = _imageGlareRight - _glareSize;
|
||||
const auto gtill = _imageGlareRight;
|
||||
|
||||
auto path = QPainterPath();
|
||||
const auto width = _outer.width();
|
||||
const auto height = _outer.height();
|
||||
const auto radius = (height + 1) / 2;
|
||||
const auto diameter = height;
|
||||
path.moveTo(radius, 0);
|
||||
path.lineTo(width - radius, 0);
|
||||
path.arcTo(
|
||||
QRect(QPoint(width - diameter, 0), QSize(diameter, diameter)),
|
||||
90,
|
||||
-180);
|
||||
const auto xarrow = _arrowMiddle - _skip;
|
||||
if (xarrow - arrow <= radius || xarrow + arrow >= width - radius) {
|
||||
path.lineTo(radius, height);
|
||||
} else {
|
||||
path.lineTo(xarrow + arrow, height);
|
||||
path.lineTo(xarrow, height + arrow);
|
||||
path.lineTo(xarrow - arrow, height);
|
||||
path.lineTo(radius, height);
|
||||
}
|
||||
path.arcTo(
|
||||
QRect(QPoint(0, 0), QSize(diameter, diameter)),
|
||||
-90,
|
||||
-180);
|
||||
path.closeSubpath();
|
||||
|
||||
auto p = QPainter(&_image);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
if (gtill > 0) {
|
||||
auto gradient = QLinearGradient(gfrom, 0, gtill, 0);
|
||||
gradient.setStops({
|
||||
{ 0., _collectible->edgeColor },
|
||||
{ 0.5, _collectible->centerColor },
|
||||
{ 1., _collectible->edgeColor },
|
||||
});
|
||||
p.setBrush(gradient);
|
||||
} else {
|
||||
p.setBrush(_collectible->edgeColor);
|
||||
}
|
||||
p.translate(_skip, _skip);
|
||||
p.drawPath(path);
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
p.setBrush(Qt::NoBrush);
|
||||
auto copy = _collectible->textColor;
|
||||
copy.setAlpha(0);
|
||||
if (gtill > 0) {
|
||||
auto gradient = QLinearGradient(gfrom, 0, gtill, 0);
|
||||
gradient.setStops({
|
||||
{ 0., copy },
|
||||
{ 0.5, _collectible->textColor },
|
||||
{ 1., copy },
|
||||
});
|
||||
p.setPen(QPen(gradient, _stroke));
|
||||
} else {
|
||||
p.setPen(QPen(copy, _stroke));
|
||||
}
|
||||
p.drawPath(path);
|
||||
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
p.setFont(_font);
|
||||
p.setPen(QColor(255, 255, 255));
|
||||
p.drawText(_st.padding.left(), _st.padding.top() + _font->ascent, _text);
|
||||
}
|
||||
|
||||
} // namespace Info::Profile
|
||||
@@ -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 "ui/rp_widget.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
namespace style {
|
||||
struct ImportantTooltip;
|
||||
} // namespace style
|
||||
|
||||
namespace Data {
|
||||
struct EmojiStatusCollectible;
|
||||
} // namespace Data
|
||||
|
||||
namespace Info::Profile {
|
||||
|
||||
class BadgeTooltip final : public Ui::RpWidget {
|
||||
public:
|
||||
BadgeTooltip(
|
||||
not_null<QWidget*> parent,
|
||||
std::shared_ptr<Data::EmojiStatusCollectible> collectible,
|
||||
not_null<QWidget*> pointTo);
|
||||
|
||||
void fade(bool shown);
|
||||
void finishAnimating();
|
||||
void setOpacity(float64 opacity);
|
||||
|
||||
[[nodiscard]] crl::time glarePeriod() const;
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void setupGeometry(not_null<QWidget*> pointTo);
|
||||
void prepareImage();
|
||||
void showGlare();
|
||||
|
||||
const style::ImportantTooltip &_st;
|
||||
std::shared_ptr<Data::EmojiStatusCollectible> _collectible;
|
||||
QString _text;
|
||||
const style::font &_font;
|
||||
QSize _inner;
|
||||
QSize _outer;
|
||||
int _stroke = 0;
|
||||
int _skip = 0;
|
||||
QSize _full;
|
||||
int _glareSize = 0;
|
||||
int _glareRange = 0;
|
||||
crl::time _glareDuration = 0;
|
||||
base::Timer _glareTimer;
|
||||
|
||||
Ui::Animations::Simple _showAnimation;
|
||||
Ui::Animations::Simple _glareAnimation;
|
||||
|
||||
QImage _image;
|
||||
int _glareRight = 0;
|
||||
int _imageGlareRight = 0;
|
||||
int _arrowMiddle = 0;
|
||||
int _imageArrowMiddle = 0;
|
||||
|
||||
bool _shown = false;
|
||||
float64 _opacity = 1.;
|
||||
};
|
||||
|
||||
} // namespace Info::Profile
|
||||
@@ -18,15 +18,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_saved_music.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "info/profile/info_profile_badge.h"
|
||||
#include "info/profile/info_profile_badge_tooltip.h"
|
||||
#include "info/profile/info_profile_emoji_status_panel.h"
|
||||
#include "info/profile/info_profile_music_button.h"
|
||||
#include "info/profile/info_profile_status_label.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "info/saved/info_saved_music_widget.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "boxes/peers/edit_forum_topic_box.h"
|
||||
@@ -66,41 +65,6 @@ constexpr auto kGiftBadgeGlares = 3;
|
||||
constexpr auto kGlareDurationStep = crl::time(320);
|
||||
constexpr auto kGlareTimeout = crl::time(1000);
|
||||
|
||||
[[nodiscard]] auto MembersStatusText(int count) {
|
||||
return tr::lng_chat_status_members(tr::now, lt_count_decimal, count);
|
||||
};
|
||||
|
||||
[[nodiscard]] auto OnlineStatusText(int count) {
|
||||
return tr::lng_chat_status_online(tr::now, lt_count_decimal, count);
|
||||
};
|
||||
|
||||
[[nodiscard]] auto ChatStatusText(
|
||||
int fullCount,
|
||||
int onlineCount,
|
||||
bool isGroup) {
|
||||
if (onlineCount > 1 && onlineCount <= fullCount) {
|
||||
return tr::lng_chat_status_members_online(
|
||||
tr::now,
|
||||
lt_members_count,
|
||||
MembersStatusText(fullCount),
|
||||
lt_online_count,
|
||||
OnlineStatusText(onlineCount));
|
||||
} else if (fullCount > 0) {
|
||||
return isGroup
|
||||
? tr::lng_chat_status_members(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
fullCount)
|
||||
: tr::lng_chat_status_subscribers(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
fullCount);
|
||||
}
|
||||
return isGroup
|
||||
? tr::lng_group_status(tr::now)
|
||||
: tr::lng_channel_status(tr::now);
|
||||
};
|
||||
|
||||
[[nodiscard]] const style::InfoProfileCover &CoverStyle(
|
||||
not_null<PeerData*> peer,
|
||||
Data::ForumTopic *topic,
|
||||
@@ -114,7 +78,9 @@ constexpr auto kGlareTimeout = crl::time(1000);
|
||||
: st::infoProfileCover;
|
||||
}
|
||||
|
||||
[[nodiscard]] QMargins LargeCustomEmojiMargins() {
|
||||
} // namespace
|
||||
|
||||
QMargins LargeCustomEmojiMargins() {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto emoji = Ui::Emoji::GetSizeLarge() / ratio;
|
||||
const auto size = Data::FrameSizeFromTag(Data::CustomEmojiSizeTag::Large)
|
||||
@@ -124,258 +90,6 @@ constexpr auto kGlareTimeout = crl::time(1000);
|
||||
return { left, left, right, right };
|
||||
}
|
||||
|
||||
[[nodiscard]] MusicButtonData DocumentMusicButtonData(
|
||||
not_null<DocumentData*> document) {
|
||||
if (const auto song = document->song()) {
|
||||
if (!song->performer.isEmpty() || !song->title.isEmpty()) {
|
||||
return {
|
||||
.performer = song->performer,
|
||||
.title = song->title,
|
||||
};
|
||||
}
|
||||
}
|
||||
const auto name = document->filename();
|
||||
return {
|
||||
.title = !name.isEmpty() ? name : tr::lng_all_music(tr::now),
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class Cover::BadgeTooltip final : public Ui::RpWidget {
|
||||
public:
|
||||
BadgeTooltip(
|
||||
not_null<QWidget*> parent,
|
||||
std::shared_ptr<Data::EmojiStatusCollectible> collectible,
|
||||
not_null<QWidget*> pointTo);
|
||||
|
||||
void fade(bool shown);
|
||||
void finishAnimating();
|
||||
|
||||
[[nodiscard]] crl::time glarePeriod() const;
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
void setupGeometry(not_null<QWidget*> pointTo);
|
||||
void prepareImage();
|
||||
void showGlare();
|
||||
|
||||
const style::ImportantTooltip &_st;
|
||||
std::shared_ptr<Data::EmojiStatusCollectible> _collectible;
|
||||
QString _text;
|
||||
const style::font &_font;
|
||||
QSize _inner;
|
||||
QSize _outer;
|
||||
int _stroke = 0;
|
||||
int _skip = 0;
|
||||
QSize _full;
|
||||
int _glareSize = 0;
|
||||
int _glareRange = 0;
|
||||
crl::time _glareDuration = 0;
|
||||
base::Timer _glareTimer;
|
||||
|
||||
Ui::Animations::Simple _showAnimation;
|
||||
Ui::Animations::Simple _glareAnimation;
|
||||
|
||||
QImage _image;
|
||||
int _glareRight = 0;
|
||||
int _imageGlareRight = 0;
|
||||
int _arrowMiddle = 0;
|
||||
int _imageArrowMiddle = 0;
|
||||
|
||||
bool _shown = false;
|
||||
|
||||
};
|
||||
|
||||
Cover::BadgeTooltip::BadgeTooltip(
|
||||
not_null<QWidget*> parent,
|
||||
std::shared_ptr<Data::EmojiStatusCollectible> collectible,
|
||||
not_null<QWidget*> pointTo)
|
||||
: Ui::RpWidget(parent)
|
||||
, _st(st::infoGiftTooltip)
|
||||
, _collectible(std::move(collectible))
|
||||
, _text(_collectible->title)
|
||||
, _font(st::infoGiftTooltipFont)
|
||||
, _inner(_font->width(_text), _font->height)
|
||||
, _outer(_inner.grownBy(_st.padding))
|
||||
, _stroke(st::lineWidth)
|
||||
, _skip(2 * _stroke)
|
||||
, _full(_outer + QSize(2 * _skip, _st.arrow + 2 * _skip))
|
||||
, _glareSize(_outer.height() * 3)
|
||||
, _glareRange(_outer.width() + _glareSize)
|
||||
, _glareDuration(_glareRange * kGlareDurationStep / _glareSize)
|
||||
, _glareTimer([=] { showGlare(); }) {
|
||||
resize(_full + QSize(0, _st.shift));
|
||||
setupGeometry(pointTo);
|
||||
}
|
||||
|
||||
void Cover::BadgeTooltip::fade(bool shown) {
|
||||
if (_shown == shown) {
|
||||
return;
|
||||
}
|
||||
show();
|
||||
_shown = shown;
|
||||
_showAnimation.start([=] {
|
||||
update();
|
||||
if (!_showAnimation.animating()) {
|
||||
if (!_shown) {
|
||||
hide();
|
||||
} else {
|
||||
showGlare();
|
||||
}
|
||||
}
|
||||
}, _shown ? 0. : 1., _shown ? 1. : 0., _st.duration, anim::easeInCirc);
|
||||
}
|
||||
|
||||
void Cover::BadgeTooltip::showGlare() {
|
||||
_glareAnimation.start([=] {
|
||||
update();
|
||||
if (!_glareAnimation.animating()) {
|
||||
_glareTimer.callOnce(kGlareTimeout);
|
||||
}
|
||||
}, 0., 1., _glareDuration);
|
||||
}
|
||||
|
||||
void Cover::BadgeTooltip::finishAnimating() {
|
||||
_showAnimation.stop();
|
||||
if (!_shown) {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
crl::time Cover::BadgeTooltip::glarePeriod() const {
|
||||
return _glareDuration + kGlareTimeout;
|
||||
}
|
||||
|
||||
void Cover::BadgeTooltip::paintEvent(QPaintEvent *e) {
|
||||
const auto glare = _glareAnimation.value(0.);
|
||||
_glareRight = anim::interpolate(0, _glareRange, glare);
|
||||
prepareImage();
|
||||
|
||||
auto p = QPainter(this);
|
||||
const auto shown = _showAnimation.value(_shown ? 1. : 0.);
|
||||
p.setOpacity(shown);
|
||||
const auto imageHeight = _image.height() / _image.devicePixelRatio();
|
||||
const auto top = anim::interpolate(0, height() - imageHeight, shown);
|
||||
p.drawImage(0, top, _image);
|
||||
}
|
||||
|
||||
void Cover::BadgeTooltip::setupGeometry(not_null<QWidget*> pointTo) {
|
||||
auto widget = pointTo.get();
|
||||
const auto parent = parentWidget();
|
||||
|
||||
const auto refresh = [=] {
|
||||
const auto rect = Ui::MapFrom(parent, pointTo, pointTo->rect());
|
||||
const auto point = QPoint(rect.center().x(), rect.y());
|
||||
const auto left = point.x() - (width() / 2);
|
||||
const auto skip = _st.padding.left();
|
||||
setGeometry(
|
||||
std::min(std::max(left, skip), parent->width() - width() - skip),
|
||||
std::max(point.y() - height() - _st.margin.bottom(), skip),
|
||||
width(),
|
||||
height());
|
||||
const auto arrowMiddle = point.x() - x();
|
||||
if (_arrowMiddle != arrowMiddle) {
|
||||
_arrowMiddle = arrowMiddle;
|
||||
update();
|
||||
}
|
||||
};
|
||||
refresh();
|
||||
while (widget && widget != parent) {
|
||||
base::install_event_filter(this, widget, [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Resize || e->type() == QEvent::Move || e->type() == QEvent::ZOrderChange) {
|
||||
refresh();
|
||||
raise();
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
widget = widget->parentWidget();
|
||||
}
|
||||
}
|
||||
|
||||
void Cover::BadgeTooltip::prepareImage() {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto arrow = _st.arrow;
|
||||
const auto size = _full * ratio;
|
||||
if (_image.size() != size) {
|
||||
_image = QImage(size, QImage::Format_ARGB32_Premultiplied);
|
||||
_image.setDevicePixelRatio(ratio);
|
||||
} else if (_imageGlareRight == _glareRight
|
||||
&& _imageArrowMiddle == _arrowMiddle) {
|
||||
return;
|
||||
}
|
||||
_imageGlareRight = _glareRight;
|
||||
_imageArrowMiddle = _arrowMiddle;
|
||||
_image.fill(Qt::transparent);
|
||||
|
||||
const auto gfrom = _imageGlareRight - _glareSize;
|
||||
const auto gtill = _imageGlareRight;
|
||||
|
||||
auto path = QPainterPath();
|
||||
const auto width = _outer.width();
|
||||
const auto height = _outer.height();
|
||||
const auto radius = (height + 1) / 2;
|
||||
const auto diameter = height;
|
||||
path.moveTo(radius, 0);
|
||||
path.lineTo(width - radius, 0);
|
||||
path.arcTo(
|
||||
QRect(QPoint(width - diameter, 0), QSize(diameter, diameter)),
|
||||
90,
|
||||
-180);
|
||||
const auto xarrow = _arrowMiddle - _skip;
|
||||
if (xarrow - arrow <= radius || xarrow + arrow >= width - radius) {
|
||||
path.lineTo(radius, height);
|
||||
} else {
|
||||
path.lineTo(xarrow + arrow, height);
|
||||
path.lineTo(xarrow, height + arrow);
|
||||
path.lineTo(xarrow - arrow, height);
|
||||
path.lineTo(radius, height);
|
||||
}
|
||||
path.arcTo(
|
||||
QRect(QPoint(0, 0), QSize(diameter, diameter)),
|
||||
-90,
|
||||
-180);
|
||||
path.closeSubpath();
|
||||
|
||||
auto p = QPainter(&_image);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
if (gtill > 0) {
|
||||
auto gradient = QLinearGradient(gfrom, 0, gtill, 0);
|
||||
gradient.setStops({
|
||||
{ 0., _collectible->edgeColor },
|
||||
{ 0.5, _collectible->centerColor },
|
||||
{ 1., _collectible->edgeColor },
|
||||
});
|
||||
p.setBrush(gradient);
|
||||
} else {
|
||||
p.setBrush(_collectible->edgeColor);
|
||||
}
|
||||
p.translate(_skip, _skip);
|
||||
p.drawPath(path);
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
p.setBrush(Qt::NoBrush);
|
||||
auto copy = _collectible->textColor;
|
||||
copy.setAlpha(0);
|
||||
if (gtill > 0) {
|
||||
auto gradient = QLinearGradient(gfrom, 0, gtill, 0);
|
||||
gradient.setStops({
|
||||
{ 0., copy },
|
||||
{ 0.5, _collectible->textColor },
|
||||
{ 1., copy },
|
||||
});
|
||||
p.setPen(QPen(gradient, _stroke));
|
||||
} else {
|
||||
p.setPen(QPen(copy, _stroke));
|
||||
}
|
||||
p.drawPath(path);
|
||||
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
p.setFont(_font);
|
||||
p.setPen(QColor(255, 255, 255));
|
||||
p.drawText(_st.padding.left(), _st.padding.top() + _font->ascent, _text);
|
||||
}
|
||||
|
||||
TopicIconView::TopicIconView(
|
||||
not_null<Data::ForumTopic*> topic,
|
||||
Fn<bool()> paused,
|
||||
@@ -583,20 +297,6 @@ Cover::Cover(
|
||||
nullptr) {
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<Badge::Content> BotVerifyBadgeForPeer(
|
||||
not_null<PeerData*> peer) {
|
||||
return peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::VerifyInfo
|
||||
) | rpl::map([=] {
|
||||
const auto info = peer->botVerifyDetails();
|
||||
return Badge::Content{
|
||||
.badge = info ? BadgeType::BotVerified : BadgeType::None,
|
||||
.emojiStatusId = { info ? info->iconId : DocumentId() },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
Cover::Cover(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
@@ -685,8 +385,8 @@ Cover::Cover(
|
||||
: Fn<Data::StarsRatingPending()>()))
|
||||
: nullptr)
|
||||
, _status(this, _st.status)
|
||||
, _showLastSeen(this, tr::lng_status_lastseen_when(), _st.showLastSeen)
|
||||
, _refreshStatusTimer([this] { refreshStatusText(); }) {
|
||||
, _statusLabel(std::make_unique<StatusLabel>(_status.data(), _peer))
|
||||
, _showLastSeen(this, tr::lng_status_lastseen_when(), _st.showLastSeen) {
|
||||
_peer->updateFull();
|
||||
if (const auto broadcast = _peer->monoforumBroadcast()) {
|
||||
broadcast->updateFull();
|
||||
@@ -740,9 +440,6 @@ Cover::Cover(
|
||||
initViewers(std::move(title));
|
||||
setupChildGeometry();
|
||||
setupUniqueBadgeTooltip();
|
||||
if (_role != Role::EditContact) {
|
||||
setupSavedMusic();
|
||||
}
|
||||
|
||||
if (_userpic) {
|
||||
} else if (topic->canEdit()) {
|
||||
@@ -845,45 +542,13 @@ void Cover::setupChildGeometry() {
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void Cover::setupSavedMusic() {
|
||||
if (!Data::SavedMusic::Supported(_peer->id)) {
|
||||
return;
|
||||
}
|
||||
Data::SavedMusicList(
|
||||
_peer,
|
||||
nullptr,
|
||||
1
|
||||
) | rpl::map([=](const Data::SavedMusicSlice &data) {
|
||||
return data.size() ? data[0].get() : nullptr;
|
||||
}) | rpl::start_with_next([=](HistoryItem *item) {
|
||||
const auto media = item ? item->media() : nullptr;
|
||||
const auto document = media ? media->document() : nullptr;
|
||||
if (!document) {
|
||||
_musicButton = nullptr;
|
||||
resize(width(), _st.height);
|
||||
} else if (!_musicButton) {
|
||||
using namespace Info::Saved;
|
||||
_musicButton = std::make_unique<MusicButton>(
|
||||
this,
|
||||
DocumentMusicButtonData(document),
|
||||
[=] { _controller->showSection(MakeMusic(_peer)); });
|
||||
_musicButton->show();
|
||||
|
||||
widthValue(
|
||||
) | rpl::start_with_next([=](int newWidth) {
|
||||
_musicButton->resizeToWidth(newWidth);
|
||||
const auto skip = st::infoMusicButtonBottom;
|
||||
_musicButton->moveToLeft(0, _st.height - skip, newWidth);
|
||||
resize(width(), _st.height + _musicButton->height());
|
||||
}, _musicButton->lifetime());
|
||||
} else {
|
||||
_musicButton->updateData(DocumentMusicButtonData(document));
|
||||
Cover *Cover::setOnlineCount(rpl::producer<int> &&count) {
|
||||
std::move(count) | rpl::start_with_next([=](int value) {
|
||||
if (_statusLabel) {
|
||||
_statusLabel->setOnlineCount(value);
|
||||
refreshStatusGeometry(width());
|
||||
}
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
Cover *Cover::setOnlineCount(rpl::producer<int> &&count) {
|
||||
_onlineCount = std::move(count);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -900,13 +565,16 @@ void Cover::initViewers(rpl::producer<QString> title) {
|
||||
refreshNameGeometry(width());
|
||||
}, lifetime());
|
||||
|
||||
rpl::combine(
|
||||
_peer->session().changes().peerFlagsValue(
|
||||
_peer,
|
||||
Flag::OnlineStatus | Flag::Members),
|
||||
_onlineCount.value()
|
||||
_statusLabel->setMembersLinkCallback([=] {
|
||||
_showSection.fire(Section::Type::Members);
|
||||
});
|
||||
|
||||
_peer->session().changes().peerFlagsValue(
|
||||
_peer,
|
||||
Flag::OnlineStatus | Flag::Members
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshStatusText();
|
||||
_statusLabel->refresh();
|
||||
refreshStatusGeometry(width());
|
||||
}, lifetime());
|
||||
|
||||
_peer->session().changes().peerFlagsValue(
|
||||
@@ -1056,62 +724,7 @@ void Cover::setupChangePersonal() {
|
||||
}, _changePersonal->lifetime());
|
||||
}
|
||||
|
||||
void Cover::refreshStatusText() {
|
||||
auto hasMembersLink = [&] {
|
||||
if (auto megagroup = _peer->asMegagroup()) {
|
||||
return megagroup->canViewMembers();
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
auto statusText = [&]() -> TextWithEntities {
|
||||
using namespace Ui::Text;
|
||||
auto currentTime = base::unixtime::now();
|
||||
if (auto user = _peer->asUser()) {
|
||||
const auto result = Data::OnlineTextFull(user, currentTime);
|
||||
const auto showOnline = Data::OnlineTextActive(user, currentTime);
|
||||
const auto updateIn = Data::OnlineChangeTimeout(user, currentTime);
|
||||
if (showOnline) {
|
||||
_refreshStatusTimer.callOnce(updateIn);
|
||||
}
|
||||
return showOnline
|
||||
? Ui::Text::Colorized(result)
|
||||
: TextWithEntities{ .text = result };
|
||||
} else if (auto chat = _peer->asChat()) {
|
||||
if (!chat->amIn()) {
|
||||
return tr::lng_chat_status_unaccessible({}, WithEntities);
|
||||
}
|
||||
const auto onlineCount = _onlineCount.current();
|
||||
const auto fullCount = std::max(
|
||||
chat->count,
|
||||
int(chat->participants.size()));
|
||||
return { .text = ChatStatusText(fullCount, onlineCount, true) };
|
||||
} else if (auto broadcast = _peer->monoforumBroadcast()) {
|
||||
auto result = ChatStatusText(
|
||||
qMax(broadcast->membersCount(), 1),
|
||||
0,
|
||||
false);
|
||||
return TextWithEntities{ .text = result };
|
||||
} else if (auto channel = _peer->asChannel()) {
|
||||
const auto onlineCount = _onlineCount.current();
|
||||
const auto fullCount = qMax(channel->membersCount(), 1);
|
||||
auto result = ChatStatusText(
|
||||
fullCount,
|
||||
onlineCount,
|
||||
channel->isMegagroup());
|
||||
return hasMembersLink
|
||||
? Ui::Text::Link(result)
|
||||
: TextWithEntities{ .text = result };
|
||||
}
|
||||
return tr::lng_chat_status_unaccessible(tr::now, WithEntities);
|
||||
}();
|
||||
_status->setMarkedText(statusText);
|
||||
if (hasMembersLink) {
|
||||
_status->setLink(1, std::make_shared<LambdaClickHandler>([=] {
|
||||
_showSection.fire(Section::Type::Members);
|
||||
}));
|
||||
}
|
||||
refreshStatusGeometry(width());
|
||||
}
|
||||
|
||||
|
||||
Cover::~Cover() {
|
||||
base::take(_badgeTooltip);
|
||||
|
||||
@@ -44,9 +44,12 @@ struct InfoProfileCover;
|
||||
|
||||
namespace Info::Profile {
|
||||
|
||||
class BadgeTooltip;
|
||||
class EmojiStatusPanel;
|
||||
class MusicButton;
|
||||
class Badge;
|
||||
class StatusLabel;
|
||||
|
||||
[[nodiscard]] QMargins LargeCustomEmojiMargins();
|
||||
|
||||
class TopicIconView final {
|
||||
public:
|
||||
@@ -128,7 +131,6 @@ public:
|
||||
[[nodiscard]] std::optional<QImage> updatedPersonalPhoto() const;
|
||||
|
||||
private:
|
||||
class BadgeTooltip;
|
||||
|
||||
Cover(
|
||||
QWidget *parent,
|
||||
@@ -141,9 +143,7 @@ private:
|
||||
|
||||
void setupShowLastSeen();
|
||||
void setupChildGeometry();
|
||||
void setupSavedMusic();
|
||||
void initViewers(rpl::producer<QString> title);
|
||||
void refreshStatusText();
|
||||
void refreshNameGeometry(int newWidth);
|
||||
void refreshStatusGeometry(int newWidth);
|
||||
void refreshUploadPhotoOverlay();
|
||||
@@ -161,7 +161,6 @@ private:
|
||||
rpl::variable<Badge::Content> _badgeContent;
|
||||
const std::unique_ptr<Badge> _badge;
|
||||
const std::unique_ptr<Badge> _verified;
|
||||
rpl::variable<int> _onlineCount;
|
||||
|
||||
const Fn<not_null<QWidget*>()> _parentForTooltip;
|
||||
std::unique_ptr<BadgeTooltip> _badgeTooltip;
|
||||
@@ -176,12 +175,10 @@ private:
|
||||
object_ptr<Ui::FlatLabel> _name = { nullptr };
|
||||
std::unique_ptr<Ui::StarsRating> _starsRating;
|
||||
object_ptr<Ui::FlatLabel> _status = { nullptr };
|
||||
std::unique_ptr<StatusLabel> _statusLabel;
|
||||
rpl::variable<int> _statusShift = 0;
|
||||
object_ptr<Ui::RoundButton> _showLastSeen = { nullptr };
|
||||
//object_ptr<CoverDropArea> _dropArea = { nullptr };
|
||||
base::Timer _refreshStatusTimer;
|
||||
|
||||
std::unique_ptr<MusicButton> _musicButton;
|
||||
|
||||
rpl::event_stream<Section> _showSection;
|
||||
|
||||
|
||||
@@ -8,14 +8,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "info/profile/info_profile_inner_widget.h"
|
||||
|
||||
#include "info/info_controller.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "info/profile/info_profile_widget.h"
|
||||
#include "info/profile/info_profile_cover.h"
|
||||
#include "info/profile/info_profile_icon.h"
|
||||
#include "info/profile/info_profile_members.h"
|
||||
#include "info/profile/info_profile_music_button.h"
|
||||
#include "info/profile/info_profile_top_bar.h"
|
||||
#include "info/profile/info_profile_actions.h"
|
||||
#include "info/media/info_media_buttons.h"
|
||||
#include "info/saved/info_saved_music_widget.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_photo.h"
|
||||
@@ -23,22 +27,56 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_saved_music.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "info_profile_actions.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_peer_photo.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/text/format_song_document_name.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/wrap/fade_wrap.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "styles/style_info.h"
|
||||
|
||||
namespace Info {
|
||||
namespace Profile {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] MusicButtonData DocumentMusicButtonData(
|
||||
not_null<DocumentData*> document) {
|
||||
return { Ui::Text::FormatSongNameFor(document) };
|
||||
}
|
||||
|
||||
void AddAboutVerification(
|
||||
not_null<Ui::VerticalLayout*> layout,
|
||||
not_null<PeerData*> peer) {
|
||||
const auto inner = layout->add(object_ptr<Ui::VerticalLayout>(layout));
|
||||
peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::VerifyInfo
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto info = peer->botVerifyDetails();
|
||||
while (inner->count()) {
|
||||
delete inner->widgetAt(0);
|
||||
}
|
||||
if (!info) {
|
||||
Ui::AddDivider(inner);
|
||||
} else if (!info->description.empty()) {
|
||||
Ui::AddDividerText(inner, rpl::single(info->description));
|
||||
}
|
||||
inner->resizeToWidth(inner->width());
|
||||
}, inner->lifetime());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
InnerWidget::InnerWidget(
|
||||
QWidget *parent,
|
||||
not_null<Controller*> controller,
|
||||
@@ -78,7 +116,7 @@ object_ptr<Ui::RpWidget> InnerWidget::setupContent(
|
||||
}
|
||||
|
||||
auto result = object_ptr<Ui::VerticalLayout>(parent);
|
||||
_cover = AddCover(result, _controller, _peer, _topic, _sublist);
|
||||
setupSavedMusic(result);
|
||||
if (_topic && _topic->creating()) {
|
||||
return result;
|
||||
}
|
||||
@@ -98,7 +136,7 @@ object_ptr<Ui::RpWidget> InnerWidget::setupContent(
|
||||
}
|
||||
}
|
||||
if (auto actions = SetupActions(_controller, result.data(), _peer)) {
|
||||
result->add(object_ptr<Ui::BoxContentDivider>(result));
|
||||
addAboutVerificationOrDivider(result);
|
||||
result->add(std::move(actions));
|
||||
}
|
||||
if (_peer->isChat() || _peer->isMegagroup()) {
|
||||
@@ -114,7 +152,7 @@ void InnerWidget::setupMembers(not_null<Ui::VerticalLayout*> container) {
|
||||
container,
|
||||
object_ptr<Ui::VerticalLayout>(container)));
|
||||
const auto inner = wrap->entity();
|
||||
inner->add(object_ptr<Ui::BoxContentDivider>(inner));
|
||||
addAboutVerificationOrDivider(inner);
|
||||
_members = inner->add(object_ptr<Members>(inner, _controller));
|
||||
_members->scrollToRequests(
|
||||
) | rpl::start_with_next([this](Ui::ScrollToRequest request) {
|
||||
@@ -128,7 +166,10 @@ void InnerWidget::setupMembers(not_null<Ui::VerticalLayout*> container) {
|
||||
: MapFrom(this, _members, QPoint(0, request.ymax)).y();
|
||||
_scrollToRequests.fire({ min, max });
|
||||
}, _members->lifetime());
|
||||
_cover->setOnlineCount(_members->onlineCountValue());
|
||||
_members->onlineCountValue(
|
||||
) | rpl::start_with_next([=](int count) {
|
||||
_onlineCount.fire_copy(count);
|
||||
}, _members->lifetime());
|
||||
|
||||
using namespace rpl::mappers;
|
||||
wrap->toggleOn(
|
||||
@@ -136,6 +177,60 @@ void InnerWidget::setupMembers(not_null<Ui::VerticalLayout*> container) {
|
||||
anim::type::instant);
|
||||
}
|
||||
|
||||
void InnerWidget::setupSavedMusic(not_null<Ui::VerticalLayout*> container) {
|
||||
auto musicValue = Data::SavedMusic::Supported(_peer->id)
|
||||
? Data::SavedMusicList(
|
||||
_peer,
|
||||
nullptr,
|
||||
1
|
||||
) | rpl::map([=](const Data::SavedMusicSlice &data) {
|
||||
return data.size() ? data[0].get() : nullptr;
|
||||
}) | rpl::type_erased()
|
||||
: rpl::single<HistoryItem*>((HistoryItem*)(nullptr));
|
||||
|
||||
const auto divider = container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
object_ptr<Ui::VerticalLayout>(container)));
|
||||
|
||||
rpl::combine(
|
||||
std::move(musicValue),
|
||||
_topBarColor.value()
|
||||
) | rpl::start_with_next([=](
|
||||
HistoryItem *item,
|
||||
std::optional<QColor> color) {
|
||||
while (divider->entity()->count()) {
|
||||
delete divider->entity()->widgetAt(0);
|
||||
}
|
||||
if (item) {
|
||||
if (const auto document = item->media()
|
||||
? item->media()->document()
|
||||
: nullptr) {
|
||||
const auto music = divider->entity()->add(
|
||||
object_ptr<MusicButton>(
|
||||
divider->entity(),
|
||||
DocumentMusicButtonData(document),
|
||||
[window = _controller, peer = _peer] {
|
||||
window->showSection(Info::Saved::MakeMusic(peer));
|
||||
}));
|
||||
music->setOverrideBg(color);
|
||||
}
|
||||
divider->toggle(true, anim::type::normal);
|
||||
}
|
||||
}, lifetime());
|
||||
divider->finishAnimating();
|
||||
}
|
||||
|
||||
void InnerWidget::addAboutVerificationOrDivider(
|
||||
not_null<Ui::VerticalLayout*> content) {
|
||||
if (_aboutVerificationAdded) {
|
||||
Ui::AddDivider(content);
|
||||
} else {
|
||||
AddAboutVerification(content, _peer);
|
||||
_aboutVerificationAdded = true;
|
||||
}
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> InnerWidget::setupSharedMedia(
|
||||
not_null<RpWidget*> parent) {
|
||||
using namespace rpl::mappers;
|
||||
@@ -263,16 +358,10 @@ object_ptr<Ui::RpWidget> InnerWidget::setupSharedMedia(
|
||||
|
||||
auto layout = result->entity();
|
||||
|
||||
layout->add(object_ptr<Ui::BoxContentDivider>(layout));
|
||||
layout->add(object_ptr<Ui::FixedHeightWidget>(
|
||||
layout,
|
||||
st::infoSharedMediaBottomSkip)
|
||||
)->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
addAboutVerificationOrDivider(layout);
|
||||
Ui::AddSkip(layout, st::infoSharedMediaBottomSkip);
|
||||
layout->add(std::move(content));
|
||||
layout->add(object_ptr<Ui::FixedHeightWidget>(
|
||||
layout,
|
||||
st::infoSharedMediaBottomSkip)
|
||||
)->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
Ui::AddSkip(layout, st::infoSharedMediaBottomSkip);
|
||||
|
||||
_sharedMediaWrap = result;
|
||||
return result;
|
||||
@@ -323,5 +412,38 @@ int InnerWidget::resizeGetHeight(int newWidth) {
|
||||
return _content->heightNoMargins();
|
||||
}
|
||||
|
||||
void InnerWidget::enableBackButton() {
|
||||
_backToggles.force_assign(true);
|
||||
}
|
||||
|
||||
void InnerWidget::showFinished() {
|
||||
_showFinished.fire({});
|
||||
}
|
||||
|
||||
bool InnerWidget::hasFlexibleTopBar() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
base::weak_qptr<Ui::RpWidget> InnerWidget::createPinnedToTop(
|
||||
not_null<Ui::RpWidget*> parent) {
|
||||
const auto content = Ui::CreateChild<TopBar>(
|
||||
parent,
|
||||
TopBar::Descriptor{
|
||||
.controller = _controller->parentController(),
|
||||
.key = _controller->key(),
|
||||
.wrap = _controller->wrapValue(),
|
||||
.backToggles = _backToggles.value(),
|
||||
.showFinished = _showFinished.events(),
|
||||
});
|
||||
content->setOnlineCount(_onlineCount.events());
|
||||
_topBarColor = content->edgeColor();
|
||||
return base::make_weak(not_null<Ui::RpWidget*>{ content });
|
||||
}
|
||||
|
||||
base::weak_qptr<Ui::RpWidget> InnerWidget::createPinnedToBottom(
|
||||
not_null<Ui::RpWidget*> parent) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace Profile
|
||||
} // namespace Info
|
||||
|
||||
@@ -37,7 +37,6 @@ namespace Profile {
|
||||
|
||||
class Memento;
|
||||
class Members;
|
||||
class Cover;
|
||||
struct Origin;
|
||||
|
||||
class InnerWidget final : public Ui::RpWidget {
|
||||
@@ -53,6 +52,15 @@ public:
|
||||
rpl::producer<Ui::ScrollToRequest> scrollToRequests() const;
|
||||
rpl::producer<int> desiredHeightValue() const override;
|
||||
|
||||
bool hasFlexibleTopBar() const;
|
||||
base::weak_qptr<Ui::RpWidget> createPinnedToTop(
|
||||
not_null<Ui::RpWidget*> parent);
|
||||
base::weak_qptr<Ui::RpWidget> createPinnedToBottom(
|
||||
not_null<Ui::RpWidget*> parent);
|
||||
|
||||
void enableBackButton();
|
||||
void showFinished();
|
||||
|
||||
protected:
|
||||
int resizeGetHeight(int newWidth) override;
|
||||
void visibleTopBottomUpdated(
|
||||
@@ -65,30 +73,40 @@ private:
|
||||
Origin origin);
|
||||
object_ptr<RpWidget> setupSharedMedia(not_null<RpWidget*> parent);
|
||||
void setupMembers(not_null<Ui::VerticalLayout*> container);
|
||||
void setupSavedMusic(not_null<Ui::VerticalLayout*> container);
|
||||
|
||||
int countDesiredHeight() const;
|
||||
void updateDesiredHeight() {
|
||||
_desiredHeight.fire(countDesiredHeight());
|
||||
}
|
||||
|
||||
void addAboutVerificationOrDivider(not_null<Ui::VerticalLayout*> content);
|
||||
|
||||
const not_null<Controller*> _controller;
|
||||
const not_null<PeerData*> _peer;
|
||||
PeerData * const _migrated = nullptr;
|
||||
Data::ForumTopic * const _topic = nullptr;
|
||||
Data::SavedSublist * const _sublist = nullptr;
|
||||
|
||||
bool _inResize = false;
|
||||
rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
|
||||
rpl::event_stream<int> _desiredHeight;
|
||||
|
||||
rpl::variable<bool> _backToggles;
|
||||
rpl::event_stream<int> _onlineCount;
|
||||
rpl::event_stream<> _showFinished;
|
||||
|
||||
PeerData *_reactionGroup = nullptr;
|
||||
|
||||
std::shared_ptr<Data::PhotoMedia> _nonPersonalView;
|
||||
|
||||
rpl::variable<std::optional<QColor>> _topBarColor;
|
||||
|
||||
Members *_members = nullptr;
|
||||
Cover *_cover = nullptr;
|
||||
Ui::SlideWrap<RpWidget> *_sharedMediaWrap = nullptr;
|
||||
object_ptr<RpWidget> _content;
|
||||
|
||||
bool _inResize = false;
|
||||
rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
|
||||
rpl::event_stream<int> _desiredHeight;
|
||||
bool _aboutVerificationAdded = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -7,8 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "info/profile/info_profile_music_button.h"
|
||||
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/effects/animation_value.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_info.h"
|
||||
|
||||
namespace Info::Profile {
|
||||
@@ -18,79 +22,119 @@ MusicButton::MusicButton(
|
||||
MusicButtonData data,
|
||||
Fn<void()> handler)
|
||||
: RippleButton(parent, st::infoMusicButtonRipple)
|
||||
, _performer(std::make_unique<Ui::FlatLabel>(
|
||||
this,
|
||||
u"- "_q + data.performer,
|
||||
st::infoMusicButtonPerformer))
|
||||
, _title(std::make_unique<Ui::FlatLabel>(
|
||||
this,
|
||||
data.title,
|
||||
st::infoMusicButtonTitle)) {
|
||||
rpl::combine(
|
||||
_title->naturalWidthValue(),
|
||||
_performer->naturalWidthValue()
|
||||
) | rpl::start_with_next([=] {
|
||||
resizeToWidth(widthNoMargins());
|
||||
}, lifetime());
|
||||
|
||||
_title->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
_performer->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
, _noteSymbol(u"\u266B"_q + QChar(' '))
|
||||
, _noteWidth(st::normalFont->width(_noteSymbol)) {
|
||||
updateData(std::move(data));
|
||||
setClickedCallback(std::move(handler));
|
||||
}
|
||||
|
||||
MusicButton::~MusicButton() = default;
|
||||
|
||||
void MusicButton::updateData(MusicButtonData data) {
|
||||
_performer->setText(u"- "_q + data.performer);
|
||||
_title->setText(data.title);
|
||||
resizeToWidth(widthNoMargins());
|
||||
const auto result = data.name.textWithEntities();
|
||||
const auto performerLength = result.entities.empty()
|
||||
? 0
|
||||
: int(result.entities.front().length());
|
||||
_performer.setText(
|
||||
st::semiboldTextStyle,
|
||||
result.text.mid(0, performerLength));
|
||||
_title.setText(
|
||||
st::defaultTextStyle,
|
||||
result.text.mid(performerLength, result.text.size()));
|
||||
update();
|
||||
}
|
||||
|
||||
void MusicButton::setOverrideBg(std::optional<QColor> color) {
|
||||
_overrideBg = color;
|
||||
update();
|
||||
}
|
||||
|
||||
void MusicButton::paintEvent(QPaintEvent *e) {
|
||||
auto p = QPainter(this);
|
||||
|
||||
p.fillRect(e->rect(), st::windowBgOver);
|
||||
if (_overrideBg) {
|
||||
p.fillRect(e->rect(), Ui::BlendColors(
|
||||
*_overrideBg,
|
||||
Qt::black,
|
||||
st::infoProfileTopBarActionButtonBgOpacity));
|
||||
} else {
|
||||
p.fillRect(e->rect(), st::shadowFg);
|
||||
}
|
||||
paintRipple(p, QPoint());
|
||||
|
||||
auto pen = st::windowBoldFg->p;
|
||||
pen.setCapStyle(Qt::RoundCap);
|
||||
pen.setWidthF(st::infoMusicButtonLine);
|
||||
p.setPen(pen);
|
||||
const auto &icon = st::topicButtonArrow;
|
||||
const auto iconWidth = icon.width();
|
||||
const auto iconHeight = icon.height();
|
||||
|
||||
const auto line = st::infoMusicButtonLine;
|
||||
const auto length = height() / 4.;
|
||||
const auto half = height() / 2.;
|
||||
const auto left = st::infoProfileCover.photoLeft + (line / 2.);
|
||||
const auto padding = st::infoMusicButtonPadding;
|
||||
const auto skip = st::normalFont->spacew;
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.drawLine(
|
||||
left, half - length / 2.,
|
||||
left, half + length / 2.);
|
||||
p.drawLine(
|
||||
left + 2.5 * line, half - length,
|
||||
left + 2.5 * line, half + length);
|
||||
p.drawLine(
|
||||
left + 5 * line, half - length * 3 / 4.,
|
||||
left + 5 * line, half + length * 3 / 4.);
|
||||
const auto titleWidth = _title.maxWidth();
|
||||
const auto performerWidth = _performer.maxWidth();
|
||||
const auto totalNeeded = titleWidth + performerWidth + skip;
|
||||
const auto availableWidth = width()
|
||||
- rect::m::sum::h(padding)
|
||||
- iconWidth
|
||||
- skip
|
||||
- _noteWidth;
|
||||
|
||||
auto actualTitleWidth = 0;
|
||||
auto actualPerformerWidth = 0;
|
||||
if (totalNeeded <= availableWidth) {
|
||||
actualTitleWidth = titleWidth;
|
||||
actualPerformerWidth = performerWidth;
|
||||
} else {
|
||||
const auto ratio = float64(titleWidth) / totalNeeded;
|
||||
actualPerformerWidth = int(availableWidth * (1.0 - ratio));
|
||||
actualTitleWidth = availableWidth - actualPerformerWidth;
|
||||
}
|
||||
|
||||
const auto totalContentWidth = _noteWidth
|
||||
+ actualPerformerWidth
|
||||
+ skip
|
||||
+ actualTitleWidth
|
||||
+ skip
|
||||
+ iconWidth;
|
||||
const auto centerX = width() / 2;
|
||||
const auto contentStartX = centerX - totalContentWidth / 2;
|
||||
const auto textTop = (height() - st::normalFont->height) / 2;
|
||||
|
||||
p.setPen(_overrideBg ? st::groupCallMembersFg : st::windowBoldFg);
|
||||
p.setFont(st::normalFont);
|
||||
p.drawText(contentStartX, textTop + st::normalFont->ascent, _noteSymbol);
|
||||
|
||||
_performer.draw(p, {
|
||||
.position = { contentStartX + _noteWidth, textTop },
|
||||
.availableWidth = actualPerformerWidth,
|
||||
.now = crl::now(),
|
||||
.elisionLines = 1,
|
||||
.elisionMiddle = true,
|
||||
});
|
||||
|
||||
p.setPen(_overrideBg ? st::groupCallVideoSubTextFg : st::windowSubTextFg);
|
||||
_title.draw(p, {
|
||||
.position = QPoint(
|
||||
contentStartX + _noteWidth + actualPerformerWidth + skip,
|
||||
textTop),
|
||||
.availableWidth = actualTitleWidth,
|
||||
.now = crl::now(),
|
||||
.elisionLines = 1,
|
||||
.elisionMiddle = true,
|
||||
});
|
||||
|
||||
const auto iconLeft = contentStartX
|
||||
+ _noteWidth
|
||||
+ actualPerformerWidth
|
||||
+ actualTitleWidth
|
||||
+ skip
|
||||
+ skip;
|
||||
const auto iconTop = (height() - iconHeight) / 2;
|
||||
icon.paint(p, iconLeft, iconTop, iconWidth, p.pen().color());
|
||||
}
|
||||
|
||||
int MusicButton::resizeGetHeight(int newWidth) {
|
||||
const auto padding = st::infoMusicButtonPadding;
|
||||
const auto &font = st::infoMusicButtonTitle.style.font;
|
||||
|
||||
const auto top = padding.top();
|
||||
const auto skip = st::normalFont->spacew;
|
||||
const auto available = newWidth - padding.left() - padding.right();
|
||||
_title->resizeToNaturalWidth(available);
|
||||
_title->moveToLeft(padding.left(), top);
|
||||
if (const auto left = available - _title->width() - skip; left > 0) {
|
||||
_performer->show();
|
||||
_performer->resizeToNaturalWidth(left);
|
||||
_performer->moveToLeft(padding.left() + _title->width() + skip, top);
|
||||
} else {
|
||||
_performer->hide();
|
||||
}
|
||||
const auto &font = st::defaultTextStyle.font;
|
||||
|
||||
return padding.top() + font->height + padding.bottom();
|
||||
}
|
||||
|
||||
@@ -8,16 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
#include "ui/widgets/buttons.h"
|
||||
|
||||
namespace Ui {
|
||||
class FlatLabel;
|
||||
} // namespace Ui
|
||||
#include "ui/text/format_song_name.h"
|
||||
#include "ui/text/text.h"
|
||||
|
||||
namespace Info::Profile {
|
||||
|
||||
struct MusicButtonData {
|
||||
QString performer;
|
||||
QString title;
|
||||
Ui::Text::FormatSongName name;
|
||||
};
|
||||
|
||||
class MusicButton final : public Ui::RippleButton {
|
||||
@@ -26,13 +23,18 @@ public:
|
||||
~MusicButton();
|
||||
|
||||
void updateData(MusicButtonData data);
|
||||
void setOverrideBg(std::optional<QColor> color);
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
int resizeGetHeight(int newWidth) override;
|
||||
|
||||
std::unique_ptr<Ui::FlatLabel> _performer;
|
||||
std::unique_ptr<Ui::FlatLabel> _title;
|
||||
Ui::Text::String _performer;
|
||||
Ui::Text::String _title;
|
||||
std::optional<QColor> _overrideBg;
|
||||
|
||||
const QString _noteSymbol;
|
||||
const int _noteWidth;
|
||||
|
||||
};
|
||||
|
||||
|
||||
152
Telegram/SourceFiles/info/profile/info_profile_status_label.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
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/profile/info_profile_status_label.h"
|
||||
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/basic_click_handlers.h"
|
||||
#include "base/unixtime.h"
|
||||
|
||||
namespace Info::Profile {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] auto MembersStatusText(int count) {
|
||||
return tr::lng_chat_status_members(tr::now, lt_count_decimal, count);
|
||||
};
|
||||
|
||||
[[nodiscard]] auto OnlineStatusText(int count) {
|
||||
return tr::lng_chat_status_online(tr::now, lt_count_decimal, count);
|
||||
};
|
||||
|
||||
[[nodiscard]] auto ChatStatusText(
|
||||
int fullCount,
|
||||
int onlineCount,
|
||||
bool isGroup) {
|
||||
if (onlineCount > 1 && onlineCount <= fullCount) {
|
||||
return tr::lng_chat_status_members_online(
|
||||
tr::now,
|
||||
lt_members_count,
|
||||
MembersStatusText(fullCount),
|
||||
lt_online_count,
|
||||
OnlineStatusText(onlineCount));
|
||||
} else if (fullCount > 0) {
|
||||
return isGroup
|
||||
? tr::lng_chat_status_members(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
fullCount)
|
||||
: tr::lng_chat_status_subscribers(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
fullCount);
|
||||
}
|
||||
return isGroup
|
||||
? tr::lng_group_status(tr::now)
|
||||
: tr::lng_channel_status(tr::now);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
StatusLabel::StatusLabel(
|
||||
not_null<Ui::FlatLabel*> label,
|
||||
not_null<PeerData*> peer)
|
||||
: _label(label)
|
||||
, _peer(peer)
|
||||
, _refreshTimer([=] { refresh(); }) {
|
||||
}
|
||||
|
||||
void StatusLabel::setOnlineCount(int count) {
|
||||
_onlineCount = count;
|
||||
refresh();
|
||||
}
|
||||
|
||||
void StatusLabel::refresh() {
|
||||
auto hasMembersLink = [&] {
|
||||
if (auto megagroup = _peer->asMegagroup()) {
|
||||
return megagroup->canViewMembers();
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
auto statusText = [&]() -> TextWithEntities {
|
||||
using namespace Ui::Text;
|
||||
auto currentTime = base::unixtime::now();
|
||||
if (auto user = _peer->asUser()) {
|
||||
const auto result = Data::OnlineTextFull(user, currentTime);
|
||||
const auto showOnline = Data::OnlineTextActive(
|
||||
user,
|
||||
currentTime);
|
||||
const auto updateIn = Data::OnlineChangeTimeout(
|
||||
user,
|
||||
currentTime);
|
||||
if (showOnline) {
|
||||
_refreshTimer.callOnce(updateIn);
|
||||
}
|
||||
return (showOnline && _colorized)
|
||||
? Ui::Text::Colorized(result)
|
||||
: TextWithEntities{ .text = result };
|
||||
} else if (auto chat = _peer->asChat()) {
|
||||
if (!chat->amIn()) {
|
||||
return tr::lng_chat_status_unaccessible(
|
||||
{},
|
||||
WithEntities);
|
||||
}
|
||||
const auto onlineCount = _onlineCount;
|
||||
const auto fullCount = std::max(
|
||||
chat->count,
|
||||
int(chat->participants.size()));
|
||||
return { .text = ChatStatusText(
|
||||
fullCount,
|
||||
onlineCount,
|
||||
true) };
|
||||
} else if (auto broadcast = _peer->monoforumBroadcast()) {
|
||||
auto result = ChatStatusText(
|
||||
qMax(broadcast->membersCount(), 1),
|
||||
0,
|
||||
false);
|
||||
return TextWithEntities{ .text = result };
|
||||
} else if (auto channel = _peer->asChannel()) {
|
||||
const auto onlineCount = _onlineCount;
|
||||
const auto fullCount = qMax(channel->membersCount(), 1);
|
||||
auto result = ChatStatusText(
|
||||
fullCount,
|
||||
onlineCount,
|
||||
channel->isMegagroup());
|
||||
return hasMembersLink
|
||||
? Ui::Text::Link(result)
|
||||
: TextWithEntities{ .text = result };
|
||||
}
|
||||
return tr::lng_chat_status_unaccessible(tr::now, WithEntities);
|
||||
}();
|
||||
_label->setMarkedText(statusText);
|
||||
if (hasMembersLink && _membersLinkCallback) {
|
||||
_label->setLink(
|
||||
1,
|
||||
std::make_shared<LambdaClickHandler>(_membersLinkCallback));
|
||||
}
|
||||
}
|
||||
|
||||
void StatusLabel::setMembersLinkCallback(Fn<void()> callback) {
|
||||
_membersLinkCallback = std::move(callback);
|
||||
}
|
||||
|
||||
Fn<void()> StatusLabel::membersLinkCallback() const {
|
||||
return _membersLinkCallback;
|
||||
}
|
||||
|
||||
void StatusLabel::setColorized(bool enabled) {
|
||||
_colorized = enabled;
|
||||
refresh();
|
||||
}
|
||||
|
||||
} // namespace Info::Profile
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
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 "base/timer.h"
|
||||
|
||||
namespace Ui {
|
||||
class FlatLabel;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Info::Profile {
|
||||
|
||||
class StatusLabel final {
|
||||
public:
|
||||
StatusLabel(
|
||||
not_null<Ui::FlatLabel*> label,
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
void refresh();
|
||||
void setMembersLinkCallback(Fn<void()> callback);
|
||||
[[nodiscard]] Fn<void()> membersLinkCallback() const;
|
||||
void setOnlineCount(int count);
|
||||
void setColorized(bool enabled);
|
||||
|
||||
private:
|
||||
const not_null<Ui::FlatLabel*> _label;
|
||||
const not_null<PeerData*> _peer;
|
||||
int _onlineCount = 0;
|
||||
bool _colorized = true;
|
||||
Fn<void()> _membersLinkCallback;
|
||||
base::Timer _refreshTimer;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Info::Profile
|
||||
2172
Telegram/SourceFiles/info/profile/info_profile_top_bar.cpp
Normal file
290
Telegram/SourceFiles/info/profile/info_profile_top_bar.h
Normal file
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
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 "base/object_ptr.h"
|
||||
#include "info/info_controller.h" // Key
|
||||
#include "info/profile/info_profile_badge.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/userpic_view.h"
|
||||
|
||||
namespace Data {
|
||||
class ForumTopic;
|
||||
class DocumentMedia;
|
||||
struct SavedStarGift;
|
||||
struct ColorProfileSet;
|
||||
class SavedStarGiftId;
|
||||
} // namespace Data
|
||||
|
||||
namespace Info::Profile {
|
||||
class BadgeTooltip;
|
||||
class TopicIconView;
|
||||
} // namespace Info::Profile
|
||||
|
||||
namespace Lottie {
|
||||
class Animation;
|
||||
class MultiPlayer;
|
||||
} // namespace Lottie
|
||||
|
||||
namespace Ui {
|
||||
class VideoUserpicPlayer;
|
||||
struct OutlineSegment;
|
||||
namespace Text {
|
||||
class CustomEmoji;
|
||||
} // namespace Text
|
||||
} // namespace Ui
|
||||
|
||||
class PeerData;
|
||||
|
||||
namespace base {
|
||||
class Timer;
|
||||
} // namespace base
|
||||
|
||||
namespace style {
|
||||
struct InfoTopBar;
|
||||
struct InfoPeerBadge;
|
||||
struct FlatLabel;
|
||||
} //namespace style
|
||||
|
||||
class QGraphicsOpacityEffect;
|
||||
|
||||
namespace Ui {
|
||||
class FlatLabel;
|
||||
class IconButton;
|
||||
class PopupMenu;
|
||||
class RoundButton;
|
||||
class StarsRating;
|
||||
template <typename Widget>
|
||||
class FadeWrap;
|
||||
class HorizontalFitContainer;
|
||||
namespace Animations {
|
||||
class Simple;
|
||||
} // namespace Animations
|
||||
} //namespace Ui
|
||||
|
||||
namespace Info {
|
||||
class Controller;
|
||||
class Key;
|
||||
enum class Wrap;
|
||||
} //namespace Info
|
||||
|
||||
namespace Ui::Menu {
|
||||
struct MenuCallback;
|
||||
} //namespace Ui::Menu
|
||||
|
||||
namespace Info::Profile {
|
||||
|
||||
class Badge;
|
||||
class StatusLabel;
|
||||
|
||||
class TopBar final : public Ui::RpWidget {
|
||||
public:
|
||||
enum class Source {
|
||||
Profile,
|
||||
Stories,
|
||||
Preview,
|
||||
};
|
||||
|
||||
struct Descriptor {
|
||||
not_null<Window::SessionController*> controller;
|
||||
Key key;
|
||||
rpl::producer<Wrap> wrap;
|
||||
Source source = Source::Profile;
|
||||
PeerData *peer = nullptr;
|
||||
rpl::variable<bool> backToggles;
|
||||
rpl::producer<> showFinished;
|
||||
};
|
||||
|
||||
struct AnimatedPatternPoint {
|
||||
QPointF basePosition;
|
||||
float64 size;
|
||||
float64 startTime;
|
||||
float64 endTime;
|
||||
};
|
||||
|
||||
TopBar(not_null<Ui::RpWidget*> parent, Descriptor descriptor);
|
||||
~TopBar();
|
||||
|
||||
void setOnlineCount(rpl::producer<int> &&count);
|
||||
|
||||
void setRoundEdges(bool value);
|
||||
void setLottieSingleLoop(bool value);
|
||||
void setEnableBackButtonValue(rpl::producer<bool> &&producer);
|
||||
void setColorProfileIndex(std::optional<uint8> index);
|
||||
void setPatternEmojiId(std::optional<DocumentId> patternEmojiId);
|
||||
void setLocalEmojiStatusId(EmojiStatusId emojiStatusId);
|
||||
void addTopBarMenuButton(
|
||||
not_null<Window::SessionController*> controller,
|
||||
Wrap wrap,
|
||||
bool shouldUseColored);
|
||||
void addTopBarEditButton(
|
||||
not_null<Window::SessionController*> controller,
|
||||
Wrap wrap,
|
||||
bool shouldUseColored);
|
||||
|
||||
rpl::producer<std::optional<QColor>> edgeColor() const;
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
void paintEdges(QPainter &p, const QBrush &brush) const;
|
||||
void paintEdges(QPainter &p) const;
|
||||
void updateLabelsPosition();
|
||||
[[nodiscard]] int titleMostLeft() const;
|
||||
[[nodiscard]] int statusMostLeft() const;
|
||||
[[nodiscard]] QRect userpicGeometry() const;
|
||||
void updateUserpicButtonGeometry();
|
||||
void updateGiftButtonsGeometry(
|
||||
float64 progressCurrent,
|
||||
const QRect &userpicRect);
|
||||
void paintUserpic(QPainter &p, const QRect &geometry);
|
||||
void updateVideoUserpic();
|
||||
void showTopBarMenu(not_null<Window::SessionController*> controller, bool check);
|
||||
void fillTopBarMenu(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const Ui::Menu::MenuCallback &addAction);
|
||||
void setupUserpicButton(not_null<Window::SessionController*> controller);
|
||||
void setupActions(not_null<Window::SessionController*> controller);
|
||||
void setupButtons(
|
||||
not_null<Window::SessionController*> controller,
|
||||
rpl::producer<bool> backToggles,
|
||||
Source source);
|
||||
void setupShowLastSeen(not_null<Window::SessionController*> controller);
|
||||
void setupUniqueBadgeTooltip();
|
||||
void hideBadgeTooltip();
|
||||
void setupAnimatedPattern(const QRect &userpicGeometry = QRect());
|
||||
void paintAnimatedPattern(
|
||||
QPainter &p,
|
||||
const QRect &rect,
|
||||
const QRect &userpicGeometry);
|
||||
void setupPinnedToTopGifts(not_null<Window::SessionController*> controller);
|
||||
void setupNewGifts(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const std::vector<Data::SavedStarGift> &gifts);
|
||||
void setupGiftButtons(not_null<Window::SessionController*> controller);
|
||||
void paintPinnedToTopGifts(
|
||||
QPainter &p,
|
||||
const QRect &rect,
|
||||
const QRect &userpicGeometry);
|
||||
[[nodiscard]] QPointF calculateGiftPosition(
|
||||
int position,
|
||||
float64 progress,
|
||||
const QRect &userpicRect) const;
|
||||
void adjustColors(const std::optional<QColor> &edgeColor);
|
||||
void updateCollectibleStatus();
|
||||
void updateBadgeContent();
|
||||
void setupStoryOutline(const QRect &geometry = QRect());
|
||||
void updateStoryOutline(std::optional<QColor> edgeColor);
|
||||
void paintStoryOutline(QPainter &p, const QRect &geometry);
|
||||
void updateStatusPosition(float64 progressCurrent);
|
||||
[[nodiscard]] const style::FlatLabel &statusStyle() const;
|
||||
void setupStatusWithRating();
|
||||
|
||||
[[nodiscard]] auto effectiveColorProfile()
|
||||
const -> std::optional<Data::ColorProfileSet>;
|
||||
[[nodiscard]] auto effectiveCollectible()
|
||||
const -> std::shared_ptr<Data::EmojiStatusCollectible>;
|
||||
|
||||
const not_null<PeerData*> _peer;
|
||||
Data::ForumTopic *_topic = nullptr;
|
||||
const Key _key;
|
||||
rpl::variable<Wrap> _wrap;
|
||||
const style::InfoTopBar &_st;
|
||||
const Source _source;
|
||||
|
||||
std::unique_ptr<base::Timer> _badgeTooltipHide;
|
||||
const std::unique_ptr<Badge> _botVerify;
|
||||
rpl::variable<Badge::Content> _badgeContent;
|
||||
const Fn<bool()> _gifPausedChecker;
|
||||
const std::unique_ptr<Badge> _badge;
|
||||
const std::unique_ptr<Badge> _verified;
|
||||
|
||||
const bool _hasActions;
|
||||
const int _minForProgress;
|
||||
|
||||
std::unique_ptr<BadgeTooltip> _badgeTooltip;
|
||||
std::vector<std::unique_ptr<BadgeTooltip>> _badgeOldTooltips;
|
||||
uint64 _badgeCollectibleId = 0;
|
||||
|
||||
object_ptr<Ui::FlatLabel> _title;
|
||||
std::unique_ptr<Ui::StarsRating> _starsRating;
|
||||
object_ptr<Ui::FlatLabel> _status;
|
||||
std::unique_ptr<StatusLabel> _statusLabel;
|
||||
rpl::variable<int> _statusShift = 0;
|
||||
object_ptr<Ui::RoundButton> _showLastSeen = { nullptr };
|
||||
QGraphicsOpacityEffect *_showLastSeenOpacity = nullptr;
|
||||
|
||||
std::shared_ptr<style::FlatLabel> _statusSt;
|
||||
std::shared_ptr<style::InfoPeerBadge> _botVerifySt;
|
||||
std::shared_ptr<style::InfoPeerBadge> _badgeSt;
|
||||
std::shared_ptr<style::InfoPeerBadge> _verifiedSt;
|
||||
|
||||
rpl::variable<float64> _progress = 0.;
|
||||
bool _roundEdges = true;
|
||||
|
||||
rpl::variable<std::optional<QColor>> _edgeColor;
|
||||
bool _hasGradientBg = false;
|
||||
std::optional<QColor> _solidBg;
|
||||
QImage _cachedGradient;
|
||||
QPainterPath _cachedClipPath;
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> _patternEmoji;
|
||||
QImage _basePatternImage;
|
||||
|
||||
std::vector<AnimatedPatternPoint> _animatedPoints;
|
||||
QRect _lastUserpicRect;
|
||||
|
||||
Ui::PeerUserpicView _userpicView;
|
||||
InMemoryKey _userpicUniqueKey;
|
||||
QImage _cachedUserpic;
|
||||
QImage _monoforumMask;
|
||||
std::unique_ptr<Ui::VideoUserpicPlayer> _videoUserpicPlayer;
|
||||
std::unique_ptr<TopicIconView> _topicIconView;
|
||||
|
||||
base::unique_qptr<Ui::IconButton> _close;
|
||||
base::unique_qptr<Ui::FadeWrap<Ui::IconButton>> _back;
|
||||
|
||||
base::unique_qptr<Ui::IconButton> _topBarButton;
|
||||
base::unique_qptr<Ui::PopupMenu> _peerMenu;
|
||||
|
||||
Ui::RpWidget *_actionMore = nullptr;
|
||||
|
||||
base::unique_qptr<Ui::AbstractButton> _userpicButton;
|
||||
|
||||
base::unique_qptr<Ui::HorizontalFitContainer> _actions;
|
||||
|
||||
std::unique_ptr<Lottie::MultiPlayer> _lottiePlayer;
|
||||
bool _lottieSingleLoop = false;
|
||||
struct PinnedToTopGiftEntry {
|
||||
Data::SavedStarGiftId manageId;
|
||||
// QString slug;
|
||||
Lottie::Animation *animation = nullptr;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
QImage bg;
|
||||
QImage lastFrame;
|
||||
int position = 0;
|
||||
base::unique_qptr<Ui::AbstractButton> button;
|
||||
};
|
||||
bool _pinnedToTopGiftsFirstTimeShowed = false;
|
||||
std::vector<PinnedToTopGiftEntry> _pinnedToTopGifts;
|
||||
std::unique_ptr<Ui::Animations::Simple> _giftsAppearing;
|
||||
std::unique_ptr<Ui::Animations::Simple> _giftsHiding;
|
||||
rpl::lifetime _giftsLoadingLifetime;
|
||||
|
||||
QBrush _storyOutlineBrush;
|
||||
std::vector<Ui::OutlineSegment> _storySegments;
|
||||
bool _hasStories = false;
|
||||
|
||||
std::optional<uint8> _localColorProfileIndex;
|
||||
std::optional<DocumentId> _localPatternEmojiId;
|
||||
std::shared_ptr<Data::EmojiStatusCollectible> _localCollectible;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Info::Profile
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
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/profile/info_profile_top_bar_action_button.h"
|
||||
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "lottie/lottie_icon.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_info.h"
|
||||
|
||||
namespace Info::Profile {
|
||||
namespace {
|
||||
|
||||
constexpr auto kIconFadeStart = 0.4;
|
||||
constexpr auto kIconFadeRange = 1.0 - kIconFadeStart;
|
||||
|
||||
} // namespace
|
||||
|
||||
TopBarActionButton::TopBarActionButton(
|
||||
not_null<QWidget*> parent,
|
||||
const QString &text,
|
||||
const QString &lottieName)
|
||||
: RippleButton(parent, st::universalRippleAnimation)
|
||||
, _text(text) {
|
||||
setupLottie(lottieName);
|
||||
}
|
||||
|
||||
TopBarActionButton::TopBarActionButton(
|
||||
not_null<QWidget*> parent,
|
||||
const QString &text,
|
||||
const style::icon &icon)
|
||||
: RippleButton(parent, st::universalRippleAnimation)
|
||||
, _text(text)
|
||||
, _icon(&icon) {
|
||||
}
|
||||
|
||||
TopBarActionButton::~TopBarActionButton() = default;
|
||||
|
||||
void TopBarActionButton::setupLottie(const QString &lottieName) {
|
||||
_lottie = std::make_unique<Lottie::Icon>(Lottie::IconDescriptor{
|
||||
.name = lottieName,
|
||||
.color = _lottieColor,
|
||||
.sizeOverride = Size(st::infoProfileTopBarActionButtonLottieSize),
|
||||
});
|
||||
_lottie->animate([=] { update(); }, 0, _lottie->framesCount() - 1);
|
||||
}
|
||||
|
||||
void TopBarActionButton::convertToToggle(
|
||||
const style::icon &offIcon,
|
||||
const style::icon &onIcon,
|
||||
const QString &offLottie,
|
||||
const QString &onLottie) {
|
||||
_isToggle = true;
|
||||
_offIcon = &offIcon;
|
||||
_onIcon = &onIcon;
|
||||
_offLottie = offLottie;
|
||||
_onLottie = onLottie;
|
||||
_icon = _offIcon;
|
||||
_lottie.reset();
|
||||
}
|
||||
|
||||
void TopBarActionButton::toggle(bool state) {
|
||||
if (!_isToggle || _toggleState == state) {
|
||||
return;
|
||||
}
|
||||
_toggleState = state;
|
||||
const auto &lottie = _toggleState ? _onLottie : _offLottie;
|
||||
setupLottie(lottie);
|
||||
_lottie->animate([=] {
|
||||
update();
|
||||
if (_lottie->frameIndex() == _lottie->framesCount() - 1) {
|
||||
_icon = _toggleState ? _onIcon : _offIcon;
|
||||
_lottie.reset();
|
||||
}
|
||||
}, 0, _lottie->framesCount() - 1);
|
||||
}
|
||||
|
||||
void TopBarActionButton::finishAnimating() {
|
||||
if (_lottie) {
|
||||
_icon = _toggleState ? _onIcon : _offIcon;
|
||||
_lottie.reset();
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void TopBarActionButton::setText(const QString &text) {
|
||||
_text = text;
|
||||
update();
|
||||
}
|
||||
|
||||
void TopBarActionButton::setLottieColor(const style::color *color) {
|
||||
_lottieColor = color;
|
||||
_lottie.reset();
|
||||
update();
|
||||
}
|
||||
|
||||
void TopBarActionButton::setStyle(const TopBarActionButtonStyle &style) {
|
||||
_bgColor = style.bgColor;
|
||||
_fgColor = style.fgColor;
|
||||
_shadowColor = style.shadowColor;
|
||||
update();
|
||||
}
|
||||
|
||||
void TopBarActionButton::paintEvent(QPaintEvent *e) {
|
||||
auto p = Painter(this);
|
||||
|
||||
const auto progress = float64(height())
|
||||
/ st::infoProfileTopBarActionButtonSize;
|
||||
p.setOpacity(progress);
|
||||
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(_bgColor);
|
||||
{
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
// Todo shadows.
|
||||
p.drawRoundedRect(rect(), st::boxRadius, st::boxRadius);
|
||||
}
|
||||
|
||||
paintRipple(p, 0, 0);
|
||||
|
||||
const auto iconSize = st::infoProfileTopBarActionButtonIconSize;
|
||||
const auto iconTop = st::infoProfileTopBarActionButtonIconTop;
|
||||
|
||||
if (_lottie || _icon) {
|
||||
const auto iconScale = (progress > kIconFadeStart)
|
||||
? (progress - kIconFadeStart) / kIconFadeRange
|
||||
: 0.0;
|
||||
p.setOpacity(iconScale);
|
||||
p.save();
|
||||
const auto iconLeft = (width() - iconSize) / 2;
|
||||
const auto half = iconSize / 2;
|
||||
const auto iconCenter = QPoint(iconLeft + half, iconTop + half);
|
||||
p.translate(iconCenter);
|
||||
p.scale(iconScale, iconScale);
|
||||
p.translate(-iconCenter);
|
||||
if (_lottie) {
|
||||
_lottie->paint(p, iconLeft, iconTop, _fgColor);
|
||||
} else if (_icon) {
|
||||
if (_fgColor) {
|
||||
_icon->paint(p, iconLeft, iconTop, width(), *_fgColor);
|
||||
} else {
|
||||
_icon->paint(p, iconLeft, iconTop, width());
|
||||
}
|
||||
}
|
||||
p.restore();
|
||||
p.setOpacity(progress);
|
||||
}
|
||||
|
||||
const auto skip = st::infoProfileTopBarActionButtonTextSkip;
|
||||
|
||||
p.setClipRect(0, 0, width(), height() - skip);
|
||||
|
||||
if (_fgColor.has_value()) {
|
||||
p.setPen(*_fgColor);
|
||||
} else {
|
||||
p.setPen(st::windowBoldFg);
|
||||
}
|
||||
|
||||
p.setFont(st::infoProfileTopBarActionButtonFont);
|
||||
|
||||
const auto textScale = std::max(kIconFadeStart, progress);
|
||||
const auto textRect = rect()
|
||||
- QMargins(0, st::infoProfileTopBarActionButtonTextTop, 0, 0);
|
||||
const auto textCenter = rect::center(textRect);
|
||||
p.translate(textCenter);
|
||||
p.scale(textScale, textScale);
|
||||
p.translate(-textCenter);
|
||||
|
||||
const auto elidedText = st::infoProfileTopBarActionButtonFont->elided(
|
||||
_text,
|
||||
textRect.width(),
|
||||
Qt::ElideMiddle);
|
||||
p.drawText(textRect, elidedText, style::al_top);
|
||||
}
|
||||
|
||||
QImage TopBarActionButton::prepareRippleMask() const {
|
||||
return Ui::RippleAnimation::RoundRectMask(size(), st::boxRadius);
|
||||
}
|
||||
|
||||
QPoint TopBarActionButton::prepareRippleStartPosition() const {
|
||||
return mapFromGlobal(QCursor::pos());
|
||||
}
|
||||
|
||||
} // namespace Info::Profile
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
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/widgets/buttons.h"
|
||||
|
||||
namespace Lottie {
|
||||
class Icon;
|
||||
} // namespace Lottie
|
||||
|
||||
namespace Info::Profile {
|
||||
|
||||
struct TopBarActionButtonStyle {
|
||||
QColor bgColor;
|
||||
std::optional<QColor> fgColor;
|
||||
std::optional<QColor> shadowColor;
|
||||
};
|
||||
|
||||
class TopBarActionButton final : public Ui::RippleButton {
|
||||
public:
|
||||
TopBarActionButton(
|
||||
not_null<QWidget*> parent,
|
||||
const QString &text,
|
||||
const QString &lottieName);
|
||||
TopBarActionButton(
|
||||
not_null<QWidget*> parent,
|
||||
const QString &text,
|
||||
const style::icon &icon);
|
||||
|
||||
void convertToToggle(
|
||||
const style::icon &offIcon,
|
||||
const style::icon &onIcon,
|
||||
const QString &offLottie,
|
||||
const QString &onLottie);
|
||||
void setLottieColor(const style::color *color);
|
||||
void toggle(bool state);
|
||||
void finishAnimating();
|
||||
void setText(const QString &text);
|
||||
void setStyle(const TopBarActionButtonStyle &style);
|
||||
|
||||
~TopBarActionButton();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
QImage prepareRippleMask() const override;
|
||||
QPoint prepareRippleStartPosition() const override;
|
||||
|
||||
private:
|
||||
void setupLottie(const QString &lottieName);
|
||||
|
||||
QString _text;
|
||||
std::unique_ptr<Lottie::Icon> _lottie;
|
||||
const style::icon *_icon = nullptr;
|
||||
|
||||
bool _isToggle = false;
|
||||
bool _toggleState = false;
|
||||
const style::icon *_offIcon = nullptr;
|
||||
const style::icon *_onIcon = nullptr;
|
||||
QString _offLottie;
|
||||
QString _onLottie;
|
||||
const style::color *_lottieColor = nullptr;
|
||||
|
||||
QColor _bgColor;
|
||||
std::optional<QColor> _fgColor;
|
||||
std::optional<QColor> _shadowColor;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Info::Profile
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history.h"
|
||||
#include "info/profile/info_profile_inner_widget.h"
|
||||
#include "info/profile/info_profile_members.h"
|
||||
#include "info/settings/info_settings_widget.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "data/data_peer.h"
|
||||
@@ -20,9 +21,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "styles/style_info.h"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QScrollBar>
|
||||
|
||||
namespace Info::Settings {
|
||||
struct SectionCustomTopBarData;
|
||||
} // namespace Info::Settings
|
||||
|
||||
namespace Info::Profile {
|
||||
|
||||
using Info::Settings::SectionCustomTopBarData;
|
||||
|
||||
Memento::Memento(not_null<Controller*> controller)
|
||||
: Memento(
|
||||
controller->peer(),
|
||||
@@ -84,13 +95,15 @@ Widget::Widget(
|
||||
QWidget *parent,
|
||||
not_null<Controller*> controller,
|
||||
Origin origin)
|
||||
: ContentWidget(parent, controller) {
|
||||
: ContentWidget(parent, controller)
|
||||
, _inner(
|
||||
setupFlexibleInnerWidget(
|
||||
object_ptr<InnerWidget>(this, controller, origin),
|
||||
_flexibleScroll))
|
||||
, _pinnedToTop(_inner->createPinnedToTop(this))
|
||||
, _pinnedToBottom(_inner->createPinnedToBottom(this)) {
|
||||
controller->setSearchEnabledByContent(false);
|
||||
|
||||
_inner = setInnerWidget(object_ptr<InnerWidget>(
|
||||
this,
|
||||
controller,
|
||||
origin));
|
||||
_inner->move(0, 0);
|
||||
_inner->scrollToRequests(
|
||||
) | rpl::start_with_next([this](Ui::ScrollToRequest request) {
|
||||
@@ -101,12 +114,68 @@ Widget::Widget(
|
||||
scrollTo(request);
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
if (_pinnedToTop) {
|
||||
_inner->widthValue(
|
||||
) | rpl::start_with_next([=](int w) {
|
||||
_pinnedToTop->resizeToWidth(w);
|
||||
setScrollTopSkip(_pinnedToTop->height());
|
||||
}, _pinnedToTop->lifetime());
|
||||
|
||||
_pinnedToTop->heightValue(
|
||||
) | rpl::start_with_next([=](int h) {
|
||||
setScrollTopSkip(h);
|
||||
}, _pinnedToTop->lifetime());
|
||||
}
|
||||
|
||||
if (_pinnedToBottom) {
|
||||
const auto processHeight = [=] {
|
||||
setScrollBottomSkip(_pinnedToBottom->height());
|
||||
_pinnedToBottom->moveToLeft(
|
||||
_pinnedToBottom->x(),
|
||||
height() - _pinnedToBottom->height());
|
||||
};
|
||||
|
||||
_inner->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &s) {
|
||||
_pinnedToBottom->resizeToWidth(s.width());
|
||||
}, _pinnedToBottom->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
_pinnedToBottom->heightValue(),
|
||||
heightValue()
|
||||
) | rpl::start_with_next(processHeight, _pinnedToBottom->lifetime());
|
||||
}
|
||||
|
||||
if (_pinnedToTop
|
||||
&& _pinnedToTop->minimumHeight()
|
||||
&& _inner->hasFlexibleTopBar()) {
|
||||
_flexibleScrollHelper = std::make_unique<FlexibleScrollHelper>(
|
||||
scroll(),
|
||||
_inner,
|
||||
_pinnedToTop.get(),
|
||||
[=](QMargins margins) {
|
||||
ContentWidget::setPaintPadding(std::move(margins));
|
||||
},
|
||||
[=](rpl::producer<not_null<QEvent*>> &&events) {
|
||||
ContentWidget::setViewport(std::move(events));
|
||||
},
|
||||
_flexibleScroll);
|
||||
}
|
||||
}
|
||||
|
||||
void Widget::setInnerFocus() {
|
||||
_inner->setFocus();
|
||||
}
|
||||
|
||||
void Widget::enableBackButton() {
|
||||
_inner->enableBackButton();
|
||||
}
|
||||
|
||||
void Widget::showFinished() {
|
||||
_inner->showFinished();
|
||||
}
|
||||
|
||||
rpl::producer<QString> Widget::title() {
|
||||
if (const auto topic = controller()->key().topic()) {
|
||||
return topic->peer()->isBot()
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
#include "info/info_content_widget.h"
|
||||
#include "ui/effects/animations.h"
|
||||
|
||||
namespace Data {
|
||||
class ForumTopic;
|
||||
@@ -78,6 +79,8 @@ public:
|
||||
not_null<Memento*> memento);
|
||||
|
||||
void setInnerFocus() override;
|
||||
void enableBackButton() override;
|
||||
void showFinished() override;
|
||||
|
||||
rpl::producer<QString> title() override;
|
||||
rpl::producer<Dialogs::Stories::Content> titleStories() override;
|
||||
@@ -88,7 +91,11 @@ private:
|
||||
|
||||
std::shared_ptr<ContentMemento> doCreateMemento() override;
|
||||
|
||||
FlexibleScrollData _flexibleScroll;
|
||||
InnerWidget *_inner = nullptr;
|
||||
base::weak_qptr<Ui::RpWidget> _pinnedToTop;
|
||||
base::weak_qptr<Ui::RpWidget> _pinnedToBottom;
|
||||
std::unique_ptr<FlexibleScrollHelper> _flexibleScrollHelper;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -43,6 +43,11 @@ MusicProvider::MusicProvider(not_null<AbstractController*> controller)
|
||||
: _controller(controller)
|
||||
, _peer(controller->key().musicPeer())
|
||||
, _history(_peer->owner().history(_peer)) {
|
||||
_controller->session().data().itemRemoved(
|
||||
) | rpl::start_with_next([this](auto item) {
|
||||
itemRemoved(item);
|
||||
}, _lifetime);
|
||||
|
||||
style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
for (auto &layout : _layouts) {
|
||||
@@ -214,6 +219,13 @@ std::vector<ListSection> MusicProvider::fillSections(
|
||||
return result;
|
||||
}
|
||||
|
||||
void MusicProvider::itemRemoved(not_null<const HistoryItem*> item) {
|
||||
if (const auto i = _layouts.find(item); i != end(_layouts)) {
|
||||
_layoutRemoved.fire(i->second.item.get());
|
||||
_layouts.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
void MusicProvider::markLayoutsStale() {
|
||||
for (auto &layout : _layouts) {
|
||||
layout.second.stale = true;
|
||||
|
||||
@@ -96,6 +96,7 @@ private:
|
||||
not_null<const Media::BaseLayout*> item,
|
||||
not_null<const Media::BaseLayout*> previous) override;
|
||||
|
||||
void itemRemoved(not_null<const HistoryItem*> item);
|
||||
void markLayoutsStale();
|
||||
void clearStaleLayouts();
|
||||
void clear();
|
||||
@@ -115,7 +116,9 @@ private:
|
||||
int _idsLimit = kMinimalIdsLimit;
|
||||
Data::SavedMusicSlice _slice;
|
||||
|
||||
std::unordered_map<not_null<HistoryItem*>, Media::CachedItem> _layouts;
|
||||
std::unordered_map<
|
||||
not_null<const HistoryItem*>,
|
||||
Media::CachedItem> _layouts;
|
||||
rpl::event_stream<not_null<Media::BaseLayout*>> _layoutRemoved;
|
||||
rpl::event_stream<> _refreshed;
|
||||
|
||||
|
||||
@@ -43,46 +43,22 @@ Widget::Widget(
|
||||
: ContentWidget(parent, controller)
|
||||
, _self(controller->key().settingsSelf())
|
||||
, _type(controller->section().settingsType())
|
||||
, _inner([&] {
|
||||
auto inner = _type->create(
|
||||
this,
|
||||
controller->parentController(),
|
||||
scroll(),
|
||||
controller->wrapValue(
|
||||
) | rpl::map([](Wrap wrap) { return (wrap == Wrap::Layer)
|
||||
? ::Settings::Container::Layer
|
||||
: ::Settings::Container::Section; }));
|
||||
if (inner->hasFlexibleTopBar()) {
|
||||
auto filler = setInnerWidget(object_ptr<Ui::RpWidget>(this));
|
||||
filler->resize(1, 1);
|
||||
|
||||
_flexibleScroll.contentHeightValue.events(
|
||||
) | rpl::start_with_next([=](int h) {
|
||||
filler->resize(filler->width(), h);
|
||||
}, filler->lifetime());
|
||||
|
||||
filler->widthValue(
|
||||
) | rpl::start_to_stream(
|
||||
_flexibleScroll.fillerWidthValue,
|
||||
lifetime());
|
||||
|
||||
controller->stepDataReference() = SectionCustomTopBarData{
|
||||
.backButtonEnables = _flexibleScroll.backButtonEnables.events(),
|
||||
.wrapValue = controller->wrapValue(),
|
||||
};
|
||||
|
||||
// ScrollArea -> PaddingWrap -> RpWidget.
|
||||
inner->setParent(filler->parentWidget()->parentWidget());
|
||||
inner->raise();
|
||||
|
||||
using InnerPtr = base::unique_qptr<::Settings::AbstractSection>;
|
||||
auto owner = filler->lifetime().make_state<InnerPtr>(
|
||||
std::move(inner.release()));
|
||||
return owner->get();
|
||||
} else {
|
||||
return setInnerWidget(std::move(inner));
|
||||
}
|
||||
}())
|
||||
, _inner(setupFlexibleInnerWidget(
|
||||
_type->create(
|
||||
this,
|
||||
controller->parentController(),
|
||||
scroll(),
|
||||
controller->wrapValue(
|
||||
) | rpl::map([](Wrap wrap) { return (wrap == Wrap::Layer)
|
||||
? ::Settings::Container::Layer
|
||||
: ::Settings::Container::Section; })),
|
||||
_flexibleScroll,
|
||||
[=](Ui::RpWidget*) {
|
||||
controller->stepDataReference() = SectionCustomTopBarData{
|
||||
.backButtonEnables = _flexibleScroll.backButtonEnables.events(),
|
||||
.wrapValue = controller->wrapValue(),
|
||||
};
|
||||
}))
|
||||
, _pinnedToTop(_inner->createPinnedToTop(this))
|
||||
, _pinnedToBottom(_inner->createPinnedToBottom(this)) {
|
||||
_inner->sectionShowOther(
|
||||
|
||||
@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "info/peer_gifts/info_peer_gifts_widget.h"
|
||||
#include "info/profile/info_profile_actions.h"
|
||||
#include "info/profile/info_profile_icon.h"
|
||||
#include "info/profile/info_profile_top_bar.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "info/profile/info_profile_widget.h"
|
||||
#include "info/stories/info_stories_albums.h"
|
||||
@@ -293,7 +294,6 @@ void InnerWidget::createProfileTop() {
|
||||
startTop();
|
||||
|
||||
using namespace Profile;
|
||||
AddCover(_top, _controller, _peer, nullptr, nullptr);
|
||||
AddDetails(_top, _controller, _peer, nullptr, nullptr, { v::null });
|
||||
|
||||
auto tracker = Ui::MultiSlideTracker();
|
||||
@@ -481,6 +481,46 @@ void InnerWidget::addGiftsButton(Ui::MultiSlideTracker &tracker) {
|
||||
tracker.track(giftsWrap);
|
||||
}
|
||||
|
||||
bool InnerWidget::hasFlexibleTopBar() const {
|
||||
return (_controller->key().storiesAlbumId() != Stories::ArchiveId()
|
||||
&& _controller->key().storiesPeer()
|
||||
&& _controller->key().storiesPeer()->isSelf());
|
||||
}
|
||||
|
||||
base::weak_qptr<Ui::RpWidget> InnerWidget::createPinnedToTop(
|
||||
not_null<Ui::RpWidget*> parent) {
|
||||
if (!hasFlexibleTopBar()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto content = Ui::CreateChild<Profile::TopBar>(
|
||||
parent,
|
||||
Profile::TopBar::Descriptor{
|
||||
.controller = _controller->parentController(),
|
||||
.key = _controller->key(),
|
||||
.wrap = _controller->wrapValue(),
|
||||
.source = Profile::TopBar::Source::Stories,
|
||||
.peer = _peer,
|
||||
.backToggles = _backToggles.value(),
|
||||
.showFinished = _showFinished.events(),
|
||||
});
|
||||
_topBarColor = content->edgeColor();
|
||||
return base::make_weak(not_null<Ui::RpWidget*>{ content });
|
||||
}
|
||||
|
||||
base::weak_qptr<Ui::RpWidget> InnerWidget::createPinnedToBottom(
|
||||
not_null<Ui::RpWidget*> parent) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void InnerWidget::enableBackButton() {
|
||||
_backToggles.force_assign(true);
|
||||
}
|
||||
|
||||
void InnerWidget::showFinished() {
|
||||
_showFinished.fire({});
|
||||
}
|
||||
|
||||
void InnerWidget::finalizeTop() {
|
||||
const auto addPossibleAlbums = !_addingToAlbumId
|
||||
&& (_albumId.current() != Data::kStoriesAlbumIdArchive);
|
||||
|
||||
@@ -86,6 +86,15 @@ public:
|
||||
[[nodiscard]] rpl::producer<int> albumIdChanges() const;
|
||||
[[nodiscard]] rpl::producer<Data::StoryAlbumUpdate> changes() const;
|
||||
|
||||
bool hasFlexibleTopBar() const;
|
||||
base::weak_qptr<Ui::RpWidget> createPinnedToTop(
|
||||
not_null<Ui::RpWidget*> parent);
|
||||
base::weak_qptr<Ui::RpWidget> createPinnedToBottom(
|
||||
not_null<Ui::RpWidget*> parent);
|
||||
|
||||
void enableBackButton();
|
||||
void showFinished();
|
||||
|
||||
protected:
|
||||
int resizeGetHeight(int newWidth) override;
|
||||
void visibleTopBottomUpdated(
|
||||
@@ -152,6 +161,10 @@ private:
|
||||
rpl::variable<int> _topHeight;
|
||||
rpl::variable<bool> _albumEmpty;
|
||||
|
||||
rpl::variable<bool> _backToggles;
|
||||
rpl::event_stream<> _showFinished;
|
||||
rpl::variable<std::optional<QColor>> _topBarColor;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Info::Stories
|
||||
|
||||