Save to Profile / Archive / Delete in list.

This commit is contained in:
John Preston 2023-06-30 16:45:41 +04:00
parent af0e578da5
commit 5f72a5238c
14 changed files with 179 additions and 36 deletions

View File

@ -1298,6 +1298,8 @@ void Stories::togglePinnedList(
const auto loaded = saved.loaded;
const auto lastId = !saved.ids.list.empty()
? saved.ids.list.back()
: saved.lastId
? saved.lastId
: std::numeric_limits<StoryId>::max();
auto dirty = false;
for (const auto &id : result.v) {

View File

@ -44,7 +44,7 @@ rpl::producer<StoriesIdsSlice> SavedStoriesIds(
const auto hasBefore = int(around - begin(saved->list));
const auto hasAfter = int(end(saved->list) - around);
if (hasAfter < limit) {
//stories->savedLoadMore(peer->id);
stories->savedLoadMore(peer->id);
}
const auto takeBefore = std::min(hasBefore, limit);
const auto takeAfter = std::min(hasAfter, limit);

View File

@ -1988,7 +1988,7 @@ bool HistoryItem::canDelete() const {
if (isSponsored()) {
return false;
} else if (IsStoryMsgId(id)) {
return false && _history->peer->isSelf(); // #TODO stories
return false;
} else if (isService() && !isRegular()) {
return false;
} else if (topicRootId() == id) {

View File

@ -352,6 +352,10 @@ void TopBar::updateSelectionControlsGeometry(int newWidth) {
_delete->moveToRight(right, 0, newWidth);
right += _delete->width();
}
if (_canToggleStoryPin) {
_toggleStoryPin->moveToRight(right, 0, newWidth);
right += _toggleStoryPin->width();
}
if (_canForward) {
_forward->moveToRight(right, 0, newWidth);
right += _forward->width();
@ -496,6 +500,10 @@ void TopBar::setStories(rpl::producer<Dialogs::Stories::Content> content) {
updateControlsVisibility(anim::type::instant);
}
void TopBar::setStoriesArchive(bool archive) {
_storiesArchive = archive;
}
void TopBar::setSelectedItems(SelectedItems &&items) {
auto wasSelectionMode = selectionMode();
_selectedItems = std::move(items);
@ -523,13 +531,14 @@ rpl::producer<SelectionAction> TopBar::selectionActionRequests() const {
}
void TopBar::updateSelectionState() {
Expects(_selectionText && _delete && _forward);
Expects(_selectionText && _delete && _forward && _toggleStoryPin);
_canDelete = computeCanDelete();
_canForward = computeCanForward();
_selectionText->entity()->setValue(generateSelectedText());
_delete->toggle(_canDelete, anim::type::instant);
_forward->toggle(_canForward, anim::type::instant);
_toggleStoryPin->toggle(_canToggleStoryPin, anim::type::instant);
updateSelectionControlsGeometry(width());
}
@ -544,6 +553,7 @@ void TopBar::createSelectionControls() {
};
_canDelete = computeCanDelete();
_canForward = computeCanForward();
_canToggleStoryPin = computeCanToggleStoryPin();
_cancelSelection = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>(
this,
object_ptr<Ui::IconButton>(this, _st.mediaCancel),
@ -595,6 +605,24 @@ void TopBar::createSelectionControls() {
_selectionActionRequests,
_cancelSelection->lifetime());
_delete->entity()->setVisible(_canDelete);
const auto archive =
_toggleStoryPin = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>(
this,
object_ptr<Ui::IconButton>(
this,
_storiesArchive ? _st.storiesSave : _st.storiesArchive),
st::infoTopBarScale));
registerToggleControlCallback(
_toggleStoryPin.data(),
[this] { return selectionMode() && _canToggleStoryPin; });
_toggleStoryPin->setDuration(st::infoTopBarDuration);
_toggleStoryPin->entity()->clicks(
) | rpl::map_to(
SelectionAction::ToggleStoryPin
) | rpl::start_to_stream(
_selectionActionRequests,
_cancelSelection->lifetime());
_toggleStoryPin->entity()->setVisible(_canToggleStoryPin);
updateControlsGeometry(width());
}
@ -607,6 +635,12 @@ bool TopBar::computeCanForward() const {
return ranges::all_of(_selectedItems.list, &SelectedItem::canForward);
}
bool TopBar::computeCanToggleStoryPin() const {
return ranges::all_of(
_selectedItems.list,
&SelectedItem::canToggleStoryPin);
}
Ui::StringWithNumbers TopBar::generateSelectedText() const {
using Type = Storage::SharedMediaType;
const auto phrase = [&] {
@ -618,8 +652,7 @@ Ui::StringWithNumbers TopBar::generateSelectedText() const {
case Type::MusicFile: return tr::lng_media_selected_song;
case Type::Link: return tr::lng_media_selected_link;
case Type::RoundVoiceFile: return tr::lng_media_selected_audio;
// #TODO stories
case Type::PhotoVideo: return tr::lng_media_selected_photo;
case Type::PhotoVideo: return tr::lng_stories_row_count;
}
Unexpected("Type in TopBar::generateSelectedText()");
}();

View File

@ -57,6 +57,7 @@ public:
void setTitle(rpl::producer<QString> &&title);
void setStories(rpl::producer<Dialogs::Stories::Content> content);
void setStoriesArchive(bool archive);
void enableBackButton();
void highlight();
@ -120,11 +121,13 @@ private:
[[nodiscard]] Ui::StringWithNumbers generateSelectedText() const;
[[nodiscard]] bool computeCanDelete() const;
[[nodiscard]] bool computeCanForward() const;
[[nodiscard]] bool computeCanToggleStoryPin() const;
void updateSelectionState();
void createSelectionControls();
void performForward();
void performDelete();
void performToggleStoryPin();
void setSearchField(
base::unique_qptr<Ui::InputField> field,
@ -163,10 +166,13 @@ private:
SelectedItems _selectedItems;
bool _canDelete = false;
bool _canForward = false;
bool _canToggleStoryPin = false;
bool _storiesArchive = false;
QPointer<Ui::FadeWrap<Ui::IconButton>> _cancelSelection;
QPointer<Ui::FadeWrap<Ui::LabelWithNumbers>> _selectionText;
QPointer<Ui::FadeWrap<Ui::IconButton>> _forward;
QPointer<Ui::FadeWrap<Ui::IconButton>> _delete;
QPointer<Ui::FadeWrap<Ui::IconButton>> _toggleStoryPin;
rpl::event_stream<SelectionAction> _selectionActionRequests;
QPointer<Ui::FadeWrap<Dialogs::Stories::List>> _stories;

View File

@ -585,6 +585,8 @@ void WrapWidget::finishShowContent() {
if (_topBar) {
_topBar->setTitle(_content->title());
_topBar->setStories(_content->titleStories());
_topBar->setStoriesArchive(
_controller->key().storiesTab() == Stories::Tab::Archive);
}
_desiredHeights.fire(desiredHeightForContent());
_desiredShadowVisibilities.fire(_content->desiredShadowVisibility());

View File

@ -58,6 +58,7 @@ struct SelectedItem {
GlobalMsgId globalId;
bool canDelete = false;
bool canForward = false;
bool canToggleStoryPin = false;
};
struct SelectedItems {
@ -73,6 +74,7 @@ enum class SelectionAction {
Clear,
Forward,
Delete,
ToggleStoryPin,
};
class WrapWidget final : public Window::SectionWidget {

View File

@ -30,15 +30,12 @@ struct ListItemSelectionData {
TextSelection text;
bool canDelete = false;
bool canForward = false;
};
bool canToggleStoryPin = false;
inline bool operator==(
ListItemSelectionData a,
ListItemSelectionData b) {
return (a.text == b.text)
&& (a.canDelete == b.canDelete)
&& (a.canForward == b.canForward);
}
friend inline bool operator==(
ListItemSelectionData,
ListItemSelectionData) = default;
};
using ListSelectedMap = base::flat_map<
not_null<const HistoryItem*>,

View File

@ -338,7 +338,7 @@ void ListSection::resizeToWidth(int newWidth) {
switch (_type) {
case Type::Photo:
case Type::Video:
case Type::PhotoVideo: // #TODO stories
case Type::PhotoVideo:
case Type::RoundFile: {
const auto skip = st::infoMediaSkip;
_itemsLeft = st::infoMediaLeft;
@ -379,7 +379,7 @@ int ListSection::recountHeight() {
switch (_type) {
case Type::Photo:
case Type::Video:
case Type::PhotoVideo: // #TODO stories
case Type::PhotoVideo:
case Type::RoundFile: {
auto itemHeight = _itemHeight + st::infoMediaSkip;
auto index = 0;

View File

@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_service_message.h"
#include "media/stories/media_stories_controller.h" // ...TogglePinnedToast.
#include "window/window_session_controller.h"
#include "window/window_peer_menu.h"
#include "ui/widgets/popup_menu.h"
@ -55,6 +56,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peer_list_controllers.h"
#include "core/file_utilities.h"
#include "core/application.h"
#include "ui/toast/toast.h"
#include "styles/style_overview.h"
#include "styles/style_info.h"
#include "styles/style_layers.h"
@ -258,6 +260,7 @@ void ListWidget::selectionAction(SelectionAction action) {
case SelectionAction::Clear: clearSelected(); return;
case SelectionAction::Forward: forwardSelected(); return;
case SelectionAction::Delete: deleteSelected(); return;
case SelectionAction::ToggleStoryPin: toggleStoryPinSelected(); return;
}
}
@ -335,6 +338,7 @@ auto ListWidget::collectSelectedItems() const -> SelectedItems {
auto result = SelectedItem(item->globalId());
result.canDelete = selection.canDelete;
result.canForward = selection.canForward;
result.canToggleStoryPin = selection.canToggleStoryPin;
return result;
};
auto transformation = [&](const auto &item) {
@ -349,6 +353,12 @@ auto ListWidget::collectSelectedItems() const -> SelectedItems {
std::back_inserter(items.list),
transformation);
}
if (_controller->storiesPeer() && items.list.size() > 1) {
// Don't allow forwarding more than one story.
for (auto &entry : items.list) {
entry.canForward = false;
}
}
return items;
}
@ -1126,6 +1136,43 @@ void ListWidget::deleteSelected() {
}));
}
void ListWidget::toggleStoryPinSelected() {
auto list = std::vector<FullStoryId>();
const auto confirmed = crl::guard(this, [=] {
clearSelected();
});
for (const auto &item : collectSelectedItems().list) {
const auto id = item.globalId.itemId;
if (IsStoryMsgId(id.msg)) {
list.push_back({ id.peer, StoryIdFromMsgId(id.msg) });
}
}
const auto count = int(list.size());
const auto pin = (_controller->storiesTab() == Stories::Tab::Archive);
const auto controller = _controller;
const auto sure = [=](Fn<void()> close) {
controller->session().data().stories().togglePinnedList(list, pin);
controller->showToast(
::Media::Stories::PrepareTogglePinnedToast(count, pin));
close();
confirmed();
};
const auto session = &_controller->session();
const auto onePhrase = pin
? tr::lng_stories_save_sure
: tr::lng_stories_archive_sure;
const auto manyPhrase = pin
? tr::lng_stories_save_sure_many
: tr::lng_stories_archive_sure_many;
_controller->parentController()->show(Ui::MakeConfirmBox({
.text = (count == 1
? onePhrase()
: manyPhrase(lt_count, rpl::single(count) | tr::to_count())),
.confirmed = sure,
.confirmText = tr::lng_box_ok(),
}));
}
void ListWidget::deleteItem(GlobalMsgId globalId) {
if (const auto item = MessageByGlobalId(globalId)) {
auto items = SelectedItems(_provider->type());
@ -1134,7 +1181,6 @@ void ListWidget::deleteItem(GlobalMsgId globalId) {
item,
FullSelection);
items.list.back().canDelete = selectionData.canDelete;
items.list.back().canForward = selectionData.canForward;
deleteItems(std::move(items));
}
}
@ -1180,6 +1226,33 @@ void ListWidget::deleteItems(SelectedItems &&items, Fn<void()> confirmed) {
.confirmText = tr::lng_box_delete(tr::now),
.confirmStyle = &st::attentionBoxButton,
})));
} else if (_controller->storiesPeer()) {
auto list = std::vector<FullStoryId>();
for (const auto &item : items.list) {
const auto id = item.globalId.itemId;
if (IsStoryMsgId(id.msg)) {
list.push_back({ id.peer, StoryIdFromMsgId(id.msg) });
}
}
const auto session = &_controller->session();
const auto sure = [=](Fn<void()> close) {
session->data().stories().deleteList(list);
close();
if (confirmed) {
confirmed();
}
};
const auto count = int(list.size());
window->show(Ui::MakeConfirmBox({
.text = (count == 1
? tr::lng_stories_delete_one_sure()
: tr::lng_stories_delete_sure(
lt_count,
rpl::single(count) | tr::to_count())),
.confirmed = sure,
.confirmText = tr::lng_selected_delete(),
.confirmStyle = &st::attentionBoxButton,
}));
} else if (auto list = collectSelectedIds(items); !list.empty()) {
auto box = Box<DeleteMessagesBox>(
&_controller->session(),

View File

@ -189,6 +189,7 @@ private:
void forwardItem(GlobalMsgId globalId);
void forwardItems(MessageIdsList &&items);
void deleteSelected();
void toggleStoryPinSelected();
void deleteItem(GlobalMsgId globalId);
void deleteItems(SelectedItems &&items, Fn<void()> confirmed = nullptr);
void applyItemSelection(

View File

@ -61,7 +61,7 @@ Type Provider::type() {
}
bool Provider::hasSelectRestriction() {
return true; // #TODO stories
return !_peer->isSelf();
}
rpl::producer<bool> Provider::hasSelectRestrictionChanges() {
@ -312,8 +312,10 @@ ListItemSelectionData Provider::computeSelectionData(
not_null<const HistoryItem*> item,
TextSelection selection) {
auto result = ListItemSelectionData(selection);
result.canDelete = item->canDelete();
result.canForward = item->allowsForward();
const auto peer = item->history()->peer;
result.canDelete = peer->isSelf();
result.canForward = peer->isSelf();
result.canToggleStoryPin = peer->isSelf();
return result;
}
@ -334,9 +336,10 @@ void Provider::applyDragSelection(
}
}
for (auto &layoutItem : _layouts) {
const auto id = StoryIdToMsgId(layoutItem.first);
const auto storyId = layoutItem.first;
const auto id = StoryIdToMsgId(storyId);
if (id <= fromId && id > tillId) {
const auto i = _items.find(id);
const auto i = _items.find(storyId);
Assert(i != end(_items));
const auto item = i->second.get();
ChangeItemSelection(

View File

@ -1309,21 +1309,7 @@ void Controller::togglePinnedRequested(bool pinned) {
moveFromShown();
}
story->owner().stories().togglePinnedList({ story->fullId() }, pinned);
uiShow()->showToast({
.text = (pinned
? tr::lng_stories_save_done(
tr::now,
Ui::Text::Bold).append(
'\n').append(
tr::lng_stories_save_done_about(tr::now))
: tr::lng_stories_archive_done(
tr::now,
Ui::Text::WithEntities)),
.st = &st::storiesActionToast,
.duration = (pinned
? Data::Stories::kPinnedToastDuration
: Ui::Toast::kDefaultDuration),
});
uiShow()->showToast(PrepareTogglePinnedToast(1, pinned));
}
void Controller::moveFromShown() {
@ -1375,4 +1361,34 @@ void Controller::startReactionAnimation(
}, layer->lifetime());
}
Ui::Toast::Config PrepareTogglePinnedToast(int count, bool pinned) {
return {
.text = (pinned
? (count == 1
? tr::lng_stories_save_done(
tr::now,
Ui::Text::Bold)
: tr::lng_stories_save_done_many(
tr::now,
lt_count,
count,
Ui::Text::Bold)).append(
'\n').append(
tr::lng_stories_save_done_about(tr::now))
: (count == 1
? tr::lng_stories_archive_done(
tr::now,
Ui::Text::WithEntities)
: tr::lng_stories_archive_done_many(
tr::now,
lt_count,
count,
Ui::Text::WithEntities))),
.st = &st::storiesActionToast,
.duration = (pinned
? Data::Stories::kPinnedToastDuration
: Ui::Toast::kDefaultDuration),
};
}
} // namespace Media::Stories

View File

@ -34,6 +34,10 @@ struct MessageSendingAnimationFrom;
class EmojiFlyAnimation;
} // namespace Ui
namespace Ui::Toast {
struct Config;
} // namespace Ui::Toast
namespace Main {
class Session;
} // namespace Main
@ -253,4 +257,8 @@ private:
};
[[nodiscard]] Ui::Toast::Config PrepareTogglePinnedToast(
int count,
bool pinned);
} // namespace Media::Stories