Start topic profile cover design.

This commit is contained in:
John Preston 2022-10-14 20:53:06 +04:00
parent d4255bbfe4
commit 791addd0ee
10 changed files with 232 additions and 125 deletions

View file

@ -25,13 +25,6 @@ UserpicButton {
uploadIconPosition: point;
}
FeedUserpicButton {
size: size;
innerSize: pixels;
innerPosition: point;
innerPart: UserpicButton;
}
countryRowHeight: 36px;
countryRowNameFont: semiboldFont;
countryRowNameFg: boxTextFg;
@ -69,12 +62,6 @@ defaultUserpicButton: UserpicButton {
uploadIcon: defaultUploadUserpicIcon;
uploadIconPosition: point(-1px, 1px);
}
defaultFeedUserpicButton: FeedUserpicButton {
size: size(76px, 76px);
innerSize: 76px;
innerPosition: point(-1px, -1px);
innerPart: defaultUserpicButton;
}
confirmInviteTitle: FlatLabel(defaultFlatLabel) {
align: align(center);

View file

@ -149,6 +149,8 @@ struct TopicUpdate {
UnreadMentions = (1U << 2),
UnreadReactions = (1U << 3),
Notifications = (1U << 4),
Title = (1U << 5),
Icon = (1U << 6),
LastUsedBit = (1U << 4),
};

View file

@ -443,6 +443,7 @@ void ForumTopic::applyTitle(const QString &title) {
_defaultIcon = QImage();
indexTitleParts();
updateChatListEntry();
session().changes().topicUpdated(this, UpdateFlag::Title);
}
DocumentId ForumTopic::iconId() const {
@ -450,19 +451,21 @@ DocumentId ForumTopic::iconId() const {
}
void ForumTopic::applyIconId(DocumentId iconId) {
if (_iconId != iconId) {
_iconId = iconId;
_icon = iconId
? owner().customEmojiManager().create(
_iconId,
[=] { updateChatListEntry(); },
Data::CustomEmojiManager::SizeTag::Normal)
: nullptr;
if (iconId) {
_defaultIcon = QImage();
}
if (_iconId == iconId) {
return;
}
_iconId = iconId;
_icon = iconId
? owner().customEmojiManager().create(
_iconId,
[=] { updateChatListEntry(); },
Data::CustomEmojiManager::SizeTag::Normal)
: nullptr;
if (iconId) {
_defaultIcon = QImage();
}
updateChatListEntry();
session().changes().topicUpdated(this, UpdateFlag::Icon);
}
int32 ForumTopic::colorId() const {

View file

@ -255,31 +255,24 @@ infoTabs: SettingsSlider(defaultTabsSlider) {
labelTop: 19px;
}
InfoProfileCover {
height: pixels;
photo: UserpicButton;
photoLeft: pixels;
photoTop: pixels;
name: FlatLabel;
nameLeft: pixels;
nameTop: pixels;
status: FlatLabel;
statusLeft: pixels;
statusTop: pixels;
rightSkip: pixels;
}
infoProfilePhotoInnerSize: 72px;
infoProfilePhotoSize: size(
infoProfilePhotoInnerSize,
infoProfilePhotoInnerSize);
infoProfilePhoto: UserpicButton(defaultUserpicButton) {
size: infoProfilePhotoSize;
photoSize: infoProfilePhotoInnerSize;
}
infoFeedProfilePhoto: FeedUserpicButton(defaultFeedUserpicButton) {
size: infoProfilePhotoSize;
innerSize: infoProfilePhotoInnerSize;
innerPart: UserpicButton(defaultUserpicButton) {
size: size(35px, 35px);
photoSize: 35px;
}
}
infoProfilePhotoLeft: 19px;
infoProfilePhotoTop: 18px;
infoProfilePhotoBottom: 18px;
infoProfileStatusLeft: 109px;
infoProfileStatusRight: 20px;
infoProfileStatusTop: 58px;
infoProfileStatusLabel: FlatLabel(defaultFlatLabel) {
infoProfileStatus: FlatLabel(defaultFlatLabel) {
maxHeight: 18px;
textFg: windowSubTextFg;
style: TextStyle(defaultTextStyle) {
@ -288,25 +281,46 @@ infoProfileStatusLabel: FlatLabel(defaultFlatLabel) {
linkFontOver: normalFont;
}
}
infoProfileMegagroupStatusLabel: FlatLabel(infoProfileStatusLabel) {
style: defaultTextStyle;
palette: TextPalette(defaultTextPalette) {
linkFg: windowSubTextFg;
infoProfileCover: InfoProfileCover {
height: 108px;
photo: UserpicButton(defaultUserpicButton) {
size: infoProfilePhotoSize;
photoSize: infoProfilePhotoInnerSize;
}
photoLeft: 19px;
photoTop: 18px;
name: FlatLabel(defaultFlatLabel) {
maxHeight: 24px;
textFg: windowBoldFg;
style: TextStyle(defaultTextStyle) {
font: font(16px semibold);
linkFont: font(16px semibold);
linkFontOver: font(16px semibold underline);
}
}
nameLeft: 109px;
nameTop: 32px;
status: infoProfileStatus;
statusLeft: 109px;
statusTop: 58px;
rightSkip: 20px;
}
infoProfileMegagroupCover: InfoProfileCover(infoProfileCover) {
status: FlatLabel(infoProfileStatus) {
style: defaultTextStyle;
palette: TextPalette(defaultTextPalette) {
linkFg: windowSubTextFg;
}
}
}
infoTopicCover: InfoProfileCover(infoProfileMegagroupCover) {
height: 77px;
nameLeft: 79px;
nameTop: 14px;
statusLeft: 79px;
statusTop: 38px;
}
infoProfileNameLeft: infoProfileStatusLeft;
infoProfileNameRight: infoProfileStatusRight;
infoProfileNameTop: 32px;
infoProfileNameLabel: FlatLabel(infoProfileStatusLabel) {
maxHeight: 24px;
textFg: windowBoldFg;
style: TextStyle(defaultTextStyle) {
font: font(16px semibold);
linkFont: font(16px semibold);
linkFontOver: font(16px semibold underline);
}
}
infoVerifiedCheckPosition: point(4px, 2px);
infoVerifiedCheck: icon {
{ "profile_verified_star", profileVerifiedCheckBg },
@ -392,7 +406,7 @@ infoLabeled: FlatLabel(infoLabeledOneLine) {
margin: margins(5px, 5px, 5px, 5px);
}
infoBlockHeaderLabel: FlatLabel(infoProfileStatusLabel) {
infoBlockHeaderLabel: FlatLabel(infoProfileStatus) {
textFg: windowBoldFg;
style: TextStyle(defaultTextStyle) {
font: semiboldFont;
@ -719,15 +733,6 @@ topBarInfoButton: UserpicButton(defaultUserpicButton) {
photoSize: topBarInfoButtonInnerSize;
photoPosition: topBarInfoButtonInnerPosition;
}
topBarFeedInfoButton: FeedUserpicButton(defaultFeedUserpicButton) {
size: topBarInfoButtonSize;
innerSize: topBarInfoButtonInnerSize;
innerPosition: topBarInfoButtonInnerPosition;
innerPart: UserpicButton(defaultUserpicButton) {
size: size(20px, 20px);
photoSize: 20px;
}
}
topBarConnectingPosition: point(2px, 5px);
topBarConnectingSkip: 6px;
topBarConnectingAnimation: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {

View file

@ -12,6 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat.h"
#include "data/data_peer.h"
#include "data/data_changes.h"
#include "data/data_session.h"
#include "data/data_forum_topic.h"
#include "data/stickers/data_custom_emoji.h"
#include "info/profile/info_profile_values.h"
#include "info/profile/info_profile_badge.h"
#include "info/profile/info_profile_emoji_status_panel.h"
@ -73,16 +76,49 @@ Cover::Cover(
: Cover(parent, peer, controller, NameValue(peer)) {
}
Cover::Cover(
QWidget *parent,
not_null<Data::ForumTopic*> topic,
not_null<Window::SessionController*> controller)
: Cover(
parent,
topic->channel(),
topic,
controller,
TitleValue(topic)) {
}
Cover::Cover(
QWidget *parent,
not_null<PeerData*> peer,
not_null<Window::SessionController*> controller,
rpl::producer<QString> title)
: FixedHeightWidget(
: Cover(
parent,
st::infoProfilePhotoTop
+ st::infoProfilePhoto.size.height()
+ st::infoProfilePhotoBottom)
peer,
nullptr,
controller,
std::move(title)) {
}
[[nodiscard]] const style::InfoProfileCover &CoverStyle(
not_null<PeerData*> peer,
Data::ForumTopic *topic) {
return topic
? st::infoTopicCover
: peer->isMegagroup()
? st::infoProfileMegagroupCover
: st::infoProfileCover;
}
Cover::Cover(
QWidget *parent,
not_null<PeerData*> peer,
Data::ForumTopic *topic,
not_null<Window::SessionController*> controller,
rpl::producer<QString> title)
: FixedHeightWidget(parent, CoverStyle(peer, topic).height)
, _st(CoverStyle(peer, topic))
, _controller(controller)
, _peer(peer)
, _emojiStatusPanel(peer->isSelf()
@ -98,19 +134,22 @@ Cover::Cover(
return controller->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer);
}))
, _userpic(
this,
controller,
_peer,
Ui::UserpicButton::Role::OpenPhoto,
st::infoProfilePhoto)
, _name(this, st::infoProfileNameLabel)
, _status(
this,
_peer->isMegagroup()
? st::infoProfileMegagroupStatusLabel
: st::infoProfileStatusLabel)
, _userpic(topic
? nullptr
: object_ptr<Ui::UserpicButton>(
this,
controller,
_peer,
Ui::UserpicButton::Role::OpenPhoto,
_st.photo))
, _iconView(topic ? object_ptr<Ui::RpWidget>(this) : nullptr)
, _name(this, _st.name)
, _status(this, _st.status)
, _refreshStatusTimer([this] { refreshStatusText(); }) {
if (topic) {
setupIcon(topic);
}
_peer->updateFull();
_name->setSelectable(true);
@ -134,21 +173,56 @@ Cover::Cover(
initViewers(std::move(title));
setupChildGeometry();
_userpic->uploadPhotoRequests(
if (_userpic) {
_userpic->uploadPhotoRequests(
) | rpl::start_with_next([=] {
_peer->session().api().peerPhoto().upload(
_peer,
_userpic->takeResultImage());
}, _userpic->lifetime());
} else {
// #TODO forum icon change on click if possible
}
}
void Cover::setupIcon(not_null<Data::ForumTopic*> topic) {
const auto tag = Data::CustomEmojiManager::SizeTag::Large;
IconIdValue(
topic
) | rpl::start_with_next([=](DocumentId id) {
_icon = id
? topic->owner().customEmojiManager().create(
id,
[=] { _iconView->update(); },
tag)
: nullptr;
}, lifetime());
const auto size = Data::FrameSizeFromTag(tag);
_iconView->resize(size, size);
_iconView->paintRequest(
) | rpl::start_with_next([=] {
_peer->session().api().peerPhoto().upload(
_peer,
_userpic->takeResultImage());
}, _userpic->lifetime());
auto p = QPainter(_iconView.data());
if (_icon) {
_icon->paint(p, {
.preview = st::windowBgOver->c,
.now = crl::now(),
.paused = _controller->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer),
});
} else {
}
}, _iconView->lifetime());
}
void Cover::setupChildGeometry() {
widthValue(
) | rpl::start_with_next([this](int newWidth) {
_userpic->moveToLeft(
st::infoProfilePhotoLeft,
st::infoProfilePhotoTop,
newWidth);
if (_userpic) {
_userpic->moveToLeft(_st.photoLeft, _st.photoTop, newWidth);
} else {
_iconView->moveToLeft(_st.photoLeft, _st.photoTop, newWidth);
}
refreshNameGeometry(newWidth);
refreshStatusGeometry(newWidth);
}, lifetime());
@ -192,6 +266,9 @@ void Cover::initViewers(rpl::producer<QString> title) {
}
void Cover::refreshUploadPhotoOverlay() {
if (!_userpic) {
return;
}
_userpic->switchChangePhotoOverlay([&] {
if (const auto chat = _peer->asChat()) {
return chat->canEditInformation();
@ -255,31 +332,22 @@ Cover::~Cover() {
}
void Cover::refreshNameGeometry(int newWidth) {
auto nameLeft = st::infoProfileNameLeft;
auto nameTop = st::infoProfileNameTop;
auto nameWidth = newWidth
- nameLeft
- st::infoProfileNameRight;
auto nameWidth = newWidth - _st.nameLeft - _st.rightSkip;
if (const auto widget = _badge->widget()) {
nameWidth -= st::infoVerifiedCheckPosition.x() + widget->width();
}
_name->resizeToNaturalWidth(nameWidth);
_name->moveToLeft(nameLeft, nameTop, newWidth);
const auto badgeLeft = nameLeft + _name->width();
const auto badgeTop = nameTop;
const auto badgeBottom = nameTop + _name->height();
_name->moveToLeft(_st.nameLeft, _st.nameTop, newWidth);
const auto badgeLeft = _st.nameLeft + _name->width();
const auto badgeTop = _st.nameTop;
const auto badgeBottom = _st.nameTop + _name->height();
_badge->move(badgeLeft, badgeTop, badgeBottom);
}
void Cover::refreshStatusGeometry(int newWidth) {
auto statusWidth = newWidth
- st::infoProfileStatusLeft
- st::infoProfileStatusRight;
auto statusWidth = newWidth - _st.statusLeft - _st.rightSkip;
_status->resizeToWidth(statusWidth);
_status->moveToLeft(
st::infoProfileStatusLeft,
st::infoProfileStatusTop,
newWidth);
_status->moveToLeft(_st.statusLeft, _st.statusTop, newWidth);
}
} // namespace Info::Profile

View file

@ -25,11 +25,19 @@ namespace Ui::Text {
struct CustomEmojiColored;
} // namespace Ui::Text
namespace Data {
class ForumTopic;
} // namespace Data
namespace Info {
class Controller;
class Section;
} // namespace Info
namespace style {
struct InfoProfileCover;
} // namespace style
namespace Info::Profile {
class EmojiStatusPanel;
@ -41,6 +49,10 @@ public:
QWidget *parent,
not_null<PeerData*> peer,
not_null<Window::SessionController*> controller);
Cover(
QWidget *parent,
not_null<Data::ForumTopic*> topic,
not_null<Window::SessionController*> controller);
Cover(
QWidget *parent,
not_null<PeerData*> peer,
@ -55,6 +67,14 @@ public:
}
private:
Cover(
QWidget *parent,
not_null<PeerData*> peer,
Data::ForumTopic *topic,
not_null<Window::SessionController*> controller,
rpl::producer<QString> title);
void setupIcon(not_null<Data::ForumTopic*> topic);
void setupChildGeometry();
void initViewers(rpl::producer<QString> title);
void refreshStatusText();
@ -62,6 +82,8 @@ private:
void refreshStatusGeometry(int newWidth);
void refreshUploadPhotoOverlay();
const style::InfoProfileCover &_st;
const not_null<Window::SessionController*> _controller;
const not_null<PeerData*> _peer;
const std::unique_ptr<EmojiStatusPanel> _emojiStatusPanel;
@ -69,6 +91,8 @@ private:
int _onlineCount = 0;
object_ptr<Ui::UserpicButton> _userpic;
object_ptr<Ui::RpWidget> _iconView;
std::unique_ptr<Ui::Text::CustomEmoji> _icon;
object_ptr<Ui::FlatLabel> _name = { nullptr };
object_ptr<Ui::FlatLabel> _status = { nullptr };
//object_ptr<CoverDropArea> _dropArea = { nullptr };

View file

@ -66,11 +66,15 @@ InnerWidget::InnerWidget(
object_ptr<Ui::RpWidget> InnerWidget::setupContent(
not_null<RpWidget*> parent) {
auto result = object_ptr<Ui::VerticalLayout>(parent);
_cover = result->add(object_ptr<Cover>(
result,
_peer,
_controller->parentController(),
_topic ? TitleValue(_topic) : NameValue(_peer)));
_cover = _topic
? result->add(object_ptr<Cover>(
result,
_topic,
_controller->parentController()))
: result->add(object_ptr<Cover>(
result,
_peer,
_controller->parentController()));
_cover->showSection(
) | rpl::start_with_next([=](Section section) {
_controller->showSection(_topic

View file

@ -83,7 +83,17 @@ rpl::producer<QString> NameValue(not_null<PeerData*> peer) {
}
rpl::producer<QString> TitleValue(not_null<Data::ForumTopic*> topic) {
return rpl::single(topic->title()); // #TODO forum title changes
return topic->session().changes().topicFlagsValue(
topic,
Data::TopicUpdate::Flag::Title
) | rpl::map([=] { return topic->title(); });
}
rpl::producer<DocumentId> IconIdValue(not_null<Data::ForumTopic*> topic) {
return topic->session().changes().topicFlagsValue(
topic,
Data::TopicUpdate::Flag::Icon
) | rpl::map([=] { return topic->iconId(); });
}
rpl::producer<TextWithEntities> PhoneValue(not_null<UserData*> user) {

View file

@ -47,6 +47,8 @@ rpl::producer<not_null<PeerData*>> MigratedOrMeValue(
[[nodiscard]] rpl::producer<QString> NameValue(not_null<PeerData*> peer);
[[nodiscard]] rpl::producer<QString> TitleValue(
not_null<Data::ForumTopic*> topic);
[[nodiscard]] rpl::producer<DocumentId> IconIdValue(
not_null<Data::ForumTopic*> topic);
[[nodiscard]] rpl::producer<TextWithEntities> PhoneValue(
not_null<UserData*> user);
[[nodiscard]] rpl::producer<TextWithEntities> PhoneOrHiddenValue(

View file

@ -103,7 +103,7 @@ Cover::Cover(
: FixedHeightWidget(
parent,
st::settingsPhotoTop
+ st::infoProfilePhoto.size.height()
+ st::infoProfileCover.photo.size.height()
+ st::settingsPhotoBottom)
, _controller(controller)
, _user(user)
@ -123,10 +123,10 @@ Cover::Cover(
controller,
_user,
Ui::UserpicButton::Role::OpenPhoto,
st::infoProfilePhoto)
, _name(this, st::infoProfileNameLabel)
st::infoProfileCover.photo)
, _name(this, st::infoProfileCover.name)
, _phone(this, st::defaultFlatLabel)
, _username(this, st::infoProfileMegagroupStatusLabel) {
, _username(this, st::infoProfileMegagroupCover.status) {
_user->updateFull();
_name->setSelectable(true);
@ -216,7 +216,7 @@ void Cover::refreshNameGeometry(int newWidth) {
const auto nameTop = st::settingsNameTop;
auto nameWidth = newWidth
- nameLeft
- st::infoProfileNameRight;
- st::infoProfileCover.rightSkip;
if (const auto width = _badge.widget() ? _badge.widget()->width() : 0) {
nameWidth -= st::infoVerifiedCheckPosition.x() + width;
}
@ -231,7 +231,9 @@ void Cover::refreshNameGeometry(int newWidth) {
void Cover::refreshPhoneGeometry(int newWidth) {
const auto phoneLeft = st::settingsPhoneLeft;
const auto phoneTop = st::settingsPhoneTop;
const auto phoneWidth = newWidth - phoneLeft - st::infoProfileNameRight;
const auto phoneWidth = newWidth
- phoneLeft
- st::infoProfileCover.rightSkip;
_phone->resizeToWidth(phoneWidth);
_phone->moveToLeft(phoneLeft, phoneTop, newWidth);
}
@ -239,7 +241,7 @@ void Cover::refreshPhoneGeometry(int newWidth) {
void Cover::refreshUsernameGeometry(int newWidth) {
const auto usernameLeft = st::settingsUsernameLeft;
const auto usernameTop = st::settingsUsernameTop;
const auto usernameRight = st::infoProfileNameRight;
const auto usernameRight = st::infoProfileCover.rightSkip;
const auto usernameWidth = newWidth - usernameLeft - usernameRight;
_username->resizeToWidth(usernameWidth);
_username->moveToLeft(usernameLeft, usernameTop, newWidth);