info_media_provider.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  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 "info/media/info_media_provider.h"
  8. #include "info/media/info_media_widget.h"
  9. #include "info/media/info_media_list_section.h"
  10. #include "info/info_controller.h"
  11. #include "layout/layout_selection.h"
  12. #include "main/main_session.h"
  13. #include "lang/lang_keys.h"
  14. #include "history/history.h"
  15. #include "history/history_item.h"
  16. #include "history/history_item_helpers.h"
  17. #include "data/data_session.h"
  18. #include "data/data_chat.h"
  19. #include "data/data_channel.h"
  20. #include "data/data_forum_topic.h"
  21. #include "data/data_user.h"
  22. #include "data/data_peer_values.h"
  23. #include "data/data_document.h"
  24. #include "styles/style_info.h"
  25. #include "styles/style_overview.h"
  26. namespace Info::Media {
  27. namespace {
  28. constexpr auto kPreloadedScreensCount = 4;
  29. constexpr auto kPreloadedScreensCountFull
  30. = kPreloadedScreensCount + 1 + kPreloadedScreensCount;
  31. } // namespace
  32. Provider::Provider(not_null<AbstractController*> controller)
  33. : _controller(controller)
  34. , _peer(_controller->key().peer())
  35. , _topicRootId(_controller->key().topic()
  36. ? _controller->key().topic()->rootId()
  37. : 0)
  38. , _migrated(_controller->migrated())
  39. , _type(_controller->section().mediaType())
  40. , _slice(sliceKey(_universalAroundId)) {
  41. _controller->session().data().itemRemoved(
  42. ) | rpl::start_with_next([this](auto item) {
  43. itemRemoved(item);
  44. }, _lifetime);
  45. style::PaletteChanged(
  46. ) | rpl::start_with_next([=] {
  47. for (auto &layout : _layouts) {
  48. layout.second.item->invalidateCache();
  49. }
  50. }, _lifetime);
  51. }
  52. Type Provider::type() {
  53. return _type;
  54. }
  55. bool Provider::hasSelectRestriction() {
  56. if (_peer->allowsForwarding()) {
  57. return false;
  58. } else if (const auto chat = _peer->asChat()) {
  59. return !chat->canDeleteMessages();
  60. } else if (const auto channel = _peer->asChannel()) {
  61. return !channel->canDeleteMessages();
  62. }
  63. return true;
  64. }
  65. rpl::producer<bool> Provider::hasSelectRestrictionChanges() {
  66. if (_peer->isUser()) {
  67. return rpl::never<bool>();
  68. }
  69. const auto chat = _peer->asChat();
  70. const auto channel = _peer->asChannel();
  71. auto noForwards = chat
  72. ? Data::PeerFlagValue(chat, ChatDataFlag::NoForwards)
  73. : Data::PeerFlagValue(
  74. channel,
  75. ChannelDataFlag::NoForwards
  76. ) | rpl::type_erased();
  77. auto rights = chat
  78. ? chat->adminRightsValue()
  79. : channel->adminRightsValue();
  80. auto canDelete = std::move(
  81. rights
  82. ) | rpl::map([=] {
  83. return chat
  84. ? chat->canDeleteMessages()
  85. : channel->canDeleteMessages();
  86. });
  87. return rpl::combine(
  88. std::move(noForwards),
  89. std::move(canDelete)
  90. ) | rpl::map([=] {
  91. return hasSelectRestriction();
  92. }) | rpl::distinct_until_changed() | rpl::skip(1);
  93. }
  94. bool Provider::sectionHasFloatingHeader() {
  95. switch (_type) {
  96. case Type::Photo:
  97. case Type::GIF:
  98. case Type::Video:
  99. case Type::RoundFile:
  100. case Type::RoundVoiceFile:
  101. case Type::MusicFile:
  102. return false;
  103. case Type::File:
  104. case Type::Link:
  105. return true;
  106. }
  107. Unexpected("Type in HasFloatingHeader()");
  108. }
  109. QString Provider::sectionTitle(not_null<const BaseLayout*> item) {
  110. switch (_type) {
  111. case Type::Photo:
  112. case Type::GIF:
  113. case Type::Video:
  114. case Type::RoundFile:
  115. case Type::RoundVoiceFile:
  116. case Type::File:
  117. return langMonthFull(item->dateTime().date());
  118. case Type::Link:
  119. return langDayOfMonthFull(item->dateTime().date());
  120. case Type::MusicFile:
  121. return QString();
  122. }
  123. Unexpected("Type in ListSection::setHeader()");
  124. }
  125. bool Provider::sectionItemBelongsHere(
  126. not_null<const BaseLayout*> item,
  127. not_null<const BaseLayout*> previous) {
  128. const auto date = item->dateTime().date();
  129. const auto sectionDate = previous->dateTime().date();
  130. switch (_type) {
  131. case Type::Photo:
  132. case Type::GIF:
  133. case Type::Video:
  134. case Type::RoundFile:
  135. case Type::RoundVoiceFile:
  136. case Type::File:
  137. return date.year() == sectionDate.year()
  138. && date.month() == sectionDate.month();
  139. case Type::Link:
  140. return date == sectionDate;
  141. case Type::MusicFile:
  142. return true;
  143. }
  144. Unexpected("Type in ListSection::belongsHere()");
  145. }
  146. bool Provider::isPossiblyMyItem(not_null<const HistoryItem*> item) {
  147. return isPossiblyMyPeerId(item->history()->peer->id);
  148. }
  149. bool Provider::isPossiblyMyPeerId(PeerId peerId) const {
  150. return (peerId == _peer->id) || (_migrated && peerId == _migrated->id);
  151. }
  152. std::optional<int> Provider::fullCount() {
  153. return _slice.fullCount();
  154. }
  155. void Provider::restart() {
  156. _layouts.clear();
  157. _universalAroundId = kDefaultAroundId;
  158. _idsLimit = kMinimalIdsLimit;
  159. _slice = SparseIdsMergedSlice(sliceKey(_universalAroundId));
  160. refreshViewer();
  161. }
  162. void Provider::checkPreload(
  163. QSize viewport,
  164. not_null<BaseLayout*> topLayout,
  165. not_null<BaseLayout*> bottomLayout,
  166. bool preloadTop,
  167. bool preloadBottom) {
  168. const auto visibleWidth = viewport.width();
  169. const auto visibleHeight = viewport.height();
  170. const auto preloadedHeight = kPreloadedScreensCountFull * visibleHeight;
  171. const auto minItemHeight = MinItemHeight(_type, visibleWidth);
  172. const auto preloadedCount = preloadedHeight / minItemHeight;
  173. const auto preloadIdsLimitMin = (preloadedCount / 2) + 1;
  174. const auto preloadIdsLimit = preloadIdsLimitMin
  175. + (visibleHeight / minItemHeight);
  176. const auto after = _slice.skippedAfter();
  177. const auto topLoaded = after && (*after == 0);
  178. const auto before = _slice.skippedBefore();
  179. const auto bottomLoaded = before && (*before == 0);
  180. const auto minScreenDelta = kPreloadedScreensCount
  181. - kPreloadIfLessThanScreens;
  182. const auto minUniversalIdDelta = (minScreenDelta * visibleHeight)
  183. / minItemHeight;
  184. const auto preloadAroundItem = [&](not_null<BaseLayout*> layout) {
  185. auto preloadRequired = false;
  186. auto universalId = GetUniversalId(layout);
  187. if (!preloadRequired) {
  188. preloadRequired = (_idsLimit < preloadIdsLimitMin);
  189. }
  190. if (!preloadRequired) {
  191. auto delta = _slice.distance(
  192. sliceKey(_universalAroundId),
  193. sliceKey(universalId));
  194. Assert(delta != std::nullopt);
  195. preloadRequired = (qAbs(*delta) >= minUniversalIdDelta);
  196. }
  197. if (preloadRequired) {
  198. _idsLimit = preloadIdsLimit;
  199. _universalAroundId = universalId;
  200. refreshViewer();
  201. }
  202. };
  203. if (preloadTop && !topLoaded) {
  204. preloadAroundItem(topLayout);
  205. } else if (preloadBottom && !bottomLoaded) {
  206. preloadAroundItem(bottomLayout);
  207. }
  208. }
  209. void Provider::refreshViewer() {
  210. _viewerLifetime.destroy();
  211. const auto idForViewer = sliceKey(_universalAroundId).universalId;
  212. _controller->mediaSource(
  213. idForViewer,
  214. _idsLimit,
  215. _idsLimit
  216. ) | rpl::start_with_next([=](SparseIdsMergedSlice &&slice) {
  217. if (!slice.fullCount()) {
  218. // Don't display anything while full count is unknown.
  219. return;
  220. }
  221. _slice = std::move(slice);
  222. if (auto nearest = _slice.nearest(idForViewer)) {
  223. _universalAroundId = GetUniversalId(*nearest);
  224. }
  225. _refreshed.fire({});
  226. }, _viewerLifetime);
  227. }
  228. rpl::producer<> Provider::refreshed() {
  229. return _refreshed.events();
  230. }
  231. std::vector<ListSection> Provider::fillSections(
  232. not_null<Overview::Layout::Delegate*> delegate) {
  233. markLayoutsStale();
  234. const auto guard = gsl::finally([&] { clearStaleLayouts(); });
  235. auto result = std::vector<ListSection>();
  236. auto section = ListSection(_type, sectionDelegate());
  237. auto count = _slice.size();
  238. for (auto i = count; i != 0;) {
  239. auto universalId = GetUniversalId(_slice[--i]);
  240. if (auto layout = getLayout(universalId, delegate)) {
  241. if (!section.addItem(layout)) {
  242. section.finishSection();
  243. result.push_back(std::move(section));
  244. section = ListSection(_type, sectionDelegate());
  245. section.addItem(layout);
  246. }
  247. }
  248. }
  249. if (!section.empty()) {
  250. section.finishSection();
  251. result.push_back(std::move(section));
  252. }
  253. return result;
  254. }
  255. void Provider::markLayoutsStale() {
  256. for (auto &layout : _layouts) {
  257. layout.second.stale = true;
  258. }
  259. }
  260. void Provider::clearStaleLayouts() {
  261. for (auto i = _layouts.begin(); i != _layouts.end();) {
  262. if (i->second.stale) {
  263. _layoutRemoved.fire(i->second.item.get());
  264. i = _layouts.erase(i);
  265. } else {
  266. ++i;
  267. }
  268. }
  269. }
  270. rpl::producer<not_null<BaseLayout*>> Provider::layoutRemoved() {
  271. return _layoutRemoved.events();
  272. }
  273. BaseLayout *Provider::lookupLayout(
  274. const HistoryItem *item) {
  275. const auto i = _layouts.find(GetUniversalId(item));
  276. return (i != _layouts.end()) ? i->second.item.get() : nullptr;
  277. }
  278. bool Provider::isMyItem(not_null<const HistoryItem*> item) {
  279. const auto peer = item->history()->peer;
  280. return (_peer == peer) || (_migrated == peer);
  281. }
  282. bool Provider::isAfter(
  283. not_null<const HistoryItem*> a,
  284. not_null<const HistoryItem*> b) {
  285. return (GetUniversalId(a) < GetUniversalId(b));
  286. }
  287. void Provider::setSearchQuery(QString query) {
  288. Unexpected("Media::Provider::setSearchQuery.");
  289. }
  290. SparseIdsMergedSlice::Key Provider::sliceKey(
  291. UniversalMsgId universalId) const {
  292. using Key = SparseIdsMergedSlice::Key;
  293. if (!_topicRootId && _migrated) {
  294. return Key(_peer->id, _topicRootId, _migrated->id, universalId);
  295. }
  296. if (universalId < 0) {
  297. // Convert back to plain id for non-migrated histories.
  298. universalId = universalId + ServerMaxMsgId;
  299. }
  300. return Key(_peer->id, _topicRootId, 0, universalId);
  301. }
  302. void Provider::itemRemoved(not_null<const HistoryItem*> item) {
  303. const auto id = GetUniversalId(item);
  304. if (const auto i = _layouts.find(id); i != end(_layouts)) {
  305. _layoutRemoved.fire(i->second.item.get());
  306. _layouts.erase(i);
  307. }
  308. }
  309. FullMsgId Provider::computeFullId(
  310. UniversalMsgId universalId) const {
  311. Expects(universalId != 0);
  312. return (universalId > 0)
  313. ? FullMsgId(_peer->id, universalId)
  314. : FullMsgId(
  315. (_migrated ? _migrated : _peer.get())->id,
  316. ServerMaxMsgId + universalId);
  317. }
  318. BaseLayout *Provider::getLayout(
  319. UniversalMsgId universalId,
  320. not_null<Overview::Layout::Delegate*> delegate) {
  321. auto it = _layouts.find(universalId);
  322. if (it == _layouts.end()) {
  323. if (auto layout = createLayout(universalId, delegate, _type)) {
  324. layout->initDimensions();
  325. it = _layouts.emplace(
  326. universalId,
  327. std::move(layout)).first;
  328. } else {
  329. return nullptr;
  330. }
  331. }
  332. it->second.stale = false;
  333. return it->second.item.get();
  334. }
  335. std::unique_ptr<BaseLayout> Provider::createLayout(
  336. UniversalMsgId universalId,
  337. not_null<Overview::Layout::Delegate*> delegate,
  338. Type type) {
  339. const auto item = _controller->session().data().message(
  340. computeFullId(universalId));
  341. if (!item) {
  342. return nullptr;
  343. }
  344. const auto getPhoto = [&]() -> PhotoData* {
  345. if (const auto media = item->media()) {
  346. return media->photo();
  347. }
  348. return nullptr;
  349. };
  350. const auto getFile = [&]() -> DocumentData* {
  351. if (const auto media = item->media()) {
  352. return media->document();
  353. }
  354. return nullptr;
  355. };
  356. const auto &songSt = st::overviewFileLayout;
  357. using namespace Overview::Layout;
  358. const auto options = [&] {
  359. const auto media = item->media();
  360. return MediaOptions{ .spoiler = media && media->hasSpoiler() };
  361. };
  362. switch (type) {
  363. case Type::Photo:
  364. if (const auto photo = getPhoto()) {
  365. return std::make_unique<Photo>(
  366. delegate,
  367. item,
  368. photo,
  369. options());
  370. }
  371. return nullptr;
  372. case Type::GIF:
  373. if (const auto file = getFile()) {
  374. return std::make_unique<Gif>(delegate, item, file);
  375. }
  376. return nullptr;
  377. case Type::Video:
  378. if (const auto file = getFile()) {
  379. return std::make_unique<Video>(delegate, item, file, options());
  380. }
  381. return nullptr;
  382. case Type::File:
  383. if (const auto file = getFile()) {
  384. return std::make_unique<Document>(
  385. delegate,
  386. item,
  387. DocumentFields{ .document = file },
  388. songSt);
  389. }
  390. return nullptr;
  391. case Type::MusicFile:
  392. if (const auto file = getFile()) {
  393. return std::make_unique<Document>(
  394. delegate,
  395. item,
  396. DocumentFields{ .document = file },
  397. songSt);
  398. }
  399. return nullptr;
  400. case Type::RoundVoiceFile:
  401. if (const auto file = getFile()) {
  402. return std::make_unique<Voice>(delegate, item, file, songSt);
  403. }
  404. return nullptr;
  405. case Type::Link:
  406. return std::make_unique<Link>(delegate, item, item->media());
  407. case Type::RoundFile:
  408. return nullptr;
  409. }
  410. Unexpected("Type in ListWidget::createLayout()");
  411. }
  412. ListItemSelectionData Provider::computeSelectionData(
  413. not_null<const HistoryItem*> item,
  414. TextSelection selection) {
  415. auto result = ListItemSelectionData(selection);
  416. result.canDelete = item->canDelete();
  417. result.canForward = item->allowsForward();
  418. return result;
  419. }
  420. bool Provider::allowSaveFileAs(
  421. not_null<const HistoryItem*> item,
  422. not_null<DocumentData*> document) {
  423. return item->allowsForward();
  424. }
  425. QString Provider::showInFolderPath(
  426. not_null<const HistoryItem*> item,
  427. not_null<DocumentData*> document) {
  428. return document->filepath(true);
  429. }
  430. void Provider::applyDragSelection(
  431. ListSelectedMap &selected,
  432. not_null<const HistoryItem*> fromItem,
  433. bool skipFrom,
  434. not_null<const HistoryItem*> tillItem,
  435. bool skipTill) {
  436. const auto fromId = GetUniversalId(fromItem) - (skipFrom ? 1 : 0);
  437. const auto tillId = GetUniversalId(tillItem) - (skipTill ? 0 : 1);
  438. for (auto i = selected.begin(); i != selected.end();) {
  439. const auto itemId = GetUniversalId(i->first);
  440. if (itemId > fromId || itemId <= tillId) {
  441. i = selected.erase(i);
  442. } else {
  443. ++i;
  444. }
  445. }
  446. for (auto &layoutItem : _layouts) {
  447. auto &&universalId = layoutItem.first;
  448. if (universalId <= fromId && universalId > tillId) {
  449. const auto item = layoutItem.second.item->getItem();
  450. ChangeItemSelection(
  451. selected,
  452. item,
  453. computeSelectionData(item, FullSelection));
  454. }
  455. }
  456. }
  457. int64 Provider::scrollTopStatePosition(not_null<HistoryItem*> item) {
  458. return GetUniversalId(item).bare;
  459. }
  460. HistoryItem *Provider::scrollTopStateItem(ListScrollTopState state) {
  461. if (state.item && _slice.indexOf(state.item->fullId())) {
  462. return state.item;
  463. } else if (const auto id = _slice.nearest(state.position)) {
  464. if (const auto item = _controller->session().data().message(*id)) {
  465. return item;
  466. }
  467. }
  468. return state.item;
  469. }
  470. void Provider::saveState(
  471. not_null<Memento*> memento,
  472. ListScrollTopState scrollState) {
  473. if (_universalAroundId != kDefaultAroundId && scrollState.item) {
  474. memento->setAroundId(computeFullId(_universalAroundId));
  475. memento->setIdsLimit(_idsLimit);
  476. memento->setScrollTopItem(scrollState.item->globalId());
  477. memento->setScrollTopItemPosition(scrollState.position);
  478. memento->setScrollTopShift(scrollState.shift);
  479. }
  480. }
  481. void Provider::restoreState(
  482. not_null<Memento*> memento,
  483. Fn<void(ListScrollTopState)> restoreScrollState) {
  484. if (const auto limit = memento->idsLimit()) {
  485. auto wasAroundId = memento->aroundId();
  486. if (isPossiblyMyPeerId(wasAroundId.peer)) {
  487. _idsLimit = limit;
  488. _universalAroundId = GetUniversalId(wasAroundId);
  489. restoreScrollState({
  490. .position = memento->scrollTopItemPosition(),
  491. .item = MessageByGlobalId(memento->scrollTopItem()),
  492. .shift = memento->scrollTopShift(),
  493. });
  494. refreshViewer();
  495. }
  496. }
  497. }
  498. } // namespace Info::Media