dialogs_stories_content.cpp 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  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 "dialogs/ui/dialogs_stories_content.h"
  8. #include "data/data_changes.h"
  9. #include "data/data_document.h"
  10. #include "data/data_document_media.h"
  11. #include "data/data_file_origin.h"
  12. #include "data/data_photo.h"
  13. #include "data/data_photo_media.h"
  14. #include "data/data_session.h"
  15. #include "data/data_stories.h"
  16. #include "data/data_user.h"
  17. #include "dialogs/ui/dialogs_stories_list.h"
  18. #include "info/stories/info_stories_widget.h"
  19. #include "info/info_controller.h"
  20. #include "info/info_memento.h"
  21. #include "main/main_session.h"
  22. #include "lang/lang_keys.h"
  23. #include "ui/dynamic_image.h"
  24. #include "ui/dynamic_thumbnails.h"
  25. #include "ui/painter.h"
  26. #include "window/window_session_controller.h"
  27. #include "styles/style_menu_icons.h"
  28. namespace Dialogs::Stories {
  29. namespace {
  30. constexpr auto kShownLastCount = 3;
  31. class State final {
  32. public:
  33. State(not_null<Data::Stories*> data, Data::StorySourcesList list);
  34. [[nodiscard]] Content next();
  35. private:
  36. const not_null<Data::Stories*> _data;
  37. const Data::StorySourcesList _list;
  38. base::flat_map<
  39. not_null<PeerData*>,
  40. std::shared_ptr<Ui::DynamicImage>> _userpics;
  41. };
  42. State::State(not_null<Data::Stories*> data, Data::StorySourcesList list)
  43. : _data(data)
  44. , _list(list) {
  45. }
  46. Content State::next() {
  47. const auto &sources = _data->sources(_list);
  48. auto result = Content{ .total = int(sources.size()) };
  49. result.elements.reserve(sources.size());
  50. for (const auto &info : sources) {
  51. const auto source = _data->source(info.id);
  52. Assert(source != nullptr);
  53. auto userpic = std::shared_ptr<Ui::DynamicImage>();
  54. const auto peer = source->peer;
  55. if (const auto i = _userpics.find(peer); i != end(_userpics)) {
  56. userpic = i->second;
  57. } else {
  58. userpic = Ui::MakeUserpicThumbnail(peer, true);
  59. _userpics.emplace(peer, userpic);
  60. }
  61. result.elements.push_back({
  62. .id = uint64(peer->id.value),
  63. .name = peer->shortName(),
  64. .thumbnail = std::move(userpic),
  65. .count = info.count,
  66. .unreadCount = info.unreadCount,
  67. .skipSmall = peer->isSelf() ? 1U : 0U,
  68. });
  69. }
  70. return result;
  71. }
  72. } // namespace
  73. rpl::producer<Content> ContentForSession(
  74. not_null<Main::Session*> session,
  75. Data::StorySourcesList list) {
  76. return [=](auto consumer) {
  77. auto result = rpl::lifetime();
  78. const auto stories = &session->data().stories();
  79. const auto state = result.make_state<State>(stories, list);
  80. rpl::single(
  81. rpl::empty
  82. ) | rpl::then(
  83. stories->sourcesChanged(list)
  84. ) | rpl::start_with_next([=] {
  85. consumer.put_next(state->next());
  86. }, result);
  87. return result;
  88. };
  89. }
  90. rpl::producer<Content> LastForPeer(not_null<PeerData*> peer) {
  91. using namespace rpl::mappers;
  92. const auto stories = &peer->owner().stories();
  93. const auto peerId = peer->id;
  94. return rpl::single(
  95. peerId
  96. ) | rpl::then(
  97. stories->sourceChanged() | rpl::filter(_1 == peerId)
  98. ) | rpl::map([=] {
  99. auto ids = std::vector<StoryId>();
  100. auto readTill = StoryId();
  101. auto total = 0;
  102. if (const auto source = stories->source(peerId)) {
  103. readTill = source->readTill;
  104. total = int(source->ids.size());
  105. ids = ranges::views::all(source->ids)
  106. | ranges::views::reverse
  107. | ranges::views::take(kShownLastCount)
  108. | ranges::views::transform(&Data::StoryIdDates::id)
  109. | ranges::to_vector;
  110. }
  111. return rpl::make_producer<Content>([=](auto consumer) {
  112. auto lifetime = rpl::lifetime();
  113. if (ids.empty()) {
  114. consumer.put_next(Content());
  115. consumer.put_done();
  116. return lifetime;
  117. }
  118. struct State {
  119. Fn<void()> check;
  120. base::has_weak_ptr guard;
  121. int readTill = StoryId();
  122. bool pushed = false;
  123. };
  124. const auto state = lifetime.make_state<State>();
  125. state->readTill = readTill;
  126. state->check = [=] {
  127. if (state->pushed) {
  128. return;
  129. }
  130. auto done = true;
  131. auto resolving = false;
  132. auto result = Content{ .total = total };
  133. for (const auto id : ids) {
  134. const auto storyId = FullStoryId{ peerId, id };
  135. const auto maybe = stories->lookup(storyId);
  136. if (maybe) {
  137. if (!resolving) {
  138. const auto unread = (id > state->readTill);
  139. result.elements.reserve(ids.size());
  140. result.elements.push_back({
  141. .id = uint64(id),
  142. .thumbnail = Ui::MakeStoryThumbnail(*maybe),
  143. .count = 1U,
  144. .unreadCount = unread ? 1U : 0U,
  145. });
  146. if (unread) {
  147. done = false;
  148. }
  149. }
  150. } else if (maybe.error() == Data::NoStory::Unknown) {
  151. resolving = true;
  152. stories->resolve(
  153. storyId,
  154. crl::guard(&state->guard, state->check));
  155. }
  156. }
  157. if (resolving) {
  158. return;
  159. }
  160. state->pushed = true;
  161. consumer.put_next(std::move(result));
  162. if (done) {
  163. consumer.put_done();
  164. }
  165. };
  166. rpl::single(peerId) | rpl::then(
  167. stories->itemsChanged() | rpl::filter(_1 == peerId)
  168. ) | rpl::start_with_next(state->check, lifetime);
  169. stories->session().changes().storyUpdates(
  170. Data::StoryUpdate::Flag::MarkRead
  171. ) | rpl::start_with_next([=](const Data::StoryUpdate &update) {
  172. if (update.story->peer()->id == peerId) {
  173. if (update.story->id() > state->readTill) {
  174. state->readTill = update.story->id();
  175. if (ranges::contains(ids, state->readTill)
  176. || state->readTill > ids.front()) {
  177. state->pushed = false;
  178. state->check();
  179. }
  180. }
  181. }
  182. }, lifetime);
  183. return lifetime;
  184. });
  185. }) | rpl::flatten_latest();
  186. }
  187. void FillSourceMenu(
  188. not_null<Window::SessionController*> controller,
  189. const ShowMenuRequest &request) {
  190. const auto owner = &controller->session().data();
  191. const auto peer = owner->peer(PeerId(request.id));
  192. const auto &add = request.callback;
  193. if (peer->isSelf()) {
  194. add(tr::lng_stories_archive_button(tr::now), [=] {
  195. controller->showSection(Info::Stories::Make(
  196. peer,
  197. Info::Stories::Tab::Archive));
  198. }, &st::menuIconStoriesArchiveSection);
  199. add(tr::lng_stories_my_title(tr::now), [=] {
  200. controller->showSection(Info::Stories::Make(peer));
  201. }, &st::menuIconStoriesSavedSection);
  202. } else {
  203. const auto group = peer->isMegagroup();
  204. const auto channel = peer->isChannel();
  205. const auto showHistoryText = group
  206. ? tr::lng_context_open_group(tr::now)
  207. : channel
  208. ? tr::lng_context_open_channel(tr::now)
  209. : tr::lng_profile_send_message(tr::now);
  210. add(showHistoryText, [=] {
  211. controller->showPeerHistory(peer);
  212. }, channel ? &st::menuIconChannel : &st::menuIconChatBubble);
  213. const auto viewProfileText = group
  214. ? tr::lng_context_view_group(tr::now)
  215. : channel
  216. ? tr::lng_context_view_channel(tr::now)
  217. : tr::lng_context_view_profile(tr::now);
  218. add(viewProfileText, [=] {
  219. controller->showPeerInfo(peer);
  220. }, channel ? &st::menuIconInfo : &st::menuIconProfile);
  221. const auto in = [&](Data::StorySourcesList list) {
  222. return ranges::contains(
  223. owner->stories().sources(list),
  224. peer->id,
  225. &Data::StoriesSourceInfo::id);
  226. };
  227. const auto toggle = [=](bool shown) {
  228. owner->stories().toggleHidden(
  229. peer->id,
  230. !shown,
  231. controller->uiShow());
  232. };
  233. if (in(Data::StorySourcesList::NotHidden)) {
  234. add(tr::lng_stories_archive(tr::now), [=] {
  235. toggle(false);
  236. }, &st::menuIconArchive);
  237. }
  238. if (in(Data::StorySourcesList::Hidden)) {
  239. add(tr::lng_stories_unarchive(tr::now), [=] {
  240. toggle(true);
  241. }, &st::menuIconUnarchive);
  242. }
  243. }
  244. }
  245. } // namespace Dialogs::Stories