From 5f72a5238c6558fc77ac5211f2d693cd233e1e6b Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 30 Jun 2023 16:45:41 +0400 Subject: [PATCH] Save to Profile / Archive / Delete in list. --- Telegram/SourceFiles/data/data_stories.cpp | 2 + .../SourceFiles/data/data_stories_ids.cpp | 2 +- Telegram/SourceFiles/history/history_item.cpp | 2 +- Telegram/SourceFiles/info/info_top_bar.cpp | 39 +++++++++- Telegram/SourceFiles/info/info_top_bar.h | 6 ++ .../SourceFiles/info/info_wrap_widget.cpp | 2 + Telegram/SourceFiles/info/info_wrap_widget.h | 2 + .../info/media/info_media_common.h | 13 ++-- .../info/media/info_media_list_section.cpp | 4 +- .../info/media/info_media_list_widget.cpp | 75 ++++++++++++++++++- .../info/media/info_media_list_widget.h | 1 + .../info/stories/info_stories_provider.cpp | 13 ++-- .../stories/media_stories_controller.cpp | 46 ++++++++---- .../media/stories/media_stories_controller.h | 8 ++ 14 files changed, 179 insertions(+), 36 deletions(-) diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index a6db98e37..dfb5129e5 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -1298,6 +1298,8 @@ void Stories::togglePinnedList( const auto loaded = saved.loaded; const auto lastId = !saved.ids.list.empty() ? saved.ids.list.back() + : saved.lastId + ? saved.lastId : std::numeric_limits::max(); auto dirty = false; for (const auto &id : result.v) { diff --git a/Telegram/SourceFiles/data/data_stories_ids.cpp b/Telegram/SourceFiles/data/data_stories_ids.cpp index 31604f2eb..6a96f4e5f 100644 --- a/Telegram/SourceFiles/data/data_stories_ids.cpp +++ b/Telegram/SourceFiles/data/data_stories_ids.cpp @@ -44,7 +44,7 @@ rpl::producer SavedStoriesIds( const auto hasBefore = int(around - begin(saved->list)); const auto hasAfter = int(end(saved->list) - around); if (hasAfter < limit) { - //stories->savedLoadMore(peer->id); + stories->savedLoadMore(peer->id); } const auto takeBefore = std::min(hasBefore, limit); const auto takeAfter = std::min(hasAfter, limit); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 72745e74d..c6ddbb4c4 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -1988,7 +1988,7 @@ bool HistoryItem::canDelete() const { if (isSponsored()) { return false; } else if (IsStoryMsgId(id)) { - return false && _history->peer->isSelf(); // #TODO stories + return false; } else if (isService() && !isRegular()) { return false; } else if (topicRootId() == id) { diff --git a/Telegram/SourceFiles/info/info_top_bar.cpp b/Telegram/SourceFiles/info/info_top_bar.cpp index 309ca167d..68aae3228 100644 --- a/Telegram/SourceFiles/info/info_top_bar.cpp +++ b/Telegram/SourceFiles/info/info_top_bar.cpp @@ -352,6 +352,10 @@ void TopBar::updateSelectionControlsGeometry(int newWidth) { _delete->moveToRight(right, 0, newWidth); right += _delete->width(); } + if (_canToggleStoryPin) { + _toggleStoryPin->moveToRight(right, 0, newWidth); + right += _toggleStoryPin->width(); + } if (_canForward) { _forward->moveToRight(right, 0, newWidth); right += _forward->width(); @@ -496,6 +500,10 @@ void TopBar::setStories(rpl::producer content) { updateControlsVisibility(anim::type::instant); } +void TopBar::setStoriesArchive(bool archive) { + _storiesArchive = archive; +} + void TopBar::setSelectedItems(SelectedItems &&items) { auto wasSelectionMode = selectionMode(); _selectedItems = std::move(items); @@ -523,13 +531,14 @@ rpl::producer TopBar::selectionActionRequests() const { } void TopBar::updateSelectionState() { - Expects(_selectionText && _delete && _forward); + Expects(_selectionText && _delete && _forward && _toggleStoryPin); _canDelete = computeCanDelete(); _canForward = computeCanForward(); _selectionText->entity()->setValue(generateSelectedText()); _delete->toggle(_canDelete, anim::type::instant); _forward->toggle(_canForward, anim::type::instant); + _toggleStoryPin->toggle(_canToggleStoryPin, anim::type::instant); updateSelectionControlsGeometry(width()); } @@ -544,6 +553,7 @@ void TopBar::createSelectionControls() { }; _canDelete = computeCanDelete(); _canForward = computeCanForward(); + _canToggleStoryPin = computeCanToggleStoryPin(); _cancelSelection = wrap(Ui::CreateChild>( this, object_ptr(this, _st.mediaCancel), @@ -595,6 +605,24 @@ void TopBar::createSelectionControls() { _selectionActionRequests, _cancelSelection->lifetime()); _delete->entity()->setVisible(_canDelete); + const auto archive = + _toggleStoryPin = wrap(Ui::CreateChild>( + this, + object_ptr( + this, + _storiesArchive ? _st.storiesSave : _st.storiesArchive), + st::infoTopBarScale)); + registerToggleControlCallback( + _toggleStoryPin.data(), + [this] { return selectionMode() && _canToggleStoryPin; }); + _toggleStoryPin->setDuration(st::infoTopBarDuration); + _toggleStoryPin->entity()->clicks( + ) | rpl::map_to( + SelectionAction::ToggleStoryPin + ) | rpl::start_to_stream( + _selectionActionRequests, + _cancelSelection->lifetime()); + _toggleStoryPin->entity()->setVisible(_canToggleStoryPin); updateControlsGeometry(width()); } @@ -607,6 +635,12 @@ bool TopBar::computeCanForward() const { return ranges::all_of(_selectedItems.list, &SelectedItem::canForward); } +bool TopBar::computeCanToggleStoryPin() const { + return ranges::all_of( + _selectedItems.list, + &SelectedItem::canToggleStoryPin); +} + Ui::StringWithNumbers TopBar::generateSelectedText() const { using Type = Storage::SharedMediaType; const auto phrase = [&] { @@ -618,8 +652,7 @@ Ui::StringWithNumbers TopBar::generateSelectedText() const { case Type::MusicFile: return tr::lng_media_selected_song; case Type::Link: return tr::lng_media_selected_link; case Type::RoundVoiceFile: return tr::lng_media_selected_audio; - // #TODO stories - case Type::PhotoVideo: return tr::lng_media_selected_photo; + case Type::PhotoVideo: return tr::lng_stories_row_count; } Unexpected("Type in TopBar::generateSelectedText()"); }(); diff --git a/Telegram/SourceFiles/info/info_top_bar.h b/Telegram/SourceFiles/info/info_top_bar.h index b038e213a..c41125904 100644 --- a/Telegram/SourceFiles/info/info_top_bar.h +++ b/Telegram/SourceFiles/info/info_top_bar.h @@ -57,6 +57,7 @@ public: void setTitle(rpl::producer &&title); void setStories(rpl::producer content); + void setStoriesArchive(bool archive); void enableBackButton(); void highlight(); @@ -120,11 +121,13 @@ private: [[nodiscard]] Ui::StringWithNumbers generateSelectedText() const; [[nodiscard]] bool computeCanDelete() const; [[nodiscard]] bool computeCanForward() const; + [[nodiscard]] bool computeCanToggleStoryPin() const; void updateSelectionState(); void createSelectionControls(); void performForward(); void performDelete(); + void performToggleStoryPin(); void setSearchField( base::unique_qptr field, @@ -163,10 +166,13 @@ private: SelectedItems _selectedItems; bool _canDelete = false; bool _canForward = false; + bool _canToggleStoryPin = false; + bool _storiesArchive = false; QPointer> _cancelSelection; QPointer> _selectionText; QPointer> _forward; QPointer> _delete; + QPointer> _toggleStoryPin; rpl::event_stream _selectionActionRequests; QPointer> _stories; diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp index 2d8e2669a..214e191fb 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.cpp +++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp @@ -585,6 +585,8 @@ void WrapWidget::finishShowContent() { if (_topBar) { _topBar->setTitle(_content->title()); _topBar->setStories(_content->titleStories()); + _topBar->setStoriesArchive( + _controller->key().storiesTab() == Stories::Tab::Archive); } _desiredHeights.fire(desiredHeightForContent()); _desiredShadowVisibilities.fire(_content->desiredShadowVisibility()); diff --git a/Telegram/SourceFiles/info/info_wrap_widget.h b/Telegram/SourceFiles/info/info_wrap_widget.h index a04c4d89c..47215d340 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.h +++ b/Telegram/SourceFiles/info/info_wrap_widget.h @@ -58,6 +58,7 @@ struct SelectedItem { GlobalMsgId globalId; bool canDelete = false; bool canForward = false; + bool canToggleStoryPin = false; }; struct SelectedItems { @@ -73,6 +74,7 @@ enum class SelectionAction { Clear, Forward, Delete, + ToggleStoryPin, }; class WrapWidget final : public Window::SectionWidget { diff --git a/Telegram/SourceFiles/info/media/info_media_common.h b/Telegram/SourceFiles/info/media/info_media_common.h index 20c3051ca..27e8d4b9f 100644 --- a/Telegram/SourceFiles/info/media/info_media_common.h +++ b/Telegram/SourceFiles/info/media/info_media_common.h @@ -30,15 +30,12 @@ struct ListItemSelectionData { TextSelection text; bool canDelete = false; bool canForward = false; -}; + bool canToggleStoryPin = false; -inline bool operator==( - ListItemSelectionData a, - ListItemSelectionData b) { - return (a.text == b.text) - && (a.canDelete == b.canDelete) - && (a.canForward == b.canForward); -} + friend inline bool operator==( + ListItemSelectionData, + ListItemSelectionData) = default; +}; using ListSelectedMap = base::flat_map< not_null, diff --git a/Telegram/SourceFiles/info/media/info_media_list_section.cpp b/Telegram/SourceFiles/info/media/info_media_list_section.cpp index ed70623ce..2bf957192 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_section.cpp +++ b/Telegram/SourceFiles/info/media/info_media_list_section.cpp @@ -338,7 +338,7 @@ void ListSection::resizeToWidth(int newWidth) { switch (_type) { case Type::Photo: case Type::Video: - case Type::PhotoVideo: // #TODO stories + case Type::PhotoVideo: case Type::RoundFile: { const auto skip = st::infoMediaSkip; _itemsLeft = st::infoMediaLeft; @@ -379,7 +379,7 @@ int ListSection::recountHeight() { switch (_type) { case Type::Photo: case Type::Video: - case Type::PhotoVideo: // #TODO stories + case Type::PhotoVideo: case Type::RoundFile: { auto itemHeight = _itemHeight + st::infoMediaSkip; auto index = 0; diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp index 7940bca59..985fde209 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp @@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/view/history_view_cursor_state.h" #include "history/view/history_view_service_message.h" +#include "media/stories/media_stories_controller.h" // ...TogglePinnedToast. #include "window/window_session_controller.h" #include "window/window_peer_menu.h" #include "ui/widgets/popup_menu.h" @@ -55,6 +56,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peer_list_controllers.h" #include "core/file_utilities.h" #include "core/application.h" +#include "ui/toast/toast.h" #include "styles/style_overview.h" #include "styles/style_info.h" #include "styles/style_layers.h" @@ -258,6 +260,7 @@ void ListWidget::selectionAction(SelectionAction action) { case SelectionAction::Clear: clearSelected(); return; case SelectionAction::Forward: forwardSelected(); return; case SelectionAction::Delete: deleteSelected(); return; + case SelectionAction::ToggleStoryPin: toggleStoryPinSelected(); return; } } @@ -335,6 +338,7 @@ auto ListWidget::collectSelectedItems() const -> SelectedItems { auto result = SelectedItem(item->globalId()); result.canDelete = selection.canDelete; result.canForward = selection.canForward; + result.canToggleStoryPin = selection.canToggleStoryPin; return result; }; auto transformation = [&](const auto &item) { @@ -349,6 +353,12 @@ auto ListWidget::collectSelectedItems() const -> SelectedItems { std::back_inserter(items.list), transformation); } + if (_controller->storiesPeer() && items.list.size() > 1) { + // Don't allow forwarding more than one story. + for (auto &entry : items.list) { + entry.canForward = false; + } + } return items; } @@ -1126,6 +1136,43 @@ void ListWidget::deleteSelected() { })); } +void ListWidget::toggleStoryPinSelected() { + auto list = std::vector(); + const auto confirmed = crl::guard(this, [=] { + clearSelected(); + }); + for (const auto &item : collectSelectedItems().list) { + const auto id = item.globalId.itemId; + if (IsStoryMsgId(id.msg)) { + list.push_back({ id.peer, StoryIdFromMsgId(id.msg) }); + } + } + const auto count = int(list.size()); + const auto pin = (_controller->storiesTab() == Stories::Tab::Archive); + const auto controller = _controller; + const auto sure = [=](Fn close) { + controller->session().data().stories().togglePinnedList(list, pin); + controller->showToast( + ::Media::Stories::PrepareTogglePinnedToast(count, pin)); + close(); + confirmed(); + }; + const auto session = &_controller->session(); + const auto onePhrase = pin + ? tr::lng_stories_save_sure + : tr::lng_stories_archive_sure; + const auto manyPhrase = pin + ? tr::lng_stories_save_sure_many + : tr::lng_stories_archive_sure_many; + _controller->parentController()->show(Ui::MakeConfirmBox({ + .text = (count == 1 + ? onePhrase() + : manyPhrase(lt_count, rpl::single(count) | tr::to_count())), + .confirmed = sure, + .confirmText = tr::lng_box_ok(), + })); +} + void ListWidget::deleteItem(GlobalMsgId globalId) { if (const auto item = MessageByGlobalId(globalId)) { auto items = SelectedItems(_provider->type()); @@ -1134,7 +1181,6 @@ void ListWidget::deleteItem(GlobalMsgId globalId) { item, FullSelection); items.list.back().canDelete = selectionData.canDelete; - items.list.back().canForward = selectionData.canForward; deleteItems(std::move(items)); } } @@ -1180,6 +1226,33 @@ void ListWidget::deleteItems(SelectedItems &&items, Fn confirmed) { .confirmText = tr::lng_box_delete(tr::now), .confirmStyle = &st::attentionBoxButton, }))); + } else if (_controller->storiesPeer()) { + auto list = std::vector(); + for (const auto &item : items.list) { + const auto id = item.globalId.itemId; + if (IsStoryMsgId(id.msg)) { + list.push_back({ id.peer, StoryIdFromMsgId(id.msg) }); + } + } + const auto session = &_controller->session(); + const auto sure = [=](Fn close) { + session->data().stories().deleteList(list); + close(); + if (confirmed) { + confirmed(); + } + }; + const auto count = int(list.size()); + window->show(Ui::MakeConfirmBox({ + .text = (count == 1 + ? tr::lng_stories_delete_one_sure() + : tr::lng_stories_delete_sure( + lt_count, + rpl::single(count) | tr::to_count())), + .confirmed = sure, + .confirmText = tr::lng_selected_delete(), + .confirmStyle = &st::attentionBoxButton, + })); } else if (auto list = collectSelectedIds(items); !list.empty()) { auto box = Box( &_controller->session(), diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.h b/Telegram/SourceFiles/info/media/info_media_list_widget.h index 701754c13..b518e212e 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_widget.h +++ b/Telegram/SourceFiles/info/media/info_media_list_widget.h @@ -189,6 +189,7 @@ private: void forwardItem(GlobalMsgId globalId); void forwardItems(MessageIdsList &&items); void deleteSelected(); + void toggleStoryPinSelected(); void deleteItem(GlobalMsgId globalId); void deleteItems(SelectedItems &&items, Fn confirmed = nullptr); void applyItemSelection( diff --git a/Telegram/SourceFiles/info/stories/info_stories_provider.cpp b/Telegram/SourceFiles/info/stories/info_stories_provider.cpp index 2c6621fff..c42569c8d 100644 --- a/Telegram/SourceFiles/info/stories/info_stories_provider.cpp +++ b/Telegram/SourceFiles/info/stories/info_stories_provider.cpp @@ -61,7 +61,7 @@ Type Provider::type() { } bool Provider::hasSelectRestriction() { - return true; // #TODO stories + return !_peer->isSelf(); } rpl::producer Provider::hasSelectRestrictionChanges() { @@ -312,8 +312,10 @@ ListItemSelectionData Provider::computeSelectionData( not_null item, TextSelection selection) { auto result = ListItemSelectionData(selection); - result.canDelete = item->canDelete(); - result.canForward = item->allowsForward(); + const auto peer = item->history()->peer; + result.canDelete = peer->isSelf(); + result.canForward = peer->isSelf(); + result.canToggleStoryPin = peer->isSelf(); return result; } @@ -334,9 +336,10 @@ void Provider::applyDragSelection( } } for (auto &layoutItem : _layouts) { - const auto id = StoryIdToMsgId(layoutItem.first); + const auto storyId = layoutItem.first; + const auto id = StoryIdToMsgId(storyId); if (id <= fromId && id > tillId) { - const auto i = _items.find(id); + const auto i = _items.find(storyId); Assert(i != end(_items)); const auto item = i->second.get(); ChangeItemSelection( diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 3259f0958..7ae534475 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -1309,21 +1309,7 @@ void Controller::togglePinnedRequested(bool pinned) { moveFromShown(); } story->owner().stories().togglePinnedList({ story->fullId() }, pinned); - uiShow()->showToast({ - .text = (pinned - ? tr::lng_stories_save_done( - tr::now, - Ui::Text::Bold).append( - '\n').append( - tr::lng_stories_save_done_about(tr::now)) - : tr::lng_stories_archive_done( - tr::now, - Ui::Text::WithEntities)), - .st = &st::storiesActionToast, - .duration = (pinned - ? Data::Stories::kPinnedToastDuration - : Ui::Toast::kDefaultDuration), - }); + uiShow()->showToast(PrepareTogglePinnedToast(1, pinned)); } void Controller::moveFromShown() { @@ -1375,4 +1361,34 @@ void Controller::startReactionAnimation( }, layer->lifetime()); } +Ui::Toast::Config PrepareTogglePinnedToast(int count, bool pinned) { + return { + .text = (pinned + ? (count == 1 + ? tr::lng_stories_save_done( + tr::now, + Ui::Text::Bold) + : tr::lng_stories_save_done_many( + tr::now, + lt_count, + count, + Ui::Text::Bold)).append( + '\n').append( + tr::lng_stories_save_done_about(tr::now)) + : (count == 1 + ? tr::lng_stories_archive_done( + tr::now, + Ui::Text::WithEntities) + : tr::lng_stories_archive_done_many( + tr::now, + lt_count, + count, + Ui::Text::WithEntities))), + .st = &st::storiesActionToast, + .duration = (pinned + ? Data::Stories::kPinnedToastDuration + : Ui::Toast::kDefaultDuration), + }; +} + } // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index 87c588f37..9bcbe4049 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -34,6 +34,10 @@ struct MessageSendingAnimationFrom; class EmojiFlyAnimation; } // namespace Ui +namespace Ui::Toast { +struct Config; +} // namespace Ui::Toast + namespace Main { class Session; } // namespace Main @@ -253,4 +257,8 @@ private: }; +[[nodiscard]] Ui::Toast::Config PrepareTogglePinnedToast( + int count, + bool pinned); + } // namespace Media::Stories