diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 40783dbab..154400cb6 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3800,6 +3800,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stories_views#other" = "{count} views"; "lng_stories_no_views" = "No views"; +"lng_stories_my_title" = "My Stories"; +"lng_stories_archive_button" = "Archive"; +"lng_stories_archive_title" = "Stories Archive"; + // Wnd specific "lng_wnd_choose_program_menu" = "Choose Default Program..."; diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index fc164eb17..972e3c69a 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -33,8 +33,8 @@ constexpr auto kMaxResolveTogether = 100; constexpr auto kIgnorePreloadAroundIfLoaded = 15; constexpr auto kPreloadAroundCount = 30; constexpr auto kMarkAsReadDelay = 3 * crl::time(1000); -constexpr auto kExpiredMineFirstPerPage = 30; -constexpr auto kExpiredMinePerPage = 100; +constexpr auto kArchiveFirstPerPage = 30; +constexpr auto kArchivePerPage = 100; constexpr auto kSavedFirstPerPage = 30; constexpr auto kSavedPerPage = 100; @@ -333,6 +333,9 @@ void Stories::apply(const MTPDupdateStory &data) { const auto wasInfo = i->second.info(); i->second.ids.emplace(idDates); const auto nowInfo = i->second.info(); + if (user->isSelf() && i->second.readTill < idDates.id) { + i->second.readTill = idDates.id; + } if (wasInfo == nowInfo) { return; } @@ -361,15 +364,44 @@ void Stories::apply(not_null peer, const MTPUserStories *data) { if (i != end(_stories)) { auto stories = base::take(i->second); _stories.erase(i); + + auto archiveChanged = false; + auto savedChanged = false; + if (peer->isSelf()) { + for (const auto &[id, story] : stories) { + if (_archive.list.remove(id)) { + archiveChanged = true; + if (_archiveTotal > 0) { + --_archiveTotal; + } + } + } + } + const auto j = _saved.find(peer->id); + const auto saved = (j != end(_saved)) ? &j->second : nullptr; for (const auto &[id, story] : stories) { // Duplicated in Stories::applyDeleted. _deleted.emplace(FullStoryId{ peer->id, id }); _expiring.remove(story->expires(), story->fullId()); + if (story->pinned() && saved) { + if (saved->ids.list.remove(id)) { + savedChanged = true; + if (saved->total > 0) { + --saved->total; + } + } + } session().changes().storyUpdated( story.get(), UpdateFlag::Destroyed); removeDependencyStory(story.get()); } + if (archiveChanged) { + _archiveChanged.fire({}); + } + if (savedChanged) { + _savedChanged.fire_copy(peer->id); + } } _sourceChanged.fire_copy(peer->id); } else { @@ -471,6 +503,8 @@ void Stories::parseAndApply(const MTPUserStories &stories) { if (result.ids.empty()) { applyDeletedFromSources(peerId, StorySourcesList::All); return; + } else if (user->isSelf()) { + result.readTill = result.ids.back().id; } const auto info = result.info(); const auto i = _all.find(peerId); @@ -526,15 +560,20 @@ Story *Stories::parseAndApply( auto &stories = _stories[peer->id]; const auto i = stories.find(id); if (i != end(stories)) { - if (i->second->applyChanges(*media, data)) { + const auto result = i->second.get(); + const auto pinned = result->pinned(); + if (result->applyChanges(*media, data)) { + if (result->pinned() != pinned) { + savedStateUpdated(result); + } session().changes().storyUpdated( - i->second.get(), + result, UpdateFlag::Edited); - if (const auto item = lookupItem(i->second.get())) { - item->applyChanges(i->second.get()); + if (const auto item = lookupItem(result)) { + item->applyChanges(result); } } - return i->second.get(); + return result; } const auto result = stories.emplace(id, std::make_unique( id, @@ -543,6 +582,19 @@ Story *Stories::parseAndApply( data.vdate().v, data.vexpire_date().v)).first->second.get(); result->applyChanges(*media, data); + if (result->pinned()) { + savedStateUpdated(result); + } + + if (peer->isSelf()) { + const auto added = _archive.list.emplace(id).second; + if (added) { + if (_archiveTotal >= 0 && id > _archiveLastId) { + ++_archiveTotal; + } + _archiveChanged.fire({}); + } + } if (expired) { _expiring.remove(expires, result->fullId()); @@ -617,6 +669,30 @@ void Stories::unregisterDependentMessage( } } +void Stories::savedStateUpdated(not_null story) { + const auto id = story->id(); + const auto peer = story->peer()->id; + const auto pinned = story->pinned(); + if (pinned) { + auto &saved = _saved[peer]; + const auto added = saved.ids.list.emplace(id).second; + if (added) { + if (saved.total >= 0 && id > saved.lastId) { + ++saved.total; + } + _savedChanged.fire_copy(peer); + } + } else if (const auto i = _saved.find(peer); i != end(_saved)) { + auto &saved = i->second; + if (saved.ids.list.remove(id)) { + if (saved.total > 0) { + --saved.total; + } + _savedChanged.fire_copy(peer); + } + } +} + void Stories::loadMore(StorySourcesList list) { const auto index = static_cast(list); if (_loadMoreRequestId[index] || _sourcesLoaded[index]) { @@ -744,20 +820,38 @@ void Stories::applyDeleted(FullStoryId id) { applyRemovedFromActive(id); _deleted.emplace(id); - const auto j = _stories.find(id.peer); - if (j != end(_stories)) { - const auto k = j->second.find(id.story); - if (k != end(j->second)) { + const auto i = _stories.find(id.peer); + if (i != end(_stories)) { + const auto j = i->second.find(id.story); + if (j != end(i->second)) { // Duplicated in Stories::apply(peer, const MTPUserStories*). - auto story = std::move(k->second); + auto story = std::move(j->second); _expiring.remove(story->expires(), story->fullId()); - j->second.erase(k); + i->second.erase(j); session().changes().storyUpdated( story.get(), UpdateFlag::Destroyed); removeDependencyStory(story.get()); - if (j->second.empty()) { - _stories.erase(j); + if (id.peer == session().userPeerId() + && _archive.list.remove(id.story)) { + if (_archiveTotal > 0) { + --_archiveTotal; + } + _archiveChanged.fire({}); + } + if (story->pinned()) { + if (const auto k = _saved.find(id.peer); k != end(_saved)) { + const auto saved = &k->second; + if (saved->ids.list.remove(id.story)) { + if (saved->total > 0) { + --saved->total; + } + _savedChanged.fire_copy(id.peer); + } + } + } + if (i->second.empty()) { + _stories.erase(i); } } } @@ -766,9 +860,7 @@ void Stories::applyDeleted(FullStoryId id) { void Stories::applyExpired(FullStoryId id) { if (const auto maybeStory = lookup(id)) { const auto story = *maybeStory; - if (story->peer()->isSelf()) { - addToExpiredMine(story); - } else if (!story->pinned()) { + if (!story->peer()->isSelf() && !story->pinned()) { applyDeleted(id); return; } @@ -776,13 +868,6 @@ void Stories::applyExpired(FullStoryId id) { applyRemovedFromActive(id); } -void Stories::addToExpiredMine(not_null story) { - const auto added = _expiredMine.list.emplace(story->id()).second; - if (added && _expiredMineTotal >= 0) { - ++_expiredMineTotal; - } -} - void Stories::applyRemovedFromActive(FullStoryId id) { const auto removeFromList = [&](StorySourcesList list) { const auto index = static_cast(list); @@ -1172,24 +1257,24 @@ void Stories::loadViewsSlice( }).send(); } -const StoriesIds &Stories::expiredMine() const { - return _expiredMine; +const StoriesIds &Stories::archive() const { + return _archive; } -rpl::producer<> Stories::expiredMineChanged() const { - return _expiredMineChanged.events(); +rpl::producer<> Stories::archiveChanged() const { + return _archiveChanged.events(); } -int Stories::expiredMineCount() const { - return std::max(_expiredMineTotal, 0); +int Stories::archiveCount() const { + return std::max(_archiveTotal, 0); } -bool Stories::expiredMineCountKnown() const { - return _expiredMineTotal >= 0; +bool Stories::archiveCountKnown() const { + return _archiveTotal >= 0; } -bool Stories::expiredMineLoaded() const { - return _expiredMineLoaded; +bool Stories::archiveLoaded() const { + return _archiveLoaded; } const StoriesIds *Stories::saved(PeerId peerId) const { @@ -1216,46 +1301,43 @@ bool Stories::savedLoaded(PeerId peerId) const { return (i != end(_saved)) && i->second.loaded; } -void Stories::expiredMineLoadMore() { - if (_expiredMineRequestId) { +void Stories::archiveLoadMore() { + if (_archiveRequestId || _archiveLoaded) { return; } const auto api = &_owner->session().api(); - _expiredMineRequestId = api->request(MTPstories_GetExpiredStories( - MTP_int(_expiredMineLastId), - MTP_int(_expiredMineLastId - ? kExpiredMinePerPage - : kExpiredMineFirstPerPage) + _archiveRequestId = api->request(MTPstories_GetStoriesArchive( + MTP_int(_archiveLastId), + MTP_int(_archiveLastId ? kArchivePerPage : kArchiveFirstPerPage) )).done([=](const MTPstories_Stories &result) { - _expiredMineRequestId = 0; + _archiveRequestId = 0; const auto &data = result.data(); const auto self = _owner->session().user(); const auto now = base::unixtime::now(); - _expiredMineTotal = data.vcount().v; + _archiveTotal = data.vcount().v; for (const auto &story : data.vstories().v) { const auto id = story.match([&](const auto &id) { return id.vid().v; }); - _expiredMine.list.emplace(id); - _expiredMineLastId = id; + _archive.list.emplace(id); + _archiveLastId = id; if (!parseAndApply(self, story, now)) { - _expiredMine.list.remove(id); - if (_expiredMineTotal > 0) { - --_expiredMineTotal; + _archive.list.remove(id); + if (_archiveTotal > 0) { + --_archiveTotal; } } } - _expiredMineTotal = std::max( - _expiredMineTotal, - int(_expiredMine.list.size())); - _expiredMineLoaded = data.vstories().v.empty(); - _expiredMineChanged.fire({}); + const auto ids = int(_archive.list.size()); + _archiveLoaded = data.vstories().v.empty(); + _archiveTotal = _archiveLoaded ? ids : std::max(_archiveTotal, ids); + _archiveChanged.fire({}); }).fail([=] { - _expiredMineRequestId = 0; - _expiredMineLoaded = true; - _expiredMineTotal = int(_expiredMine.list.size()); - _expiredMineChanged.fire({}); + _archiveRequestId = 0; + _archiveLoaded = true; + _archiveTotal = int(_archive.list.size()); + _archiveChanged.fire({}); }).send(); } @@ -1263,7 +1345,7 @@ void Stories::savedLoadMore(PeerId peerId) { Expects(peerIsUser(peerId)); auto &saved = _saved[peerId]; - if (saved.requestId) { + if (saved.requestId || saved.loaded) { return; } const auto api = &_owner->session().api(); @@ -1292,8 +1374,9 @@ void Stories::savedLoadMore(PeerId peerId) { } } } - saved.total = std::max(saved.total, int(saved.ids.list.size())); + const auto ids = int(saved.ids.list.size()); saved.loaded = data.vstories().v.empty(); + saved.total = saved.loaded ? ids : std::max(saved.total, ids); _savedChanged.fire_copy(peerId); }).fail([=] { auto &saved = _saved[peerId]; diff --git a/Telegram/SourceFiles/data/data_stories.h b/Telegram/SourceFiles/data/data_stories.h index 5d26f7474..a94b6ef12 100644 --- a/Telegram/SourceFiles/data/data_stories.h +++ b/Telegram/SourceFiles/data/data_stories.h @@ -230,12 +230,12 @@ public: std::optional offset, Fn)> done); - [[nodiscard]] const StoriesIds &expiredMine() const; - [[nodiscard]] rpl::producer<> expiredMineChanged() const; - [[nodiscard]] int expiredMineCount() const; - [[nodiscard]] bool expiredMineCountKnown() const; - [[nodiscard]] bool expiredMineLoaded() const; - void expiredMineLoadMore(); + [[nodiscard]] const StoriesIds &archive() const; + [[nodiscard]] rpl::producer<> archiveChanged() const; + [[nodiscard]] int archiveCount() const; + [[nodiscard]] bool archiveCountKnown() const; + [[nodiscard]] bool archiveLoaded() const; + void archiveLoadMore(); [[nodiscard]] const StoriesIds *saved(PeerId peerId) const; [[nodiscard]] rpl::producer savedChanged() const; @@ -273,6 +273,7 @@ private: void applyRemovedFromActive(FullStoryId id); void applyDeletedFromSources(PeerId id, StorySourcesList list); void removeDependencyStory(not_null story); + void savedStateUpdated(not_null story); void sort(StorySourcesList list); [[nodiscard]] std::shared_ptr lookupItem( @@ -282,7 +283,7 @@ private: void sendMarkAsReadRequest(not_null peer, StoryId tillId); void requestUserStories(not_null user); - void addToExpiredMine(not_null story); + void addToArchive(not_null story); void registerExpiring(TimeId expires, FullStoryId id); void scheduleExpireTimer(); void processExpired(); @@ -321,12 +322,12 @@ private: rpl::event_stream _sourceChanged; rpl::event_stream _itemsChanged; - StoriesIds _expiredMine; - int _expiredMineTotal = -1; - StoryId _expiredMineLastId = 0; - bool _expiredMineLoaded = false; - rpl::event_stream<> _expiredMineChanged; - mtpRequestId _expiredMineRequestId = 0; + StoriesIds _archive; + int _archiveTotal = -1; + StoryId _archiveLastId = 0; + bool _archiveLoaded = false; + rpl::event_stream<> _archiveChanged; + mtpRequestId _archiveRequestId = 0; std::unordered_map _saved; rpl::event_stream _savedChanged; diff --git a/Telegram/SourceFiles/data/data_stories_ids.cpp b/Telegram/SourceFiles/data/data_stories_ids.cpp index 610d51c3a..21d0878a6 100644 --- a/Telegram/SourceFiles/data/data_stories_ids.cpp +++ b/Telegram/SourceFiles/data/data_stories_ids.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_stories_ids.h" +#include "data/data_changes.h" #include "data/data_peer.h" #include "data/data_session.h" #include "data/data_stories.h" @@ -23,62 +24,108 @@ rpl::producer SavedStoriesIds( struct State { StoriesIdsSlice slice; + base::has_weak_ptr guard; + bool scheduled = false; }; const auto state = lifetime.make_state(); const auto push = [=] { + state->scheduled = false; + const auto stories = &peer->owner().stories(); if (!stories->savedCountKnown(peer->id)) { return; } - const auto source = stories->source(peer->id); const auto saved = stories->saved(peer->id); const auto count = stories->savedCount(peer->id); + const auto around = saved->list.lower_bound(aroundId); Assert(saved != nullptr); - auto ids = base::flat_set(); - ids.reserve(saved->list.size() + 1); - auto total = count; - if (source && !source->ids.empty()) { - ++total; - const auto current = source->ids.front().id; - for (const auto id : ranges::views::reverse(saved->list)) { - const auto i = source->ids.lower_bound( - StoryIdDates{ id }); - if (i != end(source->ids) && i->id == id) { - --total; - } else { + const auto source = stories->source(peer->id); + if (!source || source->ids.empty()) { + const auto hasBefore = int(around - begin(saved->list)); + const auto hasAfter = int(end(saved->list) - around); + if (hasAfter < limit) { + stories->savedLoadMore(peer->id); + } + const auto takeBefore = std::min(hasBefore, limit); + const auto takeAfter = std::min(hasAfter, limit); + auto ids = base::flat_set{ + std::make_reverse_iterator(around + takeAfter), + std::make_reverse_iterator(around - takeBefore) + }; + const auto added = int(ids.size()); + state->slice = StoriesIdsSlice( + std::move(ids), + count, + (hasBefore - takeBefore), + count - hasBefore - added); + } else { + auto ids = base::flat_set(); + auto added = 0; + auto skipped = 0; + auto skippedBefore = (around - begin(saved->list)); + auto skippedAfter = (end(saved->list) - around); + const auto &active = source->ids; + const auto process = [&](StoryId id) { + const auto i = active.lower_bound(StoryIdDates{ id }); + if (i == end(active) || i->id != id) { ids.emplace(id); + ++added; + } else { + ++skipped; + } + return (added < limit); + }; + ids.reserve(2 * limit + 1); + for (auto i = around, b = begin(saved->list); i != b;) { + --skippedBefore; + if (!process(*--i)) { + break; } } - ids.emplace(current); - } else { - auto all = saved->list | ranges::views::reverse; - ids = { begin(all), end(all) }; + if (ids.size() < limit) { + ids.emplace(active.back().id); // #TODO stories fake max story id + } else { + ++skippedBefore; + } + added = 0; + for (auto i = around, e = end(saved->list); i != e; ++i) { + --skippedAfter; + if (!process(*i)) { + break; + } + } + state->slice = StoriesIdsSlice( + std::move(ids), + count - skipped + 1, + skippedBefore, + skippedAfter); } - const auto added = int(ids.size()); - state->slice = StoriesIdsSlice( - std::move(ids), - total, - 0, - total - added); consumer.put_next_copy(state->slice); }; + const auto schedule = [=] { + if (state->scheduled) { + return; + } + state->scheduled = true; + Ui::PostponeCall(&state->guard, [=] { + if (state->scheduled) { + push(); + } + }); + }; const auto stories = &peer->owner().stories(); stories->sourceChanged( ) | rpl::filter( rpl::mappers::_1 == peer->id - ) | rpl::start_with_next([=] { - push(); - }, lifetime); + ) | rpl::start_with_next(schedule, lifetime); stories->savedChanged( ) | rpl::filter( rpl::mappers::_1 == peer->id - ) | rpl::start_with_next([=] { - push(); - }, lifetime); + ) | rpl::start_with_next(schedule, lifetime); if (!stories->savedCountKnown(peer->id)) { stories->savedLoadMore(peer->id); @@ -99,38 +146,59 @@ rpl::producer ArchiveStoriesIds( struct State { StoriesIdsSlice slice; + base::has_weak_ptr guard; + bool scheduled = false; }; const auto state = lifetime.make_state(); const auto push = [=] { + state->scheduled = false; + const auto stories = &session->data().stories(); - if (!stories->expiredMineCountKnown()) { + if (!stories->archiveCountKnown()) { return; } - const auto expired = stories->expiredMine(); - const auto count = stories->expiredMineCount(); - auto ids = base::flat_set(); - ids.reserve(expired.list.size() + 1); - auto all = expired.list | ranges::views::reverse; - ids = { begin(all), end(all) }; + const auto &archive = stories->archive(); + const auto count = stories->archiveCount(); + const auto i = archive.list.lower_bound(aroundId); + const auto hasBefore = int(i - begin(archive.list)); + const auto hasAfter = int(end(archive.list) - i); + if (hasAfter < limit) { + stories->archiveLoadMore(); + } + const auto takeBefore = std::min(hasBefore, limit); + const auto takeAfter = std::min(hasAfter, limit); + auto ids = base::flat_set{ + std::make_reverse_iterator(i + takeAfter), + std::make_reverse_iterator(i - takeBefore) + }; const auto added = int(ids.size()); state->slice = StoriesIdsSlice( std::move(ids), count, - 0, - count - added); + (hasBefore - takeBefore), + count - hasBefore - added); consumer.put_next_copy(state->slice); }; + const auto schedule = [=] { + if (state->scheduled) { + return; + } + state->scheduled = true; + Ui::PostponeCall(&state->guard, [=] { + if (state->scheduled) { + push(); + } + }); + }; const auto stories = &session->data().stories(); - stories->expiredMineChanged( - ) | rpl::start_with_next([=] { - push(); - }, lifetime); + stories->archiveChanged( + ) | rpl::start_with_next(schedule, lifetime); - if (!stories->expiredMineCountKnown()) { - stories->expiredMineLoadMore(); + if (!stories->archiveCountKnown()) { + stories->archiveLoadMore(); } push(); diff --git a/Telegram/SourceFiles/info/info_controller.cpp b/Telegram/SourceFiles/info/info_controller.cpp index a9c57a75f..d7beb981e 100644 --- a/Telegram/SourceFiles/info/info_controller.cpp +++ b/Telegram/SourceFiles/info/info_controller.cpp @@ -266,7 +266,8 @@ bool Controller::validateMementoPeer( not_null memento) const { return memento->peer() == peer() && memento->migratedPeerId() == migratedPeerId() - && memento->settingsSelf() == settingsSelf(); + && memento->settingsSelf() == settingsSelf() + && memento->storiesPeer() == storiesPeer(); } void Controller::setSection(not_null memento) { diff --git a/Telegram/SourceFiles/info/media/info_media_buttons.h b/Telegram/SourceFiles/info/media/info_media_buttons.h index b460edada..8ba60b121 100644 --- a/Telegram/SourceFiles/info/media/info_media_buttons.h +++ b/Telegram/SourceFiles/info/media/info_media_buttons.h @@ -131,13 +131,13 @@ inline auto AddStoriesButton( not_null navigation, not_null user, Ui::MultiSlideTracker &tracker) { - auto count = Data::SavedStoriesIds( + auto count = rpl::single(0) | rpl::then(Data::SavedStoriesIds( user, ServerMaxStoryId - 1, 0 ) | rpl::map([](const Data::StoriesIdsSlice &slice) { - return slice.fullCount(); - }) | rpl::filter_optional(); + return slice.fullCount().value_or(0); + })); auto result = AddCountedButton( parent, std::move(count), diff --git a/Telegram/SourceFiles/info/media/info_media_inner_widget.h b/Telegram/SourceFiles/info/media/info_media_inner_widget.h index a7a2ce61d..4e305de40 100644 --- a/Telegram/SourceFiles/info/media/info_media_inner_widget.h +++ b/Telegram/SourceFiles/info/media/info_media_inner_widget.h @@ -20,10 +20,10 @@ class SearchFieldController; } // namespace Ui namespace Info { - class Controller; +} // namespace Info -namespace Media { +namespace Info::Media { class Memento; class ListWidget; @@ -86,5 +86,4 @@ private: }; -} // namespace Media -} // namespace Info +} // namespace Info::Media diff --git a/Telegram/SourceFiles/info/stories/info_stories_inner_widget.cpp b/Telegram/SourceFiles/info/stories/info_stories_inner_widget.cpp index a7739a77b..fe32be04d 100644 --- a/Telegram/SourceFiles/info/stories/info_stories_inner_widget.cpp +++ b/Telegram/SourceFiles/info/stories/info_stories_inner_widget.cpp @@ -7,10 +7,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/stories/info_stories_inner_widget.h" -#include "info/stories/info_stories_widget.h" +#include "data/data_peer.h" #include "info/media/info_media_list_widget.h" +#include "info/profile/info_profile_icon.h" +#include "info/stories/info_stories_widget.h" #include "info/info_controller.h" +#include "info/info_memento.h" +#include "lang/lang_keys.h" +#include "settings/settings_common.h" +#include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" +#include "ui/wrap/vertical_layout.h" #include "styles/style_info.h" namespace Info::Stories { @@ -83,6 +90,49 @@ InnerWidget::InnerWidget( _list = setupList(); } +void InnerWidget::setupArchive() { + const auto key = _controller->key(); + const auto peer = key.storiesPeer(); + if (peer + && peer->isSelf() + && key.storiesTab() == Stories::Tab::Saved + && _isStackBottom) { + createArchiveButton(); + } else { + _archive.destroy(); + refreshHeight(); + } +} + +void InnerWidget::createArchiveButton() { + _archive.create(this); + _archive->show(); + + const auto button = ::Settings::AddButton( + _archive, + tr::lng_stories_archive_button(), + st::infoSharedMediaButton); + button->addClickHandler([=] { + _controller->showSection(Info::Stories::Make( + _controller->key().storiesPeer(), + Stories::Tab::Archive)); + }); + object_ptr( + button, + st::infoIconMediaGroup, + st::infoSharedMediaButtonIconPosition)->show(); + _archive->add(object_ptr( + _archive, + st::infoProfileSkip)); + _archive->add(object_ptr(_archive)); + + _archive->resizeToWidth(width()); + _archive->heightValue( + ) | rpl::start_with_next([=] { + refreshHeight(); + }, _archive->lifetime()); +} + void InnerWidget::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { @@ -144,6 +194,9 @@ int InnerWidget::resizeGetHeight(int newWidth) { _inResize = true; auto guard = gsl::finally([this] { _inResize = false; }); + if (_archive) { + _archive->resizeToWidth(newWidth); + } _list->resizeToWidth(newWidth); _empty->resizeToWidth(newWidth); return recountHeight(); @@ -158,6 +211,10 @@ void InnerWidget::refreshHeight() { int InnerWidget::recountHeight() { auto top = 0; + if (_archive) { + _archive->moveToLeft(0, top); + top += _archive->heightNoMargins() - st::lineWidth; + } auto listHeight = 0; if (_list) { _list->moveToLeft(0, top); diff --git a/Telegram/SourceFiles/info/stories/info_stories_inner_widget.h b/Telegram/SourceFiles/info/stories/info_stories_inner_widget.h index f874b00f2..e683013fe 100644 --- a/Telegram/SourceFiles/info/stories/info_stories_inner_widget.h +++ b/Telegram/SourceFiles/info/stories/info_stories_inner_widget.h @@ -38,6 +38,10 @@ public: not_null controller); bool showInternal(not_null memento); + void setIsStackBottom(bool isStackBottom) { + _isStackBottom = isStackBottom; + setupArchive(); + } void saveState(not_null memento); void restoreState(not_null memento); @@ -60,14 +64,19 @@ private: int recountHeight(); void refreshHeight(); + void setupArchive(); + void createArchiveButton(); + object_ptr setupList(); const not_null _controller; + object_ptr _archive = { nullptr }; object_ptr _list = { nullptr }; object_ptr _empty; bool _inResize = false; + bool _isStackBottom = false; rpl::event_stream _scrollToRequests; rpl::event_stream> _selectedLists; diff --git a/Telegram/SourceFiles/info/stories/info_stories_widget.cpp b/Telegram/SourceFiles/info/stories/info_stories_widget.cpp index 67b8f602b..bca32b356 100644 --- a/Telegram/SourceFiles/info/stories/info_stories_widget.cpp +++ b/Telegram/SourceFiles/info/stories/info_stories_widget.cpp @@ -20,8 +20,8 @@ Memento::Memento(not_null controller) , _media(controller) { } -Memento::Memento(not_null peer) -: ContentMemento(Tag{ peer }) +Memento::Memento(not_null peer, Tab tab) +: ContentMemento(Tag{ peer, tab }) , _media(peer, 0, Media::Type::PhotoVideo) { } @@ -54,13 +54,21 @@ Widget::Widget( }, _inner->lifetime()); } +void Widget::setIsStackBottom(bool isStackBottom) { + ContentWidget::setIsStackBottom(isStackBottom); + _inner->setIsStackBottom(isStackBottom); +} + bool Widget::showInternal(not_null memento) { if (!controller()->validateMementoPeer(memento)) { return false; } if (auto storiesMemento = dynamic_cast(memento.get())) { - restoreState(storiesMemento); - return true; + const auto tab = controller()->key().storiesTab(); + if (storiesMemento->storiesTab() == tab) { + restoreState(storiesMemento); + return true; + } } return false; } @@ -98,14 +106,16 @@ void Widget::selectionAction(SelectionAction action) { } rpl::producer Widget::title() { - return tr::lng_menu_my_stories(); // #TODO stories + return (controller()->key().storiesTab() == Tab::Archive) + ? tr::lng_stories_archive_title() + : tr::lng_stories_my_title(); } -std::shared_ptr Make(not_null peer) { +std::shared_ptr Make(not_null peer, Tab tab) { return std::make_shared( std::vector>( 1, - std::make_shared(peer))); + std::make_shared(peer, tab))); } } // namespace Info::Stories diff --git a/Telegram/SourceFiles/info/stories/info_stories_widget.h b/Telegram/SourceFiles/info/stories/info_stories_widget.h index 17e96a1d6..45df73f2b 100644 --- a/Telegram/SourceFiles/info/stories/info_stories_widget.h +++ b/Telegram/SourceFiles/info/stories/info_stories_widget.h @@ -13,11 +13,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Info::Stories { class InnerWidget; +enum class Tab; class Memento final : public ContentMemento { public: Memento(not_null controller); - Memento(not_null peer); + Memento(not_null peer, Tab tab); ~Memento(); object_ptr createWidget( @@ -43,6 +44,8 @@ class Widget final : public ContentWidget { public: Widget(QWidget *parent, not_null controller); + void setIsStackBottom(bool isStackBottom) override; + bool showInternal( not_null memento) override; @@ -65,6 +68,8 @@ private: }; -[[nodiscard]] std::shared_ptr Make(not_null peer); +[[nodiscard]] std::shared_ptr Make( + not_null peer, + Tab tab = {}); } // namespace Info::Stories diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index c51984f6a..577a291a1 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -2093,7 +2093,7 @@ stories.togglePinned#51602944 id:Vector pinned:Bool = Vector; stories.getAllStories#eeb0d625 flags:# next:flags.1?true include_hidden:flags.2?true state:flags.0?string = stories.AllStories; stories.getUserStories#96d528e0 user_id:InputUser = stories.UserStories; stories.getPinnedStories#b471137 user_id:InputUser offset_id:int limit:int = stories.Stories; -stories.getExpiredStories#8f792f2 offset_id:int limit:int = stories.Stories; +stories.getStoriesArchive#1f5bc5d2 offset_id:int limit:int = stories.Stories; stories.getStoriesByID#6a15cf46 user_id:InputUser id:Vector = stories.Stories; stories.readStories#edc5105b user_id:InputUser max_id:int = Vector; stories.incrementStoryViews#22126127 user_id:InputUser id:Vector = Bool; diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 17aa1c405..bfdc586d6 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -104,10 +104,7 @@ settingsIconMinus: icon {{ "settings/minus", settingsIconFg }}; settingsIconTimer: icon {{ "settings/timer", settingsIconFg }}; settingsIconLaptop: icon {{ "settings/laptop", settingsIconFg }}; settingsIconArrows: icon {{ "settings/arrows", settingsIconFg }}; -settingsIconOnline: icon {{ "settings/online", settingsIconFg }}; -settingsIconVideoCalls: icon {{ "settings/video_calls", settingsIconFg }}; settingsIconEmail: icon {{ "settings/email", settingsIconFg }}; -settingsIconForward: icon {{ "settings/forward", settingsIconFg }}; settingsIconSound: icon {{ "settings/sound", settingsIconFg }}; settingsIconDock: icon {{ "settings/dock", settingsIconFg }}; settingsIconPin: icon {{ "settings/pin", settingsIconFg }}; diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.cpp b/Telegram/SourceFiles/settings/settings_privacy_security.cpp index 7b8b3e73c..92f8229fe 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_security.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_security.cpp @@ -125,17 +125,15 @@ void AddPremiumPrivacyButton( not_null controller, not_null container, rpl::producer label, - IconDescriptor &&descriptor, Privacy::Key key, Fn()> controllerFactory) { const auto shower = Ui::CreateChild(container.get()); const auto session = &controller->session(); - const auto &st = st::settingsButton; + const auto &st = st::settingsButtonNoIcon; const auto button = AddButton( container, rpl::duplicate(label), - st, - std::move(descriptor)); + st); struct State { State(QWidget *parent) : widget(parent) { widget.setAttribute(Qt::WA_TransparentForMouseEvents); @@ -260,59 +258,50 @@ void SetupPrivacy( using Key = Privacy::Key; const auto add = [&]( rpl::producer label, - IconDescriptor &&descriptor, Key key, auto controllerFactory) { AddPrivacyButton( controller, container, std::move(label), - std::move(descriptor), + {}, key, controllerFactory); }; add( tr::lng_settings_phone_number_privacy(), - { &st::settingsIconCalls, kIconGreen }, Key::PhoneNumber, [=] { return std::make_unique( controller); }); add( tr::lng_settings_last_seen(), - { &st::settingsIconOnline, kIconLightBlue }, Key::LastSeen, [=] { return std::make_unique(session); }); add( tr::lng_settings_profile_photo_privacy(), - { &st::settingsIconAccount, kIconRed }, Key::ProfilePhoto, [] { return std::make_unique(); }); add( tr::lng_settings_forwards_privacy(), - { &st::settingsIconForward, kIconLightOrange }, Key::Forwards, [=] { return std::make_unique( controller); }); add( tr::lng_settings_calls(), - { &st::settingsIconVideoCalls, kIconGreen }, Key::Calls, [] { return std::make_unique(); }); add( tr::lng_settings_groups_invite(), - { &st::settingsIconGroup, kIconDarkBlue }, Key::Invites, [] { return std::make_unique(); }); AddPremiumPrivacyButton( controller, container, tr::lng_settings_voices_privacy(), - { &st::settingsPremiumIconVoice, kIconRed }, Key::Voices, [=] { return std::make_unique(session); }); add( tr::lng_settings_bio_privacy(), - { &st::settingsIconAccount, kIconDarkOrange }, Key::About, [] { return std::make_unique(); }); @@ -832,7 +821,7 @@ void AddPrivacyButton( container, std::move(label), PrivacyString(session, key), - st::settingsButton, + st::settingsButtonNoIcon, std::move(descriptor) )->addClickHandler([=] { *shower = session->api().userPrivacy().value( diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index b2f213527..227a00d42 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -775,18 +775,15 @@ void MainMenu::setupMenu() { kIconLightOrange }))); const auto stories = &controller->session().data().stories(); - const auto mine = stories->source( - controller->session().userPeerId()); - if ((mine && !mine->ids.empty()) - || stories->expiredMineCount() > 0) { + if (stories->archiveCount() > 0) { wrap->toggle(true, anim::type::instant); } else { wrap->toggle(false, anim::type::instant); - if (!stories->expiredMineCountKnown()) { - stories->expiredMineLoadMore(); - wrap->toggleOn(stories->expiredMineChanged( + if (!stories->archiveCountKnown()) { + stories->archiveLoadMore(); + wrap->toggleOn(stories->archiveChanged( ) | rpl::map([=] { - return stories->expiredMineCount() > 0; + return stories->archiveCount() > 0; }) | rpl::filter(rpl::mappers::_1) | rpl::take(1)); } }