| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "boxes/peers/prepare_short_info_box.h"
- #include "base/unixtime.h"
- #include "boxes/peers/peer_short_info_box.h"
- #include "core/application.h"
- #include "data/data_changes.h"
- #include "data/data_channel.h"
- #include "data/data_chat.h"
- #include "data/data_file_origin.h"
- #include "data/data_peer.h"
- #include "data/data_peer_values.h"
- #include "data/data_photo.h"
- #include "data/data_photo_media.h"
- #include "data/data_session.h"
- #include "data/data_streaming.h"
- #include "data/data_user.h"
- #include "data/data_user_photos.h"
- #include "info/profile/info_profile_values.h"
- #include "lang/lang_keys.h"
- #include "main/main_session.h"
- #include "ui/delayed_activation.h" // PreventDelayedActivation
- #include "ui/text/format_values.h"
- #include "ui/widgets/menu/menu_add_action_callback.h"
- #include "window/window_session_controller.h"
- #include "styles/style_info.h"
- #include "styles/style_menu_icons.h"
- namespace {
- constexpr auto kOverviewLimit = 48;
- struct UserpicState {
- PeerShortInfoUserpic current;
- std::optional<UserPhotosSlice> userSlice;
- PhotoId userpicPhotoId = PeerData::kUnknownPhotoId;
- Ui::PeerUserpicView userpicView;
- std::shared_ptr<Data::PhotoMedia> photoView;
- std::vector<std::shared_ptr<Data::PhotoMedia>> photoPreloads;
- InMemoryKey userpicKey;
- PhotoId photoId = PeerData::kUnknownPhotoId;
- std::array<QImage, 4> roundMask;
- int size = 0;
- bool waitingFull = false;
- bool waitingLoad = false;
- };
- void GenerateImage(
- not_null<UserpicState*> state,
- QImage image,
- bool blurred = false) {
- using namespace Images;
- const auto size = state->size;
- const auto ratio = style::DevicePixelRatio();
- const auto options = blurred ? Option::Blur : Option();
- state->current.photo = Images::Round(
- Images::Prepare(
- std::move(image),
- QSize(size, size) * ratio,
- { .options = options, .outer = { size, size } }),
- state->roundMask,
- RectPart::TopLeft | RectPart::TopRight);
- }
- void GenerateImage(
- not_null<UserpicState*> state,
- not_null<Image*> image,
- bool blurred = false) {
- GenerateImage(state, image->original(), blurred);
- }
- void ProcessUserpic(
- not_null<PeerData*> peer,
- not_null<UserpicState*> state) {
- state->current.videoDocument = nullptr;
- state->userpicKey = peer->userpicUniqueKey(state->userpicView);
- if (!state->userpicView.cloud) {
- GenerateImage(
- state,
- PeerData::GenerateUserpicImage(
- peer,
- state->userpicView,
- st::shortInfoWidth * style::DevicePixelRatio(),
- 0),
- false);
- state->current.photoLoadingProgress = 1.;
- state->photoView = nullptr;
- return;
- }
- peer->loadUserpic();
- if (Ui::PeerUserpicLoading(state->userpicView)) {
- state->current.photoLoadingProgress = 0.;
- state->current.photo = QImage();
- state->waitingLoad = true;
- return;
- }
- GenerateImage(state, *state->userpicView.cloud, true);
- state->current.photoLoadingProgress = peer->userpicPhotoId() ? 0. : 1.;
- state->photoView = nullptr;
- }
- void Preload(
- not_null<PeerData*> peer,
- not_null<UserpicState*> state) {
- auto taken = base::take(state->photoPreloads);
- if (state->userSlice && state->userSlice->size() > 0) {
- const auto preload = [&](int index) {
- const auto photo = peer->owner().photo(
- (*state->userSlice)[index]);
- const auto current = (peer->userpicPhotoId() == photo->id);
- const auto origin = current
- ? peer->userpicPhotoOrigin()
- : Data::FileOriginUserPhoto(peerToUser(peer->id), photo->id);
- state->photoPreloads.push_back(photo->createMediaView());
- if (photo->hasVideo()) {
- state->photoPreloads.back()->videoWanted(
- Data::PhotoSize::Large,
- origin);
- } else {
- state->photoPreloads.back()->wanted(
- Data::PhotoSize::Large,
- origin);
- }
- };
- const auto skip = (state->userSlice->size() == state->current.count)
- ? 0
- : 1;
- if (state->current.index - skip > 0) {
- preload(state->current.index - skip - 1);
- } else if (!state->current.index && state->current.count > 1) {
- preload(state->userSlice->size() - 1);
- }
- if (state->current.index - skip + 1 < state->userSlice->size()) {
- preload(state->current.index - skip + 1);
- } else if (!skip && state->current.index > 0) {
- preload(0);
- }
- }
- }
- void ProcessFullPhoto(
- not_null<PeerData*> peer,
- not_null<UserpicState*> state,
- not_null<PhotoData*> photo) {
- using PhotoSize = Data::PhotoSize;
- const auto current = (peer->userpicPhotoId() == photo->id);
- const auto video = photo->hasVideo();
- const auto originCurrent = peer->userpicPhotoOrigin();
- const auto originOther = peer->isUser()
- ? Data::FileOriginUserPhoto(peerToUser(peer->id), photo->id)
- : originCurrent;
- const auto origin = current ? originCurrent : originOther;
- const auto was = base::take(state->current.videoDocument);
- const auto view = photo->createMediaView();
- if (!video) {
- view->wanted(PhotoSize::Large, origin);
- }
- if (const auto image = view->image(PhotoSize::Large)) {
- GenerateImage(state, image);
- Preload(peer, state);
- state->photoView = nullptr;
- state->current.photoLoadingProgress = 1.;
- } else {
- if (const auto thumbnail = view->image(PhotoSize::Thumbnail)) {
- GenerateImage(state, thumbnail, true);
- } else if (const auto small = view->image(PhotoSize::Small)) {
- GenerateImage(state, small, true);
- } else {
- if (current) {
- ProcessUserpic(peer, state);
- }
- if (!current || state->current.photo.isNull()) {
- if (const auto blurred = view->thumbnailInline()) {
- GenerateImage(state, blurred, true);
- } else {
- state->current.photo = QImage();
- }
- }
- }
- state->waitingLoad = !video;
- state->photoView = view;
- state->current.photoLoadingProgress = photo->progress();
- }
- if (!video) {
- return;
- }
- state->current.videoDocument = peer->owner().streaming().sharedDocument(
- photo,
- origin);
- state->current.videoStartPosition = photo->videoStartPosition();
- state->photoView = nullptr;
- state->current.photoLoadingProgress = 1.;
- }
- } // namespace
- [[nodiscard]] rpl::producer<PeerShortInfoFields> FieldsValue(
- not_null<PeerData*> peer) {
- using UpdateFlag = Data::PeerUpdate::Flag;
- return peer->session().changes().peerFlagsValue(
- peer,
- (UpdateFlag::Name
- | UpdateFlag::PersonalChannel
- | UpdateFlag::PhoneNumber
- | UpdateFlag::Username
- | UpdateFlag::About
- | UpdateFlag::Birthday)
- ) | rpl::map([=] {
- const auto user = peer->asUser();
- const auto username = peer->username();
- const auto channelId = user ? user->personalChannelId() : 0;
- const auto channel = channelId
- ? user->owner().channel(channelId).get()
- : nullptr;
- const auto channelUsername = channel
- ? channel->username()
- : QString();
- const auto hasChannel = !channelUsername.isEmpty();
- return PeerShortInfoFields{
- .name = peer->name(),
- .channelName = hasChannel ? channel->name() : QString(),
- .channelLink = (hasChannel
- ? channel->session().createInternalLinkFull(channelUsername)
- : QString()),
- .phone = user ? Ui::FormatPhone(user->phone()) : QString(),
- .link = ((user || username.isEmpty())
- ? QString()
- : peer->session().createInternalLinkFull(username)),
- .about = Info::Profile::AboutWithEntities(peer, peer->about()),
- .username = ((user && !username.isEmpty())
- ? ('@' + username)
- : QString()),
- .birthday = user ? user->birthday() : Data::Birthday(),
- .isBio = (user && !user->isBot()),
- };
- });
- }
- [[nodiscard]] rpl::producer<QString> StatusValue(not_null<PeerData*> peer) {
- if (const auto user = peer->asUser()) {
- const auto now = base::unixtime::now();
- return [=](auto consumer) {
- auto lifetime = rpl::lifetime();
- const auto timer = lifetime.make_state<base::Timer>();
- const auto push = [=] {
- consumer.put_next(Data::OnlineText(user, now));
- timer->callOnce(Data::OnlineChangeTimeout(user, now));
- };
- timer->setCallback(push);
- push();
- return lifetime;
- };
- }
- return peer->session().changes().peerFlagsValue(
- peer,
- Data::PeerUpdate::Flag::Members
- ) | rpl::map([=] {
- const auto chat = peer->asChat();
- const auto channel = peer->asChannel();
- const auto count = std::max({
- chat ? chat->count : channel->membersCount(),
- chat ? int(chat->participants.size()) : 0,
- 0,
- });
- return (chat && !chat->amIn())
- ? tr::lng_chat_status_unaccessible(tr::now)
- : (count > 0)
- ? ((channel && channel->isBroadcast())
- ? tr::lng_chat_status_subscribers(
- tr::now,
- lt_count_decimal,
- count)
- : tr::lng_chat_status_members(
- tr::now,
- lt_count_decimal,
- count))
- : ((channel && channel->isBroadcast())
- ? tr::lng_channel_status(tr::now)
- : tr::lng_group_status(tr::now));
- });
- }
- void ValidatePhotoId(
- not_null<UserpicState*> state,
- PhotoId oldUserpicPhotoId) {
- if (state->userSlice) {
- const auto count = state->userSlice->size();
- const auto hasOld = state->userSlice->indexOf(
- oldUserpicPhotoId).has_value();
- const auto hasNew = state->userSlice->indexOf(
- state->userpicPhotoId).has_value();
- const auto shift = (hasNew ? 0 : 1);
- const auto fullCount = count + shift;
- state->current.count = fullCount;
- if (hasOld && !hasNew && state->current.index + 1 < fullCount) {
- ++state->current.index;
- } else if (!hasOld && hasNew && state->current.index > 0) {
- --state->current.index;
- }
- const auto index = state->current.index;
- if (!index || index >= fullCount) {
- state->current.index = 0;
- state->photoId = state->userpicPhotoId;
- } else {
- state->photoId = (*state->userSlice)[index - shift];
- }
- } else {
- state->photoId = state->userpicPhotoId;
- }
- }
- bool ProcessCurrent(
- not_null<PeerData*> peer,
- not_null<UserpicState*> state) {
- const auto userpicPhotoId = peer->userpicPhotoId();
- const auto userpicPhoto = (userpicPhotoId
- && (userpicPhotoId != PeerData::kUnknownPhotoId)
- && (state->userpicPhotoId != userpicPhotoId))
- ? peer->owner().photo(userpicPhotoId).get()
- : (state->photoId == userpicPhotoId && state->photoView)
- ? state->photoView->owner().get()
- : nullptr;
- state->waitingFull = (state->userpicPhotoId != userpicPhotoId)
- && ((userpicPhotoId == PeerData::kUnknownPhotoId)
- || (userpicPhotoId && userpicPhoto->isNull()));
- if (state->waitingFull) {
- peer->updateFullForced();
- }
- const auto oldUserpicPhotoId = state->waitingFull
- ? state->userpicPhotoId
- : std::exchange(state->userpicPhotoId, userpicPhotoId);
- const auto changedUserpic = (state->userpicKey
- != peer->userpicUniqueKey(state->userpicView));
- const auto wasIndex = state->current.index;
- const auto wasCount = state->current.count;
- const auto wasPhotoId = state->photoId;
- ValidatePhotoId(state, oldUserpicPhotoId);
- const auto changedInSlice = (state->current.index != wasIndex)
- || (state->current.count != wasCount);
- const auto changedPhotoId = (state->photoId != wasPhotoId);
- const auto photo = (state->photoId == state->userpicPhotoId
- && userpicPhoto)
- ? userpicPhoto
- : (state->photoId
- && (state->photoId != PeerData::kUnknownPhotoId)
- && changedPhotoId)
- ? peer->owner().photo(state->photoId).get()
- : state->photoView
- ? state->photoView->owner().get()
- : nullptr;
- state->current.additionalStatus = (!peer->isUser())
- ? QString()
- : ((state->photoId == userpicPhotoId)
- && peer->asUser()->hasPersonalPhoto())
- ? tr::lng_profile_photo_by_you(tr::now)
- : ((state->current.index == (state->current.count - 1))
- && SyncUserFallbackPhotoViewer(peer->asUser()))
- ? tr::lng_profile_public_photo(tr::now)
- : QString();
- state->waitingLoad = false;
- if (!changedPhotoId
- && (state->current.index > 0 || !changedUserpic)
- && !state->photoView
- && (!state->current.photo.isNull()
- || state->current.videoDocument)) {
- return changedInSlice;
- } else if (photo && !photo->isNull()) {
- ProcessFullPhoto(peer, state, photo);
- } else if (state->current.index > 0) {
- return changedInSlice;
- } else {
- ProcessUserpic(peer, state);
- }
- return true;
- }
- [[nodiscard]] PreparedShortInfoUserpic UserpicValue(
- not_null<PeerData*> peer,
- const style::ShortInfoCover &st,
- rpl::producer<UserPhotosSlice> slices,
- Fn<bool(not_null<UserpicState*>)> customProcess) {
- const auto moveRequests = std::make_shared<rpl::event_stream<int>>();
- auto move = [=](int shift) {
- moveRequests->fire_copy(shift);
- };
- const auto size = st.size;
- const auto radius = st.radius;
- auto value = [=](auto consumer) {
- auto lifetime = rpl::lifetime();
- const auto state = lifetime.make_state<UserpicState>();
- state->size = size;
- state->roundMask = Images::CornersMask(radius);
- const auto push = [=](bool force = false) {
- if (customProcess(state) || force) {
- consumer.put_next_copy(state->current);
- }
- };
- using UpdateFlag = Data::PeerUpdate::Flag;
- peer->session().changes().peerFlagsValue(
- peer,
- UpdateFlag::Photo | UpdateFlag::FullInfo
- ) | rpl::filter([=](const Data::PeerUpdate &update) {
- return (update.flags & UpdateFlag::Photo) || state->waitingFull;
- }) | rpl::start_with_next([=] {
- push();
- }, lifetime);
- rpl::duplicate(
- slices
- ) | rpl::start_with_next([=](UserPhotosSlice &&slice) {
- state->userSlice = std::move(slice);
- push();
- }, lifetime);
- moveRequests->events(
- ) | rpl::filter([=] {
- return (state->current.count > 1);
- }) | rpl::start_with_next([=](int shift) {
- state->current.index = std::clamp(
- ((state->current.index + shift + state->current.count)
- % state->current.count),
- 0,
- state->current.count - 1);
- push(true);
- }, lifetime);
- peer->session().downloaderTaskFinished(
- ) | rpl::filter([=] {
- return state->waitingLoad
- && (state->photoView
- ? (!!state->photoView->image(Data::PhotoSize::Large))
- : (!Ui::PeerUserpicLoading(state->userpicView)));
- }) | rpl::start_with_next([=] {
- push();
- }, lifetime);
- return lifetime;
- };
- return { .value = std::move(value), .move = std::move(move) };
- }
- object_ptr<Ui::BoxContent> PrepareShortInfoBox(
- not_null<PeerData*> peer,
- Fn<void()> open,
- Fn<bool()> videoPaused,
- Fn<void(Ui::Menu::MenuCallback)> menuFiller,
- const style::ShortInfoBox *stOverride) {
- const auto type = peer->isSelf()
- ? PeerShortInfoType::Self
- : peer->isUser()
- ? PeerShortInfoType::User
- : peer->isBroadcast()
- ? PeerShortInfoType::Channel
- : PeerShortInfoType::Group;
- auto userpic = PrepareShortInfoUserpic(peer, st::shortInfoCover);
- auto result = Box<PeerShortInfoBox>(
- type,
- FieldsValue(peer),
- StatusValue(peer),
- std::move(userpic.value),
- std::move(videoPaused),
- stOverride);
- if (menuFiller) {
- result->fillMenuRequests(
- ) | rpl::start_with_next([=](Ui::Menu::MenuCallback callback) {
- menuFiller(std::move(callback));
- }, result->lifetime());
- }
- result->openRequests(
- ) | rpl::start_with_next(open, result->lifetime());
- result->moveRequests(
- ) | rpl::start_with_next(userpic.move, result->lifetime());
- return result;
- }
- object_ptr<Ui::BoxContent> PrepareShortInfoBox(
- not_null<PeerData*> peer,
- std::shared_ptr<ChatHelpers::Show> show,
- const style::ShortInfoBox *stOverride) {
- const auto open = [=] {
- if (const auto window = show->resolveWindow()) {
- window->showPeerHistory(peer);
- }
- };
- const auto videoIsPaused = [=] {
- return show->paused(Window::GifPauseReason::Layer);
- };
- auto menuFiller = [=](Ui::Menu::MenuCallback addAction) {
- const auto peerSeparateId = Window::SeparateId(peer);
- const auto window = show->resolveWindow();
- if (window && window->windowId() != peerSeparateId) {
- addAction(tr::lng_context_new_window(tr::now), [=] {
- Ui::PreventDelayedActivation();
- window->showInNewWindow(peer);
- }, &st::menuIconNewWindow);
- }
- };
- return PrepareShortInfoBox(
- peer,
- open,
- videoIsPaused,
- std::move(menuFiller),
- stOverride);
- }
- object_ptr<Ui::BoxContent> PrepareShortInfoBox(
- not_null<PeerData*> peer,
- not_null<Window::SessionNavigation*> navigation,
- const style::ShortInfoBox *stOverride) {
- return PrepareShortInfoBox(peer, navigation->uiShow(), stOverride);
- }
- rpl::producer<QString> PrepareShortInfoStatus(not_null<PeerData*> peer) {
- return StatusValue(peer);
- }
- PreparedShortInfoUserpic PrepareShortInfoUserpic(
- not_null<PeerData*> peer,
- const style::ShortInfoCover &st) {
- auto slices = peer->isUser()
- ? UserPhotosReversedViewer(
- &peer->session(),
- UserPhotosSlice::Key(peerToUser(peer->asUser()->id), PhotoId()),
- kOverviewLimit,
- kOverviewLimit)
- : rpl::never<UserPhotosSlice>();
- auto process = [=](not_null<UserpicState*> state) {
- return ProcessCurrent(peer, state);
- };
- return UserpicValue(peer, st, std::move(slices), std::move(process));
- }
- PreparedShortInfoUserpic PrepareShortInfoFallbackUserpic(
- not_null<PeerData*> peer,
- const style::ShortInfoCover &st) {
- Expects(peer->isUser());
- const auto photoId = SyncUserFallbackPhotoViewer(peer->asUser());
- auto slices = photoId
- ? rpl::single<UserPhotosSlice>(UserPhotosSlice(
- Storage::UserPhotosKey(peerToUser(peer->id), *photoId),
- std::deque<PhotoId>({ *photoId }),
- 1,
- 1,
- 1))
- : (rpl::never<UserPhotosSlice>() | rpl::type_erased());
- auto process = [=](not_null<UserpicState*> state) {
- if (photoId) {
- ProcessFullPhoto(peer, state, peer->owner().photo(*photoId));
- return true;
- }
- return false;
- };
- return UserpicValue(peer, st, std::move(slices), std::move(process));
- }
|