prepare_short_info_box.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  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 "boxes/peers/prepare_short_info_box.h"
  8. #include "base/unixtime.h"
  9. #include "boxes/peers/peer_short_info_box.h"
  10. #include "core/application.h"
  11. #include "data/data_changes.h"
  12. #include "data/data_channel.h"
  13. #include "data/data_chat.h"
  14. #include "data/data_file_origin.h"
  15. #include "data/data_peer.h"
  16. #include "data/data_peer_values.h"
  17. #include "data/data_photo.h"
  18. #include "data/data_photo_media.h"
  19. #include "data/data_session.h"
  20. #include "data/data_streaming.h"
  21. #include "data/data_user.h"
  22. #include "data/data_user_photos.h"
  23. #include "info/profile/info_profile_values.h"
  24. #include "lang/lang_keys.h"
  25. #include "main/main_session.h"
  26. #include "ui/delayed_activation.h" // PreventDelayedActivation
  27. #include "ui/text/format_values.h"
  28. #include "ui/widgets/menu/menu_add_action_callback.h"
  29. #include "window/window_session_controller.h"
  30. #include "styles/style_info.h"
  31. #include "styles/style_menu_icons.h"
  32. namespace {
  33. constexpr auto kOverviewLimit = 48;
  34. struct UserpicState {
  35. PeerShortInfoUserpic current;
  36. std::optional<UserPhotosSlice> userSlice;
  37. PhotoId userpicPhotoId = PeerData::kUnknownPhotoId;
  38. Ui::PeerUserpicView userpicView;
  39. std::shared_ptr<Data::PhotoMedia> photoView;
  40. std::vector<std::shared_ptr<Data::PhotoMedia>> photoPreloads;
  41. InMemoryKey userpicKey;
  42. PhotoId photoId = PeerData::kUnknownPhotoId;
  43. std::array<QImage, 4> roundMask;
  44. int size = 0;
  45. bool waitingFull = false;
  46. bool waitingLoad = false;
  47. };
  48. void GenerateImage(
  49. not_null<UserpicState*> state,
  50. QImage image,
  51. bool blurred = false) {
  52. using namespace Images;
  53. const auto size = state->size;
  54. const auto ratio = style::DevicePixelRatio();
  55. const auto options = blurred ? Option::Blur : Option();
  56. state->current.photo = Images::Round(
  57. Images::Prepare(
  58. std::move(image),
  59. QSize(size, size) * ratio,
  60. { .options = options, .outer = { size, size } }),
  61. state->roundMask,
  62. RectPart::TopLeft | RectPart::TopRight);
  63. }
  64. void GenerateImage(
  65. not_null<UserpicState*> state,
  66. not_null<Image*> image,
  67. bool blurred = false) {
  68. GenerateImage(state, image->original(), blurred);
  69. }
  70. void ProcessUserpic(
  71. not_null<PeerData*> peer,
  72. not_null<UserpicState*> state) {
  73. state->current.videoDocument = nullptr;
  74. state->userpicKey = peer->userpicUniqueKey(state->userpicView);
  75. if (!state->userpicView.cloud) {
  76. GenerateImage(
  77. state,
  78. PeerData::GenerateUserpicImage(
  79. peer,
  80. state->userpicView,
  81. st::shortInfoWidth * style::DevicePixelRatio(),
  82. 0),
  83. false);
  84. state->current.photoLoadingProgress = 1.;
  85. state->photoView = nullptr;
  86. return;
  87. }
  88. peer->loadUserpic();
  89. if (Ui::PeerUserpicLoading(state->userpicView)) {
  90. state->current.photoLoadingProgress = 0.;
  91. state->current.photo = QImage();
  92. state->waitingLoad = true;
  93. return;
  94. }
  95. GenerateImage(state, *state->userpicView.cloud, true);
  96. state->current.photoLoadingProgress = peer->userpicPhotoId() ? 0. : 1.;
  97. state->photoView = nullptr;
  98. }
  99. void Preload(
  100. not_null<PeerData*> peer,
  101. not_null<UserpicState*> state) {
  102. auto taken = base::take(state->photoPreloads);
  103. if (state->userSlice && state->userSlice->size() > 0) {
  104. const auto preload = [&](int index) {
  105. const auto photo = peer->owner().photo(
  106. (*state->userSlice)[index]);
  107. const auto current = (peer->userpicPhotoId() == photo->id);
  108. const auto origin = current
  109. ? peer->userpicPhotoOrigin()
  110. : Data::FileOriginUserPhoto(peerToUser(peer->id), photo->id);
  111. state->photoPreloads.push_back(photo->createMediaView());
  112. if (photo->hasVideo()) {
  113. state->photoPreloads.back()->videoWanted(
  114. Data::PhotoSize::Large,
  115. origin);
  116. } else {
  117. state->photoPreloads.back()->wanted(
  118. Data::PhotoSize::Large,
  119. origin);
  120. }
  121. };
  122. const auto skip = (state->userSlice->size() == state->current.count)
  123. ? 0
  124. : 1;
  125. if (state->current.index - skip > 0) {
  126. preload(state->current.index - skip - 1);
  127. } else if (!state->current.index && state->current.count > 1) {
  128. preload(state->userSlice->size() - 1);
  129. }
  130. if (state->current.index - skip + 1 < state->userSlice->size()) {
  131. preload(state->current.index - skip + 1);
  132. } else if (!skip && state->current.index > 0) {
  133. preload(0);
  134. }
  135. }
  136. }
  137. void ProcessFullPhoto(
  138. not_null<PeerData*> peer,
  139. not_null<UserpicState*> state,
  140. not_null<PhotoData*> photo) {
  141. using PhotoSize = Data::PhotoSize;
  142. const auto current = (peer->userpicPhotoId() == photo->id);
  143. const auto video = photo->hasVideo();
  144. const auto originCurrent = peer->userpicPhotoOrigin();
  145. const auto originOther = peer->isUser()
  146. ? Data::FileOriginUserPhoto(peerToUser(peer->id), photo->id)
  147. : originCurrent;
  148. const auto origin = current ? originCurrent : originOther;
  149. const auto was = base::take(state->current.videoDocument);
  150. const auto view = photo->createMediaView();
  151. if (!video) {
  152. view->wanted(PhotoSize::Large, origin);
  153. }
  154. if (const auto image = view->image(PhotoSize::Large)) {
  155. GenerateImage(state, image);
  156. Preload(peer, state);
  157. state->photoView = nullptr;
  158. state->current.photoLoadingProgress = 1.;
  159. } else {
  160. if (const auto thumbnail = view->image(PhotoSize::Thumbnail)) {
  161. GenerateImage(state, thumbnail, true);
  162. } else if (const auto small = view->image(PhotoSize::Small)) {
  163. GenerateImage(state, small, true);
  164. } else {
  165. if (current) {
  166. ProcessUserpic(peer, state);
  167. }
  168. if (!current || state->current.photo.isNull()) {
  169. if (const auto blurred = view->thumbnailInline()) {
  170. GenerateImage(state, blurred, true);
  171. } else {
  172. state->current.photo = QImage();
  173. }
  174. }
  175. }
  176. state->waitingLoad = !video;
  177. state->photoView = view;
  178. state->current.photoLoadingProgress = photo->progress();
  179. }
  180. if (!video) {
  181. return;
  182. }
  183. state->current.videoDocument = peer->owner().streaming().sharedDocument(
  184. photo,
  185. origin);
  186. state->current.videoStartPosition = photo->videoStartPosition();
  187. state->photoView = nullptr;
  188. state->current.photoLoadingProgress = 1.;
  189. }
  190. } // namespace
  191. [[nodiscard]] rpl::producer<PeerShortInfoFields> FieldsValue(
  192. not_null<PeerData*> peer) {
  193. using UpdateFlag = Data::PeerUpdate::Flag;
  194. return peer->session().changes().peerFlagsValue(
  195. peer,
  196. (UpdateFlag::Name
  197. | UpdateFlag::PersonalChannel
  198. | UpdateFlag::PhoneNumber
  199. | UpdateFlag::Username
  200. | UpdateFlag::About
  201. | UpdateFlag::Birthday)
  202. ) | rpl::map([=] {
  203. const auto user = peer->asUser();
  204. const auto username = peer->username();
  205. const auto channelId = user ? user->personalChannelId() : 0;
  206. const auto channel = channelId
  207. ? user->owner().channel(channelId).get()
  208. : nullptr;
  209. const auto channelUsername = channel
  210. ? channel->username()
  211. : QString();
  212. const auto hasChannel = !channelUsername.isEmpty();
  213. return PeerShortInfoFields{
  214. .name = peer->name(),
  215. .channelName = hasChannel ? channel->name() : QString(),
  216. .channelLink = (hasChannel
  217. ? channel->session().createInternalLinkFull(channelUsername)
  218. : QString()),
  219. .phone = user ? Ui::FormatPhone(user->phone()) : QString(),
  220. .link = ((user || username.isEmpty())
  221. ? QString()
  222. : peer->session().createInternalLinkFull(username)),
  223. .about = Info::Profile::AboutWithEntities(peer, peer->about()),
  224. .username = ((user && !username.isEmpty())
  225. ? ('@' + username)
  226. : QString()),
  227. .birthday = user ? user->birthday() : Data::Birthday(),
  228. .isBio = (user && !user->isBot()),
  229. };
  230. });
  231. }
  232. [[nodiscard]] rpl::producer<QString> StatusValue(not_null<PeerData*> peer) {
  233. if (const auto user = peer->asUser()) {
  234. const auto now = base::unixtime::now();
  235. return [=](auto consumer) {
  236. auto lifetime = rpl::lifetime();
  237. const auto timer = lifetime.make_state<base::Timer>();
  238. const auto push = [=] {
  239. consumer.put_next(Data::OnlineText(user, now));
  240. timer->callOnce(Data::OnlineChangeTimeout(user, now));
  241. };
  242. timer->setCallback(push);
  243. push();
  244. return lifetime;
  245. };
  246. }
  247. return peer->session().changes().peerFlagsValue(
  248. peer,
  249. Data::PeerUpdate::Flag::Members
  250. ) | rpl::map([=] {
  251. const auto chat = peer->asChat();
  252. const auto channel = peer->asChannel();
  253. const auto count = std::max({
  254. chat ? chat->count : channel->membersCount(),
  255. chat ? int(chat->participants.size()) : 0,
  256. 0,
  257. });
  258. return (chat && !chat->amIn())
  259. ? tr::lng_chat_status_unaccessible(tr::now)
  260. : (count > 0)
  261. ? ((channel && channel->isBroadcast())
  262. ? tr::lng_chat_status_subscribers(
  263. tr::now,
  264. lt_count_decimal,
  265. count)
  266. : tr::lng_chat_status_members(
  267. tr::now,
  268. lt_count_decimal,
  269. count))
  270. : ((channel && channel->isBroadcast())
  271. ? tr::lng_channel_status(tr::now)
  272. : tr::lng_group_status(tr::now));
  273. });
  274. }
  275. void ValidatePhotoId(
  276. not_null<UserpicState*> state,
  277. PhotoId oldUserpicPhotoId) {
  278. if (state->userSlice) {
  279. const auto count = state->userSlice->size();
  280. const auto hasOld = state->userSlice->indexOf(
  281. oldUserpicPhotoId).has_value();
  282. const auto hasNew = state->userSlice->indexOf(
  283. state->userpicPhotoId).has_value();
  284. const auto shift = (hasNew ? 0 : 1);
  285. const auto fullCount = count + shift;
  286. state->current.count = fullCount;
  287. if (hasOld && !hasNew && state->current.index + 1 < fullCount) {
  288. ++state->current.index;
  289. } else if (!hasOld && hasNew && state->current.index > 0) {
  290. --state->current.index;
  291. }
  292. const auto index = state->current.index;
  293. if (!index || index >= fullCount) {
  294. state->current.index = 0;
  295. state->photoId = state->userpicPhotoId;
  296. } else {
  297. state->photoId = (*state->userSlice)[index - shift];
  298. }
  299. } else {
  300. state->photoId = state->userpicPhotoId;
  301. }
  302. }
  303. bool ProcessCurrent(
  304. not_null<PeerData*> peer,
  305. not_null<UserpicState*> state) {
  306. const auto userpicPhotoId = peer->userpicPhotoId();
  307. const auto userpicPhoto = (userpicPhotoId
  308. && (userpicPhotoId != PeerData::kUnknownPhotoId)
  309. && (state->userpicPhotoId != userpicPhotoId))
  310. ? peer->owner().photo(userpicPhotoId).get()
  311. : (state->photoId == userpicPhotoId && state->photoView)
  312. ? state->photoView->owner().get()
  313. : nullptr;
  314. state->waitingFull = (state->userpicPhotoId != userpicPhotoId)
  315. && ((userpicPhotoId == PeerData::kUnknownPhotoId)
  316. || (userpicPhotoId && userpicPhoto->isNull()));
  317. if (state->waitingFull) {
  318. peer->updateFullForced();
  319. }
  320. const auto oldUserpicPhotoId = state->waitingFull
  321. ? state->userpicPhotoId
  322. : std::exchange(state->userpicPhotoId, userpicPhotoId);
  323. const auto changedUserpic = (state->userpicKey
  324. != peer->userpicUniqueKey(state->userpicView));
  325. const auto wasIndex = state->current.index;
  326. const auto wasCount = state->current.count;
  327. const auto wasPhotoId = state->photoId;
  328. ValidatePhotoId(state, oldUserpicPhotoId);
  329. const auto changedInSlice = (state->current.index != wasIndex)
  330. || (state->current.count != wasCount);
  331. const auto changedPhotoId = (state->photoId != wasPhotoId);
  332. const auto photo = (state->photoId == state->userpicPhotoId
  333. && userpicPhoto)
  334. ? userpicPhoto
  335. : (state->photoId
  336. && (state->photoId != PeerData::kUnknownPhotoId)
  337. && changedPhotoId)
  338. ? peer->owner().photo(state->photoId).get()
  339. : state->photoView
  340. ? state->photoView->owner().get()
  341. : nullptr;
  342. state->current.additionalStatus = (!peer->isUser())
  343. ? QString()
  344. : ((state->photoId == userpicPhotoId)
  345. && peer->asUser()->hasPersonalPhoto())
  346. ? tr::lng_profile_photo_by_you(tr::now)
  347. : ((state->current.index == (state->current.count - 1))
  348. && SyncUserFallbackPhotoViewer(peer->asUser()))
  349. ? tr::lng_profile_public_photo(tr::now)
  350. : QString();
  351. state->waitingLoad = false;
  352. if (!changedPhotoId
  353. && (state->current.index > 0 || !changedUserpic)
  354. && !state->photoView
  355. && (!state->current.photo.isNull()
  356. || state->current.videoDocument)) {
  357. return changedInSlice;
  358. } else if (photo && !photo->isNull()) {
  359. ProcessFullPhoto(peer, state, photo);
  360. } else if (state->current.index > 0) {
  361. return changedInSlice;
  362. } else {
  363. ProcessUserpic(peer, state);
  364. }
  365. return true;
  366. }
  367. [[nodiscard]] PreparedShortInfoUserpic UserpicValue(
  368. not_null<PeerData*> peer,
  369. const style::ShortInfoCover &st,
  370. rpl::producer<UserPhotosSlice> slices,
  371. Fn<bool(not_null<UserpicState*>)> customProcess) {
  372. const auto moveRequests = std::make_shared<rpl::event_stream<int>>();
  373. auto move = [=](int shift) {
  374. moveRequests->fire_copy(shift);
  375. };
  376. const auto size = st.size;
  377. const auto radius = st.radius;
  378. auto value = [=](auto consumer) {
  379. auto lifetime = rpl::lifetime();
  380. const auto state = lifetime.make_state<UserpicState>();
  381. state->size = size;
  382. state->roundMask = Images::CornersMask(radius);
  383. const auto push = [=](bool force = false) {
  384. if (customProcess(state) || force) {
  385. consumer.put_next_copy(state->current);
  386. }
  387. };
  388. using UpdateFlag = Data::PeerUpdate::Flag;
  389. peer->session().changes().peerFlagsValue(
  390. peer,
  391. UpdateFlag::Photo | UpdateFlag::FullInfo
  392. ) | rpl::filter([=](const Data::PeerUpdate &update) {
  393. return (update.flags & UpdateFlag::Photo) || state->waitingFull;
  394. }) | rpl::start_with_next([=] {
  395. push();
  396. }, lifetime);
  397. rpl::duplicate(
  398. slices
  399. ) | rpl::start_with_next([=](UserPhotosSlice &&slice) {
  400. state->userSlice = std::move(slice);
  401. push();
  402. }, lifetime);
  403. moveRequests->events(
  404. ) | rpl::filter([=] {
  405. return (state->current.count > 1);
  406. }) | rpl::start_with_next([=](int shift) {
  407. state->current.index = std::clamp(
  408. ((state->current.index + shift + state->current.count)
  409. % state->current.count),
  410. 0,
  411. state->current.count - 1);
  412. push(true);
  413. }, lifetime);
  414. peer->session().downloaderTaskFinished(
  415. ) | rpl::filter([=] {
  416. return state->waitingLoad
  417. && (state->photoView
  418. ? (!!state->photoView->image(Data::PhotoSize::Large))
  419. : (!Ui::PeerUserpicLoading(state->userpicView)));
  420. }) | rpl::start_with_next([=] {
  421. push();
  422. }, lifetime);
  423. return lifetime;
  424. };
  425. return { .value = std::move(value), .move = std::move(move) };
  426. }
  427. object_ptr<Ui::BoxContent> PrepareShortInfoBox(
  428. not_null<PeerData*> peer,
  429. Fn<void()> open,
  430. Fn<bool()> videoPaused,
  431. Fn<void(Ui::Menu::MenuCallback)> menuFiller,
  432. const style::ShortInfoBox *stOverride) {
  433. const auto type = peer->isSelf()
  434. ? PeerShortInfoType::Self
  435. : peer->isUser()
  436. ? PeerShortInfoType::User
  437. : peer->isBroadcast()
  438. ? PeerShortInfoType::Channel
  439. : PeerShortInfoType::Group;
  440. auto userpic = PrepareShortInfoUserpic(peer, st::shortInfoCover);
  441. auto result = Box<PeerShortInfoBox>(
  442. type,
  443. FieldsValue(peer),
  444. StatusValue(peer),
  445. std::move(userpic.value),
  446. std::move(videoPaused),
  447. stOverride);
  448. if (menuFiller) {
  449. result->fillMenuRequests(
  450. ) | rpl::start_with_next([=](Ui::Menu::MenuCallback callback) {
  451. menuFiller(std::move(callback));
  452. }, result->lifetime());
  453. }
  454. result->openRequests(
  455. ) | rpl::start_with_next(open, result->lifetime());
  456. result->moveRequests(
  457. ) | rpl::start_with_next(userpic.move, result->lifetime());
  458. return result;
  459. }
  460. object_ptr<Ui::BoxContent> PrepareShortInfoBox(
  461. not_null<PeerData*> peer,
  462. std::shared_ptr<ChatHelpers::Show> show,
  463. const style::ShortInfoBox *stOverride) {
  464. const auto open = [=] {
  465. if (const auto window = show->resolveWindow()) {
  466. window->showPeerHistory(peer);
  467. }
  468. };
  469. const auto videoIsPaused = [=] {
  470. return show->paused(Window::GifPauseReason::Layer);
  471. };
  472. auto menuFiller = [=](Ui::Menu::MenuCallback addAction) {
  473. const auto peerSeparateId = Window::SeparateId(peer);
  474. const auto window = show->resolveWindow();
  475. if (window && window->windowId() != peerSeparateId) {
  476. addAction(tr::lng_context_new_window(tr::now), [=] {
  477. Ui::PreventDelayedActivation();
  478. window->showInNewWindow(peer);
  479. }, &st::menuIconNewWindow);
  480. }
  481. };
  482. return PrepareShortInfoBox(
  483. peer,
  484. open,
  485. videoIsPaused,
  486. std::move(menuFiller),
  487. stOverride);
  488. }
  489. object_ptr<Ui::BoxContent> PrepareShortInfoBox(
  490. not_null<PeerData*> peer,
  491. not_null<Window::SessionNavigation*> navigation,
  492. const style::ShortInfoBox *stOverride) {
  493. return PrepareShortInfoBox(peer, navigation->uiShow(), stOverride);
  494. }
  495. rpl::producer<QString> PrepareShortInfoStatus(not_null<PeerData*> peer) {
  496. return StatusValue(peer);
  497. }
  498. PreparedShortInfoUserpic PrepareShortInfoUserpic(
  499. not_null<PeerData*> peer,
  500. const style::ShortInfoCover &st) {
  501. auto slices = peer->isUser()
  502. ? UserPhotosReversedViewer(
  503. &peer->session(),
  504. UserPhotosSlice::Key(peerToUser(peer->asUser()->id), PhotoId()),
  505. kOverviewLimit,
  506. kOverviewLimit)
  507. : rpl::never<UserPhotosSlice>();
  508. auto process = [=](not_null<UserpicState*> state) {
  509. return ProcessCurrent(peer, state);
  510. };
  511. return UserpicValue(peer, st, std::move(slices), std::move(process));
  512. }
  513. PreparedShortInfoUserpic PrepareShortInfoFallbackUserpic(
  514. not_null<PeerData*> peer,
  515. const style::ShortInfoCover &st) {
  516. Expects(peer->isUser());
  517. const auto photoId = SyncUserFallbackPhotoViewer(peer->asUser());
  518. auto slices = photoId
  519. ? rpl::single<UserPhotosSlice>(UserPhotosSlice(
  520. Storage::UserPhotosKey(peerToUser(peer->id), *photoId),
  521. std::deque<PhotoId>({ *photoId }),
  522. 1,
  523. 1,
  524. 1))
  525. : (rpl::never<UserPhotosSlice>() | rpl::type_erased());
  526. auto process = [=](not_null<UserpicState*> state) {
  527. if (photoId) {
  528. ProcessFullPhoto(peer, state, peer->owner().photo(*photoId));
  529. return true;
  530. }
  531. return false;
  532. };
  533. return UserpicValue(peer, st, std::move(slices), std::move(process));
  534. }