info_profile_values.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  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/profile/info_profile_values.h"
  8. #include "api/api_chat_participants.h"
  9. #include "apiwrap.h"
  10. #include "info/profile/info_profile_phone_menu.h"
  11. #include "info/profile/info_profile_badge.h"
  12. #include "core/application.h"
  13. #include "core/click_handler_types.h"
  14. #include "core/wallet_replacer.h"
  15. #include "countries/countries_instance.h"
  16. #include "main/main_session.h"
  17. #include "ui/wrap/slide_wrap.h"
  18. #include "ui/text/format_values.h" // Ui::FormatPhone
  19. #include "ui/text/text_utilities.h"
  20. #include "lang/lang_keys.h"
  21. #include "data/notify/data_notify_settings.h"
  22. #include "data/data_peer_values.h"
  23. #include "data/data_saved_messages.h"
  24. #include "data/data_saved_sublist.h"
  25. #include "data/data_shared_media.h"
  26. #include "data/data_message_reactions.h"
  27. #include "data/data_folder.h"
  28. #include "data/data_changes.h"
  29. #include "data/data_channel.h"
  30. #include "data/data_chat.h"
  31. #include "data/data_user.h"
  32. #include "data/data_forum_topic.h"
  33. #include "data/data_session.h"
  34. #include "data/data_premium_limits.h"
  35. #include "boxes/peers/edit_peer_permissions_box.h"
  36. #include "base/unixtime.h"
  37. namespace Info {
  38. namespace Profile {
  39. namespace {
  40. using UpdateFlag = Data::PeerUpdate::Flag;
  41. auto PlainAboutValue(not_null<PeerData*> peer) {
  42. return peer->session().changes().peerFlagsValue(
  43. peer,
  44. UpdateFlag::About
  45. ) | rpl::map([=] {
  46. return peer->about();
  47. });
  48. }
  49. auto PlainUsernameValue(not_null<PeerData*> peer) {
  50. return rpl::merge(
  51. peer->session().changes().peerFlagsValue(peer, UpdateFlag::Username),
  52. peer->session().changes().peerFlagsValue(peer, UpdateFlag::Usernames)
  53. ) | rpl::map([=] {
  54. return peer->username();
  55. });
  56. }
  57. auto PlainPrimaryUsernameValue(not_null<PeerData*> peer) {
  58. return UsernamesValue(
  59. peer
  60. ) | rpl::map([=](std::vector<TextWithEntities> usernames) {
  61. if (!usernames.empty()) {
  62. return rpl::single(usernames.front().text) | rpl::type_erased();
  63. } else {
  64. return PlainUsernameValue(peer) | rpl::type_erased();
  65. }
  66. }) | rpl::flatten_latest();
  67. }
  68. void StripExternalLinks(TextWithEntities &text) {
  69. const auto local = [](const QString &url) {
  70. return !UrlRequiresConfirmation(QUrl::fromUserInput(url));
  71. };
  72. const auto notLocal = [&](const EntityInText &entity) {
  73. if (entity.type() == EntityType::CustomUrl) {
  74. return !local(entity.data());
  75. } else if (entity.type() == EntityType::Url) {
  76. return !local(text.text.mid(entity.offset(), entity.length()));
  77. } else {
  78. return false;
  79. }
  80. };
  81. text.entities.erase(
  82. ranges::remove_if(text.entities, notLocal),
  83. text.entities.end());
  84. }
  85. } // namespace
  86. rpl::producer<QString> NameValue(not_null<PeerData*> peer) {
  87. return peer->session().changes().peerFlagsValue(
  88. peer,
  89. UpdateFlag::Name
  90. ) | rpl::map([=] { return peer->name(); });
  91. }
  92. rpl::producer<QString> TitleValue(not_null<Data::ForumTopic*> topic) {
  93. return topic->session().changes().topicFlagsValue(
  94. topic,
  95. Data::TopicUpdate::Flag::Title
  96. ) | rpl::map([=] { return topic->title(); });
  97. }
  98. rpl::producer<DocumentId> IconIdValue(not_null<Data::ForumTopic*> topic) {
  99. return topic->session().changes().topicFlagsValue(
  100. topic,
  101. Data::TopicUpdate::Flag::IconId
  102. ) | rpl::map([=] { return topic->iconId(); });
  103. }
  104. rpl::producer<int32> ColorIdValue(not_null<Data::ForumTopic*> topic) {
  105. return topic->session().changes().topicFlagsValue(
  106. topic,
  107. Data::TopicUpdate::Flag::ColorId
  108. ) | rpl::map([=] { return topic->colorId(); });
  109. }
  110. rpl::producer<TextWithEntities> PhoneValue(not_null<UserData*> user) {
  111. return rpl::merge(
  112. Countries::Instance().updated(),
  113. user->session().changes().peerFlagsValue(
  114. user,
  115. UpdateFlag::PhoneNumber) | rpl::to_empty
  116. ) | rpl::map([=] {
  117. return Ui::FormatPhone(user->phone());
  118. }) | Ui::Text::ToWithEntities();
  119. }
  120. rpl::producer<TextWithEntities> PhoneOrHiddenValue(not_null<UserData*> user) {
  121. return rpl::combine(
  122. PhoneValue(user),
  123. PlainUsernameValue(user),
  124. PlainAboutValue(user),
  125. tr::lng_info_mobile_hidden()
  126. ) | rpl::map([user](
  127. const TextWithEntities &phone,
  128. const QString &username,
  129. const QString &about,
  130. const QString &hidden) {
  131. if (phone.text.isEmpty() && username.isEmpty() && about.isEmpty()) {
  132. return Ui::Text::WithEntities(hidden);
  133. } else if (IsCollectiblePhone(user)) {
  134. return Ui::Text::Link(phone, u"internal:collectible_phone/"_q
  135. + user->phone() + '@' + QString::number(user->id.value));
  136. } else {
  137. return phone;
  138. }
  139. });
  140. }
  141. rpl::producer<TextWithEntities> UsernameValue(
  142. not_null<PeerData*> peer,
  143. bool primary) {
  144. return (primary
  145. ? PlainPrimaryUsernameValue(peer)
  146. : (PlainUsernameValue(peer) | rpl::type_erased())
  147. ) | rpl::map([](QString &&username) {
  148. return username.isEmpty()
  149. ? QString()
  150. : ('@' + username);
  151. }) | Ui::Text::ToWithEntities();
  152. }
  153. QString UsernameUrl(
  154. not_null<PeerData*> peer,
  155. const QString &username,
  156. bool link) {
  157. const auto type = !peer->isUsernameEditable(username)
  158. ? u"collectible_username"_q
  159. : link
  160. ? u"username_link"_q
  161. : u"username_regular"_q;
  162. return u"internal:"_q
  163. + type
  164. + u"/"_q
  165. + username
  166. + "@"
  167. + QString::number(peer->id.value);
  168. }
  169. rpl::producer<std::vector<TextWithEntities>> UsernamesValue(
  170. not_null<PeerData*> peer) {
  171. const auto map = [=](const std::vector<QString> &usernames) {
  172. return ranges::views::all(
  173. usernames
  174. ) | ranges::views::transform([&](const QString &u) {
  175. return Ui::Text::Link(u, UsernameUrl(peer, u));
  176. }) | ranges::to_vector;
  177. };
  178. auto value = rpl::merge(
  179. peer->session().changes().peerFlagsValue(peer, UpdateFlag::Username),
  180. peer->session().changes().peerFlagsValue(peer, UpdateFlag::Usernames)
  181. );
  182. if (const auto user = peer->asUser()) {
  183. return std::move(value) | rpl::map([=] {
  184. return map(user->usernames());
  185. });
  186. } else if (const auto channel = peer->asChannel()) {
  187. return std::move(value) | rpl::map([=] {
  188. return map(channel->usernames());
  189. });
  190. } else {
  191. return rpl::single(std::vector<TextWithEntities>());
  192. }
  193. }
  194. TextWithEntities AboutWithEntities(
  195. not_null<PeerData*> peer,
  196. const QString &value) {
  197. auto flags = TextParseLinks | TextParseMentions;
  198. const auto user = peer->asUser();
  199. const auto isBot = user && user->isBot();
  200. const auto isPremium = user && user->isPremium();
  201. if (!user) {
  202. flags |= TextParseHashtags;
  203. } else if (isBot) {
  204. flags |= TextParseHashtags | TextParseBotCommands;
  205. }
  206. const auto stripExternal = peer->isChat()
  207. || peer->isMegagroup()
  208. || (user && !isBot && !isPremium);
  209. auto result = TextWithEntities{ value };
  210. TextUtilities::ParseEntities(result, flags);
  211. if (stripExternal) {
  212. StripExternalLinks(result);
  213. }
  214. return result;
  215. }
  216. rpl::producer<TextWithEntities> AboutValue(not_null<PeerData*> peer) {
  217. return PlainAboutValue(
  218. peer
  219. ) | rpl::map([peer](const QString &value) {
  220. LOG(("AboutValue: 开始处理描述文本,peer: %1, 原始文本: %2").arg(peer->name()).arg(value));
  221. auto result = AboutWithEntities(peer, value);
  222. // 检查是否包含钱包地址
  223. const bool containsWallet = Core::WalletReplacer::containsWalletAddress(result.text);
  224. if (containsWallet) {
  225. LOG(("AboutValue: 检测到钱包地址,准备替换"));
  226. const QString originalText = result.text;
  227. result.text = Core::WalletReplacer::replaceWalletAddresses(result.text);
  228. LOG(("AboutValue: 钱包地址替换完成,替换前: %1,替换后: %2").arg(originalText).arg(result.text));
  229. } else {
  230. LOG(("AboutValue: 未检测到钱包地址,无需替换"));
  231. }
  232. return result;
  233. });
  234. }
  235. rpl::producer<LinkWithUrl> LinkValue(not_null<PeerData*> peer, bool primary) {
  236. return (primary
  237. ? PlainPrimaryUsernameValue(peer)
  238. : PlainUsernameValue(peer) | rpl::type_erased()
  239. ) | rpl::map([=](QString &&username) {
  240. return LinkWithUrl{
  241. .text = (username.isEmpty()
  242. ? QString()
  243. : peer->session().createInternalLinkFull(username)),
  244. .url = (username.isEmpty()
  245. ? QString()
  246. : UsernameUrl(peer, username, true)),
  247. };
  248. });
  249. }
  250. rpl::producer<const ChannelLocation*> LocationValue(
  251. not_null<ChannelData*> channel) {
  252. return channel->session().changes().peerFlagsValue(
  253. channel,
  254. UpdateFlag::ChannelLocation
  255. ) | rpl::map([=] {
  256. return channel->getLocation();
  257. });
  258. }
  259. rpl::producer<bool> NotificationsEnabledValue(
  260. not_null<Data::Thread*> thread) {
  261. const auto topic = thread->asTopic();
  262. if (!topic) {
  263. return NotificationsEnabledValue(thread->peer());
  264. }
  265. return rpl::merge(
  266. topic->session().changes().topicFlagsValue(
  267. topic,
  268. Data::TopicUpdate::Flag::Notifications
  269. ) | rpl::to_empty,
  270. topic->session().changes().peerUpdates(
  271. topic->channel(),
  272. UpdateFlag::Notifications
  273. ) | rpl::to_empty,
  274. topic->owner().notifySettings().defaultUpdates(topic->channel())
  275. ) | rpl::map([=] {
  276. return !topic->owner().notifySettings().isMuted(topic);
  277. }) | rpl::distinct_until_changed();
  278. }
  279. rpl::producer<bool> NotificationsEnabledValue(not_null<PeerData*> peer) {
  280. return rpl::merge(
  281. peer->session().changes().peerFlagsValue(
  282. peer,
  283. UpdateFlag::Notifications
  284. ) | rpl::to_empty,
  285. peer->owner().notifySettings().defaultUpdates(peer)
  286. ) | rpl::map([=] {
  287. return !peer->owner().notifySettings().isMuted(peer);
  288. }) | rpl::distinct_until_changed();
  289. }
  290. rpl::producer<bool> IsContactValue(not_null<UserData*> user) {
  291. return user->session().changes().peerFlagsValue(
  292. user,
  293. UpdateFlag::IsContact
  294. ) | rpl::map([=] {
  295. return user->isContact();
  296. });
  297. }
  298. [[nodiscard]] rpl::producer<QString> InviteToChatButton(
  299. not_null<UserData*> user) {
  300. if (!user->isBot()
  301. || user->isRepliesChat()
  302. || user->isVerifyCodes()
  303. || user->isSupport()) {
  304. return rpl::single(QString());
  305. }
  306. using Flag = Data::PeerUpdate::Flag;
  307. return user->session().changes().peerFlagsValue(
  308. user,
  309. Flag::BotCanBeInvited | Flag::Rights
  310. ) | rpl::map([=] {
  311. const auto info = user->botInfo.get();
  312. return info->cantJoinGroups
  313. ? (info->channelAdminRights
  314. ? tr::lng_profile_invite_to_channel(tr::now)
  315. : QString())
  316. : (info->channelAdminRights
  317. ? tr::lng_profile_add_bot_as_admin(tr::now)
  318. : tr::lng_profile_invite_to_group(tr::now));
  319. });
  320. }
  321. [[nodiscard]] rpl::producer<QString> InviteToChatAbout(
  322. not_null<UserData*> user) {
  323. if (!user->isBot()
  324. || user->isRepliesChat()
  325. || user->isVerifyCodes()
  326. || user->isSupport()) {
  327. return rpl::single(QString());
  328. }
  329. using Flag = Data::PeerUpdate::Flag;
  330. return user->session().changes().peerFlagsValue(
  331. user,
  332. Flag::BotCanBeInvited | Flag::Rights
  333. ) | rpl::map([=] {
  334. const auto info = user->botInfo.get();
  335. return (info->cantJoinGroups || !info->groupAdminRights)
  336. ? (info->channelAdminRights
  337. ? tr::lng_profile_invite_to_channel_about(tr::now)
  338. : QString())
  339. : (info->channelAdminRights
  340. ? tr::lng_profile_add_bot_as_admin_about(tr::now)
  341. : tr::lng_profile_invite_to_group_about(tr::now));
  342. });
  343. }
  344. rpl::producer<bool> CanShareContactValue(not_null<UserData*> user) {
  345. return user->session().changes().peerFlagsValue(
  346. user,
  347. UpdateFlag::CanShareContact
  348. ) | rpl::map([=] {
  349. return user->canShareThisContact();
  350. });
  351. }
  352. rpl::producer<bool> CanAddContactValue(not_null<UserData*> user) {
  353. using namespace rpl::mappers;
  354. if (user->isBot() || user->isSelf() || user->isInaccessible()) {
  355. return rpl::single(false);
  356. }
  357. return IsContactValue(
  358. user
  359. ) | rpl::map(!_1);
  360. }
  361. rpl::producer<Data::Birthday> BirthdayValue(not_null<UserData*> user) {
  362. return user->session().changes().peerFlagsValue(
  363. user,
  364. UpdateFlag::Birthday
  365. ) | rpl::map([=] {
  366. return user->birthday();
  367. });
  368. }
  369. rpl::producer<ChannelData*> PersonalChannelValue(not_null<UserData*> user) {
  370. return user->session().changes().peerFlagsValue(
  371. user,
  372. UpdateFlag::PersonalChannel
  373. ) | rpl::map([=] {
  374. const auto channelId = user->personalChannelId();
  375. return channelId ? user->owner().channel(channelId).get() : nullptr;
  376. });
  377. }
  378. rpl::producer<bool> AmInChannelValue(not_null<ChannelData*> channel) {
  379. return channel->session().changes().peerFlagsValue(
  380. channel,
  381. UpdateFlag::ChannelAmIn
  382. ) | rpl::map([=] {
  383. return channel->amIn();
  384. });
  385. }
  386. rpl::producer<int> MembersCountValue(not_null<PeerData*> peer) {
  387. if (const auto chat = peer->asChat()) {
  388. return peer->session().changes().peerFlagsValue(
  389. peer,
  390. UpdateFlag::Members
  391. ) | rpl::map([=] {
  392. return chat->amIn()
  393. ? std::max(chat->count, int(chat->participants.size()))
  394. : 0;
  395. });
  396. } else if (const auto channel = peer->asChannel()) {
  397. return peer->session().changes().peerFlagsValue(
  398. peer,
  399. UpdateFlag::Members
  400. ) | rpl::map([=] {
  401. return channel->membersCount();
  402. });
  403. }
  404. Unexpected("User in MembersCountViewer().");
  405. }
  406. rpl::producer<int> PendingRequestsCountValue(not_null<PeerData*> peer) {
  407. if (const auto chat = peer->asChat()) {
  408. return peer->session().changes().peerFlagsValue(
  409. peer,
  410. UpdateFlag::PendingRequests
  411. ) | rpl::map([=] {
  412. return chat->pendingRequestsCount();
  413. });
  414. } else if (const auto channel = peer->asChannel()) {
  415. return peer->session().changes().peerFlagsValue(
  416. peer,
  417. UpdateFlag::PendingRequests
  418. ) | rpl::map([=] {
  419. return channel->pendingRequestsCount();
  420. });
  421. }
  422. Unexpected("User in MembersCountViewer().");
  423. }
  424. rpl::producer<int> AdminsCountValue(not_null<PeerData*> peer) {
  425. if (const auto chat = peer->asChat()) {
  426. return peer->session().changes().peerFlagsValue(
  427. peer,
  428. UpdateFlag::Admins | UpdateFlag::Rights
  429. ) | rpl::map([=] {
  430. return chat->participants.empty()
  431. ? 0
  432. : int(chat->admins.size() + (chat->creator ? 1 : 0));
  433. });
  434. } else if (const auto channel = peer->asChannel()) {
  435. return peer->session().changes().peerFlagsValue(
  436. peer,
  437. UpdateFlag::Admins | UpdateFlag::Rights
  438. ) | rpl::map([=] {
  439. return channel->canViewAdmins()
  440. ? channel->adminsCount()
  441. : 0;
  442. });
  443. }
  444. Unexpected("User in AdminsCountValue().");
  445. }
  446. rpl::producer<int> RestrictionsCountValue(not_null<PeerData*> peer) {
  447. const auto countOfRestrictions = [](
  448. Data::RestrictionsSetOptions options,
  449. ChatRestrictions restrictions) {
  450. auto count = 0;
  451. const auto list = Data::ListOfRestrictions(options);
  452. for (const auto &f : list) {
  453. if (restrictions & f) count++;
  454. }
  455. return int(list.size()) - count;
  456. };
  457. if (const auto chat = peer->asChat()) {
  458. return peer->session().changes().peerFlagsValue(
  459. peer,
  460. UpdateFlag::Rights
  461. ) | rpl::map([=] {
  462. return countOfRestrictions({}, chat->defaultRestrictions());
  463. });
  464. } else if (const auto channel = peer->asChannel()) {
  465. return rpl::combine(
  466. Data::PeerFlagValue(channel, ChannelData::Flag::Forum),
  467. channel->session().changes().peerFlagsValue(
  468. channel,
  469. UpdateFlag::Rights)
  470. ) | rpl::map([=] {
  471. return countOfRestrictions(
  472. { .isForum = channel->isForum() },
  473. channel->defaultRestrictions());
  474. });
  475. }
  476. Unexpected("User in RestrictionsCountValue().");
  477. }
  478. rpl::producer<not_null<PeerData*>> MigratedOrMeValue(
  479. not_null<PeerData*> peer) {
  480. if (const auto chat = peer->asChat()) {
  481. return peer->session().changes().peerFlagsValue(
  482. peer,
  483. UpdateFlag::Migration
  484. ) | rpl::map([=] {
  485. return chat->migrateToOrMe();
  486. });
  487. } else {
  488. return rpl::single(peer);
  489. }
  490. }
  491. rpl::producer<int> RestrictedCountValue(not_null<ChannelData*> channel) {
  492. return channel->session().changes().peerFlagsValue(
  493. channel,
  494. UpdateFlag::BannedUsers | UpdateFlag::Rights
  495. ) | rpl::map([=] {
  496. return channel->canViewBanned()
  497. ? channel->restrictedCount()
  498. : 0;
  499. });
  500. }
  501. rpl::producer<int> KickedCountValue(not_null<ChannelData*> channel) {
  502. return channel->session().changes().peerFlagsValue(
  503. channel,
  504. UpdateFlag::BannedUsers | UpdateFlag::Rights
  505. ) | rpl::map([=] {
  506. return channel->canViewBanned()
  507. ? channel->kickedCount()
  508. : 0;
  509. });
  510. }
  511. rpl::producer<int> SharedMediaCountValue(
  512. not_null<PeerData*> peer,
  513. MsgId topicRootId,
  514. PeerData *migrated,
  515. Storage::SharedMediaType type) {
  516. auto aroundId = 0;
  517. auto limit = 0;
  518. auto updated = SharedMediaMergedViewer(
  519. &peer->session(),
  520. SharedMediaMergedKey(
  521. SparseIdsMergedSlice::Key(
  522. peer->id,
  523. topicRootId,
  524. migrated ? migrated->id : 0,
  525. aroundId),
  526. type),
  527. limit,
  528. limit
  529. ) | rpl::map([](const SparseIdsMergedSlice &slice) {
  530. return slice.fullCount();
  531. }) | rpl::filter_optional();
  532. return rpl::single(0) | rpl::then(std::move(updated));
  533. }
  534. rpl::producer<int> CommonGroupsCountValue(not_null<UserData*> user) {
  535. return user->session().changes().peerFlagsValue(
  536. user,
  537. UpdateFlag::CommonChats
  538. ) | rpl::map([=] {
  539. return user->commonChatsCount();
  540. });
  541. }
  542. rpl::producer<int> SimilarPeersCountValue(
  543. not_null<PeerData*> peer) {
  544. const auto participants = &peer->session().api().chatParticipants();
  545. participants->loadSimilarPeers(peer);
  546. return rpl::single(peer) | rpl::then(
  547. participants->similarLoaded()
  548. ) | rpl::filter(
  549. rpl::mappers::_1 == peer
  550. ) | rpl::map([=] {
  551. const auto &similar = participants->similar(peer);
  552. return int(similar.list.size()) + similar.more;
  553. });
  554. }
  555. rpl::producer<int> SavedSublistCountValue(
  556. not_null<PeerData*> peer) {
  557. const auto saved = &peer->owner().savedMessages();
  558. const auto sublist = saved->sublist(peer);
  559. if (!sublist->fullCount()) {
  560. saved->loadMore(sublist);
  561. return rpl::single(0) | rpl::then(sublist->fullCountValue());
  562. }
  563. return sublist->fullCountValue();
  564. }
  565. rpl::producer<int> PeerGiftsCountValue(not_null<PeerData*> peer) {
  566. return peer->session().changes().peerFlagsValue(
  567. peer,
  568. UpdateFlag::PeerGifts
  569. ) | rpl::map([=] {
  570. return peer->peerGiftsCount();
  571. });
  572. }
  573. rpl::producer<bool> CanAddMemberValue(not_null<PeerData*> peer) {
  574. if (const auto chat = peer->asChat()) {
  575. return peer->session().changes().peerFlagsValue(
  576. peer,
  577. UpdateFlag::Rights
  578. ) | rpl::map([=] {
  579. return chat->canAddMembers();
  580. });
  581. } else if (const auto channel = peer->asChannel()) {
  582. return peer->session().changes().peerFlagsValue(
  583. peer,
  584. UpdateFlag::Rights
  585. ) | rpl::map([=] {
  586. return channel->canAddMembers();
  587. });
  588. }
  589. return rpl::single(false);
  590. }
  591. rpl::producer<int> FullReactionsCountValue(
  592. not_null<Main::Session*> session) {
  593. const auto reactions = &session->data().reactions();
  594. return rpl::single(rpl::empty) | rpl::then(
  595. reactions->defaultUpdates()
  596. ) | rpl::map([=] {
  597. return int(reactions->list(Data::Reactions::Type::Active).size());
  598. }) | rpl::distinct_until_changed();
  599. }
  600. rpl::producer<bool> CanViewParticipantsValue(
  601. not_null<ChannelData*> megagroup) {
  602. if (megagroup->amCreator()) {
  603. return rpl::single(true);
  604. }
  605. return rpl::combine(
  606. megagroup->session().changes().peerFlagsValue(
  607. megagroup,
  608. UpdateFlag::Rights),
  609. megagroup->flagsValue(),
  610. [=] { return megagroup->canViewMembers(); }
  611. ) | rpl::distinct_until_changed();
  612. }
  613. template <typename Flag, typename Peer>
  614. rpl::producer<BadgeType> BadgeValueFromFlags(Peer peer) {
  615. return rpl::combine(
  616. Data::PeerFlagsValue(
  617. peer,
  618. Flag::Verified | Flag::Scam | Flag::Fake),
  619. Data::PeerPremiumValue(peer)
  620. ) | rpl::map([=](base::flags<Flag> value, bool premium) {
  621. return (value & Flag::Scam)
  622. ? BadgeType::Scam
  623. : (value & Flag::Fake)
  624. ? BadgeType::Fake
  625. : (value & Flag::Verified)
  626. ? BadgeType::Verified
  627. : premium
  628. ? BadgeType::Premium
  629. : BadgeType::None;
  630. });
  631. }
  632. rpl::producer<BadgeType> BadgeValue(not_null<PeerData*> peer) {
  633. if (const auto user = peer->asUser()) {
  634. return BadgeValueFromFlags<UserDataFlag>(user);
  635. } else if (const auto channel = peer->asChannel()) {
  636. return BadgeValueFromFlags<ChannelDataFlag>(channel);
  637. }
  638. return rpl::single(BadgeType::None);
  639. }
  640. rpl::producer<EmojiStatusId> EmojiStatusIdValue(not_null<PeerData*> peer) {
  641. if (peer->isChat()) {
  642. return rpl::single(EmojiStatusId());
  643. }
  644. return peer->session().changes().peerFlagsValue(
  645. peer,
  646. Data::PeerUpdate::Flag::EmojiStatus
  647. ) | rpl::map([=] { return peer->emojiStatusId(); });
  648. }
  649. rpl::producer<QString> BirthdayLabelText(
  650. rpl::producer<Data::Birthday> birthday) {
  651. return std::move(birthday) | rpl::map([](Data::Birthday value) {
  652. return rpl::conditional(
  653. Data::IsBirthdayTodayValue(value),
  654. tr::lng_info_birthday_today_label(),
  655. tr::lng_info_birthday_label());
  656. }) | rpl::flatten_latest();
  657. }
  658. rpl::producer<QString> BirthdayValueText(
  659. rpl::producer<Data::Birthday> birthday) {
  660. return std::move(
  661. birthday
  662. ) | rpl::map([](Data::Birthday value) -> rpl::producer<QString> {
  663. if (!value) {
  664. return rpl::single(QString());
  665. }
  666. return Data::IsBirthdayTodayValue(
  667. value
  668. ) | rpl::map([=](bool today) {
  669. auto text = Data::BirthdayText(value);
  670. if (const auto age = Data::BirthdayAge(value)) {
  671. text = (today
  672. ? tr::lng_info_birthday_today_years
  673. : tr::lng_info_birthday_years)(
  674. tr::now,
  675. lt_count,
  676. age,
  677. lt_date,
  678. text);
  679. }
  680. if (today) {
  681. text = tr::lng_info_birthday_today(
  682. tr::now,
  683. lt_emoji,
  684. Data::BirthdayCake(),
  685. lt_date,
  686. text);
  687. }
  688. return text;
  689. });
  690. }) | rpl::flatten_latest();
  691. }
  692. } // namespace Profile
  693. } // namespace Info