Support correct saved stories / archive loading.

This commit is contained in:
John Preston 2023-06-07 19:56:38 +04:00
parent 7f8a985067
commit 5e5b252f2f
15 changed files with 382 additions and 162 deletions

View File

@ -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...";

View File

@ -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<PeerData*> 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<Story>(
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*> 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<int>(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*> 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<int>(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];

View File

@ -230,12 +230,12 @@ public:
std::optional<StoryView> offset,
Fn<void(std::vector<StoryView>)> 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<PeerId> savedChanged() const;
@ -273,6 +273,7 @@ private:
void applyRemovedFromActive(FullStoryId id);
void applyDeletedFromSources(PeerId id, StorySourcesList list);
void removeDependencyStory(not_null<Story*> story);
void savedStateUpdated(not_null<Story*> story);
void sort(StorySourcesList list);
[[nodiscard]] std::shared_ptr<HistoryItem> lookupItem(
@ -282,7 +283,7 @@ private:
void sendMarkAsReadRequest(not_null<PeerData*> peer, StoryId tillId);
void requestUserStories(not_null<UserData*> user);
void addToExpiredMine(not_null<Story*> story);
void addToArchive(not_null<Story*> story);
void registerExpiring(TimeId expires, FullStoryId id);
void scheduleExpireTimer();
void processExpired();
@ -321,12 +322,12 @@ private:
rpl::event_stream<PeerId> _sourceChanged;
rpl::event_stream<PeerId> _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<PeerId, Saved> _saved;
rpl::event_stream<PeerId> _savedChanged;

View File

@ -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<StoriesIdsSlice> SavedStoriesIds(
struct State {
StoriesIdsSlice slice;
base::has_weak_ptr guard;
bool scheduled = false;
};
const auto state = lifetime.make_state<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<StoryId>();
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<StoryId>{
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<StoryId>();
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<StoriesIdsSlice> ArchiveStoriesIds(
struct State {
StoriesIdsSlice slice;
base::has_weak_ptr guard;
bool scheduled = false;
};
const auto state = lifetime.make_state<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<StoryId>();
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<StoryId>{
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();

View File

@ -266,7 +266,8 @@ bool Controller::validateMementoPeer(
not_null<ContentMemento*> memento) const {
return memento->peer() == peer()
&& memento->migratedPeerId() == migratedPeerId()
&& memento->settingsSelf() == settingsSelf();
&& memento->settingsSelf() == settingsSelf()
&& memento->storiesPeer() == storiesPeer();
}
void Controller::setSection(not_null<ContentMemento*> memento) {

View File

@ -131,13 +131,13 @@ inline auto AddStoriesButton(
not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> 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),

View File

@ -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

View File

@ -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<Profile::FloatingIcon>(
button,
st::infoIconMediaGroup,
st::infoSharedMediaButtonIconPosition)->show();
_archive->add(object_ptr<Ui::FixedHeightWidget>(
_archive,
st::infoProfileSkip));
_archive->add(object_ptr<Ui::BoxContentDivider>(_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);

View File

@ -38,6 +38,10 @@ public:
not_null<Controller*> controller);
bool showInternal(not_null<Memento*> memento);
void setIsStackBottom(bool isStackBottom) {
_isStackBottom = isStackBottom;
setupArchive();
}
void saveState(not_null<Memento*> memento);
void restoreState(not_null<Memento*> memento);
@ -60,14 +64,19 @@ private:
int recountHeight();
void refreshHeight();
void setupArchive();
void createArchiveButton();
object_ptr<Media::ListWidget> setupList();
const not_null<Controller*> _controller;
object_ptr<Ui::VerticalLayout> _archive = { nullptr };
object_ptr<Media::ListWidget> _list = { nullptr };
object_ptr<EmptyWidget> _empty;
bool _inResize = false;
bool _isStackBottom = false;
rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
rpl::event_stream<rpl::producer<SelectedItems>> _selectedLists;

View File

@ -20,8 +20,8 @@ Memento::Memento(not_null<Controller*> controller)
, _media(controller) {
}
Memento::Memento(not_null<PeerData*> peer)
: ContentMemento(Tag{ peer })
Memento::Memento(not_null<PeerData*> 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<ContentMemento*> memento) {
if (!controller()->validateMementoPeer(memento)) {
return false;
}
if (auto storiesMemento = dynamic_cast<Memento*>(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<QString> 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<Info::Memento> Make(not_null<PeerData*> peer) {
std::shared_ptr<Info::Memento> Make(not_null<PeerData*> peer, Tab tab) {
return std::make_shared<Info::Memento>(
std::vector<std::shared_ptr<ContentMemento>>(
1,
std::make_shared<Memento>(peer)));
std::make_shared<Memento>(peer, tab)));
}
} // namespace Info::Stories

View File

@ -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*> controller);
Memento(not_null<PeerData*> peer);
Memento(not_null<PeerData*> peer, Tab tab);
~Memento();
object_ptr<ContentWidget> createWidget(
@ -43,6 +44,8 @@ class Widget final : public ContentWidget {
public:
Widget(QWidget *parent, not_null<Controller*> controller);
void setIsStackBottom(bool isStackBottom) override;
bool showInternal(
not_null<ContentMemento*> memento) override;
@ -65,6 +68,8 @@ private:
};
[[nodiscard]] std::shared_ptr<Info::Memento> Make(not_null<PeerData*> peer);
[[nodiscard]] std::shared_ptr<Info::Memento> Make(
not_null<PeerData*> peer,
Tab tab = {});
} // namespace Info::Stories

View File

@ -2093,7 +2093,7 @@ stories.togglePinned#51602944 id:Vector<int> pinned:Bool = Vector<int>;
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<int> = stories.Stories;
stories.readStories#edc5105b user_id:InputUser max_id:int = Vector<int>;
stories.incrementStoryViews#22126127 user_id:InputUser id:Vector<int> = Bool;

View File

@ -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 }};

View File

@ -125,17 +125,15 @@ void AddPremiumPrivacyButton(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container,
rpl::producer<QString> label,
IconDescriptor &&descriptor,
Privacy::Key key,
Fn<std::unique_ptr<EditPrivacyController>()> controllerFactory) {
const auto shower = Ui::CreateChild<rpl::lifetime>(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<QString> 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<PhoneNumberPrivacyController>(
controller); });
add(
tr::lng_settings_last_seen(),
{ &st::settingsIconOnline, kIconLightBlue },
Key::LastSeen,
[=] { return std::make_unique<LastSeenPrivacyController>(session); });
add(
tr::lng_settings_profile_photo_privacy(),
{ &st::settingsIconAccount, kIconRed },
Key::ProfilePhoto,
[] { return std::make_unique<ProfilePhotoPrivacyController>(); });
add(
tr::lng_settings_forwards_privacy(),
{ &st::settingsIconForward, kIconLightOrange },
Key::Forwards,
[=] { return std::make_unique<ForwardsPrivacyController>(
controller); });
add(
tr::lng_settings_calls(),
{ &st::settingsIconVideoCalls, kIconGreen },
Key::Calls,
[] { return std::make_unique<CallsPrivacyController>(); });
add(
tr::lng_settings_groups_invite(),
{ &st::settingsIconGroup, kIconDarkBlue },
Key::Invites,
[] { return std::make_unique<GroupsInvitePrivacyController>(); });
AddPremiumPrivacyButton(
controller,
container,
tr::lng_settings_voices_privacy(),
{ &st::settingsPremiumIconVoice, kIconRed },
Key::Voices,
[=] { return std::make_unique<VoicesPrivacyController>(session); });
add(
tr::lng_settings_bio_privacy(),
{ &st::settingsIconAccount, kIconDarkOrange },
Key::About,
[] { return std::make_unique<AboutPrivacyController>(); });
@ -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(

View File

@ -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));
}
}