Add hide / unpin all button in pinned section.

This commit is contained in:
John Preston 2020-10-22 10:53:56 +03:00
parent 61d335469f
commit 994e3d8da7
12 changed files with 254 additions and 26 deletions

View file

@ -170,6 +170,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_pinned_pin" = "Pin";
"lng_pinned_unpin" = "Unpin";
"lng_pinned_notify" = "Notify all members";
"lng_pinned_messages_title#one" = "{count} pinned message";
"lng_pinned_messages_title#other" = "{count} pinned messages";
"lng_pinned_hide_all" = "Don't show pinned messages";
"lng_pinned_unpin_all#one" = "Unpin {count} message";
"lng_pinned_unpin_all#other" = "Unpin all {count} messages";
"lng_pinned_unpin_all_sure" = "Do you want to unpin all messages?";
"lng_pinned_hide_all_sure" = "Do you want to hide the pinned messages bar? It will stay hidden until a new message is pinned.";
"lng_pinned_hide_all_hide" = "Hide";
"lng_edit_media_album_error" = "This file cannot be saved as a part of an album.";
"lng_edit_media_invalid_file" = "Sorry, no way to use this file.";
@ -316,8 +324,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_events_title" = "Events";
"lng_settings_events_joined" = "Contact joined Telegram";
"lng_settings_events_pinned" = "Pinned messages";
"lng_pinned_messages_title#one" = "{count} pinned message";
"lng_pinned_messages_title#other" = "{count} pinned messages";
"lng_notification_preview" = "You have a new message";
"lng_notification_reply" = "Reply";

View file

@ -435,6 +435,7 @@ QString PeerData::computeUnavailableReason() const {
return (first != filtered.end()) ? first->text : QString();
}
// This is duplicated in CanPinMessagesValue().
bool PeerData::canPinMessages() const {
if (const auto user = asUser()) {
return user->fullFlags() & MTPDuserFull::Flag::f_can_pin_message;
@ -1028,4 +1029,16 @@ MsgId ResolveTopPinnedId(not_null<PeerData*> peer) {
return slice.messageIds.empty() ? 0 : slice.messageIds.back();
}
std::optional<int> ResolvePinnedCount(not_null<PeerData*> peer) {
const auto slice = peer->session().storage().snapshot(
Storage::SharedMediaQuery(
Storage::SharedMediaKey(
peer->id,
Storage::SharedMediaType::Pinned,
0),
0,
0));
return slice.count;
}
} // namespace Data

View file

@ -444,5 +444,6 @@ std::optional<QString> RestrictionError(
void SetTopPinnedMessageId(not_null<PeerData*> peer, MsgId messageId);
[[nodiscard]] MsgId ResolveTopPinnedId(not_null<PeerData*> peer);
[[nodiscard]] std::optional<int> ResolvePinnedCount(not_null<PeerData*> peer);
} // namespace Data

View file

@ -252,7 +252,71 @@ rpl::producer<bool> CanWriteValue(not_null<PeerData*> peer) {
} else if (auto channel = peer->asChannel()) {
return CanWriteValue(channel);
}
Unexpected("Bad peer value in CanWriteValue()");
Unexpected("Bad peer value in CanWriteValue");
}
// This is duplicated in PeerData::canPinMessages().
rpl::producer<bool> CanPinMessagesValue(not_null<PeerData*> peer) {
using namespace rpl::mappers;
if (const auto user = peer->asUser()) {
return PeerFullFlagsValue(
user,
MTPDuserFull::Flag::f_can_pin_message
) | rpl::map(_1 != MTPDuserFull::Flag(0));
} else if (const auto chat = peer->asChat()) {
const auto mask = 0
| MTPDchat::Flag::f_deactivated
| MTPDchat_ClientFlag::f_forbidden
| MTPDchat::Flag::f_left
| MTPDchat::Flag::f_creator
| MTPDchat::Flag::f_kicked;
return rpl::combine(
PeerFlagsValue(chat, mask),
AdminRightValue(chat, ChatAdminRight::f_pin_messages),
DefaultRestrictionValue(chat, ChatRestriction::f_pin_messages),
[](
MTPDchat::Flags flags,
bool adminRightAllows,
bool defaultRestriction) {
const auto amOutFlags = 0
| MTPDchat::Flag::f_deactivated
| MTPDchat_ClientFlag::f_forbidden
| MTPDchat::Flag::f_left
| MTPDchat::Flag::f_kicked;
return !(flags & amOutFlags)
&& ((flags & MTPDchat::Flag::f_creator)
|| adminRightAllows
|| !defaultRestriction);
});
} else if (const auto megagroup = peer->asMegagroup()) {
if (megagroup->amCreator()) {
return rpl::single(true);
}
return rpl::combine(
AdminRightValue(megagroup, ChatAdminRight::f_pin_messages),
DefaultRestrictionValue(megagroup, ChatRestriction::f_pin_messages),
PeerFlagValue(megagroup, MTPDchannel::Flag::f_username),
PeerFullFlagValue(megagroup, MTPDchannelFull::Flag::f_location),
megagroup->restrictionsValue()
) | rpl::map([=](
bool adminRightAllows,
bool defaultRestriction,
bool hasUsername,
bool hasLocation,
Data::Flags<ChatRestrictions>::Change restrictions) {
return adminRightAllows
|| (!hasUsername
&& !hasLocation
&& !defaultRestriction
&& !(restrictions.value & ChatRestriction::f_pin_messages));
});
} else if (const auto channel = peer->asChannel()) {
if (channel->amCreator()) {
return rpl::single(true);
}
return AdminRightValue(channel, ChatAdminRight::f_edit_messages);
}
Unexpected("Peer type in CanPinMessagesValue.");
}
TimeId SortByOnlineValue(not_null<UserData*> user, TimeId now) {

View file

@ -84,6 +84,7 @@ template <
typename = typename PeerType::FullFlags::Change>
inline auto PeerFullFlagsValue(PeerType *peer) {
Expects(peer != nullptr);
return peer->fullFlagsValue();
}
@ -105,10 +106,11 @@ inline auto PeerFullFlagValue(
return SingleFlagValue(PeerFullFlagsValue(peer), flag);
}
rpl::producer<bool> CanWriteValue(UserData *user);
rpl::producer<bool> CanWriteValue(ChatData *chat);
rpl::producer<bool> CanWriteValue(ChannelData *channel);
rpl::producer<bool> CanWriteValue(not_null<PeerData*> peer);
[[nodiscard]] rpl::producer<bool> CanWriteValue(UserData *user);
[[nodiscard]] rpl::producer<bool> CanWriteValue(ChatData *chat);
[[nodiscard]] rpl::producer<bool> CanWriteValue(ChannelData *channel);
[[nodiscard]] rpl::producer<bool> CanWriteValue(not_null<PeerData*> peer);
[[nodiscard]] rpl::producer<bool> CanPinMessagesValue(not_null<PeerData*> peer);
[[nodiscard]] TimeId SortByOnlineValue(not_null<UserData*> user, TimeId now);
[[nodiscard]] crl::time OnlineChangeTimeout(TimeId online, TimeId now);

View file

@ -5565,15 +5565,15 @@ void HistoryWidget::hidePinnedMessage() {
{ peerToChannel(_peer->id), id.message },
false);
} else {
const auto top = Data::ResolveTopPinnedId(_peer);
if (top) {
session().settings().setHiddenPinnedMessageId(_peer->id, top);
session().saveSettingsDelayed();
checkPinnedBarState();
} else {
session().api().requestFullPeer(_peer);
}
const auto callback = [=] {
if (_pinnedTracker) {
checkPinnedBarState();
}
};
Window::HidePinnedBar(
controller(),
_peer,
crl::guard(this, callback));
}
}

View file

@ -77,13 +77,17 @@ MsgId ItemIdAcrossData(not_null<HistoryItem*> item) {
return session->data().scheduledMessages().lookupId(item);
}
bool HasEditMessageAction(const ContextMenuRequest &request) {
bool HasEditMessageAction(
const ContextMenuRequest &request,
not_null<ListWidget*> list) {
const auto item = request.item;
const auto context = list->elementContext();
if (!item
|| item->isSending()
|| item->hasFailed()
|| item->isEditingMedia()
|| !request.selectedItems.empty()) {
|| !request.selectedItems.empty()
|| (context != Context::History && context != Context::Replies)) {
return false;
}
const auto peer = item->history()->peer;
@ -441,8 +445,10 @@ bool AddSendNowMessageAction(
bool AddRescheduleMessageAction(
not_null<Ui::PopupMenu*> menu,
const ContextMenuRequest &request) {
if (!HasEditMessageAction(request) || !request.item->isScheduled()) {
const ContextMenuRequest &request,
not_null<ListWidget*> list) {
if (!HasEditMessageAction(request, list)
|| !request.item->isScheduled()) {
return false;
}
const auto owner = &request.item->history()->owner();
@ -551,7 +557,7 @@ bool AddEditMessageAction(
not_null<Ui::PopupMenu*> menu,
const ContextMenuRequest &request,
not_null<ListWidget*> list) {
if (!HasEditMessageAction(request)) {
if (!HasEditMessageAction(request, list)) {
return false;
}
const auto item = request.item;
@ -591,6 +597,29 @@ bool AddPinMessageAction(
return true;
}
bool AddGoToMessageAction(
not_null<Ui::PopupMenu*> menu,
const ContextMenuRequest &request,
not_null<ListWidget*> list) {
const auto context = list->elementContext();
const auto view = request.view;
if (!view
|| !IsServerMsgId(view->data()->id)
|| context != Context::Pinned
|| !view->hasOutLayout()) {
return false;
}
const auto itemId = view->data()->fullId();
const auto controller = list->controller();
menu->addAction(tr::lng_context_to_msg(tr::now), crl::guard(controller, [=] {
const auto item = controller->session().data().message(itemId);
if (item) {
goToMessageClickHandler(item)->onClick(ClickContext{});
}
}));
return true;
}
void AddSendNowAction(
not_null<Ui::PopupMenu*> menu,
const ContextMenuRequest &request,
@ -774,6 +803,7 @@ void AddTopMessageActions(
const ContextMenuRequest &request,
not_null<ListWidget*> list) {
AddReplyToMessageAction(menu, request, list);
AddGoToMessageAction(menu, request, list);
AddViewRepliesAction(menu, request, list);
AddEditMessageAction(menu, request, list);
AddPinMessageAction(menu, request, list);
@ -789,7 +819,7 @@ void AddMessageActions(
AddDeleteAction(menu, request, list);
AddReportAction(menu, request, list);
AddSelectionAction(menu, request, list);
AddRescheduleMessageAction(menu, request);
AddRescheduleMessageAction(menu, request, list);
}
void AddCopyLinkAction(

View file

@ -1316,6 +1316,7 @@ void ListWidget::updateItemsGeometry() {
view->setDisplayDate(false);
} else {
view->setDisplayDate(true);
view->setAttachToPrevious(false);
return i;
}
}
@ -2611,6 +2612,9 @@ void ListWidget::refreshAttachmentsFromTill(int from, int till) {
view = next;
}
}
if (till == int(_items.size())) {
_items.back()->setAttachToNext(false);
}
updateSize();
}

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item_components.h"
#include "history/history_item.h"
#include "boxes/confirm_box.h"
#include "data/data_peer_values.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/shadow.h"
#include "ui/layers/generic_box.h"
@ -93,6 +94,10 @@ PinnedWidget::PinnedWidget(
, _topBar(this, controller)
, _topBarShadow(this)
, _scroll(std::make_unique<Ui::ScrollArea>(this, st::historyScroll, false))
, _clearButton(std::make_unique<Ui::FlatButton>(
this,
QString(),
st::historyComposeButton))
, _scrollDown(_scroll.get(), st::historyToDown) {
_topBar->setActiveChat(
_history,
@ -129,6 +134,7 @@ PinnedWidget::PinnedWidget(
_scroll->show();
connect(_scroll.get(), &Ui::ScrollArea::scrolled, [=] { onScroll(); });
setupClearButton();
setupScrollDownButton();
}
@ -149,6 +155,28 @@ void PinnedWidget::setupScrollDownButton() {
updateScrollDownVisibility();
}
void PinnedWidget::setupClearButton() {
Data::CanPinMessagesValue(
_history->peer
) | rpl::start_with_next([=] {
refreshClearButtonText();
}, _clearButton->lifetime());
_clearButton->setClickedCallback([=] {
if (!_history->peer->canPinMessages()) {
const auto callback = [=] {
controller()->showBackFromStack();
};
Window::HidePinnedBar(
controller(),
_history->peer,
crl::guard(this, callback));
} else {
Window::UnpinAllMessages(controller(), _history);
}
});
}
void PinnedWidget::scrollDownClicked() {
if (QGuiApplication::keyboardModifiers() == Qt::ControlModifier) {
showAtEnd();
@ -357,6 +385,26 @@ void PinnedWidget::recountChatWidth() {
}
}
void PinnedWidget::setMessagesCount(int count) {
if (_messagesCount == count) {
return;
}
_messagesCount = count;
_topBar->setCustomTitle(
tr::lng_pinned_messages_title(tr::now, lt_count, count));
refreshClearButtonText();
}
void PinnedWidget::refreshClearButtonText() {
const auto can = _history->peer->canPinMessages();
_clearButton->setText(can
? tr::lng_pinned_unpin_all(
tr::now,
lt_count,
std::max(_messagesCount, 1)).toUpper()
: tr::lng_pinned_hide_all(tr::now).toUpper());
}
void PinnedWidget::updateControlsGeometry() {
const auto contentWidth = width();
@ -366,7 +414,9 @@ void PinnedWidget::updateControlsGeometry() {
_topBar->resizeToWidth(contentWidth);
_topBarShadow->resize(contentWidth, st::lineWidth);
const auto bottom = height();
const auto bottom = height() - _clearButton->height();
_clearButton->resizeToWidth(width());
_clearButton->move(0, bottom);
const auto controlsHeight = 0;
const auto scrollY = _topBar->height();
const auto scrollHeight = bottom - scrollY - controlsHeight;
@ -479,8 +529,7 @@ rpl::producer<Data::MessagesSlice> PinnedWidget::listSource(
if (!count.has_value()) {
return true;
} else if (*count != 0) {
_topBar->setCustomTitle(
tr::lng_pinned_messages_title(tr::now, lt_count, *count));
setMessagesCount(*count);
return true;
} else {
controller()->showBackFromStack();

View file

@ -120,6 +120,7 @@ private:
HistoryItem *originItem,
anim::type animated = anim::type::normal);
void setupClearButton();
void setupScrollDownButton();
void scrollDownClicked();
void scrollDownAnimationFinish();
@ -131,6 +132,9 @@ private:
void clearSelected();
void recountChatWidth();
void setMessagesCount(int count);
void refreshClearButtonText();
const not_null<History*> _history;
QPointer<ListWidget> _inner;
object_ptr<TopBarWidget> _topBar;
@ -138,13 +142,14 @@ private:
bool _skipScrollEvent = false;
std::unique_ptr<Ui::ScrollArea> _scroll;
object_ptr<Ui::FlatButton> _clearButton = { nullptr };
std::unique_ptr<Ui::FlatButton> _clearButton;
Ui::Animations::Simple _scrollDownShown;
bool _scrollDownIsShown = false;
object_ptr<Ui::HistoryDownButton> _scrollDown;
Data::MessagesSlice _lastSlice;
int _messagesCount = -1;
};

View file

@ -1150,6 +1150,53 @@ void ToggleMessagePinned(
}
}
void HidePinnedBar(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
Fn<void()> onHidden) {
Ui::show(Box<ConfirmBox>(tr::lng_pinned_hide_all_sure(tr::now), tr::lng_pinned_hide_all_hide(tr::now), crl::guard(navigation, [=] {
Ui::hideLayer();
auto &session = peer->session();
const auto top = Data::ResolveTopPinnedId(peer);
if (top) {
session.settings().setHiddenPinnedMessageId(peer->id, top);
session.saveSettingsDelayed();
if (onHidden) {
onHidden();
}
} else {
session.api().requestFullPeer(peer);
}
})));
}
void UnpinAllMessages(
not_null<Window::SessionNavigation*> navigation,
not_null<History*> history) {
Ui::show(Box<ConfirmBox>(tr::lng_pinned_unpin_all_sure(tr::now), tr::lng_pinned_unpin(tr::now), crl::guard(navigation, [=] {
Ui::hideLayer();
const auto api = &history->session().api();
const auto peer = history->peer;
const auto sendRequest = [=](auto self) -> void {
api->request(MTPmessages_UnpinAllMessages(
peer->input
)).done([=](const MTPmessages_AffectedHistory &result) {
const auto offset = api->applyAffectedHistory(peer, result);
if (offset > 0) {
self(self);
} else {
peer->session().storage().remove(
Storage::SharedMediaRemoveAll(
peer->id,
Storage::SharedMediaType::Pinned));
peer->setHasPinnedMessages(false);
}
}).send();
};
sendRequest(sendRequest);
})));
}
void PeerMenuAddMuteAction(
not_null<PeerData*> peer,
const PeerMenuCallback &addAction) {

View file

@ -115,5 +115,12 @@ void ToggleMessagePinned(
not_null<Window::SessionNavigation*> navigation,
FullMsgId itemId,
bool pin);
void HidePinnedBar(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
Fn<void()> onHidden);
void UnpinAllMessages(
not_null<Window::SessionNavigation*> navigation,
not_null<History*> history);
} // namespace Window