Implement story album management.

This commit is contained in:
John Preston
2025-07-28 22:29:39 +04:00
parent d4d4f3c082
commit 8d2a7b00dc
25 changed files with 470 additions and 158 deletions

View File

@@ -477,6 +477,19 @@ Story *Stories::parseAndApply(
return nullptr;
}
const auto id = data.vid().v;
auto albumInfo = (Albums*)nullptr;
auto list = std::optional<base::flat_set<int>>();
if (const auto albums = data.valbums()) {
list.emplace();
if (!albums->v.empty()) {
albumInfo = &_albums[peer->id];
list->reserve(albums->v.size());
for (const auto &albumId : albums->v) {
albumInfo->sets[albumId.v].albumKnownInArchive.emplace(id);
list->emplace(albumId.v);
}
}
}
const auto fullId = FullStoryId{ peer->id, id };
auto &stories = _stories[peer->id];
const auto i = stories.find(id);
@@ -484,6 +497,21 @@ Story *Stories::parseAndApply(
const auto result = i->second.get();
const auto mediaChanged = (result->media() != *media);
result->applyChanges(*media, data, now);
if (list) {
const auto &was = result->albumIds();
if (*list != was) {
if (!albumInfo && !was.empty()) {
albumInfo = &_albums[peer->id];
}
for (const auto wasId : result->albumIds()) {
if (!list->contains(wasId)) {
albumInfo->sets[wasId].albumKnownInArchive.remove(id);
}
}
result->setAlbumIds(*base::take(list));
}
}
const auto j = _pollingSettings.find(result);
if (j != end(_pollingSettings)) {
maybeSchedulePolling(result, j->second, now);
@@ -508,6 +536,9 @@ Story *Stories::parseAndApply(
data,
now
)).first->second.get();
if (list) {
result->setAlbumIds(*base::take(list));
}
if (const auto archive = lookupArchive(peer)) {
const auto added = archive->ids.list.emplace(id).second;
@@ -827,6 +858,7 @@ void Stories::applyDeleted(not_null<PeerData*> peer, StoryId id) {
removeDependencyStory(story.get());
const auto removeFromAlbum = [&](int albumId) {
if (const auto set = albumIdsSet(peerId, albumId, true)) {
set->albumKnownInArchive.remove(id);
if (set->ids.list.remove(id)) {
if (set->total > 0) {
--set->total;
@@ -1669,9 +1701,18 @@ bool Stories::albumIdsLoaded(PeerId peerId, int albumId) const {
}
void Stories::albumIdsLoadMore(PeerId peerId, int albumId) {
albumIdsLoadMore(peerId, albumId, false);
}
void Stories::albumIdsLoadMore(PeerId peerId, int albumId, bool reload) {
Expects(!reload || albumId > 0);
const auto peer = _owner->peer(peerId);
const auto set = albumIdsSet(peerId, albumId);
if (!set || set->requestId || set->loaded) {
if (set && reload) {
_owner->session().api().request(base::take(set->requestId)).cancel();
}
if (!set || set->requestId || (!reload && set->loaded)) {
return;
}
const auto api = &_owner->session().api();
@@ -1681,6 +1722,11 @@ void Stories::albumIdsLoadMore(PeerId peerId, int albumId) {
return;
}
set->requestId = 0;
if (reload) {
set->ids.list.clear();
set->ids.pinnedToTop.clear();
set->lastId = StoryId();
}
const auto &data = result.data();
const auto now = base::unixtime::now();
auto pinnedToTopIds = data.vpinned_to_top().value_or_empty();
@@ -1734,11 +1780,28 @@ void Stories::albumIdsLoadMore(PeerId peerId, int albumId) {
: api->request(MTPstories_GetAlbumStories(
peer->input,
MTP_int(albumId),
MTP_int(set->lastId),
MTP_int(set->lastId ? kSavedPerPage : kSavedFirstPerPage)
MTP_int(reload ? 0 : set->ids.list.size()),
MTP_int((reload || set->ids.list.empty())
? kSavedFirstPerPage
: kSavedPerPage)
)).done(done).fail(fail).send();
}
const base::flat_set<StoryId> &Stories::albumKnownInArchive(
PeerId peerId,
int albumId) const {
static const auto empty = base::flat_set<StoryId>();
const auto i = _albums.find(peerId);
if (i == end(_albums)) {
return empty;
}
const auto j = i->second.sets.find(albumId);
return (j != end(i->second.sets))
? j->second.albumKnownInArchive
: empty;
}
auto Stories::albumsListValue(PeerId peerId)
-> rpl::producer<std::vector<Data::StoryAlbum>> {
auto &albums = _albums[peerId];
@@ -1839,6 +1902,33 @@ void Stories::albumRename(
}
void Stories::notifyAlbumUpdate(StoryAlbumUpdate &&update) {
const auto peerId = update.peer->id;
const auto i = _albums.find(peerId);
if (i != end(_albums)) {
const auto albumId = update.albumId;
const auto j = i->second.sets.find(albumId);
if (j != end(i->second.sets)) {
for (const auto &id : update.added) {
j->second.albumKnownInArchive.emplace(id);
if (const auto story = lookup({ peerId, id })) {
auto now = (*story)->albumIds();
if (now.emplace(albumId).second) {
(*story)->setAlbumIds(std::move(now));
}
}
}
for (const auto &id : update.removed) {
j->second.albumKnownInArchive.remove(id);
if (const auto story = lookup({ peerId, id })) {
auto now = (*story)->albumIds();
if (now.remove(albumId)) {
(*story)->setAlbumIds(std::move(now));
}
}
}
albumIdsLoadMore(peerId, albumId, true);
}
}
_albumUpdates.fire(std::move(update));
}

View File

@@ -213,10 +213,13 @@ public:
PeerId peerId,
int albumId) const;
[[nodiscard]] rpl::producer<StoryAlbumIdsKey> albumIdsChanged() const;
[[nodiscard]] int albumIdsCount(PeerId peerIdl, int albumId) const;
[[nodiscard]] int albumIdsCount(PeerId peerId, int albumId) const;
[[nodiscard]] bool albumIdsCountKnown(PeerId peerId, int albumId) const;
[[nodiscard]] bool albumIdsLoaded(PeerId peerId, int albumId) const;
void albumIdsLoadMore(PeerId peerId, int albumId);
[[nodiscard]] const base::flat_set<StoryId> &albumKnownInArchive(
PeerId peerId,
int albumId) const;
[[nodiscard]] auto albumsListValue(PeerId peerId)
-> rpl::producer<std::vector<Data::StoryAlbum>>;
@@ -286,6 +289,7 @@ public:
private:
struct Set {
StoriesIds ids;
base::flat_set<StoryId> albumKnownInArchive;
int total = -1;
StoryId lastId = 0;
bool loaded = false;
@@ -307,6 +311,7 @@ private:
DirectRequest,
};
void albumIdsLoadMore(PeerId peerId, int albumId, bool reload);
void parseAndApply(const MTPPeerStories &stories, ParseSource source);
[[nodiscard]] Story *parseAndApply(
not_null<PeerData*> peer,

View File

@@ -899,10 +899,14 @@ StoryId Story::repostSourceId() const {
return _repostSourceId;
}
const std::vector<int> &Story::albumIds() const {
const base::flat_set<int> &Story::albumIds() const {
return _albumIds;
}
void Story::setAlbumIds(base::flat_set<int> ids) {
_albumIds = std::move(ids);
}
PeerData *Story::fromPeer() const {
return _fromPeer;
}

View File

@@ -237,7 +237,8 @@ public:
[[nodiscard]] QString repostSourceName() const;
[[nodiscard]] StoryId repostSourceId() const;
[[nodiscard]] const std::vector<int> &albumIds() const;
[[nodiscard]] const base::flat_set<int> &albumIds() const;
void setAlbumIds(base::flat_set<int> ids);
[[nodiscard]] PeerData *fromPeer() const;
@@ -267,7 +268,7 @@ private:
PeerData * const _repostSourcePeer = nullptr;
const QString _repostSourceName;
const StoryId _repostSourceId = 0;
std::vector<int> _albumIds;
base::flat_set<int> _albumIds;
PeerData * const _fromPeer = nullptr;
Data::ReactionId _sentReactionId;
StoryMedia _media;

View File

@@ -16,8 +16,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document.h"
#include "data/data_media_types.h"
#include "data/data_session.h"
#include "main/main_session.h"
#include "main/main_account.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "history/history_item.h"
#include "history/history_item_helpers.h"
#include "history/history.h"
@@ -34,7 +35,8 @@ using namespace Media;
} // namespace
Provider::Provider(not_null<AbstractController*> controller)
: _controller(controller) {
: _controller(controller)
, _storiesAddToAlbumId(_controller->storiesAddToAlbumId()) {
style::PaletteChanged(
) | rpl::start_with_next([=] {
for (auto &layout : _layouts) {
@@ -485,6 +487,9 @@ void Provider::applyDragSelection(
return;
}
const auto search = !_queryWords.isEmpty();
const auto selectLimit = _storiesAddToAlbumId
? _controller->session().appConfig().storiesAlbumLimit()
: MaxSelectedItems;
auto chosen = base::flat_set<not_null<const HistoryItem*>>();
chosen.reserve(till - from);
for (auto i = from; i != till; ++i) {
@@ -496,7 +501,8 @@ void Provider::applyDragSelection(
ChangeItemSelection(
selected,
item,
computeSelectionData(item, FullSelection));
computeSelectionData(item, FullSelection),
selectLimit);
}
if (selected.size() != chosen.size()) {
for (auto i = begin(selected); i != end(selected);) {

View File

@@ -129,6 +129,7 @@ private:
std::optional<int> _fullCount;
base::flat_set<not_null<const HistoryItem*>> _downloading;
base::flat_set<not_null<const HistoryItem*>> _downloaded;
int _storiesAddToAlbumId = 0;
std::vector<Element> _addPostponed;

View File

@@ -477,7 +477,11 @@ Key ContentMemento::key() const {
} else if (const auto self = settingsSelf()) {
return Settings::Tag{ self };
} else if (const auto stories = storiesPeer()) {
return Stories::Tag{ stories, storiesAlbumId() };
return Stories::Tag{
stories,
storiesAlbumId(),
storiesAddToAlbumId(),
};
} else if (statisticsTag().peer) {
return statisticsTag();
} else if (const auto starref = starrefPeer()) {
@@ -521,7 +525,8 @@ ContentMemento::ContentMemento(Downloads::Tag downloads) {
ContentMemento::ContentMemento(Stories::Tag stories)
: _storiesPeer(stories.peer)
, _storiesAlbumId(stories.albumId) {
, _storiesAlbumId(stories.albumId)
, _storiesAddToAlbumId(stories.addingToAlbumId) {
}
ContentMemento::ContentMemento(Statistics::Tag statistics)

View File

@@ -250,6 +250,9 @@ public:
[[nodiscard]] int storiesAlbumId() const {
return _storiesAlbumId;
}
[[nodiscard]] int storiesAddToAlbumId() const {
return _storiesAddToAlbumId;
}
[[nodiscard]] Statistics::Tag statisticsTag() const {
return _statisticsTag;
}
@@ -315,6 +318,7 @@ private:
UserData * const _settingsSelf = nullptr;
PeerData * const _storiesPeer = nullptr;
int _storiesAlbumId = 0;
int _storiesAddToAlbumId = 0;
Statistics::Tag _statisticsTag;
PeerData * const _starrefPeer = nullptr;
BotStarRef::Type _starrefType = {};

View File

@@ -125,6 +125,13 @@ int Key::storiesAlbumId() const {
return 0;
}
int Key::storiesAddToAlbumId() const {
if (const auto tag = std::get_if<Stories::Tag>(&_value)) {
return tag->addingToAlbumId;
}
return 0;
}
Statistics::Tag Key::statisticsTag() const {
if (const auto tag = std::get_if<Statistics::Tag>(&_value)) {
return *tag;

View File

@@ -90,22 +90,24 @@ public:
Data::ReactionId selected,
FullMsgId contextId);
PeerData *peer() const;
Data::ForumTopic *topic() const;
Data::SavedSublist *sublist() const;
UserData *settingsSelf() const;
bool isDownloads() const;
bool isGlobalMedia() const;
PeerData *storiesPeer() const;
int storiesAlbumId() const;
Statistics::Tag statisticsTag() const;
PeerData *starrefPeer() const;
BotStarRef::Type starrefType() const;
PollData *poll() const;
FullMsgId pollContextId() const;
std::shared_ptr<Api::WhoReadList> reactionsWhoReadIds() const;
Data::ReactionId reactionsSelected() const;
FullMsgId reactionsContextId() const;
[[nodiscard]] PeerData *peer() const;
[[nodiscard]] Data::ForumTopic *topic() const;
[[nodiscard]] Data::SavedSublist *sublist() const;
[[nodiscard]] UserData *settingsSelf() const;
[[nodiscard]] bool isDownloads() const;
[[nodiscard]] bool isGlobalMedia() const;
[[nodiscard]] PeerData *storiesPeer() const;
[[nodiscard]] int storiesAlbumId() const;
[[nodiscard]] int storiesAddToAlbumId() const;
[[nodiscard]] Statistics::Tag statisticsTag() const;
[[nodiscard]] PeerData *starrefPeer() const;
[[nodiscard]] BotStarRef::Type starrefType() const;
[[nodiscard]] PollData *poll() const;
[[nodiscard]] FullMsgId pollContextId() const;
[[nodiscard]] auto reactionsWhoReadIds() const
-> std::shared_ptr<Api::WhoReadList>;
[[nodiscard]] Data::ReactionId reactionsSelected() const;
[[nodiscard]] FullMsgId reactionsContextId() const;
private:
struct PollKey {
@@ -229,6 +231,9 @@ public:
[[nodiscard]] int storiesAlbumId() const {
return key().storiesAlbumId();
}
[[nodiscard]] int storiesAddToAlbumId() const {
return key().storiesAddToAlbumId();
}
[[nodiscard]] Statistics::Tag statisticsTag() const {
return key().statisticsTag();
}

View File

@@ -312,7 +312,9 @@ void WrapWidget::forceContentRepaint() {
}
void WrapWidget::setupTop() {
if (HasCustomTopBar(_controller.get()) || wrap() == Wrap::Search) {
if (HasCustomTopBar(_controller.get())
|| wrap() == Wrap::Search
|| wrap() == Wrap::StoryAlbumEdit) {
_topBar.destroy();
return;
}
@@ -696,7 +698,13 @@ rpl::producer<int> WrapWidget::desiredHeightForContent() const {
}
rpl::producer<SelectedItems> WrapWidget::selectedListValue() const {
return _selectedLists.events() | rpl::flatten_latest();
auto current = _content
? _content->selectedListValue()
: nullptr;
return _selectedLists.events_starting_with(current
? std::move(current)
: rpl::single(SelectedItems(Storage::SharedMediaType::Photo))
) | rpl::flatten_latest();
}
object_ptr<ContentWidget> WrapWidget::createContent(

View File

@@ -53,6 +53,7 @@ enum class Wrap {
Narrow,
Side,
Search,
StoryAlbumEdit,
};
struct SelectedItem {
@@ -143,6 +144,8 @@ public:
return _removeRequests.events();
}
[[nodiscard]] rpl::producer<SelectedItems> selectedListValue() const;
void replaceSwipeHandler(Ui::Controls::SwipeHandlerArgs *incompleteArgs);
~WrapWidget();
@@ -206,7 +209,6 @@ private:
not_null<Window::SessionController*> window,
not_null<ContentMemento*> memento);
rpl::producer<SelectedItems> selectedListValue() const;
bool requireTopBarSearch() const;
void addTopBarMenuButton();

View File

@@ -31,7 +31,11 @@ UniversalMsgId GetUniversalId(not_null<const BaseLayout*> layout) {
bool ChangeItemSelection(
ListSelectedMap &selected,
not_null<const HistoryItem*> item,
ListItemSelectionData selectionData) {
ListItemSelectionData selectionData,
int limit) {
if (!limit) {
limit = MaxSelectedItems;
}
const auto changeExisting = [&](auto it) {
if (it == selected.cend()) {
return false;
@@ -41,7 +45,7 @@ bool ChangeItemSelection(
}
return false;
};
if (selected.size() < MaxSelectedItems) {
if (selected.size() < limit) {
const auto &[i, ok] = selected.try_emplace(item, selectionData);
if (ok) {
return true;

View File

@@ -90,7 +90,8 @@ using UniversalMsgId = MsgId;
bool ChangeItemSelection(
ListSelectedMap &selected,
not_null<const HistoryItem*> item,
ListItemSelectionData selectionData);
ListItemSelectionData selectionData,
int limit = 0);
class ListSectionDelegate {
public:

View File

@@ -47,8 +47,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/ui_utility.h"
#include "ui/inactive_press.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "main/main_account.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "base/platform/base_platform_info.h"
@@ -148,9 +149,10 @@ ListWidget::ListWidget(
, _controller(controller)
, _provider(MakeProvider(_controller))
, _dateBadge(std::make_unique<DateBadge>(
_provider->type(),
[=] { scrollDateCheck(); },
[=] { scrollDateHide(); })) {
_provider->type(),
[=] { scrollDateCheck(); },
[=] { scrollDateHide(); }))
, _storiesAddToAlbumId(controller->storiesAddToAlbumId()) {
start();
}
@@ -184,6 +186,7 @@ void ListWidget::start() {
_provider->setSearchQuery(std::move(query));
}, lifetime());
} else if (_controller->storiesPeer()) {
setupStoriesTrackIds();
trackSession(&session());
restart();
} else {
@@ -260,6 +263,41 @@ void ListWidget::setupSelectRestriction() {
}, lifetime());
}
void ListWidget::setupStoriesTrackIds() {
if (!_storiesAddToAlbumId) {
return;
}
const auto peerId = _controller->storiesPeer()->id;
const auto stories = &session().data().stories();
constexpr auto kArchive = Data::kStoriesAlbumIdArchive;
const auto key = Data::StoryAlbumIdsKey{ peerId, kArchive };
rpl::single(rpl::empty) | rpl::then(
stories->albumIdsChanged() | rpl::filter(
rpl::mappers::_1 == key
) | rpl::to_empty
) | rpl::start_with_next([=] {
const auto albumId = _storiesAddToAlbumId;
const auto &ids = stories->albumKnownInArchive(peerId, albumId);
if (_storiesInAlbum != ids) {
for (const auto id : ids) {
if (_storiesInAlbum.emplace(id).second) {
_storyMsgsToMarkSelected.emplace(StoryIdToMsgId(id));
}
}
if (_storiesInAlbum.size() > ids.size()) {
for (auto i = begin(_storiesInAlbum); i != end(_storiesInAlbum);) {
if (ids.contains(*i)) {
++i;
} else {
_storyMsgsToMarkSelected.remove(StoryIdToMsgId(*i));
i = _storiesInAlbum.erase(i);
}
}
}
}
}, lifetime());
}
rpl::producer<int> ListWidget::scrollToRequests() const {
return _scrollToRequests.events();
}
@@ -544,6 +582,37 @@ void ListWidget::trackSession(not_null<Main::Session*> session) {
}, lifetime);
}
void ListWidget::markStoryMsgsSelected() {
const auto now = int(_storyMsgsToMarkSelected.size());
const auto guard = gsl::finally([&] {
if (now != int(_storyMsgsToMarkSelected.size())) {
pushSelectedItems();
}
});
const auto &appConfig = _controller->session().appConfig();
const auto selectLimit = appConfig.storiesAlbumLimit();
const auto selection = FullSelection;
for (const auto &section : _sections) {
for (const auto &entry : section.items()) {
const auto item = entry->getItem();
const auto id = item->id;
const auto i = _storyMsgsToMarkSelected.find(id);
if (i != end(_storyMsgsToMarkSelected)) {
ChangeItemSelection(
_selected,
item,
_provider->computeSelectionData(item, selection),
selectLimit);
repaintItem(item);
_storyMsgsToMarkSelected.erase(i);
if (_storyMsgsToMarkSelected.empty()) {
return;
}
}
}
}
}
void ListWidget::refreshRows() {
saveScrollState();
@@ -554,6 +623,8 @@ void ListWidget::refreshRows() {
for (const auto &item : _sections.back().items()) {
trackSession(&item->getItem()->history()->session());
}
} else if (!_storyMsgsToMarkSelected.empty()) {
markStoryMsgsSelected();
}
if (const auto count = _provider->fullCount()) {
@@ -819,8 +890,9 @@ void ListWidget::paintEvent(QPaintEvent *e) {
const auto window = _controller->parentController();
const auto paused = window->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer);
const auto selecting = hasSelectedItems() || _storiesAddToAlbumId;
auto context = ListContext{
Overview::Layout::PaintContext(ms, hasSelectedItems(), paused),
Overview::Layout::PaintContext(ms, selecting, paused),
&_selected,
&_dragSelected,
_dragSelectAction
@@ -890,6 +962,9 @@ void ListWidget::mouseDoubleClickEvent(QMouseEvent *e) {
void ListWidget::showContextMenu(
QContextMenuEvent *e,
ContextMenuSource source) {
if (_storiesAddToAlbumId) {
return;
}
if (_contextMenu) {
_contextMenu = nullptr;
repaintItem(_contextItem);
@@ -1487,11 +1562,15 @@ void ListWidget::switchToWordSelection() {
void ListWidget::applyItemSelection(
HistoryItem *item,
TextSelection selection) {
const auto selectLimit = _storiesAddToAlbumId
? _controller->session().appConfig().storiesAlbumLimit()
: MaxSelectedItems;
if (item
&& ChangeItemSelection(
_selected,
item,
_provider->computeSelectionData(item, selection))) {
_provider->computeSelectionData(item, selection),
selectLimit)) {
repaintItem(item);
pushSelectedItems();
}
@@ -1961,22 +2040,21 @@ void ListWidget::mouseActionFinish(
auto pressState = base::take(_pressState);
repaintItem(pressState.item);
const auto selectionMode = hasSelectedItems() || _storiesAddToAlbumId;
auto simpleSelectionChange = pressState.item
&& pressState.inside
&& !_pressWasInactive
&& (button != Qt::RightButton)
&& (_mouseAction == MouseAction::PrepareDrag
|| _mouseAction == MouseAction::PrepareSelect);
auto needSelectionToggle = simpleSelectionChange
&& hasSelectedItems();
auto needSelectionClear = simpleSelectionChange
&& hasSelectedText();
auto needSelectionToggle = simpleSelectionChange && selectionMode;
auto needSelectionClear = simpleSelectionChange && hasSelectedText();
auto activated = ClickHandler::unpressed();
if (_mouseAction == MouseAction::Dragging
|| _mouseAction == MouseAction::Selecting) {
activated = nullptr;
} else if (needSelectionToggle) {
} else if (needSelectionToggle || _storiesAddToAlbumId) {
activated = nullptr;
}
@@ -2034,11 +2112,15 @@ void ListWidget::applyDragSelection() {
void ListWidget::applyDragSelection(SelectedMap &applyTo) const {
if (_dragSelectAction == DragSelectAction::Selecting) {
const auto selectLimit = _storiesAddToAlbumId
? _controller->session().appConfig().storiesAlbumLimit()
: MaxSelectedItems;
for (auto &[item, data] : _dragSelected) {
ChangeItemSelection(
applyTo,
item,
_provider->computeSelectionData(item, FullSelection));
_provider->computeSelectionData(item, FullSelection),
selectLimit);
}
} else if (_dragSelectAction == DragSelectAction::Deselecting) {
for (auto &[item, data] : _dragSelected) {

View File

@@ -171,6 +171,7 @@ private:
void itemLayoutChanged(not_null<const HistoryItem*> item);
void refreshRows();
void markStoryMsgsSelected();
void trackSession(not_null<Main::Session*> session);
[[nodiscard]] SelectedItems collectSelectedItems() const;
@@ -274,6 +275,8 @@ private:
void setActionBoxWeak(base::weak_qptr<Ui::BoxContent> box);
void setupStoriesTrackIds();
const not_null<AbstractController*> _controller;
const std::unique_ptr<ListProvider> _provider;
@@ -305,6 +308,10 @@ private:
const std::unique_ptr<DateBadge> _dateBadge;
int _storiesAddToAlbumId = 0;
base::flat_set<StoryId> _storiesInAlbum;
base::flat_set<MsgId> _storyMsgsToMarkSelected;
base::unique_qptr<Ui::PopupMenu> _contextMenu;
rpl::event_stream<> _checkForHide;
base::weak_qptr<Ui::BoxContent> _actionBoxWeak;

View File

@@ -12,13 +12,18 @@ namespace Info::Stories {
[[nodiscard]] int ArchiveId();
struct Tag {
explicit Tag(not_null<PeerData*> peer, int albumId = 0)
explicit Tag(
not_null<PeerData*> peer,
int albumId = 0,
int addingToAlbumId = 0)
: peer(peer)
, albumId(albumId) {
, albumId(albumId)
, addingToAlbumId(addingToAlbumId) {
}
not_null<PeerData*> peer;
int albumId = 0;
int addingToAlbumId = 0;
};
} // namespace Info::Stories

View File

@@ -47,6 +47,150 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_settings.h"
namespace Info::Stories {
namespace {
class EditAlbumBox final : public Ui::BoxContent {
public:
EditAlbumBox(
QWidget*,
not_null<Controller*> controller,
not_null<PeerData*> peer,
Fn<void()> reload,
int albumId);
private:
void prepare() override;
void resizeEvent(QResizeEvent *e) override;
const not_null<Window::SessionController*> _window;
const not_null<WrapWidget*> _content;
rpl::variable<Data::StoryAlbumUpdate> _changes;
Fn<void()> _reload;
bool _saving = false;
};
EditAlbumBox::EditAlbumBox(
QWidget*,
not_null<Controller*> controller,
not_null<PeerData*> peer,
Fn<void()> reload,
int albumId)
: _window(controller->parentController())
, _content(Ui::CreateChild<WrapWidget>(
this,
_window,
Wrap::StoryAlbumEdit,
std::make_shared<Info::Memento>(
std::vector<std::shared_ptr<ContentMemento>>(
1,
std::make_shared<Memento>(
peer,
Data::kStoriesAlbumIdArchive,
albumId))).get()))
, _changes(Data::StoryAlbumUpdate{ .peer = peer, .albumId = albumId })
, _reload(std::move(reload)) {
_content->selectedListValue(
) | rpl::start_with_next([=](const SelectedItems &selection) {
const auto stories = &_window->session().data().stories();
auto ids = stories->albumKnownInArchive(peer->id, albumId);
auto now = _changes.current();
now.added.clear();
now.added.reserve(selection.list.size());
now.removed.clear();
now.removed.reserve(ids.size());
for (const auto &entry : selection.list) {
const auto id = StoryIdFromMsgId(entry.globalId.itemId.msg);
if (!ids.remove(id)) {
now.added.push_back(id);
}
}
for (const auto id : ids) {
now.removed.push_back(id);
}
_changes = std::move(now);
}, lifetime());
}
void EditAlbumBox::prepare() {
setTitle(tr::lng_stories_album_add_title());
setStyle(st::collectionEditBox);
_content->desiredHeightValue(
) | rpl::start_with_next([=](int height) {
setDimensions(st::boxWideWidth, height);
}, _content->lifetime());
addTopButton(st::boxTitleClose, [=] {
closeBox();
});
const auto weakBox = base::make_weak(this);
auto text = _changes.value(
) | rpl::map([=](const Data::StoryAlbumUpdate &update) {
return (!update.added.empty() && update.removed.empty())
? tr::lng_stories_album_add_title()
: tr::lng_settings_save();
}) | rpl::flatten_latest();
addButton(std::move(text), [=] {
if (_saving) {
return;
}
auto add = QVector<MTPint>();
auto remove = QVector<MTPint>();
const auto &changes = _changes.current();
for (const auto &id : changes.added) {
add.push_back(MTP_int(id));
}
for (const auto &id : changes.removed) {
remove.push_back(MTP_int(id));
}
if (add.empty() && remove.empty()) {
closeBox();
return;
}
_saving = true;
const auto session = &_window->session();
const auto reload = _reload;
using Flag = MTPstories_UpdateAlbum::Flag;
session->api().request(
MTPstories_UpdateAlbum(
MTP_flags(Flag()
| (add.isEmpty() ? Flag() : Flag::f_add_stories)
| (remove.isEmpty()
? Flag()
: Flag::f_delete_stories)),
changes.peer->input,
MTP_int(changes.albumId),
MTPstring(),
MTP_vector<MTPint>(remove),
MTP_vector<MTPint>(add),
MTPVector<MTPint>())
).done([=] {
if (const auto strong = weakBox.get()) {
strong->_saving = false;
strong->closeBox();
}
session->data().stories().notifyAlbumUpdate(
base::duplicate(changes));
if (const auto onstack = reload) {
onstack();
}
}).fail([=](const MTP::Error &error) {
if (const auto strong = weakBox.get()) {
strong->_saving = false;
strong->uiShow()->showToast(error.type());
}
}).send();
});
}
void EditAlbumBox::resizeEvent(QResizeEvent *e) {
_content->setGeometry(rect());
}
} // namespace
InnerWidget::InnerWidget(
QWidget *parent,
@@ -66,11 +210,8 @@ InnerWidget::InnerWidget(
_albumId.value(
) | rpl::start_with_next([=](int albumId) {
_list.destroy();
_controller->replaceKey(Key(Tag(_peer, albumId)));
setupList();
setupEmpty();
resizeToWidth(width());
_controller->replaceKey(Key(Tag(_peer, albumId, _addingToAlbumId)));
reload();
}, lifetime());
}
@@ -108,7 +249,9 @@ InnerWidget::~InnerWidget() = default;
void InnerWidget::setupTop() {
const auto albumId = _albumId.current();
if (albumId == Data::kStoriesAlbumIdArchive) {
if (_addingToAlbumId) {
return;
} else if (albumId == Data::kStoriesAlbumIdArchive) {
createAboutArchive();
} else if (_isStackBottom) {
if (_peer->isSelf()) {
@@ -608,107 +751,24 @@ rpl::producer<Data::StoryAlbumUpdate> InnerWidget::changes() const {
return _albumChanges.value();
}
void InnerWidget::reloadAlbum(int id) {
// #TODO stories
void InnerWidget::reload() {
auto old = std::exchange(_list, object_ptr<Media::ListWidget>(nullptr));
setupList();
setupEmpty();
old.destroy();
resizeToWidth(width());
}
void InnerWidget::editAlbumStories(int id) {
const auto weak = base::make_weak(this);
_controller->uiShow()->show(Box([=](not_null<Ui::GenericBox*> box) {
box->setTitle(tr::lng_stories_album_add_title());
box->setWidth(st::boxWideWidth);
box->setStyle(st::collectionEditBox);
auto box = Box<EditAlbumBox>(_controller, _peer, crl::guard(this, [=] {
if (_albumId.current() == id) {
reload();
}
}), id);
struct State {
rpl::variable<Data::StoryAlbumUpdate> changes;
base::unique_qptr<Ui::PopupMenu> menu;
bool saving = false;
};
const auto state = box->lifetime().make_state<State>(State{
.changes = Data::StoryAlbumUpdate{
.peer = _peer,
.albumId = id,
}
});
const auto content = box->addRow(
object_ptr<InnerWidget>(
box,
_controller,
rpl::single(Data::kStoriesAlbumIdArchive),
id/*,
_peer,
state->descriptor.value(),
id,
(_all.filter == Filter()) ? _all : Entries()*/),
{});
state->changes = content->changes();
content->scrollToRequests(
) | rpl::start_with_next([=](Ui::ScrollToRequest request) {
box->scrollTo(request);
}, content->lifetime());
box->addTopButton(st::boxTitleClose, [=] {
box->closeBox();
});
const auto weakBox = base::make_weak(box);
auto text = state->changes.value(
) | rpl::map([=](const Data::StoryAlbumUpdate &update) {
return (!update.added.empty() && update.removed.empty())
? tr::lng_stories_album_add_title()
: tr::lng_settings_save();
}) | rpl::flatten_latest();
box->addButton(std::move(text), [=] {
if (state->saving) {
return;
}
auto add = QVector<MTPint>();
auto remove = QVector<MTPint>();
const auto &changes = state->changes.current();
for (const auto &id : changes.added) {
add.push_back(MTP_int(id));
}
for (const auto &id : changes.removed) {
remove.push_back(MTP_int(id));
}
if (add.empty() && remove.empty()) {
box->closeBox();
return;
}
state->saving = true;
const auto session = &_controller->session();
using Flag = MTPstories_UpdateAlbum::Flag;
session->api().request(
MTPstories_UpdateAlbum(
MTP_flags(Flag()
| (add.isEmpty() ? Flag() : Flag::f_add_stories)
| (remove.isEmpty()
? Flag()
: Flag::f_delete_stories)),
_peer->input,
MTP_int(id),
MTPstring(),
MTP_vector<MTPint>(remove),
MTP_vector<MTPint>(add),
MTPVector<MTPint>())
).done([=] {
if (const auto strong = weakBox.get()) {
state->saving = false;
strong->closeBox();
}
session->data().stories().notifyAlbumUpdate(
base::duplicate(changes));
if (const auto strong = weak.get()) {
strong->reloadAlbum(id);
}
}).fail([=](const MTP::Error &error) {
if (const auto strong = weakBox.get()) {
state->saving = false;
strong->uiShow()->showToast(error.type());
}
}).send();
});
}));
_controller->uiShow()->show(std::move(box));
}
void InnerWidget::editAlbumName(int id) {

View File

@@ -67,7 +67,7 @@ public:
rpl::producer<SelectedItems> selectedListValue() const;
void selectionAction(SelectionAction action);
void reloadAlbum(int id);
void reload();
void editAlbumStories(int id);
void editAlbumName(int id);
void confirmDeleteAlbum(int id);

View File

@@ -49,7 +49,8 @@ Provider::Provider(not_null<AbstractController*> controller)
: _controller(controller)
, _peer(controller->key().storiesPeer())
, _history(_peer->owner().history(_peer))
, _albumId(controller->key().storiesAlbumId()) {
, _albumId(controller->key().storiesAlbumId())
, _addingToAlbumId(controller->key().storiesAddToAlbumId()) {
style::PaletteChanged(
) | rpl::start_with_next([=] {
for (auto &layout : _layouts) {
@@ -366,7 +367,7 @@ ListItemSelectionData Provider::computeSelectionData(
TextSelection selection) {
auto result = ListItemSelectionData(selection);
const auto id = item->id;
if (!IsStoryMsgId(id)) {
if (_addingToAlbumId || !IsStoryMsgId(id)) {
return result;
}
const auto peer = item->history()->peer;

View File

@@ -117,6 +117,7 @@ private:
const not_null<PeerData*> _peer;
const not_null<History*> _history;
const int _albumId = 0;
const int _addingToAlbumId = 0;
StoryId _aroundId = kDefaultAroundId;
int _idsLimit = kMinimalIdsLimit;

View File

@@ -29,8 +29,8 @@ Memento::Memento(not_null<Controller*> controller)
, _media(controller) {
}
Memento::Memento(not_null<PeerData*> peer, int albumId)
: ContentMemento(Tag{ peer, albumId })
Memento::Memento(not_null<PeerData*> peer, int albumId, int addingToAlbumId)
: ContentMemento(Tag{ peer, albumId, addingToAlbumId })
, _media(peer, 0, Media::Type::PhotoVideo) {
}
@@ -57,7 +57,8 @@ Widget::Widget(
_inner = setInnerWidget(object_ptr<InnerWidget>(
this,
controller,
_albumId.value()));
_albumId.value(),
controller->key().storiesAddToAlbumId()));
_inner->albumIdChanges() | rpl::start_with_next([=](int id) {
controller->showSection(
Make(controller->storiesPeer(), id),
@@ -139,7 +140,7 @@ std::shared_ptr<Info::Memento> Make(not_null<PeerData*> peer, int albumId) {
return std::make_shared<Info::Memento>(
std::vector<std::shared_ptr<ContentMemento>>(
1,
std::make_shared<Memento>(peer, albumId)));
std::make_shared<Memento>(peer, albumId, 0)));
}
} // namespace Info::Stories

View File

@@ -18,7 +18,7 @@ class InnerWidget;
class Memento final : public ContentMemento {
public:
Memento(not_null<Controller*> controller);
Memento(not_null<PeerData*> peer, int albumId);
Memento(not_null<PeerData*> peer, int albumId, int addingToAlbumId);
~Memento();
object_ptr<ContentWidget> createWidget(
@@ -37,6 +37,7 @@ public:
private:
Media::Memento _media;
int _addingToAlbumId = 0;
};

View File

@@ -238,6 +238,14 @@ QString AppConfig::starsRatingLearnMoreUrl() const {
u"https://telegram.org/blog"_q);
}
int AppConfig::storiesAlbumsLimit() const {
return get<int>(u"stories_albums_limit"_q, 100);
}
int AppConfig::storiesAlbumLimit() const {
return get<int>(u"stories_album_stories_limit"_q, 1000);
}
void AppConfig::refresh(bool force) {
if (_requestId || !_api) {
if (force) {

View File

@@ -111,6 +111,9 @@ public:
[[nodiscard]] QString starsRatingLearnMoreUrl() const;
[[nodiscard]] int storiesAlbumsLimit() const;
[[nodiscard]] int storiesAlbumLimit() const;
void refresh(bool force = false);
private: