info_stories_provider.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  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/stories/info_stories_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 "data/data_changes.h"
  12. #include "data/data_channel.h"
  13. #include "data/data_document.h"
  14. #include "data/data_media_types.h"
  15. #include "data/data_session.h"
  16. #include "data/data_stories.h"
  17. #include "data/data_stories_ids.h"
  18. #include "main/main_account.h"
  19. #include "main/main_session.h"
  20. #include "history/history_item.h"
  21. #include "history/history_item_helpers.h"
  22. #include "history/history.h"
  23. #include "core/application.h"
  24. #include "storage/storage_shared_media.h"
  25. #include "layout/layout_selection.h"
  26. #include "styles/style_info.h"
  27. namespace Info::Stories {
  28. namespace {
  29. using namespace Media;
  30. constexpr auto kPreloadedScreensCount = 4;
  31. constexpr auto kPreloadedScreensCountFull
  32. = kPreloadedScreensCount + 1 + kPreloadedScreensCount;
  33. [[nodiscard]] int MinStoryHeight(int width) {
  34. auto itemsLeft = st::infoMediaSkip;
  35. auto itemsInRow = (width - itemsLeft)
  36. / (st::infoMediaMinGridSize + st::infoMediaSkip);
  37. return (st::infoMediaMinGridSize + st::infoMediaSkip) / itemsInRow;
  38. }
  39. } // namespace
  40. Provider::Provider(not_null<AbstractController*> controller)
  41. : _controller(controller)
  42. , _peer(controller->key().storiesPeer())
  43. , _history(_peer->owner().history(_peer))
  44. , _tab(controller->key().storiesTab()) {
  45. style::PaletteChanged(
  46. ) | rpl::start_with_next([=] {
  47. for (auto &layout : _layouts) {
  48. layout.second.item->invalidateCache();
  49. }
  50. }, _lifetime);
  51. _peer->session().changes().storyUpdates(
  52. Data::StoryUpdate::Flag::Destroyed
  53. ) | rpl::filter([=](const Data::StoryUpdate &update) {
  54. return update.story->peer() == _peer;
  55. }) | rpl::start_with_next([=](const Data::StoryUpdate &update) {
  56. storyRemoved(update.story);
  57. }, _lifetime);
  58. }
  59. Provider::~Provider() {
  60. clear();
  61. }
  62. Type Provider::type() {
  63. return Type::PhotoVideo;
  64. }
  65. bool Provider::hasSelectRestriction() {
  66. if (const auto channel = _peer->asChannel()) {
  67. return !channel->canEditStories() && !channel->canDeleteStories();
  68. }
  69. return !_peer->isSelf();
  70. }
  71. rpl::producer<bool> Provider::hasSelectRestrictionChanges() {
  72. return rpl::never<bool>();
  73. }
  74. bool Provider::sectionHasFloatingHeader() {
  75. return false;
  76. }
  77. QString Provider::sectionTitle(not_null<const BaseLayout*> item) {
  78. return QString();
  79. }
  80. bool Provider::sectionItemBelongsHere(
  81. not_null<const BaseLayout*> item,
  82. not_null<const BaseLayout*> previous) {
  83. return true;
  84. }
  85. bool Provider::isPossiblyMyItem(not_null<const HistoryItem*> item) {
  86. return true;
  87. }
  88. std::optional<int> Provider::fullCount() {
  89. return _slice.fullCount();
  90. }
  91. void Provider::clear() {
  92. for (const auto &[storyId, _] : _layouts) {
  93. _peer->owner().stories().unregisterPolling(
  94. { _peer->id, storyId },
  95. Data::Stories::Polling::Chat);
  96. }
  97. _layouts.clear();
  98. _aroundId = kDefaultAroundId;
  99. _idsLimit = kMinimalIdsLimit;
  100. _slice = Data::StoriesIdsSlice();
  101. }
  102. void Provider::restart() {
  103. clear();
  104. refreshViewer();
  105. }
  106. void Provider::checkPreload(
  107. QSize viewport,
  108. not_null<BaseLayout*> topLayout,
  109. not_null<BaseLayout*> bottomLayout,
  110. bool preloadTop,
  111. bool preloadBottom) {
  112. const auto visibleWidth = viewport.width();
  113. const auto visibleHeight = viewport.height();
  114. const auto preloadedHeight = kPreloadedScreensCountFull * visibleHeight;
  115. const auto minItemHeight = MinStoryHeight(visibleWidth);
  116. const auto preloadedCount = preloadedHeight / minItemHeight;
  117. const auto preloadIdsLimitMin = (preloadedCount / 2) + 1;
  118. const auto preloadIdsLimit = preloadIdsLimitMin
  119. + (visibleHeight / minItemHeight);
  120. const auto after = _slice.skippedAfter();
  121. const auto topLoaded = after && (*after == 0);
  122. const auto before = _slice.skippedBefore();
  123. const auto bottomLoaded = before && (*before == 0);
  124. const auto minScreenDelta = kPreloadedScreensCount
  125. - kPreloadIfLessThanScreens;
  126. const auto minIdDelta = (minScreenDelta * visibleHeight)
  127. / minItemHeight;
  128. const auto preloadAroundItem = [&](not_null<BaseLayout*> layout) {
  129. auto preloadRequired = false;
  130. const auto id = StoryIdFromMsgId(layout->getItem()->id);
  131. if (!preloadRequired) {
  132. preloadRequired = (_idsLimit < preloadIdsLimitMin);
  133. }
  134. if (!preloadRequired) {
  135. auto delta = _slice.distance(_aroundId, id);
  136. Assert(delta != std::nullopt);
  137. preloadRequired = (qAbs(*delta) >= minIdDelta);
  138. }
  139. if (preloadRequired) {
  140. _idsLimit = preloadIdsLimit;
  141. _aroundId = id;
  142. refreshViewer();
  143. }
  144. };
  145. if (preloadTop && !topLoaded) {
  146. preloadAroundItem(topLayout);
  147. } else if (preloadBottom && !bottomLoaded) {
  148. preloadAroundItem(bottomLayout);
  149. }
  150. }
  151. void Provider::setSearchQuery(QString query) {
  152. }
  153. void Provider::refreshViewer() {
  154. _viewerLifetime.destroy();
  155. const auto idForViewer = _aroundId;
  156. auto ids = (_tab == Tab::Saved)
  157. ? Data::SavedStoriesIds(_peer, idForViewer, _idsLimit)
  158. : Data::ArchiveStoriesIds(_peer, idForViewer, _idsLimit);
  159. std::move(
  160. ids
  161. ) | rpl::start_with_next([=](Data::StoriesIdsSlice &&slice) {
  162. if (!slice.fullCount()) {
  163. // Don't display anything while full count is unknown.
  164. return;
  165. }
  166. _slice = std::move(slice);
  167. auto nearestId = std::optional<StoryId>();
  168. for (auto i = 0; i != _slice.size(); ++i) {
  169. if (!nearestId
  170. || std::abs(*nearestId - idForViewer)
  171. > std::abs(_slice[i] - idForViewer)) {
  172. nearestId = _slice[i];
  173. }
  174. }
  175. if (nearestId) {
  176. _aroundId = *nearestId;
  177. }
  178. //if (const auto nearest = _slice.nearest(idForViewer)) {
  179. // _aroundId = *nearest;
  180. //}
  181. _refreshed.fire({});
  182. }, _viewerLifetime);
  183. }
  184. rpl::producer<> Provider::refreshed() {
  185. return _refreshed.events();
  186. }
  187. std::vector<ListSection> Provider::fillSections(
  188. not_null<Overview::Layout::Delegate*> delegate) {
  189. markLayoutsStale();
  190. const auto guard = gsl::finally([&] { clearStaleLayouts(); });
  191. auto result = std::vector<ListSection>();
  192. auto section = ListSection(Type::PhotoVideo, sectionDelegate());
  193. auto count = _slice.size();
  194. for (auto i = 0; i != count; ++i) {
  195. const auto storyId = _slice[i];
  196. if (const auto layout = getLayout(storyId, delegate)) {
  197. if (!section.addItem(layout)) {
  198. section.finishSection();
  199. result.push_back(std::move(section));
  200. section = ListSection(Type::PhotoVideo, sectionDelegate());
  201. section.addItem(layout);
  202. }
  203. }
  204. }
  205. if (!section.empty()) {
  206. section.finishSection();
  207. result.push_back(std::move(section));
  208. }
  209. return result;
  210. }
  211. void Provider::markLayoutsStale() {
  212. for (auto &layout : _layouts) {
  213. layout.second.stale = true;
  214. }
  215. }
  216. void Provider::clearStaleLayouts() {
  217. for (auto i = _layouts.begin(); i != _layouts.end();) {
  218. if (i->second.stale) {
  219. _peer->owner().stories().unregisterPolling(
  220. { _peer->id, i->first },
  221. Data::Stories::Polling::Chat);
  222. _layoutRemoved.fire(i->second.item.get());
  223. const auto taken = _items.take(i->first);
  224. i = _layouts.erase(i);
  225. } else {
  226. ++i;
  227. }
  228. }
  229. }
  230. rpl::producer<not_null<BaseLayout*>> Provider::layoutRemoved() {
  231. return _layoutRemoved.events();
  232. }
  233. BaseLayout *Provider::lookupLayout(const HistoryItem *item) {
  234. return nullptr;
  235. }
  236. bool Provider::isMyItem(not_null<const HistoryItem*> item) {
  237. return IsStoryMsgId(item->id) && (item->history()->peer == _peer);
  238. }
  239. bool Provider::isAfter(
  240. not_null<const HistoryItem*> a,
  241. not_null<const HistoryItem*> b) {
  242. return (a->id < b->id);
  243. }
  244. void Provider::storyRemoved(not_null<Data::Story*> story) {
  245. Expects(story->peer() == _peer);
  246. if (const auto i = _layouts.find(story->id()); i != end(_layouts)) {
  247. _peer->owner().stories().unregisterPolling(
  248. story,
  249. Data::Stories::Polling::Chat);
  250. _layoutRemoved.fire(i->second.item.get());
  251. _layouts.erase(i);
  252. }
  253. _items.remove(story->id());
  254. }
  255. BaseLayout *Provider::getLayout(
  256. StoryId id,
  257. not_null<Overview::Layout::Delegate*> delegate) {
  258. auto it = _layouts.find(id);
  259. if (it == _layouts.end()) {
  260. if (auto layout = createLayout(id, delegate)) {
  261. layout->initDimensions();
  262. it = _layouts.emplace(id, std::move(layout)).first;
  263. const auto ok = _peer->owner().stories().registerPolling(
  264. { _peer->id, id },
  265. Data::Stories::Polling::Chat);
  266. Assert(ok);
  267. } else {
  268. return nullptr;
  269. }
  270. }
  271. it->second.stale = false;
  272. return it->second.item.get();
  273. }
  274. HistoryItem *Provider::ensureItem(StoryId id) {
  275. const auto i = _items.find(id);
  276. if (i != end(_items)) {
  277. return i->second.get();
  278. }
  279. auto item = _peer->owner().stories().resolveItem({ _peer->id, id });
  280. if (!item) {
  281. return nullptr;
  282. }
  283. return _items.emplace(id, std::move(item)).first->second.get();
  284. }
  285. std::unique_ptr<BaseLayout> Provider::createLayout(
  286. StoryId id,
  287. not_null<Overview::Layout::Delegate*> delegate) {
  288. const auto item = ensureItem(id);
  289. if (!item) {
  290. return nullptr;
  291. }
  292. const auto getPhoto = [&]() -> PhotoData* {
  293. if (const auto media = item->media()) {
  294. return media->photo();
  295. }
  296. return nullptr;
  297. };
  298. const auto getFile = [&]() -> DocumentData* {
  299. if (const auto media = item->media()) {
  300. return media->document();
  301. }
  302. return nullptr;
  303. };
  304. using namespace Overview::Layout;
  305. const auto options = MediaOptions{
  306. .pinned = item->isPinned(),
  307. .story = true,
  308. };
  309. if (const auto photo = getPhoto()) {
  310. return std::make_unique<Photo>(delegate, item, photo, options);
  311. } else if (const auto file = getFile()) {
  312. return std::make_unique<Video>(delegate, item, file, options);
  313. } else {
  314. return std::make_unique<Photo>(
  315. delegate,
  316. item,
  317. Data::MediaStory::LoadingStoryPhoto(&item->history()->owner()),
  318. options);
  319. }
  320. return nullptr;
  321. }
  322. ListItemSelectionData Provider::computeSelectionData(
  323. not_null<const HistoryItem*> item,
  324. TextSelection selection) {
  325. auto result = ListItemSelectionData(selection);
  326. const auto id = item->id;
  327. if (!IsStoryMsgId(id)) {
  328. return result;
  329. }
  330. const auto peer = item->history()->peer;
  331. const auto channel = peer->asChannel();
  332. const auto maybeStory = peer->owner().stories().lookup(
  333. { peer->id, StoryIdFromMsgId(id) });
  334. if (maybeStory) {
  335. const auto story = *maybeStory;
  336. result.canForward = peer->isSelf() && story->canShare();
  337. result.canDelete = story->canDelete();
  338. result.canUnpinStory = story->pinnedToTop();
  339. }
  340. result.canToggleStoryPin = peer->isSelf()
  341. || (channel && channel->canEditStories());
  342. return result;
  343. }
  344. void Provider::applyDragSelection(
  345. ListSelectedMap &selected,
  346. not_null<const HistoryItem*> fromItem,
  347. bool skipFrom,
  348. not_null<const HistoryItem*> tillItem,
  349. bool skipTill) {
  350. const auto fromId = fromItem->id - (skipFrom ? 1 : 0);
  351. const auto tillId = tillItem->id - (skipTill ? 0 : 1);
  352. for (auto i = selected.begin(); i != selected.end();) {
  353. const auto itemId = i->first->id;
  354. if (itemId > fromId || itemId <= tillId) {
  355. i = selected.erase(i);
  356. } else {
  357. ++i;
  358. }
  359. }
  360. for (auto &layoutItem : _layouts) {
  361. const auto storyId = layoutItem.first;
  362. const auto id = StoryIdToMsgId(storyId);
  363. if (id <= fromId && id > tillId) {
  364. const auto i = _items.find(storyId);
  365. Assert(i != end(_items));
  366. const auto item = i->second.get();
  367. ChangeItemSelection(
  368. selected,
  369. item,
  370. computeSelectionData(item, FullSelection));
  371. }
  372. }
  373. }
  374. bool Provider::allowSaveFileAs(
  375. not_null<const HistoryItem*> item,
  376. not_null<DocumentData*> document) {
  377. return false;
  378. }
  379. QString Provider::showInFolderPath(
  380. not_null<const HistoryItem*> item,
  381. not_null<DocumentData*> document) {
  382. return QString();
  383. }
  384. int64 Provider::scrollTopStatePosition(not_null<HistoryItem*> item) {
  385. return StoryIdFromMsgId(item->id);
  386. }
  387. HistoryItem *Provider::scrollTopStateItem(ListScrollTopState state) {
  388. if (state.item && _slice.indexOf(StoryIdFromMsgId(state.item->id))) {
  389. return state.item;
  390. //} else if (const auto id = _slice.nearest(state.position)) {
  391. // const auto full = FullMsgId(_peer->id, StoryIdToMsgId(*id));
  392. // if (const auto item = _controller->session().data().message(full)) {
  393. // return item;
  394. // }
  395. }
  396. auto nearestId = std::optional<StoryId>();
  397. for (auto i = 0; i != _slice.size(); ++i) {
  398. if (!nearestId
  399. || std::abs(*nearestId - state.position)
  400. > std::abs(_slice[i] - state.position)) {
  401. nearestId = _slice[i];
  402. }
  403. }
  404. if (nearestId) {
  405. const auto full = FullMsgId(_peer->id, StoryIdToMsgId(*nearestId));
  406. if (const auto item = _controller->session().data().message(full)) {
  407. return item;
  408. }
  409. }
  410. return state.item;
  411. }
  412. void Provider::saveState(
  413. not_null<Media::Memento*> memento,
  414. ListScrollTopState scrollState) {
  415. if (_aroundId != kDefaultAroundId && scrollState.item) {
  416. memento->setAroundId({ _peer->id, _aroundId });
  417. memento->setIdsLimit(_idsLimit);
  418. memento->setScrollTopItem(scrollState.item->globalId());
  419. memento->setScrollTopItemPosition(scrollState.position);
  420. memento->setScrollTopShift(scrollState.shift);
  421. }
  422. }
  423. void Provider::restoreState(
  424. not_null<Media::Memento*> memento,
  425. Fn<void(ListScrollTopState)> restoreScrollState) {
  426. if (const auto limit = memento->idsLimit()) {
  427. const auto wasAroundId = memento->aroundId();
  428. if (wasAroundId.peer == _peer->id) {
  429. _idsLimit = limit;
  430. _aroundId = StoryIdFromMsgId(wasAroundId.msg);
  431. restoreScrollState({
  432. .position = memento->scrollTopItemPosition(),
  433. .item = MessageByGlobalId(memento->scrollTopItem()),
  434. .shift = memento->scrollTopShift(),
  435. });
  436. refreshViewer();
  437. }
  438. }
  439. }
  440. } // namespace Info::Stories