mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 14:17:12 +02:00
Save to Profile / Archive / Delete in list.
This commit is contained in:
parent
af0e578da5
commit
5f72a5238c
14 changed files with 179 additions and 36 deletions
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()");
|
||||
}();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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*>,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue