diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 8cfd5b8cb1..556f2fd351 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -166,6 +166,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_pinned_quiz" = "Pinned quiz"; "lng_pinned_unpin_sure" = "Would you like to unpin this message?"; "lng_pinned_pin_sure" = "Would you like to pin this message?"; +"lng_pinned_pin_old_sure" = "Do you want to pin an older message while leaving a more recent one pinned?"; "lng_pinned_pin" = "Pin"; "lng_pinned_unpin" = "Unpin"; "lng_pinned_notify" = "Notify all members"; diff --git a/Telegram/SourceFiles/boxes/confirm_box.cpp b/Telegram/SourceFiles/boxes/confirm_box.cpp index a534f04007..b098ef1002 100644 --- a/Telegram/SourceFiles/boxes/confirm_box.cpp +++ b/Telegram/SourceFiles/boxes/confirm_box.cpp @@ -448,7 +448,7 @@ PinMessageBox::PinMessageBox( , _text( this, (_pinningOld - ? "Do you want to pin an older message while leaving a more recent one pinned?" // #TODO pinned + ? tr::lng_pinned_pin_old_sure(tr::now) : tr::lng_pinned_pin_sure(tr::now)), st::boxLabel) { } diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index a74f957142..3e14909094 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "boxes/confirm_box.h" #include "main/main_session.h" +#include "main/main_session_settings.h" #include "main/main_account.h" #include "main/main_domain.h" #include "main/main_app_config.h" @@ -469,14 +470,12 @@ void PeerData::ensurePinnedMessagesCreated() { if (!_pinnedMessages) { _pinnedMessages = std::make_unique( peerToChannel(id)); - session().changes().peerUpdated(this, UpdateFlag::PinnedMessage); } } void PeerData::removeEmptyPinnedMessages() { if (_pinnedMessages && _pinnedMessages->empty()) { _pinnedMessages = nullptr; - session().changes().peerUpdated(this, UpdateFlag::PinnedMessage); } } @@ -489,18 +488,30 @@ bool PeerData::messageIdTooSmall(MsgId messageId) const { void PeerData::setTopPinnedMessageId(MsgId messageId) { if (messageIdTooSmall(messageId)) { + clearPinnedMessages(); return; } + if (session().settings().hiddenPinnedMessageId(id) != messageId) { + session().settings().setHiddenPinnedMessageId(id, 0); + session().saveSettingsDelayed(); + } ensurePinnedMessagesCreated(); _pinnedMessages->setTopId(messageId); + session().changes().peerUpdated(this, UpdateFlag::PinnedMessage); } void PeerData::clearPinnedMessages(MsgId lessThanId) { + if (lessThanId == ServerMaxMsgId + && session().settings().hiddenPinnedMessageId(id) != 0) { + session().settings().setHiddenPinnedMessageId(id, 0); + session().saveSettingsDelayed(); + } if (!_pinnedMessages) { return; } _pinnedMessages->clearLessThanId(lessThanId); removeEmptyPinnedMessages(); + session().changes().peerUpdated(this, UpdateFlag::PinnedMessage); } void PeerData::addPinnedMessage(MsgId messageId) { @@ -509,6 +520,7 @@ void PeerData::addPinnedMessage(MsgId messageId) { } ensurePinnedMessagesCreated(); _pinnedMessages->add(messageId); + session().changes().peerUpdated(this, UpdateFlag::PinnedMessage); } void PeerData::addPinnedSlice( @@ -532,6 +544,7 @@ void PeerData::addPinnedSlice( } ensurePinnedMessagesCreated(); _pinnedMessages->add(std::move(ids), noSkipRange, std::nullopt); + session().changes().peerUpdated(this, UpdateFlag::PinnedMessage); } void PeerData::removePinnedMessage(MsgId messageId) { @@ -540,6 +553,7 @@ void PeerData::removePinnedMessage(MsgId messageId) { } _pinnedMessages->remove(messageId); removeEmptyPinnedMessages(); + session().changes().peerUpdated(this, UpdateFlag::PinnedMessage); } bool PeerData::canExportChatHistory() const { diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 973e26affb..847d512a9e 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1005,9 +1005,12 @@ void History::applyServiceChanges( case mtpc_messageActionPinMessage: { if (const auto replyTo = data.vreply_to()) { replyTo->match([&](const MTPDmessageReplyHeader &data) { + const auto id = data.vreply_to_msg_id().v; if (item) { - //item->history()->peer->setTopPinnedMessageId( // #TODO pinned - // data.vreply_to_msg_id().v); + item->history()->peer->addPinnedSlice( + { id }, + { id, ServerMaxMsgId }, + std::nullopt); } }); } diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index ff32dd3e1f..705ff00fdd 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -563,6 +563,7 @@ HistoryWidget::HistoryWidget( | UpdateFlag::ChannelLinkedChat | UpdateFlag::Slowmode | UpdateFlag::BotStartToken + | UpdateFlag::PinnedMessage ) | rpl::filter([=](const Data::PeerUpdate &update) { return (update.peer.get() == _peer); }) | rpl::map([](const Data::PeerUpdate &update) { @@ -603,6 +604,9 @@ HistoryWidget::HistoryWidget( | UpdateFlag::ChannelLinkedChat)) { handlePeerUpdate(); } + if (_pinnedTracker && (flags & UpdateFlag::PinnedMessage)) { + checkPinnedBarState(); + } }, lifetime()); rpl::merge( @@ -5211,12 +5215,50 @@ void HistoryWidget::setupPinnedTracker() { Expects(_history != nullptr); _pinnedTracker = std::make_unique(_history); + _pinnedBar = nullptr; + checkPinnedBarState(); +} + +void HistoryWidget::checkPinnedBarState() { + Expects(_pinnedTracker != nullptr); + + const auto hiddenId = _peer->canPinMessages() + ? MsgId(0) + : session().settings().hiddenPinnedMessageId(_peer->id); + const auto currentPinnedId = _peer->topPinnedMessageId(); + if (currentPinnedId == hiddenId) { + if (_pinnedBar) { + _pinnedTracker->reset(); + auto qobject = base::unique_qptr{ + Ui::WrapAsQObject(this, std::move(_pinnedBar)).get() + }; + auto destroyer = [this, object = std::move(qobject)]() mutable { + object = nullptr; + updateHistoryGeometry(); + updateControlsGeometry(); + }; + base::call_delayed( + st::defaultMessageBar.duration, + this, + std::move(destroyer)); + } + return; + } + if (_pinnedBar || !currentPinnedId) { + return; + } + + auto shown = _pinnedTracker->shownMessageId( + ) | rpl::map([=](HistoryView::PinnedId messageId) { + return HistoryView::PinnedBarId{ + FullMsgId{ peerToChannel(_peer->id), messageId.message }, + messageId.type + }; + }); _pinnedBar = std::make_unique( this, &session(), - _pinnedTracker->shownMessageId() | rpl::map([=](MsgId messageId) { - return FullMsgId{ peerToChannel(_peer->id), messageId }; - }), + std::move(shown), true); _pinnedBar->closeClicks( @@ -5233,68 +5275,13 @@ void HistoryWidget::setupPinnedTracker() { updateControlsGeometry(); _topDelta = 0; }, _pinnedBar->lifetime()); + orderWidgets(); + if (_a_show.animating()) { _pinnedBar->hide(); } } -// -//bool HistoryWidget::pinnedMsgVisibilityUpdated() { -// auto result = false; -// auto pinnedId = _pinnedId; -// if (pinnedId && !_peer->canPinMessages()) { -// const auto hiddenId = session().settings().hiddenPinnedMessageId( -// _peer->id); -// if (hiddenId == pinnedId.msg) { -// pinnedId = FullMsgId(); -// } else if (hiddenId) { -// session().settings().setHiddenPinnedMessageId(_peer->id, 0); -// session().saveSettings(); -// } -// } -// if (pinnedId) { -// if (!_pinnedBar) { -// _pinnedBar = std::make_unique(pinnedId.msg, this); -// if (_a_show.animating()) { -// _pinnedBar->cancel->hide(); -// _pinnedBar->bar->widget()->hide(); -// _pinnedBar->shadow->hide(); -// } else { -// _pinnedBar->cancel->show(); -// _pinnedBar->bar->widget()->show(); -// _pinnedBar->shadow->show(); -// } -// _pinnedBar->cancel->addClickHandler([=] { -// hidePinnedMessage(); -// }); -// orderWidgets(); -// -// updatePinnedBar(); -// result = true; -// -// const auto barTop = unreadBarTop(); -// if (!barTop || _scroll->scrollTop() != *barTop) { -// synteticScrollToY(_scroll->scrollTop() + st::historyReplyHeight); -// } -// } else if (_pinnedBar->msgId != pinnedId.msg) { -// _pinnedBar->msgId = pinnedId.msg; -// _pinnedBar->msg = nullptr; -// updatePinnedBar(); -// } -// if (!_pinnedBar->msg) { -// requestMessageData(_pinnedBar->msgId); -// } -// } else if (_pinnedBar) { -// destroyPinnedBar(); -// result = true; -// const auto barTop = unreadBarTop(); -// if (!barTop || _scroll->scrollTop() != *barTop) { -// synteticScrollToY(_scroll->scrollTop() - st::historyReplyHeight); -// } -// updateControlsGeometry(); -// } -// return result; -//} void HistoryWidget::requestMessageData(MsgId msgId) { const auto callback = [=](ChannelData *channel, MsgId msgId) { @@ -5574,25 +5561,25 @@ void HistoryWidget::UnpinMessage(not_null peer, MsgId msgId) { } void HistoryWidget::hidePinnedMessage() { - //const auto pinnedId = _pinnedId; // #TODO pinned - //if (!pinnedId) { - // if (pinnedMsgVisibilityUpdated()) { - // updateControlsGeometry(); - // update(); - // } - // return; - //} + Expects(_pinnedBar != nullptr); - //if (_peer->canPinMessages()) { - // unpinMessage(pinnedId); - //} else { - // session().settings().setHiddenPinnedMessageId(_peer->id, pinnedId.msg); - // session().saveSettings(); - // if (pinnedMsgVisibilityUpdated()) { - // updateControlsGeometry(); - // update(); - // } - //} + const auto id = _pinnedTracker->currentMessageId(); + if (!id.message) { + return; + } + if (_peer->canPinMessages()) { + unpinMessage({ peerToChannel(_peer->id), id.message }); + } else { + const auto top = _peer->topPinnedMessageId(); + if (top) { + session().settings().setHiddenPinnedMessageId(_peer->id, top); + session().saveSettingsDelayed(); + + checkPinnedBarState(); + } else { + session().api().requestFullPeer(_peer); + } + } } bool HistoryWidget::lastForceReplyReplied(const FullMsgId &replyTo) const { @@ -6373,16 +6360,6 @@ void HistoryWidget::drawRecording(Painter &p, float64 recordActive) { } // //void HistoryWidget::drawPinnedBar(Painter &p) { -// Expects(_pinnedBar != nullptr); -// -// auto top = _topBar->bottomNoMargins(); -// p.fillRect(myrtlrect(0, top, width(), st::historyReplyHeight), st::historyPinnedBg); -// -// top += st::msgReplyPadding.top(); -// QRect rbar(myrtlrect(st::msgReplyBarSkip + st::msgReplyBarPos.x(), top + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height())); -// p.fillRect(rbar, st::msgInReplyBarColor); -// -// //int32 left = st::msgReplyBarSkip + st::msgReplyBarSkip; // //if (_pinnedBar->msg) { // // const auto media = _pinnedBar->msg->media(); // // if (media && media->hasReplyPreview()) { @@ -6392,22 +6369,6 @@ void HistoryWidget::drawRecording(Painter &p, float64 recordActive) { // // } // // left += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); // // } -// // p.setPen(st::historyReplyNameFg); -// // p.setFont(st::msgServiceNameFont); -// // const auto poll = media ? media->poll() : nullptr; -// // const auto pinnedHeader = (_pinnedBar->msgId < _peer->topPinnedMessageId()) -// // ? tr::lng_pinned_previous(tr::now) -// // : !poll -// // ? tr::lng_pinned_message(tr::now) -// // : poll->quiz() -// // ? tr::lng_pinned_quiz(tr::now) -// // : tr::lng_pinned_poll(tr::now); -// // p.drawText(left, top + st::msgServiceNameFont->ascent, pinnedHeader); -// -// // p.setPen(st::historyComposeAreaFg); -// // p.setTextPalette(st::historyComposeAreaPalette); -// // _pinnedBar->text.drawElided(p, left, top + st::msgServiceNameFont->height, width() - left - _pinnedBar->cancel->width() - st::msgReplyPadding.right()); -// // p.restoreTextPalette(); // //} else { // // p.setFont(st::msgDateFont); // // p.setPen(st::historyComposeAreaFgService); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index db6ed145b9..1be6ab3bc5 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -486,6 +486,7 @@ private: void updatePinnedViewer(); void setupPinnedTracker(); + void checkPinnedBarState(); void sendInlineResult( not_null result, diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp index 82aaafc42d..c8af04b240 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_poll.h" #include "ui/widgets/shadow.h" #include "ui/widgets/buttons.h" +#include "history/view/history_view_pinned_tracker.h" #include "history/history_item.h" #include "history/history.h" #include "apiwrap.h" @@ -23,7 +24,8 @@ namespace HistoryView { namespace { [[nodiscard]] rpl::producer ContentByItem( - not_null item) { + not_null item, + PinnedIdType type) { return item->history()->session().changes().messageFlagsValue( item, Data::MessageUpdate::Flag::Edited @@ -32,7 +34,9 @@ namespace { const auto poll = media ? media->poll() : nullptr; return Ui::MessageBarContent{ .id = item->id, - .title = ((item->id < item->history()->peer->topPinnedMessageId()) + .title = ((type == PinnedIdType::First) + ? "First message" + : (type == PinnedIdType::Middle) ? tr::lng_pinned_previous(tr::now) : !poll ? tr::lng_pinned_message(tr::now) @@ -46,12 +50,12 @@ namespace { [[nodiscard]] rpl::producer ContentByItemId( not_null session, - FullMsgId id, + PinnedBarId id, bool alreadyLoaded = false) { - if (!id) { + if (!id.message) { return rpl::single(Ui::MessageBarContent()); - } else if (const auto item = session->data().message(id)) { - return ContentByItem(item); + } else if (const auto item = session->data().message(id.message)) { + return ContentByItem(item, id.type); } else if (alreadyLoaded) { return rpl::single(Ui::MessageBarContent()); // Deleted message?.. } @@ -59,13 +63,13 @@ namespace { consumer.put_next(Ui::MessageBarContent{ .text = tr::lng_contacts_loading(tr::now), }); - const auto channel = id.channel - ? session->data().channel(id.channel).get() + const auto channel = id.message.channel + ? session->data().channel(id.message.channel).get() : nullptr; const auto callback = [=](ChannelData *channel, MsgId id) { consumer.put_done(); }; - session->api().requestMessageData(channel, id.msg, callback); + session->api().requestMessageData(channel, id.message.msg, callback); return rpl::lifetime(); }); return std::move( @@ -80,7 +84,7 @@ namespace { PinnedBar::PinnedBar( not_null parent, not_null session, - rpl::producer itemId, + rpl::producer itemId, bool withClose) : _wrap(parent, object_ptr(parent)) , _close(withClose @@ -101,7 +105,7 @@ PinnedBar::PinnedBar( rpl::duplicate( itemId ) | rpl::distinct_until_changed( - ) | rpl::map([=](FullMsgId id) { + ) | rpl::map([=](PinnedBarId id) { return ContentByItemId(session, id); }) | rpl::flatten_latest( ) | rpl::filter([=](const Ui::MessageBarContent &content) { @@ -119,15 +123,18 @@ PinnedBar::PinnedBar( std::move( itemId - ) | rpl::map([=](FullMsgId id) { - return !id; - }) | rpl::start_with_next([=](bool hidden) { + ) | rpl::map([=](PinnedBarId id) { + return !id.message; + }) | rpl::start_with_next_done([=](bool hidden) { _shouldBeShown = !hidden; if (!_forceHidden) { _wrap.toggle(_shouldBeShown, anim::type::normal); } else if (!_shouldBeShown) { _bar = nullptr; } + }, [=] { + _forceHidden = true; + _wrap.toggle(false, anim::type::normal); }, lifetime()); } diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.h b/Telegram/SourceFiles/history/view/history_view_pinned_bar.h index e6e61e9314..397cbc92f8 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_bar.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.h @@ -21,12 +21,25 @@ class PlainShadow; namespace HistoryView { +enum class PinnedIdType; +struct PinnedBarId { + FullMsgId message; + PinnedIdType type = PinnedIdType(); + + bool operator<(const PinnedBarId &other) const { + return std::tie(message, type) < std::tie(other.message, other.type); + } + bool operator==(const PinnedBarId &other) const { + return std::tie(message, type) == std::tie(other.message, other.type); + } +}; + class PinnedBar final { public: PinnedBar( not_null parent, not_null session, - rpl::producer itemId, + rpl::producer itemId, bool withClose = false); void show(); diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp index ea6f076d79..bb18c8356f 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp @@ -40,15 +40,23 @@ PinnedTracker::~PinnedTracker() { _history->owner().histories().cancelRequest(_afterRequestId); } -rpl::producer PinnedTracker::shownMessageId() const { +rpl::producer PinnedTracker::shownMessageId() const { return _current.value(); } +void PinnedTracker::reset() { + _current.reset(currentMessageId()); +} + +PinnedId PinnedTracker::currentMessageId() const { + return _current.current(); +} + void PinnedTracker::refreshData() { const auto now = _history->peer->currentPinnedMessages(); if (!now) { _dataLifetime.destroy(); - _current = MsgId(0); + _current = PinnedId(); } else if (_data.get() != now) { _dataLifetime.destroy(); _data = now; @@ -65,7 +73,7 @@ void PinnedTracker::trackAround(MsgId messageId) { _dataLifetime.destroy(); _aroundId = messageId; if (!_aroundId) { - _current = MsgId(0); + _current = PinnedId(); } else if (const auto now = _data.get()) { setupViewer(now); } @@ -90,10 +98,19 @@ void PinnedTracker::setupViewer(not_null data) { Data::LoadDirection::After, empty ? _aroundId : snapshot.ids.back()); } + if (snapshot.ids.empty()) { + _current = PinnedId(); + return; + } + const auto type = (!after && (snapshot.skippedAfter == 0)) + ? PinnedIdType::Last + : (before < 2 && (snapshot.skippedBefore == 0)) + ? PinnedIdType::First + : PinnedIdType::Middle; if (i != begin(snapshot.ids)) { - _current = *(i - 1); + _current = PinnedId{ *(i - 1), type }; } else if (snapshot.skippedBefore == 0) { - _current = 0; + _current = PinnedId{ snapshot.ids.front(), type }; } }, _dataLifetime); } diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h index e34df6af40..1fbf061b4e 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h @@ -16,13 +16,32 @@ enum class LoadDirection : char; namespace HistoryView { +enum class PinnedIdType { + First, + Middle, + Last, +}; +struct PinnedId { + MsgId message = 0; + PinnedIdType type = PinnedIdType::Middle; + + bool operator<(const PinnedId &other) const { + return std::tie(message, type) < std::tie(other.message, other.type); + } + bool operator==(const PinnedId &other) const { + return std::tie(message, type) == std::tie(other.message, other.type); + } +}; + class PinnedTracker final { public: explicit PinnedTracker(not_null history); ~PinnedTracker(); - [[nodiscard]] rpl::producer shownMessageId() const; + [[nodiscard]] rpl::producer shownMessageId() const; + [[nodiscard]] PinnedId currentMessageId() const; void trackAround(MsgId messageId); + void reset(); private: void refreshData(); @@ -36,7 +55,7 @@ private: const not_null _history; base::weak_ptr _data; - rpl::variable _current = MsgId(); + rpl::variable _current; rpl::lifetime _dataLifetime; MsgId _aroundId = 0;