From d2dd63e90af13948b722f714bd9f9b828ea47faf Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 20 Jun 2023 19:31:40 +0400 Subject: [PATCH] Show active stories in profile top bar. --- Telegram/SourceFiles/dialogs/dialogs.style | 3 + .../dialogs/ui/dialogs_stories_list.cpp | 2 +- .../SourceFiles/info/info_content_widget.cpp | 5 + .../SourceFiles/info/info_content_widget.h | 6 ++ Telegram/SourceFiles/info/info_top_bar.cpp | 94 ++++++++++++++++++- Telegram/SourceFiles/info/info_top_bar.h | 24 ++++- .../SourceFiles/info/info_wrap_widget.cpp | 6 ++ .../info/profile/info_profile_widget.cpp | 10 ++ .../info/profile/info_profile_widget.h | 1 + 9 files changed, 143 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 30b36538d..8e9f09232 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -505,6 +505,7 @@ DialogsStoriesList { full: DialogsStories; bg: color; readOpacity: double; + fullClickable: int; } dialogsStories: DialogsStories { @@ -544,9 +545,11 @@ dialogsStoriesList: DialogsStoriesList { full: dialogsStoriesFull; bg: dialogsBg; readOpacity: 0.6; + fullClickable: 0; } dialogsStoriesListInfo: DialogsStoriesList(dialogsStoriesList) { bg: transparent; + fullClickable: 1; } dialogsStoriesListMine: DialogsStoriesList(dialogsStoriesListInfo) { readOpacity: 1.; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp index c022e025c..b630d9b64 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp @@ -846,7 +846,7 @@ void List::updateSelected() { + lastRightAdd) ? (infiniteIndex - 1) // Last small part should still be clickable. : (startIndex + infiniteIndex >= endIndex) - ? -1 + ? (_st.fullClickable ? (endIndex - 1) : -1) : infiniteIndex; const auto selected = (index < 0 || startIndex + index >= layout.itemsCount) diff --git a/Telegram/SourceFiles/info/info_content_widget.cpp b/Telegram/SourceFiles/info/info_content_widget.cpp index 640c75d67..e96ba3cfc 100644 --- a/Telegram/SourceFiles/info/info_content_widget.cpp +++ b/Telegram/SourceFiles/info/info_content_widget.cpp @@ -273,6 +273,11 @@ void ContentWidget::setViewport( }, _scroll->lifetime()); } +auto ContentWidget::titleStories() +-> rpl::producer { + return nullptr; +} + void ContentWidget::saveChanges(FnMut done) { done(); } diff --git a/Telegram/SourceFiles/info/info_content_widget.h b/Telegram/SourceFiles/info/info_content_widget.h index 1a54f5fdf..6a6f75bbd 100644 --- a/Telegram/SourceFiles/info/info_content_widget.h +++ b/Telegram/SourceFiles/info/info_content_widget.h @@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/rp_widget.h" #include "info/info_wrap_widget.h" +namespace Dialogs::Stories { +struct Content; +} // namespace Dialogs::Stories + namespace Storage { enum class SharedMediaType : signed char; } // namespace Storage @@ -88,6 +92,8 @@ public: } [[nodiscard]] virtual rpl::producer title() = 0; + [[nodiscard]] virtual auto titleStories() + -> rpl::producer; virtual void saveChanges(FnMut done); diff --git a/Telegram/SourceFiles/info/info_top_bar.cpp b/Telegram/SourceFiles/info/info_top_bar.cpp index a9e114515..309ca167d 100644 --- a/Telegram/SourceFiles/info/info_top_bar.cpp +++ b/Telegram/SourceFiles/info/info_top_bar.cpp @@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +#include "dialogs/ui/dialogs_stories_content.h" +#include "dialogs/ui/dialogs_stories_list.h" #include "lang/lang_keys.h" #include "lang/lang_numbers_animation.h" #include "info/info_wrap_widget.h" @@ -30,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_channel.h" #include "data/data_user.h" +#include "styles/style_dialogs.h" #include "styles/style_info.h" namespace Info { @@ -88,9 +91,11 @@ void TopBar::setTitle(rpl::producer &&title) { object_ptr(this, std::move(title), _st.title), st::infoTopBarScale); _title->setDuration(st::infoTopBarDuration); - _title->toggle(!selectionMode(), anim::type::instant); + _title->toggle( + !selectionMode() && !storiesTitle(), + anim::type::instant); registerToggleControlCallback(_title.data(), [=] { - return !selectionMode() && !searchMode(); + return !selectionMode() && !storiesTitle() && !searchMode(); }); if (_back) { @@ -309,12 +314,15 @@ int TopBar::resizeGetHeight(int newWidth) { void TopBar::updateControlsGeometry(int newWidth) { updateDefaultControlsGeometry(newWidth); updateSelectionControlsGeometry(newWidth); + updateStoriesGeometry(newWidth); } void TopBar::updateDefaultControlsGeometry(int newWidth) { auto right = 0; for (auto &button : _buttons) { - if (!button) continue; + if (!button) { + continue; + } button->moveToRight(right, 0, newWidth); right += button->width(); } @@ -362,6 +370,28 @@ void TopBar::updateSelectionControlsGeometry(int newWidth) { newWidth); } +void TopBar::updateStoriesGeometry(int newWidth) { + if (!_stories) { + return; + } + + auto right = 0; + for (auto &button : _buttons) { + if (!button) { + continue; + } + button->moveToRight(right, 0, newWidth); + right += button->width(); + } + const auto left = (_back ? _st.back.width : _st.titlePosition.x()) + - st::dialogsStories.left - st::dialogsStories.photoLeft; + const auto top = st::dialogsStories.height + - st::dialogsStoriesFull.height + + (_st.height - st::dialogsStories.height) / 2; + _stories->resizeToWidth(newWidth - left - right); + _stories->moveToLeft(left, top, newWidth); +} + void TopBar::paintEvent(QPaintEvent *e) { auto p = QPainter(this); @@ -412,6 +442,60 @@ void TopBar::updateControlsVisibility(anim::type animated) { } } +void TopBar::setStories(rpl::producer content) { + _storiesLifetime.destroy(); + if (content) { + using namespace Dialogs::Stories; + + auto last = std::move( + content + ) | rpl::start_spawning(_storiesLifetime); + delete _stories; + + const auto stories = Ui::CreateChild>( + this, + object_ptr( + this, + st::dialogsStoriesListInfo, + rpl::duplicate( + last + ) | rpl::filter([](const Content &content) { + return !content.elements.empty(); + }), + [] { return st::dialogsStories.height; }), + st::infoTopBarScale); + registerToggleControlCallback( + stories, + [this] { return _storiesCount > 0; }); + stories->toggle(false, anim::type::instant); + stories->setDuration(st::infoTopBarDuration); + _stories = stories; + _stories->entity()->clicks( + ) | rpl::start_to_stream(_storyClicks, _stories->lifetime()); + if (_back) { + _back->raise(); + } + + rpl::duplicate( + last + ) | rpl::start_with_next([=](const Content &content) { + const auto count = int(content.elements.size()); + if (_storiesCount != count) { + const auto was = (_storiesCount > 0); + _storiesCount = count; + const auto now = (_storiesCount > 0); + if (was != now) { + updateControlsVisibility(anim::type::normal); + } + updateControlsGeometry(width()); + } + }, _storiesLifetime); + } else { + _storiesCount = 0; + } + updateControlsVisibility(anim::type::instant); +} + void TopBar::setSelectedItems(SelectedItems &&items) { auto wasSelectionMode = selectionMode(); _selectedItems = std::move(items); @@ -550,6 +634,10 @@ bool TopBar::selectionMode() const { return !_selectedItems.list.empty(); } +bool TopBar::storiesTitle() const { + return _storiesCount > 0; +} + bool TopBar::searchMode() const { return _searchModeAvailable && _searchModeEnabled; } diff --git a/Telegram/SourceFiles/info/info_top_bar.h b/Telegram/SourceFiles/info/info_top_bar.h index a2ed052c5..b038e213a 100644 --- a/Telegram/SourceFiles/info/info_top_bar.h +++ b/Telegram/SourceFiles/info/info_top_bar.h @@ -18,6 +18,11 @@ namespace style { struct InfoTopBar; } // namespace style +namespace Dialogs::Stories { +class List; +struct Content; +} // namespace Dialogs::Stories + namespace Window { class SessionNavigation; } // namespace Window @@ -43,11 +48,15 @@ public: const style::InfoTopBar &st, SelectedItems &&items); - auto backRequest() const { + [[nodiscard]] auto backRequest() const { return _backClicks.events(); } + [[nodiscard]] auto storyClicks() const { + return _storyClicks.events(); + } void setTitle(rpl::producer &&title); + void setStories(rpl::producer content); void enableBackButton(); void highlight(); @@ -95,6 +104,7 @@ private: void updateControlsGeometry(int newWidth); void updateDefaultControlsGeometry(int newWidth); void updateSelectionControlsGeometry(int newWidth); + void updateStoriesGeometry(int newWidth); Ui::FadeWrap *pushButton( base::unique_qptr button); void forceButtonVisibility( @@ -104,9 +114,10 @@ private: void startHighlightAnimation(); void updateControlsVisibility(anim::type animated); - bool selectionMode() const; - bool searchMode() const; - Ui::StringWithNumbers generateSelectedText() const; + [[nodiscard]] bool selectionMode() const; + [[nodiscard]] bool storiesTitle() const; + [[nodiscard]] bool searchMode() const; + [[nodiscard]] Ui::StringWithNumbers generateSelectedText() const; [[nodiscard]] bool computeCanDelete() const; [[nodiscard]] bool computeCanForward() const; void updateSelectionState(); @@ -147,6 +158,7 @@ private: QPointer _searchField; rpl::event_stream<> _backClicks; + rpl::event_stream _storyClicks; SelectedItems _selectedItems; bool _canDelete = false; @@ -157,6 +169,10 @@ private: QPointer> _delete; rpl::event_stream _selectionActionRequests; + QPointer> _stories; + rpl::lifetime _storiesLifetime; + int _storiesCount = 0; + using UpdateCallback = Fn; std::map _updateControlCallbacks; diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp index 3ff8fe7be..2d8e2669a 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.cpp +++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp @@ -303,6 +303,11 @@ void WrapWidget::createTopBar() { _controller->parentController()->closeThirdSection(); }); } + _topBar->storyClicks() | rpl::start_with_next([=] { + if (const auto peer = _controller->key().peer()) { + _controller->parentController()->openPeerStories(peer->id); + } + }, _topBar->lifetime()); if (wrapValue == Wrap::Layer) { auto close = _topBar->addButton( base::make_unique_q( @@ -579,6 +584,7 @@ void WrapWidget::finishShowContent() { _content->setIsStackBottom(!hasStackHistory()); if (_topBar) { _topBar->setTitle(_content->title()); + _topBar->setStories(_content->titleStories()); } _desiredHeights.fire(desiredHeightForContent()); _desiredShadowVisibilities.fire(_content->desiredShadowVisibility()); diff --git a/Telegram/SourceFiles/info/profile/info_profile_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_widget.cpp index f610c2bfe..40b374ef1 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_widget.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_widget.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_widget.h" +#include "dialogs/ui/dialogs_stories_content.h" #include "info/profile/info_profile_inner_widget.h" #include "info/profile/info_profile_members.h" #include "ui/widgets/scroll_area.h" @@ -105,7 +106,16 @@ rpl::producer Widget::title() { return tr::lng_info_group_title(); } Unexpected("Bad peer type in Info::TitleValue()"); +} +rpl::producer Widget::titleStories() { + const auto peer = controller()->key().peer(); + if (const auto user = peer->asUser()) { + if (!user->isBot()) { + return Dialogs::Stories::LastForPeer(user); + } + } + return nullptr; } bool Widget::showInternal(not_null memento) { diff --git a/Telegram/SourceFiles/info/profile/info_profile_widget.h b/Telegram/SourceFiles/info/profile/info_profile_widget.h index 51e9f0b06..38758fc00 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_widget.h +++ b/Telegram/SourceFiles/info/profile/info_profile_widget.h @@ -60,6 +60,7 @@ public: void setInnerFocus() override; rpl::producer title() override; + rpl::producer titleStories() override; private: void saveState(not_null memento);