bool HistoryItem::unread() const { // Messages from myself are always read. if (history()->peer->isSelf()) return false; if (out()) { // Outgoing messages in converted chats are always read. if (history()->peer->migrateTo()) { return false; } if (IsServerMsgId(id)) { if (!history()->isServerSideUnread(this)) { return false; } if (const auto user = history()->peer->asUser()) { if (user->botInfo) { return false; } } else if (auto channel = history()->peer->asChannel()) { if (!channel->isMegagroup()) { return false; } } } return true; } if (IsServerMsgId(id)) { if (!history()->isServerSideUnread(this)) { return false; } return true; } return (_flags & MTPDmessage_ClientFlag::f_clientside_unread); }
void Groups::refreshMessage(not_null<HistoryItem*> item) { if (!isGrouped(item)) { unregisterMessage(item); return; } if (!IsServerMsgId(item->id)) { return; } const auto groupId = item->groupId(); const auto i = _groups.find(groupId); if (i == end(_groups)) { registerMessage(item); return; } auto &items = i->second.items; const auto position = findPositionForItem(items, item); auto current = ranges::find(items, item); if (current == end(items)) { items.insert(position, item); } else if (position == current + 1) { return; } else if (position > current + 1) { for (++current; current != position; ++current) { std::swap(*(current - 1), *current); } } else if (position < current) { for (; current != position; --current) { std::swap(*(current - 1), *current); } } else { Unexpected("Position of item in Groups::refreshMessage()."); } refreshViews(items); }
HistoryItemsList::const_iterator Groups::findPositionForItem( const HistoryItemsList &group, not_null<HistoryItem*> item) { const auto itemId = item->id; const auto last = end(group); if (!IsServerMsgId(itemId)) { return last; } for (auto result = begin(group); result != last; ++result) { const auto alreadyId = (*result)->id; if (IsServerMsgId(alreadyId) && alreadyId > itemId) { return result; } } return last; }
void HistoryMessage::addToUnreadMentions(UnreadMentionType type) { if (IsServerMsgId(id) && mentionsMe() && isMediaUnread()) { if (history()->addToUnreadMentions(id, type)) { Notify::peerUpdatedDelayed( history()->peer, Notify::PeerUpdate::Flag::UnreadMentionsChanged); } } }
bool HistoryItem::hasDirectLink() const { if (!IsServerMsgId(id)) { return false; } if (auto channel = _history->peer->asChannel()) { return channel->isPublic(); } return false; }
bool HistoryItem::suggestReport() const { if (out() || serviceMsg() || !IsServerMsgId(id)) { return false; } else if (const auto channel = history()->peer->asChannel()) { return true; } else if (const auto user = history()->peer->asUser()) { return user->botInfo != nullptr; } return false; }
void HistoryItem::setRealId(MsgId newId) { Expects(!IsServerMsgId(id)); App::historyUnregItem(this); const auto oldId = std::exchange(id, newId); App::historyRegItem(this); // We don't need to call Notify::replyMarkupUpdated(this) and update keyboard // in history widget, because it can't exist for an outgoing message. // Only inline keyboards can be in outgoing messages. if (const auto markup = inlineReplyMarkup()) { if (markup->inlineKeyboard) { markup->inlineKeyboard->updateMessageId(); } } _history->owner().notifyItemIdChange({ this, oldId }); _history->owner().requestItemRepaint(this); }
bool HistoryItem::canDelete() const { if (isLogEntry() || (!IsServerMsgId(id) && serviceMsg())) { return false; } auto channel = _history->peer->asChannel(); if (!channel) { return !isGroupMigrate(); } if (id == 1) { return false; } if (channel->canDeleteMessages()) { return true; } if (out() && toHistoryMessage()) { return isPost() ? channel->canPublish() : true; } return false; }
void HistoryItem::indexAsNewItem() { if (IsServerMsgId(id)) { CrashReports::SetAnnotation("addToUnreadMentions", QString::number(id)); addToUnreadMentions(UnreadMentionType::New); CrashReports::ClearAnnotation("addToUnreadMentions"); if (const auto types = sharedMediaTypes()) { _history->session().storage().add(Storage::SharedMediaAddNew( history()->peer->id, types, id)); } if (const auto channel = history()->peer->asChannel()) { if (const auto feed = channel->feed()) { _history->session().storage().add(Storage::FeedMessagesAddNew( feed->id(), position())); } } } }
void HistoryItem::destroy() { if (isLogEntry()) { Assert(!mainView()); } else { // All this must be done for all items manually in History::clear()! eraseFromUnreadMentions(); if (IsServerMsgId(id)) { if (const auto types = sharedMediaTypes()) { _history->session().storage().remove(Storage::SharedMediaRemoveOne( _history->peer->id, types, id)); } } else { _history->session().api().cancelLocalItem(this); } _history->itemRemoved(this); } delete this; }
rpl::producer<SparseIdsSlice> SharedMediaViewer( Storage::SharedMediaKey key, int limitBefore, int limitAfter) { Expects(IsServerMsgId(key.messageId) || (key.messageId == 0)); Expects((key.messageId != 0) || (limitBefore == 0 && limitAfter == 0)); return [=](auto consumer) { auto lifetime = rpl::lifetime(); auto builder = lifetime.make_state<SparseIdsSliceBuilder>( key.messageId, limitBefore, limitAfter); auto requestMediaAround = [ peer = App::peer(key.peerId), type = key.type ](const SparseIdsSliceBuilder::AroundData &data) { Auth().api().requestSharedMedia( peer, type, data.aroundId, data.direction); }; builder->insufficientAround( ) | rpl::start_with_next(requestMediaAround, lifetime); auto pushNextSnapshot = [=] { consumer.put_next(builder->snapshot()); }; using SliceUpdate = Storage::SharedMediaSliceUpdate; Auth().storage().sharedMediaSliceUpdated( ) | rpl::filter([=](const SliceUpdate &update) { return (update.peerId == key.peerId) && (update.type == key.type); }) | rpl::filter([=](const SliceUpdate &update) { return builder->applyUpdate(update.data); }) | rpl::start_with_next(pushNextSnapshot, lifetime); using OneRemoved = Storage::SharedMediaRemoveOne; Auth().storage().sharedMediaOneRemoved( ) | rpl::filter([=](const OneRemoved &update) { return (update.peerId == key.peerId) && update.types.test(key.type); }) | rpl::filter([=](const OneRemoved &update) { return builder->removeOne(update.messageId); }) | rpl::start_with_next(pushNextSnapshot, lifetime); using AllRemoved = Storage::SharedMediaRemoveAll; Auth().storage().sharedMediaAllRemoved( ) | rpl::filter([=](const AllRemoved &update) { return (update.peerId == key.peerId); }) | rpl::filter([=] { return builder->removeAll(); }) | rpl::start_with_next(pushNextSnapshot, lifetime); using InvalidateBottom = Storage::SharedMediaInvalidateBottom; Auth().storage().sharedMediaBottomInvalidated( ) | rpl::filter([=](const InvalidateBottom &update) { return (update.peerId == key.peerId); }) | rpl::filter([=] { return builder->invalidateBottom(); }) | rpl::start_with_next(pushNextSnapshot, lifetime); using Result = Storage::SharedMediaResult; Auth().storage().query(Storage::SharedMediaQuery( key, limitBefore, limitAfter )) | rpl::filter([=](const Result &result) { return builder->applyInitial(result); }) | rpl::start_with_next_done( pushNextSnapshot, [=] { builder->checkInsufficient(); }, lifetime); return lifetime; }; }
rpl::producer<SparseIdsSlice> SearchController::simpleIdsSlice( PeerId peerId, MsgId aroundId, const Query &query, int limitBefore, int limitAfter) { Expects(peerId != 0); Expects(IsServerMsgId(aroundId) || (aroundId == 0)); Expects((aroundId != 0) || (limitBefore == 0 && limitAfter == 0)); Expects((query.peerId == peerId) || (query.migratedPeerId == peerId)); auto it = _cache.find(query); if (it == _cache.end()) { return [=](auto) { return rpl::lifetime(); }; } auto listData = (peerId == query.peerId) ? &it->second->peerData : &*it->second->migratedData; return [=](auto consumer) { auto lifetime = rpl::lifetime(); auto builder = lifetime.make_state<SparseIdsSliceBuilder>( aroundId, limitBefore, limitAfter); builder->insufficientAround( ) | rpl::start_with_next([=]( const SparseIdsSliceBuilder::AroundData &data) { requestMore(data, query, listData); }, lifetime); auto pushNextSnapshot = [=] { consumer.put_next(builder->snapshot()); }; listData->list.sliceUpdated( ) | rpl::filter([=](const SliceUpdate &update) { return builder->applyUpdate(update); }) | rpl::start_with_next(pushNextSnapshot, lifetime); Auth().data().itemRemoved( ) | rpl::filter([=](not_null<const HistoryItem*> item) { return (item->history()->peer->id == peerId); }) | rpl::filter([=](not_null<const HistoryItem*> item) { return builder->removeOne(item->id); }) | rpl::start_with_next(pushNextSnapshot, lifetime); Auth().data().historyCleared( ) | rpl::filter([=](not_null<const History*> history) { return (history->peer->id == peerId); }) | rpl::filter([=] { return builder->removeAll(); }) | rpl::start_with_next(pushNextSnapshot, lifetime); using Result = Storage::SparseIdsListResult; listData->list.query(Storage::SparseIdsListQuery( aroundId, limitBefore, limitAfter )) | rpl::filter([=](const Result &result) { return builder->applyInitial(result); }) | rpl::start_with_next_done( pushNextSnapshot, [=] { builder->checkInsufficient(); }, lifetime); return lifetime; }; }
QSize HistoryGame::countOptimalSize() { auto lineHeight = unitedLineHeight(); const auto item = _parent->data(); if (!_openl && IsServerMsgId(item->id)) { const auto row = 0; const auto column = 0; _openl = std::make_shared<ReplyMarkupClickHandler>( row, column, item->fullId()); } auto title = TextUtilities::SingleLine(_data->title); // init attach if (!_attach) { _attach = CreateAttach(_parent, _data->document, _data->photo); } // init strings if (_description.isEmpty() && !_data->description.isEmpty()) { auto text = _data->description; if (!text.isEmpty()) { if (!_attach) { text += _parent->skipBlock(); } auto marked = TextWithEntities { text }; auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText; TextUtilities::ParseEntities(marked, parseFlags); _description.setMarkedText( st::webPageDescriptionStyle, marked, Ui::WebpageTextDescriptionOptions()); } } if (_title.isEmpty() && !title.isEmpty()) { _title.setText( st::webPageTitleStyle, title, Ui::WebpageTextTitleOptions()); } // init dimensions auto l = st::msgPadding.left() + st::webPageLeft, r = st::msgPadding.right(); auto skipBlockWidth = _parent->skipBlockWidth(); auto maxWidth = skipBlockWidth; auto minHeight = 0; auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight; // enable any count of lines in game description / message auto descMaxLines = 4096; auto descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight); if (!_title.isEmpty()) { accumulate_max(maxWidth, _title.maxWidth()); minHeight += titleMinHeight; } if (!_description.isEmpty()) { accumulate_max(maxWidth, _description.maxWidth()); minHeight += descriptionMinHeight; } if (_attach) { auto attachAtTop = !_titleLines && !_descriptionLines; if (!attachAtTop) minHeight += st::mediaInBubbleSkip; _attach->initDimensions(); QMargins bubble(_attach->bubbleMargins()); auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right(); if (isBubbleBottom() && _attach->customInfoLayout()) { maxMediaWidth += skipBlockWidth; } accumulate_max(maxWidth, maxMediaWidth); minHeight += _attach->minHeight() - bubble.top() - bubble.bottom(); } maxWidth += st::msgPadding.left() + st::webPageLeft + st::msgPadding.right(); auto padding = inBubblePadding(); minHeight += padding.top() + padding.bottom(); if (!_gameTagWidth) { _gameTagWidth = st::msgDateFont->width(lang(lng_game_tag).toUpper()); } return { maxWidth, minHeight }; }