| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "history/history.h"
- #include "history/view/history_view_element.h"
- #include "history/view/history_view_item_preview.h"
- #include "history/view/history_view_translate_tracker.h"
- #include "dialogs/dialogs_indexed_list.h"
- #include "history/history_inner_widget.h"
- #include "history/history_item.h"
- #include "history/history_item_components.h"
- #include "history/history_item_helpers.h"
- #include "history/history_translation.h"
- #include "history/history_unread_things.h"
- #include "core/ui_integration.h"
- #include "dialogs/ui/dialogs_layout.h"
- #include "data/business/data_shortcut_messages.h"
- #include "data/components/scheduled_messages.h"
- #include "data/components/sponsored_messages.h"
- #include "data/components/top_peers.h"
- #include "data/notify/data_notify_settings.h"
- #include "data/stickers/data_stickers.h"
- #include "data/data_drafts.h"
- #include "data/data_saved_sublist.h"
- #include "data/data_session.h"
- #include "data/data_media_types.h"
- #include "data/data_channel_admins.h"
- #include "data/data_changes.h"
- #include "data/data_chat_filters.h"
- #include "data/data_send_action.h"
- #include "data/data_star_gift.h"
- #include "data/data_emoji_statuses.h"
- #include "data/data_folder.h"
- #include "data/data_forum.h"
- #include "data/data_forum_topic.h"
- #include "data/data_photo.h"
- #include "data/data_channel.h"
- #include "data/data_chat.h"
- #include "data/data_user.h"
- #include "data/data_document.h"
- #include "data/data_histories.h"
- #include "data/data_history_messages.h"
- #include "lang/lang_keys.h"
- #include "apiwrap.h"
- #include "api/api_chat_participants.h"
- #include "mainwidget.h"
- #include "mainwindow.h"
- #include "main/main_session.h"
- #include "window/notifications_manager.h"
- #include "calls/calls_instance.h"
- #include "spellcheck/spellcheck_types.h"
- #include "storage/localstorage.h"
- #include "storage/storage_facade.h"
- #include "storage/storage_shared_media.h"
- #include "storage/storage_account.h"
- #include "support/support_helper.h"
- #include "ui/image/image.h"
- #include "ui/text/text_options.h"
- #include "ui/text/text_utilities.h"
- #include "ui/toast/toast.h"
- #include "payments/payments_checkout_process.h"
- #include "core/crash_reports.h"
- #include "core/application.h"
- #include "base/unixtime.h"
- #include "base/qt/qt_common_adapters.h"
- #include "styles/style_dialogs.h"
- namespace {
- constexpr auto kNewBlockEachMessage = 50;
- constexpr auto kSkipCloudDraftsFor = TimeId(2);
- using UpdateFlag = Data::HistoryUpdate::Flag;
- [[nodiscard]] HistoryItemCommonFields WithLocalFlag(
- HistoryItemCommonFields fields) {
- fields.flags |= MessageFlag::Local;
- return fields;
- }
- [[nodiscard]] Dialogs::UnreadState AdjustedForumUnreadState(
- Dialogs::UnreadState state) {
- const auto allMuted = (state.chats == state.chatsMuted);
- state.chatsMuted = (state.chats && allMuted) ? 1 : 0;
- state.chats = state.chats ? 1 : 0;
- return state;
- }
- } // namespace
- History::History(not_null<Data::Session*> owner, PeerId peerId)
- : Thread(owner, Type::History)
- , peer(owner->peer(peerId))
- , _delegateMixin(HistoryInner::DelegateMixin())
- , _chatListNameSortKey(TextUtilities::NameSortKey(peer->name()))
- , _sendActionPainter(this) {
- Thread::setMuted(owner->notifySettings().isMuted(peer));
- if (const auto user = peer->asUser()) {
- if (user->isBot()) {
- _outboxReadBefore = std::numeric_limits<MsgId>::max();
- }
- }
- }
- History::~History() = default;
- void History::clearLastKeyboard() {
- if (lastKeyboardId) {
- if (lastKeyboardId == lastKeyboardHiddenId) {
- lastKeyboardHiddenId = 0;
- }
- lastKeyboardId = 0;
- session().changes().historyUpdated(this, UpdateFlag::BotKeyboard);
- }
- lastKeyboardInited = true;
- lastKeyboardFrom = 0;
- }
- int History::height() const {
- return _height;
- }
- bool History::hasPendingResizedItems() const {
- return _flags & Flag::HasPendingResizedItems;
- }
- void History::setHasPendingResizedItems() {
- _flags |= Flag::HasPendingResizedItems;
- }
- void History::itemRemoved(not_null<HistoryItem*> item) {
- if (item == _joinedMessage) {
- _joinedMessage = nullptr;
- } else if (item == _newPeerNameChange) {
- _newPeerNameChange = nullptr;
- } else if (item == _newPeerPhotoChange) {
- _newPeerPhotoChange = nullptr;
- }
- item->removeMainView();
- if (_lastServerMessage == item) {
- _lastServerMessage = std::nullopt;
- }
- if (lastMessage() == item) {
- _lastMessage = std::nullopt;
- if (loadedAtBottom()) {
- if (const auto last = lastAvailableMessage()) {
- setLastMessage(last);
- }
- }
- }
- checkChatListMessageRemoved(item);
- itemVanished(item);
- if (IsClientMsgId(item->id)) {
- unregisterClientSideMessage(item);
- }
- if (const auto topic = item->topic()) {
- topic->applyItemRemoved(item->id);
- }
- if (const auto chat = peer->asChat()) {
- if (const auto to = chat->getMigrateToChannel()) {
- if (const auto history = owner().historyLoaded(to)) {
- history->checkChatListMessageRemoved(item);
- }
- }
- }
- }
- void History::checkChatListMessageRemoved(not_null<HistoryItem*> item) {
- if (chatListMessage() != item) {
- return;
- }
- setChatListMessageUnknown();
- refreshChatListMessage();
- }
- void History::itemVanished(not_null<HistoryItem*> item) {
- item->notificationThread()->removeNotification(item);
- if (lastKeyboardId == item->id) {
- clearLastKeyboard();
- }
- if ((!item->out() || item->isPost())
- && item->unread(this)
- && unreadCount() > 0) {
- setUnreadCount(unreadCount() - 1);
- }
- if (const auto media = item->media()) {
- if (const auto gift = media->gift()) {
- using GiftAction = Data::GiftUpdate::Action;
- owner().notifyGiftUpdate({
- .id = Data::SavedStarGiftId::User(item->id),
- .action = GiftAction::Delete,
- });
- }
- }
- }
- void History::takeLocalDraft(not_null<History*> from) {
- const auto topicRootId = MsgId(0);
- const auto i = from->_drafts.find(Data::DraftKey::Local(topicRootId));
- if (i == end(from->_drafts)) {
- return;
- }
- auto &draft = i->second;
- if (!draft->textWithTags.text.isEmpty()
- && !_drafts.contains(Data::DraftKey::Local(topicRootId))) {
- // Edit and reply to drafts can't migrate.
- // Cloud drafts do not migrate automatically.
- draft->reply = FullReplyTo();
- setLocalDraft(std::move(draft));
- }
- from->clearLocalDraft(topicRootId);
- session().api().saveDraftToCloudDelayed(from);
- }
- void History::createLocalDraftFromCloud(MsgId topicRootId) {
- const auto draft = cloudDraft(topicRootId);
- if (!draft) {
- clearLocalDraft(topicRootId);
- return;
- } else if (Data::DraftIsNull(draft) || !draft->date) {
- return;
- }
- draft->reply.topicRootId = topicRootId;
- auto existing = localDraft(topicRootId);
- if (Data::DraftIsNull(existing)
- || !existing->date
- || draft->date >= existing->date) {
- if (!existing) {
- setLocalDraft(std::make_unique<Data::Draft>(
- draft->textWithTags,
- draft->reply,
- draft->cursor,
- draft->webpage));
- existing = localDraft(topicRootId);
- } else if (existing != draft) {
- existing->textWithTags = draft->textWithTags;
- existing->reply = draft->reply;
- existing->cursor = draft->cursor;
- existing->webpage = draft->webpage;
- }
- existing->date = draft->date;
- }
- }
- Data::Draft *History::draft(Data::DraftKey key) const {
- if (!key) {
- return nullptr;
- }
- const auto i = _drafts.find(key);
- return (i != _drafts.end()) ? i->second.get() : nullptr;
- }
- void History::setDraft(
- Data::DraftKey key,
- std::unique_ptr<Data::Draft> &&draft) {
- if (!key) {
- return;
- }
- const auto cloudThread = key.isCloud()
- ? threadFor(key.topicRootId())
- : nullptr;
- if (cloudThread) {
- cloudThread->cloudDraftTextCache().clear();
- }
- if (draft) {
- _drafts[key] = std::move(draft);
- } else if (_drafts.remove(key) && cloudThread) {
- cloudThread->updateChatListSortPosition();
- }
- }
- const Data::HistoryDrafts &History::draftsMap() const {
- return _drafts;
- }
- void History::setDraftsMap(Data::HistoryDrafts &&map) {
- for (auto &[key, draft] : _drafts) {
- map[key] = std::move(draft);
- }
- _drafts = std::move(map);
- }
- void History::clearDraft(Data::DraftKey key) {
- setDraft(key, nullptr);
- }
- void History::clearDrafts() {
- for (auto &[key, draft] : base::take(_drafts)) {
- const auto cloudThread = key.isCloud()
- ? threadFor(key.topicRootId())
- : nullptr;
- if (cloudThread) {
- cloudThread->cloudDraftTextCache().clear();
- cloudThread->updateChatListSortPosition();
- }
- }
- }
- Data::Draft *History::createCloudDraft(
- MsgId topicRootId,
- const Data::Draft *fromDraft) {
- if (Data::DraftIsNull(fromDraft)) {
- setCloudDraft(std::make_unique<Data::Draft>(
- TextWithTags(),
- FullReplyTo{ .topicRootId = topicRootId },
- MessageCursor(),
- Data::WebPageDraft()));
- cloudDraft(topicRootId)->date = TimeId(0);
- } else {
- auto existing = cloudDraft(topicRootId);
- if (!existing) {
- auto reply = fromDraft->reply;
- reply.topicRootId = topicRootId;
- setCloudDraft(std::make_unique<Data::Draft>(
- fromDraft->textWithTags,
- reply,
- fromDraft->cursor,
- fromDraft->webpage));
- existing = cloudDraft(topicRootId);
- } else if (existing != fromDraft) {
- existing->textWithTags = fromDraft->textWithTags;
- existing->reply = fromDraft->reply;
- existing->cursor = fromDraft->cursor;
- existing->webpage = fromDraft->webpage;
- }
- existing->date = base::unixtime::now();
- existing->reply.topicRootId = topicRootId;
- }
- if (const auto thread = threadFor(topicRootId)) {
- thread->cloudDraftTextCache().clear();
- thread->updateChatListSortPosition();
- }
- return cloudDraft(topicRootId);
- }
- bool History::skipCloudDraftUpdate(MsgId topicRootId, TimeId date) const {
- const auto i = _acceptCloudDraftsAfter.find(topicRootId);
- return _savingCloudDraftRequests.contains(topicRootId)
- || (i != _acceptCloudDraftsAfter.end() && date < i->second);
- }
- void History::startSavingCloudDraft(MsgId topicRootId) {
- ++_savingCloudDraftRequests[topicRootId];
- }
- void History::finishSavingCloudDraft(MsgId topicRootId, TimeId savedAt) {
- const auto i = _savingCloudDraftRequests.find(topicRootId);
- if (i != _savingCloudDraftRequests.end()) {
- if (--i->second <= 0) {
- _savingCloudDraftRequests.erase(i);
- }
- }
- auto &after = _acceptCloudDraftsAfter[topicRootId];
- after = std::max(after, savedAt + kSkipCloudDraftsFor);
- }
- void History::applyCloudDraft(MsgId topicRootId) {
- if (!topicRootId && session().supportMode()) {
- updateChatListEntry();
- session().supportHelper().cloudDraftChanged(this);
- } else {
- createLocalDraftFromCloud(topicRootId);
- if (const auto thread = threadFor(topicRootId)) {
- thread->updateChatListSortPosition();
- if (!topicRootId) {
- session().changes().historyUpdated(
- this,
- UpdateFlag::CloudDraft);
- } else {
- session().changes().topicUpdated(
- thread->asTopic(),
- Data::TopicUpdate::Flag::CloudDraft);
- }
- }
- }
- }
- void History::draftSavedToCloud(MsgId topicRootId) {
- if (const auto thread = threadFor(topicRootId)) {
- thread->updateChatListEntry();
- }
- session().local().writeDrafts(this);
- }
- const Data::ForwardDraft &History::forwardDraft(
- MsgId topicRootId) const {
- static const auto kEmpty = Data::ForwardDraft();
- const auto i = _forwardDrafts.find(topicRootId);
- return (i != end(_forwardDrafts)) ? i->second : kEmpty;
- }
- Data::ResolvedForwardDraft History::resolveForwardDraft(
- const Data::ForwardDraft &draft) const {
- return Data::ResolvedForwardDraft{
- .items = owner().idsToItems(draft.ids),
- .options = draft.options,
- };
- }
- Data::ResolvedForwardDraft History::resolveForwardDraft(
- MsgId topicRootId) {
- const auto &draft = forwardDraft(topicRootId);
- auto result = resolveForwardDraft(draft);
- if (result.items.size() != draft.ids.size()) {
- setForwardDraft(topicRootId, {
- .ids = owner().itemsToIds(result.items),
- .options = result.options,
- });
- }
- return result;
- }
- void History::setForwardDraft(
- MsgId topicRootId,
- Data::ForwardDraft &&draft) {
- auto changed = false;
- if (draft.ids.empty()) {
- changed = _forwardDrafts.remove(topicRootId);
- } else {
- auto &now = _forwardDrafts[topicRootId];
- if (now != draft) {
- now = std::move(draft);
- changed = true;
- }
- }
- if (changed) {
- const auto entry = topicRootId
- ? peer->forumTopicFor(topicRootId)
- : (Dialogs::Entry*)this;
- if (entry) {
- session().changes().entryUpdated(
- entry,
- Data::EntryUpdate::Flag::ForwardDraft);
- }
- }
- }
- not_null<HistoryItem*> History::createItem(
- MsgId id,
- const MTPMessage &message,
- MessageFlags localFlags,
- bool detachExistingItem,
- bool newMessage) {
- if (const auto result = owner().message(peer, id)) {
- if (detachExistingItem) {
- result->removeMainView();
- }
- if (result->needsUpdateForVideoQualities(message)) {
- owner().updateEditedMessage(message);
- }
- return result;
- }
- const auto result = message.match([&](const auto &data) {
- return makeMessage(id, data, localFlags);
- });
- if (newMessage && result->out() && result->isRegular()) {
- session().topPeers().increment(peer, result->date());
- if (result->starsPaid()) {
- session().credits().load(true);
- }
- }
- return result;
- }
- std::vector<not_null<HistoryItem*>> History::createItems(
- const QVector<MTPMessage> &data) {
- auto result = std::vector<not_null<HistoryItem*>>();
- result.reserve(data.size());
- const auto localFlags = MessageFlags();
- const auto detachExistingItem = true;
- for (auto i = data.cend(), e = data.cbegin(); i != e;) {
- const auto &data = *--i;
- const auto id = IdFromMessage(data);
- if ((id.bare == 1) && (data.type() == mtpc_messageEmpty)) {
- // The first message of channels should be a service message
- // about its creation. But if channel auto-cleaning is enabled,
- // the first message comes empty and is displayed incorrectly.
- continue;
- }
- result.emplace_back(createItem(
- id,
- data,
- localFlags,
- detachExistingItem));
- }
- return result;
- }
- not_null<HistoryItem*> History::addNewMessage(
- MsgId id,
- const MTPMessage &message,
- MessageFlags localFlags,
- NewMessageType type) {
- const auto newMessage = (type == NewMessageType::Unread);
- const auto detachExisting = newMessage;
- const auto item = createItem(
- id,
- message,
- localFlags,
- detachExisting,
- newMessage);
- if (type == NewMessageType::Existing || item->mainView()) {
- return item;
- }
- if (newMessage && item->isHistoryEntry()) {
- applyMessageChanges(item, message);
- }
- return addNewItem(item, newMessage);
- }
- not_null<HistoryItem*> History::insertItem(
- std::unique_ptr<HistoryItem> item) {
- Expects(item != nullptr);
- const auto &[i, ok] = _items.insert(std::move(item));
- const auto result = i->get();
- owner().registerMessage(result);
- Ensures(ok);
- return result;
- }
- void History::destroyMessage(not_null<HistoryItem*> item) {
- Expects(item->isHistoryEntry() || !item->mainView());
- const auto peerId = peer->id;
- if (item->isHistoryEntry()) {
- // All this must be done for all items manually in History::clear()!
- item->destroyHistoryEntry();
- if (item->isRegular()) {
- if (const auto messages = _messages.get()) {
- messages->removeOne(item->id);
- }
- if (const auto types = item->sharedMediaTypes()) {
- session().storage().remove(Storage::SharedMediaRemoveOne(
- peerId,
- types,
- item->id));
- }
- }
- itemRemoved(item);
- }
- if (item->isSending()) {
- session().api().cancelLocalItem(item);
- }
- const auto documentToCancel = [&] {
- const auto media = item->isAdminLogEntry()
- ? nullptr
- : item->media();
- return media ? media->document() : nullptr;
- }();
- owner().unregisterMessage(item);
- Core::App().notifications().clearFromItem(item);
- auto hack = std::unique_ptr<HistoryItem>(item.get());
- const auto i = _items.find(hack);
- hack.release();
- Assert(i != end(_items));
- _items.erase(i);
- if (documentToCancel) {
- session().data().documentMessageRemoved(documentToCancel);
- }
- }
- void History::destroyMessagesByDates(TimeId minDate, TimeId maxDate) {
- auto toDestroy = std::vector<not_null<HistoryItem*>>();
- toDestroy.reserve(_items.size());
- for (const auto &message : _items) {
- if (message->isRegular()
- && message->date() > minDate
- && message->date() < maxDate) {
- toDestroy.push_back(message.get());
- }
- }
- for (const auto item : toDestroy) {
- item->destroy();
- }
- }
- void History::destroyMessagesByTopic(MsgId topicRootId) {
- auto toDestroy = std::vector<not_null<HistoryItem*>>();
- toDestroy.reserve(_items.size());
- for (const auto &message : _items) {
- if (message->topicRootId() == topicRootId) {
- toDestroy.push_back(message.get());
- }
- }
- for (const auto item : toDestroy) {
- item->destroy();
- }
- }
- void History::unpinMessagesFor(MsgId topicRootId) {
- if (!topicRootId) {
- session().storage().remove(
- Storage::SharedMediaRemoveAll(
- peer->id,
- Storage::SharedMediaType::Pinned));
- setHasPinnedMessages(false);
- if (const auto forum = peer->forum()) {
- forum->enumerateTopics([](not_null<Data::ForumTopic*> topic) {
- topic->setHasPinnedMessages(false);
- });
- }
- for (const auto &item : _items) {
- if (item->isPinned()) {
- item->setIsPinned(false);
- }
- }
- } else {
- session().storage().remove(
- Storage::SharedMediaRemoveAll(
- peer->id,
- topicRootId,
- Storage::SharedMediaType::Pinned));
- if (const auto topic = peer->forumTopicFor(topicRootId)) {
- topic->setHasPinnedMessages(false);
- }
- for (const auto &item : _items) {
- if (item->isPinned() && item->topicRootId() == topicRootId) {
- item->setIsPinned(false);
- }
- }
- }
- }
- not_null<HistoryItem*> History::addNewItem(
- not_null<HistoryItem*> item,
- bool unread) {
- if (item->isScheduled()) {
- session().scheduledMessages().appendSending(item);
- return item;
- } else if (item->isBusinessShortcut()) {
- owner().shortcutMessages().appendSending(item);
- return item;
- } else if (!item->isHistoryEntry()) {
- return item;
- }
- // In case we've loaded a new 'last' message
- // and it is not in blocks and we think that
- // we have all the messages till the bottom
- // we should unload known history or mark
- // currently loaded slice as not reaching bottom.
- const auto shouldMarkBottomNotLoaded = loadedAtBottom()
- && !unread
- && !isEmpty();
- if (shouldMarkBottomNotLoaded) {
- setNotLoadedAtBottom();
- }
- if (!loadedAtBottom() || peer->migrateTo()) {
- setLastMessage(item);
- if (unread) {
- newItemAdded(item);
- }
- } else {
- addNewToBack(item, unread);
- checkForLoadedAtTop(item);
- }
- if (const auto sublist = item->savedSublist()) {
- sublist->applyMaybeLast(item, unread);
- }
- return item;
- }
- void History::checkForLoadedAtTop(not_null<HistoryItem*> added) {
- if (peer->isChat()) {
- if (added->isGroupEssential() && !added->isGroupMigrate()) {
- // We added the first message about group creation.
- _loadedAtTop = true;
- addEdgesToSharedMedia();
- }
- } else if (peer->isChannel()) {
- if (added->id == 1) {
- _loadedAtTop = true;
- checkLocalMessages();
- addEdgesToSharedMedia();
- }
- }
- }
- not_null<HistoryItem*> History::addNewLocalMessage(
- HistoryItemCommonFields &&fields,
- const TextWithEntities &text,
- const MTPMessageMedia &media) {
- return addNewItem(
- makeMessage(WithLocalFlag(std::move(fields)), text, media),
- true);
- }
- not_null<HistoryItem*> History::addNewLocalMessage(
- HistoryItemCommonFields &&fields,
- not_null<HistoryItem*> forwardOriginal) {
- return addNewItem(
- makeMessage(WithLocalFlag(std::move(fields)), forwardOriginal),
- true);
- }
- not_null<HistoryItem*> History::addNewLocalMessage(
- HistoryItemCommonFields &&fields,
- not_null<DocumentData*> document,
- const TextWithEntities &caption) {
- return addNewItem(
- makeMessage(WithLocalFlag(std::move(fields)), document, caption),
- true);
- }
- not_null<HistoryItem*> History::addNewLocalMessage(
- HistoryItemCommonFields &&fields,
- not_null<PhotoData*> photo,
- const TextWithEntities &caption) {
- return addNewItem(
- makeMessage(WithLocalFlag(std::move(fields)), photo, caption),
- true);
- }
- not_null<HistoryItem*> History::addNewLocalMessage(
- HistoryItemCommonFields &&fields,
- not_null<GameData*> game) {
- return addNewItem(
- makeMessage(WithLocalFlag(std::move(fields)), game),
- true);
- }
- not_null<HistoryItem*> History::addNewLocalMessage(
- not_null<HistoryItem*> item) {
- Expects(item->history() == this);
- Expects(item->isLocal());
- return addNewItem(item, true);
- }
- not_null<HistoryItem*> History::addSponsoredMessage(
- MsgId id,
- Data::SponsoredFrom from,
- const TextWithEntities &textWithEntities) {
- return addNewItem(
- makeMessage(id, from, textWithEntities, nullptr),
- true);
- }
- void History::clearUnreadMentionsFor(MsgId topicRootId) {
- const auto forum = peer->forum();
- if (!topicRootId) {
- if (forum) {
- forum->clearAllUnreadMentions();
- }
- unreadMentions().clear();
- return;
- } else if (forum) {
- if (const auto topic = forum->topicFor(topicRootId)) {
- topic->unreadMentions().clear();
- }
- }
- const auto &ids = unreadMentionsIds();
- if (ids.empty()) {
- return;
- }
- const auto owner = &this->owner();
- const auto peerId = peer->id;
- auto items = base::flat_set<MsgId>();
- items.reserve(ids.size());
- for (const auto &id : ids) {
- if (const auto item = owner->message(peerId, id)) {
- if (item->topicRootId() == topicRootId) {
- items.emplace(id);
- }
- }
- }
- for (const auto &id : items) {
- unreadMentions().erase(id);
- }
- }
- void History::clearUnreadReactionsFor(MsgId topicRootId) {
- const auto forum = peer->forum();
- if (!topicRootId) {
- if (forum) {
- forum->clearAllUnreadReactions();
- }
- unreadReactions().clear();
- return;
- } else if (forum) {
- if (const auto topic = forum->topicFor(topicRootId)) {
- topic->unreadReactions().clear();
- }
- }
- const auto &ids = unreadReactionsIds();
- if (ids.empty()) {
- return;
- }
- const auto owner = &this->owner();
- const auto peerId = peer->id;
- auto items = base::flat_set<MsgId>();
- items.reserve(ids.size());
- for (const auto &id : ids) {
- if (const auto item = owner->message(peerId, id)) {
- if (item->topicRootId() == topicRootId) {
- items.emplace(id);
- }
- }
- }
- for (const auto &id : items) {
- unreadReactions().erase(id);
- }
- }
- not_null<HistoryItem*> History::addNewToBack(
- not_null<HistoryItem*> item,
- bool unread) {
- Expects(!isBuildingFrontBlock());
- addItemToBlock(item);
- if (!unread && item->isRegular()) {
- const auto from = loadedAtTop() ? 0 : minMsgId();
- const auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
- if (_messages) {
- _messages->addExisting(item->id, { from, till });
- }
- if (const auto types = item->sharedMediaTypes()) {
- auto &storage = session().storage();
- storage.add(Storage::SharedMediaAddExisting(
- peer->id,
- MsgId(0), // topicRootId
- types,
- item->id,
- { from, till }));
- const auto pinned = types.test(Storage::SharedMediaType::Pinned);
- if (pinned) {
- setHasPinnedMessages(true);
- }
- if (const auto topic = item->topic()) {
- storage.add(Storage::SharedMediaAddExisting(
- peer->id,
- topic->rootId(),
- types,
- item->id,
- { item->id, item->id}));
- if (pinned) {
- topic->setHasPinnedMessages(true);
- }
- }
- }
- }
- if (item->from()->id) {
- if (auto user = item->from()->asUser()) {
- auto getLastAuthors = [this]() -> std::deque<not_null<UserData*>>* {
- if (auto chat = peer->asChat()) {
- return &chat->lastAuthors;
- } else if (auto channel = peer->asMegagroup()) {
- return channel->canViewMembers()
- ? &channel->mgInfo->lastParticipants
- : nullptr;
- }
- return nullptr;
- };
- if (auto megagroup = peer->asMegagroup()) {
- if (user->isBot()) {
- auto mgInfo = megagroup->mgInfo.get();
- Assert(mgInfo != nullptr);
- mgInfo->bots.insert(user);
- if (mgInfo->botStatus != 0 && mgInfo->botStatus < 2) {
- mgInfo->botStatus = 2;
- }
- }
- }
- if (auto lastAuthors = getLastAuthors()) {
- auto prev = ranges::find(
- *lastAuthors,
- user,
- [](not_null<UserData*> user) { return user.get(); });
- auto index = (prev != lastAuthors->end())
- ? (lastAuthors->end() - prev)
- : -1;
- if (index > 0) {
- lastAuthors->erase(prev);
- } else if (index < 0 && peer->isMegagroup()) { // nothing is outdated if just reordering
- // admins information outdated
- }
- if (index) {
- lastAuthors->push_front(user);
- }
- if (auto megagroup = peer->asMegagroup()) {
- session().changes().peerUpdated(
- peer,
- Data::PeerUpdate::Flag::Members);
- owner().addNewMegagroupParticipant(megagroup, user);
- }
- }
- }
- if (item->definesReplyKeyboard()) {
- auto markupFlags = item->replyKeyboardFlags();
- if (!(markupFlags & ReplyMarkupFlag::Selective)
- || item->mentionsMe()) {
- auto getMarkupSenders = [this]() -> base::flat_set<not_null<PeerData*>>* {
- if (auto chat = peer->asChat()) {
- return &chat->markupSenders;
- } else if (auto channel = peer->asMegagroup()) {
- return &channel->mgInfo->markupSenders;
- }
- return nullptr;
- };
- if (auto markupSenders = getMarkupSenders()) {
- markupSenders->insert(item->from());
- }
- if (markupFlags & ReplyMarkupFlag::None) {
- // None markup means replyKeyboardHide.
- if (lastKeyboardFrom == item->from()->id
- || (!lastKeyboardInited
- && !peer->isChat()
- && !peer->isMegagroup()
- && !item->out())) {
- clearLastKeyboard();
- }
- } else {
- bool botNotInChat = false;
- if (peer->isChat()) {
- botNotInChat = item->from()->isUser()
- && (!peer->asChat()->participants.empty()
- || !Data::CanSendAnything(peer))
- && !peer->asChat()->participants.contains(
- item->from()->asUser());
- } else if (peer->isMegagroup()) {
- botNotInChat = item->from()->isUser()
- && (peer->asChannel()->mgInfo->botStatus != 0
- || !Data::CanSendAnything(peer))
- && !peer->asChannel()->mgInfo->bots.contains(
- item->from()->asUser());
- }
- if (botNotInChat) {
- clearLastKeyboard();
- } else {
- lastKeyboardInited = true;
- lastKeyboardId = item->id;
- lastKeyboardFrom = item->from()->id;
- lastKeyboardUsed = false;
- }
- }
- }
- }
- }
- setLastMessage(item);
- if (unread) {
- newItemAdded(item);
- }
- owner().notifyHistoryChangeDelayed(this);
- return item;
- }
- void History::applyMessageChanges(
- not_null<HistoryItem*> item,
- const MTPMessage &data) {
- if (data.type() == mtpc_messageService) {
- applyServiceChanges(item, data.c_messageService());
- }
- owner().stickers().checkSavedGif(item);
- session().changes().messageUpdated(
- item,
- Data::MessageUpdate::Flag::NewAdded);
- }
- void History::applyServiceChanges(
- not_null<HistoryItem*> item,
- const MTPDmessageService &data) {
- const auto replyTo = data.vreply_to();
- const auto processJoinedUser = [&](
- not_null<ChannelData*> megagroup,
- not_null<MegagroupInfo*> mgInfo,
- not_null<UserData*> user) {
- if (!base::contains(mgInfo->lastParticipants, user)
- && megagroup->canViewMembers()) {
- mgInfo->lastParticipants.push_front(user);
- session().changes().peerUpdated(
- peer,
- Data::PeerUpdate::Flag::Members);
- owner().addNewMegagroupParticipant(megagroup, user);
- }
- if (user->isBot()) {
- mgInfo->bots.insert(user);
- if (mgInfo->botStatus != 0 && mgInfo->botStatus < 2) {
- mgInfo->botStatus = 2;
- }
- }
- };
- const auto processJoinedPeer = [&](not_null<PeerData*> joined) {
- if (const auto megagroup = peer->asMegagroup()) {
- const auto mgInfo = megagroup->mgInfo.get();
- Assert(mgInfo != nullptr);
- if (const auto user = joined->asUser()) {
- processJoinedUser(megagroup, mgInfo, user);
- }
- }
- };
- data.vaction().match([&](const MTPDmessageActionChatAddUser &data) {
- if (const auto megagroup = peer->asMegagroup()) {
- const auto mgInfo = megagroup->mgInfo.get();
- Assert(mgInfo != nullptr);
- for (const auto &userId : data.vusers().v) {
- if (const auto user = owner().userLoaded(userId.v)) {
- processJoinedUser(megagroup, mgInfo, user);
- }
- }
- }
- }, [&](const MTPDmessageActionChatJoinedByLink &data) {
- processJoinedPeer(item->from());
- }, [&](const MTPDmessageActionChatDeletePhoto &data) {
- if (const auto chat = peer->asChat()) {
- chat->setPhoto(MTP_chatPhotoEmpty());
- }
- }, [&](const MTPDmessageActionChatDeleteUser &data) {
- const auto uid = data.vuser_id().v;
- if (lastKeyboardFrom == peerFromUser(uid)) {
- clearLastKeyboard();
- }
- if (const auto megagroup = peer->asMegagroup()) {
- if (const auto user = owner().userLoaded(uid)) {
- const auto mgInfo = megagroup->mgInfo.get();
- Assert(mgInfo != nullptr);
- const auto i = ranges::find(
- mgInfo->lastParticipants,
- user,
- [](not_null<UserData*> user) { return user.get(); });
- if (i != mgInfo->lastParticipants.end()) {
- mgInfo->lastParticipants.erase(i);
- session().changes().peerUpdated(
- peer,
- Data::PeerUpdate::Flag::Members);
- }
- owner().removeMegagroupParticipant(megagroup, user);
- if (megagroup->membersCount() > 1) {
- megagroup->setMembersCount(
- megagroup->membersCount() - 1);
- } else {
- mgInfo->lastParticipantsStatus
- |= MegagroupInfo::LastParticipantsCountOutdated;
- mgInfo->lastParticipantsCount = 0;
- }
- if (mgInfo->lastAdmins.contains(user)) {
- mgInfo->lastAdmins.remove(user);
- if (megagroup->adminsCount() > 1) {
- megagroup->setAdminsCount(
- megagroup->adminsCount() - 1);
- }
- session().changes().peerUpdated(
- peer,
- Data::PeerUpdate::Flag::Admins);
- }
- mgInfo->bots.remove(user);
- if (mgInfo->bots.empty() && mgInfo->botStatus > 0) {
- mgInfo->botStatus = -1;
- }
- }
- Data::ChannelAdminChanges(megagroup).remove(uid);
- }
- }, [&](const MTPDmessageActionChatEditPhoto &data) {
- data.vphoto().match([&](const MTPDphoto &data) {
- using Flag = MTPDchatPhoto::Flag;
- const auto photo = owner().processPhoto(data);
- photo->peer = peer;
- const auto chatPhoto = MTP_chatPhoto(
- MTP_flags((photo->hasVideo() ? Flag::f_has_video : Flag(0))
- | (photo->inlineThumbnailBytes().isEmpty()
- ? Flag(0)
- : Flag::f_stripped_thumb)),
- MTP_long(photo->id),
- MTP_bytes(photo->inlineThumbnailBytes()),
- data.vdc_id());
- if (const auto chat = peer->asChat()) {
- chat->setPhoto(chatPhoto);
- } else if (const auto channel = peer->asChannel()) {
- channel->setPhoto(chatPhoto);
- }
- peer->loadUserpic();
- }, [&](const MTPDphotoEmpty &data) {
- if (const auto chat = peer->asChat()) {
- chat->setPhoto(MTP_chatPhotoEmpty());
- } else if (const auto channel = peer->asChannel()) {
- channel->setPhoto(MTP_chatPhotoEmpty());
- }
- });
- }, [&](const MTPDmessageActionChatEditTitle &data) {
- if (const auto chat = peer->asChat()) {
- chat->setName(qs(data.vtitle()));
- }
- }, [&](const MTPDmessageActionChatMigrateTo &data) {
- if (const auto chat = peer->asChat()) {
- chat->addFlags(ChatDataFlag::Deactivated);
- if (const auto channel = owner().channelLoaded(
- data.vchannel_id().v)) {
- Data::ApplyMigration(chat, channel);
- }
- }
- }, [&](const MTPDmessageActionChannelMigrateFrom &data) {
- if (const auto channel = peer->asChannel()) {
- channel->addFlags(ChannelDataFlag::Megagroup);
- if (const auto chat = owner().chatLoaded(data.vchat_id().v)) {
- Data::ApplyMigration(chat, channel);
- }
- }
- }, [&](const MTPDmessageActionPinMessage &data) {
- if (replyTo) {
- replyTo->match([&](const MTPDmessageReplyHeader &data) {
- const auto id = data.vreply_to_msg_id().value_or_empty();
- if (id && item) {
- session().storage().add(Storage::SharedMediaAddSlice(
- peer->id,
- MsgId(0),
- Storage::SharedMediaType::Pinned,
- { id },
- { id, ServerMaxMsgId }));
- setHasPinnedMessages(true);
- if (const auto topic = item->topic()) {
- session().storage().add(Storage::SharedMediaAddSlice(
- peer->id,
- topic->rootId(),
- Storage::SharedMediaType::Pinned,
- { id },
- { id, ServerMaxMsgId }));
- topic->setHasPinnedMessages(true);
- }
- }
- }, [&](const MTPDmessageReplyStoryHeader &data) {
- LOG(("API Error: story reply in messageActionPinMessage."));
- });
- }
- }, [&](const MTPDmessageActionGroupCall &data) {
- if (const auto channel = peer->asChannel()) {
- channel->setGroupCall(data.vcall());
- } else if (const auto chat = peer->asChat()) {
- chat->setGroupCall(data.vcall());
- }
- }, [&](const MTPDmessageActionGroupCallScheduled &data) {
- if (const auto channel = peer->asChannel()) {
- channel->setGroupCall(data.vcall(), data.vschedule_date().v);
- } else if (const auto chat = peer->asChat()) {
- chat->setGroupCall(data.vcall(), data.vschedule_date().v);
- }
- }, [&](const MTPDmessageActionPaymentSent &data) {
- if (const auto payment = item->Get<HistoryServicePayment>()) {
- auto paid = std::optional<Payments::PaidInvoice>();
- if (const auto message = payment->msg) {
- if (const auto media = message->media()) {
- if (const auto invoice = media->invoice()) {
- paid = Payments::CheckoutProcess::InvoicePaid(
- message);
- }
- }
- } else if (!payment->slug.isEmpty()) {
- using Payments::CheckoutProcess;
- paid = Payments::CheckoutProcess::InvoicePaid(
- &session(),
- payment->slug);
- }
- if (paid) {
- // Toast on a current active window.
- Ui::Toast::Show({
- .text = tr::lng_payments_success(
- tr::now,
- lt_amount,
- Ui::Text::Wrapped(
- payment->amount,
- EntityType::Bold),
- lt_title,
- Ui::Text::Bold(paid->title),
- Ui::Text::WithEntities),
- .textContext = Core::TextContext({
- .session = &session(),
- }),
- });
- }
- }
- }, [&](const MTPDmessageActionSetChatTheme &data) {
- peer->setThemeEmoji(qs(data.vemoticon()));
- }, [&](const MTPDmessageActionSetChatWallPaper &data) {
- if (item->out() || data.is_for_both()) {
- peer->setWallPaper(
- Data::WallPaper::Create(&session(), data.vwallpaper()),
- !item->out() && data.is_for_both());
- }
- }, [&](const MTPDmessageActionChatJoinedByRequest &data) {
- processJoinedPeer(item->from());
- }, [&](const MTPDmessageActionTopicCreate &data) {
- if (const auto forum = peer->forum()) {
- forum->applyTopicAdded(
- item->id,
- qs(data.vtitle()),
- data.vicon_color().v,
- data.vicon_emoji_id().value_or(DocumentId()),
- item->from()->id,
- item->date(),
- item->out());
- }
- }, [&](const MTPDmessageActionTopicEdit &data) {
- if (const auto topic = item->topic()) {
- if (const auto &title = data.vtitle()) {
- topic->applyTitle(qs(*title));
- }
- if (const auto icon = data.vicon_emoji_id()) {
- topic->applyIconId(icon->v);
- }
- if (const auto closed = data.vclosed()) {
- topic->setClosed(mtpIsTrue(*closed));
- }
- if (const auto hidden = data.vhidden()) {
- topic->setHidden(mtpIsTrue(*hidden));
- }
- }
- }, [](const auto &) {
- });
- }
- void History::mainViewRemoved(
- not_null<HistoryBlock*> block,
- not_null<HistoryView::Element*> view) {
- Expects(_joinedMessage != view->data());
- Expects(_newPeerNameChange != view->data());
- Expects(_newPeerPhotoChange != view->data());
- if (_firstUnreadView == view) {
- getNextFirstUnreadMessage();
- }
- if (_unreadBarView == view) {
- _unreadBarView = nullptr;
- }
- if (scrollTopItem == view) {
- getNextScrollTopItem(block, view->indexInBlock());
- }
- }
- void History::newItemAdded(not_null<HistoryItem*> item) {
- item->indexAsNewItem();
- item->addToMessagesIndex();
- if (const auto from = item->from() ? item->from()->asUser() : nullptr) {
- if (from == item->author()) {
- _sendActionPainter.clear(from);
- owner().sendActionManager().repliesPaintersClear(this, from);
- }
- from->madeAction(item->date());
- }
- item->contributeToSlowmode();
- auto notification = Data::ItemNotification{
- .item = item,
- .type = Data::ItemNotificationType::Message,
- };
- if (item->showNotification()) {
- item->notificationThread()->pushNotification(notification);
- }
- owner().notifyNewItemAdded(item);
- const auto stillShow = item->showNotification(); // Could be read already.
- if (stillShow) {
- Core::App().notifications().schedule(notification);
- }
- if (item->out()) {
- if (item->isFromScheduled() && unreadCountRefreshNeeded(item->id)) {
- if (unreadCountKnown()) {
- setUnreadCount(unreadCount() + 1);
- } else if (!isForum()) {
- owner().histories().requestDialogEntry(this);
- }
- } else {
- destroyUnreadBar();
- }
- if (!item->unread(this)) {
- outboxRead(item);
- }
- if (item->changesWallPaper()) {
- peer->updateFullForced();
- }
- } else {
- if (item->unread(this)) {
- if (unreadCountKnown()) {
- setUnreadCount(unreadCount() + 1);
- } else if (!isForum()) {
- owner().histories().requestDialogEntry(this);
- }
- } else {
- inboxRead(item);
- }
- }
- item->incrementReplyToTopCounter();
- if (!folderKnown()) {
- owner().histories().requestDialogEntry(this);
- }
- if (const auto topic = item->topic()) {
- topic->applyItemAdded(item);
- }
- if (const auto media = item->media()) {
- if (const auto gift = media->gift()) {
- if (const auto unique = gift->unique.get()) {
- if (unique->ownerId == session().userPeerId()) {
- owner().emojiStatuses().refreshCollectibles();
- }
- }
- }
- }
- }
- void History::registerClientSideMessage(not_null<HistoryItem*> item) {
- Expects(item->isHistoryEntry());
- Expects(IsClientMsgId(item->id));
- _clientSideMessages.emplace(item);
- session().changes().historyUpdated(this, UpdateFlag::ClientSideMessages);
- }
- void History::unregisterClientSideMessage(not_null<HistoryItem*> item) {
- const auto removed = _clientSideMessages.remove(item);
- Assert(removed);
- session().changes().historyUpdated(this, UpdateFlag::ClientSideMessages);
- }
- const base::flat_set<not_null<HistoryItem*>> &History::clientSideMessages() {
- return _clientSideMessages;
- }
- HistoryItem *History::latestSendingMessage() const {
- auto sending = ranges::views::all(
- _clientSideMessages
- ) | ranges::views::filter([](not_null<HistoryItem*> item) {
- return item->isSending();
- });
- const auto i = ranges::max_element(sending, ranges::less(), [](
- not_null<HistoryItem*> item) {
- return std::pair(item->date(), item->id.bare);
- });
- return (i == sending.end()) ? nullptr : i->get();
- }
- HistoryBlock *History::prepareBlockForAddingItem() {
- if (isBuildingFrontBlock()) {
- if (_buildingFrontBlock->block) {
- return _buildingFrontBlock->block;
- }
- blocks.push_front(std::make_unique<HistoryBlock>(this));
- for (auto i = 0, l = int(blocks.size()); i != l; ++i) {
- blocks[i]->setIndexInHistory(i);
- }
- _buildingFrontBlock->block = blocks.front().get();
- if (_buildingFrontBlock->expectedItemsCount > 0) {
- _buildingFrontBlock->block->messages.reserve(
- _buildingFrontBlock->expectedItemsCount + 1);
- }
- return _buildingFrontBlock->block;
- }
- const auto addNewBlock = blocks.empty()
- || (blocks.back()->messages.size() >= kNewBlockEachMessage);
- if (addNewBlock) {
- blocks.push_back(std::make_unique<HistoryBlock>(this));
- blocks.back()->setIndexInHistory(blocks.size() - 1);
- blocks.back()->messages.reserve(kNewBlockEachMessage);
- }
- return blocks.back().get();
- }
- void History::viewReplaced(not_null<const Element*> was, Element *now) {
- if (scrollTopItem == was) scrollTopItem = now;
- if (_firstUnreadView == was) _firstUnreadView = now;
- if (_unreadBarView == was) _unreadBarView = now;
- }
- void History::addItemToBlock(not_null<HistoryItem*> item) {
- Expects(!item->mainView());
- auto block = prepareBlockForAddingItem();
- block->messages.push_back(item->createView(_delegateMixin->delegate()));
- const auto view = block->messages.back().get();
- view->attachToBlock(block, block->messages.size() - 1);
- if (isBuildingFrontBlock() && _buildingFrontBlock->expectedItemsCount > 0) {
- --_buildingFrontBlock->expectedItemsCount;
- }
- }
- void History::addEdgesToSharedMedia() {
- auto from = loadedAtTop() ? 0 : minMsgId();
- auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
- for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) {
- const auto type = static_cast<Storage::SharedMediaType>(i);
- session().storage().add(Storage::SharedMediaAddSlice(
- peer->id,
- MsgId(0), // topicRootId
- type,
- {},
- { from, till }));
- }
- }
- void History::addOlderSlice(const QVector<MTPMessage> &slice) {
- if (slice.isEmpty()) {
- _loadedAtTop = true;
- checkLocalMessages();
- return;
- }
- if (const auto added = createItems(slice); !added.empty()) {
- addCreatedOlderSlice(added);
- } else {
- // If no items were added it means we've loaded everything old.
- _loadedAtTop = true;
- addEdgesToSharedMedia();
- }
- checkLocalMessages();
- checkLastMessage();
- }
- void History::addCreatedOlderSlice(
- const std::vector<not_null<HistoryItem*>> &items) {
- startBuildingFrontBlock(items.size());
- for (const auto &item : items) {
- addItemToBlock(item);
- }
- finishBuildingFrontBlock();
- if (loadedAtBottom()) {
- // Add photos to overview and authors to lastAuthors.
- addItemsToLists(items);
- for (const auto &item : items) {
- if (const auto sublist = item->savedSublist()) {
- sublist->applyMaybeLast(item);
- }
- }
- }
- addToSharedMedia(items);
- }
- void History::addNewerSlice(const QVector<MTPMessage> &slice) {
- bool wasLoadedAtBottom = loadedAtBottom();
- if (slice.isEmpty()) {
- _loadedAtBottom = true;
- if (!lastMessage()) {
- setLastMessage(lastAvailableMessage());
- }
- }
- if (const auto added = createItems(slice); !added.empty()) {
- Assert(!isBuildingFrontBlock());
- for (const auto &item : added) {
- addItemToBlock(item);
- }
- addToSharedMedia(added);
- } else {
- _loadedAtBottom = true;
- setLastMessage(lastAvailableMessage());
- addEdgesToSharedMedia();
- }
- if (!wasLoadedAtBottom) {
- checkAddAllToUnreadMentions();
- }
- checkLocalMessages();
- checkLastMessage();
- }
- void History::checkLastMessage() {
- if (const auto last = lastMessage()) {
- if (!_loadedAtBottom && last->mainView()) {
- _loadedAtBottom = true;
- checkAddAllToUnreadMentions();
- }
- } else if (_loadedAtBottom) {
- setLastMessage(lastAvailableMessage());
- }
- }
- void History::addItemsToLists(
- const std::vector<not_null<HistoryItem*>> &items) {
- std::deque<not_null<UserData*>> *lastAuthors = nullptr;
- base::flat_set<not_null<PeerData*>> *markupSenders = nullptr;
- if (peer->isChat()) {
- lastAuthors = &peer->asChat()->lastAuthors;
- markupSenders = &peer->asChat()->markupSenders;
- } else if (peer->isMegagroup()) {
- // We don't add users to mgInfo->lastParticipants here.
- // We're scrolling back and we see messages from users that
- // could be gone from the megagroup already. It is fine for
- // chat->lastAuthors, because they're used only for field
- // autocomplete, but this is bad for megagroups, because its
- // lastParticipants are displayed in Profile as members list.
- markupSenders = &peer->asChannel()->mgInfo->markupSenders;
- }
- for (const auto &item : ranges::views::reverse(items)) {
- item->addToUnreadThings(HistoryUnreadThings::AddType::Existing);
- if (item->from()->id) {
- if (lastAuthors) { // chats
- if (auto user = item->from()->asUser()) {
- if (!base::contains(*lastAuthors, user)) {
- lastAuthors->push_back(user);
- }
- }
- }
- }
- if (item->author()->id) {
- if (markupSenders) { // chats with bots
- if (!lastKeyboardInited && item->definesReplyKeyboard() && !item->out()) {
- const auto markupFlags = item->replyKeyboardFlags();
- if (!(markupFlags & ReplyMarkupFlag::Selective) || item->mentionsMe()) {
- bool wasKeyboardHide = markupSenders->contains(item->author());
- if (!wasKeyboardHide) {
- markupSenders->insert(item->author());
- }
- if (!(markupFlags & ReplyMarkupFlag::None)) {
- if (!lastKeyboardInited) {
- bool botNotInChat = false;
- if (peer->isChat()) {
- botNotInChat = (!Data::CanSendAnything(peer)
- || !peer->asChat()->participants.empty())
- && item->author()->isUser()
- && !peer->asChat()->participants.contains(item->author()->asUser());
- } else if (peer->isMegagroup()) {
- botNotInChat = (!Data::CanSendAnything(peer)
- || peer->asChannel()->mgInfo->botStatus != 0)
- && item->author()->isUser()
- && !peer->asChannel()->mgInfo->bots.contains(item->author()->asUser());
- }
- if (wasKeyboardHide || botNotInChat) {
- clearLastKeyboard();
- } else {
- lastKeyboardInited = true;
- lastKeyboardId = item->id;
- lastKeyboardFrom = item->author()->id;
- lastKeyboardUsed = false;
- }
- }
- }
- }
- }
- } else if (!lastKeyboardInited && item->definesReplyKeyboard() && !item->out()) { // conversations with bots
- const auto markupFlags = item->replyKeyboardFlags();
- if (!(markupFlags & ReplyMarkupFlag::Selective) || item->mentionsMe()) {
- if (markupFlags & ReplyMarkupFlag::None) {
- clearLastKeyboard();
- } else {
- lastKeyboardInited = true;
- lastKeyboardId = item->id;
- lastKeyboardFrom = item->author()->id;
- lastKeyboardUsed = false;
- }
- }
- }
- }
- }
- }
- void History::checkAddAllToUnreadMentions() {
- if (!loadedAtBottom()) {
- return;
- }
- for (const auto &block : blocks) {
- for (const auto &message : block->messages) {
- const auto item = message->data();
- item->addToUnreadThings(HistoryUnreadThings::AddType::Existing);
- }
- }
- }
- void History::addToSharedMedia(
- const std::vector<not_null<HistoryItem*>> &items) {
- std::vector<MsgId> medias[Storage::kSharedMediaTypeCount];
- auto topicsWithPinned = base::flat_set<not_null<Data::ForumTopic*>>();
- for (const auto &item : items) {
- if (const auto types = item->sharedMediaTypes()) {
- for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) {
- const auto type = static_cast<Storage::SharedMediaType>(i);
- if (types.test(type)) {
- if (medias[i].empty()) {
- medias[i].reserve(items.size());
- }
- medias[i].push_back(item->id);
- if (type == Storage::SharedMediaType::Pinned) {
- if (const auto topic = item->topic()) {
- if (!topic->hasPinnedMessages()) {
- topicsWithPinned.emplace(topic);
- }
- }
- }
- }
- }
- }
- }
- const auto from = loadedAtTop() ? 0 : minMsgId();
- const auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
- for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) {
- if (!medias[i].empty()) {
- const auto type = static_cast<Storage::SharedMediaType>(i);
- session().storage().add(Storage::SharedMediaAddSlice(
- peer->id,
- MsgId(0), // topicRootId
- type,
- std::move(medias[i]),
- { from, till }));
- if (type == Storage::SharedMediaType::Pinned) {
- setHasPinnedMessages(true);
- }
- }
- }
- for (const auto &topic : topicsWithPinned) {
- topic->setHasPinnedMessages(true);
- }
- }
- void History::calculateFirstUnreadMessage() {
- if (!_inboxReadBefore) {
- return;
- }
- _firstUnreadView = nullptr;
- if (!unreadCount() || !trackUnreadMessages()) {
- return;
- }
- for (const auto &block : ranges::views::reverse(blocks)) {
- for (const auto &message : ranges::views::reverse(block->messages)) {
- const auto item = message->data();
- if (!item->isRegular()) {
- continue;
- } else if (!item->out()) {
- if (item->id >= *_inboxReadBefore) {
- _firstUnreadView = message.get();
- } else {
- return;
- }
- }
- }
- }
- }
- bool History::readInboxTillNeedsRequest(MsgId tillId) {
- Expects(!tillId || IsServerMsgId(tillId));
- readClientSideMessages();
- if (unreadMark()) {
- owner().histories().changeDialogUnreadMark(this, false);
- }
- DEBUG_LOG(("Reading: readInboxTillNeedsRequest is_server %1, before %2."
- ).arg(Logs::b(IsServerMsgId(tillId))
- ).arg(_inboxReadBefore.value_or(-666).bare));
- return IsServerMsgId(tillId) && (_inboxReadBefore.value_or(1) <= tillId);
- }
- void History::readClientSideMessages() {
- auto &histories = owner().histories();
- for (const auto &item : _clientSideMessages) {
- histories.readClientSideMessage(item);
- }
- }
- bool History::unreadCountRefreshNeeded(MsgId readTillId) const {
- return !unreadCountKnown()
- || ((readTillId + 1) > _inboxReadBefore.value_or(0));
- }
- std::optional<int> History::countStillUnreadLocal(MsgId readTillId) const {
- if (isEmpty() || !folderKnown()) {
- DEBUG_LOG(("Reading: countStillUnreadLocal unknown %1 and %2.").arg(
- Logs::b(isEmpty()),
- Logs::b(folderKnown())));
- return std::nullopt;
- }
- if (_inboxReadBefore) {
- const auto before = *_inboxReadBefore;
- DEBUG_LOG(("Reading: check before %1 with min %2 and max %3."
- ).arg(before.bare
- ).arg(minMsgId().bare
- ).arg(maxMsgId().bare));
- if (minMsgId() <= before && maxMsgId() >= readTillId) {
- auto result = 0;
- for (const auto &block : blocks) {
- for (const auto &message : block->messages) {
- const auto item = message->data();
- if (!item->isRegular()
- || (item->out() && !item->isFromScheduled())) {
- continue;
- } else if (item->id > readTillId) {
- break;
- } else if (item->id >= before) {
- ++result;
- }
- }
- }
- DEBUG_LOG(("Reading: check before result %1 with existing %2"
- ).arg(result
- ).arg(_unreadCount.value_or(-666)));
- if (_unreadCount) {
- return std::max(*_unreadCount - result, 0);
- }
- }
- }
- const auto minimalServerId = minMsgId();
- DEBUG_LOG(("Reading: check at end loaded from %1 loaded %2 - %3").arg(
- QString::number(minimalServerId.bare),
- Logs::b(loadedAtBottom()),
- Logs::b(loadedAtTop())));
- if (!loadedAtBottom()
- || (!loadedAtTop() && !minimalServerId)
- || minimalServerId > readTillId) {
- return std::nullopt;
- }
- auto result = 0;
- for (const auto &block : ranges::views::reverse(blocks)) {
- for (const auto &message : ranges::views::reverse(block->messages)) {
- const auto item = message->data();
- if (item->isRegular()) {
- if (item->id <= readTillId) {
- return result;
- } else if (!item->out()) {
- ++result;
- }
- }
- }
- }
- DEBUG_LOG(("Reading: check at end counted %1").arg(result));
- return result;
- }
- void History::applyInboxReadUpdate(
- FolderId folderId,
- MsgId upTo,
- int stillUnread,
- int32 channelPts) {
- const auto folder = folderId ? owner().folderLoaded(folderId) : nullptr;
- if (folder && this->folder() != folder) {
- // If history folder is unknown or not synced, request both.
- owner().histories().requestDialogEntry(this);
- owner().histories().requestDialogEntry(folder);
- }
- if (_inboxReadBefore.value_or(1) <= upTo) {
- if (!peer->isChannel() || peer->asChannel()->pts() == channelPts) {
- inboxRead(upTo, stillUnread);
- } else {
- inboxRead(upTo);
- }
- }
- }
- void History::inboxRead(MsgId upTo, std::optional<int> stillUnread) {
- if (stillUnread.has_value() && folderKnown()) {
- setUnreadCount(*stillUnread);
- } else if (const auto still = countStillUnreadLocal(upTo)) {
- setUnreadCount(*still);
- } else {
- owner().histories().requestDialogEntry(this);
- }
- setInboxReadTill(upTo);
- updateChatListEntry();
- if (const auto to = peer->migrateTo()) {
- if (const auto migrated = peer->owner().historyLoaded(to->id)) {
- migrated->updateChatListEntry();
- }
- }
- _firstUnreadView = nullptr;
- Core::App().notifications().clearIncomingFromHistory(this);
- }
- void History::inboxRead(not_null<const HistoryItem*> wasRead) {
- if (wasRead->isRegular()) {
- inboxRead(wasRead->id);
- }
- }
- void History::outboxRead(MsgId upTo) {
- setOutboxReadTill(upTo);
- if (const auto last = chatListMessage()) {
- if (last->out() && last->isRegular() && last->id <= upTo) {
- session().changes().messageUpdated(
- last,
- Data::MessageUpdate::Flag::DialogRowRepaint);
- }
- }
- updateChatListEntry();
- session().changes().historyUpdated(this, UpdateFlag::OutboxRead);
- }
- void History::outboxRead(not_null<const HistoryItem*> wasRead) {
- if (wasRead->isRegular()) {
- outboxRead(wasRead->id);
- }
- }
- MsgId History::loadAroundId() const {
- if (_unreadCount && *_unreadCount > 0 && _inboxReadBefore) {
- return *_inboxReadBefore;
- }
- return MsgId(0);
- }
- bool History::inboxReadTillKnown() const {
- return _inboxReadBefore.has_value();
- }
- MsgId History::inboxReadTillId() const {
- return _inboxReadBefore.value_or(1) - 1;
- }
- MsgId History::outboxReadTillId() const {
- return _outboxReadBefore.value_or(1) - 1;
- }
- HistoryItem *History::lastAvailableMessage() const {
- return isEmpty() ? nullptr : blocks.back()->messages.back()->data().get();
- }
- int History::unreadCount() const {
- return _unreadCount ? *_unreadCount : 0;
- }
- bool History::unreadCountKnown() const {
- return _unreadCount.has_value();
- }
- void History::setUnreadCount(int newUnreadCount) {
- Expects(folderKnown());
- if (_unreadCount == newUnreadCount) {
- return;
- }
- const auto notifier = unreadStateChangeNotifier(!isForum());
- _unreadCount = newUnreadCount;
- const auto lastOutgoing = [&] {
- const auto last = lastMessage();
- return last
- && last->isRegular()
- && loadedAtBottom()
- && !isEmpty()
- && blocks.back()->messages.back()->data() == last
- && last->out();
- }();
- if (newUnreadCount == 1 && !lastOutgoing) {
- if (loadedAtBottom()) {
- _firstUnreadView = !isEmpty()
- ? blocks.back()->messages.back().get()
- : nullptr;
- }
- if (const auto last = msgIdForRead()) {
- setInboxReadTill(last - 1);
- }
- } else if (!newUnreadCount) {
- _firstUnreadView = nullptr;
- if (const auto last = msgIdForRead()) {
- setInboxReadTill(last);
- }
- } else if (!_firstUnreadView && !_unreadBarView && loadedAtBottom()) {
- calculateFirstUnreadMessage();
- }
- }
- void History::setUnreadMark(bool unread) {
- if (clearUnreadOnClientSide()) {
- unread = false;
- }
- if (unreadMark() == unread) {
- return;
- }
- const auto notifier = unreadStateChangeNotifier(
- !unreadCount() && !isForum());
- Thread::setUnreadMarkFlag(unread);
- }
- void History::setFakeUnreadWhileOpened(bool enabled) {
- if (fakeUnreadWhileOpened() == enabled) {
- return;
- } else if (enabled) {
- if (!inChatList()) {
- return;
- }
- const auto state = chatListBadgesState();
- if (!state.unread && !state.mention) {
- return;
- }
- }
- if (enabled) {
- _flags |= Flag::FakeUnreadWhileOpened;
- } else {
- _flags &= ~Flag::FakeUnreadWhileOpened;
- }
- owner().chatsFilters().refreshHistory(this);
- }
- [[nodiscard]] bool History::fakeUnreadWhileOpened() const {
- return (_flags & Flag::FakeUnreadWhileOpened);
- }
- void History::setMuted(bool muted) {
- if (this->muted() == muted) {
- return;
- } else {
- const auto state = isForum()
- ? Dialogs::BadgesState()
- : computeBadgesState();
- const auto notify = (state.unread || state.reaction);
- const auto notifier = unreadStateChangeNotifier(notify);
- Thread::setMuted(muted);
- }
- session().changes().peerUpdated(
- peer,
- Data::PeerUpdate::Flag::Notifications);
- owner().chatsFilters().refreshHistory(this);
- if (const auto forum = peer->forum()) {
- owner().notifySettings().forumParentMuteUpdated(forum);
- }
- }
- void History::getNextFirstUnreadMessage() {
- Expects(_firstUnreadView != nullptr);
- const auto block = _firstUnreadView->block();
- const auto index = _firstUnreadView->indexInBlock();
- const auto setFromMessage = [&](const auto &view) {
- if (view->data()->isRegular()) {
- _firstUnreadView = view.get();
- return true;
- }
- return false;
- };
- if (index >= 0) {
- const auto count = int(block->messages.size());
- for (auto i = index + 1; i != count; ++i) {
- const auto &message = block->messages[i];
- if (setFromMessage(message)) {
- return;
- }
- }
- }
- const auto count = int(blocks.size());
- for (auto j = block->indexInHistory() + 1; j != count; ++j) {
- for (const auto &message : blocks[j]->messages) {
- if (setFromMessage(message)) {
- return;
- }
- }
- }
- _firstUnreadView = nullptr;
- }
- MsgId History::nextNonHistoryEntryId() {
- return owner().nextNonHistoryEntryId();
- }
- bool History::folderKnown() const {
- return _folder.has_value();
- }
- Data::Folder *History::folder() const {
- return _folder.value_or(nullptr);
- }
- void History::setFolder(
- not_null<Data::Folder*> folder,
- HistoryItem *folderDialogItem) {
- setFolderPointer(folder);
- if (folderDialogItem) {
- setLastServerMessage(folderDialogItem);
- }
- }
- void History::clearFolder() {
- setFolderPointer(nullptr);
- }
- void History::setFolderPointer(Data::Folder *folder) {
- if (_folder == folder) {
- return;
- }
- if (isPinnedDialog(FilterId())) {
- owner().setChatPinned(this, FilterId(), false);
- }
- const auto wasKnown = folderKnown();
- const auto wasInList = inChatList();
- if (wasInList) {
- removeFromChatList(0, owner().chatsList(this->folder()));
- }
- const auto was = _folder.value_or(nullptr);
- _folder = folder;
- if (was) {
- was->unregisterOne(this);
- }
- if (wasInList) {
- addToChatList(0, owner().chatsList(folder));
- owner().chatsFilters().refreshHistory(this);
- updateChatListEntry();
- owner().chatsListChanged(was);
- owner().chatsListChanged(folder);
- } else if (!wasKnown) {
- updateChatListSortPosition();
- }
- if (folder) {
- folder->registerOne(this);
- }
- session().changes().historyUpdated(this, UpdateFlag::Folder);
- }
- int History::chatListNameVersion() const {
- return peer->nameVersion();
- }
- void History::hasUnreadMentionChanged(bool has) {
- if (isForum()) {
- return;
- }
- auto was = chatListUnreadState();
- if (has) {
- was.mentions = 0;
- } else {
- was.mentions = 1;
- }
- notifyUnreadStateChange(was);
- }
- void History::hasUnreadReactionChanged(bool has) {
- if (isForum()) {
- return;
- }
- auto was = chatListUnreadState();
- if (has) {
- was.reactions = was.reactionsMuted = 0;
- } else {
- was.reactions = 1;
- was.reactionsMuted = muted() ? was.reactions : 0;
- }
- notifyUnreadStateChange(was);
- }
- void History::applyPinnedUpdate(const MTPDupdateDialogPinned &data) {
- const auto folderId = data.vfolder_id().value_or_empty();
- if (!folderKnown()) {
- if (folderId) {
- setFolder(owner().folder(folderId));
- } else {
- clearFolder();
- }
- }
- owner().setChatPinned(this, FilterId(), data.is_pinned());
- }
- TimeId History::adjustedChatListTimeId() const {
- const auto result = chatListTimeId();
- if (const auto draft = cloudDraft(MsgId(0))) {
- if (!peer->forum()
- && !Data::DraftIsNull(draft)
- && !session().supportMode()) {
- return std::max(result, draft->date);
- }
- }
- return result;
- }
- void History::countScrollState(int top) {
- std::tie(scrollTopItem, scrollTopOffset) = findItemAndOffset(top);
- }
- auto History::findItemAndOffset(int top) const -> std::pair<Element*, int> {
- if (const auto element = findScrollTopItem(top)) {
- return { element, (top - element->block()->y() - element->y()) };
- }
- return {};
- }
- auto History::findScrollTopItem(int top) const -> Element* {
- if (isEmpty()) {
- return nullptr;
- }
- auto itemIndex = 0;
- auto blockIndex = 0;
- auto itemTop = 0;
- if (scrollTopItem) {
- itemIndex = scrollTopItem->indexInBlock();
- blockIndex = scrollTopItem->block()->indexInHistory();
- itemTop = blocks[blockIndex]->y() + scrollTopItem->y();
- }
- if (itemTop > top) {
- // go backward through history while we don't find an item that starts above
- do {
- const auto &block = blocks[blockIndex];
- for (--itemIndex; itemIndex >= 0; --itemIndex) {
- const auto view = block->messages[itemIndex].get();
- itemTop = block->y() + view->y();
- if (itemTop <= top) {
- return view;
- }
- }
- if (--blockIndex >= 0) {
- itemIndex = blocks[blockIndex]->messages.size();
- } else {
- break;
- }
- } while (true);
- return blocks.front()->messages.front().get();
- }
- // go forward through history while we don't find the last item that starts above
- for (auto blocksCount = int(blocks.size()); blockIndex < blocksCount; ++blockIndex) {
- const auto &block = blocks[blockIndex];
- for (auto itemsCount = int(block->messages.size()); itemIndex < itemsCount; ++itemIndex) {
- itemTop = block->y() + block->messages[itemIndex]->y();
- if (itemTop > top) {
- Assert(itemIndex > 0 || blockIndex > 0);
- if (itemIndex > 0) {
- return block->messages[itemIndex - 1].get();
- }
- return blocks[blockIndex - 1]->messages.back().get();
- }
- }
- itemIndex = 0;
- }
- return blocks.back()->messages.back().get();
- }
- void History::getNextScrollTopItem(HistoryBlock *block, int32 i) {
- ++i;
- if (i > 0 && i < block->messages.size()) {
- scrollTopItem = block->messages[i].get();
- return;
- }
- int j = block->indexInHistory() + 1;
- if (j > 0 && j < blocks.size()) {
- scrollTopItem = blocks[j]->messages.front().get();
- return;
- }
- scrollTopItem = nullptr;
- }
- void History::addUnreadBar() {
- if (!_unreadBarView && _firstUnreadView && unreadCount()) {
- _unreadBarView = _firstUnreadView;
- _unreadBarView->createUnreadBar(tr::lng_unread_bar_some());
- }
- }
- void History::destroyUnreadBar() {
- if (const auto view = base::take(_unreadBarView)) {
- view->destroyUnreadBar();
- }
- }
- void History::unsetFirstUnreadMessage() {
- _firstUnreadView = nullptr;
- }
- HistoryView::Element *History::unreadBar() const {
- return _unreadBarView;
- }
- HistoryView::Element *History::firstUnreadMessage() const {
- return _firstUnreadView;
- }
- not_null<HistoryItem*> History::addNewInTheMiddle(
- not_null<HistoryItem*> item,
- int blockIndex,
- int itemIndex) {
- Expects(blockIndex >= 0);
- Expects(blockIndex < blocks.size());
- Expects(itemIndex >= 0);
- Expects(itemIndex <= blocks[blockIndex]->messages.size());
- const auto &block = blocks[blockIndex];
- const auto it = block->messages.insert(
- block->messages.begin() + itemIndex,
- item->createView(_delegateMixin->delegate()));
- (*it)->attachToBlock(block.get(), itemIndex);
- if (itemIndex + 1 < block->messages.size()) {
- for (auto i = itemIndex + 1, l = int(block->messages.size()); i != l; ++i) {
- block->messages[i]->setIndexInBlock(i);
- }
- block->messages[itemIndex + 1]->previousInBlocksChanged();
- } else if (blockIndex + 1 < blocks.size() && !blocks[blockIndex + 1]->messages.empty()) {
- blocks[blockIndex + 1]->messages.front()->previousInBlocksChanged();
- } else {
- (*it)->nextInBlocksRemoved();
- }
- return item;
- }
- History *History::migrateSibling() const {
- const auto addFromId = [&] {
- if (const auto from = peer->migrateFrom()) {
- return from->id;
- } else if (const auto to = peer->migrateTo()) {
- return to->id;
- }
- return PeerId(0);
- }();
- return owner().historyLoaded(addFromId);
- }
- Dialogs::UnreadState History::chatListUnreadState() const {
- if (const auto forum = peer->forum()) {
- return AdjustedForumUnreadState(forum->topicsList()->unreadState());
- }
- return computeUnreadState();
- }
- Dialogs::BadgesState History::chatListBadgesState() const {
- if (const auto forum = peer->forum()) {
- return adjustBadgesStateByFolder(
- Dialogs::BadgesForUnread(
- forum->topicsList()->unreadState(),
- Dialogs::CountInBadge::Chats,
- Dialogs::IncludeInBadge::UnmutedOrAll));
- }
- return computeBadgesState();
- }
- Dialogs::BadgesState History::computeBadgesState() const {
- return adjustBadgesStateByFolder(
- Dialogs::BadgesForUnread(
- computeUnreadState(),
- Dialogs::CountInBadge::Messages,
- Dialogs::IncludeInBadge::All));
- }
- Dialogs::BadgesState History::adjustBadgesStateByFolder(
- Dialogs::BadgesState state) const {
- if (folder()) {
- state.mentionMuted = state.reactionMuted = state.unreadMuted = true;
- }
- return state;
- }
- Dialogs::UnreadState History::computeUnreadState() const {
- auto result = Dialogs::UnreadState();
- const auto count = _unreadCount.value_or(0);
- const auto mark = !count && unreadMark();
- const auto muted = this->muted();
- result.messages = count;
- result.chats = count ? 1 : 0;
- result.marks = mark ? 1 : 0;
- result.mentions = unreadMentions().has() ? 1 : 0;
- result.reactions = unreadReactions().has() ? 1 : 0;
- result.messagesMuted = muted ? result.messages : 0;
- result.chatsMuted = muted ? result.chats : 0;
- result.marksMuted = muted ? result.marks : 0;
- result.reactionsMuted = muted ? result.reactions : 0;
- result.known = _unreadCount.has_value();
- return result;
- }
- void History::allowChatListMessageResolve() {
- if (_flags & Flag::ResolveChatListMessage) {
- return;
- }
- _flags |= Flag::ResolveChatListMessage;
- if (!chatListMessageKnown()) {
- requestChatListMessage();
- } else {
- resolveChatListMessageGroup();
- }
- }
- void History::resolveChatListMessageGroup() {
- const auto item = _chatListMessage.value_or(nullptr);
- if (!(_flags & Flag::ResolveChatListMessage)
- || !item
- || !hasOrphanMediaGroupPart()) {
- return;
- }
- // If we set a single album part, request the full album.
- const auto withImages = !item->toPreview({
- .hideSender = true,
- .hideCaption = true }).images.empty();
- if (withImages) {
- owner().histories().requestGroupAround(item);
- }
- if (unreadCountKnown() && !unreadCount()) {
- // When we add just one last item, like we do while loading dialogs,
- // we want to remove a single added grouped media, otherwise it will
- // jump once we open the message history (first we show only that
- // media, then we load the rest of the group and show the group).
- //
- // That way when we open the message history we show nothing until a
- // whole history part is loaded, it certainly will contain the group.
- clear(ClearType::Unload);
- }
- }
- HistoryItem *History::chatListMessage() const {
- return _chatListMessage.value_or(nullptr);
- }
- bool History::chatListMessageKnown() const {
- return _chatListMessage.has_value();
- }
- const QString &History::chatListName() const {
- return peer->name();
- }
- const QString &History::chatListNameSortKey() const {
- return _chatListNameSortKey;
- }
- void History::refreshChatListNameSortKey() {
- _chatListNameSortKey = TextUtilities::NameSortKey(peer->name());
- }
- const base::flat_set<QString> &History::chatListNameWords() const {
- return peer->nameWords();
- }
- const base::flat_set<QChar> &History::chatListFirstLetters() const {
- return peer->nameFirstLetters();
- }
- void History::chatListPreloadData() {
- peer->loadUserpic();
- allowChatListMessageResolve();
- }
- void History::paintUserpic(
- Painter &p,
- Ui::PeerUserpicView &view,
- const Dialogs::Ui::PaintContext &context) const {
- peer->paintUserpic(
- p,
- view,
- context.st->padding.left(),
- context.st->padding.top(),
- context.st->photoSize);
- }
- void History::startBuildingFrontBlock(int expectedItemsCount) {
- Assert(!isBuildingFrontBlock());
- Assert(expectedItemsCount > 0);
- _buildingFrontBlock = std::make_unique<BuildingBlock>();
- _buildingFrontBlock->expectedItemsCount = expectedItemsCount;
- }
- void History::finishBuildingFrontBlock() {
- Expects(isBuildingFrontBlock());
- // Some checks if there was some message history already
- if (const auto block = base::take(_buildingFrontBlock)->block) {
- if (blocks.size() > 1) {
- // ... item, item, item, last ], [ first, item, item ...
- const auto first = blocks[1]->messages.front().get();
- // we've added a new front block, so previous item for
- // the old first item of a first block was changed
- first->previousInBlocksChanged();
- } else {
- block->messages.back()->nextInBlocksRemoved();
- }
- }
- }
- bool History::loadedAtBottom() const {
- return _loadedAtBottom;
- }
- bool History::loadedAtTop() const {
- return _loadedAtTop;
- }
- bool History::isReadyFor(MsgId msgId) {
- if (msgId < 0 && -msgId < ServerMaxMsgId && peer->migrateFrom()) {
- // Old group history.
- return owner().history(peer->migrateFrom()->id)->isReadyFor(-msgId);
- }
- if (msgId == ShowAtTheEndMsgId) {
- return loadedAtBottom();
- }
- if (msgId == ShowAtUnreadMsgId) {
- if (const auto migratePeer = peer->migrateFrom()) {
- if (const auto migrated = owner().historyLoaded(migratePeer)) {
- if (migrated->unreadCount()) {
- return migrated->isReadyFor(msgId);
- }
- }
- }
- if (unreadCount() && _inboxReadBefore) {
- if (!isEmpty()) {
- return (loadedAtTop() || minMsgId() <= *_inboxReadBefore)
- && (loadedAtBottom() || maxMsgId() >= *_inboxReadBefore);
- }
- return false;
- }
- return loadedAtBottom();
- }
- const auto item = owner().message(peer, msgId);
- return item && (item->history() == this) && item->mainView();
- }
- void History::getReadyFor(MsgId msgId) {
- if (msgId < 0 && -msgId < ServerMaxMsgId && peer->migrateFrom()) {
- const auto migrated = owner().history(peer->migrateFrom()->id);
- migrated->getReadyFor(-msgId);
- if (migrated->isEmpty()) {
- clear(ClearType::Unload);
- }
- return;
- }
- if (msgId == ShowAtUnreadMsgId) {
- if (const auto migratePeer = peer->migrateFrom()) {
- if (const auto migrated = owner().historyLoaded(migratePeer)) {
- if (migrated->unreadCount()) {
- clear(ClearType::Unload);
- migrated->getReadyFor(msgId);
- return;
- }
- }
- }
- }
- if (!isReadyFor(msgId)) {
- clear(ClearType::Unload);
- if (const auto migratePeer = peer->migrateFrom()) {
- if (const auto migrated = owner().historyLoaded(migratePeer)) {
- migrated->clear(ClearType::Unload);
- }
- }
- if ((msgId == ShowAtTheEndMsgId)
- || (msgId == ShowAtUnreadMsgId && !unreadCount())) {
- _loadedAtBottom = true;
- }
- }
- }
- void History::setNotLoadedAtBottom() {
- _loadedAtBottom = false;
- session().storage().invalidate(
- Storage::SharedMediaInvalidateBottom(peer->id));
- if (const auto messages = _messages.get()) {
- messages->invalidateBottom();
- }
- }
- void History::clearSharedMedia() {
- session().storage().remove(
- Storage::SharedMediaRemoveAll(peer->id));
- }
- void History::setLastServerMessage(HistoryItem *item) {
- _lastServerMessage = item;
- if (_lastMessage
- && *_lastMessage
- && !(*_lastMessage)->isRegular()
- && (!item || (*_lastMessage)->date() > item->date())) {
- return;
- }
- setLastMessage(item);
- }
- void History::setLastMessage(HistoryItem *item) {
- if (_lastMessage && *_lastMessage == item) {
- return;
- }
- _lastMessage = item;
- if (!item || item->isRegular()) {
- _lastServerMessage = item;
- }
- if (peer->migrateTo()) {
- // We don't want to request last message for all deactivated chats.
- // This is a heavy request for them, because we need to get last
- // two items by messages.getHistory to skip the migration message.
- setChatListMessageUnknown();
- } else {
- setChatListMessageFromLast();
- if (!chatListMessageKnown()) {
- setFakeChatListMessage();
- }
- }
- }
- void History::refreshChatListMessage() {
- const auto known = chatListMessageKnown();
- setChatListMessageFromLast();
- if (known && !_chatListMessage) {
- requestChatListMessage();
- }
- }
- void History::setChatListMessage(HistoryItem *item) {
- if (_chatListMessage && *_chatListMessage == item) {
- return;
- }
- const auto was = _chatListMessage.value_or(nullptr);
- if (item) {
- if (item->isSponsored()) {
- return;
- }
- if (_chatListMessage
- && *_chatListMessage
- && !(*_chatListMessage)->isRegular()
- && (*_chatListMessage)->date() > item->date()) {
- return;
- }
- _chatListMessage = item;
- setChatListTimeId(item->date());
- resolveChatListMessageGroup();
- } else if (!_chatListMessage || *_chatListMessage) {
- _chatListMessage = nullptr;
- updateChatListEntry();
- }
- if (const auto folder = this->folder()) {
- folder->oneListMessageChanged(was, item);
- }
- if (const auto to = peer->migrateTo()) {
- if (const auto history = owner().historyLoaded(to)) {
- if (!history->chatListMessageKnown()) {
- history->requestChatListMessage();
- }
- }
- }
- }
- auto History::computeChatListMessageFromLast() const
- -> std::optional<HistoryItem*> {
- if (!_lastMessage) {
- return _lastMessage;
- }
- // In migrated groups we want to skip essential message
- // about migration in the chats list and display the last
- // non-migration message from the original legacy group.
- const auto last = lastMessage();
- if (!last || !last->isGroupMigrate()) {
- return _lastMessage;
- }
- if (const auto chat = peer->asChat()) {
- // In chats we try to take the item before the 'last', which
- // is the empty-displayed migration message.
- if (!loadedAtBottom()) {
- // We don't know the tail of the history.
- return std::nullopt;
- }
- const auto before = [&]() -> HistoryItem* {
- for (const auto &block : ranges::views::reverse(blocks)) {
- const auto &messages = block->messages;
- for (const auto &item : ranges::views::reverse(messages)) {
- if (item->data() != last) {
- return item->data();
- }
- }
- }
- return nullptr;
- }();
- if (before) {
- // We found a message that is not the migration one.
- return before;
- } else if (loadedAtTop()) {
- // No other messages in this history.
- return _lastMessage;
- }
- return std::nullopt;
- } else if (const auto from = migrateFrom()) {
- // In megagroups we just try to use
- // the message from the original group.
- return from->chatListMessageKnown()
- ? std::make_optional(from->chatListMessage())
- : std::nullopt;
- }
- return _lastMessage;
- }
- void History::setChatListMessageFromLast() {
- if (const auto good = computeChatListMessageFromLast()) {
- setChatListMessage(*good);
- } else {
- setChatListMessageUnknown();
- }
- }
- void History::setChatListMessageUnknown() {
- if (!_chatListMessage.has_value()) {
- return;
- }
- const auto was = *_chatListMessage;
- _chatListMessage = std::nullopt;
- if (const auto folder = this->folder()) {
- folder->oneListMessageChanged(was, nullptr);
- }
- }
- void History::requestChatListMessage() {
- if (!lastMessageKnown()) {
- owner().histories().requestDialogEntry(this, [=] {
- requestChatListMessage();
- });
- return;
- } else if (chatListMessageKnown()) {
- return;
- }
- setChatListMessageFromLast();
- if (!chatListMessageKnown()) {
- setFakeChatListMessage();
- }
- }
- void History::setFakeChatListMessage() {
- if (!(_flags & Flag::ResolveChatListMessage)) {
- if (!chatListTimeId()) {
- if (const auto last = lastMessage()) {
- setChatListTimeId(last->date());
- }
- }
- return;
- } else if (const auto chat = peer->asChat()) {
- // In chats we try to take the item before the 'last', which
- // is the empty-displayed migration message.
- owner().histories().requestFakeChatListMessage(this);
- } else if (const auto from = migrateFrom()) {
- // In megagroups we just try to use
- // the message from the original group.
- from->allowChatListMessageResolve();
- from->requestChatListMessage();
- }
- }
- void History::setFakeChatListMessageFrom(const MTPmessages_Messages &data) {
- if (!lastMessageKnown()) {
- requestChatListMessage();
- return;
- }
- const auto finalize = gsl::finally([&] {
- // Make sure that we have chatListMessage when we get out of here.
- if (!chatListMessageKnown()) {
- setChatListMessage(lastMessage());
- }
- });
- const auto last = lastMessage();
- if (!last || !last->isGroupMigrate()) {
- // Last message is good enough.
- return;
- }
- const auto other = data.match([&](
- const MTPDmessages_messagesNotModified &) {
- return static_cast<const MTPMessage*>(nullptr);
- }, [&](const auto &data) {
- for (const auto &message : data.vmessages().v) {
- const auto id = message.match([](const auto &data) {
- return data.vid().v;
- });
- if (id != last->id) {
- return &message;
- }
- }
- return static_cast<const MTPMessage*>(nullptr);
- });
- if (!other) {
- // Other (non equal to the last one) message not found.
- return;
- }
- const auto item = owner().addNewMessage(
- *other,
- MessageFlags(),
- NewMessageType::Existing);
- if (!item || item->isGroupMigrate()) {
- // Not better than the last one.
- return;
- }
- setChatListMessage(item);
- }
- void History::applyChatListGroup(
- PeerId dataPeerId,
- const MTPmessages_Messages &data) {
- if (!isEmpty()
- || !_chatListMessage
- || !*_chatListMessage
- || (*_chatListMessage)->history() != this
- || !_lastMessage
- || !*_lastMessage
- || dataPeerId != peer->id) {
- return;
- }
- // Apply loaded album as a last slice.
- const auto processMessages = [&](const MTPVector<MTPMessage> &messages) {
- auto items = std::vector<not_null<HistoryItem*>>();
- items.reserve(messages.v.size());
- for (const auto &message : messages.v) {
- const auto id = IdFromMessage(message);
- if (const auto message = owner().message(dataPeerId, id)) {
- items.push_back(message);
- }
- }
- if (!ranges::contains(items, not_null(*_lastMessage))
- || !ranges::contains(items, not_null(*_chatListMessage))) {
- return;
- }
- _loadedAtBottom = true;
- ranges::sort(items, ranges::less{}, &HistoryItem::id);
- addCreatedOlderSlice(items);
- checkLocalMessages();
- checkLastMessage();
- };
- data.match([&](const MTPDmessages_messagesNotModified &) {
- }, [&](const auto &data) {
- processMessages(data.vmessages());
- });
- }
- HistoryItem *History::lastMessage() const {
- return _lastMessage.value_or(nullptr);
- }
- bool History::lastMessageKnown() const {
- return _lastMessage.has_value();
- }
- HistoryItem *History::lastServerMessage() const {
- return _lastServerMessage.value_or(nullptr);
- }
- bool History::lastServerMessageKnown() const {
- return _lastServerMessage.has_value();
- }
- void History::updateChatListExistence() {
- Entry::updateChatListExistence();
- }
- bool History::useTopPromotion() const {
- if (!isTopPromoted()) {
- return false;
- } else if (const auto channel = peer->asChannel()) {
- return !isPinnedDialog(FilterId()) && !channel->amIn();
- } else if (const auto user = peer->asUser()) {
- return !isPinnedDialog(FilterId()) && user->isBot() && isEmpty();
- }
- return false;
- }
- int History::fixedOnTopIndex() const {
- return useTopPromotion() ? kTopPromotionFixOnTopIndex : 0;
- }
- bool History::trackUnreadMessages() const {
- if (const auto channel = peer->asChannel()) {
- return channel->amIn();
- }
- return true;
- }
- bool History::shouldBeInChatList() const {
- if (peer->migrateTo() || !folderKnown()) {
- return false;
- } else if (isPinnedDialog(FilterId())) {
- return true;
- } else if (const auto channel = peer->asChannel()) {
- if (!channel->amIn()) {
- return isTopPromoted();
- }
- } else if (const auto chat = peer->asChat()) {
- return chat->amIn()
- || !lastMessageKnown()
- || (lastMessage() != nullptr);
- } else if (const auto user = peer->asUser()) {
- if (user->isBot() && isTopPromoted()) {
- return true;
- }
- }
- return !lastMessageKnown()
- || (lastMessage() != nullptr);
- }
- void History::unknownMessageDeleted(MsgId messageId) {
- if (_inboxReadBefore && messageId >= *_inboxReadBefore) {
- owner().histories().requestDialogEntry(this);
- }
- }
- bool History::isServerSideUnread(not_null<const HistoryItem*> item) const {
- Expects(item->isRegular());
- return item->out()
- ? (!_outboxReadBefore || (item->id >= *_outboxReadBefore))
- : (!_inboxReadBefore || (item->id >= *_inboxReadBefore));
- }
- void History::applyDialog(
- Data::Folder *requestFolder,
- const MTPDdialog &data) {
- const auto folderId = data.vfolder_id();
- const auto folder = !folderId
- ? requestFolder
- : folderId->v
- ? owner().folder(folderId->v).get()
- : nullptr;
- applyDialogFields(
- folder,
- data.vunread_count().v,
- data.vread_inbox_max_id().v,
- data.vread_outbox_max_id().v);
- applyDialogTopMessage(data.vtop_message().v);
- setUnreadMark(data.is_unread_mark());
- unreadMentions().setCount(data.vunread_mentions_count().v);
- unreadReactions().setCount(data.vunread_reactions_count().v);
- if (const auto channel = peer->asChannel()) {
- if (const auto pts = data.vpts()) {
- channel->ptsReceived(pts->v);
- }
- if (!channel->amCreator()) {
- const auto topMessageId = FullMsgId(
- channel->id,
- data.vtop_message().v);
- if (const auto item = owner().message(topMessageId)) {
- if (item->date() <= channel->date) {
- session().api().chatParticipants().requestSelf(channel);
- }
- }
- }
- channel->setViewAsMessagesFlag(data.is_view_forum_as_messages());
- }
- owner().notifySettings().apply(
- MTP_notifyPeer(data.vpeer()),
- data.vnotify_settings());
- const auto draft = data.vdraft();
- if (draft && draft->type() == mtpc_draftMessage) {
- Data::ApplyPeerCloudDraft(
- &session(),
- peer->id,
- MsgId(0), // topicRootId
- draft->c_draftMessage());
- }
- if (const auto ttl = data.vttl_period()) {
- peer->setMessagesTTL(ttl->v);
- }
- owner().histories().dialogEntryApplied(this);
- }
- void History::dialogEntryApplied() {
- if (!lastServerMessageKnown()) {
- setLastServerMessage(nullptr);
- } else if (!lastMessageKnown()) {
- setLastMessage(nullptr);
- }
- if (peer->migrateTo()) {
- return;
- } else if (!chatListMessageKnown()) {
- requestChatListMessage();
- return;
- }
- if (!chatListMessage()) {
- clear(ClearType::Unload);
- addNewerSlice(QVector<MTPMessage>());
- addOlderSlice(QVector<MTPMessage>());
- if (const auto channel = peer->asChannel()) {
- const auto inviter = channel->inviter;
- if (inviter && channel->amIn()) {
- if (const auto from = owner().userLoaded(inviter)) {
- insertJoinedMessage();
- }
- }
- }
- return;
- }
- if (chatListTimeId() != 0 && loadedAtBottom()) {
- if (const auto channel = peer->asChannel()) {
- const auto inviter = channel->inviter;
- if (inviter
- && chatListTimeId() <= channel->inviteDate
- && channel->amIn()) {
- if (const auto from = owner().userLoaded(inviter)) {
- insertJoinedMessage();
- }
- }
- }
- }
- }
- void History::cacheTopPromotion(
- bool promoted,
- const QString &type,
- const QString &message) {
- const auto changed = (isTopPromoted() != promoted);
- cacheTopPromoted(promoted);
- if (topPromotionType() != type || _topPromotedMessage != message) {
- _topPromotedType = type;
- _topPromotedMessage = message;
- cloudDraftTextCache().clear();
- } else if (changed) {
- cloudDraftTextCache().clear();
- }
- }
- QStringView History::topPromotionType() const {
- return topPromotionAboutShown()
- ? base::StringViewMid(_topPromotedType, 5)
- : QStringView(_topPromotedType);
- }
- bool History::topPromotionAboutShown() const {
- return _topPromotedType.startsWith("seen^");
- }
- void History::markTopPromotionAboutShown() {
- if (!topPromotionAboutShown()) {
- _topPromotedType = "seen^" + _topPromotedType;
- }
- }
- QString History::topPromotionMessage() const {
- return _topPromotedMessage;
- }
- bool History::clearUnreadOnClientSide() const {
- if (!session().supportMode()) {
- return false;
- }
- if (const auto user = peer->asUser()) {
- if (user->isInaccessible()) {
- return true;
- }
- }
- return false;
- }
- bool History::skipUnreadUpdate() const {
- return clearUnreadOnClientSide();
- }
- void History::applyDialogFields(
- Data::Folder *folder,
- int unreadCount,
- MsgId maxInboxRead,
- MsgId maxOutboxRead) {
- if (folder) {
- setFolder(folder);
- } else {
- clearFolder();
- }
- if (!skipUnreadUpdate()
- && maxInboxRead + 1 >= _inboxReadBefore.value_or(1)) {
- setUnreadCount(unreadCount);
- setInboxReadTill(maxInboxRead);
- }
- setOutboxReadTill(maxOutboxRead);
- }
- void History::applyDialogTopMessage(MsgId topMessageId) {
- if (topMessageId) {
- const auto itemId = FullMsgId(peer->id, topMessageId);
- if (const auto item = owner().message(itemId)) {
- setLastServerMessage(item);
- } else {
- setLastServerMessage(nullptr);
- }
- } else {
- setLastServerMessage(nullptr);
- }
- if (clearUnreadOnClientSide()) {
- setUnreadCount(0);
- if (const auto last = lastMessage()) {
- setInboxReadTill(last->id);
- }
- }
- }
- void History::setInboxReadTill(MsgId upTo) {
- if (_inboxReadBefore) {
- accumulate_max(*_inboxReadBefore, upTo + 1);
- } else {
- _inboxReadBefore = upTo + 1;
- for (const auto &item : _items) {
- item->applyEffectWatchedOnUnreadKnown();
- }
- }
- }
- void History::setOutboxReadTill(MsgId upTo) {
- if (_outboxReadBefore) {
- accumulate_max(*_outboxReadBefore, upTo + 1);
- } else {
- _outboxReadBefore = upTo + 1;
- }
- }
- MsgId History::minMsgId() const {
- for (const auto &block : blocks) {
- for (const auto &message : block->messages) {
- const auto item = message->data();
- if (item->isRegular()) {
- return item->id;
- }
- }
- }
- return 0;
- }
- MsgId History::maxMsgId() const {
- for (const auto &block : ranges::views::reverse(blocks)) {
- for (const auto &message : ranges::views::reverse(block->messages)) {
- const auto item = message->data();
- if (item->isRegular()) {
- return item->id;
- }
- }
- }
- return 0;
- }
- MsgId History::msgIdForRead() const {
- const auto last = lastMessage();
- const auto result = (last && last->isRegular())
- ? last->id
- : MsgId(0);
- return loadedAtBottom()
- ? std::max(result, maxMsgId())
- : result;
- }
- HistoryItem *History::lastEditableMessage() const {
- if (!loadedAtBottom()) {
- return nullptr;
- }
- const auto now = base::unixtime::now();
- for (const auto &block : ranges::views::reverse(blocks)) {
- for (const auto &message : ranges::views::reverse(block->messages)) {
- const auto item = message->data();
- if (item->allowsEdit(now)) {
- return owner().groups().findItemToEdit(item);
- }
- }
- }
- return nullptr;
- }
- void History::resizeToWidth(int newWidth) {
- using Request = HistoryBlock::ResizeRequest;
- const auto request = (_flags & Flag::PendingAllItemsResize)
- ? Request::ReinitAll
- : (_width != newWidth)
- ? Request::ResizeAll
- : Request::ResizePending;
- if (request == Request::ResizePending && !hasPendingResizedItems()) {
- return;
- }
- _flags &= ~(Flag::HasPendingResizedItems | Flag::PendingAllItemsResize);
- _width = newWidth;
- int y = 0;
- for (const auto &block : blocks) {
- block->setY(y);
- y += block->resizeGetHeight(newWidth, request);
- }
- _height = y;
- }
- void History::forceFullResize() {
- _width = 0;
- _flags |= Flag::HasPendingResizedItems;
- }
- Data::Thread *History::threadFor(MsgId topicRootId) {
- return topicRootId
- ? peer->forumTopicFor(topicRootId)
- : static_cast<Data::Thread*>(this);
- }
- const Data::Thread *History::threadFor(MsgId topicRootId) const {
- return const_cast<History*>(this)->threadFor(topicRootId);
- }
- void History::forumChanged(Data::Forum *old) {
- if (inChatList()) {
- notifyUnreadStateChange(old
- ? AdjustedForumUnreadState(old->topicsList()->unreadState())
- : computeUnreadState());
- }
- if (const auto forum = peer->forum()) {
- _flags |= Flag::IsForum;
- forum->topicsList()->unreadStateChanges(
- ) | rpl::filter([=] {
- return (_flags & Flag::IsForum) && inChatList();
- }) | rpl::map(
- AdjustedForumUnreadState
- ) | rpl::start_with_next([=](const Dialogs::UnreadState &old) {
- notifyUnreadStateChange(old);
- }, forum->lifetime());
- forum->chatsListChanges(
- ) | rpl::start_with_next([=] {
- updateChatListEntry();
- }, forum->lifetime());
- } else {
- _flags &= ~Flag::IsForum;
- }
- if (cloudDraft(MsgId(0))) {
- updateChatListSortPosition();
- }
- _flags |= Flag::PendingAllItemsResize;
- }
- bool History::isForum() const {
- return (_flags & Flag::IsForum);
- }
- not_null<History*> History::migrateToOrMe() const {
- if (const auto to = peer->migrateTo()) {
- return owner().history(to);
- }
- // We could get it by owner().history(peer), but we optimize.
- return const_cast<History*>(this);
- }
- History *History::migrateFrom() const {
- if (const auto from = peer->migrateFrom()) {
- return owner().history(from);
- }
- return nullptr;
- }
- MsgRange History::rangeForDifferenceRequest() const {
- auto fromId = MsgId(0);
- auto toId = MsgId(0);
- for (const auto &block : blocks) {
- for (const auto &item : block->messages) {
- const auto id = item->data()->id;
- if (id > 0) {
- fromId = id;
- break;
- }
- }
- if (fromId) break;
- }
- if (fromId) {
- for (auto blockIndex = blocks.size(); blockIndex > 0;) {
- const auto &block = blocks[--blockIndex];
- for (auto itemIndex = block->messages.size(); itemIndex > 0;) {
- const auto id = block->messages[--itemIndex]->data()->id;
- if (id > 0) {
- toId = id;
- break;
- }
- }
- if (toId) break;
- }
- return { fromId, toId + 1 };
- }
- return MsgRange();
- }
- Data::HistoryMessages &History::messages() {
- if (!_messages) {
- _messages = std::make_unique<Data::HistoryMessages>();
- const auto max = maxMsgId();
- const auto from = loadedAtTop() ? 0 : minMsgId();
- const auto till = loadedAtBottom() ? ServerMaxMsgId : max;
- auto list = std::vector<MsgId>();
- list.reserve(std::min(
- int(_items.size()),
- int(blocks.size()) * kNewBlockEachMessage));
- auto sort = false;
- for (const auto &block : blocks) {
- for (const auto &view : block->messages) {
- const auto item = view->data();
- if (item->isRegular()) {
- const auto id = item->id;
- if (!list.empty() && list.back() >= id) {
- sort = true;
- }
- list.push_back(id);
- }
- }
- }
- if (sort) {
- ranges::sort(list);
- }
- if (max || (loadedAtTop() && loadedAtBottom())) {
- _messages->addSlice(std::move(list), { from, till }, {});
- }
- }
- return *_messages;
- }
- const Data::HistoryMessages &History::messages() const {
- return const_cast<History*>(this)->messages();
- }
- Data::HistoryMessages *History::maybeMessages() {
- return _messages.get();
- }
- HistoryItem *History::insertJoinedMessage() {
- const auto channel = peer->asChannel();
- if (!channel
- || _joinedMessage
- || !channel->amIn()
- || (peer->isMegagroup()
- && channel->mgInfo->joinedMessageFound)) {
- return _joinedMessage;
- }
- const auto inviter = (channel->inviter.bare > 0)
- ? owner().userLoaded(channel->inviter)
- : nullptr;
- if (!inviter) {
- return nullptr;
- }
- if (peer->isMegagroup()
- && peer->migrateFrom()
- && !blocks.empty()
- && blocks.front()->messages.front()->data()->id == 1) {
- channel->mgInfo->joinedMessageFound = true;
- return nullptr;
- }
- _joinedMessage = GenerateJoinedMessage(
- this,
- channel->inviteDate,
- inviter,
- channel->inviteViaRequest);
- insertMessageToBlocks(_joinedMessage);
- return _joinedMessage;
- }
- void History::checkNewPeerMessages() {
- if (!loadedAtTop()) {
- return;
- }
- const auto user = peer->asUser();
- if (!user) {
- return;
- }
- const auto photo = user->photoChangeDate();
- const auto name = user->nameChangeDate();
- if (!photo && _newPeerPhotoChange) {
- _newPeerPhotoChange->destroy();
- }
- if (!name && _newPeerNameChange) {
- _newPeerNameChange->destroy();
- }
- if ((!photo || _newPeerPhotoChange) && (!name || _newPeerNameChange)) {
- return;
- }
- const auto when = [](TimeId date) {
- const auto now = base::unixtime::now();
- const auto passed = now - date;
- if (passed < 3600) {
- return tr::lng_new_contact_updated_now(tr::now);
- } else if (passed < 24 * 3600) {
- return tr::lng_new_contact_updated_hours(
- tr::now,
- lt_count,
- (passed / 3600));
- } else if (passed < 60 * 24 * 3600) {
- return tr::lng_new_contact_updated_days(
- tr::now,
- lt_count,
- (passed / (24 * 3600)));
- }
- return tr::lng_new_contact_updated_months(
- tr::now,
- lt_count,
- (passed / (30 * 24 * 3600)));
- };
- auto firstDate = TimeId();
- for (const auto &block : blocks) {
- for (const auto &message : block->messages) {
- const auto item = message->data();
- if (item != _newPeerPhotoChange && item != _newPeerNameChange) {
- firstDate = item->date();
- break;
- }
- }
- if (firstDate) {
- break;
- }
- }
- if (!firstDate) {
- firstDate = base::unixtime::serialize(
- QDateTime(QDate(2013, 8, 1), QTime(0, 0)));
- }
- const auto add = [&](tr::phrase<lngtag_when> phrase, TimeId date) {
- const auto result = makeMessage({
- .id = owner().nextLocalMessageId(),
- .flags = MessageFlag::Local | MessageFlag::HideDisplayDate,
- .date = (--firstDate),
- }, PreparedServiceText{ TextWithEntities{
- phrase(tr::now, lt_when, when(date)),
- } });
- insertMessageToBlocks(result);
- return result;
- };
- if (photo && !_newPeerPhotoChange) {
- _newPeerPhotoChange = add(tr::lng_new_contact_updated_photo, photo);
- }
- if (name && !_newPeerNameChange) {
- _newPeerNameChange = add(tr::lng_new_contact_updated_name, name);
- }
- }
- void History::insertMessageToBlocks(not_null<HistoryItem*> item) {
- Expects(item->mainView() == nullptr);
- if (isEmpty()) {
- addNewToBack(item, false);
- return;
- }
- const auto itemDate = item->date();
- for (auto blockIndex = blocks.size(); blockIndex > 0;) {
- const auto &block = blocks[--blockIndex];
- for (auto itemIndex = block->messages.size(); itemIndex > 0;) {
- if (block->messages[--itemIndex]->data()->date() <= itemDate) {
- ++itemIndex;
- addNewInTheMiddle(item, blockIndex, itemIndex);
- const auto lastDate = chatListTimeId();
- if (!lastDate || itemDate >= lastDate) {
- setLastMessage(item);
- owner().notifyHistoryChangeDelayed(this);
- }
- return;
- }
- }
- }
- startBuildingFrontBlock();
- addItemToBlock(item);
- finishBuildingFrontBlock();
- }
- void History::checkLocalMessages() {
- if (isEmpty() && (!loadedAtTop() || !loadedAtBottom())) {
- return;
- }
- const auto firstDate = loadedAtTop()
- ? 0
- : blocks.front()->messages.front()->data()->date();
- const auto lastDate = loadedAtBottom()
- ? std::numeric_limits<TimeId>::max()
- : blocks.back()->messages.back()->data()->date();
- const auto goodDate = [&](TimeId date) {
- return (date >= firstDate && date < lastDate);
- };
- for (const auto &item : _clientSideMessages) {
- if (!item->mainView() && goodDate(item->date())) {
- insertMessageToBlocks(item);
- }
- }
- if (peer->isChannel()
- && !_joinedMessage
- && peer->asChannel()->inviter
- && goodDate(peer->asChannel()->inviteDate)) {
- insertJoinedMessage();
- } else {
- checkNewPeerMessages();
- }
- }
- HistoryItem *History::joinedMessageInstance() const {
- return _joinedMessage;
- }
- void History::removeJoinedMessage() {
- if (_joinedMessage) {
- _joinedMessage->destroy();
- }
- }
- void History::removeNewPeerMessages() {
- if (_newPeerNameChange) {
- _newPeerNameChange->destroy();
- }
- if (_newPeerPhotoChange) {
- _newPeerPhotoChange->destroy();
- }
- }
- void History::reactionsEnabledChanged(bool enabled) {
- if (!enabled) {
- for (const auto &item : _items) {
- item->updateReactions(nullptr);
- }
- } else {
- for (const auto &item : _items) {
- item->updateReactionsUnknown();
- }
- }
- }
- bool History::isEmpty() const {
- return blocks.empty();
- }
- bool History::isDisplayedEmpty() const {
- if (!loadedAtTop() || !loadedAtBottom()) {
- return false;
- }
- const auto first = findFirstNonEmpty();
- if (!first) {
- return true;
- }
- const auto chat = peer->asChat();
- if (!chat || !chat->amCreator()) {
- return false;
- }
- // For legacy chats we want to show the chat with only
- // messages about you creating the group and maybe about you
- // changing the group photo as an empty chat with
- // a nice information about the group features.
- if (nonEmptyCountMoreThan(2)) {
- return false;
- }
- const auto isChangePhoto = [](not_null<HistoryItem*> item) {
- if (const auto media = item->media()) {
- return (media->photo() != nullptr) && item->isService();
- }
- return false;
- };
- const auto last = findLastNonEmpty();
- if (first == last) {
- return first->data()->isGroupEssential()
- || isChangePhoto(first->data());
- }
- return first->data()->isGroupEssential() && isChangePhoto(last->data());
- }
- auto History::findFirstNonEmpty() const -> Element* {
- for (const auto &block : blocks) {
- for (const auto &element : block->messages) {
- if (!element->data()->isEmpty()) {
- return element.get();
- }
- }
- }
- return nullptr;
- }
- auto History::findFirstDisplayed() const -> Element* {
- for (const auto &block : blocks) {
- for (const auto &element : block->messages) {
- if (!element->data()->isEmpty() && !element->isHidden()) {
- return element.get();
- }
- }
- }
- return nullptr;
- }
- auto History::findLastNonEmpty() const -> Element* {
- for (const auto &block : ranges::views::reverse(blocks)) {
- for (const auto &element : ranges::views::reverse(block->messages)) {
- if (!element->data()->isEmpty()) {
- return element.get();
- }
- }
- }
- return nullptr;
- }
- auto History::findLastDisplayed() const -> Element* {
- for (const auto &block : ranges::views::reverse(blocks)) {
- for (const auto &element : ranges::views::reverse(block->messages)) {
- if (!element->data()->isEmpty() && !element->isHidden()) {
- return element.get();
- }
- }
- }
- return nullptr;
- }
- bool History::nonEmptyCountMoreThan(int count) const {
- Expects(count >= 0);
- for (const auto &block : blocks) {
- for (const auto &element : block->messages) {
- if (!element->data()->isEmpty()) {
- if (!count--) {
- return true;
- }
- }
- }
- }
- return false;
- }
- bool History::hasOrphanMediaGroupPart() const {
- if (loadedAtTop() || !loadedAtBottom()) {
- return false;
- } else if (blocks.size() != 1) {
- return false;
- } else if (blocks.front()->messages.size() != 1) {
- return false;
- }
- const auto last = blocks.front()->messages.front()->data();
- return last->groupId() != MessageGroupId();
- }
- std::vector<MsgId> History::collectMessagesFromParticipantToDelete(
- not_null<PeerData*> participant) const {
- auto result = std::vector<MsgId>();
- for (const auto &block : blocks) {
- for (const auto &message : block->messages) {
- const auto item = message->data();
- if (item->from() == participant && item->canDelete()) {
- result.push_back(item->id);
- }
- }
- }
- return result;
- }
- void History::clear(ClearType type) {
- _unreadBarView = nullptr;
- _firstUnreadView = nullptr;
- removeJoinedMessage();
- forgetScrollState();
- blocks.clear();
- owner().notifyHistoryUnloaded(this);
- lastKeyboardInited = false;
- if (type == ClearType::Unload) {
- _loadedAtTop = _loadedAtBottom = false;
- } else {
- // Leave the 'sending' messages in local messages.
- auto local = base::flat_set<not_null<HistoryItem*>>();
- for (const auto &item : _clientSideMessages) {
- if (!item->isSending()) {
- local.emplace(item);
- }
- }
- for (const auto &item : local) {
- item->destroy();
- }
- clearNotifications();
- owner().notifyHistoryCleared(this);
- if (unreadCountKnown()) {
- setUnreadCount(0);
- }
- if (type == ClearType::DeleteChat) {
- setLastServerMessage(nullptr);
- } else if (_lastMessage && *_lastMessage) {
- if ((*_lastMessage)->isRegular()) {
- (*_lastMessage)->applyEditionToHistoryCleared();
- } else {
- _lastMessage = std::nullopt;
- }
- }
- const auto tillId = (_lastMessage && *_lastMessage)
- ? (*_lastMessage)->id
- : std::numeric_limits<MsgId>::max();
- clearUpTill(tillId);
- if (blocks.empty() && _lastMessage && *_lastMessage) {
- addItemToBlock(*_lastMessage);
- }
- _loadedAtTop = _loadedAtBottom = _lastMessage.has_value();
- clearSharedMedia();
- if (const auto messages = _messages.get()) {
- messages->removeAll();
- }
- clearLastKeyboard();
- }
- if (const auto chat = peer->asChat()) {
- chat->lastAuthors.clear();
- chat->markupSenders.clear();
- } else if (const auto channel = peer->asMegagroup()) {
- channel->mgInfo->markupSenders.clear();
- }
- owner().notifyHistoryChangeDelayed(this);
- owner().sendHistoryChangeNotifications();
- }
- void History::clearUpTill(MsgId availableMinId) {
- auto remove = std::vector<not_null<HistoryItem*>>();
- remove.reserve(_items.size());
- for (const auto &item : _items) {
- const auto itemId = item->id;
- if (!item->isRegular()) {
- continue;
- } else if (itemId == availableMinId) {
- item->applyEditionToHistoryCleared();
- } else if (itemId < availableMinId) {
- remove.push_back(item.get());
- }
- }
- for (const auto item : remove) {
- item->destroy();
- }
- requestChatListMessage();
- }
- void History::applyGroupAdminChanges(const base::flat_set<UserId> &changes) {
- for (const auto &block : blocks) {
- for (const auto &message : block->messages) {
- message->applyGroupAdminChanges(changes);
- }
- }
- }
- void History::changedChatListPinHook() {
- session().changes().historyUpdated(this, UpdateFlag::IsPinned);
- }
- void History::removeBlock(not_null<HistoryBlock*> block) {
- Expects(block->messages.empty());
- if (_buildingFrontBlock && block == _buildingFrontBlock->block) {
- _buildingFrontBlock->block = nullptr;
- }
- int index = block->indexInHistory();
- blocks.erase(blocks.begin() + index);
- if (index < blocks.size()) {
- for (int i = index, l = blocks.size(); i < l; ++i) {
- blocks[i]->setIndexInHistory(i);
- }
- blocks[index]->messages.front()->previousInBlocksChanged();
- } else if (!blocks.empty() && !blocks.back()->messages.empty()) {
- blocks.back()->messages.back()->nextInBlocksRemoved();
- }
- }
- void History::cacheTopPromoted(bool promoted) {
- if (isTopPromoted() == promoted) {
- return;
- } else if (promoted) {
- _flags |= Flag::IsTopPromoted;
- } else {
- _flags &= ~Flag::IsTopPromoted;
- }
- updateChatListSortPosition();
- updateChatListEntry();
- if (!isTopPromoted()) {
- updateChatListExistence();
- }
- }
- bool History::isTopPromoted() const {
- return (_flags & Flag::IsTopPromoted);
- }
- void History::translateOfferFrom(LanguageId id) {
- if (!id) {
- if (translatedTo()) {
- _translation->offerFrom(id);
- } else if (_translation) {
- _translation = nullptr;
- session().changes().historyUpdated(
- this,
- UpdateFlag::TranslateFrom);
- }
- } else if (!_translation) {
- _translation = std::make_unique<HistoryTranslation>(this, id);
- } else {
- _translation->offerFrom(id);
- }
- }
- LanguageId History::translateOfferedFrom() const {
- return _translation ? _translation->offeredFrom() : LanguageId();
- }
- void History::translateTo(LanguageId id) {
- if (!_translation) {
- return;
- } else if (!id && !translateOfferedFrom()) {
- _translation = nullptr;
- session().changes().historyUpdated(this, UpdateFlag::TranslatedTo);
- } else {
- _translation->translateTo(id);
- }
- }
- LanguageId History::translatedTo() const {
- return _translation ? _translation->translatedTo() : LanguageId();
- }
- HistoryTranslation *History::translation() const {
- return _translation.get();
- }
- HistoryBlock::HistoryBlock(not_null<History*> history)
- : _history(history) {
- }
- int HistoryBlock::resizeGetHeight(int newWidth, ResizeRequest request) {
- auto y = 0;
- if (request == ResizeRequest::ReinitAll) {
- for (const auto &message : messages) {
- message->setY(y);
- message->initDimensions();
- y += message->resizeGetHeight(newWidth);
- }
- } else if (request == ResizeRequest::ResizeAll) {
- for (const auto &message : messages) {
- message->setY(y);
- y += message->resizeGetHeight(newWidth);
- }
- } else {
- for (const auto &message : messages) {
- message->setY(y);
- y += message->pendingResize()
- ? message->resizeGetHeight(newWidth)
- : message->height();
- }
- }
- _height = y;
- return _height;
- }
- void HistoryBlock::remove(not_null<Element*> view) {
- Expects(view->block() == this);
- _history->mainViewRemoved(this, view);
- const auto blockIndex = indexInHistory();
- const auto itemIndex = view->indexInBlock();
- const auto item = view->data();
- item->clearMainView();
- messages.erase(messages.begin() + itemIndex);
- for (auto i = itemIndex, l = int(messages.size()); i < l; ++i) {
- messages[i]->setIndexInBlock(i);
- }
- if (messages.empty()) {
- // Deletes this.
- _history->removeBlock(this);
- } else if (itemIndex < messages.size()) {
- messages[itemIndex]->previousInBlocksChanged();
- } else if (blockIndex + 1 < _history->blocks.size()) {
- _history->blocks[blockIndex + 1]->messages.front()->previousInBlocksChanged();
- } else if (!_history->blocks.empty() && !_history->blocks.back()->messages.empty()) {
- _history->blocks.back()->messages.back()->nextInBlocksRemoved();
- }
- }
- void HistoryBlock::refreshView(not_null<Element*> view) {
- Expects(view->block() == this);
- const auto item = view->data();
- auto refreshed = item->createView(
- _history->delegateMixin()->delegate(),
- view);
- auto blockIndex = indexInHistory();
- auto itemIndex = view->indexInBlock();
- _history->viewReplaced(view, refreshed.get());
- messages[itemIndex] = std::move(refreshed);
- messages[itemIndex]->attachToBlock(this, itemIndex);
- if (itemIndex + 1 < messages.size()) {
- messages[itemIndex + 1]->previousInBlocksChanged();
- } else if (blockIndex + 1 < _history->blocks.size()) {
- _history->blocks[blockIndex + 1]->messages.front()->previousInBlocksChanged();
- } else if (!_history->blocks.empty() && !_history->blocks.back()->messages.empty()) {
- _history->blocks.back()->messages.back()->nextInBlocksRemoved();
- }
- }
- HistoryBlock::~HistoryBlock() = default;
|