window_session_controller.cpp 94 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "window/window_session_controller.h"
  8. #include "api/api_text_entities.h"
  9. #include "boxes/add_contact_box.h"
  10. #include "boxes/peers/add_bot_to_chat_box.h"
  11. #include "boxes/peers/edit_peer_info_box.h"
  12. #include "boxes/peers/replace_boost_box.h"
  13. #include "boxes/delete_messages_box.h"
  14. #include "window/window_chat_preview.h"
  15. #include "window/window_controller.h"
  16. #include "window/window_filters_menu.h"
  17. #include "window/window_separate_id.h"
  18. #include "info/channel_statistics/earn/info_channel_earn_list.h"
  19. #include "info/info_memento.h"
  20. #include "info/info_controller.h"
  21. #include "inline_bots/bot_attach_web_view.h"
  22. #include "history/history.h"
  23. #include "history/history_item.h"
  24. #include "history/view/reactions/history_view_reactions.h"
  25. //#include "history/view/reactions/history_view_reactions_button.h"
  26. #include "history/view/history_view_replies_section.h"
  27. #include "history/view/history_view_scheduled_section.h"
  28. #include "history/view/history_view_sublist_section.h"
  29. #include "media/player/media_player_instance.h"
  30. #include "media/view/media_view_open_common.h"
  31. #include "data/stickers/data_custom_emoji.h"
  32. #include "data/data_document_resolver.h"
  33. #include "data/data_download_manager.h"
  34. #include "data/data_saved_messages.h"
  35. #include "data/data_session.h"
  36. #include "data/data_file_origin.h"
  37. #include "data/data_folder.h"
  38. #include "data/data_channel.h"
  39. #include "data/data_chat.h"
  40. #include "data/data_user.h"
  41. #include "data/data_document.h"
  42. #include "data/data_document_media.h"
  43. #include "data/data_changes.h"
  44. #include "data/data_group_call.h"
  45. #include "data/data_forum.h"
  46. #include "data/data_forum_topic.h"
  47. #include "data/data_chat_filters.h"
  48. #include "data/data_replies_list.h"
  49. #include "data/data_peer_values.h"
  50. #include "data/data_premium_limits.h"
  51. #include "data/data_web_page.h"
  52. #include "passport/passport_form_controller.h"
  53. #include "chat_helpers/tabbed_selector.h"
  54. #include "chat_helpers/emoji_interactions.h"
  55. #include "core/shortcuts.h"
  56. #include "core/application.h"
  57. #include "core/click_handler_types.h"
  58. #include "core/ui_integration.h"
  59. #include "base/unixtime.h"
  60. #include "info/channel_statistics/earn/earn_icons.h"
  61. #include "ui/controls/userpic_button.h"
  62. #include "ui/text/text_utilities.h"
  63. #include "ui/text/format_values.h" // Ui::FormatPhone.
  64. #include "ui/delayed_activation.h"
  65. #include "ui/boxes/boost_box.h"
  66. #include "ui/chat/chat_style.h"
  67. #include "ui/chat/chat_theme.h"
  68. #include "ui/effects/message_sending_animation_controller.h"
  69. #include "ui/style/style_palette_colorizer.h"
  70. #include "ui/toast/toast.h"
  71. #include "calls/calls_instance.h" // Core::App().calls().inCall().
  72. #include "calls/group/calls_group_call.h"
  73. #include "ui/boxes/calendar_box.h"
  74. #include "ui/boxes/collectible_info_box.h"
  75. #include "ui/boxes/confirm_box.h"
  76. #include "ui/dynamic_thumbnails.h"
  77. #include "ui/ui_utility.h"
  78. #include "mainwidget.h"
  79. #include "main/main_app_config.h"
  80. #include "main/main_domain.h"
  81. #include "main/main_session.h"
  82. #include "main/main_session_settings.h"
  83. #include "lang/lang_keys.h"
  84. #include "apiwrap.h"
  85. #include "api/api_chat_invite.h"
  86. #include "api/api_global_privacy.h"
  87. #include "api/api_blocked_peers.h"
  88. #include "support/support_helper.h"
  89. #include "storage/file_upload.h"
  90. #include "storage/download_manager_mtproto.h"
  91. #include "storage/storage_account.h"
  92. #include "window/themes/window_theme.h"
  93. #include "window/window_peer_menu.h"
  94. #include "window/window_session_controller_link_info.h"
  95. #include "settings/settings_main.h"
  96. #include "settings/settings_premium.h"
  97. #include "settings/settings_privacy_security.h"
  98. #include "styles/style_window.h"
  99. #include "styles/style_boxes.h"
  100. #include "styles/style_dialogs.h"
  101. #include "styles/style_layers.h" // st::boxLabel
  102. namespace Window {
  103. namespace {
  104. constexpr auto kCustomThemesInMemory = 5;
  105. constexpr auto kMaxChatEntryHistorySize = 50;
  106. class MainWindowShow final : public ChatHelpers::Show {
  107. public:
  108. explicit MainWindowShow(not_null<SessionController*> controller);
  109. void activate() override;
  110. void showOrHideBoxOrLayer(
  111. std::variant<
  112. v::null_t,
  113. object_ptr<Ui::BoxContent>,
  114. std::unique_ptr<Ui::LayerWidget>> &&layer,
  115. Ui::LayerOptions options,
  116. anim::type animated) const override;
  117. not_null<QWidget*> toastParent() const override;
  118. bool valid() const override;
  119. operator bool() const override;
  120. Main::Session &session() const override;
  121. bool paused(ChatHelpers::PauseReason reason) const override;
  122. rpl::producer<> pauseChanged() const override;
  123. rpl::producer<bool> adjustShadowLeft() const override;
  124. SendMenu::Details sendMenuDetails() const override;
  125. bool showMediaPreview(
  126. Data::FileOrigin origin,
  127. not_null<DocumentData*> document) const override;
  128. bool showMediaPreview(
  129. Data::FileOrigin origin,
  130. not_null<PhotoData*> photo) const override;
  131. void processChosenSticker(
  132. ChatHelpers::FileChosen &&chosen) const override;
  133. private:
  134. const base::weak_ptr<SessionController> _window;
  135. };
  136. [[nodiscard]] Ui::ChatThemeBubblesData PrepareBubblesData(
  137. const Data::CloudTheme &theme,
  138. Data::CloudThemeType type) {
  139. const auto i = theme.settings.find(type);
  140. return {
  141. .colors = (i != end(theme.settings)
  142. ? i->second.outgoingMessagesColors
  143. : std::vector<QColor>()),
  144. .accent = (i != end(theme.settings)
  145. ? i->second.outgoingAccentColor
  146. : std::optional<QColor>()),
  147. };
  148. }
  149. [[nodiscard]] bool DownloadingDocument(not_null<DocumentData*> document) {
  150. for (const auto id : Core::App().downloadManager().loadingList()) {
  151. if (id->object.document == document.get()) {
  152. return true;
  153. }
  154. }
  155. return false;
  156. }
  157. [[nodiscard]] Ui::CollectibleDetails PrepareCollectibleDetails(
  158. not_null<Main::Session*> session) {
  159. return {
  160. .tonEmoji = Ui::Text::SingleCustomEmoji(
  161. session->data().customEmojiManager().registerInternalEmoji(
  162. Ui::Earn::IconCurrencyColored(
  163. st::collectibleInfo.style.font,
  164. st::collectibleInfo.textFg->c),
  165. st::collectibleInfoTonMargins,
  166. true)),
  167. .tonEmojiContext = Core::TextContext({ .session = session }),
  168. };
  169. }
  170. [[nodiscard]] Ui::CollectibleInfo Parse(
  171. const QString &entity,
  172. not_null<PeerData*> owner,
  173. const MTPfragment_CollectibleInfo &info) {
  174. const auto &data = info.data();
  175. return {
  176. .entity = entity,
  177. .copyText = (entity.startsWith('+')
  178. ? QString()
  179. : owner->session().createInternalLinkFull(entity)),
  180. .ownerUserpic = Ui::MakeUserpicThumbnail(owner, true),
  181. .ownerName = owner->name(),
  182. .cryptoAmount = data.vcrypto_amount().v,
  183. .amount = data.vamount().v,
  184. .cryptoCurrency = qs(data.vcrypto_currency()),
  185. .currency = qs(data.vcurrency()),
  186. .url = qs(data.vurl()),
  187. .date = data.vpurchase_date().v,
  188. };
  189. }
  190. MainWindowShow::MainWindowShow(not_null<SessionController*> controller)
  191. : _window(base::make_weak(controller)) {
  192. }
  193. void MainWindowShow::activate() {
  194. if (const auto window = _window.get()) {
  195. Window::ActivateWindow(window);
  196. }
  197. }
  198. void MainWindowShow::showOrHideBoxOrLayer(
  199. std::variant<
  200. v::null_t,
  201. object_ptr<Ui::BoxContent>,
  202. std::unique_ptr<Ui::LayerWidget>> &&layer,
  203. Ui::LayerOptions options,
  204. anim::type animated) const {
  205. if (const auto window = _window.get()) {
  206. window->window().widget()->showOrHideBoxOrLayer(
  207. std::move(layer),
  208. options,
  209. animated);
  210. }
  211. }
  212. not_null<QWidget*> MainWindowShow::toastParent() const {
  213. const auto window = _window.get();
  214. Assert(window != nullptr);
  215. return window->widget()->bodyWidget();
  216. }
  217. bool MainWindowShow::valid() const {
  218. return !_window.empty();
  219. }
  220. MainWindowShow::operator bool() const {
  221. return valid();
  222. }
  223. Main::Session &MainWindowShow::session() const {
  224. const auto window = _window.get();
  225. Assert(window != nullptr);
  226. return window->session();
  227. }
  228. bool MainWindowShow::paused(ChatHelpers::PauseReason reason) const {
  229. const auto window = _window.get();
  230. return window && window->isGifPausedAtLeastFor(reason);
  231. }
  232. rpl::producer<> MainWindowShow::pauseChanged() const {
  233. const auto window = _window.get();
  234. if (!window) {
  235. return rpl::never<>();
  236. }
  237. return window->gifPauseLevelChanged();
  238. }
  239. rpl::producer<bool> MainWindowShow::adjustShadowLeft() const {
  240. const auto window = _window.get();
  241. if (!window) {
  242. return rpl::single(false);
  243. }
  244. return window->adaptive().value(
  245. ) | rpl::map([=] {
  246. return !window->adaptive().isOneColumn();
  247. });
  248. }
  249. SendMenu::Details MainWindowShow::sendMenuDetails() const {
  250. const auto window = _window.get();
  251. if (!window) {
  252. return SendMenu::Details();
  253. }
  254. return window->content()->sendMenuDetails();
  255. }
  256. bool MainWindowShow::showMediaPreview(
  257. Data::FileOrigin origin,
  258. not_null<DocumentData*> document) const {
  259. const auto window = _window.get();
  260. return window && window->widget()->showMediaPreview(origin, document);
  261. }
  262. bool MainWindowShow::showMediaPreview(
  263. Data::FileOrigin origin,
  264. not_null<PhotoData*> photo) const {
  265. const auto window = _window.get();
  266. return window && window->widget()->showMediaPreview(origin, photo);
  267. }
  268. void MainWindowShow::processChosenSticker(
  269. ChatHelpers::FileChosen &&chosen) const {
  270. if (const auto window = _window.get()) {
  271. Ui::PostponeCall(window, [=, chosen = std::move(chosen)]() mutable {
  272. window->stickerOrEmojiChosen(std::move(chosen));
  273. });
  274. }
  275. }
  276. } // namespace
  277. void ActivateWindow(not_null<SessionController*> controller) {
  278. Ui::ActivateWindow(controller->widget());
  279. }
  280. bool IsPaused(
  281. not_null<SessionController*> controller,
  282. GifPauseReason level) {
  283. return controller->isGifPausedAtLeastFor(level);
  284. }
  285. Fn<bool()> PausedIn(
  286. not_null<SessionController*> controller,
  287. GifPauseReason level) {
  288. return [=] { return IsPaused(controller, level); };
  289. }
  290. bool operator==(const PeerThemeOverride &a, const PeerThemeOverride &b) {
  291. return (a.peer == b.peer) && (a.theme == b.theme);
  292. }
  293. bool operator!=(const PeerThemeOverride &a, const PeerThemeOverride &b) {
  294. return !(a == b);
  295. }
  296. DateClickHandler::DateClickHandler(Dialogs::Key chat, QDate date)
  297. : _chat(chat)
  298. , _weak(chat.topic())
  299. , _date(date) {
  300. }
  301. void DateClickHandler::setDate(QDate date) {
  302. _date = date;
  303. }
  304. void DateClickHandler::onClick(ClickContext context) const {
  305. const auto my = context.other.value<ClickHandlerContext>();
  306. if (const auto window = my.sessionWindow.get()) {
  307. if (!_chat.topic()) {
  308. window->showCalendar(_chat, _date);
  309. } else if (const auto strong = _weak.get()) {
  310. window->showCalendar(strong, _date);
  311. }
  312. }
  313. }
  314. SessionNavigation::SessionNavigation(not_null<Main::Session*> session)
  315. : _session(session)
  316. , _api(&_session->mtp()) {
  317. }
  318. SessionNavigation::~SessionNavigation() = default;
  319. Main::Session &SessionNavigation::session() const {
  320. return *_session;
  321. }
  322. void SessionNavigation::showPeerByLink(const PeerByLinkInfo &info) {
  323. Core::App().hideMediaView();
  324. if (!info.phone.isEmpty()) {
  325. resolvePhone(info.phone, [=](not_null<PeerData*> peer) {
  326. showPeerByLinkResolved(peer, info);
  327. });
  328. } else if (!info.chatLinkSlug.isEmpty()) {
  329. resolveChatLink(info.chatLinkSlug, [=](
  330. not_null<PeerData*> peer,
  331. TextWithEntities draft) {
  332. Data::SetChatLinkDraft(peer, draft);
  333. showPeerByLinkResolved(peer, info);
  334. });
  335. } else if (const auto name = std::get_if<QString>(&info.usernameOrId)) {
  336. resolveUsername(*name, [=](not_null<PeerData*> peer) {
  337. if (info.startAutoSubmit) {
  338. peer->session().api().blockedPeers().unblock(
  339. peer,
  340. [=](bool) { showPeerByLinkResolved(peer, info); },
  341. true);
  342. } else if (info.joinChannel && peer->isChannel()) {
  343. peer->session().api().joinChannel(peer->asChannel());
  344. } else {
  345. showPeerByLinkResolved(peer, info);
  346. }
  347. }, info.referral);
  348. } else if (const auto id = std::get_if<ChannelId>(&info.usernameOrId)) {
  349. resolveChannelById(*id, [=](not_null<ChannelData*> channel) {
  350. showPeerByLinkResolved(channel, info);
  351. });
  352. }
  353. }
  354. void SessionNavigation::resolvePhone(
  355. const QString &phone,
  356. Fn<void(not_null<PeerData*>)> done) {
  357. if (const auto peer = _session->data().userByPhone(phone)) {
  358. done(peer);
  359. return;
  360. }
  361. _api.request(base::take(_resolveRequestId)).cancel();
  362. _resolveRequestId = _api.request(MTPcontacts_ResolvePhone(
  363. MTP_string(phone)
  364. )).done([=](const MTPcontacts_ResolvedPeer &result) {
  365. resolveDone(result, done);
  366. }).fail([=](const MTP::Error &error) {
  367. _resolveRequestId = 0;
  368. if (error.code() == 400) {
  369. parentController()->show(
  370. Ui::MakeInformBox(tr::lng_username_by_phone_not_found(
  371. tr::now,
  372. lt_phone,
  373. Ui::FormatPhone(phone))),
  374. Ui::LayerOption::CloseOther);
  375. }
  376. }).send();
  377. }
  378. void SessionNavigation::resolveChatLink(
  379. const QString &slug,
  380. Fn<void(not_null<PeerData*> peer, TextWithEntities draft)> done) {
  381. _api.request(base::take(_resolveRequestId)).cancel();
  382. _resolveRequestId = _api.request(MTPaccount_ResolveBusinessChatLink(
  383. MTP_string(slug)
  384. )).done([=](const MTPaccount_ResolvedBusinessChatLinks &result) {
  385. _resolveRequestId = 0;
  386. parentController()->hideLayer();
  387. const auto &data = result.data();
  388. _session->data().processUsers(data.vusers());
  389. _session->data().processChats(data.vchats());
  390. using namespace Api;
  391. const auto peerId = peerFromMTP(data.vpeer());
  392. done(_session->data().peer(peerId), {
  393. qs(data.vmessage()),
  394. EntitiesFromMTP(_session, data.ventities().value_or_empty())
  395. });
  396. }).fail([=](const MTP::Error &error) {
  397. _resolveRequestId = 0;
  398. if (error.code() == 400) {
  399. showToast(tr::lng_confirm_phone_link_invalid(tr::now));
  400. }
  401. }).send();
  402. }
  403. void SessionNavigation::resolveUsername(
  404. const QString &username,
  405. Fn<void(not_null<PeerData*>)> done,
  406. const QString &referral) {
  407. if (referral.isEmpty()) {
  408. if (const auto peer = _session->data().peerByUsername(username)) {
  409. done(peer);
  410. return;
  411. }
  412. }
  413. _api.request(base::take(_resolveRequestId)).cancel();
  414. using Flag = MTPcontacts_ResolveUsername::Flag;
  415. _resolveRequestId = _api.request(MTPcontacts_ResolveUsername(
  416. MTP_flags(referral.isEmpty() ? Flag() : Flag::f_referer),
  417. MTP_string(username),
  418. MTP_string(referral)
  419. )).done([=](const MTPcontacts_ResolvedPeer &result) {
  420. resolveDone(result, done);
  421. }).fail([=](const MTP::Error &error) {
  422. _resolveRequestId = 0;
  423. if (error.type() == u"STARREF_EXPIRED"_q) {
  424. parentController()->showToast(tr::lng_star_ref_stopped(tr::now));
  425. } else if (error.code() == 400) {
  426. parentController()->show(
  427. Ui::MakeInformBox(
  428. tr::lng_username_not_found(tr::now, lt_user, username)),
  429. Ui::LayerOption::CloseOther);
  430. }
  431. }).send();
  432. }
  433. void SessionNavigation::resolveDone(
  434. const MTPcontacts_ResolvedPeer &result,
  435. Fn<void(not_null<PeerData*>)> done) {
  436. _resolveRequestId = 0;
  437. parentController()->hideLayer();
  438. result.match([&](const MTPDcontacts_resolvedPeer &data) {
  439. _session->data().processUsers(data.vusers());
  440. _session->data().processChats(data.vchats());
  441. if (const auto peerId = peerFromMTP(data.vpeer())) {
  442. done(_session->data().peer(peerId));
  443. }
  444. });
  445. }
  446. void SessionNavigation::resolveChannelById(
  447. ChannelId channelId,
  448. Fn<void(not_null<ChannelData*>)> done) {
  449. if (const auto channel = _session->data().channelLoaded(channelId)) {
  450. done(channel);
  451. return;
  452. }
  453. const auto fail = crl::guard(this, [=] {
  454. uiShow()->showToast(tr::lng_error_post_link_invalid(tr::now));
  455. });
  456. _api.request(base::take(_resolveRequestId)).cancel();
  457. _resolveRequestId = _api.request(MTPchannels_GetChannels(
  458. MTP_vector<MTPInputChannel>(
  459. 1,
  460. MTP_inputChannel(MTP_long(channelId.bare), MTP_long(0)))
  461. )).done([=](const MTPmessages_Chats &result) {
  462. result.match([&](const auto &data) {
  463. const auto peer = _session->data().processChats(data.vchats());
  464. if (peer && peer->id == peerFromChannel(channelId)) {
  465. done(peer->asChannel());
  466. } else {
  467. fail();
  468. }
  469. });
  470. }).fail(fail).send();
  471. }
  472. void SessionNavigation::showMessageByLinkResolved(
  473. not_null<HistoryItem*> item,
  474. const PeerByLinkInfo &info) {
  475. auto params = SectionShow{
  476. SectionShow::Way::Forward
  477. };
  478. params.origin = SectionShow::OriginMessage{
  479. info.clickFromMessageId
  480. };
  481. const auto peer = item->history()->peer;
  482. const auto topicId = peer->isForum() ? item->topicRootId() : 0;
  483. if (topicId) {
  484. const auto messageId = (item->id == topicId) ? MsgId() : item->id;
  485. showRepliesForMessage(item->history(), topicId, messageId, params);
  486. } else {
  487. showPeerHistory(peer, params, item->id);
  488. }
  489. }
  490. void SessionNavigation::showPeerByLinkResolved(
  491. not_null<PeerData*> peer,
  492. const PeerByLinkInfo &info) {
  493. auto params = SectionShow{
  494. SectionShow::Way::Forward
  495. };
  496. params.origin = SectionShow::OriginMessage{
  497. info.clickFromMessageId
  498. };
  499. if (info.voicechatHash && peer->isChannel()) {
  500. // First show the channel itself.
  501. crl::on_main(this, [=] {
  502. showPeerHistory(peer, params, ShowAtUnreadMsgId);
  503. });
  504. // Then try to join the voice chat.
  505. joinVoiceChatFromLink(peer, info);
  506. return;
  507. }
  508. using Scope = AddBotToGroupBoxController::Scope;
  509. const auto user = peer->asUser();
  510. const auto bot = (user && user->isBot()) ? user : nullptr;
  511. // t.me/username/012345 - we thought it was a channel post link, but
  512. // after resolving the username we found out it is a bot.
  513. const auto resolveType = (bot
  514. && !info.botAppName.isEmpty()
  515. && info.resolveType == ResolveType::Default)
  516. ? ResolveType::BotApp
  517. : info.resolveType;
  518. const auto &replies = info.repliesInfo;
  519. if (const auto threadId = std::get_if<ThreadId>(&replies)) {
  520. const auto history = peer->owner().history(peer);
  521. const auto controller = parentController();
  522. if (const auto forum = peer->forum()) {
  523. if (controller->windowId().hasChatsList()
  524. && !controller->adaptive().isOneColumn()
  525. && controller->shownForum().current() != forum) {
  526. controller->showForum(forum);
  527. }
  528. }
  529. showRepliesForMessage(
  530. history,
  531. threadId->id,
  532. info.messageId,
  533. params);
  534. } else if (const auto commentId = std::get_if<CommentId>(&replies)) {
  535. showRepliesForMessage(
  536. session().data().history(peer),
  537. info.messageId,
  538. commentId->id,
  539. params);
  540. } else if (resolveType == ResolveType::Profile) {
  541. showPeerInfo(peer, params);
  542. } else if (resolveType == ResolveType::HashtagSearch) {
  543. searchMessages(info.text, peer->owner().history(peer));
  544. } else if (peer->isForum() && resolveType != ResolveType::Boost) {
  545. const auto itemId = info.messageId;
  546. if (!itemId) {
  547. parentController()->showForum(peer->forum(), params);
  548. } else if (const auto item = peer->owner().message(peer, itemId)) {
  549. showMessageByLinkResolved(item, info);
  550. } else {
  551. const auto callback = crl::guard(this, [=] {
  552. if (const auto item = peer->owner().message(peer, itemId)) {
  553. showMessageByLinkResolved(item, info);
  554. } else {
  555. showPeerHistory(peer, params, itemId);
  556. }
  557. });
  558. peer->session().api().requestMessageData(
  559. peer,
  560. info.messageId,
  561. callback);
  562. }
  563. } else if (info.storyId) {
  564. const auto storyId = FullStoryId{ peer->id, info.storyId };
  565. peer->owner().stories().resolve(storyId, crl::guard(this, [=] {
  566. if (peer->owner().stories().lookup(storyId)) {
  567. parentController()->openPeerStory(
  568. peer,
  569. storyId.story,
  570. Data::StoriesContext{ Data::StoriesContextSingle() });
  571. } else {
  572. showToast(tr::lng_stories_link_invalid(tr::now));
  573. }
  574. }));
  575. } else if (bot && resolveType == ResolveType::BotApp) {
  576. const auto itemId = info.clickFromMessageId;
  577. const auto item = _session->data().message(itemId);
  578. const auto contextPeer = item
  579. ? item->history()->peer
  580. : bot;
  581. const auto action = info.clickFromBotWebviewContext
  582. ? info.clickFromBotWebviewContext->action
  583. : Api::SendAction(bot->owner().history(contextPeer));
  584. crl::on_main(this, [=] {
  585. bot->session().attachWebView().open({
  586. .bot = bot,
  587. .context = {
  588. .controller = parentController(),
  589. .action = action,
  590. .fullscreen = info.botAppFullScreen,
  591. .maySkipConfirmation = !info.botAppForceConfirmation,
  592. },
  593. .button = { .startCommand = info.startToken },
  594. .source = InlineBots::WebViewSourceLinkApp{
  595. .appname = info.botAppName,
  596. .token = info.startToken,
  597. },
  598. });
  599. });
  600. } else if (bot && resolveType == ResolveType::ShareGame) {
  601. Window::ShowShareGameBox(parentController(), bot, info.startToken);
  602. } else if (bot
  603. && (resolveType == ResolveType::AddToGroup
  604. || resolveType == ResolveType::AddToChannel)) {
  605. const auto scope = (resolveType == ResolveType::AddToGroup)
  606. ? (info.startAdminRights ? Scope::GroupAdmin : Scope::All)
  607. : (resolveType == ResolveType::AddToChannel)
  608. ? Scope::ChannelAdmin
  609. : Scope::None;
  610. Assert(scope != Scope::None);
  611. AddBotToGroupBoxController::Start(
  612. parentController(),
  613. bot,
  614. scope,
  615. info.startToken,
  616. info.startAdminRights);
  617. } else if (resolveType == ResolveType::Mention) {
  618. if (bot || peer->isChannel()) {
  619. crl::on_main(this, [=] {
  620. showPeerHistory(peer, params);
  621. });
  622. } else {
  623. showPeerInfo(peer, params);
  624. }
  625. } else if (resolveType == ResolveType::Boost && peer->isChannel()) {
  626. resolveBoostState(peer->asChannel());
  627. } else {
  628. // Show specific posts only in channels / supergroups.
  629. const auto msgId = peer->isChannel()
  630. ? info.messageId
  631. : info.startAutoSubmit
  632. ? ShowAndStartBotMsgId
  633. : (bot && !info.startToken.isEmpty())
  634. ? ShowAndMaybeStartBotMsgId
  635. : ShowAtUnreadMsgId;
  636. const auto attachBotUsername = info.attachBotUsername;
  637. if (bot && bot->botInfo->startToken != info.startToken) {
  638. bot->botInfo->startToken = info.startToken;
  639. bot->session().changes().peerUpdated(
  640. bot,
  641. Data::PeerUpdate::Flag::BotStartToken);
  642. }
  643. if (!attachBotUsername.isEmpty()) {
  644. crl::on_main(this, [=] {
  645. const auto history = peer->owner().history(peer);
  646. showPeerHistory(history, params, msgId);
  647. peer->session().attachWebView().openByUsername(
  648. parentController(),
  649. Api::SendAction(history),
  650. attachBotUsername,
  651. info.attachBotToggleCommand.value_or(QString()),
  652. info.botAppFullScreen);
  653. });
  654. } else if (bot && info.attachBotMainOpen) {
  655. const auto startCommand = info.attachBotToggleCommand.value_or(
  656. QString());
  657. bot->session().attachWebView().open({
  658. .bot = bot,
  659. .context = {
  660. .controller = parentController(),
  661. .fullscreen = info.botAppFullScreen,
  662. },
  663. .button = { .startCommand = startCommand },
  664. .source = InlineBots::WebViewSourceLinkBotProfile{
  665. .token = startCommand,
  666. .compact = info.attachBotMainCompact,
  667. },
  668. });
  669. } else if (bot && info.attachBotToggleCommand) {
  670. const auto itemId = info.clickFromMessageId;
  671. const auto item = _session->data().message(itemId);
  672. const auto contextPeer = item
  673. ? item->history()->peer.get()
  674. : nullptr;
  675. const auto contextUser = contextPeer
  676. ? contextPeer->asUser()
  677. : nullptr;
  678. bot->session().attachWebView().open({
  679. .bot = bot,
  680. .context = {
  681. .controller = parentController(),
  682. .action = (contextUser
  683. ? Api::SendAction(
  684. contextUser->owner().history(contextUser))
  685. : std::optional<Api::SendAction>()),
  686. .fullscreen = info.botAppFullScreen,
  687. },
  688. .button = { .startCommand = *info.attachBotToggleCommand },
  689. .source = InlineBots::WebViewSourceLinkAttachMenu{
  690. .choose = info.attachBotChooseTypes,
  691. .token = *info.attachBotToggleCommand,
  692. },
  693. });
  694. } else {
  695. const auto draft = info.text;
  696. params.videoTimestamp = info.videoTimestamp;
  697. crl::on_main(this, [=] {
  698. if (peer->isUser() && !draft.isEmpty()) {
  699. Data::SetChatLinkDraft(peer, { draft });
  700. }
  701. showPeerHistory(peer, params, msgId);
  702. });
  703. }
  704. }
  705. }
  706. void SessionNavigation::resolveBoostState(
  707. not_null<ChannelData*> channel,
  708. int boostsToLift) {
  709. _boostsToLift = boostsToLift;
  710. if (_boostStateResolving == channel) {
  711. return;
  712. }
  713. _boostStateResolving = channel;
  714. _api.request(MTPpremium_GetBoostsStatus(
  715. channel->input
  716. )).done([=](const MTPpremium_BoostsStatus &result) {
  717. if (base::take(_boostStateResolving) != channel) {
  718. return;
  719. }
  720. const auto boosted = std::make_shared<bool>();
  721. channel->updateLevelHint(result.data().vlevel().v);
  722. const auto submit = [=](Fn<void(Ui::BoostCounters)> done) {
  723. applyBoost(channel, [=](Ui::BoostCounters counters) {
  724. *boosted = true;
  725. done(counters);
  726. });
  727. };
  728. const auto lifting = base::take(_boostsToLift);
  729. const auto box = uiShow()->show(Box(Ui::BoostBox, Ui::BoostBoxData{
  730. .name = channel->name(),
  731. .boost = ParseBoostCounters(result),
  732. .features = LookupBoostFeatures(channel),
  733. .lifting = lifting,
  734. .allowMulti = (BoostsForGift(_session) > 0),
  735. .group = channel->isMegagroup(),
  736. }, submit));
  737. if (lifting) {
  738. box->boxClosing() | rpl::start_with_next([=] {
  739. if (*boosted) {
  740. channel->updateFullForced();
  741. }
  742. }, box->lifetime());
  743. }
  744. }).fail([=](const MTP::Error &error) {
  745. _boostStateResolving = nullptr;
  746. showToast(u"Error: "_q + error.type());
  747. }).send();
  748. }
  749. void SessionNavigation::resolveCollectible(
  750. PeerId ownerId,
  751. const QString &entity,
  752. Fn<void(QString)> fail) {
  753. if (_collectibleEntity == entity) {
  754. return;
  755. } else {
  756. _api.request(base::take(_collectibleRequestId)).cancel();
  757. }
  758. _collectibleEntity = entity;
  759. _collectibleRequestId = _api.request(MTPfragment_GetCollectibleInfo(
  760. ((Ui::DetectCollectibleType(entity) == Ui::CollectibleType::Phone)
  761. ? MTP_inputCollectiblePhone(MTP_string(entity))
  762. : MTP_inputCollectibleUsername(MTP_string(entity)))
  763. )).done([=](const MTPfragment_CollectibleInfo &result) {
  764. const auto entity = base::take(_collectibleEntity);
  765. _collectibleRequestId = 0;
  766. uiShow()->show(Box(
  767. Ui::CollectibleInfoBox,
  768. Parse(entity, _session->data().peer(ownerId), result),
  769. PrepareCollectibleDetails(_session)));
  770. }).fail([=](const MTP::Error &error) {
  771. _collectibleEntity = QString();
  772. _collectibleRequestId = 0;
  773. if (fail) {
  774. fail(error.type());
  775. }
  776. }).send();
  777. }
  778. void SessionNavigation::applyBoost(
  779. not_null<ChannelData*> channel,
  780. Fn<void(Ui::BoostCounters)> done) {
  781. _api.request(MTPpremium_GetMyBoosts(
  782. )).done([=](const MTPpremium_MyBoosts &result) {
  783. const auto &data = result.data();
  784. _session->data().processUsers(data.vusers());
  785. _session->data().processChats(data.vchats());
  786. const auto slots = ParseForChannelBoostSlots(
  787. channel,
  788. data.vmy_boosts().v);
  789. if (!slots.free.empty()) {
  790. applyBoostsChecked(channel, { slots.free.front() }, done);
  791. } else if (slots.other.empty()) {
  792. if (!slots.already.empty()) {
  793. if (const auto receive = BoostsForGift(_session)) {
  794. const auto again = true;
  795. const auto name = channel->name();
  796. uiShow()->show(
  797. Box(Ui::GiftForBoostsBox, name, receive, again));
  798. } else {
  799. uiShow()->show(
  800. Box(Ui::BoostBoxAlready, channel->isMegagroup()));
  801. }
  802. } else if (!_session->premium()) {
  803. const auto group = channel->isMegagroup();
  804. uiShow()->show(Box(Ui::PremiumForBoostsBox, group, [=] {
  805. const auto id = peerToChannel(channel->id).bare;
  806. Settings::ShowPremium(
  807. parentController(),
  808. "channel_boost__" + QString::number(id));
  809. }));
  810. } else if (const auto receive = BoostsForGift(_session)) {
  811. const auto again = false;
  812. const auto name = channel->name();
  813. uiShow()->show(
  814. Box(Ui::GiftForBoostsBox, name, receive, again));
  815. } else {
  816. uiShow()->show(
  817. Box(Ui::GiftedNoBoostsBox, channel->isMegagroup()));
  818. }
  819. done({});
  820. } else {
  821. const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
  822. const auto reassign = [=](
  823. std::vector<int> slots,
  824. int groups,
  825. int channels) {
  826. const auto count = int(slots.size());
  827. const auto callback = [=](Ui::BoostCounters counters) {
  828. if (const auto strong = weak->data()) {
  829. strong->closeBox();
  830. }
  831. done(counters);
  832. uiShow()->showToast(tr::lng_boost_reassign_done(
  833. tr::now,
  834. lt_count,
  835. count,
  836. lt_channels,
  837. (!groups
  838. ? tr::lng_boost_reassign_channels
  839. : !channels
  840. ? tr::lng_boost_reassign_groups
  841. : tr::lng_boost_reassign_mixed)(
  842. tr::now,
  843. lt_count,
  844. groups + channels)));
  845. };
  846. applyBoostsChecked(
  847. channel,
  848. slots,
  849. crl::guard(this, callback));
  850. };
  851. *weak = uiShow()->show(ReassignBoostsBox(
  852. channel,
  853. slots.other,
  854. reassign,
  855. [=] { done({}); }));
  856. }
  857. }).fail([=](const MTP::Error &error) {
  858. const auto type = error.type();
  859. showToast(u"Error: "_q + type);
  860. done({});
  861. }).handleFloodErrors().send();
  862. }
  863. void SessionNavigation::applyBoostsChecked(
  864. not_null<ChannelData*> channel,
  865. std::vector<int> slots,
  866. Fn<void(Ui::BoostCounters)> done) {
  867. auto mtp = MTP_vector_from_range(ranges::views::all(
  868. slots
  869. ) | ranges::views::transform([](int slot) {
  870. return MTP_int(slot);
  871. }));
  872. _api.request(MTPpremium_ApplyBoost(
  873. MTP_flags(MTPpremium_ApplyBoost::Flag::f_slots),
  874. std::move(mtp),
  875. channel->input
  876. )).done([=](const MTPpremium_MyBoosts &result) {
  877. _api.request(MTPpremium_GetBoostsStatus(
  878. channel->input
  879. )).done([=](const MTPpremium_BoostsStatus &result) {
  880. channel->updateLevelHint(result.data().vlevel().v);
  881. done(ParseBoostCounters(result));
  882. }).fail([=](const MTP::Error &error) {
  883. showToast(u"Error: "_q + error.type());
  884. done({});
  885. }).send();
  886. }).fail([=](const MTP::Error &error) {
  887. showToast(u"Error: "_q + error.type());
  888. done({});
  889. }).send();
  890. }
  891. void SessionNavigation::joinVoiceChatFromLink(
  892. not_null<PeerData*> peer,
  893. const PeerByLinkInfo &info) {
  894. Expects(info.voicechatHash.has_value());
  895. const auto bad = crl::guard(this, [=] {
  896. uiShow()->showToast(tr::lng_group_invite_bad_link(tr::now));
  897. });
  898. const auto hash = *info.voicechatHash;
  899. _api.request(base::take(_resolveRequestId)).cancel();
  900. _resolveRequestId = _api.request(
  901. MTPchannels_GetFullChannel(peer->asChannel()->inputChannel)
  902. ).done([=](const MTPmessages_ChatFull &result) {
  903. _session->api().processFullPeer(peer, result);
  904. const auto call = peer->groupCall();
  905. if (!call) {
  906. bad();
  907. return;
  908. }
  909. const auto join = [=] {
  910. parentController()->startOrJoinGroupCall(
  911. peer,
  912. { hash, Calls::StartGroupCallArgs::JoinConfirm::Always });
  913. };
  914. if (call->loaded()) {
  915. join();
  916. return;
  917. }
  918. const auto id = call->id();
  919. const auto limit = 5;
  920. _resolveRequestId = _api.request(
  921. MTPphone_GetGroupCall(call->input(), MTP_int(limit))
  922. ).done([=](const MTPphone_GroupCall &result) {
  923. if (const auto now = peer->groupCall(); now && now->id() == id) {
  924. if (!now->loaded()) {
  925. now->processFullCall(result);
  926. }
  927. join();
  928. } else {
  929. bad();
  930. }
  931. }).fail(bad).send();
  932. }).send();
  933. }
  934. void SessionNavigation::showRepliesForMessage(
  935. not_null<History*> history,
  936. MsgId rootId,
  937. MsgId commentId,
  938. const SectionShow &params) {
  939. if (const auto topic = history->peer->forumTopicFor(rootId)) {
  940. auto replies = topic->replies();
  941. if (replies->unreadCountKnown()) {
  942. auto memento = std::make_shared<HistoryView::RepliesMemento>(
  943. history,
  944. rootId,
  945. commentId,
  946. params.highlightPart,
  947. params.highlightPartOffsetHint);
  948. memento->setFromTopic(topic);
  949. showSection(std::move(memento), params);
  950. return;
  951. }
  952. }
  953. if (_showingRepliesRequestId
  954. && _showingRepliesHistory == history.get()
  955. && _showingRepliesRootId == rootId) {
  956. return;
  957. } else if (!history->peer->asChannel()) {
  958. // HistoryView::RepliesWidget right now handles only channels.
  959. return;
  960. }
  961. _api.request(base::take(_showingRepliesRequestId)).cancel();
  962. const auto postPeer = history->peer;
  963. _showingRepliesHistory = history;
  964. _showingRepliesRootId = rootId;
  965. _showingRepliesRequestId = _api.request(
  966. MTPmessages_GetDiscussionMessage(
  967. history->peer->input,
  968. MTP_int(rootId))
  969. ).done([=](const MTPmessages_DiscussionMessage &result) {
  970. _showingRepliesRequestId = 0;
  971. result.match([&](const MTPDmessages_discussionMessage &data) {
  972. _session->data().processUsers(data.vusers());
  973. _session->data().processChats(data.vchats());
  974. _session->data().processMessages(
  975. data.vmessages(),
  976. NewMessageType::Existing);
  977. const auto list = data.vmessages().v;
  978. const auto deleted = list.isEmpty();
  979. const auto comments = history->peer->isBroadcast();
  980. if (comments && deleted) {
  981. return;
  982. }
  983. const auto id = deleted ? rootId : IdFromMessage(list.front());
  984. const auto peer = deleted
  985. ? history->peer->id
  986. : PeerFromMessage(list.front());
  987. if (!peer || !id) {
  988. return;
  989. }
  990. auto item = deleted
  991. ? nullptr
  992. : _session->data().message(peer, id);
  993. if (comments && !item) {
  994. return;
  995. }
  996. auto &groups = _session->data().groups();
  997. if (const auto group = item ? groups.find(item) : nullptr) {
  998. item = group->items.front();
  999. }
  1000. if (comments) {
  1001. const auto post = _session->data().message(postPeer, rootId);
  1002. if (post) {
  1003. post->setCommentsItemId(item->fullId());
  1004. if (const auto maxId = data.vmax_id()) {
  1005. post->setCommentsMaxId(maxId->v);
  1006. }
  1007. post->setCommentsInboxReadTill(
  1008. data.vread_inbox_max_id().value_or_empty());
  1009. }
  1010. }
  1011. if (deleted || item) {
  1012. auto memento = item
  1013. ? std::make_shared<HistoryView::RepliesMemento>(
  1014. item,
  1015. commentId)
  1016. : std::make_shared<HistoryView::RepliesMemento>(
  1017. history,
  1018. rootId,
  1019. commentId);
  1020. memento->setReadInformation(
  1021. data.vread_inbox_max_id().value_or_empty(),
  1022. data.vunread_count().v,
  1023. data.vread_outbox_max_id().value_or_empty());
  1024. showSection(std::move(memento), params);
  1025. }
  1026. });
  1027. }).fail([=](const MTP::Error &error) {
  1028. _showingRepliesRequestId = 0;
  1029. if (error.type() == u"CHANNEL_PRIVATE"_q
  1030. || error.type() == u"USER_BANNED_IN_CHANNEL"_q) {
  1031. showToast(tr::lng_group_not_accessible(tr::now));
  1032. } else if (error.type() == u"MSG_ID_INVALID"_q) {
  1033. showToast(tr::lng_message_not_found(tr::now));
  1034. }
  1035. }).send();
  1036. }
  1037. void SessionNavigation::showPeerInfo(
  1038. PeerId peerId,
  1039. const SectionShow &params) {
  1040. showPeerInfo(_session->data().peer(peerId), params);
  1041. }
  1042. void SessionNavigation::showTopic(
  1043. not_null<Data::ForumTopic*> topic,
  1044. MsgId itemId,
  1045. const SectionShow &params) {
  1046. return showRepliesForMessage(
  1047. topic->history(),
  1048. topic->rootId(),
  1049. itemId,
  1050. params);
  1051. }
  1052. void SessionNavigation::showThread(
  1053. not_null<Data::Thread*> thread,
  1054. MsgId itemId,
  1055. const SectionShow &params) {
  1056. if (const auto topic = thread->asTopic()) {
  1057. showTopic(topic, itemId, params);
  1058. } else {
  1059. showPeerHistory(thread->asHistory(), params, itemId);
  1060. }
  1061. if (parentController()->activeChatCurrent().thread() == thread) {
  1062. parentController()->content()->hideDragForwardInfo();
  1063. }
  1064. }
  1065. void SessionNavigation::showPeerInfo(
  1066. not_null<PeerData*> peer,
  1067. const SectionShow &params) {
  1068. //if (Adaptive::ThreeColumn()
  1069. // && !Core::App().settings().thirdSectionInfoEnabled()) {
  1070. // Core::App().settings().setThirdSectionInfoEnabled(true);
  1071. // Core::App().saveSettingsDelayed();
  1072. //}
  1073. showSection(std::make_shared<Info::Memento>(peer), params);
  1074. }
  1075. void SessionNavigation::showPeerInfo(
  1076. not_null<Data::Thread*> thread,
  1077. const SectionShow &params) {
  1078. if (const auto topic = thread->asTopic()) {
  1079. showSection(std::make_shared<Info::Memento>(topic), params);
  1080. } else {
  1081. showPeerInfo(thread->peer()->id, params);
  1082. }
  1083. }
  1084. void SessionNavigation::showPeerHistory(
  1085. not_null<PeerData*> peer,
  1086. const SectionShow &params,
  1087. MsgId msgId) {
  1088. showPeerHistory(peer->id, params, msgId);
  1089. }
  1090. void SessionNavigation::showPeerHistory(
  1091. not_null<History*> history,
  1092. const SectionShow &params,
  1093. MsgId msgId) {
  1094. showPeerHistory(history->peer->id, params, msgId);
  1095. }
  1096. void SessionNavigation::showByInitialId(
  1097. const SectionShow &params,
  1098. MsgId msgId) {
  1099. const auto parent = parentController();
  1100. const auto id = parent->window().id();
  1101. auto instant = params;
  1102. instant.animated = anim::type::instant;
  1103. switch (id.type) {
  1104. case SeparateType::Archive:
  1105. clearSectionStack(instant);
  1106. parent->openFolder(id.folder());
  1107. break;
  1108. case SeparateType::Forum:
  1109. clearSectionStack(instant);
  1110. parent->showForum(id.forum(), instant);
  1111. break;
  1112. case SeparateType::Primary:
  1113. clearSectionStack(instant);
  1114. break;
  1115. case SeparateType::Chat:
  1116. showThread(id.thread, msgId, instant);
  1117. break;
  1118. case SeparateType::SharedMedia: {
  1119. Assert(id.sharedMedia != SeparateSharedMediaType::None);
  1120. clearSectionStack(instant);
  1121. const auto type = (id.sharedMedia == SeparateSharedMediaType::Photos)
  1122. ? Storage::SharedMediaType::Photo
  1123. : (id.sharedMedia == SeparateSharedMediaType::Videos)
  1124. ? Storage::SharedMediaType::Video
  1125. : (id.sharedMedia == SeparateSharedMediaType::Files)
  1126. ? Storage::SharedMediaType::File
  1127. : (id.sharedMedia == SeparateSharedMediaType::Audio)
  1128. ? Storage::SharedMediaType::MusicFile
  1129. : (id.sharedMedia == SeparateSharedMediaType::Links)
  1130. ? Storage::SharedMediaType::Link
  1131. : (id.sharedMedia == SeparateSharedMediaType::Voices)
  1132. ? Storage::SharedMediaType::RoundVoiceFile
  1133. : (id.sharedMedia == SeparateSharedMediaType::GIF)
  1134. ? Storage::SharedMediaType::GIF
  1135. : Storage::SharedMediaType::Photo;
  1136. const auto topicRootId = id.sharedMediaTopicRootId();
  1137. const auto peer = id.sharedMediaPeer();
  1138. const auto topic = topicRootId
  1139. ? peer->forumTopicFor(topicRootId)
  1140. : nullptr;
  1141. if (topicRootId && !topic) {
  1142. break;
  1143. }
  1144. showSection(
  1145. topicRootId
  1146. ? std::make_shared<Info::Memento>(topic, type)
  1147. : std::make_shared<Info::Memento>(peer, type),
  1148. instant);
  1149. parent->widget()->setMaximumWidth(st::maxWidthSharedMediaWindow);
  1150. break;
  1151. }
  1152. case SeparateType::SavedSublist:
  1153. showSection(
  1154. std::make_shared<HistoryView::SublistMemento>(id.sublist()),
  1155. instant);
  1156. break;
  1157. }
  1158. }
  1159. void SessionNavigation::showSettings(
  1160. Settings::Type type,
  1161. const SectionShow &params) {
  1162. showSection(
  1163. std::make_shared<Info::Memento>(
  1164. Info::Settings::Tag{ _session->user() },
  1165. Info::Section(type)),
  1166. params);
  1167. }
  1168. void SessionNavigation::showSettings(const SectionShow &params) {
  1169. showSettings(Settings::Main::Id(), params);
  1170. }
  1171. void SessionNavigation::showPollResults(
  1172. not_null<PollData*> poll,
  1173. FullMsgId contextId,
  1174. const SectionShow &params) {
  1175. showSection(std::make_shared<Info::Memento>(poll, contextId), params);
  1176. }
  1177. void SessionNavigation::searchInChat(
  1178. Dialogs::Key inChat,
  1179. PeerData *searchFrom) {
  1180. searchMessages(QString(), inChat, searchFrom);
  1181. }
  1182. void SessionNavigation::searchMessages(
  1183. const QString &query,
  1184. Dialogs::Key inChat,
  1185. PeerData *searchFrom) {
  1186. parentController()->content()->searchMessages(query, inChat, searchFrom);
  1187. }
  1188. auto SessionNavigation::showToast(Ui::Toast::Config &&config)
  1189. -> base::weak_ptr<Ui::Toast::Instance> {
  1190. return uiShow()->showToast(std::move(config));
  1191. }
  1192. auto SessionNavigation::showToast(const QString &text, crl::time duration)
  1193. -> base::weak_ptr<Ui::Toast::Instance> {
  1194. return uiShow()->showToast(text);
  1195. }
  1196. auto SessionNavigation::showToast(
  1197. TextWithEntities &&text,
  1198. crl::time duration)
  1199. -> base::weak_ptr<Ui::Toast::Instance> {
  1200. return uiShow()->showToast(std::move(text));
  1201. }
  1202. std::shared_ptr<ChatHelpers::Show> SessionNavigation::uiShow() {
  1203. return parentController()->uiShow();
  1204. }
  1205. struct SessionController::CachedThemeKey {
  1206. Ui::ChatThemeKey theme;
  1207. QString paper;
  1208. friend inline auto operator<=>(
  1209. const CachedThemeKey&,
  1210. const CachedThemeKey&) = default;
  1211. [[nodiscard]] explicit operator bool() const {
  1212. return theme || !paper.isEmpty();
  1213. }
  1214. };
  1215. struct SessionController::CachedTheme {
  1216. std::weak_ptr<Ui::ChatTheme> theme;
  1217. std::shared_ptr<Data::DocumentMedia> media;
  1218. Data::WallPaper paper;
  1219. bool basedOnDark = false;
  1220. bool caching = false;
  1221. rpl::lifetime lifetime;
  1222. };
  1223. SessionController::SessionController(
  1224. not_null<Main::Session*> session,
  1225. not_null<Controller*> window)
  1226. : SessionNavigation(session)
  1227. , _window(window)
  1228. , _emojiInteractions(
  1229. std::make_unique<ChatHelpers::EmojiInteractions>(session))
  1230. , _chatPreviewManager(std::make_unique<ChatPreviewManager>(this))
  1231. , _isPrimary(window->isPrimary())
  1232. , _hasDialogs(window->id().hasChatsList())
  1233. , _sendingAnimation(
  1234. std::make_unique<Ui::MessageSendingAnimationController>(this))
  1235. , _tabbedSelector(
  1236. std::make_unique<ChatHelpers::TabbedSelector>(
  1237. _window->widget(),
  1238. uiShow(),
  1239. GifPauseReason::TabbedPanel))
  1240. , _invitePeekTimer([=] { checkInvitePeek(); })
  1241. , _activeChatsFilter(session->data().chatsFilters().defaultId())
  1242. , _openedFolder(window->id().folder())
  1243. , _defaultChatTheme(std::make_shared<Ui::ChatTheme>())
  1244. , _chatStyle(std::make_unique<Ui::ChatStyle>(session->colorIndicesValue())) {
  1245. init();
  1246. _chatStyleTheme = _defaultChatTheme;
  1247. _chatStyle->apply(_defaultChatTheme.get());
  1248. pushDefaultChatBackground();
  1249. Theme::Background()->updates(
  1250. ) | rpl::start_with_next([=](const Theme::BackgroundUpdate &update) {
  1251. if (update.type == Theme::BackgroundUpdate::Type::New
  1252. || update.type == Theme::BackgroundUpdate::Type::Changed) {
  1253. pushDefaultChatBackground();
  1254. }
  1255. }, _lifetime);
  1256. style::PaletteChanged(
  1257. ) | rpl::start_with_next([=] {
  1258. for (auto &[key, value] : _customChatThemes) {
  1259. if (!key.theme.id) {
  1260. value.theme.reset();
  1261. }
  1262. }
  1263. }, _lifetime);
  1264. _authedName = session->user()->name();
  1265. session->changes().peerUpdates(
  1266. Data::PeerUpdate::Flag::FullInfo
  1267. | Data::PeerUpdate::Flag::Name
  1268. ) | rpl::filter([=](const Data::PeerUpdate &update) {
  1269. if (update.flags & Data::PeerUpdate::Flag::Name) {
  1270. const auto user = session->user();
  1271. if (update.peer == user) {
  1272. _authedName = user->name();
  1273. const auto &settings = Core::App().settings();
  1274. if (!settings.windowTitleContent().hideAccountName) {
  1275. widget()->updateTitle();
  1276. }
  1277. }
  1278. }
  1279. return (update.flags & Data::PeerUpdate::Flag::FullInfo)
  1280. && (update.peer == _showEditPeer);
  1281. }) | rpl::start_with_next([=] {
  1282. show(Box<EditPeerInfoBox>(this, base::take(_showEditPeer)));
  1283. }, lifetime());
  1284. session->data().chatsListChanges(
  1285. ) | rpl::filter([=](Data::Folder *folder) {
  1286. return (folder != nullptr)
  1287. && (folder == _openedFolder.current())
  1288. && folder->chatsList()->indexed()->empty()
  1289. && !folder->storiesCount();
  1290. }) | rpl::start_with_next([=](Data::Folder *folder) {
  1291. folder->updateChatListSortPosition();
  1292. closeFolder();
  1293. }, lifetime());
  1294. const auto processFiltersMenu = [this] {
  1295. if (SessionNavigation::session().data().chatsFilters().has()) {
  1296. const auto isHorizontal
  1297. = Core::App().settings().chatFiltersHorizontal()
  1298. || !enoughSpaceForFilters();
  1299. content()->toggleFiltersMenu(isHorizontal);
  1300. toggleFiltersMenu(!isHorizontal);
  1301. } else {
  1302. content()->toggleFiltersMenu(false);
  1303. toggleFiltersMenu(false);
  1304. }
  1305. };
  1306. rpl::merge(
  1307. enoughSpaceForFiltersValue() | rpl::skip(1) | rpl::to_empty,
  1308. Core::App().settings().chatFiltersHorizontalChanges() | rpl::to_empty,
  1309. session->data().chatsFilters().changed()
  1310. ) | rpl::start_with_next([=] {
  1311. if (!_filtersActivated) {
  1312. processFiltersMenu();
  1313. }
  1314. checkOpenedFilter();
  1315. crl::on_main(this, processFiltersMenu);
  1316. }, lifetime());
  1317. session->data().itemIdChanged(
  1318. ) | rpl::start_with_next([=](Data::Session::IdChange change) {
  1319. const auto current = _activeChatEntry.current();
  1320. if (const auto topic = current.key.topic()) {
  1321. if (topic->rootId() == change.oldId) {
  1322. setActiveChatEntry({
  1323. Dialogs::Key(topic->forum()->topicFor(change.newId.msg)),
  1324. current.fullId,
  1325. });
  1326. }
  1327. }
  1328. for (auto &entry : _chatEntryHistory) {
  1329. if (const auto topic = entry.key.topic()) {
  1330. if (topic->rootId() == change.oldId) {
  1331. entry.key = Dialogs::Key(
  1332. topic->forum()->topicFor(change.newId.msg));
  1333. }
  1334. }
  1335. }
  1336. }, lifetime());
  1337. session->api().globalPrivacy().suggestArchiveAndMute(
  1338. ) | rpl::take(1) | rpl::start_with_next([=] {
  1339. session->api().globalPrivacy().reload(crl::guard(this, [=] {
  1340. if (!session->api().globalPrivacy().archiveAndMuteCurrent()) {
  1341. suggestArchiveAndMute();
  1342. }
  1343. }));
  1344. }, _lifetime);
  1345. session->downloader().nonPremiumDelays(
  1346. ) | rpl::start_with_next([=](DocumentId id) {
  1347. checkNonPremiumLimitToastDownload(id);
  1348. }, _lifetime);
  1349. session->uploader().nonPremiumDelays(
  1350. ) | rpl::start_with_next([=](FullMsgId id) {
  1351. checkNonPremiumLimitToastUpload(id);
  1352. }, _lifetime);
  1353. session->addWindow(this);
  1354. crl::on_main(this, [=] {
  1355. activateFirstChatsFilter();
  1356. setupPremiumToast();
  1357. });
  1358. }
  1359. bool SessionController::skipNonPremiumLimitToast(bool download) const {
  1360. if (session().premium()) {
  1361. return true;
  1362. }
  1363. const auto now = base::unixtime::now();
  1364. const auto last = download
  1365. ? session().settings().lastNonPremiumLimitDownload()
  1366. : session().settings().lastNonPremiumLimitUpload();
  1367. const auto delay = session().appConfig().get<int>(
  1368. u"upload_premium_speedup_notify_period"_q,
  1369. 3600);
  1370. return (last && now < last + delay && now > last - delay);
  1371. }
  1372. void SessionController::checkNonPremiumLimitToastDownload(DocumentId id) {
  1373. if (skipNonPremiumLimitToast(true)) {
  1374. return;
  1375. }
  1376. const auto document = session().data().document(id);
  1377. const auto visible = session().data().queryDocumentVisibility(document)
  1378. || DownloadingDocument(document);
  1379. if (!visible) {
  1380. return;
  1381. }
  1382. content()->showNonPremiumLimitToast(true);
  1383. const auto now = base::unixtime::now();
  1384. session().settings().setLastNonPremiumLimitDownload(now);
  1385. session().saveSettingsDelayed();
  1386. }
  1387. void SessionController::checkNonPremiumLimitToastUpload(FullMsgId id) {
  1388. if (skipNonPremiumLimitToast(false)) {
  1389. return;
  1390. } else if (const auto item = session().data().message(id)) {
  1391. if (!session().data().queryItemVisibility(item)) {
  1392. return;
  1393. }
  1394. content()->showNonPremiumLimitToast(false);
  1395. const auto now = base::unixtime::now();
  1396. session().settings().setLastNonPremiumLimitUpload(now);
  1397. session().saveSettingsDelayed();
  1398. }
  1399. }
  1400. void SessionController::suggestArchiveAndMute() {
  1401. const auto weak = base::make_weak(this);
  1402. _window->show(Box([=](not_null<Ui::GenericBox*> box) {
  1403. box->setTitle(tr::lng_suggest_hide_new_title());
  1404. box->addRow(object_ptr<Ui::FlatLabel>(
  1405. box,
  1406. tr::lng_suggest_hide_new_about(Ui::Text::RichLangValue),
  1407. st::boxLabel));
  1408. box->addButton(tr::lng_suggest_hide_new_to_settings(), [=] {
  1409. showSettings(Settings::PrivacySecurity::Id());
  1410. });
  1411. box->setCloseByOutsideClick(false);
  1412. box->boxClosing(
  1413. ) | rpl::start_with_next([=] {
  1414. crl::on_main(weak, [=] {
  1415. auto &privacy = session().api().globalPrivacy();
  1416. privacy.dismissArchiveAndMuteSuggestion();
  1417. });
  1418. }, box->lifetime());
  1419. box->addButton(tr::lng_cancel(), [=] {
  1420. box->closeBox();
  1421. });
  1422. }));
  1423. }
  1424. SeparateId SessionController::windowId() const {
  1425. return _window->id();
  1426. }
  1427. bool SessionController::isPrimary() const {
  1428. return _isPrimary;
  1429. }
  1430. not_null<::MainWindow*> SessionController::widget() const {
  1431. return _window->widget();
  1432. }
  1433. auto SessionController::sendingAnimation() const
  1434. -> Ui::MessageSendingAnimationController & {
  1435. return *_sendingAnimation;
  1436. }
  1437. auto SessionController::tabbedSelector() const
  1438. -> not_null<ChatHelpers::TabbedSelector*> {
  1439. return _tabbedSelector.get();
  1440. }
  1441. void SessionController::takeTabbedSelectorOwnershipFrom(
  1442. not_null<QWidget*> parent) {
  1443. if (_tabbedSelector->parent() == parent) {
  1444. if (const auto chats = widget()->sessionContent()) {
  1445. chats->returnTabbedSelector();
  1446. }
  1447. if (_tabbedSelector->parent() == parent) {
  1448. _tabbedSelector->hide();
  1449. _tabbedSelector->setParent(widget());
  1450. }
  1451. }
  1452. }
  1453. bool SessionController::hasTabbedSelectorOwnership() const {
  1454. return (_tabbedSelector->parent() == widget());
  1455. }
  1456. void SessionController::showEditPeerBox(PeerData *peer) {
  1457. _showEditPeer = peer;
  1458. session().api().requestFullPeer(peer);
  1459. }
  1460. void SessionController::init() {
  1461. if (session().supportMode()) {
  1462. session().supportHelper().registerWindow(this);
  1463. }
  1464. setupShortcuts();
  1465. }
  1466. void SessionController::setupShortcuts() {
  1467. Shortcuts::Requests(
  1468. ) | rpl::filter([=] {
  1469. return (Core::App().activeWindow() == &window())
  1470. && !isLayerShown()
  1471. && !window().locked();
  1472. }) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
  1473. using C = Shortcuts::Command;
  1474. const auto app = &Core::App();
  1475. const auto accountsCount = int(app->domain().accounts().size());
  1476. auto &&accounts = ranges::views::zip(
  1477. Shortcuts::kShowAccount,
  1478. ranges::views::ints(0, accountsCount));
  1479. for (const auto &[command, index] : accounts) {
  1480. request->check(command) && request->handle([=] {
  1481. const auto list = app->domain().orderedAccounts();
  1482. if (index >= list.size()) {
  1483. return false;
  1484. }
  1485. const auto account = list[index];
  1486. if (account == &session().account()) {
  1487. return false;
  1488. }
  1489. const auto window = app->separateWindowFor(account);
  1490. if (window) {
  1491. window->activate();
  1492. } else {
  1493. app->domain().maybeActivate(account);
  1494. }
  1495. return true;
  1496. });
  1497. }
  1498. if (!session().supportMode()) {
  1499. return;
  1500. }
  1501. request->check(C::SupportHistoryBack) && request->handle([=] {
  1502. return chatEntryHistoryMove(-1);
  1503. });
  1504. request->check(C::SupportHistoryForward) && request->handle([=] {
  1505. return chatEntryHistoryMove(1);
  1506. });
  1507. }, lifetime());
  1508. }
  1509. void SessionController::toggleFiltersMenu(bool enabled) {
  1510. if (!_isPrimary || (!enabled == !_filters)) {
  1511. return;
  1512. } else if (enabled) {
  1513. _filters = std::make_unique<FiltersMenu>(
  1514. widget()->bodyWidget(),
  1515. this);
  1516. } else {
  1517. _filters = nullptr;
  1518. }
  1519. _filtersMenuChanged.fire({});
  1520. }
  1521. rpl::producer<> SessionController::filtersMenuChanged() const {
  1522. return _filtersMenuChanged.events();
  1523. }
  1524. void SessionController::checkOpenedFilter() {
  1525. activateFirstChatsFilter();
  1526. if (const auto filterId = activeChatsFilterCurrent()) {
  1527. const auto &list = session().data().chatsFilters().list();
  1528. const auto i = ranges::find(list, filterId, &Data::ChatFilter::id);
  1529. if (i == end(list)) {
  1530. setActiveChatsFilter(
  1531. 0,
  1532. { anim::type::normal, anim::activation::background });
  1533. }
  1534. }
  1535. }
  1536. void SessionController::activateFirstChatsFilter() {
  1537. if (_filtersActivated
  1538. || !isPrimary()
  1539. || !session().data().chatsFilters().loaded()) {
  1540. return;
  1541. }
  1542. _filtersActivated = true;
  1543. setActiveChatsFilter(session().data().chatsFilters().defaultId());
  1544. }
  1545. bool SessionController::uniqueChatsInSearchResults() const {
  1546. return session().supportMode()
  1547. && !session().settings().supportAllSearchResults()
  1548. && !_searchInChat.current();
  1549. }
  1550. bool SessionController::openFolderInDifferentWindow(
  1551. not_null<Data::Folder*> folder) {
  1552. const auto id = SeparateId(SeparateType::Archive, &session());
  1553. if (const auto separate = Core::App().separateWindowFor(id)) {
  1554. if (separate == _window) {
  1555. return false;
  1556. }
  1557. separate->sessionController()->showByInitialId();
  1558. separate->activate();
  1559. return true;
  1560. }
  1561. return false;
  1562. }
  1563. void SessionController::openFolder(not_null<Data::Folder*> folder) {
  1564. if (openFolderInDifferentWindow(folder)) {
  1565. return;
  1566. } else if (_openedFolder.current() != folder) {
  1567. resetFakeUnreadWhileOpened();
  1568. }
  1569. if (activeChatsFilterCurrent() != 0) {
  1570. setActiveChatsFilter(0);
  1571. } else if (adaptive().isOneColumn()) {
  1572. clearSectionStack(SectionShow::Way::ClearStack);
  1573. }
  1574. closeForum();
  1575. _openedFolder = folder.get();
  1576. }
  1577. void SessionController::closeFolder() {
  1578. if (_openedFolder.current()
  1579. && windowId().type == SeparateType::Archive) {
  1580. Core::App().closeWindow(_window);
  1581. return;
  1582. }
  1583. _openedFolder = nullptr;
  1584. }
  1585. bool SessionController::showForumInDifferentWindow(
  1586. not_null<Data::Forum*> forum,
  1587. const SectionShow &params) {
  1588. const auto window = Core::App().windowForShowingForum(forum);
  1589. if (window == _window) {
  1590. return false;
  1591. } else if (window) {
  1592. window->sessionController()->showForum(forum, params);
  1593. window->activate();
  1594. return true;
  1595. } else if (windowId().hasChatsList()) {
  1596. return false;
  1597. }
  1598. const auto account = not_null(&session().account());
  1599. auto primary = Core::App().separateWindowFor(account);
  1600. if (!primary) {
  1601. Core::App().domain().activate(account);
  1602. primary = Core::App().separateWindowFor(account);
  1603. }
  1604. if (primary && &primary->account() == account) {
  1605. primary->sessionController()->showForum(forum, params);
  1606. primary->activate();
  1607. }
  1608. return true;
  1609. }
  1610. void SessionController::showForum(
  1611. not_null<Data::Forum*> forum,
  1612. const SectionShow &params) {
  1613. if (showForumInDifferentWindow(forum, params)) {
  1614. return;
  1615. }
  1616. _shownForumLifetime.destroy();
  1617. if (_shownForum.current() != forum) {
  1618. resetFakeUnreadWhileOpened();
  1619. }
  1620. if (forum
  1621. && _activeChatEntry.current().key.peer()
  1622. && adaptive().isOneColumn()) {
  1623. clearSectionStack(params);
  1624. }
  1625. _shownForum = forum.get();
  1626. if (_shownForum.current() != forum) {
  1627. return;
  1628. }
  1629. forum->destroyed(
  1630. ) | rpl::start_with_next([=, history = forum->history()] {
  1631. const auto now = activeChatCurrent().owningHistory();
  1632. const auto showHistory = !now || (now == history);
  1633. const auto weak = base::make_weak(this);
  1634. closeForum();
  1635. if (weak && showHistory) {
  1636. showPeerHistory(history, {
  1637. SectionShow::Way::Backward,
  1638. anim::type::normal,
  1639. anim::activation::background,
  1640. });
  1641. }
  1642. }, _shownForumLifetime);
  1643. content()->showForum(forum, params);
  1644. }
  1645. void SessionController::closeForum() {
  1646. if (const auto forum = _shownForum.current()) {
  1647. const auto id = windowId();
  1648. if (id.type == SeparateType::Forum) {
  1649. const auto initial = id.forum();
  1650. if (!initial || initial == forum) {
  1651. Core::App().closeWindow(_window);
  1652. } else {
  1653. showForum(initial);
  1654. }
  1655. return;
  1656. }
  1657. }
  1658. _shownForumLifetime.destroy();
  1659. _shownForum = nullptr;
  1660. }
  1661. void SessionController::setupPremiumToast() {
  1662. rpl::combine(
  1663. Data::AmPremiumValue(&session()),
  1664. session().changes().peerUpdates(
  1665. Data::PeerUpdate::Flag::FullInfo
  1666. )
  1667. ) | rpl::filter([=] {
  1668. return session().user()->isFullLoaded();
  1669. }) | rpl::map([=](bool premium, const auto&) {
  1670. return premium;
  1671. }) | rpl::distinct_until_changed() | rpl::skip(
  1672. 1
  1673. ) | rpl::filter([=](bool premium) {
  1674. session().mtp().requestConfig();
  1675. return premium;
  1676. }) | rpl::start_with_next([=] {
  1677. MainWindowShow(this).showToast({
  1678. .text = { tr::lng_premium_success(tr::now) },
  1679. .adaptive = true,
  1680. });
  1681. }, _lifetime);
  1682. }
  1683. const rpl::variable<Data::Folder*> &SessionController::openedFolder() const {
  1684. return _openedFolder;
  1685. }
  1686. const rpl::variable<Data::Forum*> &SessionController::shownForum() const {
  1687. return _shownForum;
  1688. }
  1689. void SessionController::setActiveChatEntry(Dialogs::RowDescriptor row) {
  1690. if (windowId().type == SeparateType::SharedMedia) {
  1691. return;
  1692. }
  1693. const auto was = _activeChatEntry.current().key.history();
  1694. const auto now = row.key.history();
  1695. if (was && was != now) {
  1696. _activeHistoryLifetime.destroy();
  1697. was->setFakeUnreadWhileOpened(false);
  1698. _invitePeekTimer.cancel();
  1699. }
  1700. _activeChatEntry = row;
  1701. if (now) {
  1702. now->setFakeUnreadWhileOpened(true);
  1703. if (const auto channel = now->peer->asChannel()
  1704. ; channel && !channel->isForum()) {
  1705. Data::PeerFlagValue(
  1706. channel,
  1707. ChannelData::Flag::Forum
  1708. ) | rpl::filter(
  1709. rpl::mappers::_1
  1710. ) | rpl::start_with_next([=] {
  1711. clearSectionStack(
  1712. { anim::type::normal, anim::activation::background });
  1713. showForum(channel->forum(),
  1714. { anim::type::normal, anim::activation::background });
  1715. }, _shownForumLifetime);
  1716. }
  1717. }
  1718. if (session().supportMode()) {
  1719. pushToChatEntryHistory(row);
  1720. }
  1721. checkInvitePeek();
  1722. }
  1723. void SessionController::checkInvitePeek() {
  1724. const auto history = activeChatCurrent().history();
  1725. if (!history) {
  1726. return;
  1727. }
  1728. const auto channel = history->peer->asChannel();
  1729. if (!channel) {
  1730. return;
  1731. }
  1732. const auto expires = channel->invitePeekExpires();
  1733. if (!expires) {
  1734. return;
  1735. }
  1736. const auto now = base::unixtime::now();
  1737. if (expires > now) {
  1738. _invitePeekTimer.callOnce((expires - now) * crl::time(1000));
  1739. return;
  1740. }
  1741. const auto hash = channel->invitePeekHash();
  1742. channel->clearInvitePeek();
  1743. Api::CheckChatInvite(this, hash, channel);
  1744. }
  1745. void SessionController::resetFakeUnreadWhileOpened() {
  1746. if (const auto history = _activeChatEntry.current().key.history()) {
  1747. history->setFakeUnreadWhileOpened(false);
  1748. }
  1749. }
  1750. bool SessionController::chatEntryHistoryMove(int steps) {
  1751. if (_chatEntryHistory.empty()) {
  1752. return false;
  1753. }
  1754. const auto position = _chatEntryHistoryPosition + steps;
  1755. if (!base::in_range(position, 0, int(_chatEntryHistory.size()))) {
  1756. return false;
  1757. }
  1758. _chatEntryHistoryPosition = position;
  1759. return jumpToChatListEntry(_chatEntryHistory[position]);
  1760. }
  1761. bool SessionController::jumpToChatListEntry(Dialogs::RowDescriptor row) {
  1762. if (const auto thread = row.key.thread()) {
  1763. showThread(
  1764. thread,
  1765. row.fullId.msg,
  1766. SectionShow::Way::ClearStack);
  1767. return true;
  1768. }
  1769. return false;
  1770. }
  1771. void SessionController::setDialogsEntryState(
  1772. Dialogs::EntryState state) {
  1773. _dialogsEntryState = state;
  1774. }
  1775. Dialogs::EntryState SessionController::dialogsEntryStateCurrent() const {
  1776. return _dialogsEntryState.current();
  1777. }
  1778. auto SessionController::dialogsEntryStateValue() const
  1779. -> rpl::producer<Dialogs::EntryState> {
  1780. return _dialogsEntryState.value();
  1781. }
  1782. bool SessionController::switchInlineQuery(
  1783. Dialogs::EntryState to,
  1784. not_null<UserData*> bot,
  1785. const QString &query) {
  1786. Expects(to.key.owningHistory() != nullptr);
  1787. using Section = Dialogs::EntryState::Section;
  1788. const auto thread = to.key.thread();
  1789. if (!thread || !Data::CanSend(thread, ChatRestriction::SendInline)) {
  1790. show(Ui::MakeInformBox(tr::lng_inline_switch_cant()));
  1791. return false;
  1792. }
  1793. const auto history = to.key.owningHistory();
  1794. const auto textWithTags = TextWithTags{
  1795. '@' + bot->username() + ' ' + query,
  1796. TextWithTags::Tags(),
  1797. };
  1798. MessageCursor cursor = {
  1799. int(textWithTags.text.size()),
  1800. int(textWithTags.text.size()),
  1801. Ui::kQFixedMax
  1802. };
  1803. if (to.currentReplyTo.messageId.msg == to.currentReplyTo.topicRootId
  1804. && to.currentReplyTo.quote.empty()) {
  1805. to.currentReplyTo.messageId.msg = MsgId();
  1806. }
  1807. auto draft = std::make_unique<Data::Draft>(
  1808. textWithTags,
  1809. to.currentReplyTo,
  1810. cursor,
  1811. Data::WebPageDraft());
  1812. auto params = Window::SectionShow();
  1813. params.reapplyLocalDraft = true;
  1814. if (to.section == Section::Scheduled) {
  1815. history->setDraft(Data::DraftKey::Scheduled(), std::move(draft));
  1816. showSection(
  1817. std::make_shared<HistoryView::ScheduledMemento>(history),
  1818. params);
  1819. } else {
  1820. const auto topicRootId = to.currentReplyTo.topicRootId;
  1821. history->setLocalDraft(std::move(draft));
  1822. history->clearLocalEditDraft(topicRootId);
  1823. if (to.section == Section::Replies) {
  1824. const auto commentId = MsgId();
  1825. showRepliesForMessage(history, topicRootId, commentId, params);
  1826. } else {
  1827. showPeerHistory(history->peer, params);
  1828. }
  1829. }
  1830. return true;
  1831. }
  1832. bool SessionController::switchInlineQuery(
  1833. not_null<Data::Thread*> thread,
  1834. not_null<UserData*> bot,
  1835. const QString &query) {
  1836. const auto entryState = Dialogs::EntryState{
  1837. .key = thread,
  1838. .section = (thread->asTopic()
  1839. ? Dialogs::EntryState::Section::Replies
  1840. : Dialogs::EntryState::Section::History),
  1841. .currentReplyTo = { .topicRootId = thread->topicRootId() },
  1842. };
  1843. return switchInlineQuery(entryState, bot, query);
  1844. }
  1845. Dialogs::RowDescriptor SessionController::resolveChatNext(
  1846. Dialogs::RowDescriptor from) const {
  1847. return content()->resolveChatNext(from);
  1848. }
  1849. Dialogs::RowDescriptor SessionController::resolveChatPrevious(
  1850. Dialogs::RowDescriptor from) const {
  1851. return content()->resolveChatPrevious(from);
  1852. }
  1853. void SessionController::pushToChatEntryHistory(Dialogs::RowDescriptor row) {
  1854. if (!_chatEntryHistory.empty()
  1855. && _chatEntryHistory[_chatEntryHistoryPosition] == row) {
  1856. return;
  1857. }
  1858. _chatEntryHistory.resize(++_chatEntryHistoryPosition);
  1859. _chatEntryHistory.push_back(row);
  1860. if (_chatEntryHistory.size() > kMaxChatEntryHistorySize) {
  1861. _chatEntryHistory.pop_front();
  1862. --_chatEntryHistoryPosition;
  1863. }
  1864. }
  1865. void SessionController::setActiveChatEntry(Dialogs::Key key) {
  1866. setActiveChatEntry({ key, FullMsgId() });
  1867. }
  1868. Dialogs::RowDescriptor SessionController::activeChatEntryCurrent() const {
  1869. return _activeChatEntry.current();
  1870. }
  1871. Dialogs::Key SessionController::activeChatCurrent() const {
  1872. return activeChatEntryCurrent().key;
  1873. }
  1874. auto SessionController::activeChatEntryChanges() const
  1875. -> rpl::producer<Dialogs::RowDescriptor> {
  1876. return _activeChatEntry.changes();
  1877. }
  1878. rpl::producer<Dialogs::Key> SessionController::activeChatChanges() const {
  1879. return activeChatEntryChanges(
  1880. ) | rpl::map([](const Dialogs::RowDescriptor &value) {
  1881. return value.key;
  1882. }) | rpl::distinct_until_changed();
  1883. }
  1884. auto SessionController::activeChatEntryValue() const
  1885. -> rpl::producer<Dialogs::RowDescriptor> {
  1886. return _activeChatEntry.value();
  1887. }
  1888. rpl::producer<Dialogs::Key> SessionController::activeChatValue() const {
  1889. return activeChatEntryValue(
  1890. ) | rpl::map([](const Dialogs::RowDescriptor &value) {
  1891. return value.key;
  1892. }) | rpl::distinct_until_changed();
  1893. }
  1894. void SessionController::enableGifPauseReason(GifPauseReason reason) {
  1895. if (!(_gifPauseReasons & reason)) {
  1896. auto notify = (static_cast<int>(_gifPauseReasons) < static_cast<int>(reason));
  1897. _gifPauseReasons |= reason;
  1898. if (notify) {
  1899. _gifPauseLevelChanged.fire({});
  1900. }
  1901. }
  1902. }
  1903. void SessionController::disableGifPauseReason(GifPauseReason reason) {
  1904. if (_gifPauseReasons & reason) {
  1905. _gifPauseReasons &= ~reason;
  1906. if (_gifPauseReasons < reason) {
  1907. _gifPauseLevelChanged.fire({});
  1908. }
  1909. }
  1910. }
  1911. bool SessionController::isGifPausedAtLeastFor(GifPauseReason reason) const {
  1912. if (reason == GifPauseReason::Any) {
  1913. return (_gifPauseReasons != 0) || !widget()->isActive();
  1914. }
  1915. return (static_cast<int>(_gifPauseReasons) >= 2 * static_cast<int>(reason)) || !widget()->isActive();
  1916. }
  1917. void SessionController::floatPlayerAreaUpdated() {
  1918. if (const auto main = widget()->sessionContent()) {
  1919. main->floatPlayerAreaUpdated();
  1920. }
  1921. }
  1922. int SessionController::dialogsSmallColumnWidth() const {
  1923. return st::defaultDialogRow.padding.left()
  1924. + st::defaultDialogRow.photoSize
  1925. + st::defaultDialogRow.padding.left();
  1926. }
  1927. int SessionController::minimalThreeColumnWidth() const {
  1928. return (_hasDialogs ? st::columnMinimalWidthLeft : 0)
  1929. + st::columnMinimalWidthMain
  1930. + st::columnMinimalWidthThird;
  1931. }
  1932. auto SessionController::computeColumnLayout() const -> ColumnLayout {
  1933. auto layout = Adaptive::WindowLayout::OneColumn;
  1934. auto bodyWidth = widget()->bodyWidget()->width() - filtersWidth();
  1935. auto dialogsWidth = 0, chatWidth = 0, thirdWidth = 0;
  1936. auto useOneColumnLayout = [&] {
  1937. auto minimalNormal = st::columnMinimalWidthLeft
  1938. + st::columnMinimalWidthMain;
  1939. if (_hasDialogs && bodyWidth < minimalNormal) {
  1940. return true;
  1941. }
  1942. return false;
  1943. };
  1944. auto useNormalLayout = [&] {
  1945. // Used if useSmallColumnLayout() == false.
  1946. if (bodyWidth < minimalThreeColumnWidth()) {
  1947. return true;
  1948. }
  1949. if (!Core::App().settings().tabbedSelectorSectionEnabled()
  1950. && !Core::App().settings().thirdSectionInfoEnabled()) {
  1951. return true;
  1952. }
  1953. return false;
  1954. };
  1955. if (useOneColumnLayout()) {
  1956. dialogsWidth = chatWidth = bodyWidth;
  1957. } else if (useNormalLayout()) {
  1958. layout = Adaptive::WindowLayout::Normal;
  1959. dialogsWidth = countDialogsWidthFromRatio(bodyWidth);
  1960. accumulate_min(dialogsWidth, bodyWidth - st::columnMinimalWidthMain);
  1961. chatWidth = bodyWidth - dialogsWidth;
  1962. } else {
  1963. layout = Adaptive::WindowLayout::ThreeColumn;
  1964. dialogsWidth = countDialogsWidthFromRatio(bodyWidth);
  1965. thirdWidth = countThirdColumnWidthFromRatio(bodyWidth);
  1966. auto shrink = shrinkDialogsAndThirdColumns(
  1967. dialogsWidth,
  1968. thirdWidth,
  1969. bodyWidth);
  1970. dialogsWidth = shrink.dialogsWidth;
  1971. thirdWidth = shrink.thirdWidth;
  1972. chatWidth = bodyWidth - dialogsWidth - thirdWidth;
  1973. }
  1974. return { bodyWidth, dialogsWidth, chatWidth, thirdWidth, layout };
  1975. }
  1976. int SessionController::countDialogsWidthFromRatio(int bodyWidth) const {
  1977. if (!_hasDialogs) {
  1978. return 0;
  1979. }
  1980. const auto nochat = !mainSectionShown();
  1981. const auto width = bodyWidth
  1982. * Core::App().settings().dialogsWidthRatio(nochat);
  1983. auto result = qRound(width);
  1984. accumulate_max(result, st::columnMinimalWidthLeft);
  1985. // accumulate_min(result, st::columnMaximalWidthLeft);
  1986. return result;
  1987. }
  1988. int SessionController::countThirdColumnWidthFromRatio(int bodyWidth) const {
  1989. auto result = Core::App().settings().thirdColumnWidth();
  1990. accumulate_max(result, st::columnMinimalWidthThird);
  1991. accumulate_min(result, st::columnMaximalWidthThird);
  1992. return result;
  1993. }
  1994. SessionController::ShrinkResult SessionController::shrinkDialogsAndThirdColumns(
  1995. int dialogsWidth,
  1996. int thirdWidth,
  1997. int bodyWidth) const {
  1998. auto chatWidth = st::columnMinimalWidthMain;
  1999. if (dialogsWidth + thirdWidth + chatWidth <= bodyWidth) {
  2000. return { dialogsWidth, thirdWidth };
  2001. }
  2002. auto thirdWidthNew = ((bodyWidth - chatWidth) * thirdWidth)
  2003. / (dialogsWidth + thirdWidth);
  2004. auto dialogsWidthNew = ((bodyWidth - chatWidth) * dialogsWidth)
  2005. / (dialogsWidth + thirdWidth);
  2006. if (thirdWidthNew < st::columnMinimalWidthThird) {
  2007. thirdWidthNew = st::columnMinimalWidthThird;
  2008. dialogsWidthNew = bodyWidth - thirdWidthNew - chatWidth;
  2009. Assert(!_hasDialogs || dialogsWidthNew >= st::columnMinimalWidthLeft);
  2010. } else if (_hasDialogs && dialogsWidthNew < st::columnMinimalWidthLeft) {
  2011. dialogsWidthNew = st::columnMinimalWidthLeft;
  2012. thirdWidthNew = bodyWidth - dialogsWidthNew - chatWidth;
  2013. Assert(thirdWidthNew >= st::columnMinimalWidthThird);
  2014. }
  2015. return { dialogsWidthNew, thirdWidthNew };
  2016. }
  2017. bool SessionController::canShowThirdSection() const {
  2018. auto currentLayout = computeColumnLayout();
  2019. auto minimalExtendBy = minimalThreeColumnWidth()
  2020. - currentLayout.bodyWidth;
  2021. return (minimalExtendBy <= widget()->maximalExtendBy());
  2022. }
  2023. bool SessionController::canShowThirdSectionWithoutResize() const {
  2024. auto currentWidth = computeColumnLayout().bodyWidth;
  2025. return currentWidth >= minimalThreeColumnWidth();
  2026. }
  2027. bool SessionController::takeThirdSectionFromLayer() {
  2028. return widget()->takeThirdSectionFromLayer();
  2029. }
  2030. void SessionController::resizeForThirdSection() {
  2031. if (adaptive().isThreeColumn()) {
  2032. return;
  2033. }
  2034. auto &settings = Core::App().settings();
  2035. auto layout = computeColumnLayout();
  2036. auto tabbedSelectorSectionEnabled
  2037. = settings.tabbedSelectorSectionEnabled();
  2038. auto thirdSectionInfoEnabled
  2039. = settings.thirdSectionInfoEnabled();
  2040. settings.setTabbedSelectorSectionEnabled(false);
  2041. settings.setThirdSectionInfoEnabled(false);
  2042. auto wanted = countThirdColumnWidthFromRatio(layout.bodyWidth);
  2043. auto minimal = st::columnMinimalWidthThird;
  2044. auto extendBy = wanted;
  2045. auto extendedBy = [&] {
  2046. // Best - extend by third column without moving the window.
  2047. // Next - extend by minimal third column without moving.
  2048. // Next - show third column inside the window without moving.
  2049. // Last - extend with moving.
  2050. if (widget()->canExtendNoMove(wanted)) {
  2051. return widget()->tryToExtendWidthBy(wanted);
  2052. } else if (widget()->canExtendNoMove(minimal)) {
  2053. extendBy = minimal;
  2054. return widget()->tryToExtendWidthBy(minimal);
  2055. } else if (layout.bodyWidth >= minimalThreeColumnWidth()) {
  2056. return 0;
  2057. }
  2058. return widget()->tryToExtendWidthBy(minimal);
  2059. }();
  2060. if (extendedBy) {
  2061. if (extendBy != settings.thirdColumnWidth()) {
  2062. settings.setThirdColumnWidth(extendBy);
  2063. }
  2064. const auto nochat = !mainSectionShown();
  2065. auto newBodyWidth = layout.bodyWidth + extendedBy;
  2066. auto currentRatio = settings.dialogsWidthRatio(nochat);
  2067. settings.updateDialogsWidthRatio(
  2068. (currentRatio * layout.bodyWidth) / newBodyWidth,
  2069. nochat);
  2070. }
  2071. auto savedValue = (extendedBy == extendBy) ? -1 : extendedBy;
  2072. settings.setThirdSectionExtendedBy(savedValue);
  2073. settings.setTabbedSelectorSectionEnabled(
  2074. tabbedSelectorSectionEnabled);
  2075. settings.setThirdSectionInfoEnabled(
  2076. thirdSectionInfoEnabled);
  2077. }
  2078. void SessionController::closeThirdSection() {
  2079. auto &settings = Core::App().settings();
  2080. auto newWindowSize = widget()->size();
  2081. auto layout = computeColumnLayout();
  2082. if (layout.windowLayout == Adaptive::WindowLayout::ThreeColumn) {
  2083. const auto nochat = !mainSectionShown();
  2084. auto noResize = widget()->isFullScreen()
  2085. || widget()->isMaximized();
  2086. auto savedValue = settings.thirdSectionExtendedBy();
  2087. auto extendedBy = (savedValue == -1)
  2088. ? layout.thirdWidth
  2089. : savedValue;
  2090. auto newBodyWidth = noResize
  2091. ? layout.bodyWidth
  2092. : (layout.bodyWidth - extendedBy);
  2093. auto currentRatio = settings.dialogsWidthRatio(nochat);
  2094. settings.updateDialogsWidthRatio(
  2095. (currentRatio * layout.bodyWidth) / newBodyWidth,
  2096. nochat);
  2097. newWindowSize = QSize(
  2098. widget()->width() + (newBodyWidth - layout.bodyWidth),
  2099. widget()->height());
  2100. }
  2101. settings.setTabbedSelectorSectionEnabled(false);
  2102. settings.setThirdSectionInfoEnabled(false);
  2103. Core::App().saveSettingsDelayed();
  2104. if (widget()->size() != newWindowSize) {
  2105. widget()->resize(newWindowSize);
  2106. } else {
  2107. updateColumnLayout();
  2108. }
  2109. }
  2110. bool SessionController::canShowSeparateWindow(SeparateId id) const {
  2111. if (const auto thread = id.thread) {
  2112. return thread->peer()->computeUnavailableReason().isEmpty();
  2113. }
  2114. return true;
  2115. }
  2116. void SessionController::showPeer(not_null<PeerData*> peer, MsgId msgId) {
  2117. const auto currentPeer = activeChatCurrent().peer();
  2118. if (peer && peer->isChannel() && currentPeer != peer) {
  2119. const auto clickedChannel = peer->asChannel();
  2120. if (!clickedChannel->isPublic()
  2121. && !clickedChannel->amIn()
  2122. && (!currentPeer->isChannel()
  2123. || currentPeer->asChannel()->linkedChat()
  2124. != clickedChannel)) {
  2125. MainWindowShow(this).showToast(peer->isMegagroup()
  2126. ? tr::lng_group_not_accessible(tr::now)
  2127. : tr::lng_channel_not_accessible(tr::now));
  2128. } else {
  2129. showPeerHistory(peer->id, SectionShow(), msgId);
  2130. }
  2131. } else {
  2132. showPeerInfo(peer, SectionShow());
  2133. }
  2134. }
  2135. void SessionController::startOrJoinGroupCall(not_null<PeerData*> peer) {
  2136. startOrJoinGroupCall(peer, {});
  2137. }
  2138. void SessionController::startOrJoinGroupCall(
  2139. not_null<PeerData*> peer,
  2140. Calls::StartGroupCallArgs args) {
  2141. Core::App().calls().startOrJoinGroupCall(uiShow(), peer, args);
  2142. }
  2143. void SessionController::showCalendar(Dialogs::Key chat, QDate requestedDate) {
  2144. const auto topic = chat.topic();
  2145. const auto history = chat.owningHistory();
  2146. if (!history) {
  2147. return;
  2148. }
  2149. const auto currentPeerDate = [&] {
  2150. if (topic) {
  2151. if (const auto item = topic->lastMessage()) {
  2152. return base::unixtime::parse(item->date()).date();
  2153. }
  2154. return QDate();
  2155. } else if (history->scrollTopItem) {
  2156. return history->scrollTopItem->dateTime().date();
  2157. } else if (history->loadedAtTop()
  2158. && !history->isEmpty()
  2159. && history->peer->migrateFrom()) {
  2160. if (const auto migrated = history->owner().historyLoaded(history->peer->migrateFrom())) {
  2161. if (migrated->scrollTopItem) {
  2162. // We're up in the migrated history.
  2163. // So current date is the date of first message here.
  2164. return history->blocks.front()->messages.front()->dateTime().date();
  2165. }
  2166. }
  2167. } else if (const auto item = history->lastMessage()) {
  2168. return base::unixtime::parse(item->date()).date();
  2169. }
  2170. return QDate();
  2171. }();
  2172. const auto maxPeerDate = [&] {
  2173. if (topic) {
  2174. if (const auto item = topic->lastMessage()) {
  2175. return base::unixtime::parse(item->date()).date();
  2176. }
  2177. return QDate();
  2178. }
  2179. const auto check = history->peer->migrateTo()
  2180. ? history->owner().historyLoaded(history->peer->migrateTo())
  2181. : history;
  2182. if (const auto item = check ? check->lastMessage() : nullptr) {
  2183. return base::unixtime::parse(item->date()).date();
  2184. }
  2185. return QDate();
  2186. }();
  2187. const auto minPeerDate = [&] {
  2188. const auto startDate = [&] {
  2189. // Telegram was launched in August 2013 :)
  2190. return QDate(2013, 8, 1);
  2191. };
  2192. if (topic) {
  2193. return base::unixtime::parse(topic->creationDate()).date();
  2194. } else if (const auto chat = history->peer->migrateFrom()) {
  2195. if (const auto history = chat->owner().historyLoaded(chat)) {
  2196. if (history->loadedAtTop()) {
  2197. if (!history->isEmpty()) {
  2198. return history->blocks.front()->messages.front()->dateTime().date();
  2199. }
  2200. } else {
  2201. return startDate();
  2202. }
  2203. }
  2204. }
  2205. if (history->loadedAtTop()) {
  2206. if (!history->isEmpty()) {
  2207. return history->blocks.front()->messages.front()->dateTime().date();
  2208. }
  2209. return QDate::currentDate();
  2210. }
  2211. return startDate();
  2212. }();
  2213. const auto highlighted = !requestedDate.isNull()
  2214. ? requestedDate
  2215. : !currentPeerDate.isNull()
  2216. ? currentPeerDate
  2217. : QDate::currentDate();
  2218. struct ButtonState {
  2219. enum class Type {
  2220. None,
  2221. Disabled,
  2222. Active,
  2223. };
  2224. Type type = Type::None;
  2225. style::complex_color disabledFg = style::complex_color([] {
  2226. auto result = st::attentionBoxButton.textFg->c;
  2227. result.setAlpha(result.alpha() / 2);
  2228. return result;
  2229. });
  2230. style::RoundButton disabled = st::attentionBoxButton;
  2231. };
  2232. const auto buttonState = std::make_shared<ButtonState>();
  2233. buttonState->disabled.textFg
  2234. = buttonState->disabled.textFgOver
  2235. = buttonState->disabledFg.color();
  2236. buttonState->disabled.ripple.color
  2237. = buttonState->disabled.textBgOver
  2238. = buttonState->disabled.textBg;
  2239. const auto selectionChanged = [=](
  2240. not_null<Ui::CalendarBox*> box,
  2241. std::optional<int> selected) {
  2242. if (!selected.has_value()) {
  2243. buttonState->type = ButtonState::Type::None;
  2244. return;
  2245. }
  2246. const auto type = (*selected > 0)
  2247. ? ButtonState::Type::Active
  2248. : ButtonState::Type::Disabled;
  2249. if (buttonState->type == type) {
  2250. return;
  2251. }
  2252. buttonState->type = type;
  2253. box->clearButtons();
  2254. box->addButton(tr::lng_cancel(), [=] {
  2255. box->toggleSelectionMode(false);
  2256. });
  2257. auto text = tr::lng_profile_clear_history();
  2258. const auto button = box->addLeftButton(std::move(text), [=] {
  2259. const auto firstDate = box->selectedFirstDate();
  2260. const auto lastDate = box->selectedLastDate();
  2261. if (!firstDate.isNull()) {
  2262. auto confirm = Box<DeleteMessagesBox>(
  2263. history->peer,
  2264. firstDate,
  2265. lastDate);
  2266. confirm->setDeleteConfirmedCallback(crl::guard(box, [=] {
  2267. box->closeBox();
  2268. }));
  2269. box->getDelegate()->show(std::move(confirm));
  2270. }
  2271. }, (*selected > 0) ? st::attentionBoxButton : buttonState->disabled);
  2272. if (!*selected) {
  2273. button->setPointerCursor(false);
  2274. }
  2275. };
  2276. const auto weak = base::make_weak(this);
  2277. const auto weakTopic = base::make_weak(topic);
  2278. const auto jump = [=](const QDate &date) {
  2279. const auto open = [=](not_null<PeerData*> peer, MsgId id) {
  2280. if (const auto strong = weak.get()) {
  2281. if (!topic) {
  2282. strong->showPeerHistory(
  2283. peer,
  2284. SectionShow::Way::Forward,
  2285. id);
  2286. } else if (const auto strongTopic = weakTopic.get()) {
  2287. strong->showTopic(
  2288. strongTopic,
  2289. id,
  2290. SectionShow::Way::Forward);
  2291. strong->hideLayer(anim::type::normal);
  2292. }
  2293. }
  2294. };
  2295. if (!topic || weakTopic) {
  2296. session().api().resolveJumpToDate(chat, date, open);
  2297. }
  2298. };
  2299. show(Box<Ui::CalendarBox>(Ui::CalendarBoxArgs{
  2300. .month = highlighted,
  2301. .highlighted = highlighted,
  2302. .callback = [=](const QDate &date) { jump(date); },
  2303. .minDate = minPeerDate,
  2304. .maxDate = maxPeerDate,
  2305. .allowsSelection = history->peer->isUser(),
  2306. .selectionChanged = selectionChanged,
  2307. }));
  2308. }
  2309. void SessionController::showPassportForm(const Passport::FormRequest &request) {
  2310. _passportForm = std::make_unique<Passport::FormController>(
  2311. this,
  2312. request);
  2313. _passportForm->show();
  2314. }
  2315. void SessionController::clearPassportForm() {
  2316. _passportForm = nullptr;
  2317. }
  2318. void SessionController::showChooseReportMessages(
  2319. not_null<PeerData*> peer,
  2320. Data::ReportInput reportInput,
  2321. Fn<void(std::vector<MsgId>)> done) const {
  2322. content()->showChooseReportMessages(peer, reportInput, std::move(done));
  2323. }
  2324. void SessionController::clearChooseReportMessages() const {
  2325. content()->clearChooseReportMessages();
  2326. }
  2327. void SessionController::showInNewWindow(
  2328. SeparateId id,
  2329. MsgId msgId) {
  2330. if (!canShowSeparateWindow(id)) {
  2331. Assert(id.thread != nullptr);
  2332. showThread(id.thread, msgId, SectionShow::Way::ClearStack);
  2333. return;
  2334. }
  2335. const auto active = activeChatCurrent();
  2336. // windows check active forum / active archive
  2337. const auto fromActive = active.thread()
  2338. ? (active.thread() == id.thread)
  2339. : false;
  2340. const auto toSeparate = [=] {
  2341. Core::App().ensureSeparateWindowFor(id, msgId);
  2342. };
  2343. if (fromActive) {
  2344. window().preventOrInvoke([=] {
  2345. clearSectionStack();
  2346. toSeparate();
  2347. });
  2348. } else {
  2349. toSeparate();
  2350. }
  2351. }
  2352. void SessionController::toggleChooseChatTheme(
  2353. not_null<PeerData*> peer,
  2354. std::optional<bool> show) const {
  2355. content()->toggleChooseChatTheme(peer, show);
  2356. }
  2357. void SessionController::finishChatThemeEdit(not_null<PeerData*> peer) {
  2358. toggleChooseChatTheme(peer, false);
  2359. const auto weak = base::make_weak(this);
  2360. const auto history = activeChatCurrent().history();
  2361. if (!history || history->peer != peer) {
  2362. showPeerHistory(peer);
  2363. }
  2364. if (weak) {
  2365. hideLayer();
  2366. }
  2367. }
  2368. void SessionController::updateColumnLayout() const {
  2369. content()->updateColumnLayout();
  2370. }
  2371. void SessionController::showPeerHistory(
  2372. PeerId peerId,
  2373. const SectionShow &params,
  2374. MsgId msgId) {
  2375. content()->showHistory(peerId, params, msgId);
  2376. }
  2377. void SessionController::showMessage(
  2378. not_null<const HistoryItem*> item,
  2379. const SectionShow &params) {
  2380. _window->invokeForSessionController(
  2381. &item->history()->session().account(),
  2382. item->history()->peer,
  2383. [&](not_null<SessionController*> controller) {
  2384. if (item->isScheduled()) {
  2385. controller->showSection(
  2386. std::make_shared<HistoryView::ScheduledMemento>(
  2387. item->history()),
  2388. params);
  2389. if (params.activation != anim::activation::background) {
  2390. controller->window().activate();
  2391. }
  2392. } else {
  2393. controller->content()->showMessage(item, params);
  2394. }
  2395. });
  2396. }
  2397. void SessionController::cancelUploadLayer(not_null<HistoryItem*> item) {
  2398. const auto itemId = item->fullId();
  2399. session().uploader().pause(itemId);
  2400. const auto stopUpload = [=](Fn<void()> close) {
  2401. auto &data = session().data();
  2402. if (const auto item = data.message(itemId)) {
  2403. if (!item->isEditingMedia()) {
  2404. const auto history = item->history();
  2405. item->destroy();
  2406. history->requestChatListMessage();
  2407. } else {
  2408. item->returnSavedMedia();
  2409. session().uploader().cancel(item->fullId());
  2410. }
  2411. data.sendHistoryChangeNotifications();
  2412. }
  2413. session().uploader().unpause();
  2414. close();
  2415. };
  2416. const auto continueUpload = [=](Fn<void()> close) {
  2417. session().uploader().unpause();
  2418. close();
  2419. };
  2420. show(Ui::MakeConfirmBox({
  2421. .text = tr::lng_selected_cancel_sure_this(),
  2422. .confirmed = stopUpload,
  2423. .cancelled = continueUpload,
  2424. .confirmText = tr::lng_box_yes(),
  2425. .cancelText = tr::lng_box_no(),
  2426. }));
  2427. }
  2428. void SessionController::showSection(
  2429. std::shared_ptr<SectionMemento> memento,
  2430. const SectionShow &params) {
  2431. if (!params.thirdColumn
  2432. && widget()->showSectionInExistingLayer(memento.get(), params)) {
  2433. return;
  2434. }
  2435. content()->showSection(std::move(memento), params);
  2436. }
  2437. void SessionController::showBackFromStack(const SectionShow &params) {
  2438. const auto bad = [&] {
  2439. // If we show a currently-being-destroyed topic, then
  2440. // skip it and show back one more.
  2441. const auto topic = _activeChatEntry.current().key.topic();
  2442. return topic && topic->forum()->topicDeleted(topic->rootId());
  2443. };
  2444. do {
  2445. const auto empty = content()->stackIsEmpty();
  2446. const auto shown = content()->showBackFromStack(params);
  2447. if (empty && !shown && content()->stackIsEmpty() && bad()) {
  2448. clearSectionStack(anim::type::instant);
  2449. window().close();
  2450. break;
  2451. }
  2452. } while (bad());
  2453. }
  2454. void SessionController::showSpecialLayer(
  2455. object_ptr<Ui::LayerWidget> &&layer,
  2456. anim::type animated) {
  2457. widget()->showSpecialLayer(std::move(layer), animated);
  2458. }
  2459. void SessionController::showLayer(
  2460. std::unique_ptr<Ui::LayerWidget> &&layer,
  2461. Ui::LayerOptions options,
  2462. anim::type animated) {
  2463. _window->showLayer(std::move(layer), options, animated);
  2464. }
  2465. void SessionController::removeLayerBlackout() {
  2466. widget()->ui_removeLayerBlackout();
  2467. }
  2468. bool SessionController::isLayerShown() const {
  2469. return _window->isLayerShown();
  2470. }
  2471. not_null<MainWidget*> SessionController::content() const {
  2472. return widget()->sessionContent();
  2473. }
  2474. int SessionController::filtersWidth() const {
  2475. return _filters ? st::windowFiltersWidth : 0;
  2476. }
  2477. bool SessionController::enoughSpaceForFilters() const {
  2478. return widget()->width() >= widget()->minimumWidth() + st::windowFiltersWidth;
  2479. }
  2480. rpl::producer<bool> SessionController::enoughSpaceForFiltersValue() const {
  2481. return widget()->widthValue() | rpl::map([=] {
  2482. return enoughSpaceForFilters();
  2483. }) | rpl::distinct_until_changed();
  2484. }
  2485. rpl::producer<FilterId> SessionController::activeChatsFilter() const {
  2486. return _activeChatsFilter.value();
  2487. }
  2488. FilterId SessionController::activeChatsFilterCurrent() const {
  2489. return _activeChatsFilter.current();
  2490. }
  2491. void SessionController::setActiveChatsFilter(
  2492. FilterId id,
  2493. const SectionShow &params) {
  2494. if (!isPrimary()) {
  2495. return;
  2496. }
  2497. const auto changed = (activeChatsFilterCurrent() != id);
  2498. if (changed) {
  2499. resetFakeUnreadWhileOpened();
  2500. }
  2501. _activeChatsFilter.force_assign(id);
  2502. if (id || !changed) {
  2503. closeForum();
  2504. closeFolder();
  2505. }
  2506. if (adaptive().isOneColumn()) {
  2507. clearSectionStack(params);
  2508. }
  2509. }
  2510. void SessionController::showAddContact() {
  2511. _window->show(Box<AddContactBox>(&session()));
  2512. }
  2513. void SessionController::showNewGroup() {
  2514. _window->show(Box<GroupInfoBox>(this, GroupInfoBox::Type::Group));
  2515. }
  2516. void SessionController::showNewChannel() {
  2517. _window->show(Box<GroupInfoBox>(this, GroupInfoBox::Type::Channel));
  2518. }
  2519. Window::Adaptive &SessionController::adaptive() const {
  2520. return _window->adaptive();
  2521. }
  2522. void SessionController::setConnectingBottomSkip(int skip) {
  2523. _connectingBottomSkip = skip;
  2524. }
  2525. rpl::producer<int> SessionController::connectingBottomSkipValue() const {
  2526. return _connectingBottomSkip.value();
  2527. }
  2528. void SessionController::stickerOrEmojiChosen(FileChosen chosen) {
  2529. _stickerOrEmojiChosen.fire(std::move(chosen));
  2530. }
  2531. auto SessionController::stickerOrEmojiChosen() const
  2532. -> rpl::producer<FileChosen> {
  2533. return _stickerOrEmojiChosen.events();
  2534. }
  2535. QPointer<Ui::BoxContent> SessionController::show(
  2536. object_ptr<Ui::BoxContent> content,
  2537. Ui::LayerOptions options,
  2538. anim::type animated) {
  2539. return _window->show(std::move(content), options, animated);
  2540. }
  2541. void SessionController::hideLayer(anim::type animated) {
  2542. _window->hideLayer(animated);
  2543. }
  2544. void SessionController::openPhoto(
  2545. not_null<PhotoData*> photo,
  2546. MessageContext message,
  2547. const Data::StoriesContext *stories) {
  2548. const auto item = session().data().message(message.id);
  2549. if (openSharedStory(item) || openFakeItemStory(message.id, stories)) {
  2550. return;
  2551. }
  2552. _window->openInMediaView(
  2553. Media::View::OpenRequest(this, photo, item, message.topicRootId));
  2554. }
  2555. void SessionController::openPhoto(
  2556. not_null<PhotoData*> photo,
  2557. not_null<PeerData*> peer) {
  2558. _window->openInMediaView(Media::View::OpenRequest(this, photo, peer));
  2559. }
  2560. void SessionController::openDocument(
  2561. not_null<DocumentData*> document,
  2562. bool showInMediaView,
  2563. MessageContext message,
  2564. const Data::StoriesContext *stories,
  2565. std::optional<TimeId> videoTimestampOverride) {
  2566. const auto item = session().data().message(message.id);
  2567. if (openSharedStory(item) || openFakeItemStory(message.id, stories)) {
  2568. return;
  2569. } else if (showInMediaView) {
  2570. using namespace Media::View;
  2571. const auto saved = session().local().mediaLastPlaybackPosition(
  2572. document->id);
  2573. const auto timestamp = item ? ExtractVideoTimestamp(item) : 0;
  2574. const auto usedTimestamp = videoTimestampOverride
  2575. ? ((*videoTimestampOverride) * crl::time(1000))
  2576. : saved
  2577. ? saved
  2578. : timestamp
  2579. ? (timestamp * crl::time(1000))
  2580. : crl::time();
  2581. _window->openInMediaView(OpenRequest(
  2582. this,
  2583. document,
  2584. item,
  2585. message.topicRootId,
  2586. false,
  2587. usedTimestamp));
  2588. return;
  2589. }
  2590. Data::ResolveDocument(this, document, item, message.topicRootId);
  2591. }
  2592. bool SessionController::openSharedStory(HistoryItem *item) {
  2593. if (const auto media = item ? item->media() : nullptr) {
  2594. if (const auto storyId = media->storyId()) {
  2595. const auto story = session().data().stories().lookup(storyId);
  2596. if (story) {
  2597. _window->openInMediaView(::Media::View::OpenRequest(
  2598. this,
  2599. *story,
  2600. Data::StoriesContext{ Data::StoriesContextSingle() }));
  2601. }
  2602. return true;
  2603. }
  2604. }
  2605. return false;
  2606. }
  2607. bool SessionController::openFakeItemStory(
  2608. FullMsgId fakeItemId,
  2609. const Data::StoriesContext *stories) {
  2610. if (peerIsChat(fakeItemId.peer)
  2611. || !IsStoryMsgId(fakeItemId.msg)) {
  2612. return false;
  2613. }
  2614. const auto maybeStory = session().data().stories().lookup({
  2615. fakeItemId.peer,
  2616. StoryIdFromMsgId(fakeItemId.msg),
  2617. });
  2618. if (maybeStory) {
  2619. using namespace Data;
  2620. const auto story = *maybeStory;
  2621. const auto context = stories
  2622. ? *stories
  2623. : StoriesContext{ StoriesContextSingle() };
  2624. _window->openInMediaView(
  2625. ::Media::View::OpenRequest(this, story, context));
  2626. }
  2627. return true;
  2628. }
  2629. auto SessionController::cachedChatThemeValue(
  2630. const Data::CloudTheme &data,
  2631. const Data::WallPaper &paper,
  2632. Data::CloudThemeType type)
  2633. -> rpl::producer<std::shared_ptr<Ui::ChatTheme>> {
  2634. const auto themeKey = Ui::ChatThemeKey{
  2635. data.id,
  2636. (type == Data::CloudThemeType::Dark),
  2637. };
  2638. if (!themeKey && paper.isNull()) {
  2639. return rpl::single(_defaultChatTheme);
  2640. }
  2641. const auto settings = data.settings.find(type);
  2642. if (data.id && settings == end(data.settings)) {
  2643. return rpl::single(_defaultChatTheme);
  2644. }
  2645. if (paper.isNull()
  2646. && (!settings->second.paper
  2647. || settings->second.paper->backgroundColors().empty())) {
  2648. return rpl::single(_defaultChatTheme);
  2649. }
  2650. const auto key = CachedThemeKey{
  2651. themeKey,
  2652. !paper.isNull() ? paper.key() : settings->second.paper->key(),
  2653. };
  2654. const auto i = _customChatThemes.find(key);
  2655. if (i != end(_customChatThemes)) {
  2656. if (auto strong = i->second.theme.lock()) {
  2657. pushLastUsedChatTheme(strong);
  2658. return rpl::single(std::move(strong));
  2659. }
  2660. }
  2661. if (i == end(_customChatThemes) || !i->second.caching) {
  2662. cacheChatTheme(key, data, paper, type);
  2663. }
  2664. const auto limit = Data::CloudThemes::TestingColors() ? (1 << 20) : 1;
  2665. using namespace rpl::mappers;
  2666. return rpl::single(
  2667. _defaultChatTheme
  2668. ) | rpl::then(_cachedThemesStream.events(
  2669. ) | rpl::filter([=](const std::shared_ptr<Ui::ChatTheme> &theme) {
  2670. if (theme->key() != key.theme
  2671. || theme->background().key != key.paper) {
  2672. return false;
  2673. }
  2674. pushLastUsedChatTheme(theme);
  2675. return true;
  2676. }) | rpl::take(limit));
  2677. }
  2678. bool SessionController::chatThemeAlreadyCached(
  2679. const Data::CloudTheme &data,
  2680. const Data::WallPaper &paper,
  2681. Data::CloudThemeType type) {
  2682. Expects(paper.document() != nullptr);
  2683. const auto key = CachedThemeKey{
  2684. Ui::ChatThemeKey{
  2685. data.id,
  2686. (type == Data::CloudThemeType::Dark),
  2687. },
  2688. paper.key(),
  2689. };
  2690. const auto i = _customChatThemes.find(key);
  2691. return (i != end(_customChatThemes))
  2692. && (i->second.theme.lock() != nullptr);
  2693. }
  2694. void SessionController::pushLastUsedChatTheme(
  2695. const std::shared_ptr<Ui::ChatTheme> &theme) {
  2696. const auto i = ranges::find(_lastUsedCustomChatThemes, theme);
  2697. if (i == end(_lastUsedCustomChatThemes)) {
  2698. if (_lastUsedCustomChatThemes.size() >= kCustomThemesInMemory) {
  2699. _lastUsedCustomChatThemes.pop_back();
  2700. }
  2701. _lastUsedCustomChatThemes.push_front(theme);
  2702. } else if (i != begin(_lastUsedCustomChatThemes)) {
  2703. std::rotate(begin(_lastUsedCustomChatThemes), i, i + 1);
  2704. }
  2705. }
  2706. not_null<Ui::ChatTheme*> SessionController::currentChatTheme() const {
  2707. if (const auto custom = content()->customChatTheme()) {
  2708. return custom;
  2709. }
  2710. return defaultChatTheme().get();
  2711. }
  2712. void SessionController::setChatStyleTheme(
  2713. const std::shared_ptr<Ui::ChatTheme> &theme) {
  2714. if (_chatStyleTheme.lock() == theme) {
  2715. return;
  2716. }
  2717. _chatStyleTheme = theme;
  2718. _chatStyle->apply(theme.get());
  2719. }
  2720. void SessionController::clearCachedChatThemes() {
  2721. _customChatThemes.clear();
  2722. }
  2723. void SessionController::overridePeerTheme(
  2724. not_null<PeerData*> peer,
  2725. std::shared_ptr<Ui::ChatTheme> theme,
  2726. EmojiPtr emoji) {
  2727. _peerThemeOverride = PeerThemeOverride{
  2728. peer,
  2729. theme ? theme : _defaultChatTheme,
  2730. emoji,
  2731. };
  2732. }
  2733. void SessionController::clearPeerThemeOverride(not_null<PeerData*> peer) {
  2734. if (_peerThemeOverride.current().peer == peer.get()) {
  2735. _peerThemeOverride = PeerThemeOverride();
  2736. }
  2737. }
  2738. void SessionController::pushDefaultChatBackground() {
  2739. const auto background = Theme::Background();
  2740. const auto &paper = background->paper();
  2741. _defaultChatTheme->setBackground({
  2742. .prepared = background->prepared(),
  2743. .preparedForTiled = background->preparedForTiled(),
  2744. .gradientForFill = background->gradientForFill(),
  2745. .colorForFill = background->colorForFill(),
  2746. .colors = paper.backgroundColors(),
  2747. .patternOpacity = paper.patternOpacity(),
  2748. .gradientRotation = paper.gradientRotation(),
  2749. .isPattern = paper.isPattern(),
  2750. .tile = background->tile(),
  2751. });
  2752. }
  2753. void SessionController::cacheChatTheme(
  2754. CachedThemeKey key,
  2755. const Data::CloudTheme &data,
  2756. const Data::WallPaper &paper,
  2757. Data::CloudThemeType type) {
  2758. Expects(data.id != 0 || !paper.isNull());
  2759. const auto dark = (type == Data::CloudThemeType::Dark);
  2760. const auto i = data.settings.find(type);
  2761. Assert((!data.id || (i != end(data.settings)))
  2762. && (!paper.isNull()
  2763. || (i->second.paper.has_value()
  2764. && !i->second.paper->backgroundColors().empty())));
  2765. const auto &use = !paper.isNull() ? paper : *i->second.paper;
  2766. const auto document = use.document();
  2767. const auto media = document ? document->createMediaView() : nullptr;
  2768. use.loadDocument();
  2769. auto &theme = [&]() -> CachedTheme& {
  2770. const auto i = _customChatThemes.find(key);
  2771. if (i != end(_customChatThemes)) {
  2772. i->second.media = media;
  2773. i->second.paper = use;
  2774. i->second.basedOnDark = dark;
  2775. i->second.caching = true;
  2776. return i->second;
  2777. }
  2778. return _customChatThemes.emplace(
  2779. key,
  2780. CachedTheme{
  2781. .media = media,
  2782. .paper = use,
  2783. .basedOnDark = dark,
  2784. .caching = true,
  2785. }).first->second;
  2786. }();
  2787. auto descriptor = Ui::ChatThemeDescriptor{
  2788. .key = key.theme,
  2789. .preparePalette = (data.id
  2790. ? Theme::PreparePaletteCallback(dark, i->second.accentColor)
  2791. : Theme::PrepareCurrentPaletteCallback()),
  2792. .backgroundData = backgroundData(theme),
  2793. .bubblesData = PrepareBubblesData(data, type),
  2794. .basedOnDark = dark,
  2795. };
  2796. crl::async([
  2797. this,
  2798. descriptor = std::move(descriptor),
  2799. weak = base::make_weak(this)
  2800. ]() mutable {
  2801. crl::on_main(weak,[
  2802. this,
  2803. result = std::make_shared<Ui::ChatTheme>(std::move(descriptor))
  2804. ]() mutable {
  2805. result->finishCreateOnMain();
  2806. cacheChatThemeDone(std::move(result));
  2807. });
  2808. });
  2809. if (media && media->loaded(true)) {
  2810. theme.media = nullptr;
  2811. }
  2812. }
  2813. void SessionController::cacheChatThemeDone(
  2814. std::shared_ptr<Ui::ChatTheme> result) {
  2815. Expects(result != nullptr);
  2816. const auto key = CachedThemeKey{
  2817. result->key(),
  2818. result->background().key,
  2819. };
  2820. const auto i = _customChatThemes.find(key);
  2821. if (i == end(_customChatThemes)) {
  2822. return;
  2823. }
  2824. i->second.caching = false;
  2825. i->second.theme = result;
  2826. if (i->second.media) {
  2827. if (i->second.media->loaded(true)) {
  2828. updateCustomThemeBackground(i->second);
  2829. } else {
  2830. session().downloaderTaskFinished(
  2831. ) | rpl::filter([=] {
  2832. const auto i = _customChatThemes.find(key);
  2833. Assert(i != end(_customChatThemes));
  2834. return !i->second.media || i->second.media->loaded(true);
  2835. }) | rpl::start_with_next([=] {
  2836. const auto i = _customChatThemes.find(key);
  2837. Assert(i != end(_customChatThemes));
  2838. updateCustomThemeBackground(i->second);
  2839. }, i->second.lifetime);
  2840. }
  2841. }
  2842. _cachedThemesStream.fire(std::move(result));
  2843. }
  2844. void SessionController::updateCustomThemeBackground(CachedTheme &theme) {
  2845. const auto guard = gsl::finally([&] {
  2846. theme.lifetime.destroy();
  2847. theme.media = nullptr;
  2848. });
  2849. const auto strong = theme.theme.lock();
  2850. if (!theme.media || !strong || !theme.media->loaded(true)) {
  2851. return;
  2852. }
  2853. const auto key = strong->key();
  2854. const auto weak = base::make_weak(this);
  2855. crl::async([=, data = backgroundData(theme, false)] {
  2856. crl::on_main(weak, [
  2857. =,
  2858. result = Ui::PrepareBackgroundImage(data)
  2859. ]() mutable {
  2860. const auto cacheKey = CachedThemeKey{ key, result.key };
  2861. const auto i = _customChatThemes.find(cacheKey);
  2862. if (i != end(_customChatThemes)) {
  2863. if (const auto strong = i->second.theme.lock()) {
  2864. strong->updateBackgroundImageFrom(std::move(result));
  2865. }
  2866. }
  2867. });
  2868. });
  2869. }
  2870. Ui::ChatThemeBackgroundData SessionController::backgroundData(
  2871. CachedTheme &theme,
  2872. bool generateGradient) const {
  2873. const auto &paper = theme.paper;
  2874. const auto &media = theme.media;
  2875. const auto paperPath = media ? media->owner()->filepath() : QString();
  2876. const auto paperBytes = media ? media->bytes() : QByteArray();
  2877. const auto gzipSvg = media && media->owner()->isPatternWallPaperSVG();
  2878. const auto &colors = paper.backgroundColors();
  2879. const auto isPattern = paper.isPattern();
  2880. const auto patternOpacity = paper.patternOpacity();
  2881. const auto isBlurred = paper.isBlurred();
  2882. const auto gradientRotation = paper.gradientRotation();
  2883. const auto darkModeDimming = isPattern
  2884. ? 100
  2885. : std::clamp(paper.patternIntensity(), 0, 100);
  2886. return {
  2887. .key = paper.key(),
  2888. .path = paperPath,
  2889. .bytes = paperBytes,
  2890. .gzipSvg = gzipSvg,
  2891. .colors = colors,
  2892. .isPattern = isPattern,
  2893. .patternOpacity = patternOpacity,
  2894. .darkModeDimming = darkModeDimming,
  2895. .isBlurred = isBlurred,
  2896. .forDarkMode = theme.basedOnDark,
  2897. .generateGradient = generateGradient,
  2898. .gradientRotation = gradientRotation,
  2899. };
  2900. }
  2901. void SessionController::openPeerStory(
  2902. not_null<PeerData*> peer,
  2903. StoryId storyId,
  2904. Data::StoriesContext context) {
  2905. using namespace Media::View;
  2906. using namespace Data;
  2907. invalidate_weak_ptrs(&_storyOpenGuard);
  2908. auto &stories = session().data().stories();
  2909. const auto from = stories.lookup({ peer->id, storyId });
  2910. if (from) {
  2911. window().openInMediaView(OpenRequest(this, *from, context));
  2912. } else if (from.error() == Data::NoStory::Unknown) {
  2913. const auto done = crl::guard(&_storyOpenGuard, [=] {
  2914. openPeerStory(peer, storyId, context);
  2915. });
  2916. stories.resolve({ peer->id, storyId }, done);
  2917. }
  2918. }
  2919. void SessionController::openPeerStories(
  2920. PeerId peerId,
  2921. std::optional<Data::StorySourcesList> list) {
  2922. using namespace Media::View;
  2923. using namespace Data;
  2924. invalidate_weak_ptrs(&_storyOpenGuard);
  2925. auto &stories = session().data().stories();
  2926. if (const auto source = stories.source(peerId)) {
  2927. if (const auto idDates = source->toOpen()) {
  2928. openPeerStory(
  2929. source->peer,
  2930. idDates.id,
  2931. (list
  2932. ? StoriesContext{ *list }
  2933. : StoriesContext{ StoriesContextPeer() }));
  2934. }
  2935. } else if (const auto peer = session().data().peerLoaded(peerId)) {
  2936. const auto done = crl::guard(&_storyOpenGuard, [=] {
  2937. openPeerStories(peerId, list);
  2938. });
  2939. stories.requestPeerStories(peer, done);
  2940. }
  2941. }
  2942. HistoryView::PaintContext SessionController::preparePaintContext(
  2943. Ui::ChatPaintContextArgs &&args) {
  2944. const auto visibleAreaTopLocal = content()->mapFromGlobal(
  2945. args.visibleAreaPositionGlobal).y();
  2946. const auto viewport = QRect(
  2947. 0,
  2948. args.visibleAreaTop - visibleAreaTopLocal,
  2949. args.visibleAreaWidth,
  2950. content()->height());
  2951. return args.theme->preparePaintContext(
  2952. _chatStyle.get(),
  2953. viewport,
  2954. args.clip,
  2955. isGifPausedAtLeastFor(GifPauseReason::Any));
  2956. }
  2957. void SessionController::setPremiumRef(const QString &ref) {
  2958. _premiumRef = ref;
  2959. }
  2960. QString SessionController::premiumRef() const {
  2961. return _premiumRef;
  2962. }
  2963. bool SessionController::showChatPreview(
  2964. Dialogs::RowDescriptor row,
  2965. Fn<void(bool shown)> callback,
  2966. QPointer<QWidget> parentOverride,
  2967. std::optional<QPoint> positionOverride) {
  2968. return _chatPreviewManager->show(
  2969. std::move(row),
  2970. std::move(callback),
  2971. std::move(parentOverride),
  2972. positionOverride);
  2973. }
  2974. bool SessionController::scheduleChatPreview(
  2975. Dialogs::RowDescriptor row,
  2976. Fn<void(bool shown)> callback,
  2977. QPointer<QWidget> parentOverride,
  2978. std::optional<QPoint> positionOverride) {
  2979. return _chatPreviewManager->schedule(
  2980. std::move(row),
  2981. std::move(callback),
  2982. std::move(parentOverride),
  2983. positionOverride);
  2984. }
  2985. void SessionController::cancelScheduledPreview() {
  2986. _chatPreviewManager->cancelScheduled();
  2987. }
  2988. bool SessionController::contentOverlapped(QWidget *w, QPaintEvent *e) const {
  2989. return widget()->contentOverlapped(w, e);
  2990. }
  2991. std::shared_ptr<ChatHelpers::Show> SessionController::uiShow() {
  2992. if (!_cachedShow) {
  2993. _cachedShow = std::make_shared<MainWindowShow>(this);
  2994. }
  2995. return _cachedShow;
  2996. }
  2997. SessionController::~SessionController() {
  2998. resetFakeUnreadWhileOpened();
  2999. }
  3000. bool CheckAndJumpToNearChatsFilter(
  3001. not_null<SessionController*> controller,
  3002. bool isNext,
  3003. bool jump) {
  3004. const auto id = controller->activeChatsFilterCurrent();
  3005. const auto session = &controller->session();
  3006. const auto list = &session->data().chatsFilters().list();
  3007. const auto index = int(ranges::find(
  3008. *list,
  3009. id,
  3010. &Data::ChatFilter::id
  3011. ) - begin(*list));
  3012. if (index == list->size() && id != 0) {
  3013. return false;
  3014. }
  3015. const auto changed = index + (isNext ? 1 : -1);
  3016. if (changed >= int(list->size()) || changed < 0) {
  3017. return false;
  3018. }
  3019. if (changed > Data::PremiumLimits(session).dialogFiltersCurrent()) {
  3020. return false;
  3021. }
  3022. if (jump) {
  3023. controller->setActiveChatsFilter((changed >= 0)
  3024. ? (*list)[changed].id()
  3025. : 0);
  3026. }
  3027. return true;
  3028. }
  3029. } // namespace Window