Merge remote-tracking branch 'tdesktop-ustream/dev' into dev

# Conflicts:
#	Telegram/CMakeLists.txt
#	Telegram/Resources/winrc/Telegram.rc
#	Telegram/Resources/winrc/Updater.rc
#	Telegram/SourceFiles/core/version.h
#	Telegram/lib_ui
#	lib/xdg/org.telegram.desktop.service
This commit is contained in:
ZavaruKitsu 2023-08-01 09:59:53 +00:00
commit 1f6c440341
86 changed files with 1767 additions and 535 deletions

View file

@ -1802,6 +1802,7 @@ endif()
if (LINUX AND DESKTOP_APP_USE_PACKAGED) if (LINUX AND DESKTOP_APP_USE_PACKAGED)
include(GNUInstallDirs) include(GNUInstallDirs)
configure_file("../lib/xdg/org.telegram.desktop.service" "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.service" @ONLY)
configure_file("../lib/xdg/org.ayugram.desktop.metainfo.xml" "${CMAKE_CURRENT_BINARY_DIR}/org.ayugram.desktop.metainfo.xml" @ONLY) configure_file("../lib/xdg/org.ayugram.desktop.metainfo.xml" "${CMAKE_CURRENT_BINARY_DIR}/org.ayugram.desktop.metainfo.xml" @ONLY)
generate_appdata_changelog(Telegram "${CMAKE_SOURCE_DIR}/changelog.txt" "${CMAKE_CURRENT_BINARY_DIR}/org.ayugram.desktop.metainfo.xml") generate_appdata_changelog(Telegram "${CMAKE_SOURCE_DIR}/changelog.txt" "${CMAKE_CURRENT_BINARY_DIR}/org.ayugram.desktop.metainfo.xml")
install(TARGETS Telegram RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION "${CMAKE_INSTALL_BINDIR}") install(TARGETS Telegram RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION "${CMAKE_INSTALL_BINDIR}")
@ -1813,6 +1814,6 @@ if (LINUX AND DESKTOP_APP_USE_PACKAGED)
install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "telegram.png") install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "telegram.png")
install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "telegram.png") install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "telegram.png")
install(FILES "../lib/xdg/org.ayugram.desktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications") install(FILES "../lib/xdg/org.ayugram.desktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
install(FILES "../lib/xdg/org.ayugram.desktop.service" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/dbus-1/services") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/org.ayugram.desktop.service" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/dbus-1/services")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/org.ayugram.desktop.metainfo.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/org.ayugram.desktop.metainfo.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo")
endif() endif()

View file

@ -433,7 +433,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_channel_usernames_deactivate_description" = "Do you want to hide this link from the channel info page?"; "lng_channel_usernames_deactivate_description" = "Do you want to hide this link from the channel info page?";
"lng_channel_usernames_description" = "Drag and drop links to change the order in which they will be displayed on the channel info page."; "lng_channel_usernames_description" = "Drag and drop links to change the order in which they will be displayed on the channel info page.";
"lng_bot_username_title" = "Username"; "lng_bot_username_title" = "Public Link";
"lng_bot_username_description1" = "This link cannot be edited. You can acquire additional usernames on {link}."; "lng_bot_username_description1" = "This link cannot be edited. You can acquire additional usernames on {link}.";
"lng_bot_username_description1_link" = "Fragment"; "lng_bot_username_description1_link" = "Fragment";
"lng_bot_usernames_activate_description" = "Do you want to show this link on the bot info page?"; "lng_bot_usernames_activate_description" = "Do you want to show this link on the bot info page?";
@ -1143,6 +1143,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_shared_links#other" = "{count} shared links"; "lng_profile_shared_links#other" = "{count} shared links";
"lng_profile_copy_phone" = "Copy Phone Number"; "lng_profile_copy_phone" = "Copy Phone Number";
"lng_profile_copy_fullname" = "Copy Name"; "lng_profile_copy_fullname" = "Copy Name";
"lng_profile_photo_by_you" = "photo set by you";
"lng_profile_public_photo" = "public photo";
"lng_via_link_group_one" = "**{user}** restricts adding them to groups.\nYou can send them an invite link as message instead."; "lng_via_link_group_one" = "**{user}** restricts adding them to groups.\nYou can send them an invite link as message instead.";
"lng_via_link_group_many#one" = "**{count} user** restricts adding them to groups.\nYou can send them an invite link as message instead."; "lng_via_link_group_many#one" = "**{count} user** restricts adding them to groups.\nYou can send them an invite link as message instead.";
@ -1710,6 +1712,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_forwarded" = "Forwarded from {user}"; "lng_forwarded" = "Forwarded from {user}";
"lng_forwarded_story" = "Story from {user}"; "lng_forwarded_story" = "Story from {user}";
"lng_forwarded_story_expired" = "This story has expired.";
"lng_forwarded_date" = "Original: {date}"; "lng_forwarded_date" = "Original: {date}";
"lng_forwarded_channel" = "Forwarded from {channel}"; "lng_forwarded_channel" = "Forwarded from {channel}";
"lng_forwarded_psa_default" = "Forwarded from {channel}"; "lng_forwarded_psa_default" = "Forwarded from {channel}";
@ -1923,6 +1926,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_switch_gifs" = "GIFs"; "lng_switch_gifs" = "GIFs";
"lng_switch_masks" = "Masks"; "lng_switch_masks" = "Masks";
"lng_stickers_featured_add" = "Add"; "lng_stickers_featured_add" = "Add";
"lng_stickers_featured_installed" = "Added";
"lng_emoji_featured_unlock" = "Unlock"; "lng_emoji_featured_unlock" = "Unlock";
"lng_emoji_premium_restore" = "Restore"; "lng_emoji_premium_restore" = "Restore";
"lng_gifs_search" = "Search GIFs"; "lng_gifs_search" = "Search GIFs";
@ -3583,6 +3587,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_filters_restore" = "Undo"; "lng_filters_restore" = "Undo";
"lng_filters_new" = "New Folder"; "lng_filters_new" = "New Folder";
"lng_filters_edit" = "Edit Folder"; "lng_filters_edit" = "Edit Folder";
"lng_filters_setup_menu" = "Edit Folders";
"lng_filters_new_name" = "Folder name"; "lng_filters_new_name" = "Folder name";
"lng_filters_add_chats" = "Add chats"; "lng_filters_add_chats" = "Add chats";
"lng_filters_remove_chats" = "Remove chats"; "lng_filters_remove_chats" = "Remove chats";
@ -3832,6 +3837,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_stories_no_views" = "No views"; "lng_stories_no_views" = "No views";
"lng_stories_unsupported" = "This story is not supported\nby your version of Telegram."; "lng_stories_unsupported" = "This story is not supported\nby your version of Telegram.";
"lng_stories_cant_reply" = "You can't reply to this story."; "lng_stories_cant_reply" = "You can't reply to this story.";
"lng_stories_about_silent" = "This video has no sound.";
"lng_stories_about_close_friends" = "You're seeing this story because {user} added you to their list of **Close Friends**.";
"lng_stories_about_contacts" = "Only {user}'s contacts can view this story.";
"lng_stories_about_selected_contacts" = "Only some contacts {user} selected can view this story.";
"lng_stories_about_close_friends_my" = "Only your list of **Close Friends** can view this story.";
"lng_stories_about_contacts_my" = "Only your contacts can view this story.";
"lng_stories_about_selected_contacts_my" = "Only some contacts you selected can view this story.";
"lng_stories_click_to_view_mine" = "Click here to view your story.";
"lng_stories_click_to_view" = "Click here to view updates from {users}.";
"lng_stories_click_to_view_and_one" = "{accumulated}, {user}";
"lng_stories_click_to_view_and_last" = "{accumulated} and {user}";
"lng_stories_show_more" = "Show more";
"lng_stories_my_title" = "Saved Stories"; "lng_stories_my_title" = "Saved Stories";
"lng_stories_archive_button" = "Stories Archive"; "lng_stories_archive_button" = "Stories Archive";

View file

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

View file

@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 4,8,7,0 FILEVERSION 4,8,10,0
PRODUCTVERSION 4,8,7,0 PRODUCTVERSION 4,8,10,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -62,10 +62,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop" VALUE "FileDescription", "AyuGram Desktop"
VALUE "FileVersion", "4.8.7.0" VALUE "FileVersion", "4.8.10.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "LegalCopyright", "Copyright (C) 2014-2023"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "4.8.7.0" VALUE "ProductVersion", "4.8.10.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 4,8,7,0 FILEVERSION 4,8,10,0
PRODUCTVERSION 4,8,7,0 PRODUCTVERSION 4,8,10,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -53,10 +53,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater" VALUE "FileDescription", "AyuGram Desktop Updater"
VALUE "FileVersion", "4.8.7.0" VALUE "FileVersion", "4.8.10.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "LegalCopyright", "Copyright (C) 2014-2023"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "4.8.7.0" VALUE "ProductVersion", "4.8.10.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -156,14 +156,15 @@ void GiftBox(
// List. // List.
const auto group = std::make_shared<Ui::RadiobuttonGroup>(); const auto group = std::make_shared<Ui::RadiobuttonGroup>();
group->setChangedCallback([=](int value) { const auto groupValueChangedCallback = [=](int value) {
Expects(value < options.size() && value >= 0); Expects(value < options.size() && value >= 0);
auto text = tr::lng_premium_gift_button( auto text = tr::lng_premium_gift_button(
tr::now, tr::now,
lt_cost, lt_cost,
options[value].costTotal); options[value].costTotal);
state->buttonText.fire(std::move(text)); state->buttonText.fire(std::move(text));
}); };
group->setChangedCallback(groupValueChangedCallback);
Ui::Premium::AddGiftOptions( Ui::Premium::AddGiftOptions(
buttonsParent, buttonsParent,
group, group,
@ -215,7 +216,7 @@ void GiftBox(
}); });
box->addButton(std::move(button)); box->addButton(std::move(button));
group->setValue(0); groupValueChangedCallback(0);
Data::PeerPremiumValue( Data::PeerPremiumValue(
user user

View file

@ -59,7 +59,20 @@ constexpr auto kSearchPerPage = 50;
object_ptr<Ui::BoxContent> PrepareContactsBox( object_ptr<Ui::BoxContent> PrepareContactsBox(
not_null<Window::SessionController*> sessionController) { not_null<Window::SessionController*> sessionController) {
using Mode = ContactsBoxController::SortMode; using Mode = ContactsBoxController::SortMode;
auto controller = std::make_unique<ContactsBoxController>( class Controller final : public ContactsBoxController {
public:
using ContactsBoxController::ContactsBoxController;
protected:
std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user) override {
return !user->isSelf()
? ContactsBoxController::createRow(user)
: nullptr;
}
};
auto controller = std::make_unique<Controller>(
&sessionController->session()); &sessionController->session());
controller->setStyleOverrides(&st::contactsWithStories); controller->setStyleOverrides(&st::contactsWithStories);
controller->setStoriesShown(true); controller->setStoriesShown(true);

View file

@ -105,6 +105,7 @@ PeerShortInfoCover::PeerShortInfoCover(
userpic userpic
) | rpl::start_with_next([=](PeerShortInfoUserpic &&value) { ) | rpl::start_with_next([=](PeerShortInfoUserpic &&value) {
applyUserpic(std::move(value)); applyUserpic(std::move(value));
applyAdditionalStatus(value.additionalStatus);
}, lifetime()); }, lifetime());
style::PaletteChanged( style::PaletteChanged(
@ -136,16 +137,7 @@ PeerShortInfoCover::PeerShortInfoCover(
return base::EventFilterResult::Cancel; return base::EventFilterResult::Cancel;
}); });
_name->moveToLeft( refreshLabelsGeometry();
_st.namePosition.x(),
_st.size - _st.namePosition.y() - _name->height(),
_st.size);
_status->moveToLeft(
_st.statusPosition.x(),
(_st.size
- _st.statusPosition.y()
- _status->height()),
_st.size);
_roundedTopImage = QImage( _roundedTopImage = QImage(
QSize(_st.size, _st.radius) * style::DevicePixelRatio(), QSize(_st.size, _st.radius) * style::DevicePixelRatio(),
@ -415,6 +407,23 @@ QImage PeerShortInfoCover::currentVideoFrame() const {
: QImage(); : QImage();
} }
void PeerShortInfoCover::applyAdditionalStatus(const QString &status) {
if (status.isEmpty()) {
if (_additionalStatus) {
_additionalStatus.destroy();
refreshLabelsGeometry();
}
return;
}
if (_additionalStatus) {
_additionalStatus->setText(status);
} else {
_additionalStatus.create(_widget.get(), status, _statusStyle->st);
_additionalStatus->show();
refreshLabelsGeometry();
}
}
void PeerShortInfoCover::applyUserpic(PeerShortInfoUserpic &&value) { void PeerShortInfoCover::applyUserpic(PeerShortInfoUserpic &&value) {
if (_index != value.index) { if (_index != value.index) {
_index = value.index; _index = value.index;
@ -593,6 +602,28 @@ void PeerShortInfoCover::refreshBarImages() {
_barLarge = makeBar(_largeWidth); _barLarge = makeBar(_largeWidth);
} }
void PeerShortInfoCover::refreshLabelsGeometry() {
const auto statusTop = _st.size
- _st.statusPosition.y()
- _status->height();
const auto diff = _st.namePosition.y()
- _name->height()
- _st.statusPosition.y();
if (_additionalStatus) {
_additionalStatus->moveToLeft(
_status->x(),
statusTop - diff - _additionalStatus->height());
}
_name->moveToLeft(
_st.namePosition.x(),
_st.size
- _st.namePosition.y()
- _name->height()
- (_additionalStatus ? (diff + _additionalStatus->height()) : 0),
_st.size);
_status->moveToLeft(_st.statusPosition.x(), statusTop, _st.size);
}
QRect PeerShortInfoCover::radialRect() const { QRect PeerShortInfoCover::radialRect() const {
const auto cover = _widget->rect(); const auto cover = _widget->rect();
const auto size = st::boxLoadingSize; const auto size = st::boxLoadingSize;
@ -654,12 +685,16 @@ rpl::producer<int> PeerShortInfoBox::moveRequests() const {
void PeerShortInfoBox::prepare() { void PeerShortInfoBox::prepare() {
addButton(tr::lng_close(), [=] { closeBox(); }); addButton(tr::lng_close(), [=] { closeBox(); });
// Perhaps a new lang key should be added for opening a group. if (_type != PeerShortInfoType::Self) {
addLeftButton((_type == PeerShortInfoType::User) // Perhaps a new lang key should be added for opening a group.
? tr::lng_profile_send_message() addLeftButton(
: (_type == PeerShortInfoType::Group) (_type == PeerShortInfoType::User)
? tr::lng_view_button_group() ? tr::lng_profile_send_message()
: tr::lng_profile_view_channel(), [=] { _openRequests.fire({}); }); : (_type == PeerShortInfoType::Group)
? tr::lng_view_button_group()
: tr::lng_profile_view_channel(),
[=] { _openRequests.fire({}); });
}
prepareRows(); prepareRows();

View file

@ -28,6 +28,7 @@ class RpWidget;
} // namespace Ui } // namespace Ui
enum class PeerShortInfoType { enum class PeerShortInfoType {
Self,
User, User,
Group, Group,
Channel, Channel,
@ -50,6 +51,7 @@ struct PeerShortInfoUserpic {
float64 photoLoadingProgress = 0.; float64 photoLoadingProgress = 0.;
std::shared_ptr<Media::Streaming::Document> videoDocument; std::shared_ptr<Media::Streaming::Document> videoDocument;
crl::time videoStartPosition = 0; crl::time videoStartPosition = 0;
QString additionalStatus;
}; };
class PeerShortInfoCover final { class PeerShortInfoCover final {
@ -87,6 +89,7 @@ private:
[[nodiscard]] QImage currentVideoFrame() const; [[nodiscard]] QImage currentVideoFrame() const;
void applyUserpic(PeerShortInfoUserpic &&value); void applyUserpic(PeerShortInfoUserpic &&value);
void applyAdditionalStatus(const QString &status);
[[nodiscard]] QRect radialRect() const; [[nodiscard]] QRect radialRect() const;
void videoWaiting(); void videoWaiting();
@ -99,6 +102,7 @@ private:
void updateRadialState(); void updateRadialState();
void refreshCoverCursor(); void refreshCoverCursor();
void refreshBarImages(); void refreshBarImages();
void refreshLabelsGeometry();
const style::ShortInfoCover &_st; const style::ShortInfoCover &_st;
@ -108,6 +112,7 @@ private:
object_ptr<Ui::FlatLabel> _name; object_ptr<Ui::FlatLabel> _name;
std::unique_ptr<CustomLabelStyle> _statusStyle; std::unique_ptr<CustomLabelStyle> _statusStyle;
object_ptr<Ui::FlatLabel> _status; object_ptr<Ui::FlatLabel> _status;
object_ptr<Ui::FlatLabel> _additionalStatus = { nullptr };
std::array<QImage, 4> _roundMask; std::array<QImage, 4> _roundMask;
QImage _userpicImage; QImage _userpicImage;

View file

@ -335,6 +335,15 @@ bool ProcessCurrent(
: state->photoView : state->photoView
? state->photoView->owner().get() ? state->photoView->owner().get()
: nullptr; : nullptr;
state->current.additionalStatus = (!peer->isUser())
? QString()
: ((state->photoId == userpicPhotoId)
&& peer->asUser()->hasPersonalPhoto())
? tr::lng_profile_photo_by_you(tr::now)
: ((state->current.index == (state->current.count - 1))
&& SyncUserFallbackPhotoViewer(peer->asUser()))
? tr::lng_profile_public_photo(tr::now)
: QString();
state->waitingLoad = false; state->waitingLoad = false;
if (!changedPhotoId if (!changedPhotoId
&& (state->current.index > 0 || !changedUserpic) && (state->current.index > 0 || !changedUserpic)
@ -422,7 +431,9 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
Fn<void()> open, Fn<void()> open,
Fn<bool()> videoPaused, Fn<bool()> videoPaused,
const style::ShortInfoBox *stOverride) { const style::ShortInfoBox *stOverride) {
const auto type = peer->isUser() const auto type = peer->isSelf()
? PeerShortInfoType::Self
: peer->isUser()
? PeerShortInfoType::User ? PeerShortInfoType::User
: peer->isBroadcast() : peer->isBroadcast()
? PeerShortInfoType::Channel ? PeerShortInfoType::Channel

View file

@ -211,7 +211,7 @@ private:
void setActionDown(int newActionDown); void setActionDown(int newActionDown);
void setPressed(SelectedRow pressed); void setPressed(SelectedRow pressed);
void setup(); void setup();
QRect relativeButtonRect(bool removeButton) const; QRect relativeButtonRect(bool removeButton, bool installedSet) const;
void ensureRipple(const style::RippleAnimation &st, QImage mask, bool removeButton); void ensureRipple(const style::RippleAnimation &st, QImage mask, bool removeButton);
bool shiftingAnimationCallback(crl::time now); bool shiftingAnimationCallback(crl::time now);
@ -234,7 +234,7 @@ private:
void readVisibleSets(); void readVisibleSets();
void updateControlsGeometry(); void updateControlsGeometry();
void rebuildAppendSet(not_null<StickersSet*> set, int maxNameWidth); void rebuildAppendSet(not_null<StickersSet*> set);
void fillSetCover(not_null<StickersSet*> set, DocumentData **outSticker, int *outWidth, int *outHeight) const; void fillSetCover(not_null<StickersSet*> set, DocumentData **outSticker, int *outWidth, int *outHeight) const;
int fillSetCount(not_null<StickersSet*> set) const; int fillSetCount(not_null<StickersSet*> set) const;
[[nodiscard]] QString fillSetTitle( [[nodiscard]] QString fillSetTitle(
@ -247,7 +247,7 @@ private:
void handleMegagroupSetAddressChange(); void handleMegagroupSetAddressChange();
void setMegagroupSelectedSet(const StickerSetIdentifier &set); void setMegagroupSelectedSet(const StickerSetIdentifier &set);
int countMaxNameWidth() const; int countMaxNameWidth(bool installedSet) const;
[[nodiscard]] bool skipPremium() const; [[nodiscard]] bool skipPremium() const;
const std::shared_ptr<ChatHelpers::Show> _show; const std::shared_ptr<ChatHelpers::Show> _show;
@ -255,9 +255,9 @@ private:
MTP::Sender _api; MTP::Sender _api;
const Section _section; const Section _section;
const bool _isInstalled; const bool _isInstalledTab;
Ui::RoundRect _buttonBgOver, _buttonBg; Ui::RoundRect _buttonBgOver, _buttonBg, _inactiveButtonBg;
int32 _rowHeight = 0; int32 _rowHeight = 0;
@ -282,6 +282,8 @@ private:
int _addWidth = 0; int _addWidth = 0;
QString _undoText; QString _undoText;
int _undoWidth = 0; int _undoWidth = 0;
QString _installedText;
int _installedWidth = 0;
QPoint _mouse; QPoint _mouse;
bool _inDragArea = false; bool _inDragArea = false;
@ -678,19 +680,19 @@ void StickersBox::refreshTabs() {
_tabIndices.clear(); _tabIndices.clear();
auto sections = std::vector<QString>(); auto sections = std::vector<QString>();
if (_installed.widget()) { if (_installed.widget()) {
sections.push_back(tr::lng_stickers_installed_tab(tr::now).toUpper()); sections.push_back(tr::lng_stickers_installed_tab(tr::now));
_tabIndices.push_back(Section::Installed); _tabIndices.push_back(Section::Installed);
} }
if (_masks.widget()) { if (_masks.widget()) {
sections.push_back(tr::lng_stickers_masks_tab(tr::now).toUpper()); sections.push_back(tr::lng_stickers_masks_tab(tr::now));
_tabIndices.push_back(Section::Masks); _tabIndices.push_back(Section::Masks);
} }
if (!stickers.featuredSetsOrder().isEmpty() && _featured.widget()) { if (!stickers.featuredSetsOrder().isEmpty() && _featured.widget()) {
sections.push_back(tr::lng_stickers_featured_tab(tr::now).toUpper()); sections.push_back(tr::lng_stickers_featured_tab(tr::now));
_tabIndices.push_back(Section::Featured); _tabIndices.push_back(Section::Featured);
} }
if (!archivedSetsOrder().isEmpty() && _archived.widget()) { if (!archivedSetsOrder().isEmpty() && _archived.widget()) {
sections.push_back(tr::lng_stickers_archived_tab(tr::now).toUpper()); sections.push_back(tr::lng_stickers_archived_tab(tr::now));
_tabIndices.push_back(Section::Archived); _tabIndices.push_back(Section::Archived);
} }
_tabs->setSections(sections); _tabs->setSections(sections);
@ -771,7 +773,7 @@ void StickersBox::updateTabsGeometry() {
auto featuredLeft = width() / maxTabs; auto featuredLeft = width() / maxTabs;
auto featuredRight = 2 * width() / maxTabs; auto featuredRight = 2 * width() / maxTabs;
auto featuredTextWidth = st::stickersTabs.labelStyle.font->width(tr::lng_stickers_featured_tab(tr::now).toUpper()); auto featuredTextWidth = st::stickersTabs.labelStyle.font->width(tr::lng_stickers_featured_tab(tr::now));
auto featuredTextRight = featuredLeft + (featuredRight - featuredLeft - featuredTextWidth) / 2 + featuredTextWidth; auto featuredTextRight = featuredLeft + (featuredRight - featuredLeft - featuredTextWidth) / 2 + featuredTextWidth;
auto unreadBadgeLeft = featuredTextRight - st::stickersFeaturedBadgeSkip; auto unreadBadgeLeft = featuredTextRight - st::stickersFeaturedBadgeSkip;
auto unreadBadgeTop = st::stickersFeaturedBadgeTop; auto unreadBadgeTop = st::stickersFeaturedBadgeTop;
@ -1133,26 +1135,32 @@ StickersBox::Inner::Inner(
, _session(&_show->session()) , _session(&_show->session())
, _api(&_session->mtp()) , _api(&_session->mtp())
, _section(section) , _section(section)
, _isInstalled(_section == Section::Installed || _section == Section::Masks) , _isInstalledTab(_section == Section::Installed
|| _section == Section::Masks)
, _buttonBgOver( , _buttonBgOver(
ImageRoundRadius::Small, ImageRoundRadius::Large,
(_isInstalled (_isInstalledTab
? st::stickersUndoRemove ? st::stickersUndoRemove
: st::stickersTrendingAdd).textBgOver) : st::stickersTrendingAdd).textBgOver)
, _buttonBg( , _buttonBg(
ImageRoundRadius::Small, ImageRoundRadius::Large,
(_isInstalled (_isInstalledTab
? st::stickersUndoRemove ? st::stickersUndoRemove
: st::stickersTrendingAdd).textBg) : st::stickersTrendingAdd).textBg)
, _inactiveButtonBg(
ImageRoundRadius::Large,
st::stickersTrendingInstalled.textBg)
, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
, _shiftingAnimation([=](crl::time now) { , _shiftingAnimation([=](crl::time now) {
return shiftingAnimationCallback(now); return shiftingAnimationCallback(now);
}) })
, _itemsTop(st::membersMarginTop) , _itemsTop(st::membersMarginTop)
, _addText(tr::lng_stickers_featured_add(tr::now).toUpper()) , _addText(tr::lng_stickers_featured_add(tr::now))
, _addWidth(st::stickersTrendingAdd.font->width(_addText)) , _addWidth(st::stickersTrendingAdd.font->width(_addText))
, _undoText(tr::lng_stickers_return(tr::now).toUpper()) , _undoText(tr::lng_stickers_return(tr::now))
, _undoWidth(st::stickersUndoRemove.font->width(_undoText)) { , _undoWidth(st::stickersUndoRemove.font->width(_undoText))
, _installedText(tr::lng_stickers_featured_installed(tr::now))
, _installedWidth(st::stickersTrendingInstalled.font->width(_installedText)) {
setup(); setup();
} }
@ -1165,17 +1173,21 @@ StickersBox::Inner::Inner(
, _session(&_show->session()) , _session(&_show->session())
, _api(&_session->mtp()) , _api(&_session->mtp())
, _section(StickersBox::Section::Installed) , _section(StickersBox::Section::Installed)
, _isInstalled(_section == Section::Installed || _section == Section::Masks) , _isInstalledTab(_section == Section::Installed
|| _section == Section::Masks)
, _buttonBgOver( , _buttonBgOver(
ImageRoundRadius::Small, ImageRoundRadius::Large,
(_isInstalled (_isInstalledTab
? st::stickersUndoRemove ? st::stickersUndoRemove
: st::stickersTrendingAdd).textBgOver) : st::stickersTrendingAdd).textBgOver)
, _buttonBg( , _buttonBg(
ImageRoundRadius::Small, ImageRoundRadius::Large,
(_isInstalled (_isInstalledTab
? st::stickersUndoRemove ? st::stickersUndoRemove
: st::stickersTrendingAdd).textBg) : st::stickersTrendingAdd).textBg)
, _inactiveButtonBg(
ImageRoundRadius::Large,
st::stickersTrendingInstalled.textBg)
, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
, _shiftingAnimation([=](crl::time now) { , _shiftingAnimation([=](crl::time now) {
return shiftingAnimationCallback(now); return shiftingAnimationCallback(now);
@ -1295,15 +1307,23 @@ void StickersBox::Inner::updateControlsGeometry() {
} }
} }
QRect StickersBox::Inner::relativeButtonRect(bool removeButton) const { QRect StickersBox::Inner::relativeButtonRect(
bool removeButton,
bool installedSet) const {
auto buttonw = st::stickersRemove.width; auto buttonw = st::stickersRemove.width;
auto buttonh = st::stickersRemove.height; auto buttonh = st::stickersRemove.height;
auto buttonshift = st::stickersRemoveSkip; auto buttonshift = st::stickersRemoveSkip;
if (!removeButton) { if (!removeButton) {
const auto &st = _isInstalled const auto &st = installedSet
? st::stickersTrendingInstalled
: _isInstalledTab
? st::stickersUndoRemove ? st::stickersUndoRemove
: st::stickersTrendingAdd; : st::stickersTrendingAdd;
const auto textWidth = _isInstalled ? _undoWidth : _addWidth; const auto textWidth = installedSet
? _installedWidth
: _isInstalledTab
? _undoWidth
: _addWidth;
buttonw = textWidth - st.width; buttonw = textWidth - st.width;
buttonh = st.height; buttonh = st.height;
buttonshift = 0; buttonshift = 0;
@ -1332,7 +1352,7 @@ void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> row, int index) {
} }
} }
if (_isInstalled) { if (_isInstalledTab) {
if (index >= 0 && index == _above) { if (index >= 0 && index == _above) {
auto current = _aboveShadowFadeOpacity.current(); auto current = _aboveShadowFadeOpacity.current();
if (_started >= 0) { if (_started >= 0) {
@ -1359,13 +1379,13 @@ void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> row, int index) {
paintFakeButton(p, row, index); paintFakeButton(p, row, index);
} }
if (row->removed && _isInstalled) { if (row->removed && _isInstalledTab) {
p.setOpacity(st::stickersRowDisabledOpacity); p.setOpacity(st::stickersRowDisabledOpacity);
} }
auto stickerx = st::contactsPadding.left(); auto stickerx = st::contactsPadding.left();
if (!_megagroupSet && _isInstalled) { if (!_megagroupSet && _isInstalledTab) {
stickerx += st::stickersReorderIcon.width() + st::stickersReorderSkip; stickerx += st::stickersReorderIcon.width() + st::stickersReorderSkip;
if (!row->isRecentSet()) { if (!row->isRecentSet()) {
st::stickersReorderIcon.paint(p, st::contactsPadding.left(), (_rowHeight - st::stickersReorderIcon.height()) / 2, width()); st::stickersReorderIcon.paint(p, st::contactsPadding.left(), (_rowHeight - st::stickersReorderIcon.height()) / 2, width());
@ -1553,7 +1573,7 @@ void StickersBox::Inner::updateRowThumbnail(not_null<Row*> row) {
Unexpected("StickersBox::Inner::updateRowThumbnail: row not found"); Unexpected("StickersBox::Inner::updateRowThumbnail: row not found");
}(); }();
const auto left = st::contactsPadding.left() const auto left = st::contactsPadding.left()
+ ((!_megagroupSet && _isInstalled) + ((!_megagroupSet && _isInstalledTab)
? st::stickersReorderIcon.width() + st::stickersReorderSkip ? st::stickersReorderIcon.width() + st::stickersReorderSkip
: 0); : 0);
update( update(
@ -1564,14 +1584,19 @@ void StickersBox::Inner::updateRowThumbnail(not_null<Row*> row) {
} }
void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> row, int index) { void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> row, int index) {
auto removeButton = (_isInstalled && !row->removed); const auto removeButton = (_isInstalledTab && !row->removed);
auto rect = relativeButtonRect(removeButton); if (!_isInstalledTab && row->isInstalled() && !row->isArchived() && !row->removed) {
if (!_isInstalled && row->isInstalled() && !row->isArchived() && !row->removed) { // Round button "Added" after installed from Trending or Archived.
// Checkbox after installed from Trending or Archived. const auto rect = relativeButtonRect(removeButton, true);
int checkx = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x() + (rect.width() + st::stickersFeaturedInstalled.width()) / 2); const auto &st = st::stickersTrendingInstalled;
int checky = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersFeaturedInstalled.height()) / 2; const auto textWidth = _installedWidth;
st::stickersFeaturedInstalled.paint(p, QPoint(checkx, checky), width()); const auto &text = _installedText;
_inactiveButtonBg.paint(p, myrtlrect(rect));
p.setFont(st.font);
p.setPen(st.textFg);
p.drawTextLeft(rect.x() - (st.width / 2), rect.y() + st.textTop, width(), text, textWidth);
} else { } else {
const auto rect = relativeButtonRect(removeButton, false);
auto selected = (index == _actionSel && _actionDown < 0) || (index == _actionDown); auto selected = (index == _actionSel && _actionDown < 0) || (index == _actionDown);
if (removeButton) { if (removeButton) {
// Trash icon button when not disabled in Installed. // Trash icon button when not disabled in Installed.
@ -1589,11 +1614,11 @@ void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> row, int ind
} else { } else {
// Round button ADD when not installed from Trending or Archived. // Round button ADD when not installed from Trending or Archived.
// Or round button UNDO after disabled from Installed. // Or round button UNDO after disabled from Installed.
const auto &st = _isInstalled const auto &st = _isInstalledTab
? st::stickersUndoRemove ? st::stickersUndoRemove
: st::stickersTrendingAdd; : st::stickersTrendingAdd;
const auto textWidth = _isInstalled ? _undoWidth : _addWidth; const auto textWidth = _isInstalledTab ? _undoWidth : _addWidth;
const auto &text = _isInstalled ? _undoText : _addText; const auto &text = _isInstalledTab ? _undoText : _addText;
(selected ? _buttonBgOver : _buttonBg).paint(p, myrtlrect(rect)); (selected ? _buttonBgOver : _buttonBg).paint(p, myrtlrect(rect));
if (row->ripple) { if (row->ripple) {
row->ripple->paint(p, rect.x(), rect.y(), width()); row->ripple->paint(p, rect.x(), rect.y(), width());
@ -1618,7 +1643,7 @@ void StickersBox::Inner::mousePressEvent(QMouseEvent *e) {
setActionDown(_actionSel); setActionDown(_actionSel);
update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight); update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight);
} else if (auto selectedIndex = std::get_if<int>(&_selected)) { } else if (auto selectedIndex = std::get_if<int>(&_selected)) {
if (_isInstalled && !_rows[*selectedIndex]->isRecentSet() && _inDragArea) { if (_isInstalledTab && !_rows[*selectedIndex]->isRecentSet() && _inDragArea) {
_above = _dragging = _started = *selectedIndex; _above = _dragging = _started = *selectedIndex;
_dragStart = mapFromGlobal(_mouse); _dragStart = mapFromGlobal(_mouse);
} }
@ -1640,12 +1665,12 @@ void StickersBox::Inner::setActionDown(int newActionDown) {
if (_actionDown >= 0 && _actionDown < _rows.size()) { if (_actionDown >= 0 && _actionDown < _rows.size()) {
update(0, _itemsTop + _actionDown * _rowHeight, width(), _rowHeight); update(0, _itemsTop + _actionDown * _rowHeight, width(), _rowHeight);
const auto row = _rows[_actionDown].get(); const auto row = _rows[_actionDown].get();
auto removeButton = (_isInstalled && !row->removed); auto removeButton = (_isInstalledTab && !row->removed);
if (!row->ripple) { if (!row->ripple) {
if (_isInstalled) { if (_isInstalledTab) {
if (row->removed) { if (row->removed) {
auto rippleSize = QSize(_undoWidth - st::stickersUndoRemove.width, st::stickersUndoRemove.height); auto rippleSize = QSize(_undoWidth - st::stickersUndoRemove.width, st::stickersUndoRemove.height);
auto rippleMask = Ui::RippleAnimation::RoundRectMask(rippleSize, st::roundRadiusSmall); auto rippleMask = Ui::RippleAnimation::RoundRectMask(rippleSize, st::roundRadiusLarge);
ensureRipple(st::stickersUndoRemove.ripple, std::move(rippleMask), removeButton); ensureRipple(st::stickersUndoRemove.ripple, std::move(rippleMask), removeButton);
} else { } else {
auto rippleSize = st::stickersRemove.rippleAreaSize; auto rippleSize = st::stickersRemove.rippleAreaSize;
@ -1654,12 +1679,12 @@ void StickersBox::Inner::setActionDown(int newActionDown) {
} }
} else if (!row->isInstalled() || row->isArchived() || row->removed) { } else if (!row->isInstalled() || row->isArchived() || row->removed) {
auto rippleSize = QSize(_addWidth - st::stickersTrendingAdd.width, st::stickersTrendingAdd.height); auto rippleSize = QSize(_addWidth - st::stickersTrendingAdd.width, st::stickersTrendingAdd.height);
auto rippleMask = Ui::RippleAnimation::RoundRectMask(rippleSize, st::roundRadiusSmall); auto rippleMask = Ui::RippleAnimation::RoundRectMask(rippleSize, st::roundRadiusLarge);
ensureRipple(st::stickersTrendingAdd.ripple, std::move(rippleMask), removeButton); ensureRipple(st::stickersTrendingAdd.ripple, std::move(rippleMask), removeButton);
} }
} }
if (row->ripple) { if (row->ripple) {
auto rect = relativeButtonRect(removeButton); auto rect = relativeButtonRect(removeButton, false);
row->ripple->add(mapFromGlobal(QCursor::pos()) - QPoint(myrtlrect(rect).x(), _itemsTop + _actionDown * _rowHeight + rect.y())); row->ripple->add(mapFromGlobal(QCursor::pos()) - QPoint(myrtlrect(rect).x(), _itemsTop + _actionDown * _rowHeight + rect.y()));
} }
} }
@ -1722,7 +1747,7 @@ void StickersBox::Inner::setPressed(SelectedRow pressed) {
void StickersBox::Inner::ensureRipple(const style::RippleAnimation &st, QImage mask, bool removeButton) { void StickersBox::Inner::ensureRipple(const style::RippleAnimation &st, QImage mask, bool removeButton) {
_rows[_actionDown]->ripple = std::make_unique<Ui::RippleAnimation>(st, std::move(mask), [this, index = _actionDown, removeButton] { _rows[_actionDown]->ripple = std::make_unique<Ui::RippleAnimation>(st, std::move(mask), [this, index = _actionDown, removeButton] {
update(myrtlrect(relativeButtonRect(removeButton).translated(0, _itemsTop + index * _rowHeight))); update(myrtlrect(relativeButtonRect(removeButton, false).translated(0, _itemsTop + index * _rowHeight)));
}); });
} }
@ -1785,14 +1810,14 @@ void StickersBox::Inner::updateSelected() {
selected = selectedIndex; selected = selectedIndex;
local.setY(local.y() - _itemsTop - selectedIndex * _rowHeight); local.setY(local.y() - _itemsTop - selectedIndex * _rowHeight);
const auto row = _rows[selectedIndex].get(); const auto row = _rows[selectedIndex].get();
if (!_megagroupSet && (_isInstalled || !row->isInstalled() || row->isArchived() || row->removed)) { if (!_megagroupSet && (_isInstalledTab || !row->isInstalled() || row->isArchived() || row->removed)) {
auto removeButton = (_isInstalled && !row->removed); auto removeButton = (_isInstalledTab && !row->removed);
auto rect = myrtlrect(relativeButtonRect(removeButton)); auto rect = myrtlrect(relativeButtonRect(removeButton, false));
actionSel = rect.contains(local) ? selectedIndex : -1; actionSel = rect.contains(local) ? selectedIndex : -1;
} else { } else {
actionSel = -1; actionSel = -1;
} }
if (!_megagroupSet && _isInstalled && !row->isRecentSet()) { if (!_megagroupSet && _isInstalledTab && !row->isRecentSet()) {
auto dragAreaWidth = st::contactsPadding.left() + st::stickersReorderIcon.width() + st::stickersReorderSkip; auto dragAreaWidth = st::contactsPadding.left() + st::stickersReorderIcon.width() + st::stickersReorderSkip;
auto dragArea = myrtlrect(0, 0, dragAreaWidth, _rowHeight); auto dragArea = myrtlrect(0, 0, dragAreaWidth, _rowHeight);
inDragArea = dragArea.contains(local); inDragArea = dragArea.contains(local);
@ -1816,7 +1841,7 @@ void StickersBox::Inner::updateSelected() {
void StickersBox::Inner::updateCursor() { void StickersBox::Inner::updateCursor() {
setCursor(_inDragArea setCursor(_inDragArea
? style::cur_sizeall ? style::cur_sizeall
: (!_megagroupSet && _isInstalled) : (!_megagroupSet && _isInstalledTab)
? ((_actionSel >= 0 && (_actionDown < 0 || _actionDown == _actionSel)) ? ((_actionSel >= 0 && (_actionDown < 0 || _actionDown == _actionSel))
? style::cur_pointer ? style::cur_pointer
: style::cur_default) : style::cur_default)
@ -1841,7 +1866,7 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
_mouse = e->globalPos(); _mouse = e->globalPos();
updateSelected(); updateSelected();
if (_actionDown == _actionSel && _actionSel >= 0) { if (_actionDown == _actionSel && _actionSel >= 0) {
if (_isInstalled) { if (_isInstalledTab) {
setRowRemoved(_actionDown, !_rows[_actionDown]->removed); setRowRemoved(_actionDown, !_rows[_actionDown]->removed);
} else if (_installSetCallback) { } else if (_installSetCallback) {
_installSetCallback(_rows[_actionDown]->set->id); _installSetCallback(_rows[_actionDown]->set->id);
@ -2079,15 +2104,15 @@ void StickersBox::Inner::rebuildMegagroupSet() {
} }
const auto set = it->second.get(); const auto set = it->second.get();
auto maxNameWidth = countMaxNameWidth();
auto titleWidth = 0;
auto title = fillSetTitle(set, maxNameWidth, &titleWidth);
auto count = fillSetCount(set); auto count = fillSetCount(set);
auto sticker = (DocumentData*)nullptr; auto sticker = (DocumentData*)nullptr;
auto pixw = 0, pixh = 0; auto pixw = 0, pixh = 0;
fillSetCover(set, &sticker, &pixw, &pixh); fillSetCover(set, &sticker, &pixw, &pixh);
auto flagsOverride = SetFlag::Installed; auto flagsOverride = SetFlag::Installed;
auto removed = false; auto removed = false;
auto maxNameWidth = countMaxNameWidth(!_isInstalledTab);
auto titleWidth = 0;
auto title = fillSetTitle(set, maxNameWidth, &titleWidth);
if (!_megagroupSelectedSet if (!_megagroupSelectedSet
|| _megagroupSelectedSet->set->id != set->id) { || _megagroupSelectedSet->set->id != set->id) {
_megagroupSetField->setText(set->shortName); _megagroupSetField->setText(set->shortName);
@ -2125,8 +2150,6 @@ void StickersBox::Inner::rebuild(bool masks) {
rebuildMegagroupSet(); rebuildMegagroupSet();
} }
auto maxNameWidth = countMaxNameWidth();
_oldRows = std::move(_rows); _oldRows = std::move(_rows);
clear(); clear();
const auto &order = ([&]() -> const StickersSetsOrder & { const auto &order = ([&]() -> const StickersSetsOrder & {
@ -2155,12 +2178,12 @@ void StickersBox::Inner::rebuild(bool masks) {
? tr::lng_stickers_group_from_featured(tr::now) ? tr::lng_stickers_group_from_featured(tr::now)
: tr::lng_stickers_group_from_your(tr::now)); : tr::lng_stickers_group_from_your(tr::now));
updateControlsGeometry(); updateControlsGeometry();
} else if (_isInstalled) { } else if (_isInstalledTab) {
const auto cloudIt = sets.find((_section == Section::Masks) const auto cloudIt = sets.find((_section == Section::Masks)
? Data::Stickers::CloudRecentAttachedSetId ? Data::Stickers::CloudRecentAttachedSetId
: Data::Stickers::CloudRecentSetId); // Section::Installed. : Data::Stickers::CloudRecentSetId); // Section::Installed.
if (cloudIt != sets.cend() && !cloudIt->second->stickers.isEmpty()) { if (cloudIt != sets.cend() && !cloudIt->second->stickers.isEmpty()) {
rebuildAppendSet(cloudIt->second.get(), maxNameWidth); rebuildAppendSet(cloudIt->second.get());
} }
} }
for (const auto setId : order) { for (const auto setId : order) {
@ -2170,7 +2193,7 @@ void StickersBox::Inner::rebuild(bool masks) {
} }
const auto set = it->second.get(); const auto set = it->second.get();
rebuildAppendSet(set, maxNameWidth); rebuildAppendSet(set);
if (set->stickers.isEmpty() if (set->stickers.isEmpty()
|| (set->flags & SetFlag::NotLoaded)) { || (set->flags & SetFlag::NotLoaded)) {
@ -2205,7 +2228,8 @@ void StickersBox::Inner::updateSize(int newWidth) {
} }
void StickersBox::Inner::updateRows() { void StickersBox::Inner::updateRows() {
int maxNameWidth = countMaxNameWidth(); const auto maxNameWidth = countMaxNameWidth(false);
const auto maxNameWidthInstalled = countMaxNameWidth(true);
const auto &sets = session().data().stickers().sets(); const auto &sets = session().data().stickers().sets();
for (const auto &row : _rows) { for (const auto &row : _rows) {
const auto it = sets.find(row->set->id); const auto it = sets.find(row->set->id);
@ -2231,7 +2255,7 @@ void StickersBox::Inner::updateRows() {
auto wasInstalled = row->isInstalled(); auto wasInstalled = row->isInstalled();
auto wasArchived = row->isArchived(); auto wasArchived = row->isArchived();
row->flagsOverride = fillSetFlags(set); row->flagsOverride = fillSetFlags(set);
if (_isInstalled) { if (_isInstalledTab) {
row->flagsOverride &= ~SetFlag::Archived; row->flagsOverride &= ~SetFlag::Archived;
} }
if (row->isInstalled() != wasInstalled if (row->isInstalled() != wasInstalled
@ -2239,7 +2263,14 @@ void StickersBox::Inner::updateRows() {
row->ripple.reset(); row->ripple.reset();
} }
} }
row->title = fillSetTitle(set, maxNameWidth, &row->titleWidth); const auto installedSet = (!_isInstalledTab
&& row->isInstalled()
&& !row->isArchived()
&& !row->removed);
row->title = fillSetTitle(
set,
installedSet ? maxNameWidthInstalled : maxNameWidth,
&row->titleWidth);
row->count = fillSetCount(set); row->count = fillSetCount(set);
} }
update(); update();
@ -2251,7 +2282,7 @@ bool StickersBox::Inner::appendSet(not_null<StickersSet*> set) {
return false; return false;
} }
} }
rebuildAppendSet(set, countMaxNameWidth()); rebuildAppendSet(set);
return true; return true;
} }
@ -2259,18 +2290,20 @@ bool StickersBox::Inner::skipPremium() const {
return !_session->premiumPossible(); return !_session->premiumPossible();
} }
int StickersBox::Inner::countMaxNameWidth() const { int StickersBox::Inner::countMaxNameWidth(bool installedSet) const {
int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left();
if (!_megagroupSet && _isInstalled) { if (!_megagroupSet && _isInstalledTab) {
namex += st::stickersReorderIcon.width() + st::stickersReorderSkip; namex += st::stickersReorderIcon.width() + st::stickersReorderSkip;
} }
int namew = st::boxWideWidth - namex - st::contactsPadding.right() - st::contactsCheckPosition.x(); int namew = st::boxWideWidth - namex - st::contactsPadding.right() - st::contactsCheckPosition.x();
if (_isInstalled) { if (_isInstalledTab) {
if (!_megagroupSet) { if (!_megagroupSet) {
namew -= _undoWidth - st::stickersUndoRemove.width; namew -= _undoWidth - st::stickersUndoRemove.width;
} }
} else { } else {
namew -= _addWidth - st::stickersTrendingAdd.width; namew -= installedSet
? (_installedWidth - st::stickersTrendingInstalled.width)
: (_addWidth - st::stickersTrendingAdd.width);
if (_section == Section::Featured) { if (_section == Section::Featured) {
namew -= st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip; namew -= st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip;
} }
@ -2278,14 +2311,12 @@ int StickersBox::Inner::countMaxNameWidth() const {
return namew; return namew;
} }
void StickersBox::Inner::rebuildAppendSet( void StickersBox::Inner::rebuildAppendSet(not_null<StickersSet*> set) {
not_null<StickersSet*> set,
int maxNameWidth) {
auto flagsOverride = (set->id != Data::Stickers::CloudRecentSetId) auto flagsOverride = (set->id != Data::Stickers::CloudRecentSetId)
? fillSetFlags(set) ? fillSetFlags(set)
: SetFlag::Installed; : SetFlag::Installed;
auto removed = false; auto removed = false;
if (_isInstalled && (flagsOverride & SetFlag::Archived)) { if (_isInstalledTab && (flagsOverride & SetFlag::Archived)) {
return; return;
} }
@ -2293,6 +2324,10 @@ void StickersBox::Inner::rebuildAppendSet(
int pixw = 0, pixh = 0; int pixw = 0, pixh = 0;
fillSetCover(set, &sticker, &pixw, &pixh); fillSetCover(set, &sticker, &pixw, &pixh);
const auto maxNameWidth = countMaxNameWidth(!_isInstalledTab
&& (flagsOverride & SetFlag::Installed)
&& !(flagsOverride & SetFlag::Archived)
&& !removed);
int titleWidth = 0; int titleWidth = 0;
QString title = fillSetTitle(set, maxNameWidth, &titleWidth); QString title = fillSetTitle(set, maxNameWidth, &titleWidth);
int count = fillSetCount(set); int count = fillSetCount(set);

View file

@ -393,8 +393,12 @@ void UsernamesBox(
editor->submitted( editor->submitted(
) | rpl::start_with_next(finish, editor->lifetime()); ) | rpl::start_with_next(finish, editor->lifetime());
box->addButton(tr::lng_settings_save(), finish); if (isBot) {
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); box->addButton(tr::lng_close(), [=] { box->closeBox(); });
} else {
box->addButton(tr::lng_settings_save(), finish);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
} }
void AddUsernameCheckLabel( void AddUsernameCheckLabel(

View file

@ -744,6 +744,11 @@ groupCallShareBoxComment: InputField(groupCallField) {
} }
groupCallShareBoxList: PeerList(groupCallMembersList) { groupCallShareBoxList: PeerList(groupCallMembersList) {
item: PeerListItem(groupCallMembersListItem) { item: PeerListItem(groupCallMembersListItem) {
nameStyle: TextStyle(defaultTextStyle) {
font: font(11px);
linkFont: font(11px);
linkFontOver: font(11px);
}
checkbox: RoundImageCheckbox(groupCallMembersListCheckbox) { checkbox: RoundImageCheckbox(groupCallMembersListCheckbox) {
imageRadius: 28px; imageRadius: 28px;
imageSmallRadius: 24px; imageSmallRadius: 24px;
@ -1333,13 +1338,7 @@ groupCallNiceTooltip: ImportantTooltip(defaultImportantTooltip) {
radius: 4px; radius: 4px;
arrow: 4px; arrow: 4px;
} }
groupCallNiceTooltipLabel: FlatLabel(defaultImportantTooltipLabel) { groupCallNiceTooltipLabel: defaultImportantTooltipLabel;
style: TextStyle(defaultTextStyle) {
font: font(11px);
linkFont: font(11px);
linkFontOver: font(11px underline);
}
}
groupCallStickedTooltip: ImportantTooltip(groupCallNiceTooltip) { groupCallStickedTooltip: ImportantTooltip(groupCallNiceTooltip) {
padding: margins(10px, 1px, 6px, 3px); padding: margins(10px, 1px, 6px, 3px);
} }

View file

@ -2060,6 +2060,7 @@ void Panel::showNiceTooltip(
(normal ? widget().get() : container), (normal ? widget().get() : container),
std::move(text), std::move(text),
st::groupCallNiceTooltipLabel); st::groupCallNiceTooltipLabel);
label->resizeToNaturalWidth(label->naturalWidth());
if (normal) { if (normal) {
return label; return label;
} }

View file

@ -105,7 +105,6 @@ EmojiPan {
trendingHeaderFg: color; trendingHeaderFg: color;
trendingSubheaderFg: color; trendingSubheaderFg: color;
trendingUnreadFg: color; trendingUnreadFg: color;
trendingInstalled: icon;
overBg: color; overBg: color;
pathBg: color; pathBg: color;
pathFg: color; pathFg: color;
@ -204,6 +203,7 @@ ComposeControls {
record: RecordBar; record: RecordBar;
files: ComposeFiles; files: ComposeFiles;
premium: PremiumLimits; premium: PremiumLimits;
boxField: InputField;
} }
ReportBox { ReportBox {
@ -287,6 +287,12 @@ stickersTrendingAdd: RoundButton(defaultActiveButton) {
height: 26px; height: 26px;
textTop: 4px; textTop: 4px;
} }
stickersTrendingInstalled: RoundButton(stickersTrendingAdd) {
textFg: activeButtonBg;
textFgOver: activeButtonBgOver;
textBg: activeButtonSecondaryFg;
textBgOver: activeButtonSecondaryFgOver;
}
stickersRemove: IconButton(defaultIconButton) { stickersRemove: IconButton(defaultIconButton) {
width: 40px; width: 40px;
height: 40px; height: 40px;
@ -323,7 +329,6 @@ stickersFeaturedUnreadBg: msgFileInBg;
stickersFeaturedUnreadSize: 5px; stickersFeaturedUnreadSize: 5px;
stickersFeaturedUnreadSkip: 5px; stickersFeaturedUnreadSkip: 5px;
stickersFeaturedUnreadTop: 7px; stickersFeaturedUnreadTop: 7px;
stickersFeaturedInstalled: icon {{ "chat/input_save", lightButtonFg }};
stickersMaxHeight: 320px; stickersMaxHeight: 320px;
stickersPadding: margins(19px, 13px, 19px, 13px); stickersPadding: margins(19px, 13px, 19px, 13px);
@ -590,7 +595,6 @@ defaultEmojiPan: EmojiPan {
trendingHeaderFg: stickersTrendingHeaderFg; trendingHeaderFg: stickersTrendingHeaderFg;
trendingSubheaderFg: stickersTrendingSubheaderFg; trendingSubheaderFg: stickersTrendingSubheaderFg;
trendingUnreadFg: stickersFeaturedUnreadBg; trendingUnreadFg: stickersFeaturedUnreadBg;
trendingInstalled: stickersFeaturedInstalled;
overBg: emojiPanHover; overBg: emojiPanHover;
pathBg: windowBgRipple; pathBg: windowBgRipple;
pathFg: windowBgOver; pathFg: windowBgOver;
@ -1148,6 +1152,7 @@ defaultComposeControls: ComposeControls {
record: defaultRecordBar; record: defaultRecordBar;
files: defaultComposeFiles; files: defaultComposeFiles;
premium: defaultPremiumLimits; premium: defaultPremiumLimits;
boxField: defaultInputField;
} }
moreChatsBarHeight: 48px; moreChatsBarHeight: 48px;

View file

@ -188,7 +188,7 @@ StickersListWidget::StickersListWidget(
, _mode(descriptor.mode) , _mode(descriptor.mode)
, _show(std::move(descriptor.show)) , _show(std::move(descriptor.show))
, _features(descriptor.features) , _features(descriptor.features)
, _overBg(st::roundRadiusSmall, st().overBg) , _overBg(st::roundRadiusLarge, st().overBg)
, _api(&session().mtp()) , _api(&session().mtp())
, _localSetsManager(std::make_unique<LocalStickersManager>(&session())) , _localSetsManager(std::make_unique<LocalStickersManager>(&session()))
, _section(Section::Stickers) , _section(Section::Stickers)
@ -196,22 +196,27 @@ StickersListWidget::StickersListWidget(
, _updateItemsTimer([=] { updateItems(); }) , _updateItemsTimer([=] { updateItems(); })
, _updateSetsTimer([=] { updateSets(); }) , _updateSetsTimer([=] { updateSets(); })
, _trendingAddBgOver( , _trendingAddBgOver(
ImageRoundRadius::Small, ImageRoundRadius::Large,
st::stickersTrendingAdd.textBgOver) st::stickersTrendingAdd.textBgOver)
, _trendingAddBg(ImageRoundRadius::Small, st::stickersTrendingAdd.textBg) , _trendingAddBg(ImageRoundRadius::Large, st::stickersTrendingAdd.textBg)
, _inactiveButtonBg(
ImageRoundRadius::Large,
st::stickersTrendingInstalled.textBg)
, _groupCategoryAddBgOver( , _groupCategoryAddBgOver(
ImageRoundRadius::Small, ImageRoundRadius::Large,
st::stickerGroupCategoryAdd.textBgOver) st::stickerGroupCategoryAdd.textBgOver)
, _groupCategoryAddBg( , _groupCategoryAddBg(
ImageRoundRadius::Small, ImageRoundRadius::Large,
st::stickerGroupCategoryAdd.textBg) st::stickerGroupCategoryAdd.textBg)
, _pathGradient(std::make_unique<Ui::PathShiftGradient>( , _pathGradient(std::make_unique<Ui::PathShiftGradient>(
st().pathBg, st().pathBg,
st().pathFg, st().pathFg,
[=] { update(); })) [=] { update(); }))
, _megagroupSetAbout(st::columnMinimalWidthThird - st::emojiScroll.width - st().headerLeft) , _megagroupSetAbout(st::columnMinimalWidthThird - st::emojiScroll.width - st().headerLeft)
, _addText(tr::lng_stickers_featured_add(tr::now).toUpper()) , _addText(tr::lng_stickers_featured_add(tr::now))
, _addWidth(st::stickersTrendingAdd.font->width(_addText)) , _addWidth(st::stickersTrendingAdd.font->width(_addText))
, _installedText(tr::lng_stickers_featured_installed(tr::now))
, _installedWidth(st::stickersTrendingInstalled.font->width(_installedText))
, _settings(this, tr::lng_stickers_you_have(tr::now)) , _settings(this, tr::lng_stickers_you_have(tr::now))
, _previewTimer([=] { showPreview(); }) , _previewTimer([=] { showPreview(); })
, _premiumMark(std::make_unique<StickerPremiumMark>(&session())) , _premiumMark(std::make_unique<StickerPremiumMark>(&session()))
@ -898,26 +903,40 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
: loadedCount; : loadedCount;
auto widthForTitle = stickersRight() - (st().headerLeft - st().margin.left()); auto widthForTitle = stickersRight() - (st().headerLeft - st().margin.left());
if (featuredHasAddButton(info.section)) { {
auto add = featuredAddRect(info); const auto installedSet = !featuredHasAddButton(info.section);
auto selected = selectedButton ? (selectedButton->section == info.section) : false; const auto add = featuredAddRect(info, installedSet);
(selected ? _trendingAddBgOver : _trendingAddBg).paint(p, myrtlrect(add)); const auto selected = selectedButton
? (selectedButton->section == info.section)
: false;
(installedSet
? _inactiveButtonBg
: selected
? _trendingAddBgOver
: _trendingAddBg).paint(p, myrtlrect(add));
if (set.ripple) { if (set.ripple) {
set.ripple->paint(p, add.x(), add.y(), width()); set.ripple->paint(p, add.x(), add.y(), width());
if (set.ripple->empty()) { if (set.ripple->empty()) {
set.ripple.reset(); set.ripple.reset();
} }
} }
p.setFont(st::stickersTrendingAdd.font); const auto &text = installedSet ? _installedText : _addText;
p.setPen(selected ? st::stickersTrendingAdd.textFgOver : st::stickersTrendingAdd.textFg); const auto textWidth = installedSet
p.drawTextLeft(add.x() - (st::stickersTrendingAdd.width / 2), add.y() + st::stickersTrendingAdd.textTop, width(), _addText, _addWidth); ? _installedWidth
: _addWidth;
const auto &st = installedSet
? st::stickersTrendingInstalled
: st::stickersTrendingAdd;
p.setFont(st.font);
p.setPen(selected ? st.textFgOver : st.textFg);
p.drawTextLeft(
add.x() - (st.width / 2),
add.y() + st.textTop,
width(),
text,
textWidth);
widthForTitle -= add.width() - (st::stickersTrendingAdd.width / 2); widthForTitle -= add.width() - (st.width / 2);
} else {
auto add = featuredAddRect(info);
int checkx = add.left() + (add.width() - st::stickersFeaturedInstalled.width()) / 2;
int checky = add.top() + (add.height() - st::stickersFeaturedInstalled.height()) / 2;
st().trendingInstalled.paint(p, QPoint(checkx, checky), width());
} }
if (set.flags & SetFlag::Unread) { if (set.flags & SetFlag::Unread) {
widthForTitle -= st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip; widthForTitle -= st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip;
@ -1455,14 +1474,17 @@ bool StickersListWidget::featuredHasAddButton(int index) const {
} }
QRect StickersListWidget::featuredAddRect(int index) const { QRect StickersListWidget::featuredAddRect(int index) const {
return featuredAddRect(sectionInfo(index)); return featuredAddRect(sectionInfo(index), false);
} }
QRect StickersListWidget::featuredAddRect(const SectionInfo &info) const { QRect StickersListWidget::featuredAddRect(
auto addw = _addWidth - st::stickersTrendingAdd.width; const SectionInfo &info,
auto addh = st::stickersTrendingAdd.height; bool installedSet) const {
auto addx = stickersRight() - addw; const auto addw = (installedSet ? _installedWidth : _addWidth)
auto addy = info.top + st::stickersTrendingAddTop; - st::stickersTrendingAdd.width;
const auto addh = st::stickersTrendingAdd.height;
const auto addx = stickersRight() - addw;
const auto addy = info.top + st::stickersTrendingAddTop;
return QRect(addx, addy, addw, addh); return QRect(addx, addy, addw, addh);
} }
@ -1538,7 +1560,7 @@ void StickersListWidget::setPressed(OverState newPressed) {
} else if (std::get_if<OverGroupAdd>(&_pressed)) { } else if (std::get_if<OverGroupAdd>(&_pressed)) {
if (!_megagroupSetButtonRipple) { if (!_megagroupSetButtonRipple) {
auto maskSize = _megagroupSetButtonRect.size(); auto maskSize = _megagroupSetButtonRect.size();
auto mask = Ui::RippleAnimation::RoundRectMask(maskSize, st::roundRadiusSmall); auto mask = Ui::RippleAnimation::RoundRectMask(maskSize, st::roundRadiusLarge);
_megagroupSetButtonRipple = std::make_unique<Ui::RippleAnimation>(st::stickerGroupCategoryAdd.ripple, std::move(mask), [this] { _megagroupSetButtonRipple = std::make_unique<Ui::RippleAnimation>(st::stickerGroupCategoryAdd.ripple, std::move(mask), [this] {
rtlupdate(megagroupSetButtonRectFinal()); rtlupdate(megagroupSetButtonRectFinal());
}); });
@ -1566,7 +1588,7 @@ std::unique_ptr<Ui::RippleAnimation> StickersListWidget::createButtonRipple(int
if (shownSets()[section].externalLayout) { if (shownSets()[section].externalLayout) {
auto maskSize = QSize(_addWidth - st::stickersTrendingAdd.width, st::stickersTrendingAdd.height); auto maskSize = QSize(_addWidth - st::stickersTrendingAdd.width, st::stickersTrendingAdd.height);
auto mask = Ui::RippleAnimation::RoundRectMask(maskSize, st::roundRadiusSmall); auto mask = Ui::RippleAnimation::RoundRectMask(maskSize, st::roundRadiusLarge);
return std::make_unique<Ui::RippleAnimation>( return std::make_unique<Ui::RippleAnimation>(
st::stickersTrendingAdd.ripple, st::stickersTrendingAdd.ripple,
std::move(mask), std::move(mask),
@ -1768,6 +1790,7 @@ void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) {
Assert(button->section >= 0 && button->section < sets.size()); Assert(button->section >= 0 && button->section < sets.size());
if (sets[button->section].externalLayout) { if (sets[button->section].externalLayout) {
_localSetsManager->install(sets[button->section].id); _localSetsManager->install(sets[button->section].id);
update();
} else { } else {
removeSet(sets[button->section].id); removeSet(sets[button->section].id);
} }
@ -2384,7 +2407,7 @@ void StickersListWidget::updateSelected() {
if (p.y() >= info.top && p.y() < info.rowsTop) { if (p.y() >= info.top && p.y() < info.rowsTop) {
if (hasRemoveButton(section) && myrtlrect(removeButtonRect(info)).contains(p.x(), p.y())) { if (hasRemoveButton(section) && myrtlrect(removeButtonRect(info)).contains(p.x(), p.y())) {
newSelected = OverButton{ section }; newSelected = OverButton{ section };
} else if (featuredHasAddButton(section) && myrtlrect(featuredAddRect(info)).contains(p.x(), p.y())) { } else if (featuredHasAddButton(section) && myrtlrect(featuredAddRect(info, false)).contains(p.x(), p.y())) {
newSelected = OverButton{ section }; newSelected = OverButton{ section };
} else if (_features.openStickerSets } else if (_features.openStickerSets
&& !(sets[section].flags & SetFlag::Special)) { && !(sets[section].flags & SetFlag::Special)) {
@ -2587,7 +2610,7 @@ void StickersListWidget::showMegagroupSet(ChannelData *megagroup) {
_megagroupSetAbout.setText( _megagroupSetAbout.setText(
st::stickerGroupCategoryAbout, st::stickerGroupCategoryAbout,
tr::lng_group_stickers_description(tr::now)); tr::lng_group_stickers_description(tr::now));
_megagroupSetButtonText = tr::lng_group_stickers_add(tr::now).toUpper(); _megagroupSetButtonText = tr::lng_group_stickers_add(tr::now);
refreshMegagroupSetGeometry(); refreshMegagroupSetGeometry();
} }
_megagroupSetButtonRipple.reset(); _megagroupSetButtonRipple.reset();

View file

@ -302,7 +302,9 @@ private:
[[nodiscard]] int stickersRight() const; [[nodiscard]] int stickersRight() const;
[[nodiscard]] bool featuredHasAddButton(int index) const; [[nodiscard]] bool featuredHasAddButton(int index) const;
[[nodiscard]] QRect featuredAddRect(int index) const; [[nodiscard]] QRect featuredAddRect(int index) const;
[[nodiscard]] QRect featuredAddRect(const SectionInfo &info) const; [[nodiscard]] QRect featuredAddRect(
const SectionInfo &info,
bool installedSet) const;
[[nodiscard]] bool hasRemoveButton(int index) const; [[nodiscard]] bool hasRemoveButton(int index) const;
[[nodiscard]] QRect removeButtonRect(int index) const; [[nodiscard]] QRect removeButtonRect(int index) const;
[[nodiscard]] QRect removeButtonRect(const SectionInfo &info) const; [[nodiscard]] QRect removeButtonRect(const SectionInfo &info) const;
@ -392,7 +394,7 @@ private:
OverState _pressed; OverState _pressed;
QPoint _lastMousePosition; QPoint _lastMousePosition;
Ui::RoundRect _trendingAddBgOver, _trendingAddBg; Ui::RoundRect _trendingAddBgOver, _trendingAddBg, _inactiveButtonBg;
Ui::RoundRect _groupCategoryAddBgOver, _groupCategoryAddBg; Ui::RoundRect _groupCategoryAddBgOver, _groupCategoryAddBg;
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient; const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
@ -405,6 +407,8 @@ private:
QString _addText; QString _addText;
int _addWidth; int _addWidth;
QString _installedText;
int _installedWidth;
object_ptr<Ui::LinkButton> _settings; object_ptr<Ui::LinkButton> _settings;

View file

@ -147,6 +147,7 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) {
my.show->showBox(std::move(box)); my.show->showBox(std::move(box));
} else if (use) { } else if (use) {
use->show(std::move(box)); use->show(std::move(box));
use->activate();
} }
} else { } else {
open(); open();

View file

@ -202,7 +202,8 @@ QByteArray Settings::serialize() const {
+ sizeof(qint32) * 3 + sizeof(qint32) * 3
+ Serialize::bytearraySize(mediaViewPosition) + Serialize::bytearraySize(mediaViewPosition)
+ sizeof(qint32) + sizeof(qint32)
+ sizeof(quint64); + sizeof(quint64)
+ sizeof(qint32);
auto result = QByteArray(); auto result = QByteArray();
result.reserve(size); result.reserve(size);
@ -336,7 +337,8 @@ QByteArray Settings::serialize() const {
<< qint32(_windowTitleContent.current().hideTotalUnread ? 1 : 0) << qint32(_windowTitleContent.current().hideTotalUnread ? 1 : 0)
<< mediaViewPosition << mediaViewPosition
<< qint32(_ignoreBatterySaving.current() ? 1 : 0) << qint32(_ignoreBatterySaving.current() ? 1 : 0)
<< quint64(_macRoundIconDigest.value_or(0)); << quint64(_macRoundIconDigest.value_or(0))
<< qint32(_storiesClickTooltipHidden.current() ? 1 : 0);
} }
return result; return result;
} }
@ -444,6 +446,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
QByteArray mediaViewPosition; QByteArray mediaViewPosition;
qint32 ignoreBatterySaving = _ignoreBatterySaving.current() ? 1 : 0; qint32 ignoreBatterySaving = _ignoreBatterySaving.current() ? 1 : 0;
quint64 macRoundIconDigest = _macRoundIconDigest.value_or(0); quint64 macRoundIconDigest = _macRoundIconDigest.value_or(0);
qint32 storiesClickTooltipHidden = _storiesClickTooltipHidden.current() ? 1 : 0;
stream >> themesAccentColors; stream >> themesAccentColors;
if (!stream.atEnd()) { if (!stream.atEnd()) {
@ -678,6 +681,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
if (!stream.atEnd()) { if (!stream.atEnd()) {
stream >> macRoundIconDigest; stream >> macRoundIconDigest;
} }
if (!stream.atEnd()) {
stream >> storiesClickTooltipHidden;
}
if (stream.status() != QDataStream::Ok) { if (stream.status() != QDataStream::Ok) {
LOG(("App Error: " LOG(("App Error: "
"Bad data for Core::Settings::constructFromSerialized()")); "Bad data for Core::Settings::constructFromSerialized()"));
@ -869,6 +875,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
} }
_ignoreBatterySaving = (ignoreBatterySaving == 1); _ignoreBatterySaving = (ignoreBatterySaving == 1);
_macRoundIconDigest = macRoundIconDigest ? macRoundIconDigest : std::optional<uint64>(); _macRoundIconDigest = macRoundIconDigest ? macRoundIconDigest : std::optional<uint64>();
_storiesClickTooltipHidden = (storiesClickTooltipHidden == 1);
} }
QString Settings::getSoundPath(const QString &key) const { QString Settings::getSoundPath(const QString &key) const {
@ -1159,6 +1166,7 @@ void Settings::resetOnLastLogout() {
_tabbedReplacedWithInfo = false; // per-window _tabbedReplacedWithInfo = false; // per-window
_systemDarkModeEnabled = false; _systemDarkModeEnabled = false;
_hiddenGroupCallTooltips = 0; _hiddenGroupCallTooltips = 0;
_storiesClickTooltipHidden = 0;
_recentEmojiPreload.clear(); _recentEmojiPreload.clear();
_recentEmoji.clear(); _recentEmoji.clear();

View file

@ -802,6 +802,15 @@ public:
[[nodiscard]] std::optional<uint64> macRoundIconDigest() const { [[nodiscard]] std::optional<uint64> macRoundIconDigest() const {
return _macRoundIconDigest; return _macRoundIconDigest;
} }
[[nodiscard]] bool storiesClickTooltipHidden() const {
return _storiesClickTooltipHidden.current();
}
[[nodiscard]] rpl::producer<bool> storiesClickTooltipHiddenValue() const {
return _storiesClickTooltipHidden.value();
}
void setStoriesClickTooltipHidden(bool value) {
_storiesClickTooltipHidden = value;
}
[[nodiscard]] static bool ThirdColumnByDefault(); [[nodiscard]] static bool ThirdColumnByDefault();
[[nodiscard]] static float64 DefaultDialogsWidthRatio(); [[nodiscard]] static float64 DefaultDialogsWidthRatio();
@ -923,6 +932,7 @@ private:
WindowPosition _mediaViewPosition = { .maximized = 2 }; WindowPosition _mediaViewPosition = { .maximized = 2 };
rpl::variable<bool> _ignoreBatterySaving = false; rpl::variable<bool> _ignoreBatterySaving = false;
std::optional<uint64> _macRoundIconDigest; std::optional<uint64> _macRoundIconDigest;
rpl::variable<bool> _storiesClickTooltipHidden = false;
bool _tabbedReplacedWithInfo = false; // per-window bool _tabbedReplacedWithInfo = false; // per-window
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window

View file

@ -394,6 +394,18 @@ int Launcher::exec() {
return result; return result;
} }
bool Launcher::validateCustomWorkingDir() {
if (customWorkingDir()) {
if (_customWorkingDir == cWorkingDir()) {
_customWorkingDir = QString();
return false;
}
cForceWorkingDir(_customWorkingDir);
return true;
}
return false;
}
void Launcher::workingFolderReady() { void Launcher::workingFolderReady() {
srand((unsigned int)time(nullptr)); srand((unsigned int)time(nullptr));
@ -435,7 +447,7 @@ const QStringList &Launcher::arguments() const {
} }
bool Launcher::customWorkingDir() const { bool Launcher::customWorkingDir() const {
return _customWorkingDir; return !_customWorkingDir.isEmpty();
} }
void Launcher::prepareSettings() { void Launcher::prepareSettings() {
@ -534,9 +546,9 @@ void Launcher::processArguments() {
gStartInTray = parseResult.contains("-startintray"); gStartInTray = parseResult.contains("-startintray");
gQuit = parseResult.contains("-quit"); gQuit = parseResult.contains("-quit");
gSendPaths = parseResult.value("-sendpath", {}); gSendPaths = parseResult.value("-sendpath", {});
cForceWorkingDir(parseResult.value("-workdir", {}).join(QString())); _customWorkingDir = parseResult.value("-workdir", {}).join(QString());
if (!gWorkingDir.isEmpty()) { if (!_customWorkingDir.isEmpty()) {
_customWorkingDir = true; _customWorkingDir = QDir(_customWorkingDir).absolutePath() + '/';
} }
gStartUrl = parseResult.value("--", {}).join(QString()); gStartUrl = parseResult.value("--", {}).join(QString());

View file

@ -34,6 +34,7 @@ public:
uint64 installationTag() const; uint64 installationTag() const;
bool checkPortableVersionFolder(); bool checkPortableVersionFolder();
bool validateCustomWorkingDir();
void workingFolderReady(); void workingFolderReady();
void writeDebugModeSetting(); void writeDebugModeSetting();
void writeInstallBetaVersionsSetting(); void writeInstallBetaVersionsSetting();
@ -83,7 +84,7 @@ private:
QStringList _arguments; QStringList _arguments;
BaseIntegration _baseIntegration; BaseIntegration _baseIntegration;
bool _customWorkingDir = false; QString _customWorkingDir;
}; };

View file

@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs; constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs; constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs; constexpr auto AppFile = "AyuGram"_cs;
constexpr auto AppVersion = 4008007; constexpr auto AppVersion = 4008010;
constexpr auto AppVersionStr = "4.8.7"; constexpr auto AppVersionStr = "4.8.10";
constexpr auto AppBetaVersion = false; constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View file

@ -54,6 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace { namespace {
constexpr auto kDefaultCoverThumbnailSize = 100; constexpr auto kDefaultCoverThumbnailSize = 100;
constexpr auto kMaxAllowedPreloadPrefix = 6 * 1024 * 1024;
const auto kLottieStickerDimensions = QSize( const auto kLottieStickerDimensions = QSize(
kStickerSideSize, kStickerSideSize,
@ -393,7 +394,7 @@ void DocumentData::setattributes(
if (data.is_round_message()) { if (data.is_round_message()) {
_additional = std::make_unique<RoundData>(); _additional = std::make_unique<RoundData>();
} else if (const auto size = data.vpreload_prefix_size()) { } else if (const auto size = data.vpreload_prefix_size()) {
if (size->v > 0) { if (size->v > 0 && size->v < kMaxAllowedPreloadPrefix) {
_videoPreloadPrefix = size->v; _videoPreloadPrefix = size->v;
} }
} }

View file

@ -185,7 +185,8 @@ rpl::producer<SparseIdsMergedSlice> SharedScheduledMediaViewer(
SharedMediaMergedKey key, SharedMediaMergedKey key,
int limitBefore, int limitBefore,
int limitAfter) { int limitAfter) {
Expects(!IsServerMsgId(key.mergedKey.universalId)); Expects(!key.mergedKey.universalId
|| Data::IsScheduledMsgId(key.mergedKey.universalId));
Expects((key.mergedKey.universalId != 0) Expects((key.mergedKey.universalId != 0)
|| (limitBefore == 0 && limitAfter == 0)); || (limitBefore == 0 && limitAfter == 0));

View file

@ -217,7 +217,7 @@ public:
void registerPolling(not_null<Story*> story, Polling polling); void registerPolling(not_null<Story*> story, Polling polling);
void unregisterPolling(not_null<Story*> story, Polling polling); void unregisterPolling(not_null<Story*> story, Polling polling);
bool registerPolling(FullStoryId id, Polling polling); [[nodiscard]] bool registerPolling(FullStoryId id, Polling polling);
void unregisterPolling(FullStoryId id, Polling polling); void unregisterPolling(FullStoryId id, Polling polling);
void requestUserStories( void requestUserStories(
not_null<UserData*> user, not_null<UserData*> user,

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h" #include "main/main_session.h"
#include "media/streaming/media_streaming_reader.h" #include "media/streaming/media_streaming_reader.h"
#include "storage/download_manager_mtproto.h" #include "storage/download_manager_mtproto.h"
#include "storage/file_download.h" // kMaxFileInMemory
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
namespace Data { namespace Data {
@ -574,6 +575,7 @@ void StoryPreload::load() {
} }
_task = std::make_unique<LoadTask>(id(), video, [=](QByteArray data) { _task = std::make_unique<LoadTask>(id(), video, [=](QByteArray data) {
if (!data.isEmpty()) { if (!data.isEmpty()) {
Assert(data.size() < Storage::kMaxFileInMemory);
_story->owner().cacheBigFile().putIfEmpty( _story->owner().cacheBigFile().putIfEmpty(
key, key,
Storage::Cache::Database::TaggedValue(std::move(data), 0)); Storage::Cache::Database::TaggedValue(std::move(data), 0));

View file

@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lottie/lottie_frame_generator.h" #include "lottie/lottie_frame_generator.h"
#include "ffmpeg/ffmpeg_frame_generator.h" #include "ffmpeg/ffmpeg_frame_generator.h"
#include "chat_helpers/stickers_lottie.h" #include "chat_helpers/stickers_lottie.h"
#include "storage/file_download.h" // kMaxFileInMemory
#include "ui/widgets/input_fields.h" #include "ui/widgets/input_fields.h"
#include "ui/text/text_custom_emoji.h" #include "ui/text/text_custom_emoji.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
@ -345,7 +346,7 @@ void CustomEmojiLoader::check() {
}; };
auto put = [=, key = cacheKey(document)](QByteArray value) { auto put = [=, key = cacheKey(document)](QByteArray value) {
const auto size = value.size(); const auto size = value.size();
if (size <= Storage::Cache::Database::Settings().maxDataSize) { if (size <= Storage::kMaxFileInMemory) {
document->owner().cacheBigFile().put(key, std::move(value)); document->owner().cacheBigFile().put(key, std::move(value));
} else { } else {
LOG(("Data Error: Cached emoji size too big: %1.").arg(size)); LOG(("Data Error: Cached emoji size too big: %1.").arg(size));

View file

@ -562,3 +562,16 @@ dialogsStoriesListInfo: DialogsStoriesList(dialogsStoriesList) {
dialogsStoriesListMine: DialogsStoriesList(dialogsStoriesListInfo) { dialogsStoriesListMine: DialogsStoriesList(dialogsStoriesListInfo) {
readOpacity: 1.; readOpacity: 1.;
} }
dialogsStoriesTooltip: ImportantTooltip(defaultImportantTooltip) {
padding: margins(0px, 0px, 0px, 0px);
}
dialogsStoriesTooltipLabel: defaultImportantTooltipLabel;
dialogsStoriesTooltipMaxWidth: 200px;
dialogsStoriesTooltipHide: IconButton(defaultIconButton) {
width: 34px;
height: 20px;
iconPosition: point(-1px, -1px);
icon: icon {{ "calls/video_tooltip", importantTooltipFg }};
iconOver: icon {{ "calls/video_tooltip", importantTooltipFg }};
ripple: emptyRippleAnimation;
}

View file

@ -798,6 +798,22 @@ void Widget::setupStories() {
_scroll->viewportEvent(e); _scroll->viewportEvent(e);
}, _stories->lifetime()); }, _stories->lifetime());
if (!Core::App().settings().storiesClickTooltipHidden()) {
// Don't create tooltip
// until storiesClickTooltipHidden can be returned to false.
const auto hideTooltip = [=] {
Core::App().settings().setStoriesClickTooltipHidden(true);
Core::App().saveSettingsDelayed();
};
_stories->setShowTooltip(
parentWidget(),
rpl::combine(
Core::App().settings().storiesClickTooltipHiddenValue(),
shownValue(),
!rpl::mappers::_1 && rpl::mappers::_2),
hideTooltip);
}
_storiesContents.fire(Stories::ContentForSession( _storiesContents.fire(Stories::ContentForSession(
&controller()->session(), &controller()->session(),
Data::StorySourcesList::NotHidden)); Data::StorySourcesList::NotHidden));
@ -1370,6 +1386,15 @@ void Widget::jumpToTop(bool belowPinned) {
} }
} }
void Widget::raiseWithTooltip() {
raise();
if (_stories) {
Ui::PostponeCall(this, [=] {
_stories->raiseTooltip();
});
}
}
void Widget::scrollToDefault(bool verytop) { void Widget::scrollToDefault(bool verytop) {
if (verytop) { if (verytop) {
//_scroll->verticalScrollBar()->setMinimum(0); //_scroll->verticalScrollBar()->setMinimum(0);
@ -1447,6 +1472,7 @@ void Widget::stopWidthAnimation() {
} }
void Widget::updateStoriesVisibility() { void Widget::updateStoriesVisibility() {
updateLockUnlockVisibility();
if (!_stories) { if (!_stories) {
return; return;
} }
@ -1476,7 +1502,6 @@ void Widget::updateStoriesVisibility() {
if (_aboveScrollAdded > 0 && _updateScrollGeometryCached) { if (_aboveScrollAdded > 0 && _updateScrollGeometryCached) {
_updateScrollGeometryCached(); _updateScrollGeometryCached();
} }
updateLockUnlockVisibility();
updateLockUnlockPosition(); updateLockUnlockPosition();
} }
} }

View file

@ -98,6 +98,7 @@ public:
void setInnerFocus(); void setInnerFocus();
void jumpToTop(bool belowPinned = false); void jumpToTop(bool belowPinned = false);
void raiseWithTooltip();
void startWidthAnimation(); void startWidthAnimation();
void stopWidthAnimation(); void stopWidthAnimation();

View file

@ -7,14 +7,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "dialogs/ui/dialogs_stories_list.h" #include "dialogs/ui/dialogs_stories_list.h"
#include "base/event_filter.h"
#include "base/qt_signal_producer.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "ui/effects/outline_segments.h" #include "ui/effects/outline_segments.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "ui/widgets/tooltip.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "styles/style_dialogs.h" #include "styles/style_dialogs.h"
#include <QtWidgets/QApplication> #include <QtWidgets/QApplication>
#include <QtGui/QWindow>
#include <QtGui/QPainter>
#include "base/debug_log.h" #include "base/debug_log.h"
@ -27,6 +35,8 @@ constexpr auto kExpandAfterRatio = 0.72;
constexpr auto kCollapseAfterRatio = 0.68; constexpr auto kCollapseAfterRatio = 0.68;
constexpr auto kFrictionRatio = 0.15; constexpr auto kFrictionRatio = 0.15;
constexpr auto kExpandCatchUpDuration = crl::time(200); constexpr auto kExpandCatchUpDuration = crl::time(200);
constexpr auto kMaxTooltipNames = 3;
constexpr auto kStoriesTooltipHideBgOpacity = 0.2;
[[nodiscard]] int AvailableNameWidth(const style::DialogsStoriesList &st) { [[nodiscard]] int AvailableNameWidth(const style::DialogsStoriesList &st) {
const auto &full = st.full; const auto &full = st.full;
@ -35,6 +45,33 @@ constexpr auto kExpandCatchUpDuration = crl::time(200);
return full.photoLeft * 2 + full.photo - 2 * skip; return full.photoLeft * 2 + full.photo - 2 * skip;
} }
[[nodiscard]] object_ptr<Ui::RpWidget> MakeTooltipContent(
not_null<QWidget*> parent,
rpl::producer<TextWithEntities> text,
Fn<void()> hide) {
const auto size = st::dialogsStoriesTooltipHide.width;
const auto skip = st::defaultImportantTooltip.padding.right();
auto result = object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
parent,
Ui::MakeNiceTooltipLabel(
parent,
std::move(text),
st::dialogsStoriesTooltipMaxWidth,
st::dialogsStoriesTooltipLabel),
(st::defaultImportantTooltip.padding
+ QMargins(0, 0, skip + size, 0)));
const auto button = Ui::CreateChild<Ui::IconButton>(
result.data(),
st::dialogsStoriesTooltipHide);
result->sizeValue(
) | rpl::start_with_next([=](QSize size) {
button->resize(button->width(), size.height());
button->moveToRight(0, 0, size.width());
}, button->lifetime());
button->setClickedCallback(std::move(hide));
return result;
}
} // namespace } // namespace
struct List::Layout { struct List::Layout {
@ -112,6 +149,7 @@ void List::showContent(Content &&content) {
_data.items.emplace_back(Item{ .element = element }); _data.items.emplace_back(Item{ .element = element });
} }
} }
_lastCollapsedGeometry = {};
if (int(_data.items.size()) != wasCount) { if (int(_data.items.size()) != wasCount) {
updateGeometry(); updateGeometry();
} }
@ -120,6 +158,8 @@ void List::showContent(Content &&content) {
if (!wasCount) { if (!wasCount) {
_empty = false; _empty = false;
} }
_tooltipText = computeTooltipText();
updateTooltipGeometry();
} }
void List::updateScrollMax() { void List::updateScrollMax() {
@ -162,10 +202,16 @@ void List::requestExpanded(bool expanded) {
const auto from = _expanded ? 0. : 1.; const auto from = _expanded ? 0. : 1.;
const auto till = _expanded ? 2. : 0.; const auto till = _expanded ? 2. : 0.;
const auto duration = (_expanded ? 2 : 1) * st::slideWrapDuration; const auto duration = (_expanded ? 2 : 1) * st::slideWrapDuration;
if (!isHidden() && _expanded) {
toggleTooltip(false);
}
_expandedAnimation.start([=] { _expandedAnimation.start([=] {
checkForFullState(); checkForFullState();
update(); update();
_collapsedGeometryChanged.fire({}); _collapsedGeometryChanged.fire({});
if (!isHidden() && !_expandedAnimation.animating()) {
toggleTooltip(false);
}
}, from, till, duration, anim::sineInOut); }, from, till, duration, anim::sineInOut);
} }
_toggleExpandedRequests.fire_copy(_expanded); _toggleExpandedRequests.fire_copy(_expanded);
@ -179,6 +225,12 @@ void List::resizeEvent(QResizeEvent *e) {
updateScrollMax(); updateScrollMax();
} }
void List::updateExpanding() {
updateExpanding(
_lastExpandedHeight * _expandCatchUpAnimation.value(1.),
_st.full.height);
}
void List::updateExpanding(int expandingHeight, int expandedHeight) { void List::updateExpanding(int expandingHeight, int expandedHeight) {
Expects(!expandingHeight || expandedHeight > 0); Expects(!expandingHeight || expandedHeight > 0);
@ -196,12 +248,10 @@ void List::updateExpanding(int expandingHeight, int expandedHeight) {
if (change) { if (change) {
requestExpanded(!_expanded); requestExpanded(!_expanded);
} }
updateTooltipGeometry();
} }
List::Layout List::computeLayout() { List::Layout List::computeLayout() {
updateExpanding(
_lastExpandedHeight * _expandCatchUpAnimation.value(1.),
_st.full.height);
return computeLayout(_expandedAnimation.value(_expanded ? 2. : 0.)); return computeLayout(_expandedAnimation.value(_expanded ? 2. : 0.));
} }
@ -332,11 +382,7 @@ void List::paint(
bool layered) { bool layered) {
const auto &st = _st.small; const auto &st = _st.small;
const auto &full = _st.full; const auto &full = _st.full;
const auto ratio = layout.ratio;
const auto expandRatio = layout.expandRatio; const auto expandRatio = layout.expandRatio;
const auto lerp = [&](float64 a, float64 b) {
return a + (b - a) * ratio;
};
const auto elerp = [&](float64 a, float64 b) { const auto elerp = [&](float64 a, float64 b) {
return a + (b - a) * expandRatio; return a + (b - a) * expandRatio;
}; };
@ -682,6 +728,9 @@ void List::mousePressEvent(QMouseEvent *e) {
return; return;
} else if (_state == State::Small) { } else if (_state == State::Small) {
requestExpanded(true); requestExpanded(true);
if (const auto onstack = _tooltipHide) {
onstack();
}
return; return;
} else if (_state != State::Full) { } else if (_state != State::Full) {
return; return;
@ -761,6 +810,7 @@ void List::setExpandedHeight(int height, bool momentum) {
} else if (!momentum && _expandIgnored && height > 0) { } else if (!momentum && _expandIgnored && height > 0) {
_expandIgnored = false; _expandIgnored = false;
_expandCatchUpAnimation.start([=] { _expandCatchUpAnimation.start([=] {
updateExpanding();
update(); update();
checkForFullState(); checkForFullState();
}, 0., 1., kExpandCatchUpDuration); }, 0., 1., kExpandCatchUpDuration);
@ -768,6 +818,7 @@ void List::setExpandedHeight(int height, bool momentum) {
_expandCatchUpAnimation.stop(); _expandCatchUpAnimation.stop();
} }
_lastExpandedHeight = height; _lastExpandedHeight = height;
updateExpanding();
if (!checkForFullState()) { if (!checkForFullState()) {
setState(!height ? State::Small : State::Changing); setState(!height ? State::Small : State::Changing);
} }
@ -788,17 +839,189 @@ void List::setLayoutConstraints(
QPoint positionSmall, QPoint positionSmall,
style::align alignSmall, style::align alignSmall,
QRect geometryFull) { QRect geometryFull) {
if (_positionSmall == positionSmall
&& _alignSmall == alignSmall
&& _geometryFull == geometryFull) {
return;
}
_positionSmall = positionSmall; _positionSmall = positionSmall;
_alignSmall = alignSmall; _alignSmall = alignSmall;
_geometryFull = geometryFull; _geometryFull = geometryFull;
_lastCollapsedGeometry = {};
updateGeometry(); updateGeometry();
update(); update();
} }
TextWithEntities List::computeTooltipText() const {
const auto &list = _data.items;
if (list.empty()) {
return {};
} else if (list.size() == 1 && list.front().element.skipSmall) {
return { tr::lng_stories_click_to_view_mine(tr::now) };
}
auto names = QStringList();
for (const auto &item : list) {
if (item.element.skipSmall) {
continue;
}
names.append(item.element.name);
if (names.size() >= kMaxTooltipNames) {
break;
}
}
auto sequence = Ui::Text::Bold(names.front());
if (names.size() > 1) {
for (auto i = 1; i + 1 != names.size(); ++i) {
sequence = tr::lng_stories_click_to_view_and_one(
tr::now,
lt_accumulated,
sequence,
lt_user,
Ui::Text::Bold(names[i]),
Ui::Text::WithEntities);
}
sequence = tr::lng_stories_click_to_view_and_last(
tr::now,
lt_accumulated,
sequence,
lt_user,
Ui::Text::Bold(names.back()),
Ui::Text::WithEntities);
}
return tr::lng_stories_click_to_view(
tr::now,
lt_users,
sequence,
Ui::Text::WithEntities);
}
void List::setShowTooltip(
not_null<QWidget*> tooltipParent,
rpl::producer<bool> shown,
Fn<void()> hide) {
_tooltip = nullptr;
_tooltipHide = std::move(hide);
_tooltipNotHidden = std::move(shown);
_tooltipText = computeTooltipText();
const auto notEmpty = [](const TextWithEntities &text) {
return !text.empty();
};
_tooltip = std::make_unique<Ui::ImportantTooltip>(
tooltipParent,
MakeTooltipContent(
tooltipParent,
_tooltipText.value() | rpl::filter(notEmpty),
_tooltipHide),
st::dialogsStoriesTooltip);
const auto tooltip = _tooltip.get();
const auto weak = QPointer<QWidget>(tooltip);
tooltip->toggleFast(false);
updateTooltipGeometry();
const auto handle = tooltipParent->window()->windowHandle();
auto windowActive = rpl::single(
handle->isActive()
) | rpl::then(base::qt_signal_producer(
handle,
&QWindow::activeChanged
) | rpl::map([=] {
return handle->isActive();
})) | rpl::distinct_until_changed();
{
const auto recompute = [=] {
updateTooltipGeometry();
tooltip->raise();
};
using namespace base;
using Event = not_null<QEvent*>;
install_event_filter(tooltip, tooltipParent, [=](Event e) {
if (e->type() == QEvent::ChildAdded) {
recompute();
}
return EventFilterResult::Continue;
});
}
rpl::combine(
_tooltipNotHidden.value(),
_tooltipText.value() | rpl::map(
notEmpty
) | rpl::distinct_until_changed(),
std::move(windowActive)
) | rpl::start_with_next([=](bool, bool, bool active) {
_tooltipWindowActive = active;
if (!isHidden()) {
toggleTooltip(false);
}
}, tooltip->lifetime());
shownValue(
) | rpl::skip(1) | rpl::start_with_next([=](bool shown) {
toggleTooltip(true);
}, tooltip->lifetime());
}
void List::raiseTooltip() {
if (_tooltip) {
_tooltip->raise();
}
}
void List::toggleTooltip(bool fast) {
const auto shown = !_expanded
&& !_expandedAnimation.animating()
&& !isHidden()
&& _tooltipNotHidden.current()
&& !_tooltipText.current().empty()
&& window()->windowHandle()->isActive();
if (_tooltip) {
if (fast) {
_tooltip->toggleFast(shown);
} else {
_tooltip->toggleAnimated(shown);
}
}
if (shown) {
updateTooltipGeometry();
}
}
void List::updateTooltipGeometry() {
if (!_tooltip || _expanded || _expandedAnimation.animating()) {
return;
}
const auto collapsed = collapsedGeometryCurrent();
const auto geometry = Ui::MapFrom(
_tooltip->parentWidget(),
parentWidget(),
QRect(
collapsed.geometry.x(),
collapsed.geometry.y(),
int(std::ceil(collapsed.singleWidth)),
collapsed.geometry.height()));
const auto weak = QPointer<QWidget>(_tooltip.get());
const auto countPosition = [=](QSize size) {
const auto left = geometry.x()
+ (geometry.width() - size.width()) / 2;
const auto right = _tooltip->parentWidget()->width()
- st::dialogsStoriesTooltip.padding.right();
return QPoint(
std::max(std::min(left, right - size.width()), 0),
geometry.y() + geometry.height());
};
_tooltip->pointAt(geometry, RectPart::Bottom, countPosition);
}
List::CollapsedGeometry List::collapsedGeometryCurrent() const { List::CollapsedGeometry List::collapsedGeometryCurrent() const {
const auto expanded = _expandedAnimation.value(_expanded ? 2. : 0.); const auto expanded = _expandedAnimation.value(_expanded ? 2. : 0.);
if (expanded >= 1.) { if (expanded >= 1.) {
return { QRect(), 1. }; const auto single = 2 * _st.full.photoLeft + _st.full.photo;
return { QRect(), 1., float64(single) };
} else if (_lastCollapsedRatio == _lastRatio
&& _lastCollapsedGeometry.expanded == expanded
&& !_lastCollapsedGeometry.geometry.isEmpty()) {
return _lastCollapsedGeometry;
} }
const auto layout = computeLayout(0.); const auto layout = computeLayout(0.);
const auto small = countSmallGeometry(); const auto small = countSmallGeometry();
@ -807,10 +1030,21 @@ List::CollapsedGeometry List::collapsedGeometryCurrent() const {
const auto left = int(base::SafeRound( const auto left = int(base::SafeRound(
shift + layout.left + layout.single * index)); shift + layout.left + layout.single * index));
const auto width = small.x() + small.width() - left; const auto width = small.x() + small.width() - left;
return { const auto photoTopSmall = _st.small.photoTop;
QRect(left, small.y(), width, small.height()), const auto photoTop = photoTopSmall
+ (_st.full.photoTop - photoTopSmall) * layout.expandedRatio;
const auto ySmall = photoTopSmall
+ ((photoTop - photoTopSmall) * kSmallThumbsShown / 0.5);
const auto photo = _st.small.photo
+ (_st.full.photo - _st.small.photo) * layout.ratio;
const auto top = y() + layout.geometryShift.y();
_lastCollapsedRatio = _lastRatio;
_lastCollapsedGeometry = {
QRect(left, top, width, ySmall + photo + _st.full.photoTop),
expanded, expanded,
layout.photoLeft * 2 + photo,
}; };
return _lastCollapsedGeometry;
} }
rpl::producer<> List::collapsedGeometryChanged() const { rpl::producer<> List::collapsedGeometryChanged() const {
@ -826,6 +1060,7 @@ void List::updateGeometry() {
} break; } break;
case State::Full: setGeometry(_geometryFull); case State::Full: setGeometry(_geometryFull);
} }
updateTooltipGeometry();
update(); update();
} }

View file

@ -23,6 +23,7 @@ struct DialogsStoriesList;
namespace Ui { namespace Ui {
class PopupMenu; class PopupMenu;
struct OutlineSegment; struct OutlineSegment;
class ImportantTooltip;
} // namespace Ui } // namespace Ui
namespace Dialogs::Stories { namespace Dialogs::Stories {
@ -72,9 +73,16 @@ public:
QPoint positionSmall, QPoint positionSmall,
style::align alignSmall, style::align alignSmall,
QRect geometryFull = QRect()); QRect geometryFull = QRect());
void setShowTooltip(
not_null<QWidget*> tooltipParent,
rpl::producer<bool> shown,
Fn<void()> hide);
void raiseTooltip();
struct CollapsedGeometry { struct CollapsedGeometry {
QRect geometry; QRect geometry;
float64 expanded = 0.; float64 expanded = 0.;
float64 singleWidth = 0.;
}; };
[[nodiscard]] CollapsedGeometry collapsedGeometryCurrent() const; [[nodiscard]] CollapsedGeometry collapsedGeometryCurrent() const;
[[nodiscard]] rpl::producer<> collapsedGeometryChanged() const; [[nodiscard]] rpl::producer<> collapsedGeometryChanged() const;
@ -142,10 +150,15 @@ private:
void checkLoadMore(); void checkLoadMore();
void requestExpanded(bool expanded); void requestExpanded(bool expanded);
void updateTooltipGeometry();
[[nodiscard]] TextWithEntities computeTooltipText() const;
void toggleTooltip(bool fast);
bool checkForFullState(); bool checkForFullState();
void setState(State state); void setState(State state);
void updateGeometry(); void updateGeometry();
[[nodiscard]] QRect countSmallGeometry() const; [[nodiscard]] QRect countSmallGeometry() const;
void updateExpanding();
void updateExpanding(int expandingHeight, int expandedHeight); void updateExpanding(int expandingHeight, int expandedHeight);
void validateSegments( void validateSegments(
not_null<Item*> item, not_null<Item*> item,
@ -189,11 +202,20 @@ private:
bool _expandIgnored : 1 = false; bool _expandIgnored : 1 = false;
bool _expanded : 1 = false; bool _expanded : 1 = false;
mutable CollapsedGeometry _lastCollapsedGeometry;
mutable float64 _lastCollapsedRatio = 0.;
int _selected = -1; int _selected = -1;
int _pressed = -1; int _pressed = -1;
rpl::event_stream<not_null<QWheelEvent*>> _verticalScrollEvents; rpl::event_stream<not_null<QWheelEvent*>> _verticalScrollEvents;
rpl::variable<TextWithEntities> _tooltipText;
rpl::variable<bool> _tooltipNotHidden;
Fn<void()> _tooltipHide;
std::unique_ptr<Ui::ImportantTooltip> _tooltip;
bool _tooltipWindowActive = false;
base::unique_qptr<Ui::PopupMenu> _menu; base::unique_qptr<Ui::PopupMenu> _menu;
base::has_weak_ptr _menuGuard; base::has_weak_ptr _menuGuard;

View file

@ -1055,6 +1055,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
_translateTracker->startBunch(); _translateTracker->startBunch();
auto readTill = (HistoryItem*)nullptr; auto readTill = (HistoryItem*)nullptr;
auto readContents = base::flat_set<not_null<HistoryItem*>>(); auto readContents = base::flat_set<not_null<HistoryItem*>>();
const auto markingAsViewed = _widget->markingContentsRead();
const auto guard = gsl::finally([&] { const auto guard = gsl::finally([&] {
if (_pinnedItem) { if (_pinnedItem) {
_translateTracker->add(_pinnedItem); _translateTracker->add(_pinnedItem);
@ -1063,7 +1064,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
if (readTill && _widget->markingMessagesRead()) { if (readTill && _widget->markingMessagesRead()) {
session().data().histories().readInboxTill(readTill); session().data().histories().readInboxTill(readTill);
} }
if (!readContents.empty() && _widget->markingContentsRead()) { if (markingAsViewed && !readContents.empty()) {
session().api().markContentsRead(readContents); session().api().markContentsRead(readContents);
} }
_userpicsCache.clear(); _userpicsCache.clear();
@ -1096,7 +1097,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
} else if (isUnread) { } else if (isUnread) {
readTill = item; readTill = item;
} }
if (item->hasViews()) { if (markingAsViewed && item->hasViews()) {
session().api().views().scheduleIncrement(item); session().api().views().scheduleIncrement(item);
} }
if (withReaction) { if (withReaction) {

View file

@ -2851,14 +2851,35 @@ void HistoryWidget::updateControlsVisibility() {
if (_botMenuButton) { if (_botMenuButton) {
_botMenuButton->show(); _botMenuButton->show();
} }
if (_silent) { {
_silent->setVisible(!_editMsgId); auto rightButtonsChanged = false;
} if (_silent) {
if (_scheduled) { const auto was = _silent->isVisible();
_scheduled->show(); const auto now = (!_editMsgId);
} if (was != now) {
if (_ttlInfo) { _silent->setVisible(now);
_ttlInfo->show(); rightButtonsChanged = true;
}
}
if (_scheduled) {
const auto was = _scheduled->isVisible();
const auto now = (!_editMsgId);
if (was != now) {
_scheduled->setVisible(now);
rightButtonsChanged = true;
}
}
if (_ttlInfo) {
const auto was = _ttlInfo->isVisible();
const auto now = (!_editMsgId);
if (was != now) {
_ttlInfo->setVisible(now);
rightButtonsChanged = true;
}
}
if (rightButtonsChanged) {
updateFieldSize();
}
} }
if (_sendAs) { if (_sendAs) {
_sendAs->show(); _sendAs->show();
@ -5028,19 +5049,33 @@ void HistoryWidget::moveFieldControls() {
} }
void HistoryWidget::updateFieldSize() { void HistoryWidget::updateFieldSize() {
auto kbShowShown = _history && !_kbShown && _keyboard->hasMarkup(); const auto kbShowShown = _history && !_kbShown && _keyboard->hasMarkup();
auto fieldWidth = width() auto fieldWidth = width()
- _attachToggle->width() - _attachToggle->width()
- st::historySendRight - st::historySendRight
- _send->width() - _send->width()
- _tabbedSelectorToggle->width(); - _tabbedSelectorToggle->width();
if (_botMenuButton) fieldWidth -= st::historyBotMenuSkip + _botMenuButton->width(); if (_botMenuButton) {
if (_sendAs) fieldWidth -= _sendAs->width(); fieldWidth -= st::historyBotMenuSkip + _botMenuButton->width();
if (kbShowShown) fieldWidth -= _botKeyboardShow->width(); }
if (_cmdStartShown) fieldWidth -= _botCommandStart->width(); if (_sendAs) {
if (_silent && !_silent->isHidden()) fieldWidth -= _silent->width(); fieldWidth -= _sendAs->width();
if (_scheduled) fieldWidth -= _scheduled->width(); }
if (_ttlInfo) fieldWidth -= _ttlInfo->width(); if (kbShowShown) {
fieldWidth -= _botKeyboardShow->width();
}
if (_cmdStartShown) {
fieldWidth -= _botCommandStart->width();
}
if (_silent && _silent->isVisible()) {
fieldWidth -= _silent->width();
}
if (_scheduled && _scheduled->isVisible()) {
fieldWidth -= _scheduled->width();
}
if (_ttlInfo && _ttlInfo->isVisible()) {
fieldWidth -= _ttlInfo->width();
}
if (_fieldDisabled) { if (_fieldDisabled) {
_fieldDisabled->resize(fieldWidth, fieldHeight()); _fieldDisabled->resize(fieldWidth, fieldHeight());
@ -6547,6 +6582,8 @@ void HistoryWidget::checkPinnedBarState() {
_list->setShownPinned( _list->setShownPinned(
session().data().message( session().data().message(
_pinnedTracker->currentMessageId().message)); _pinnedTracker->currentMessageId().message));
} else {
_list->setShownPinned(nullptr);
} }
return std::move(content); return std::move(content);
})); }));

View file

@ -347,7 +347,7 @@ public:
void setHistory(const SetHistoryArgs &args); void setHistory(const SetHistoryArgs &args);
void init(); void init();
void editMessage(FullMsgId id); void editMessage(FullMsgId id, bool photoEditAllowed = false);
void replyToMessage(FullMsgId id); void replyToMessage(FullMsgId id);
void updateForwarding( void updateForwarding(
Data::Thread *thread, Data::Thread *thread,
@ -363,8 +363,10 @@ public:
[[nodiscard]] bool readyToForward() const; [[nodiscard]] bool readyToForward() const;
[[nodiscard]] const HistoryItemsList &forwardItems() const; [[nodiscard]] const HistoryItemsList &forwardItems() const;
[[nodiscard]] FullMsgId replyingToMessage() const; [[nodiscard]] FullMsgId replyingToMessage() const;
[[nodiscard]] rpl::producer<FullMsgId> editMsgId() const; [[nodiscard]] FullMsgId editMsgId() const;
[[nodiscard]] rpl::producer<FullMsgId> editMsgIdValue() const;
[[nodiscard]] rpl::producer<FullMsgId> scrollToItemRequests() const; [[nodiscard]] rpl::producer<FullMsgId> scrollToItemRequests() const;
[[nodiscard]] rpl::producer<> editPhotoRequests() const;
[[nodiscard]] MessageToEdit queryToEdit(); [[nodiscard]] MessageToEdit queryToEdit();
[[nodiscard]] WebPageId webPageId() const; [[nodiscard]] WebPageId webPageId() const;
@ -425,16 +427,24 @@ private:
HistoryItem *_shownMessage = nullptr; HistoryItem *_shownMessage = nullptr;
Ui::Text::String _shownMessageName; Ui::Text::String _shownMessageName;
Ui::Text::String _shownMessageText; Ui::Text::String _shownMessageText;
std::unique_ptr<Ui::SpoilerAnimation> _shownPreviewSpoiler;
Ui::Animations::Simple _inPhotoEditOver;
int _shownMessageNameVersion = -1; int _shownMessageNameVersion = -1;
bool _repaintScheduled = false; bool _shownMessageHasPreview : 1 = false;
bool _inPhotoEdit : 1 = false;
bool _photoEditAllowed : 1 = false;
bool _repaintScheduled : 1 = false;
bool _inClickable : 1 = false;
const not_null<Data::Session*> _data; const not_null<Data::Session*> _data;
const not_null<Ui::IconButton*> _cancel; const not_null<Ui::IconButton*> _cancel;
QRect _clickableRect; QRect _clickableRect;
QRect _shownMessagePreviewRect;
rpl::event_stream<bool> _visibleChanged; rpl::event_stream<bool> _visibleChanged;
rpl::event_stream<FullMsgId> _scrollToItemRequests; rpl::event_stream<FullMsgId> _scrollToItemRequests;
rpl::event_stream<> _editPhotoRequests;
}; };
@ -554,26 +564,45 @@ void FieldHeader::init() {
}, lifetime()); }, lifetime());
setMouseTracking(true); setMouseTracking(true);
const auto inClickable = lifetime().make_state<bool>(false);
events( events(
) | rpl::filter([=](not_null<QEvent*> event) { ) | rpl::filter([=](not_null<QEvent*> event) {
return ranges::contains(kMouseEvents, event->type()) const auto type = event->type();
const auto leaving = (type == QEvent::Leave);
return (ranges::contains(kMouseEvents, type) || leaving)
&& (isEditingMessage() && (isEditingMessage()
|| readyToForward() || readyToForward()
|| replyingToMessage()); || replyingToMessage());
}) | rpl::start_with_next([=](not_null<QEvent*> event) { }) | rpl::start_with_next([=](not_null<QEvent*> event) {
const auto type = event->type(); const auto updateOver = [&](bool inClickable, bool inPhotoEdit) {
const auto e = static_cast<QMouseEvent*>(event.get()); if (_inClickable != inClickable) {
const auto pos = e ? e->pos() : mapFromGlobal(QCursor::pos()); _inClickable = inClickable;
const auto inPreviewRect = _clickableRect.contains(pos); setCursor(_inClickable
if (type == QEvent::MouseMove) {
if (inPreviewRect != *inClickable) {
*inClickable = inPreviewRect;
setCursor(*inClickable
? style::cur_pointer ? style::cur_pointer
: style::cur_default); : style::cur_default);
} }
if (_inPhotoEdit != inPhotoEdit) {
_inPhotoEdit = inPhotoEdit;
_inPhotoEditOver.start(
[=] { update(); },
_inPhotoEdit ? 0. : 1.,
_inPhotoEdit ? 1. : 0.,
st::defaultMessageBar.duration);
}
};
const auto type = event->type();
if (type == QEvent::Leave) {
updateOver(false, false);
return;
}
const auto e = static_cast<QMouseEvent*>(event.get());
const auto pos = e ? e->pos() : mapFromGlobal(QCursor::pos());
const auto inPreviewRect = _clickableRect.contains(pos);
const auto inPhotoEdit = _shownMessageHasPreview
&& _photoEditAllowed
&& _shownMessagePreviewRect.contains(pos);
if (type == QEvent::MouseMove) {
updateOver(inPreviewRect, inPhotoEdit);
return; return;
} }
const auto isLeftIcon = (pos.x() < st::historyReplySkip); const auto isLeftIcon = (pos.x() < st::historyReplySkip);
@ -582,6 +611,8 @@ void FieldHeader::init() {
if (isLeftButton && isLeftIcon) { if (isLeftButton && isLeftIcon) {
*leftIconPressed = true; *leftIconPressed = true;
update(); update();
} else if (isLeftButton && inPhotoEdit) {
_editPhotoRequests.fire({});
} else if (isLeftButton && inPreviewRect) { } else if (isLeftButton && inPreviewRect) {
if (!isEditingMessage() && readyToForward()) { if (!isEditingMessage() && readyToForward()) {
_forwardPanel->editOptions(_show); _forwardPanel->editOptions(_show);
@ -794,20 +825,74 @@ void FieldHeader::paintEditOrReplyToMessage(Painter &p) {
} }
} }
const auto media = _shownMessage->media();
_shownMessageHasPreview = media && media->hasReplyPreview();
const auto preview = _shownMessageHasPreview
? media->replyPreview()
: nullptr;
const auto spoilered = preview && media->hasSpoiler();
if (!spoilered) {
_shownPreviewSpoiler = nullptr;
} else if (!_shownPreviewSpoiler) {
_shownPreviewSpoiler = std::make_unique<Ui::SpoilerAnimation>([=] {
update();
});
}
const auto previewSkipValue = st::msgReplyBarSize.height()
+ st::msgReplyBarSkip
- st::msgReplyBarSize.width()
- st::msgReplyBarPos.x();
const auto previewSkip = _shownMessageHasPreview ? previewSkipValue : 0;
const auto textLeft = replySkip + previewSkip;
const auto textAvailableWidth = availableWidth - previewSkip;
if (preview) {
const auto overEdit = _photoEditAllowed
? _inPhotoEditOver.value(_inPhotoEdit ? 1. : 0.)
: 0.;
const auto to = QRect(
replySkip,
st::msgReplyPadding.top(),
st::msgReplyBarSize.height(),
st::msgReplyBarSize.height());
p.drawPixmap(to.x(), to.y(), preview->pixSingle(
preview->size() / style::DevicePixelRatio(),
{
.options = Images::Option::RoundSmall,
.outer = to.size(),
}));
if (_shownPreviewSpoiler) {
if (overEdit > 0.) {
p.setOpacity(1. - overEdit);
}
Ui::FillSpoilerRect(
p,
to,
Ui::DefaultImageSpoiler().frame(
_shownPreviewSpoiler->index(crl::now(), p.inactive())));
}
if (overEdit > 0.) {
p.setOpacity(overEdit);
p.fillRect(to, st::historyEditMediaBg);
st::historyEditMedia.paintInCenter(p, to);
p.setOpacity(1.);
}
}
p.setPen(st::historyReplyNameFg); p.setPen(st::historyReplyNameFg);
p.setFont(st::msgServiceNameFont); p.setFont(st::msgServiceNameFont);
_shownMessageName.drawElided( _shownMessageName.drawElided(
p, p,
replySkip, textLeft,
st::msgReplyPadding.top(), st::msgReplyPadding.top(),
availableWidth); textAvailableWidth);
p.setPen(st::historyComposeAreaFg); p.setPen(st::historyComposeAreaFg);
_shownMessageText.draw(p, { _shownMessageText.draw(p, {
.position = QPoint( .position = QPoint(
replySkip, textLeft,
st::msgReplyPadding.top() + st::msgServiceNameFont->height), st::msgReplyPadding.top() + st::msgServiceNameFont->height),
.availableWidth = availableWidth, .availableWidth = textAvailableWidth,
.palette = &st::historyComposeAreaPalette, .palette = &st::historyComposeAreaPalette,
.spoiler = Ui::Text::DefaultSpoilerCache(), .spoiler = Ui::Text::DefaultSpoilerCache(),
.now = crl::now(), .now = crl::now(),
@ -848,6 +933,10 @@ bool FieldHeader::isEditingMessage() const {
return !!_editMsgId.current(); return !!_editMsgId.current();
} }
FullMsgId FieldHeader::editMsgId() const {
return _editMsgId.current();
}
bool FieldHeader::readyToForward() const { bool FieldHeader::readyToForward() const {
return !_forwardPanel->empty(); return !_forwardPanel->empty();
} }
@ -879,10 +968,21 @@ void FieldHeader::updateControlsGeometry(QSize size) {
0, 0,
width() - st::historyReplySkip - _cancel->width(), width() - st::historyReplySkip - _cancel->width(),
height()); height());
_shownMessagePreviewRect = QRect(
st::historyReplySkip,
st::msgReplyPadding.top(),
st::msgReplyBarSize.height(),
st::msgReplyBarSize.height());
} }
void FieldHeader::editMessage(FullMsgId id) { void FieldHeader::editMessage(FullMsgId id, bool photoEditAllowed) {
_photoEditAllowed = photoEditAllowed;
_editMsgId = id; _editMsgId = id;
if (!photoEditAllowed) {
_inPhotoEdit = false;
_inPhotoEditOver.stop();
}
update();
} }
void FieldHeader::replyToMessage(FullMsgId id) { void FieldHeader::replyToMessage(FullMsgId id) {
@ -898,7 +998,7 @@ void FieldHeader::updateForwarding(
} }
} }
rpl::producer<FullMsgId> FieldHeader::editMsgId() const { rpl::producer<FullMsgId> FieldHeader::editMsgIdValue() const {
return _editMsgId.value(); return _editMsgId.value();
} }
@ -906,6 +1006,10 @@ rpl::producer<FullMsgId> FieldHeader::scrollToItemRequests() const {
return _scrollToItemRequests.events(); return _scrollToItemRequests.events();
} }
rpl::producer<> FieldHeader::editPhotoRequests() const {
return _editPhotoRequests.events();
}
MessageToEdit FieldHeader::queryToEdit() { MessageToEdit FieldHeader::queryToEdit() {
const auto item = _data->message(_editMsgId.current()); const auto item = _data->message(_editMsgId.current());
if (!isEditingMessage() || !item) { if (!isEditingMessage() || !item) {
@ -1470,7 +1574,7 @@ void ComposeControls::init() {
paintBackground(clip); paintBackground(clip);
}, _wrap->lifetime()); }, _wrap->lifetime());
_header->editMsgId( _header->editMsgIdValue(
) | rpl::start_with_next([=](const auto &id) { ) | rpl::start_with_next([=](const auto &id) {
unregisterDraftSources(); unregisterDraftSources();
updateSendButtonType(); updateSendButtonType();
@ -1482,6 +1586,16 @@ void ComposeControls::init() {
registerDraftSource(); registerDraftSource();
}, _wrap->lifetime()); }, _wrap->lifetime());
_header->editPhotoRequests(
) | rpl::start_with_next([=] {
EditCaptionBox::StartPhotoEdit(
_regularWindow,
_photoEditMedia,
_editingId,
_field->getTextWithTags(),
crl::guard(_wrap.get(), [=] { cancelEditMessage(); }));
}, _wrap->lifetime());
_header->previewCancelled( _header->previewCancelled(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
if (_preview) { if (_preview) {
@ -1521,7 +1635,7 @@ void ComposeControls::init() {
_voiceRecordBar->requestToSendWithOptions(options); _voiceRecordBar->requestToSendWithOptions(options);
}, _wrap->lifetime()); }, _wrap->lifetime());
_header->editMsgId( _header->editMsgIdValue(
) | rpl::start_with_next([=](const auto &id) { ) | rpl::start_with_next([=](const auto &id) {
_editingId = id; _editingId = id;
}, _wrap->lifetime()); }, _wrap->lifetime());
@ -1663,6 +1777,8 @@ void ComposeControls::initField() {
} }
return false; return false;
}); });
_field->setEditLinkCallback(
DefaultEditLinkCallback(_show, _field, &_st.boxField));
initAutocomplete(); initAutocomplete();
const auto allow = [=](const auto &) { const auto allow = [=](const auto &) {
return _history && Data::AllowEmojiWithoutPremium(_history->peer); return _history && Data::AllowEmojiWithoutPremium(_history->peer);
@ -2049,7 +2165,43 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) {
} }
if (draft == editDraft) { if (draft == editDraft) {
_header->editMessage(editingId); const auto resolve = [=] {
if (const auto item = _history->owner().message(editingId)) {
const auto media = item->media();
_canReplaceMedia = media && media->allowsEditMedia();
_photoEditMedia = (_canReplaceMedia
&& _regularWindow
&& media->photo()
&& !media->photo()->isNull())
? media->photo()->createMediaView()
: nullptr;
if (_photoEditMedia) {
_photoEditMedia->wanted(
Data::PhotoSize::Large,
item->fullId());
}
_header->editMessage(editingId, _photoEditMedia != nullptr);
return true;
}
_canReplaceMedia = false;
_photoEditMedia = nullptr;
_header->editMessage(editingId, false);
return false;
};
if (!resolve()) {
const auto callback = crl::guard(_header.get(), [=] {
if (_header->editMsgId() == editingId
&& resolve()
&& updateReplaceMediaButton()) {
updateControlsVisibility();
updateControlsGeometry(_wrap->size());
}
});
_history->session().api().requestMessageData(
_history->peer,
editingId.msg,
callback);
}
_header->replyToMessage({}); _header->replyToMessage({});
} else { } else {
_canReplaceMedia = false; _canReplaceMedia = false;
@ -2708,17 +2860,6 @@ void ComposeControls::editMessage(not_null<HistoryItem*> item) {
cursor, cursor,
previewState)); previewState));
applyDraft(); applyDraft();
const auto media = item->media();
_canReplaceMedia = media && media->allowsEditMedia();
_photoEditMedia = (_canReplaceMedia
&& media->photo()
&& !media->photo()->isNull())
? media->photo()->createMediaView()
: nullptr;
if (_photoEditMedia) {
_photoEditMedia->wanted(Data::PhotoSize::Large, item->fullId());
}
if (updateReplaceMediaButton()) { if (updateReplaceMediaButton()) {
updateControlsVisibility(); updateControlsVisibility();
updateControlsGeometry(_wrap->size()); updateControlsGeometry(_wrap->size());

View file

@ -50,6 +50,14 @@ void TTLButton::hide() {
_button.hide(); _button.hide();
} }
void TTLButton::setVisible(bool visible) {
_button.setVisible(visible);
}
bool TTLButton::isVisible() const {
return _button.isVisible();
}
void TTLButton::move(int x, int y) { void TTLButton::move(int x, int y) {
_button.move(x, y); _button.move(x, y);
} }

View file

@ -28,6 +28,8 @@ public:
void show(); void show();
void hide(); void hide();
void setVisible(bool visible);
[[nodiscard]] bool isVisible() const;
void move(int x, int y); void move(int x, int y);
[[nodiscard]] int width() const; [[nodiscard]] int width() const;

View file

@ -846,8 +846,8 @@ void Element::validateText() {
_media = nullptr; _media = nullptr;
if (!storyMention) { if (!storyMention) {
if (_text.isEmpty()) { if (_text.isEmpty()) {
setTextWithLinks( setTextWithLinks(Ui::Text::Italic(
Ui::Text::Italic(u"This story has expired"_q)); tr::lng_forwarded_story_expired(tr::now)));
} }
return; return;
} }

View file

@ -933,7 +933,7 @@ Element *ListWidget::viewByPosition(Data::MessagePosition position) const {
const auto result = (index < 0) ? nullptr : _items[index].get(); const auto result = (index < 0) ? nullptr : _items[index].get();
return (position == Data::MinMessagePosition return (position == Data::MinMessagePosition
|| position == Data::MaxMessagePosition || position == Data::MaxMessagePosition
|| result->data()->position() == position) || (result && result->data()->position() == position))
? result ? result
: nullptr; : nullptr;
} }
@ -2059,12 +2059,13 @@ void ListWidget::paintEvent(QPaintEvent *e) {
} }
auto readTill = (HistoryItem*)nullptr; auto readTill = (HistoryItem*)nullptr;
auto readContents = base::flat_set<not_null<HistoryItem*>>(); auto readContents = base::flat_set<not_null<HistoryItem*>>();
const auto markingAsViewed = markingMessagesRead();
const auto guard = gsl::finally([&] { const auto guard = gsl::finally([&] {
if (_translateTracker) { if (_translateTracker) {
_delegate->listAddTranslatedItems(_translateTracker.get()); _delegate->listAddTranslatedItems(_translateTracker.get());
_translateTracker->finishBunch(); _translateTracker->finishBunch();
} }
if (readTill && markingMessagesRead()) { if (markingAsViewed && readTill) {
_delegate->listMarkReadTill(readTill); _delegate->listMarkReadTill(readTill);
} }
if (!readContents.empty() && markingContentsRead()) { if (!readContents.empty() && markingContentsRead()) {
@ -2136,7 +2137,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
} else if (isUnread) { } else if (isUnread) {
readTill = item; readTill = item;
} }
if (item->hasViews()) { if (markingAsViewed && item->hasViews()) {
session->api().views().scheduleIncrement(item); session->api().views().scheduleIncrement(item);
} }
if (withReaction) { if (withReaction) {

View file

@ -88,14 +88,13 @@ Gif::Gif(
bool spoiler) bool spoiler)
: File(parent, realParent) : File(parent, realParent)
, _data(document) , _data(document)
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) , _storyId(realParent->media()
? realParent->media()->storyId()
: FullStoryId())
, _caption(
st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right())
, _spoiler(spoiler ? std::make_unique<MediaSpoiler>() : nullptr) , _spoiler(spoiler ? std::make_unique<MediaSpoiler>() : nullptr)
, _downloadSize(Ui::FormatSizeText(_data->size)) { , _downloadSize(Ui::FormatSizeText(_data->size)) {
if (const auto media = realParent->media()) {
if (media->storyId()) {
_story = true;
}
}
setDocumentLinks(_data, realParent, [=] { setDocumentLinks(_data, realParent, [=] {
if (!_data->createMediaView()->canBePlayed(realParent) if (!_data->createMediaView()->canBePlayed(realParent)
|| !_data->isAnimation() || !_data->isAnimation()
@ -1438,15 +1437,14 @@ void Gif::dataMediaCreated() const {
} }
void Gif::togglePollingStory(bool enabled) const { void Gif::togglePollingStory(bool enabled) const {
if (!_story || _pollingStory == enabled) { if (!_storyId || _pollingStory == enabled) {
return; return;
} }
const auto polling = Data::Stories::Polling::Chat; const auto polling = Data::Stories::Polling::Chat;
const auto media = _parent->data()->media();
const auto id = media ? media->storyId() : FullStoryId();
if (!enabled) { if (!enabled) {
_data->owner().stories().unregisterPolling(id, polling); _data->owner().stories().unregisterPolling(_storyId, polling);
} else if (!_data->owner().stories().registerPolling(id, polling)) { } else if (
!_data->owner().stories().registerPolling(_storyId, polling)) {
return; return;
} }
_pollingStory = enabled; _pollingStory = enabled;
@ -1464,7 +1462,7 @@ void Gif::hideSpoilers() {
} }
bool Gif::needsBubble() const { bool Gif::needsBubble() const {
if (_story) { if (_storyId) {
return true; return true;
} else if (_data->isVideoMessage()) { } else if (_data->isVideoMessage()) {
return false; return false;

View file

@ -211,6 +211,7 @@ private:
void togglePollingStory(bool enabled) const; void togglePollingStory(bool enabled) const;
const not_null<DocumentData*> _data; const not_null<DocumentData*> _data;
const FullStoryId _storyId;
Ui::Text::String _caption; Ui::Text::String _caption;
std::unique_ptr<Streamed> _streamed; std::unique_ptr<Streamed> _streamed;
const std::unique_ptr<MediaSpoiler> _spoiler; const std::unique_ptr<MediaSpoiler> _spoiler;
@ -223,7 +224,6 @@ private:
mutable std::optional<Ui::BubbleRounding> _thumbCacheRounding; mutable std::optional<Ui::BubbleRounding> _thumbCacheRounding;
mutable bool _thumbCacheBlurred : 1 = false; mutable bool _thumbCacheBlurred : 1 = false;
mutable bool _thumbIsEllipse : 1 = false; mutable bool _thumbIsEllipse : 1 = false;
mutable bool _story : 1 = false;
mutable bool _pollingStory : 1 = false; mutable bool _pollingStory : 1 = false;
}; };

View file

@ -69,13 +69,11 @@ Photo::Photo(
bool spoiler) bool spoiler)
: File(parent, realParent) : File(parent, realParent)
, _data(photo) , _data(photo)
, _storyId(realParent->media()
? realParent->media()->storyId()
: FullStoryId())
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right())
, _spoiler(spoiler ? std::make_unique<MediaSpoiler>() : nullptr) { , _spoiler(spoiler ? std::make_unique<MediaSpoiler>() : nullptr) {
if (const auto media = realParent->media()) {
if (media->storyId()) {
_story = 1;
}
}
_caption = createCaption(realParent); _caption = createCaption(realParent);
create(realParent->fullId()); create(realParent->fullId());
} }
@ -168,15 +166,14 @@ void Photo::unloadHeavyPart() {
void Photo::togglePollingStory(bool enabled) const { void Photo::togglePollingStory(bool enabled) const {
const auto pollingStory = (enabled ? 1 : 0); const auto pollingStory = (enabled ? 1 : 0);
if (!_story || _pollingStory == pollingStory) { if (!_storyId || _pollingStory == pollingStory) {
return; return;
} }
const auto polling = Data::Stories::Polling::Chat; const auto polling = Data::Stories::Polling::Chat;
const auto media = _parent->data()->media();
const auto id = media ? media->storyId() : FullStoryId();
if (!enabled) { if (!enabled) {
_data->owner().stories().unregisterPolling(id, polling); _data->owner().stories().unregisterPolling(_storyId, polling);
} else if (!_data->owner().stories().registerPolling(id, polling)) { } else if (
!_data->owner().stories().registerPolling(_storyId, polling)) {
return; return;
} }
_pollingStory = pollingStory; _pollingStory = pollingStory;
@ -285,7 +282,7 @@ int Photo::adjustHeightForLessCrop(QSize dimensions, QSize current) const {
void Photo::draw(Painter &p, const PaintContext &context) const { void Photo::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
return; return;
} else if (_story && _data->isNull()) { } else if (_storyId && _data->isNull()) {
return; return;
} }
@ -623,7 +620,7 @@ void Photo::paintUserpicFrame(
} }
QSize Photo::photoSize() const { QSize Photo::photoSize() const {
if (_story) { if (_storyId) {
return { kStoryWidth, kStoryHeight }; return { kStoryWidth, kStoryHeight };
} }
return QSize(_data->width(), _data->height()); return QSize(_data->width(), _data->height());
@ -634,7 +631,7 @@ TextState Photo::textState(QPoint point, StateRequest request) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
return result; return result;
} else if (_story && _data->isNull()) { } else if (_storyId && _data->isNull()) {
return result; return result;
} }
auto paintx = 0, painty = 0, paintw = width(), painth = height(); auto paintx = 0, painty = 0, paintw = width(), painth = height();
@ -1054,7 +1051,7 @@ void Photo::hideSpoilers() {
} }
bool Photo::needsBubble() const { bool Photo::needsBubble() const {
if (_story || !_caption.isEmpty()) { if (_storyId || !_caption.isEmpty()) {
return true; return true;
} }
const auto item = _parent->data(); const auto item = _parent->data();

View file

@ -163,6 +163,7 @@ private:
void togglePollingStory(bool enabled) const; void togglePollingStory(bool enabled) const;
const not_null<PhotoData*> _data; const not_null<PhotoData*> _data;
const FullStoryId _storyId;
Ui::Text::String _caption; Ui::Text::String _caption;
mutable std::shared_ptr<Data::PhotoMedia> _dataMedia; mutable std::shared_ptr<Data::PhotoMedia> _dataMedia;
mutable std::unique_ptr<Streamed> _streamed; mutable std::unique_ptr<Streamed> _streamed;
@ -172,7 +173,6 @@ private:
uint32 _serviceWidth : 28 = 0; uint32 _serviceWidth : 28 = 0;
mutable uint32 _imageCacheForum : 1 = 0; mutable uint32 _imageCacheForum : 1 = 0;
mutable uint32 _imageCacheBlurred : 1 = 0; mutable uint32 _imageCacheBlurred : 1 = 0;
mutable uint32 _story : 1 = 0;
mutable uint32 _pollingStory : 1 = 0; mutable uint32 _pollingStory : 1 = 0;
}; };

View file

@ -283,9 +283,10 @@ BaseLayout *Provider::getLayout(
if (auto layout = createLayout(id, delegate)) { if (auto layout = createLayout(id, delegate)) {
layout->initDimensions(); layout->initDimensions();
it = _layouts.emplace(id, std::move(layout)).first; it = _layouts.emplace(id, std::move(layout)).first;
_peer->owner().stories().registerPolling( const auto ok = _peer->owner().stories().registerPolling(
{ _peer->id, id }, { _peer->id, id },
Data::Stories::Polling::Chat); Data::Stories::Polling::Chat);
Assert(ok);
} else { } else {
return nullptr; return nullptr;
} }

View file

@ -346,7 +346,8 @@ bool WritingEntry() {
void start() { void start() {
Assert(LogsData == nullptr); Assert(LogsData == nullptr);
if (!Core::Launcher::Instance().checkPortableVersionFolder()) { auto &launcher = Core::Launcher::Instance();
if (!launcher.checkPortableVersionFolder()) {
return; return;
} }
@ -362,7 +363,6 @@ void start() {
if (!cWorkingDir().isEmpty()) { if (!cWorkingDir().isEmpty()) {
// This value must come from TelegramForcePortable // This value must come from TelegramForcePortable
// or from the "-workdir" command line argument.
cForceWorkingDir(cWorkingDir()); cForceWorkingDir(cWorkingDir());
workingDirChosen = true; workingDirChosen = true;
} else { } else {
@ -390,7 +390,6 @@ void start() {
if (!cWorkingDir().isEmpty()) { if (!cWorkingDir().isEmpty()) {
// This value must come from TelegramForcePortable // This value must come from TelegramForcePortable
// or from the "-workdir" command line argument.
cForceWorkingDir(cWorkingDir()); cForceWorkingDir(cWorkingDir());
workingDirChosen = true; workingDirChosen = true;
} }
@ -407,6 +406,11 @@ void start() {
} }
} }
if (launcher.validateCustomWorkingDir()) {
delete LogsData;
LogsData = new LogsDataFields();
}
// WinRT build requires the working dir to stay the same for plugin loading. // WinRT build requires the working dir to stay the same for plugin loading.
#ifndef Q_OS_WINRT #ifndef Q_OS_WINRT
QDir().setCurrent(cWorkingDir()); QDir().setCurrent(cWorkingDir());
@ -414,7 +418,7 @@ void start() {
QDir().mkpath(cWorkingDir() + u"tdata"_q); QDir().mkpath(cWorkingDir() + u"tdata"_q);
Core::Launcher::Instance().workingFolderReady(); launcher.workingFolderReady();
CrashReports::StartCatching(); CrashReports::StartCatching();
if (!LogsData->openMain()) { if (!LogsData->openMain()) {
@ -430,7 +434,7 @@ void start() {
LOG(("Executable dir: %1, name: %2").arg(cExeDir(), cExeName())); LOG(("Executable dir: %1, name: %2").arg(cExeDir(), cExeName()));
LOG(("Initial working dir: %1").arg(initialWorkingDir)); LOG(("Initial working dir: %1").arg(initialWorkingDir));
LOG(("Working dir: %1").arg(cWorkingDir())); LOG(("Working dir: %1").arg(cWorkingDir()));
LOG(("Command line: %1").arg(Core::Launcher::Instance().arguments().join(' '))); LOG(("Command line: %1").arg(launcher.arguments().join(' ')));
if (!LogsData) { if (!LogsData) {
LOG(("FATAL: Could not open '%1' for writing log!" LOG(("FATAL: Could not open '%1' for writing log!"

View file

@ -1926,7 +1926,7 @@ void MainWidget::showBackFromStack(
void MainWidget::orderWidgets() { void MainWidget::orderWidgets() {
if (_dialogs) { if (_dialogs) {
_dialogs->raise(); _dialogs->raiseWithTooltip();
} }
if (_player) { if (_player) {
_player->raise(); _player->raise();

View file

@ -106,7 +106,7 @@ mediaPlayerRepeatButton: IconButton {
icon: icon { icon: icon {
{ "player/player_repeat", mediaPlayerActiveFg } { "player/player_repeat", mediaPlayerActiveFg }
}; };
iconPosition: point(3px, 6px); iconPosition: point(2px, 5px);
rippleAreaPosition: point(2px, 6px); rippleAreaPosition: point(2px, 6px);
rippleAreaSize: 24px; rippleAreaSize: 24px;
@ -135,6 +135,10 @@ mediaPlayerReverseDisabledIconOver: icon {
mediaPlayerShuffleIcon: icon { mediaPlayerShuffleIcon: icon {
{ "player/player_shuffle", mediaPlayerActiveFg } { "player/player_shuffle", mediaPlayerActiveFg }
}; };
mediaPlayerOrderButton: IconButton(mediaPlayerRepeatButton) {
iconPosition: point(2px, 6px);
rippleAreaPosition: point(2px, 6px);
}
mediaPlayerRepeatDisabledRippleBg: windowBgOver; mediaPlayerRepeatDisabledRippleBg: windowBgOver;
mediaPlayerPlayButton: IconButton(mediaPlayerRepeatButton) { mediaPlayerPlayButton: IconButton(mediaPlayerRepeatButton) {
@ -233,7 +237,7 @@ mediaPlayerVolumeToggle: IconButton(mediaPlayerRepeatButton) {
{ "player/player_mini_full", mediaPlayerActiveFg }, { "player/player_mini_full", mediaPlayerActiveFg },
}; };
iconPosition: point(5px, 6px); iconPosition: point(5px, 6px);
rippleAreaPosition: point(4px, 5px); rippleAreaPosition: point(5px, 6px);
} }
mediaPlayerVolumeMargin: 10px; mediaPlayerVolumeMargin: 10px;
mediaPlayerVolumeSize: size(27px, 100px); mediaPlayerVolumeSize: size(27px, 100px);
@ -267,9 +271,9 @@ mediaPlayerClose: IconButton(mediaPlayerRepeatButton) {
width: 39px; width: 39px;
icon: icon {{ "player/panel_close", menuIconFg }}; icon: icon {{ "player/panel_close", menuIconFg }};
iconOver: icon {{ "player/panel_close", menuIconFgOver }}; iconOver: icon {{ "player/panel_close", menuIconFgOver }};
iconPosition: point(5px, 6px); iconPosition: point(4px, 6px);
rippleAreaPosition: point(4px, 5px); rippleAreaPosition: point(4px, 6px);
ripple: RippleAnimation(defaultRippleAnimation) { ripple: RippleAnimation(defaultRippleAnimation) {
color: windowBgOver; color: windowBgOver;
} }

View file

@ -55,7 +55,7 @@ Widget::Widget(
, _playPause(this, st::mediaPlayerPlayButton) , _playPause(this, st::mediaPlayerPlayButton)
, _volumeToggle(rightControls(), st::mediaPlayerVolumeToggle) , _volumeToggle(rightControls(), st::mediaPlayerVolumeToggle)
, _repeatToggle(rightControls(), st::mediaPlayerRepeatButton) , _repeatToggle(rightControls(), st::mediaPlayerRepeatButton)
, _orderToggle(rightControls(), st::mediaPlayerRepeatButton) , _orderToggle(rightControls(), st::mediaPlayerOrderButton)
, _speedToggle(rightControls(), st::mediaPlayerSpeedButton) , _speedToggle(rightControls(), st::mediaPlayerSpeedButton)
, _close(this, st::mediaPlayerClose) , _close(this, st::mediaPlayerClose)
, _shadow(this) , _shadow(this)

View file

@ -7,79 +7,168 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "media/stories/media_stories_caption_full_view.h" #include "media/stories/media_stories_caption_full_view.h"
#include "base/event_filter.h"
#include "core/ui_integration.h" #include "core/ui_integration.h"
#include "ui/widgets/scroll_area.h" #include "chat_helpers/compose/compose_show.h"
#include "media/stories/media_stories_controller.h"
#include "media/stories/media_stories_view.h"
#include "ui/widgets/elastic_scroll.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/click_handler.h"
#include "styles/style_media_view.h" #include "styles/style_media_view.h"
namespace Media::Stories { namespace Media::Stories {
CaptionFullView::CaptionFullView( CaptionFullView::CaptionFullView(not_null<Controller*> controller)
not_null<Ui::RpWidget*> parent, : _controller(controller)
not_null<Main::Session*> session, , _scroll(std::make_unique<Ui::ElasticScroll>(controller->wrap()))
const TextWithEntities &text, , _wrap(_scroll->setOwnedWidget(
Fn<void()> close)
: RpWidget(parent)
, _scroll(std::make_unique<Ui::ScrollArea>((RpWidget*)this))
, _text(_scroll->setOwnedWidget(
object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>( object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
_scroll.get(), _scroll.get(),
object_ptr<Ui::FlatLabel>(_scroll.get(), st::storiesCaptionFull), object_ptr<Ui::FlatLabel>(_scroll.get(), st::storiesCaptionFull),
st::mediaviewCaptionPadding))->entity()) st::mediaviewCaptionPadding)))
, _close(std::move(close)) , _text(_wrap->entity()) {
, _background(st::storiesRadius, st::mediaviewCaptionBg) { _text->setMarkedText(controller->captionText(), Core::MarkedTextContext{
_text->setMarkedText(text, Core::MarkedTextContext{ .session = &controller->uiShow()->session(),
.session = session,
.customEmojiRepaint = [=] { _text->update(); }, .customEmojiRepaint = [=] { _text->update(); },
}); });
parent->sizeValue() | rpl::start_with_next([=](QSize size) { startAnimation();
setGeometry(QRect(QPoint(), size)); _controller->layoutValue(
}, lifetime()); ) | rpl::start_with_next([=](const Layout &layout) {
if (_outer != layout.content) {
const auto skip = layout.header.y()
+ layout.header.height()
- layout.content.y();
_outer = layout.content.marginsRemoved({ 0, skip, 0, 0 });
updateGeometry();
}
}, _scroll->lifetime());
show(); const auto filter = [=](not_null<QEvent*> e) {
setFocus(); const auto mouse = [&] {
return static_cast<QMouseEvent*>(e.get());
};
const auto type = e->type();
if (type == QEvent::MouseButtonPress
&& mouse()->button() == Qt::LeftButton
&& !ClickHandler::getActive()) {
_down = true;
} else if (type == QEvent::MouseButtonRelease && _down) {
_down = false;
if (!ClickHandler::getPressed()) {
close();
}
} else if (type == QEvent::KeyPress
&& static_cast<QKeyEvent*>(e.get())->key() == Qt::Key_Escape) {
close();
return base::EventFilterResult::Cancel;
}
return base::EventFilterResult::Continue;
};
base::install_event_filter(_text.get(), filter);
base::install_event_filter(_wrap.get(), filter);
using Type = Ui::ElasticScroll::OverscrollType;
rpl::combine(
_scroll->positionValue(),
_scroll->movementValue()
) | rpl::filter([=] {
return !_closing;
}) | rpl::start_with_next([=](
Ui::ElasticScrollPosition position,
Ui::ElasticScrollMovement movement) {
const auto overscrollTop = std::max(-position.overscroll, 0);
using Phase = Ui::ElasticScrollMovement;
if (movement == Phase::Progress) {
if (overscrollTop > 0) {
_pulling = true;
} else {
_pulling = false;
}
} else if (_pulling
&& (movement == Phase::Momentum
|| movement == Phase::Returning)) {
_pulling = false;
if (overscrollTop > st::storiesCaptionPullThreshold) {
_closingTopAdded = overscrollTop;
_scroll->setOverscrollTypes(Type::None, Type::Real);
close();
updateGeometry();
}
}
}, _scroll->lifetime());
_scroll->show();
_scroll->setOverscrollBg(QColor(0, 0, 0, 0));
_scroll->setOverscrollTypes(Type::Real, Type::Real);
_text->show();
_text->setFocus();
} }
CaptionFullView::~CaptionFullView() = default; CaptionFullView::~CaptionFullView() = default;
void CaptionFullView::paintEvent(QPaintEvent *e) { bool CaptionFullView::closing() const {
auto p = QPainter(this); return _closing;
_background.paint(p, _scroll->geometry());
_background.paint(p, _scroll->geometry());
} }
void CaptionFullView::resizeEvent(QResizeEvent *e) { bool CaptionFullView::focused() const {
const auto wanted = _text->naturalWidth(); return Ui::InFocusChain(_scroll.get());
}
void CaptionFullView::close() {
if (_closing) {
return;
}
_closing = true;
_controller->captionClosing();
startAnimation();
}
void CaptionFullView::updateGeometry() {
if (_outer.isEmpty()) {
return;
}
const auto lineHeight = st::mediaviewCaptionStyle.font->height;
const auto padding = st::mediaviewCaptionPadding; const auto padding = st::mediaviewCaptionPadding;
const auto margin = st::mediaviewCaptionMargin * 2; _text->resizeToWidth(_outer.width() - padding.left() - padding.right());
const auto available = (rect() - padding).width() const auto add = padding.top() + padding.bottom();
- (margin.width() * 2); const auto maxShownHeight = lineHeight * kMaxShownCaptionLines;
const auto use = std::min(wanted, available); const auto shownHeight = (_text->height() > maxShownHeight)
_text->resizeToWidth(use); ? (lineHeight * kCollapsedCaptionLines)
const auto fullw = use + padding.left() + padding.right(); : _text->height();
const auto fullh = std::min( const auto collapsedHeight = shownHeight + add;
_text->height() + padding.top() + padding.bottom(), const auto addedToBottom = lineHeight;
height() - (margin.height() * 2)); const auto expandedHeight = _text->height() + add + addedToBottom;
const auto left = (width() - fullw) / 2; const auto fullHeight = std::min(expandedHeight, _outer.height());
const auto top = (height() - fullh) / 2; const auto shown = _animation.value(_closing ? 0. : 1.);
_scroll->setGeometry(left, top, fullw, fullh); const auto height = (_closing || _animation.animating())
} ? anim::interpolate(collapsedHeight, fullHeight, shown)
: _outer.height();
void CaptionFullView::keyPressEvent(QKeyEvent *e) { const auto added = anim::interpolate(0, _closingTopAdded, shown);
if (e->key() == Qt::Key_Escape) { const auto bottomPadding = anim::interpolate(0, addedToBottom, shown);
if (const auto onstack = _close) { const auto use = padding + ((_closing || _animation.animating())
onstack(); ? QMargins(0, 0, 0, bottomPadding)
} : QMargins(0, height - fullHeight, 0, bottomPadding));
_wrap->setPadding(use);
_scroll->setGeometry(
_outer.x(),
added + _outer.y() + _outer.height() - height,
_outer.width(),
std::max(height - added, 0));
if (_closing && !_animation.animating()) {
_controller->captionClosed();
} }
} }
void CaptionFullView::mousePressEvent(QMouseEvent *e) { void CaptionFullView::startAnimation() {
if (e->button() == Qt::LeftButton) { _animation.start(
if (const auto onstack = _close) { [=] { updateGeometry(); },
onstack(); _closing ? 1. : 0.,
} _closing ? 0. : 1.,
} st::fadeWrapDuration,
anim::sineInOut);
} }
} // namespace Media::Stories } // namespace Media::Stories

View file

@ -7,8 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "ui/rp_widget.h" #include "ui/effects/animations.h"
#include "ui/round_rect.h"
namespace Main { namespace Main {
class Session; class Session;
@ -16,30 +15,38 @@ class Session;
namespace Ui { namespace Ui {
class FlatLabel; class FlatLabel;
class ScrollArea; class ElasticScroll;
template <typename Widget>
class PaddingWrap;
} // namespace Ui } // namespace Ui
namespace Media::Stories { namespace Media::Stories {
class CaptionFullView final : private Ui::RpWidget { class Controller;
class CaptionFullView final {
public: public:
CaptionFullView( explicit CaptionFullView(not_null<Controller*> controller);
not_null<Ui::RpWidget*> parent,
not_null<Main::Session*> session,
const TextWithEntities &text,
Fn<void()> close);
~CaptionFullView(); ~CaptionFullView();
private: void close();
void paintEvent(QPaintEvent *e) override; [[nodiscard]] bool closing() const;
void resizeEvent(QResizeEvent *e) override; [[nodiscard]] bool focused() const;
void keyPressEvent(QKeyEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
std::unique_ptr<Ui::ScrollArea> _scroll; private:
void updateGeometry();
void startAnimation();
const not_null<Controller*> _controller;
const std::unique_ptr<Ui::ElasticScroll> _scroll;
const not_null<Ui::PaddingWrap<Ui::FlatLabel>*> _wrap;
const not_null<Ui::FlatLabel*> _text; const not_null<Ui::FlatLabel*> _text;
Fn<void()> _close; Ui::Animations::Simple _animation;
Ui::RoundRect _background; QRect _outer;
int _closingTopAdded = 0;
bool _pulling = false;
bool _closing = false;
bool _down = false;
}; };

View file

@ -64,7 +64,7 @@ namespace {
constexpr auto kPhotoProgressInterval = crl::time(100); constexpr auto kPhotoProgressInterval = crl::time(100);
constexpr auto kPhotoDuration = 5 * crl::time(1000); constexpr auto kPhotoDuration = 5 * crl::time(1000);
constexpr auto kFullContentFade = 0.35; constexpr auto kFullContentFade = 0.6;
constexpr auto kSiblingMultiplierDefault = 0.448; constexpr auto kSiblingMultiplierDefault = 0.448;
constexpr auto kSiblingMultiplierMax = 0.72; constexpr auto kSiblingMultiplierMax = 0.72;
constexpr auto kSiblingOutsidePart = 0.24; constexpr auto kSiblingOutsidePart = 0.24;
@ -280,9 +280,6 @@ Controller::Controller(not_null<Delegate*> delegate)
_1 || _2 _1 || _2
) | rpl::distinct_until_changed( ) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](bool active) { ) | rpl::start_with_next([=](bool active) {
if (active) {
_captionFullView = nullptr;
}
_replyActive = active; _replyActive = active;
updateContentFaded(); updateContentFaded();
}, _lifetime); }, _lifetime);
@ -322,8 +319,18 @@ Controller::Controller(not_null<Delegate*> delegate)
_delegate->storiesLayerShown( _delegate->storiesLayerShown(
) | rpl::start_with_next([=](bool shown) { ) | rpl::start_with_next([=](bool shown) {
_layerShown = shown; if (_layerShown != shown) {
updatePlayingAllowed(); _layerShown = shown;
updatePlayingAllowed();
}
}, _lifetime);
_header->tooltipShownValue(
) | rpl::start_with_next([=](bool shown) {
if (_tooltipShown != shown) {
_tooltipShown = shown;
updatePlayingAllowed();
}
}, _lifetime); }, _lifetime);
const auto window = _wrap->window()->windowHandle(); const auto window = _wrap->window()->windowHandle();
@ -345,7 +352,8 @@ Controller::~Controller() {
} }
void Controller::updateContentFaded() { void Controller::updateContentFaded() {
const auto faded = _replyActive || _captionFullView || _captionExpanded; const auto faded = _replyActive
|| (_captionFullView && !_captionFullView->closing());
if (_contentFaded == faded) { if (_contentFaded == faded) {
return; return;
} }
@ -378,6 +386,8 @@ void Controller::initLayout() {
_layout = _wrap->sizeValue( _layout = _wrap->sizeValue(
) | rpl::map([=](QSize size) { ) | rpl::map([=](QSize size) {
const auto topNotchSkip = _delegate->storiesTopNotchSkip();
size = QSize( size = QSize(
std::max(size.width(), st::mediaviewMinWidth), std::max(size.width(), st::mediaviewMinWidth),
std::max(size.height(), st::mediaviewMinHeight)); std::max(size.height(), st::mediaviewMinHeight));
@ -387,7 +397,8 @@ void Controller::initLayout() {
? HeaderLayout::Outside ? HeaderLayout::Outside
: HeaderLayout::Normal; : HeaderLayout::Normal;
const auto topSkip = st::storiesFieldMargin.bottom() const auto topSkip = topNotchSkip
+ st::storiesFieldMargin.bottom()
+ (layout.headerLayout == HeaderLayout::Outside + (layout.headerLayout == HeaderLayout::Outside
? outsideHeaderHeight ? outsideHeaderHeight
: 0); : 0);
@ -571,26 +582,31 @@ TextWithEntities Controller::captionText() const {
return _captionText; return _captionText;
} }
void Controller::setCaptionExpanded(bool expanded) { bool Controller::skipCaption() const {
if (_captionExpanded == expanded) { return _captionFullView != nullptr;
return;
}
_captionExpanded = expanded;
updateContentFaded();
} }
void Controller::showFullCaption() { void Controller::showFullCaption() {
if (_captionText.empty()) { if (_captionText.empty()) {
return; return;
} }
_captionFullView = std::make_unique<CaptionFullView>( _captionFullView = std::make_unique<CaptionFullView>(this);
wrap(),
&_delegate->storiesShow()->session(),
_captionText,
[=] { _captionFullView = nullptr; updateContentFaded(); });
updateContentFaded(); updateContentFaded();
} }
void Controller::captionClosing() {
updateContentFaded();
}
void Controller::captionClosed() {
if (!_captionFullView) {
return;
} else if (_captionFullView->focused()) {
_wrap->setFocus();
}
_captionFullView = nullptr;
}
std::shared_ptr<ChatHelpers::Show> Controller::uiShow() const { std::shared_ptr<ChatHelpers::Show> Controller::uiShow() const {
return _delegate->storiesShow(); return _delegate->storiesShow();
} }
@ -807,9 +823,8 @@ void Controller::show(
_slider->raise(); _slider->raise();
} }
captionClosed();
_captionText = story->caption(); _captionText = story->caption();
_captionFullView = nullptr;
_captionExpanded = false;
_contentFaded = false; _contentFaded = false;
_contentFadeAnimation.stop(); _contentFadeAnimation.stop();
const auto document = story->document(); const auto document = story->document();
@ -823,6 +838,7 @@ void Controller::show(
.video = (document != nullptr), .video = (document != nullptr),
.silent = (document && document->isSilentVideo()), .silent = (document && document->isSilentVideo()),
}); });
uiShow()->hideLayer(anim::type::instant);
if (!changeShown(story)) { if (!changeShown(story)) {
return; return;
} }
@ -856,9 +872,10 @@ bool Controller::changeShown(Data::Story *story) {
if (_shown == id && !sessionChanged) { if (_shown == id && !sessionChanged) {
return false; return false;
} }
if (const auto now = this->story()) { if (_shown) {
now->owner().stories().unregisterPolling( Assert(_session != nullptr);
now, _session->data().stories().unregisterPolling(
_shown,
Data::Stories::Polling::Viewer); Data::Stories::Polling::Viewer);
} }
if (sessionChanged) { if (sessionChanged) {
@ -958,16 +975,13 @@ void Controller::updatePlayingAllowed() {
&& _windowActive && _windowActive
&& !_paused && !_paused
&& !_replyActive && !_replyActive
&& !_captionFullView && (!_captionFullView || _captionFullView->closing())
&& !_captionExpanded
&& !_layerShown && !_layerShown
&& !_menuShown); && !_menuShown
&& !_tooltipShown);
} }
void Controller::setPlayingAllowed(bool allowed) { void Controller::setPlayingAllowed(bool allowed) {
if (allowed) {
_captionFullView = nullptr;
}
if (_photoPlayback) { if (_photoPlayback) {
_photoPlayback->togglePaused(!allowed); _photoPlayback->togglePaused(!allowed);
} else { } else {
@ -1177,6 +1191,9 @@ void Controller::togglePaused(bool paused) {
void Controller::contentPressed(bool pressed) { void Controller::contentPressed(bool pressed) {
togglePaused(pressed); togglePaused(pressed);
if (_captionFullView) {
_captionFullView->close();
}
if (pressed) { if (pressed) {
_reactions->collapse(); _reactions->collapse();
} }

View file

@ -122,8 +122,10 @@ public:
[[nodiscard]] bool closeByClickAt(QPoint position) const; [[nodiscard]] bool closeByClickAt(QPoint position) const;
[[nodiscard]] Data::FileOrigin fileOrigin() const; [[nodiscard]] Data::FileOrigin fileOrigin() const;
[[nodiscard]] TextWithEntities captionText() const; [[nodiscard]] TextWithEntities captionText() const;
void setCaptionExpanded(bool expanded); [[nodiscard]] bool skipCaption() const;
void showFullCaption(); void showFullCaption();
void captionClosing();
void captionClosed();
[[nodiscard]] std::shared_ptr<ChatHelpers::Show> uiShow() const; [[nodiscard]] std::shared_ptr<ChatHelpers::Show> uiShow() const;
[[nodiscard]] auto stickerOrEmojiChosen() const [[nodiscard]] auto stickerOrEmojiChosen() const
@ -250,13 +252,13 @@ private:
Ui::Animations::Simple _contentFadeAnimation; Ui::Animations::Simple _contentFadeAnimation;
bool _contentFaded = false; bool _contentFaded = false;
bool _captionExpanded = false;
bool _windowActive = false; bool _windowActive = false;
bool _replyFocused = false; bool _replyFocused = false;
bool _replyActive = false; bool _replyActive = false;
bool _hasSendText = false; bool _hasSendText = false;
bool _layerShown = false; bool _layerShown = false;
bool _menuShown = false; bool _menuShown = false;
bool _tooltipShown = false;
bool _paused = false; bool _paused = false;
FullStoryId _shown; FullStoryId _shown;

View file

@ -64,6 +64,7 @@ public:
virtual void storiesVolumeToggle() = 0; virtual void storiesVolumeToggle() = 0;
virtual void storiesVolumeChanged(float64 volume) = 0; virtual void storiesVolumeChanged(float64 volume) = 0;
virtual void storiesVolumeChangeFinished() = 0; virtual void storiesVolumeChangeFinished() = 0;
[[nodiscard]] virtual int storiesTopNotchSkip() = 0;
}; };
} // namespace Media::Stories } // namespace Media::Stories

View file

@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/continuous_sliders.h" #include "ui/widgets/continuous_sliders.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/widgets/tooltip.h"
#include "ui/wrap/fade_wrap.h" #include "ui/wrap/fade_wrap.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/rp_widget.h" #include "ui/rp_widget.h"
@ -50,10 +51,9 @@ struct PrivacyBadge {
class UserpicBadge final : public Ui::RpWidget { class UserpicBadge final : public Ui::RpWidget {
public: public:
UserpicBadge( UserpicBadge(not_null<QWidget*> userpic, PrivacyBadge badge);
not_null<QWidget*> userpic,
PrivacyBadge badge, [[nodiscard]] QRect badgeGeometry() const;
Fn<void()> clicked);
private: private:
bool eventFilter(QObject *o, QEvent *e) override; bool eventFilter(QObject *o, QEvent *e) override;
@ -63,7 +63,6 @@ private:
const not_null<QWidget*> _userpic; const not_null<QWidget*> _userpic;
const PrivacyBadge _badgeData; const PrivacyBadge _badgeData;
const std::unique_ptr<Ui::AbstractButton> _clickable;
QRect _badge; QRect _badge;
QImage _layer; QImage _layer;
bool _grabbing = false; bool _grabbing = false;
@ -95,15 +94,10 @@ private:
return {}; return {};
} }
UserpicBadge::UserpicBadge( UserpicBadge::UserpicBadge(not_null<QWidget*> userpic, PrivacyBadge badge)
not_null<QWidget*> userpic,
PrivacyBadge badge,
Fn<void()> clicked)
: RpWidget(userpic->parentWidget()) : RpWidget(userpic->parentWidget())
, _userpic(userpic) , _userpic(userpic)
, _badgeData(badge) , _badgeData(badge) {
, _clickable(std::make_unique<Ui::AbstractButton>(parentWidget())) {
_clickable->setClickedCallback(std::move(clicked));
userpic->installEventFilter(this); userpic->installEventFilter(this);
updateGeometry(); updateGeometry();
setAttribute(Qt::WA_TransparentForMouseEvents); setAttribute(Qt::WA_TransparentForMouseEvents);
@ -113,6 +107,10 @@ UserpicBadge::UserpicBadge(
show(); show();
} }
QRect UserpicBadge::badgeGeometry() const {
return _badge;
}
bool UserpicBadge::eventFilter(QObject *o, QEvent *e) { bool UserpicBadge::eventFilter(QObject *o, QEvent *e) {
if (o != _userpic) { if (o != _userpic) {
return false; return false;
@ -173,22 +171,27 @@ void UserpicBadge::updateGeometry() {
_badge = QRect( _badge = QRect(
QPoint(width - badge.width(), height - badge.height()), QPoint(width - badge.width(), height - badge.height()),
badge); badge);
_clickable->setGeometry(_badge.translated(pos()));
update(); update();
} }
[[nodiscard]] std::unique_ptr<Ui::RpWidget> MakePrivacyBadge( struct MadePrivacyBadge {
std::unique_ptr<Ui::RpWidget> widget;
QRect geometry;
};
[[nodiscard]] MadePrivacyBadge MakePrivacyBadge(
not_null<QWidget*> userpic, not_null<QWidget*> userpic,
Data::StoryPrivacy privacy, Data::StoryPrivacy privacy) {
Fn<void()> clicked) {
const auto badge = LookupPrivacyBadge(privacy); const auto badge = LookupPrivacyBadge(privacy);
if (!badge.icon) { if (!badge.icon) {
return nullptr; return {};
} }
return std::make_unique<UserpicBadge>( auto widget = std::make_unique<UserpicBadge>(userpic, badge);
userpic, const auto geometry = widget->badgeGeometry();
badge, return {
std::move(clicked)); .widget = std::move(widget),
.geometry = geometry,
};
} }
[[nodiscard]] Timestamp ComposeTimestamp(TimeId when, TimeId now) { [[nodiscard]] Timestamp ComposeTimestamp(TimeId when, TimeId now) {
@ -277,6 +280,8 @@ void Header::show(HeaderData data) {
_info->setGeometry({ 0, 0, r, _widget->height() }); _info->setGeometry({ 0, 0, r, _widget->height() });
} }
}; };
_tooltip = nullptr;
_tooltipShown = false;
if (userChanged) { if (userChanged) {
_volume = nullptr; _volume = nullptr;
_date = nullptr; _date = nullptr;
@ -310,7 +315,7 @@ void Header::show(HeaderData data) {
raw, raw,
rpl::single(data.user->isSelf() rpl::single(data.user->isSelf()
? tr::lng_stories_my_name(tr::now) ? tr::lng_stories_my_name(tr::now)
: data.user->shortName()), : data.user->name()),
st::storiesHeaderName); st::storiesHeaderName);
_name->setAttribute(Qt::WA_TransparentForMouseEvents); _name->setAttribute(Qt::WA_TransparentForMouseEvents);
_name->setOpacity(kNameOpacity); _name->setOpacity(kNameOpacity);
@ -328,6 +333,8 @@ void Header::show(HeaderData data) {
_controller->layoutValue( _controller->layoutValue(
) | rpl::start_with_next([=](const Layout &layout) { ) | rpl::start_with_next([=](const Layout &layout) {
raw->setGeometry(layout.header); raw->setGeometry(layout.header);
_contentGeometry = layout.content;
updateTooltipGeometry();
}, raw->lifetime()); }, raw->lifetime());
} }
auto timestamp = ComposeDetails(data, base::unixtime::now()); auto timestamp = ComposeDetails(data, base::unixtime::now());
@ -357,8 +364,29 @@ void Header::show(HeaderData data) {
_counter = nullptr; _counter = nullptr;
} }
_privacy = MakePrivacyBadge(_userpic.get(), data.privacy, [=] { auto made = MakePrivacyBadge(_userpic.get(), data.privacy);
}); _privacy = std::move(made.widget);
_privacyBadgeOver = false;
_privacyBadgeGeometry = _privacy
? Ui::MapFrom(_info.get(), _privacy.get(), made.geometry)
: QRect();
if (_privacy) {
_info->setMouseTracking(true);
_info->events(
) | rpl::filter([=](not_null<QEvent*> e) {
const auto type = e->type();
if (type != QEvent::Leave && type != QEvent::MouseMove) {
return false;
}
const auto over = (type == QEvent::MouseMove)
&& _privacyBadgeGeometry.contains(
static_cast<QMouseEvent*>(e.get())->pos());
return (_privacyBadgeOver != over);
}) | rpl::start_with_next([=] {
_privacyBadgeOver = !_privacyBadgeOver;
toggleTooltip(Tooltip::Privacy, _privacyBadgeOver);
}, _privacy->lifetime());
}
if (data.video) { if (data.video) {
createPlayPause(); createPlayPause();
@ -369,6 +397,7 @@ void Header::show(HeaderData data) {
_playPause->moveToRight(playPause.x(), playPause.y(), width); _playPause->moveToRight(playPause.x(), playPause.y(), width);
const auto volume = st::storiesVolumeButtonPosition; const auto volume = st::storiesVolumeButtonPosition;
_volumeToggle->moveToRight(volume.x(), volume.y(), width); _volumeToggle->moveToRight(volume.x(), volume.y(), width);
updateTooltipGeometry();
}, _playPause->lifetime()); }, _playPause->lifetime());
_pauseState = _controller->pauseState(); _pauseState = _controller->pauseState();
@ -496,15 +525,14 @@ void Header::createVolumeToggle() {
_volumeToggle->events( _volumeToggle->events(
) | rpl::start_with_next([=](not_null<QEvent*> e) { ) | rpl::start_with_next([=](not_null<QEvent*> e) {
if (state->silent) {
return;
}
const auto type = e->type(); const auto type = e->type();
if (type == QEvent::Enter || type == QEvent::Leave) { if (type == QEvent::Enter || type == QEvent::Leave) {
const auto over = (e->type() == QEvent::Enter); const auto over = (e->type() == QEvent::Enter);
if (state->over != over) { if (state->over != over) {
state->over = over; state->over = over;
if (over) { if (state->silent) {
toggleTooltip(Tooltip::SilentVideo, over);
} else if (over) {
state->hideTimer.cancel(); state->hideTimer.cancel();
_volume->toggle(true, anim::type::normal); _volume->toggle(true, anim::type::normal);
} else if (!state->dropdownOver) { } else if (!state->dropdownOver) {
@ -565,6 +593,123 @@ void Header::createVolumeToggle() {
} }
} }
void Header::toggleTooltip(Tooltip type, bool show) {
const auto guard = gsl::finally([&] {
_tooltipShown = (_tooltip != nullptr);
});
if (const auto was = _tooltip.release()) {
was->toggleAnimated(false);
}
if (!show) {
return;
}
const auto text = [&]() -> TextWithEntities {
using Privacy = Data::StoryPrivacy;
const auto boldName = Ui::Text::Bold(_data->user->shortName());
const auto self = _data->user->isSelf();
switch (type) {
case Tooltip::SilentVideo:
return { tr::lng_stories_about_silent(tr::now) };
case Tooltip::Privacy: switch (_data->privacy) {
case Privacy::CloseFriends:
return self
? tr::lng_stories_about_close_friends_my(
tr::now,
Ui::Text::RichLangValue)
: tr::lng_stories_about_close_friends(
tr::now,
lt_user,
boldName,
Ui::Text::RichLangValue);
case Privacy::Contacts:
return self
? tr::lng_stories_about_contacts_my(
tr::now,
Ui::Text::RichLangValue)
: tr::lng_stories_about_contacts(
tr::now,
lt_user,
boldName,
Ui::Text::RichLangValue);
case Privacy::SelectedContacts:
return self
? tr::lng_stories_about_selected_contacts_my(
tr::now,
Ui::Text::RichLangValue)
: tr::lng_stories_about_selected_contacts(
tr::now,
lt_user,
boldName,
Ui::Text::RichLangValue);
}
}
return {};
}();
if (text.empty()) {
return;
}
_tooltipType = type;
_tooltip = std::make_unique<Ui::ImportantTooltip>(
_widget->parentWidget(),
Ui::MakeNiceTooltipLabel(
_widget.get(),
rpl::single(text),
st::storiesInfoTooltipMaxWidth,
st::storiesInfoTooltipLabel),
st::storiesInfoTooltip);
const auto tooltip = _tooltip.get();
const auto weak = QPointer<QWidget>(tooltip);
const auto destroy = [=] {
delete weak.data();
};
tooltip->setAttribute(Qt::WA_TransparentForMouseEvents);
tooltip->setHiddenCallback(destroy);
updateTooltipGeometry();
tooltip->toggleAnimated(true);
}
void Header::updateTooltipGeometry() {
if (!_tooltip) {
return;
}
const auto geometry = [&] {
switch (_tooltipType) {
case Tooltip::SilentVideo:
return Ui::MapFrom(
_widget->parentWidget(),
_volumeToggle.get(),
_volumeToggle->rect());
case Tooltip::Privacy:
return Ui::MapFrom(
_widget->parentWidget(),
_info.get(),
_privacyBadgeGeometry.marginsAdded(
st::storiesInfoTooltip.padding));
}
return QRect();
}();
if (geometry.isEmpty()) {
toggleTooltip(Tooltip::None, false);
return;
}
const auto weak = QPointer<QWidget>(_tooltip.get());
const auto countPosition = [=](QSize size) {
const auto result = geometry.bottomLeft()
- QPoint(size.width() / 2, 0);
const auto inner = _contentGeometry.marginsRemoved(
st::storiesInfoTooltip.padding);
if (size.width() > inner.width()) {
return QPoint(
inner.x() + (inner.width() - size.width()) / 2,
result.y());
} else if (result.x() < inner.x()) {
return QPoint(inner.x(), result.y());
}
return result;
};
_tooltip->pointAt(geometry, RectPart::Bottom, countPosition);
}
void Header::rebuildVolumeControls( void Header::rebuildVolumeControls(
not_null<Ui::RpWidget*> dropdown, not_null<Ui::RpWidget*> dropdown,
bool horizontal) { bool horizontal) {
@ -682,11 +827,14 @@ void Header::raise() {
} }
} }
bool Header::ignoreWindowMove(QPoint position) const { bool Header::ignoreWindowMove(QPoint position) const {
return _ignoreWindowMove; return _ignoreWindowMove;
} }
rpl::producer<bool> Header::tooltipShownValue() const {
return _tooltipShown.value();
}
void Header::updateDateText() { void Header::updateDateText() {
if (!_date || !_data || !_data->date) { if (!_date || !_data || !_data->date) {
return; return;

View file

@ -20,6 +20,7 @@ class FlatLabel;
class IconButton; class IconButton;
class AbstractButton; class AbstractButton;
class UserpicButton; class UserpicButton;
class ImportantTooltip;
template <typename Widget> template <typename Widget>
class FadeWrap; class FadeWrap;
} // namespace Ui } // namespace Ui
@ -55,8 +56,15 @@ public:
void raise(); void raise();
[[nodiscard]] bool ignoreWindowMove(QPoint position) const; [[nodiscard]] bool ignoreWindowMove(QPoint position) const;
[[nodiscard]] rpl::producer<bool> tooltipShownValue() const;
private: private:
enum class Tooltip {
None,
SilentVideo,
Privacy,
};
void updateDateText(); void updateDateText();
void applyPauseState(); void applyPauseState();
void createPlayPause(); void createPlayPause();
@ -64,6 +72,8 @@ private:
void rebuildVolumeControls( void rebuildVolumeControls(
not_null<Ui::RpWidget*> dropdown, not_null<Ui::RpWidget*> dropdown,
bool horizontal); bool horizontal);
void toggleTooltip(Tooltip type, bool show);
void updateTooltipGeometry();
const not_null<Controller*> _controller; const not_null<Controller*> _controller;
@ -81,9 +91,15 @@ private:
std::unique_ptr<Ui::FadeWrap<Ui::RpWidget>> _volume; std::unique_ptr<Ui::FadeWrap<Ui::RpWidget>> _volume;
rpl::variable<const style::icon*> _volumeIcon; rpl::variable<const style::icon*> _volumeIcon;
std::unique_ptr<Ui::RpWidget> _privacy; std::unique_ptr<Ui::RpWidget> _privacy;
QRect _privacyBadgeGeometry;
std::optional<HeaderData> _data; std::optional<HeaderData> _data;
std::unique_ptr<Ui::ImportantTooltip> _tooltip;
rpl::variable<bool> _tooltipShown = false;
QRect _contentGeometry;
Tooltip _tooltipType = {};
base::Timer _dateUpdateTimer; base::Timer _dateUpdateTimer;
bool _ignoreWindowMove = false; bool _ignoreWindowMove = false;
bool _privacyBadgeOver = false;
}; };

View file

@ -186,8 +186,8 @@ void ReplyArea::send(
session().api().sendMessage(std::move(message)); session().api().sendMessage(std::move(message));
_controls->clear();
finishSending(skipToast); finishSending(skipToast);
_controls->clear();
} }
void ReplyArea::sendVoice(VoiceToSend &&data) { void ReplyArea::sendVoice(VoiceToSend &&data) {
@ -276,8 +276,6 @@ void ReplyArea::sendInlineResult(
action.generateLocal = true; action.generateLocal = true;
session().api().sendInlineResult(bot, result, action, localMessageId); session().api().sendInlineResult(bot, result, action, localMessageId);
_controls->clear();
auto &bots = cRefRecentInlineBots(); auto &bots = cRefRecentInlineBots();
const auto index = bots.indexOf(bot); const auto index = bots.indexOf(bot);
if (index) { if (index) {
@ -290,11 +288,12 @@ void ReplyArea::sendInlineResult(
bot->session().local().writeRecentHashtagsAndBots(); bot->session().local().writeRecentHashtagsAndBots();
} }
finishSending(); finishSending();
_controls->clear();
} }
void ReplyArea::finishSending(bool skipToast) { void ReplyArea::finishSending(bool skipToast) {
_controls->hidePanelsAnimated(); _controls->hidePanelsAnimated();
_controller->wrap()->setFocus(); _controller->unfocusReply();
if (!skipToast) { if (!skipToast) {
_controller->uiShow()->showToast( _controller->uiShow()->showToast(
tr::lng_stories_reply_sent(tr::now)); tr::lng_stories_reply_sent(tr::now));

View file

@ -32,6 +32,7 @@ constexpr auto kSiblingFade = 0.5;
constexpr auto kSiblingFadeOver = 0.4; constexpr auto kSiblingFadeOver = 0.4;
constexpr auto kSiblingNameOpacity = 0.8; constexpr auto kSiblingNameOpacity = 0.8;
constexpr auto kSiblingNameOpacityOver = 1.; constexpr auto kSiblingNameOpacityOver = 1.;
constexpr auto kSiblingScaleOver = 0.05;
[[nodiscard]] StoryId LookupShownId( [[nodiscard]] StoryId LookupShownId(
const Data::StoriesSource &source, const Data::StoriesSource &source,
@ -325,6 +326,7 @@ SiblingView Sibling::view(const SiblingLayout &layout, float64 over) {
.namePosition = namePosition(layout, name), .namePosition = namePosition(layout, name),
.nameOpacity = (kSiblingNameOpacity * (1 - over) .nameOpacity = (kSiblingNameOpacity * (1 - over)
+ kSiblingNameOpacityOver * over), + kSiblingNameOpacityOver * over),
.scale = 1. + (over * kSiblingScaleOver),
}; };
} }

View file

@ -123,8 +123,8 @@ TextWithEntities View::captionText() const {
return _controller->captionText(); return _controller->captionText();
} }
void View::setCaptionExpanded(bool expanded) { bool View::skipCaption() const {
_controller->setCaptionExpanded(expanded); return _controller->skipCaption();
} }
void View::showFullCaption() { void View::showFullCaption() {

View file

@ -25,6 +25,7 @@ class Controller;
struct ContentLayout { struct ContentLayout {
QRect geometry; QRect geometry;
float64 fade = 0.; float64 fade = 0.;
float64 scale = 1.;
int radius = 0; int radius = 0;
bool headerOutside = false; bool headerOutside = false;
}; };
@ -39,6 +40,7 @@ struct SiblingView {
QImage name; QImage name;
QPoint namePosition; QPoint namePosition;
float64 nameOpacity = 0.; float64 nameOpacity = 0.;
float64 scale = 1.;
[[nodiscard]] bool valid() const { [[nodiscard]] bool valid() const {
return !image.isNull(); return !image.isNull();
@ -48,6 +50,9 @@ struct SiblingView {
} }
}; };
inline constexpr auto kCollapsedCaptionLines = 2;
inline constexpr auto kMaxShownCaptionLines = 4;
class View final { class View final {
public: public:
explicit View(not_null<Delegate*> delegate); explicit View(not_null<Delegate*> delegate);
@ -64,7 +69,7 @@ public:
[[nodiscard]] SiblingView sibling(SiblingType type) const; [[nodiscard]] SiblingView sibling(SiblingType type) const;
[[nodiscard]] Data::FileOrigin fileOrigin() const; [[nodiscard]] Data::FileOrigin fileOrigin() const;
[[nodiscard]] TextWithEntities captionText() const; [[nodiscard]] TextWithEntities captionText() const;
void setCaptionExpanded(bool expanded); [[nodiscard]] bool skipCaption() const;
void showFullCaption(); void showFullCaption();
void updatePlayback(const Player::TrackState &state); void updatePlayback(const Player::TrackState &state);

View file

@ -445,7 +445,8 @@ storiesSideSkip: 145px;
storiesCaptionFull: FlatLabel(defaultFlatLabel) { storiesCaptionFull: FlatLabel(defaultFlatLabel) {
style: mediaviewCaptionStyle; style: mediaviewCaptionStyle;
textFg: mediaviewCaptionFg; textFg: mediaviewCaptionFg;
minWidth: 360px; palette: mediaviewTextPalette;
minWidth: 36px;
} }
storiesComposeBg: groupCallMembersBg; storiesComposeBg: groupCallMembersBg;
storiesComposeBgOver: groupCallMembersBgOver; storiesComposeBgOver: groupCallMembersBgOver;
@ -570,7 +571,6 @@ storiesEmojiPan: EmojiPan(defaultEmojiPan) {
trendingHeaderFg: storiesComposeWhiteText; trendingHeaderFg: storiesComposeWhiteText;
trendingSubheaderFg: storiesComposeGrayText; trendingSubheaderFg: storiesComposeGrayText;
trendingUnreadFg: storiesComposeBlue; trendingUnreadFg: storiesComposeBlue;
trendingInstalled: icon {{ "chat/input_save", storiesComposeBlue }};
overBg: storiesComposeBgOver; overBg: storiesComposeBgOver;
pathBg: storiesComposeBgRipple; pathBg: storiesComposeBgRipple;
pathFg: storiesComposeBgOver; pathFg: storiesComposeBgOver;
@ -752,6 +752,20 @@ storiesComposeControls: ComposeControls(defaultComposeControls) {
statusFg: storiesComposeGrayText; statusFg: storiesComposeGrayText;
} }
premium: storiesComposePremium; premium: storiesComposePremium;
boxField: InputField(defaultInputField) {
textBg: transparent;
textFg: groupCallMembersFg;
placeholderFg: groupCallMemberNotJoinedStatus;
placeholderFgActive: groupCallMemberNotJoinedStatus;
placeholderFgError: groupCallMemberNotJoinedStatus;
borderFg: inputBorderFg;
borderFgActive: groupCallMemberInactiveStatus;
borderFgError: activeLineFgError;
menu: storiesPopupMenu;
}
} }
storiesViewsMenu: PopupMenu(storiesPopupMenuWithIcons) { storiesViewsMenu: PopupMenu(storiesPopupMenuWithIcons) {
scrollPadding: margins(0px, 6px, 0px, 4px); scrollPadding: margins(0px, 6px, 0px, 4px);
@ -891,3 +905,9 @@ storiesVolumeSlider: MediaSlider {
seekSize: size(12px, 12px); seekSize: size(12px, 12px);
duration: mediaviewOverDuration; duration: mediaviewOverDuration;
} }
storiesInfoTooltipLabel: defaultImportantTooltipLabel;
storiesInfoTooltip: defaultImportantTooltip;
storiesInfoTooltipMaxWidth: 360px;
storiesCaptionPullThreshold: 50px;
storiesShowMorePadding: margins(6px, 4px, 6px, 4px);
storiesShowMoreFont: semiboldFont;

View file

@ -21,7 +21,8 @@ namespace {
using namespace Ui::GL; using namespace Ui::GL;
constexpr auto kRadialLoadingOffset = 4; constexpr auto kNotchOffset = 4;
constexpr auto kRadialLoadingOffset = kNotchOffset + 4;
constexpr auto kThemePreviewOffset = kRadialLoadingOffset + 4; constexpr auto kThemePreviewOffset = kRadialLoadingOffset + 4;
constexpr auto kDocumentBubbleOffset = kThemePreviewOffset + 4; constexpr auto kDocumentBubbleOffset = kThemePreviewOffset + 4;
constexpr auto kSaveMsgOffset = kDocumentBubbleOffset + 4; constexpr auto kSaveMsgOffset = kDocumentBubbleOffset + 4;
@ -129,7 +130,7 @@ OverlayWidget::RendererGL::RendererGL(not_null<OverlayWidget*> owner)
void OverlayWidget::RendererGL::init( void OverlayWidget::RendererGL::init(
not_null<QOpenGLWidget*> widget, not_null<QOpenGLWidget*> widget,
QOpenGLFunctions &f) { QOpenGLFunctions &f) {
constexpr auto kQuads = 8; constexpr auto kQuads = 9;
constexpr auto kQuadVertices = kQuads * 4; constexpr auto kQuadVertices = kQuads * 4;
constexpr auto kQuadValues = kQuadVertices * 4; constexpr auto kQuadValues = kQuadVertices * 4;
constexpr auto kControlsValues = kControlsCount * kControlValues; constexpr auto kControlsValues = kControlsCount * kControlValues;
@ -291,6 +292,28 @@ bool OverlayWidget::RendererGL::handleHideWorkaround(QOpenGLFunctions &f) {
void OverlayWidget::RendererGL::paintBackground() { void OverlayWidget::RendererGL::paintBackground() {
_contentBuffer->bind(); _contentBuffer->bind();
if (const auto notch = _owner->topNotchSkip()) {
const auto top = transformRect(QRect(0, 0, _owner->width(), notch));
const GLfloat coords[] = {
top.left(), top.top(),
top.right(), top.top(),
top.right(), top.bottom(),
top.left(), top.bottom(),
};
const auto offset = kNotchOffset;
_contentBuffer->write(
offset * 4 * sizeof(GLfloat),
coords,
sizeof(coords));
_fillProgram->bind();
_fillProgram->setUniformValue("viewport", _uniformViewport);
FillRectangle(
*_f,
&*_fillProgram,
offset,
QColor(0, 0, 0));
}
} }
void OverlayWidget::RendererGL::paintTransformedVideoFrame( void OverlayWidget::RendererGL::paintTransformedVideoFrame(
@ -465,7 +488,9 @@ void OverlayWidget::RendererGL::paintTransformedContent(
not_null<QOpenGLShaderProgram*> program, not_null<QOpenGLShaderProgram*> program,
ContentGeometry geometry, ContentGeometry geometry,
bool fillTransparentBackground) { bool fillTransparentBackground) {
const auto rect = transformRect(geometry.rect); const auto rect = scaleRect(
transformRect(geometry.rect),
geometry.scale);
const auto centerx = rect.x() + rect.width() / 2; const auto centerx = rect.x() + rect.width() / 2;
const auto centery = rect.y() + rect.height() / 2; const auto centery = rect.y() + rect.height() / 2;
const auto rsin = float(std::sin(geometry.rotation * M_PI / 180.)); const auto rsin = float(std::sin(geometry.rotation * M_PI / 180.));
@ -1043,4 +1068,17 @@ Rect OverlayWidget::RendererGL::transformRect(const QRect &raster) const {
return TransformRect(Rect(raster), _viewport, _factor); return TransformRect(Rect(raster), _viewport, _factor);
} }
Rect OverlayWidget::RendererGL::scaleRect(
const Rect &unscaled,
float64 scale) const {
const auto added = scale - 1.;
const auto addw = unscaled.width() * added;
const auto addh = unscaled.height() * added;
return Rect(
unscaled.x() - addw / 2,
unscaled.y() - addh / 2,
unscaled.width() + addw,
unscaled.height() + addh);
}
} // namespace Media::View } // namespace Media::View

View file

@ -97,6 +97,9 @@ private:
[[nodiscard]] Ui::GL::Rect transformRect(const QRectF &raster) const; [[nodiscard]] Ui::GL::Rect transformRect(const QRectF &raster) const;
[[nodiscard]] Ui::GL::Rect transformRect( [[nodiscard]] Ui::GL::Rect transformRect(
const Ui::GL::Rect &raster) const; const Ui::GL::Rect &raster) const;
[[nodiscard]] Ui::GL::Rect scaleRect(
const Ui::GL::Rect &unscaled,
float64 scale) const;
void uploadTexture( void uploadTexture(
GLint internalformat, GLint internalformat,

View file

@ -45,6 +45,12 @@ void OverlayWidget::RendererSW::paintBackground() {
for (const auto &rect : region) { for (const auto &rect : region) {
_p->fillRect(rect, bg); _p->fillRect(rect, bg);
} }
if (const auto notch = _owner->topNotchSkip()) {
const auto top = QRect(0, 0, _owner->width(), notch);
if (const auto black = top.intersected(_clipOuter); !black.isEmpty()) {
_p->fillRect(black, Qt::black);
}
}
_p->setCompositionMode(m); _p->setCompositionMode(m);
} }
@ -101,8 +107,8 @@ void OverlayWidget::RendererSW::paintTransformedStaticContent(
} }
void OverlayWidget::RendererSW::paintControlsFade( void OverlayWidget::RendererSW::paintControlsFade(
QRect content, QRect content,
const ContentGeometry &geometry) { const ContentGeometry &geometry) {
auto opacity = geometry.controlsOpacity; auto opacity = geometry.controlsOpacity;
if (geometry.fade > 0.) { if (geometry.fade > 0.) {
_p->setOpacity(geometry.fade); _p->setOpacity(geometry.fade);

View file

@ -587,6 +587,16 @@ OverlayWidget::OverlayWidget()
update(); update();
}, lifetime()); }, lifetime());
_helper->topNotchSkipValue(
) | rpl::start_with_next([=](int notch) {
if (_topNotchSize != notch) {
_topNotchSize = notch;
if (_fullscreen) {
updateControlsGeometry();
}
}
}, lifetime());
_window->setTitle(tr::lng_mediaview_title(tr::now)); _window->setTitle(tr::lng_mediaview_title(tr::now));
_window->setTitleStyle(st::mediaviewTitle); _window->setTitleStyle(st::mediaviewTitle);
@ -673,7 +683,11 @@ void OverlayWidget::showSaveMsgToastWith(
const auto h = st::mediaviewSaveMsgStyle.font->height const auto h = st::mediaviewSaveMsgStyle.font->height
+ st::mediaviewSaveMsgPadding.top() + st::mediaviewSaveMsgPadding.top()
+ st::mediaviewSaveMsgPadding.bottom(); + st::mediaviewSaveMsgPadding.bottom();
_saveMsg = QRect((width() - w) / 2, (height() - h) / 2, w, h); _saveMsg = QRect(
(width() - w) / 2,
_minUsedTop + (_maxUsedHeight - h) / 2,
w,
h);
const auto callback = [=](float64 value) { const auto callback = [=](float64 value) {
updateSaveMsg(); updateSaveMsg();
if (!_saveMsgAnimation.animating()) { if (!_saveMsgAnimation.animating()) {
@ -703,7 +717,7 @@ void OverlayWidget::setupWindow() {
&& _streamed->controls && _streamed->controls
&& _streamed->controls->dragging())) { && _streamed->controls->dragging())) {
return Flag::None | Flag(0); return Flag::None | Flag(0);
} else if ((_w > _widget->width() || _h > _widget->height()) } else if ((_w > _widget->width() || _h > _maxUsedHeight)
&& (widgetPoint.y() > st::mediaviewHeaderTop) && (widgetPoint.y() > st::mediaviewHeaderTop)
&& QRect(_x, _y, _w, _h).contains(widgetPoint)) { && QRect(_x, _y, _w, _h).contains(widgetPoint)) {
return Flag::None | Flag(0); return Flag::None | Flag(0);
@ -940,8 +954,14 @@ void OverlayWidget::updateGeometryToScreen(bool inMove) {
void OverlayWidget::updateControlsGeometry() { void OverlayWidget::updateControlsGeometry() {
updateNavigationControlsGeometry(); updateNavigationControlsGeometry();
_saveMsg.moveTo((width() - _saveMsg.width()) / 2, (height() - _saveMsg.height()) / 2); _saveMsg.moveTo(
_photoRadialRect = QRect(QPoint((width() - st::radialSize.width()) / 2, (height() - st::radialSize.height()) / 2), st::radialSize); (width() - _saveMsg.width()) / 2,
_minUsedTop + (_maxUsedHeight - _saveMsg.height()) / 2);
_photoRadialRect = QRect(
QPoint(
(width() - st::radialSize.width()) / 2,
_minUsedTop + (_maxUsedHeight - st::radialSize.height()) / 2),
st::radialSize);
const auto bottom = st::mediaviewShadowBottom.height(); const auto bottom = st::mediaviewShadowBottom.height();
const auto top = st::mediaviewShadowTop.size(); const auto top = st::mediaviewShadowTop.size();
@ -960,6 +980,9 @@ void OverlayWidget::updateControlsGeometry() {
} }
void OverlayWidget::updateNavigationControlsGeometry() { void OverlayWidget::updateNavigationControlsGeometry() {
_minUsedTop = topNotchSkip();
_maxUsedHeight = height() - _minUsedTop;
const auto overRect = QRect( const auto overRect = QRect(
QPoint(), QPoint(),
QSize(st::mediaviewIconOver, st::mediaviewIconOver)); QSize(st::mediaviewIconOver, st::mediaviewIconOver));
@ -969,14 +992,22 @@ void OverlayWidget::updateNavigationControlsGeometry() {
const auto navSkip = st::mediaviewHeaderTop; const auto navSkip = st::mediaviewHeaderTop;
const auto xLeft = _stories ? (_x - navSize) : 0; const auto xLeft = _stories ? (_x - navSize) : 0;
const auto xRight = _stories ? (_x + _w) : (width() - navSize); const auto xRight = _stories ? (_x + _w) : (width() - navSize);
_leftNav = QRect(xLeft, navSkip, navSize, height() - 2 * navSkip); _leftNav = QRect(
xLeft,
_minUsedTop + navSkip,
navSize,
_maxUsedHeight - 2 * navSkip);
_leftNavOver = _stories _leftNavOver = _stories
? QRect() ? QRect()
: style::centerrect(_leftNav, overRect); : style::centerrect(_leftNav, overRect);
_leftNavIcon = style::centerrect( _leftNavIcon = style::centerrect(
_leftNav, _leftNav,
_stories ? st::storiesLeft : st::mediaviewLeft); _stories ? st::storiesLeft : st::mediaviewLeft);
_rightNav = QRect(xRight, navSkip, navSize, height() - 2 * navSkip); _rightNav = QRect(
xRight,
_minUsedTop + navSkip,
navSize,
_maxUsedHeight - 2 * navSkip);
_rightNavOver = _stories _rightNavOver = _stories
? QRect() ? QRect()
: style::centerrect(_rightNav, overRect); : style::centerrect(_rightNav, overRect);
@ -1213,7 +1244,7 @@ void OverlayWidget::updateControls() {
if (_document && documentBubbleShown()) { if (_document && documentBubbleShown()) {
_docRect = QRect( _docRect = QRect(
(width() - st::mediaviewFileSize.width()) / 2, (width() - st::mediaviewFileSize.width()) / 2,
(height() - st::mediaviewFileSize.height()) / 2, _minUsedTop + (_maxUsedHeight - st::mediaviewFileSize.height()) / 2,
st::mediaviewFileSize.width(), st::mediaviewFileSize.width(),
st::mediaviewFileSize.height()); st::mediaviewFileSize.height());
_docIconRect = QRect( _docIconRect = QRect(
@ -1244,7 +1275,7 @@ void OverlayWidget::updateControls() {
} else { } else {
_docIconRect = QRect( _docIconRect = QRect(
(width() - st::mediaviewFileIconSize) / 2, (width() - st::mediaviewFileIconSize) / 2,
(height() - st::mediaviewFileIconSize) / 2, _minUsedTop + (_maxUsedHeight - st::mediaviewFileIconSize) / 2,
st::mediaviewFileIconSize, st::mediaviewFileIconSize,
st::mediaviewFileIconSize); st::mediaviewFileIconSize);
_docDownload->hide(); _docDownload->hide();
@ -1263,7 +1294,8 @@ void OverlayWidget::updateControls() {
_shareVisible = story && story->canShare(); _shareVisible = story && story->canShare();
_rotateVisible = !_themePreviewShown && !story; _rotateVisible = !_themePreviewShown && !story;
const auto navRect = [&](int i) { const auto navRect = [&](int i) {
return QRect(width() - st::mediaviewIconSize.width() * i, return QRect(
width() - st::mediaviewIconSize.width() * i,
height() - st::mediaviewIconSize.height(), height() - st::mediaviewIconSize.height(),
st::mediaviewIconSize.width(), st::mediaviewIconSize.width(),
st::mediaviewIconSize.height()); st::mediaviewIconSize.height());
@ -1302,12 +1334,27 @@ void OverlayWidget::updateControls() {
}(); }();
_dateText = d.isValid() ? Ui::FormatDateTime(d) : QString(); _dateText = d.isValid() ? Ui::FormatDateTime(d) : QString();
if (!_fromName.isEmpty()) { if (!_fromName.isEmpty()) {
_fromNameLabel.setText(st::mediaviewTextStyle, _fromName, Ui::NameTextOptions()); _fromNameLabel.setText(
_nameNav = QRect(st::mediaviewTextLeft, height() - st::mediaviewTextTop, qMin(_fromNameLabel.maxWidth(), width() / 3), st::mediaviewFont->height); st::mediaviewTextStyle,
_dateNav = QRect(st::mediaviewTextLeft + _nameNav.width() + st::mediaviewTextSkip, height() - st::mediaviewTextTop, st::mediaviewFont->width(_dateText), st::mediaviewFont->height); _fromName,
Ui::NameTextOptions());
_nameNav = QRect(
st::mediaviewTextLeft,
height() - st::mediaviewTextTop,
qMin(_fromNameLabel.maxWidth(), width() / 3),
st::mediaviewFont->height);
_dateNav = QRect(
st::mediaviewTextLeft + _nameNav.width() + st::mediaviewTextSkip,
height() - st::mediaviewTextTop,
st::mediaviewFont->width(_dateText),
st::mediaviewFont->height);
} else { } else {
_nameNav = QRect(); _nameNav = QRect();
_dateNav = QRect(st::mediaviewTextLeft, height() - st::mediaviewTextTop, st::mediaviewFont->width(_dateText), st::mediaviewFont->height); _dateNav = QRect(
st::mediaviewTextLeft,
height() - st::mediaviewTextTop,
st::mediaviewFont->width(_dateText),
st::mediaviewFont->height);
} }
updateHeader(); updateHeader();
refreshNavVisibility(); refreshNavVisibility();
@ -1336,6 +1383,10 @@ void OverlayWidget::resizeCenteredControls() {
} }
void OverlayWidget::refreshCaptionGeometry() { void OverlayWidget::refreshCaptionGeometry() {
_caption.updateSkipBlock(0, 0);
_captionShowMoreWidth = 0;
_captionSkipBlockWidth = 0;
if (_caption.isEmpty()) { if (_caption.isEmpty()) {
_captionRect = QRect(); _captionRect = QRect();
return; return;
@ -1361,32 +1412,28 @@ void OverlayWidget::refreshCaptionGeometry() {
- st::mediaviewCaptionPadding.left() - st::mediaviewCaptionPadding.left()
- st::mediaviewCaptionPadding.right()), - st::mediaviewCaptionPadding.right()),
_caption.maxWidth()); _caption.maxWidth());
const auto maxExpandedOuterHeight = (_stories
? (_h - st::storiesShadowTop.height())
: height());
const auto maxCollapsedOuterHeight = !_stories
? (height() / 4)
: (_h / 3);
const auto maxExpandedHeight = maxExpandedOuterHeight
- st::mediaviewCaptionPadding.top()
- st::mediaviewCaptionPadding.bottom();
const auto maxCollapsedHeight = maxCollapsedOuterHeight
- st::mediaviewCaptionPadding.top()
- st::mediaviewCaptionPadding.bottom();
const auto lineHeight = st::mediaviewCaptionStyle.font->height; const auto lineHeight = st::mediaviewCaptionStyle.font->height;
const auto wantedHeight = _caption.countHeight(captionWidth); const auto wantedHeight = _caption.countHeight(captionWidth);
const auto maxHeight = _captionExpanded const auto maxHeight = !_stories
? maxExpandedHeight ? (_maxUsedHeight / 4)
: maxCollapsedHeight; : (wantedHeight > lineHeight * Stories::kMaxShownCaptionLines)
? (lineHeight * Stories::kCollapsedCaptionLines)
: wantedHeight;
const auto captionHeight = std::min( const auto captionHeight = std::min(
wantedHeight, wantedHeight,
(maxHeight / lineHeight) * lineHeight); (maxHeight / lineHeight) * lineHeight);
_captionFitsIfExpanded = _stories if (_stories && captionHeight < wantedHeight) {
&& (wantedHeight <= maxExpandedHeight); const auto padding = st::storiesShowMorePadding;
_captionShownFull = (wantedHeight <= maxCollapsedHeight); _captionShowMoreWidth = st::storiesShowMoreFont->width(
if (_captionShownFull && _captionExpanded && _stories) { tr::lng_stories_show_more(tr::now));
_captionExpanded = false; _captionSkipBlockWidth = _captionShowMoreWidth
_stories->setCaptionExpanded(false); + padding.left()
+ padding.right()
- st::mediaviewCaptionPadding.right();
const auto skiph = st::storiesShowMoreFont->height
+ padding.bottom()
- st::mediaviewCaptionPadding.bottom();
_caption.updateSkipBlock(_captionSkipBlockWidth, skiph);
} }
_captionRect = QRect( _captionRect = QRect(
(width() - captionWidth) / 2, (width() - captionWidth) / 2,
@ -1748,11 +1795,13 @@ OverlayWidget::ContentGeometry OverlayWidget::contentGeometry() const {
} }
OverlayWidget::ContentGeometry OverlayWidget::storiesContentGeometry( OverlayWidget::ContentGeometry OverlayWidget::storiesContentGeometry(
const Stories::ContentLayout &layout) const { const Stories::ContentLayout &layout,
float64 scale) const {
return { return {
.rect = QRectF(layout.geometry), .rect = QRectF(layout.geometry),
.controlsOpacity = kStoriesControlsOpacity, .controlsOpacity = kStoriesControlsOpacity,
.fade = layout.fade, .fade = layout.fade,
.scale = scale,
.roundRadius = layout.radius, .roundRadius = layout.radius,
.topShadowShown = !layout.headerOutside, .topShadowShown = !layout.headerOutside,
}; };
@ -1777,7 +1826,7 @@ void OverlayWidget::recountSkipTop() {
? height() ? height()
: (_streamed->controls->y() - st::mediaviewCaptionPadding.bottom()); : (_streamed->controls->y() - st::mediaviewCaptionPadding.bottom());
const auto skipHeightBottom = (height() - bottom); const auto skipHeightBottom = (height() - bottom);
_skipTop = std::min( _skipTop = _minUsedTop + std::min(
std::max( std::max(
st::mediaviewCaptionMargin.height(), st::mediaviewCaptionMargin.height(),
height() - _height - skipHeightBottom), height() - _height - skipHeightBottom),
@ -1785,7 +1834,7 @@ void OverlayWidget::recountSkipTop() {
_availableHeight = height() - skipHeightBottom - _skipTop; _availableHeight = height() - skipHeightBottom - _skipTop;
if (_fullScreenVideo && skipHeightBottom > 0 && _width > 0) { if (_fullScreenVideo && skipHeightBottom > 0 && _width > 0) {
const auto h = width() * _height / _width; const auto h = width() * _height / _width;
const auto topAllFit = height() - skipHeightBottom - h; const auto topAllFit = _maxUsedHeight - skipHeightBottom - h;
if (_skipTop > topAllFit) { if (_skipTop > topAllFit) {
_skipTop = std::max(topAllFit, 0); _skipTop = std::max(topAllFit, 0);
} }
@ -1818,12 +1867,12 @@ void OverlayWidget::resizeContentByScreenSize() {
}; };
if (_width > 0 && _height > 0) { if (_width > 0 && _height > 0) {
_zoomToDefault = countZoomFor(availableWidth, _availableHeight); _zoomToDefault = countZoomFor(availableWidth, _availableHeight);
_zoomToScreen = countZoomFor(width(), height()); _zoomToScreen = countZoomFor(width(), _maxUsedHeight);
} else { } else {
_zoomToDefault = _zoomToScreen = 0; _zoomToDefault = _zoomToScreen = 0;
} }
const auto usew = _fullScreenVideo ? width() : availableWidth; const auto usew = _fullScreenVideo ? width() : availableWidth;
const auto useh = _fullScreenVideo ? height() : _availableHeight; const auto useh = _fullScreenVideo ? _maxUsedHeight : _availableHeight;
if ((_width > usew) || (_height > useh) || _fullScreenVideo) { if ((_width > usew) || (_height > useh) || _fullScreenVideo) {
const auto use = _fullScreenVideo ? _zoomToScreen : _zoomToDefault; const auto use = _fullScreenVideo ? _zoomToScreen : _zoomToDefault;
_zoom = kZoomToScreenLevel; _zoom = kZoomToScreenLevel;
@ -3156,6 +3205,10 @@ void OverlayWidget::show(OpenRequest request) {
} }
} }
} }
if (isHidden() || isMinimized()) {
// Count top notch on macOS before counting geometry.
_helper->beforeShow(_fullscreen);
}
if (photo) { if (photo) {
if (contextItem && contextPeer) { if (contextItem && contextPeer) {
return; return;
@ -3444,7 +3497,6 @@ void OverlayWidget::updateThemePreviewGeometry() {
} }
void OverlayWidget::displayFinished(anim::activation activation) { void OverlayWidget::displayFinished(anim::activation activation) {
_captionExpanded = _captionFitsIfExpanded = _captionShownFull = false;
updateControls(); updateControls();
if (isHidden()) { if (isHidden()) {
_helper->beforeShow(_fullscreen); _helper->beforeShow(_fullscreen);
@ -3646,6 +3698,7 @@ bool OverlayWidget::createStreamingObjects() {
_streamed->instance.setPriority(kOverlayLoaderPriority); _streamed->instance.setPriority(kOverlayLoaderPriority);
_streamed->instance.lockPlayer(); _streamed->instance.lockPlayer();
_streamed->withSound = _document _streamed->withSound = _document
&& !_document->isSilentVideo()
&& (_document->isAudioFile() && (_document->isAudioFile()
|| _document->isVideoFile() || _document->isVideoFile()
|| _document->isVoiceMessage() || _document->isVoiceMessage()
@ -3986,12 +4039,14 @@ void OverlayWidget::restartAtSeekPosition(crl::time position) {
}; };
if (!_streamed->withSound) { if (!_streamed->withSound) {
options.mode = Streaming::Mode::Video; options.mode = Streaming::Mode::Video;
options.loop = true; options.loop = !_stories;
} else { } else {
Assert(_document != nullptr); Assert(_document != nullptr);
const auto messageId = _message ? _message->fullId() : FullMsgId(); const auto messageId = _message ? _message->fullId() : FullMsgId();
options.audioId = AudioMsgId(_document, messageId); options.audioId = AudioMsgId(_document, messageId);
options.speed = Core::App().settings().videoPlaybackSpeed(); options.speed = _stories
? Core::App().settings().videoPlaybackSpeed()
: 1.;
if (_pip) { if (_pip) {
_pip = nullptr; _pip = nullptr;
} }
@ -4059,7 +4114,7 @@ void OverlayWidget::playbackControlsSpeedChanged(float64 speed) {
Core::App().settings().setVideoPlaybackSpeed(speed); Core::App().settings().setVideoPlaybackSpeed(speed);
Core::App().saveSettingsDelayed(); Core::App().saveSettingsDelayed();
} }
if (_streamed && _streamed->controls) { if (_streamed && _streamed->controls && !_stories) {
DEBUG_LOG(("Media playback speed: %1 to _streamed.").arg(speed)); DEBUG_LOG(("Media playback speed: %1 to _streamed.").arg(speed));
_streamed->instance.setSpeed(speed); _streamed->instance.setSpeed(speed);
} }
@ -4240,6 +4295,14 @@ void OverlayWidget::storiesVolumeChangeFinished() {
playbackControlsVolumeChangeFinished(); playbackControlsVolumeChangeFinished();
} }
int OverlayWidget::topNotchSkip() const {
return _fullscreen ? _topNotchSize : 0;
}
int OverlayWidget::storiesTopNotchSkip() {
return topNotchSkip();
}
void OverlayWidget::playbackToggleFullScreen() { void OverlayWidget::playbackToggleFullScreen() {
Expects(_streamed != nullptr); Expects(_streamed != nullptr);
@ -4400,7 +4463,7 @@ void OverlayWidget::paint(not_null<Renderer*> renderer) {
const auto paint = [&](const SiblingView &view, int index) { const auto paint = [&](const SiblingView &view, int index) {
renderer->paintTransformedStaticContent( renderer->paintTransformedStaticContent(
view.image, view.image,
storiesContentGeometry(view.layout), storiesContentGeometry(view.layout, view.scale),
false, // semi-transparent false, // semi-transparent
false, // fill transparent background false, // fill transparent background
index); index);
@ -4443,7 +4506,8 @@ void OverlayWidget::paint(not_null<Renderer*> renderer) {
if (!_stories) { if (!_stories) {
renderer->paintFooter(footerGeometry(), opacity); renderer->paintFooter(footerGeometry(), opacity);
} }
if (!_caption.isEmpty()) { if (!_caption.isEmpty()
&& (!_stories || !_stories->skipCaption())) {
renderer->paintCaption(captionGeometry(), opacity); renderer->paintCaption(captionGeometry(), opacity);
} }
if (_groupThumbs) { if (_groupThumbs) {
@ -4853,6 +4917,7 @@ void OverlayWidget::paintCaptionContent(
} }
if (inner.intersects(clip)) { if (inner.intersects(clip)) {
p.setPen(st::mediaviewCaptionFg); p.setPen(st::mediaviewCaptionFg);
const auto lineHeight = st::mediaviewCaptionStyle.font->height;
_caption.draw(p, { _caption.draw(p, {
.position = inner.topLeft(), .position = inner.topLeft(),
.availableWidth = inner.width(), .availableWidth = inner.width(),
@ -4860,8 +4925,31 @@ void OverlayWidget::paintCaptionContent(
.spoiler = Ui::Text::DefaultSpoilerCache(), .spoiler = Ui::Text::DefaultSpoilerCache(),
.pausedEmoji = On(PowerSaving::kEmojiChat), .pausedEmoji = On(PowerSaving::kEmojiChat),
.pausedSpoiler = On(PowerSaving::kChatSpoiler), .pausedSpoiler = On(PowerSaving::kChatSpoiler),
.elisionLines = inner.height() / st::mediaviewCaptionStyle.font->height, .elisionLines = inner.height() / lineHeight,
.elisionRemoveFromEnd = _captionSkipBlockWidth,
}); });
if (_captionShowMoreWidth > 0) {
const auto padding = st::storiesShowMorePadding;
const auto showMoreLeft = outer.x()
+ outer.width()
- padding.right()
- _captionShowMoreWidth;
const auto showMoreTop = outer.y()
+ outer.height()
- padding.bottom()
- st::storiesShowMoreFont->height;
const auto underline = _captionExpandLink
&& ClickHandler::showAsActive(_captionExpandLink);
p.setFont(underline
? st::storiesShowMoreFont->underline()
: st::storiesShowMoreFont);
p.drawTextLeft(
showMoreLeft,
showMoreTop,
width(),
tr::lng_stories_show_more(tr::now));
}
} }
} }
@ -4960,7 +5048,9 @@ void OverlayWidget::handleKeyPress(not_null<QKeyEvent*> e) {
if (_controlsHideTimer.isActive()) { if (_controlsHideTimer.isActive()) {
activateControls(); activateControls();
} }
moveToNext(1); if (!moveToNext(1) && _stories) {
storiesClose();
}
} else if (ctrl) { } else if (ctrl) {
if (key == Qt::Key_Plus if (key == Qt::Key_Plus
|| key == Qt::Key_Equal || key == Qt::Key_Equal
@ -4978,20 +5068,23 @@ void OverlayWidget::handleKeyPress(not_null<QKeyEvent*> e) {
void OverlayWidget::handleWheelEvent(not_null<QWheelEvent*> e) { void OverlayWidget::handleWheelEvent(not_null<QWheelEvent*> e) {
constexpr auto step = int(QWheelEvent::DefaultDeltasPerStep); constexpr auto step = int(QWheelEvent::DefaultDeltasPerStep);
const auto acceptForJump = !_stories
&& ((e->source() == Qt::MouseEventNotSynthesized)
|| (e->source() == Qt::MouseEventSynthesizedBySystem));
_verticalWheelDelta += e->angleDelta().y(); _verticalWheelDelta += e->angleDelta().y();
while (qAbs(_verticalWheelDelta) >= step) { while (qAbs(_verticalWheelDelta) >= step) {
if (_verticalWheelDelta < 0) { if (_verticalWheelDelta < 0) {
_verticalWheelDelta += step; _verticalWheelDelta += step;
if (e->modifiers().testFlag(Qt::ControlModifier)) { if (e->modifiers().testFlag(Qt::ControlModifier)) {
zoomOut(); zoomOut();
} else if (e->source() == Qt::MouseEventNotSynthesized) { } else if (acceptForJump) {
moveToNext(1); moveToNext(1);
} }
} else { } else {
_verticalWheelDelta -= step; _verticalWheelDelta -= step;
if (e->modifiers().testFlag(Qt::ControlModifier)) { if (e->modifiers().testFlag(Qt::ControlModifier)) {
zoomIn(); zoomIn();
} else if (e->source() == Qt::MouseEventNotSynthesized) { } else if (acceptForJump) {
moveToNext(-1); moveToNext(-1);
} }
} }
@ -5366,7 +5459,7 @@ bool OverlayWidget::handleDoubleClick(
void OverlayWidget::snapXY() { void OverlayWidget::snapXY() {
auto xmin = width() - _w, xmax = 0; auto xmin = width() - _w, xmax = 0;
auto ymin = height() - _h, ymax = 0; auto ymin = height() - _h, ymax = _minUsedTop;
accumulate_min(xmin, (width() - _w) / 2); accumulate_min(xmin, (width() - _w) / 2);
accumulate_max(xmax, (width() - _w) / 2); accumulate_max(xmax, (width() - _w) / 2);
accumulate_min(ymin, _skipTop + (_availableHeight - _h) / 2); accumulate_min(ymin, _skipTop + (_availableHeight - _h) / 2);
@ -5390,7 +5483,7 @@ void OverlayWidget::handleMouseMove(QPoint position) {
>= QApplication::startDragDistance())) { >= QApplication::startDragDistance())) {
_dragging = QRect(_x, _y, _w, _h).contains(_mStart) ? 1 : -1; _dragging = QRect(_x, _y, _w, _h).contains(_mStart) ? 1 : -1;
if (_dragging > 0) { if (_dragging > 0) {
if (_w > width() || _h > height()) { if (_w > width() || _h > _maxUsedHeight) {
setCursor(style::cur_sizeall); setCursor(style::cur_sizeall);
} else { } else {
setCursor(style::cur_default); setCursor(style::cur_default);
@ -5486,9 +5579,13 @@ void OverlayWidget::updateOver(QPoint pos) {
lnk = textState.link; lnk = textState.link;
lnkhost = this; lnkhost = this;
} else if (_captionRect.contains(pos)) { } else if (_captionRect.contains(pos)) {
auto textState = _caption.getState(pos - _captionRect.topLeft(), _captionRect.width()); auto request = Ui::Text::StateRequestElided();
const auto lineHeight = st::mediaviewCaptionStyle.font->height;
request.lines = _captionRect.height() / lineHeight;
request.removeFromEnd = _captionSkipBlockWidth;
auto textState = _caption.getStateElided(pos - _captionRect.topLeft(), _captionRect.width(), request);
lnk = textState.link; lnk = textState.link;
if (_stories && !_captionShownFull && !lnk) { if (_stories && !lnk) {
lnk = ensureCaptionExpandLink(); lnk = ensureCaptionExpandLink();
} }
lnkhost = this; lnkhost = this;
@ -5567,19 +5664,7 @@ void OverlayWidget::updateOver(QPoint pos) {
ClickHandlerPtr OverlayWidget::ensureCaptionExpandLink() { ClickHandlerPtr OverlayWidget::ensureCaptionExpandLink() {
if (!_captionExpandLink) { if (!_captionExpandLink) {
const auto toggle = crl::guard(_widget, [=] { const auto toggle = crl::guard(_widget, [=] {
if (!_stories) { if (_stories) {
return;
} else if (_captionExpanded) {
_captionExpanded = false;
_stories->setCaptionExpanded(false);
refreshCaptionGeometry();
update();
} else if (_captionFitsIfExpanded) {
_captionExpanded = true;
_stories->setCaptionExpanded(true);
refreshCaptionGeometry();
update();
} else {
_stories->showFullCaption(); _stories->showFullCaption();
} }
}); });

View file

@ -180,6 +180,7 @@ private:
// Stories. // Stories.
qreal fade = 0.; qreal fade = 0.;
qreal scale = 1.;
int bottomShadowSkip = 0; int bottomShadowSkip = 0;
int roundRadius = 0; int roundRadius = 0;
bool topShadowShown = false; bool topShadowShown = false;
@ -243,6 +244,7 @@ private:
void playbackResumeOnCall(); void playbackResumeOnCall();
void playbackPauseMusic(); void playbackPauseMusic();
void switchToPip(); void switchToPip();
[[nodiscard]] int topNotchSkip() const;
not_null<Ui::RpWidget*> storiesWrap() override; not_null<Ui::RpWidget*> storiesWrap() override;
std::shared_ptr<ChatHelpers::Show> storiesShow() override; std::shared_ptr<ChatHelpers::Show> storiesShow() override;
@ -264,6 +266,7 @@ private:
void storiesVolumeToggle() override; void storiesVolumeToggle() override;
void storiesVolumeChanged(float64 volume) override; void storiesVolumeChanged(float64 volume) override;
void storiesVolumeChangeFinished() override; void storiesVolumeChangeFinished() override;
int storiesTopNotchSkip() override;
void hideControls(bool force = false); void hideControls(bool force = false);
void subscribeToScreenGeometry(); void subscribeToScreenGeometry();
@ -419,7 +422,8 @@ private:
[[nodiscard]] QRect finalContentRect() const; [[nodiscard]] QRect finalContentRect() const;
[[nodiscard]] ContentGeometry contentGeometry() const; [[nodiscard]] ContentGeometry contentGeometry() const;
[[nodiscard]] ContentGeometry storiesContentGeometry( [[nodiscard]] ContentGeometry storiesContentGeometry(
const Stories::ContentLayout &layout) const; const Stories::ContentLayout &layout,
float64 scale = 1.) const;
void updateContentRect(); void updateContentRect();
void contentSizeChanged(); void contentSizeChanged();
@ -585,14 +589,16 @@ private:
Ui::Text::String _caption; Ui::Text::String _caption;
QRect _captionRect; QRect _captionRect;
ClickHandlerPtr _captionExpandLink; ClickHandlerPtr _captionExpandLink;
bool _captionShownFull = false; int _captionShowMoreWidth = 0;
bool _captionFitsIfExpanded = false; int _captionSkipBlockWidth = 0;
bool _captionExpanded = false;
int _topNotchSize = 0;
int _width = 0; int _width = 0;
int _height = 0; int _height = 0;
int _skipTop = 0; int _skipTop = 0;
int _availableHeight = 0; int _availableHeight = 0;
int _minUsedTop = 0; // Geometry without top notch on macOS.
int _maxUsedHeight = 0;
int _x = 0, _y = 0, _w = 0, _h = 0; int _x = 0, _y = 0, _w = 0, _h = 0;
int _xStart = 0, _yStart = 0; int _xStart = 0, _yStart = 0;
int _zoom = 0; // < 0 - out, 0 - none, > 0 - in int _zoom = 0; // < 0 - out, 0 - none, > 0 - in

View file

@ -700,7 +700,7 @@ Voice::Voice(
updateName(); updateName();
const auto dateText = Ui::Text::Link( const auto dateText = Ui::Text::Link(
langDateTime(base::unixtime::parse(_data->date))); // Link 1. langDateTime(base::unixtime::parse(parent->date()))); // Link 1.
_details.setMarkedText( _details.setMarkedText(
st::defaultTextStyle, st::defaultTextStyle,
tr::lng_date_and_duration( tr::lng_date_and_duration(
@ -1020,7 +1020,7 @@ Document::Document(
, _forceFileLayout(fields.forceFileLayout) , _forceFileLayout(fields.forceFileLayout)
, _date(langDateTime(base::unixtime::parse(fields.dateOverride , _date(langDateTime(base::unixtime::parse(fields.dateOverride
? fields.dateOverride ? fields.dateOverride
: _data->date))) : parent->date())))
, _ext(_generic.ext) , _ext(_generic.ext)
, _datew(st::normalFont->width(_date)) { , _datew(st::normalFont->width(_date)) {
_name.setMarkedText( _name.setMarkedText(

View file

@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "base/platform/base_platform_info.h" #include "base/platform/base_platform_info.h"
#include "base/event_filter.h" #include "base/event_filter.h"
#include "ui/platform/ui_platform_window_title.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "ui/widgets/input_fields.h" #include "ui/widgets/input_fields.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"

View file

@ -309,6 +309,10 @@ bool GenerateDesktopFile(
hashMd5Hex(d.constData(), d.size(), md5Hash); hashMd5Hex(d.constData(), d.size(), md5Hash);
if (!Core::Launcher::Instance().customWorkingDir()) { if (!Core::Launcher::Instance().customWorkingDir()) {
QFile::remove(u"%1org.telegram.desktop._%2.desktop"_q.arg(
targetPath,
md5Hash));
const auto exePath = QFile::encodeName( const auto exePath = QFile::encodeName(
cExeDir() + cExeName()); cExeDir() + cExeName());
hashMd5Hex(exePath.constData(), exePath.size(), md5Hash); hashMd5Hex(exePath.constData(), exePath.size(), md5Hash);
@ -335,7 +339,7 @@ bool GenerateServiceFile(bool silent = false) {
+ QGuiApplication::desktopFileName() + QGuiApplication::desktopFileName()
+ u".service"_q; + u".service"_q;
DEBUG_LOG(("App Info: placing .service file to %1").arg(targetPath)); DEBUG_LOG(("App Info: placing D-Bus service file to %1").arg(targetPath));
if (!QDir(targetPath).exists()) QDir().mkpath(targetPath); if (!QDir(targetPath).exists()) QDir().mkpath(targetPath);
const auto target = Glib::KeyFile::create(); const auto target = Glib::KeyFile::create();
@ -366,6 +370,18 @@ bool GenerateServiceFile(bool silent = false) {
return false; return false;
} }
if (!Core::UpdaterDisabled() && !Core::Launcher::Instance().customWorkingDir()) {
DEBUG_LOG(("App Info: removing old D-Bus service files"));
char md5Hash[33] = { 0 };
const auto d = QFile::encodeName(QDir(cWorkingDir()).absolutePath());
hashMd5Hex(d.constData(), d.size(), md5Hash);
QFile::remove(u"%1org.telegram.desktop._%2.service"_q.arg(
targetPath,
md5Hash));
}
QProcess::execute(u"systemctl"_q, { QProcess::execute(u"systemctl"_q, {
u"--user"_q, u"--user"_q,
u"reload"_q, u"reload"_q,

View file

@ -36,6 +36,7 @@ public:
void clearState() override; void clearState() override;
void setControlsOpacity(float64 opacity) override; void setControlsOpacity(float64 opacity) override;
rpl::producer<bool> controlsSideRightValue() override; rpl::producer<bool> controlsSideRightValue() override;
rpl::producer<int> topNotchSkipValue() override;
private: private:
using Control = Ui::Platform::TitleControl; using Control = Ui::Platform::TitleControl;

View file

@ -35,6 +35,7 @@ struct MacOverlayWidgetHelper::Data {
rpl::event_stream<> clearStateRequests; rpl::event_stream<> clearStateRequests;
bool anyOver = false; bool anyOver = false;
NSWindow * __weak native = nil; NSWindow * __weak native = nil;
rpl::variable<int> topNotchSkip;
}; };
MacOverlayWidgetHelper::MacOverlayWidgetHelper( MacOverlayWidgetHelper::MacOverlayWidgetHelper(
@ -96,6 +97,9 @@ void MacOverlayWidgetHelper::updateStyles(bool fullscreen) {
[window setTitleVisibility:NSWindowTitleHidden]; [window setTitleVisibility:NSWindowTitleHidden];
[window setTitlebarAppearsTransparent:YES]; [window setTitlebarAppearsTransparent:YES];
[window setStyleMask:[window styleMask] | NSWindowStyleMaskFullSizeContentView]; [window setStyleMask:[window styleMask] | NSWindowStyleMaskFullSizeContentView];
if (@available(macOS 12.0, *)) {
_data->topNotchSkip = [[window screen] safeAreaInsets].top;
}
} }
void MacOverlayWidgetHelper::refreshButtons(bool fullscreen) { void MacOverlayWidgetHelper::refreshButtons(bool fullscreen) {
@ -153,6 +157,10 @@ rpl::producer<bool> MacOverlayWidgetHelper::controlsSideRightValue() {
return rpl::single(false); return rpl::single(false);
} }
rpl::producer<int> MacOverlayWidgetHelper::topNotchSkipValue() {
return _data->topNotchSkip.value();
}
object_ptr<Ui::AbstractButton> MacOverlayWidgetHelper::create( object_ptr<Ui::AbstractButton> MacOverlayWidgetHelper::create(
not_null<QWidget*> parent, not_null<QWidget*> parent,
Control control) { Control control) {

View file

@ -58,6 +58,9 @@ public:
-> rpl::producer<not_null<QMouseEvent*>> { -> rpl::producer<not_null<QMouseEvent*>> {
return rpl::never<not_null<QMouseEvent*>>(); return rpl::never<not_null<QMouseEvent*>>();
} }
[[nodiscard]] virtual rpl::producer<int> topNotchSkipValue() {
return rpl::single(0);
}
}; };
[[nodiscard]] std::unique_ptr<OverlayWidgetHelper> CreateOverlayWidgetHelper( [[nodiscard]] std::unique_ptr<OverlayWidgetHelper> CreateOverlayWidgetHelper(

View file

@ -1653,7 +1653,7 @@ QPointer<Ui::RpWidget> Premium::createPinnedToBottom(
} else { } else {
#endif #endif
{ {
_radioGroup->setChangedCallback([=](int value) { const auto callback = [=](int value) {
const auto options = const auto options =
_controller->session().api().premium().subscriptionOptions(); _controller->session().api().premium().subscriptionOptions();
if (options.empty()) { if (options.empty()) {
@ -1665,8 +1665,9 @@ QPointer<Ui::RpWidget> Premium::createPinnedToBottom(
lt_cost, lt_cost,
options[value].costPerMonth); options[value].costPerMonth);
_buttonText = std::move(text); _buttonText = std::move(text);
}); };
_radioGroup->setValue(0); _radioGroup->setChangedCallback(callback);
callback(0);
} }
_showFinished.events( _showFinished.events(

View file

@ -165,8 +165,9 @@ Action::Action(
+ _st.itemStyle.font->height + _st.itemStyle.font->height
+ st::defaultWhoRead.itemPadding.bottom()) { + st::defaultWhoRead.itemPadding.bottom()) {
const auto parent = parentMenu->menu(); const auto parent = parentMenu->menu();
const auto checkAppeared = [=, now = crl::now()] { const auto delay = anim::Disabled() ? 0 : parentMenu->st().duration;
_appeared = (crl::now() - now) >= parentMenu->st().duration; const auto checkAppeared = [=, now = crl::now()](bool force = false) {
_appeared = force || ((crl::now() - now) >= delay);
}; };
setAcceptBoth(true); setAcceptBoth(true);
@ -224,8 +225,10 @@ Action::Action(
enableMouseSelecting(); enableMouseSelecting();
base::call_delayed(parentMenu->st().duration, this, [=] { base::call_delayed(parentMenu->st().duration, this, [=] {
checkAppeared(); if (!_appeared) {
updateUserpicsFromContent(); checkAppeared(true);
updateUserpicsFromContent();
}
}); });
} }

View file

@ -1138,6 +1138,10 @@ void AddGiftOptions(
stCheckbox, stCheckbox,
std::move(radioView)); std::move(radioView));
radio->setAttribute(Qt::WA_TransparentForMouseEvents); radio->setAttribute(Qt::WA_TransparentForMouseEvents);
{ // Paint the last frame instantly for the layer animation.
group->setValue(0);
radio->finishAnimating();
}
row->sizeValue( row->sizeValue(
) | rpl::start_with_next([=, margins = stCheckbox.margin]( ) | rpl::start_with_next([=, margins = stCheckbox.margin](

View file

@ -40,6 +40,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Window { namespace Window {
namespace { namespace {
[[nodiscard]] Dialogs::UnreadState MainListMapUnreadState(
not_null<Main::Session*> session,
const Dialogs::UnreadState &state) {
const auto folderId = Data::Folder::kId;
if (const auto folder = session->data().folderLoaded(folderId)) {
return state - folder->chatsList()->unreadState();
}
return state;
}
[[nodiscard]] rpl::producer<Dialogs::UnreadState> MainListUnreadState( [[nodiscard]] rpl::producer<Dialogs::UnreadState> MainListUnreadState(
not_null<Dialogs::MainList*> list) { not_null<Dialogs::MainList*> list) {
return rpl::single(rpl::empty) | rpl::then( return rpl::single(rpl::empty) | rpl::then(
@ -59,11 +69,7 @@ namespace {
return MainListUnreadState( return MainListUnreadState(
session->data().chatsList() session->data().chatsList()
) | rpl::map([=](const Dialogs::UnreadState &state) { ) | rpl::map([=](const Dialogs::UnreadState &state) {
const auto folderId = Data::Folder::kId; return MainListMapUnreadState(session, state);
if (const auto folder = session->data().folderLoaded(folderId)) {
return state - folder->chatsList()->unreadState();
}
return state;
}); });
} }
@ -314,24 +320,14 @@ base::unique_qptr<Ui::SideBarButton> FiltersMenu::prepareButton(
} else if (id >= 0) { } else if (id >= 0) {
_session->setActiveChatsFilter(id); _session->setActiveChatsFilter(id);
} else { } else {
const auto filters = &_session->session().data().chatsFilters(); openFiltersSettings();
if (filters->suggestedLoaded()) {
_session->showSettings(Settings::Folders::Id());
} else if (!_waitingSuggested) {
_waitingSuggested = true;
filters->requestSuggested();
filters->suggestedUpdated(
) | rpl::take(1) | rpl::start_with_next([=] {
_session->showSettings(Settings::Folders::Id());
}, _outer.lifetime());
}
} }
}); });
if (id >= 0) { if (id >= 0) {
raw->setAcceptDrops(true); raw->setAcceptDrops(true);
raw->events( raw->events(
) | rpl::filter([=](not_null<QEvent*> e) { ) | rpl::filter([=](not_null<QEvent*> e) {
return ((e->type() == QEvent::ContextMenu) && (id > 0)) return ((e->type() == QEvent::ContextMenu) && (id >= 0))
|| e->type() == QEvent::DragEnter || e->type() == QEvent::DragEnter
|| e->type() == QEvent::DragMove || e->type() == QEvent::DragMove
|| e->type() == QEvent::DragLeave; || e->type() == QEvent::DragLeave;
@ -362,13 +358,27 @@ base::unique_qptr<Ui::SideBarButton> FiltersMenu::prepareButton(
return button; return button;
} }
void FiltersMenu::openFiltersSettings() {
const auto filters = &_session->session().data().chatsFilters();
if (filters->suggestedLoaded()) {
_session->showSettings(Settings::Folders::Id());
} else if (!_waitingSuggested) {
_waitingSuggested = true;
filters->requestSuggested();
filters->suggestedUpdated(
) | rpl::take(1) | rpl::start_with_next([=] {
_session->showSettings(Settings::Folders::Id());
}, _outer.lifetime());
}
}
void FiltersMenu::showMenu(QPoint position, FilterId id) { void FiltersMenu::showMenu(QPoint position, FilterId id) {
if (_popupMenu) { if (_popupMenu) {
_popupMenu = nullptr; _popupMenu = nullptr;
return; return;
} }
const auto i = _filters.find(id); const auto i = _filters.find(id);
if (i == end(_filters)) { if ((i == end(_filters)) && id) {
return; return;
} }
_popupMenu = base::make_unique_q<Ui::PopupMenu>( _popupMenu = base::make_unique_q<Ui::PopupMenu>(
@ -382,23 +392,46 @@ void FiltersMenu::showMenu(QPoint position, FilterId id) {
args.icon); args.icon);
}); });
addAction( if (id) {
tr::lng_filters_context_edit(tr::now), addAction(
[=] { showEditBox(id); }, tr::lng_filters_context_edit(tr::now),
&st::menuIconEdit); [=] { showEditBox(id); },
&st::menuIconEdit);
auto filteredChats = [=] { auto filteredChats = [=] {
return _session->session().data().chatsFilters().chatsList(id); return _session->session().data().chatsFilters().chatsList(id);
}; };
Window::MenuAddMarkAsReadChatListAction( Window::MenuAddMarkAsReadChatListAction(
_session, _session,
std::move(filteredChats), std::move(filteredChats),
addAction); addAction);
addAction( addAction(
tr::lng_filters_context_remove(tr::now), tr::lng_filters_context_remove(tr::now),
[=] { showRemoveBox(id); }, [=] { showRemoveBox(id); },
&st::menuIconDelete); &st::menuIconDelete);
} else {
auto customUnreadState = [=] {
const auto session = &_session->session();
return MainListMapUnreadState(
session,
session->data().chatsList()->unreadState());
};
Window::MenuAddMarkAsReadChatListAction(
_session,
[=] { return _session->session().data().chatsList(); },
addAction,
std::move(customUnreadState));
addAction(
tr::lng_filters_setup_menu(tr::now),
[=] { openFiltersSettings(); },
&st::menuIconEdit);
}
if (_popupMenu->empty()) {
_popupMenu = nullptr;
return;
}
_popupMenu->popup(position); _popupMenu->popup(position);
} }

View file

@ -52,6 +52,7 @@ private:
void showRemoveBox(FilterId id); void showRemoveBox(FilterId id);
void remove(FilterId id, std::vector<not_null<PeerData*>> leave = {}); void remove(FilterId id, std::vector<not_null<PeerData*>> leave = {});
void scrollToButton(not_null<Ui::RpWidget*> widget); void scrollToButton(not_null<Ui::RpWidget*> widget);
void openFiltersSettings();
const not_null<SessionController*> _session; const not_null<SessionController*> _session;
const not_null<Ui::RpWidget*> _parent; const not_null<Ui::RpWidget*> _parent;

View file

@ -2303,9 +2303,12 @@ void MenuAddMarkAsReadAllChatsAction(
void MenuAddMarkAsReadChatListAction( void MenuAddMarkAsReadChatListAction(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
Fn<not_null<Dialogs::MainList*>()> &&list, Fn<not_null<Dialogs::MainList*>()> &&list,
const PeerMenuCallback &addAction) { const PeerMenuCallback &addAction,
Fn<Dialogs::UnreadState()> customUnreadState) {
// There is no async to make weak from controller. // There is no async to make weak from controller.
const auto unreadState = list()->unreadState(); const auto unreadState = customUnreadState
? customUnreadState()
: list()->unreadState();
if (!unreadState.messages && !unreadState.marks && !unreadState.chats) { if (!unreadState.messages && !unreadState.marks && !unreadState.chats) {
return; return;
} }

View file

@ -32,6 +32,7 @@ class Thread;
namespace Dialogs { namespace Dialogs {
class MainList; class MainList;
struct EntryState; struct EntryState;
struct UnreadState;
} // namespace Dialogs } // namespace Dialogs
namespace ChatHelpers { namespace ChatHelpers {
@ -69,7 +70,8 @@ void MenuAddMarkAsReadAllChatsAction(
void MenuAddMarkAsReadChatListAction( void MenuAddMarkAsReadChatListAction(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
Fn<not_null<Dialogs::MainList*>()> &&list, Fn<not_null<Dialogs::MainList*>()> &&list,
const PeerMenuCallback &addAction); const PeerMenuCallback &addAction,
Fn<Dialogs::UnreadState()> customUnreadState = nullptr);
void PeerMenuExportChat(not_null<PeerData*> peer); void PeerMenuExportChat(not_null<PeerData*> peer);
void PeerMenuDeleteContact( void PeerMenuDeleteContact(

View file

@ -520,7 +520,7 @@ mac:
""") """)
stage('mozjpeg', """ stage('mozjpeg', """
git clone -b v4.0.3 https://github.com/mozilla/mozjpeg.git git clone -b v4.1.3 https://github.com/mozilla/mozjpeg.git
cd mozjpeg cd mozjpeg
win: win:
cmake . ^ cmake . ^
@ -675,13 +675,31 @@ stage('dav1d', """
win: win:
git clone -b 1.2.1 --depth 1 https://code.videolan.org/videolan/dav1d.git git clone -b 1.2.1 --depth 1 https://code.videolan.org/videolan/dav1d.git
cd dav1d cd dav1d
if "%X8664%" equ "x64" (
SET "TARGET=x86_64"
) else (
SET "TARGET=x86"
)
set FILE=cross-file.txt
echo [binaries] > %FILE%
echo c = 'cl' >> %FILE%
echo cpp = 'cl' >> %FILE%
echo ar = 'lib' >> %FILE%
echo windres = 'rc' >> %FILE%
echo [host_machine] >> %FILE%
echo system = 'windows' >> %FILE%
echo cpu_family = '%TARGET%' >> %FILE%
echo cpu = '%TARGET%' >> %FILE%
echo endian = 'little'>> %FILE%
depends:python/Scripts/activate.bat depends:python/Scripts/activate.bat
%THIRDPARTY_DIR%\\python\\Scripts\\activate.bat %THIRDPARTY_DIR%\\python\\Scripts\\activate.bat
meson setup --prefix %LIBS_DIR%/local --default-library=static --buildtype=debug -Denable_tools=false -Denable_tests=false -Db_vscrt=mtd builddir-debug meson setup --cross-file %FILE% --prefix %LIBS_DIR%/local --default-library=static --buildtype=debug -Denable_tools=false -Denable_tests=false -Db_vscrt=mtd builddir-debug
meson compile -C builddir-debug meson compile -C builddir-debug
meson install -C builddir-debug meson install -C builddir-debug
release: release:
meson setup --prefix %LIBS_DIR%/local --default-library=static --buildtype=release -Denable_tools=false -Denable_tests=false -Db_vscrt=mt builddir-release meson setup --cross-file %FILE% --prefix %LIBS_DIR%/local --default-library=static --buildtype=release -Denable_tools=false -Denable_tests=false -Db_vscrt=mt builddir-release
meson compile -C builddir-release meson compile -C builddir-release
meson install -C builddir-release meson install -C builddir-release
win: win:

View file

@ -1,7 +1,7 @@
AppVersion 4008007 AppVersion 4008010
AppVersionStrMajor 4.8 AppVersionStrMajor 4.8
AppVersionStrSmall 4.8.7 AppVersionStrSmall 4.8.10
AppVersionStr 4.8.7 AppVersionStr 4.8.10
BetaChannel 0 BetaChannel 0
AlphaVersion 0 AlphaVersion 0
AppVersionOriginal 4.8.7 AppVersionOriginal 4.8.10

@ -1 +1 @@
Subproject commit 2669a04579069942b6208a18abe93c26adfddf2a Subproject commit efd052594db9f3d85a2fbcba76db6fdecf226597

View file

@ -1,3 +1,16 @@
4.8.10 (28.07.23)
- Send story sharing comments as separate messages.
- Fix stories explanation tooltip ordering.
4.8.9 (26.07.23)
- Bug fixes and other minor improvements.
4.8.8 (25.07.23)
- Several crash fixes and story viewer improvements.
4.8.7 (21.07.23) 4.8.7 (21.07.23)
- Several crash fixes and small stories improvements. - Several crash fixes and small stories improvements.

View file