Start PeerShortInfoBox for small in-box profiles.

This commit is contained in:
John Preston 2021-10-18 17:21:49 +04:00
parent 49b28ac695
commit 61ac7e6c1d
9 changed files with 529 additions and 10 deletions

View File

@ -182,6 +182,10 @@ PRIVATE
boxes/peers/edit_peer_requests_box.h
boxes/peers/edit_peer_type_box.cpp
boxes/peers/edit_peer_type_box.h
boxes/peers/peer_short_info_box.cpp
boxes/peers/peer_short_info_box.h
boxes/peers/prepare_short_info_box.cpp
boxes/peers/prepare_short_info_box.h
boxes/about_box.cpp
boxes/about_box.h
boxes/about_sponsored_box.cpp

View File

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peer_list_controllers.h"
#include "boxes/peers/edit_participants_box.h" // SubscribeToMigration
#include "boxes/peers/edit_peer_invite_link.h" // PrepareRequestedRowStatus
#include "boxes/peers/prepare_short_info_box.h" // PrepareShortInfoBox
#include "history/view/history_view_requests_bar.h" // kRecentRequestsLimit
#include "data/data_peer.h"
#include "data/data_user.h"
@ -354,7 +355,9 @@ void RequestsBoxController::refreshDescription() {
}
void RequestsBoxController::rowClicked(not_null<PeerListRow*> row) {
_navigation->showPeerInfo(row->peer());
_navigation->parentController()->show(PrepareShortInfoBox(
row->peer(),
_navigation));
}
void RequestsBoxController::rowElementClicked(

View File

@ -0,0 +1,94 @@
/*
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/peer_short_info_box.h"
#include "ui/widgets/labels.h"
#include "ui/image/image_prepare.h"
#include "media/streaming/media_streaming_instance.h"
#include "lang/lang_keys.h"
#include "styles/style_layers.h"
#include "styles/style_info.h"
namespace {
} // namespace
PeerShortInfoBox::PeerShortInfoBox(
QWidget*,
PeerShortInfoType type,
rpl::producer<PeerShortInfoFields> fields,
rpl::producer<QString> status,
rpl::producer<PeerShortInfoUserpic> userpic)
: _type(type)
, _fields(std::move(fields))
, _name(this, nameValue(), st::shortInfoName)
, _status(this, std::move(status), st::shortInfoStatus) {
std::move(
userpic
) | rpl::start_with_next([=](PeerShortInfoUserpic &&value) {
applyUserpic(std::move(value));
}, lifetime());
}
PeerShortInfoBox::~PeerShortInfoBox() = default;
rpl::producer<> PeerShortInfoBox::openRequests() const {
return _openRequests.events();
}
void PeerShortInfoBox::prepare() {
addButton(tr::lng_close(), [=] { closeBox(); });
// Perhaps a new lang key should be added for opening a group.
addLeftButton((_type == PeerShortInfoType::User)
? tr::lng_profile_send_message()
: (_type == PeerShortInfoType::Group)
? tr::lng_view_button_group()
: tr::lng_profile_view_channel(), [=] { _openRequests.fire({}); });
setNoContentMargin(true);
setDimensions(st::shortInfoWidth, st::shortInfoWidth);
}
RectParts PeerShortInfoBox::customCornersFilling() {
return RectPart::FullTop;
}
void PeerShortInfoBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
}
void PeerShortInfoBox::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
const auto coverSize = st::shortInfoWidth;
if (_userpicImage.isNull()) {
const auto size = coverSize * style::DevicePixelRatio();
auto image = QImage(size, size, QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::black);
Images::prepareRound(
image,
ImageRoundRadius::Small,
RectPart::TopLeft | RectPart::TopRight);
}
p.drawImage(QRect(0, 0, coverSize, coverSize), _userpicImage);
}
rpl::producer<QString> PeerShortInfoBox::nameValue() const {
return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) {
return fields.name;
}) | rpl::distinct_until_changed();
}
void PeerShortInfoBox::applyUserpic(PeerShortInfoUserpic &&value) {
if (!value.photo.isNull()
&& _userpicImage.cacheKey() != value.photo.cacheKey()) {
_userpicImage = std::move(value.photo);
update();
}
}

View File

@ -0,0 +1,74 @@
/*
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
*/
#pragma once
#include "boxes/abstract_box.h"
namespace Media::Streaming {
class Document;
class Instance;
} // namespace Media::Streaming
enum class PeerShortInfoType {
User,
Group,
Channel,
};
struct PeerShortInfoFields {
QString name;
QString phone;
QString link;
TextWithEntities about;
QString username;
};
struct PeerShortInfoUserpic {
int index = 0;
int count = 0;
QImage photo;
float64 photoLoadingProgress = 0.;
std::shared_ptr<Media::Streaming::Document> videoDocument;
};
class PeerShortInfoBox final : public Ui::BoxContent {
public:
PeerShortInfoBox(
QWidget*,
PeerShortInfoType type,
rpl::producer<PeerShortInfoFields> fields,
rpl::producer<QString> status,
rpl::producer<PeerShortInfoUserpic> userpic);
~PeerShortInfoBox();
[[nodiscard]] rpl::producer<> openRequests() const;
private:
void prepare() override;
RectParts customCornersFilling() override;
void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override;
[[nodiscard]] rpl::producer<QString> nameValue() const;
void applyUserpic(PeerShortInfoUserpic &&value);
const PeerShortInfoType _type = PeerShortInfoType::User;
rpl::variable<PeerShortInfoFields> _fields;
object_ptr<Ui::FlatLabel> _name;
object_ptr<Ui::FlatLabel> _status;
QImage _userpicImage;
std::unique_ptr<Media::Streaming::Instance> _videoInstance;
rpl::event_stream<> _openRequests;
};

View File

@ -0,0 +1,301 @@
/*
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 "boxes/peers/peer_short_info_box.h"
#include "data/data_peer.h"
#include "data/data_photo.h"
#include "data/data_photo_media.h"
#include "data/data_streaming.h"
#include "data/data_file_origin.h"
#include "data/data_user.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "data/data_peer_values.h"
#include "data/data_changes.h"
#include "data/data_session.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "info/profile/info_profile_values.h"
#include "ui/text/format_values.h"
#include "base/unixtime.h"
#include "lang/lang_keys.h"
#include "styles/style_info.h"
namespace {
struct UserpicState {
PeerShortInfoUserpic current;
std::shared_ptr<Data::CloudImageView> userpicView;
std::shared_ptr<Data::PhotoMedia> photoView;
InMemoryKey userpicKey;
PhotoId photoId = 0;
bool waitingFull = false;
bool waitingLoad = false;
};
void GenerateImage(
not_null<UserpicState*> state,
QImage image,
bool blurred = false) {
using namespace Images;
const auto size = st::shortInfoWidth;
const auto factor = style::DevicePixelRatio();
const auto options = Option::Smooth
| Option::RoundedSmall
| Option::RoundedTopLeft
| Option::RoundedTopRight
| (blurred ? Option::Blurred : Option());
state->current.photo = Images::prepare(
std::move(image),
size * factor,
size * factor,
options,
size * factor,
size * factor);
}
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) {
GenerateImage(
state,
peer->generateUserpicImage(
state->userpicView,
st::shortInfoWidth * style::DevicePixelRatio(),
ImageRoundRadius::None),
false);
state->photoView = nullptr;
return;
}
peer->loadUserpic();
state->current.photoLoadingProgress = 0.;
const auto image = state->userpicView->image();
if (!image) {
state->current.photo = QImage();
state->waitingLoad = true;
return;
}
GenerateImage(state, image, true);
state->photoView = nullptr;
}
void ProcessFullPhoto(
not_null<PeerData*> peer,
not_null<UserpicState*> state,
not_null<PhotoData*> photo) {
using PhotoSize = Data::PhotoSize;
const auto video = photo->hasVideo();
const auto origin = peer->userpicPhotoOrigin();
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);
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 {
ProcessUserpic(peer, state);
if (state->current.photo.isNull()) {
if (const auto blurred = view->thumbnailInline()) {
GenerateImage(state, blurred, true);
}
}
}
state->waitingLoad = !video;
state->photoView = view;
state->current.photoLoadingProgress = photo->progress();
}
if (!video) {
return;
}
state->current.videoDocument = peer->owner().streaming().sharedDocument(
photo,
origin);
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::PhoneNumber
| UpdateFlag::Username
| UpdateFlag::About)
) | rpl::map([=] {
const auto user = peer->asUser();
const auto username = peer->userName();
return PeerShortInfoFields{
.name = peer->name,
.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())
};
});
}
[[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));
});
}
[[nodiscard]] rpl::producer<PeerShortInfoUserpic> UserpicValue(
not_null<PeerData*> peer) {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
const auto state = lifetime.make_state<UserpicState>();
const auto push = [=] {
state->waitingLoad = false;
const auto nowPhotoId = peer->userpicPhotoId();
const auto photo = (nowPhotoId
&& (nowPhotoId != PeerData::kUnknownPhotoId)
&& (state->photoId != nowPhotoId || state->photoView))
? peer->owner().photo(nowPhotoId).get()
: nullptr;
state->waitingFull = !(state->photoId == nowPhotoId)
&& ((nowPhotoId == PeerData::kUnknownPhotoId)
|| (nowPhotoId && photo->isNull()));
if (state->waitingFull) {
peer->updateFullForced();
}
const auto oldPhotoId = state->waitingFull
? state->photoId
: std::exchange(state->photoId, nowPhotoId);
const auto changedPhotoId = (state->photoId != oldPhotoId);
const auto changedUserpic = (state->userpicKey
!= peer->userpicUniqueKey(state->userpicView));
if (!changedPhotoId
&& !changedUserpic
&& !state->photoView
&& (!state->current.photo.isNull()
|| state->current.videoDocument)) {
return;
} else if (photo && !photo->isNull()) {
ProcessFullPhoto(peer, state, photo);
} else {
ProcessUserpic(peer, state);
}
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);
peer->session().downloaderTaskFinished(
) | rpl::filter([=] {
return state->waitingLoad
&& (state->photoView
? (!!state->photoView->image(Data::PhotoSize::Large))
: (state->userpicView && state->userpicView->image()));
}) | rpl::start_with_next([=] {
push();
}, lifetime);
return lifetime;
};
}
object_ptr<Ui::BoxContent> PrepareShortInfoBox(
not_null<PeerData*> peer,
Fn<void()> open) {
const auto type = peer->isUser()
? PeerShortInfoType::User
: peer->isBroadcast()
? PeerShortInfoType::Channel
: PeerShortInfoType::Group;
auto result = Box<PeerShortInfoBox>(
type,
FieldsValue(peer),
StatusValue(peer),
UserpicValue(peer));
result->openRequests(
) | rpl::start_with_next(open, result->lifetime());
return result;
}
object_ptr<Ui::BoxContent> PrepareShortInfoBox(
not_null<PeerData*> peer,
not_null<Window::SessionNavigation*> navigation) {
return PrepareShortInfoBox(
peer,
[=] { navigation->showPeerHistory(peer); });
}

View File

@ -0,0 +1,28 @@
/*
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
*/
#pragma once
#include "base/object_ptr.h"
class PeerData;
namespace Ui {
class BoxContent;
} // namespace Ui
namespace Window {
class SessionNavigation;
} // namespace Window
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareShortInfoBox(
not_null<PeerData*> peer,
Fn<void()> open);
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareShortInfoBox(
not_null<PeerData*> peer,
not_null<Window::SessionNavigation*> navigation);

View File

@ -952,3 +952,9 @@ infoAboutGigagroup: FlatLabel(defaultFlatLabel) {
infoScrollDateHideTimeout: historyScrollDateHideTimeout;
infoDateFadeDuration: historyDateFadeDuration;
shortInfoName: FlatLabel(defaultFlatLabel) {
}
shortInfoStatus: FlatLabel(defaultFlatLabel) {
}
shortInfoWidth: 336px;

View File

@ -74,7 +74,7 @@ rpl::producer<TextWithEntities> NameValue(not_null<PeerData*> peer) {
UpdateFlag::Name
) | rpl::map([=] {
return peer->name;
}) | Ui::Text::ToWithEntities();;
}) | Ui::Text::ToWithEntities();
}
rpl::producer<TextWithEntities> PhoneValue(not_null<UserData*> user) {
@ -113,7 +113,9 @@ rpl::producer<TextWithEntities> UsernameValue(not_null<UserData*> user) {
}) | Ui::Text::ToWithEntities();
}
rpl::producer<TextWithEntities> AboutValue(not_null<PeerData*> peer) {
TextWithEntities AboutWithEntities(
not_null<PeerData*> peer,
const QString &value) {
auto flags = TextParseLinks | TextParseMentions;
const auto user = peer->asUser();
const auto isBot = user && user->isBot();
@ -125,15 +127,19 @@ rpl::producer<TextWithEntities> AboutValue(not_null<PeerData*> peer) {
const auto stripExternal = peer->isChat()
|| peer->isMegagroup()
|| (user && !isBot);
auto result = TextWithEntities{ value };
TextUtilities::ParseEntities(result, flags);
if (stripExternal) {
StripExternalLinks(result);
}
return result;
}
rpl::producer<TextWithEntities> AboutValue(not_null<PeerData*> peer) {
return PlainAboutValue(
peer
) | Ui::Text::ToWithEntities(
) | rpl::map([=](TextWithEntities &&text) {
TextUtilities::ParseEntities(text, flags);
if (stripExternal) {
StripExternalLinks(text);
}
return std::move(text);
) | rpl::map([peer](const QString &value) {
return AboutWithEntities(peer, value);
});
}

View File

@ -38,6 +38,9 @@ rpl::producer<TextWithEntities> NameValue(not_null<PeerData*> peer);
rpl::producer<TextWithEntities> PhoneValue(not_null<UserData*> user);
rpl::producer<TextWithEntities> PhoneOrHiddenValue(not_null<UserData*> user);
rpl::producer<TextWithEntities> UsernameValue(not_null<UserData*> user);
[[nodiscard]] TextWithEntities AboutWithEntities(
not_null<PeerData*> peer,
const QString &value);
rpl::producer<TextWithEntities> AboutValue(not_null<PeerData*> peer);
rpl::producer<QString> LinkValue(not_null<PeerData*> peer);
rpl::producer<const ChannelLocation*> LocationValue(