Files
AyuGramDesktop/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp
T
2026-06-05 10:36:43 +04:00

514 lines
14 KiB
C++

/*
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 "info/profile/info_profile_inner_widget.h"
#include "info/info_controller.h"
#include "info/info_memento.h"
#include "info/profile/info_profile_widget.h"
#include "info/profile/info_profile_icon.h"
#include "info/profile/info_profile_members.h"
#include "info/profile/info_profile_music_button.h"
#include "info/profile/info_profile_top_bar.h"
#include "info/profile/info_profile_actions.h"
#include "info/media/info_media_buttons.h"
#include "info/saved/info_saved_music_widget.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_document.h"
#include "data/data_forum_topic.h"
#include "data/data_peer.h"
#include "data/data_photo.h"
#include "data/data_file_origin.h"
#include "data/data_user.h"
#include "data/data_saved_music.h"
#include "data/data_saved_sublist.h"
#include "info/saved/info_saved_music_common.h"
#include "info_profile_actions.h"
#include "main/main_session.h"
#include "apiwrap.h"
#include "api/api_peer_photo.h"
#include "lang/lang_keys.h"
#include "ui/text/custom_emoji_helper.h"
#include "ui/text/format_song_document_name.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/shadow.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "ui/ui_utility.h"
#include "styles/style_info.h"
namespace Info {
namespace Profile {
namespace {
void AddSavedMusic(
not_null<Ui::VerticalLayout*> layout,
not_null<Controller*> controller,
not_null<PeerData*> peer,
rpl::producer<std::optional<QColor>> topBarColor) {
const auto wrap = layout->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
layout,
object_ptr<Ui::VerticalLayout>(layout)));
Info::Saved::SetupSavedMusic(
wrap->entity(),
controller,
peer,
std::move(topBarColor));
using namespace rpl::mappers;
wrap->toggleOn(
wrap->entity()->heightValue() | rpl::map(_1 > 0),
anim::type::instant);
}
void AddUnofficialSecurityRiskWarning(
not_null<Ui::VerticalLayout*> layout,
not_null<UserData*> user) {
const auto wrap = layout->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
layout,
object_ptr<Ui::VerticalLayout>(layout)));
const auto content = wrap->entity();
user->session().changes().peerFlagsValue(
user,
Data::PeerUpdate::Flag::FullInfo
) | rpl::on_next([=] {
while (content->count()) {
delete content->widgetAt(0);
}
if (user->unofficialSecurityRisk()) {
auto helper = Ui::Text::CustomEmojiHelper();
auto icon = helper.paletteDependent({
.factory = [] {
const auto s = st::infoSecurityRiskIconSize;
const auto ratio = style::DevicePixelRatio();
const auto rect = QRect(0, 0, s, s);
auto result = QImage(
rect.size() * ratio,
QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(ratio);
result.fill(Qt::transparent);
auto p = QPainter(&result);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::attentionButtonFg);
p.drawEllipse(rect);
p.setPen(st::windowFgActive);
p.setFont(st::semiboldFont);
p.drawText(rect, u"!"_q, style::al_center);
p.end();
return result;
},
.margin = st::infoSecurityRiskIconMargin,
});
auto label = object_ptr<Ui::FlatLabel>(
content,
tr::lng_profile_unofficial_warning(
lt_icon,
rpl::single(std::move(icon)),
lt_name,
rpl::single(TextWithEntities{ user->firstName }),
tr::marked),
st::defaultDividerLabel.label,
st::defaultPopupMenu,
helper.context([=] { content->update(); }));
content->add(object_ptr<Ui::DividerLabel>(
content,
std::move(label),
st::defaultBoxDividerLabelPadding,
st::defaultDividerLabel.bar,
RectParts()));
}
content->resizeToWidth(content->width());
}, content->lifetime());
using namespace rpl::mappers;
wrap->toggleOn(
content->heightValue() | rpl::map(_1 > 0),
anim::type::instant);
}
} // namespace
InnerWidget::InnerWidget(
QWidget *parent,
not_null<Controller*> controller,
Origin origin)
: RpWidget(parent)
, _controller(controller)
, _peer(_controller->key().peer())
, _migrated(_controller->migrated())
, _topic(_controller->key().topic())
, _sublist(_controller->key().sublist())
, _content(setupContent(this, origin)) {
_content->heightValue(
) | rpl::on_next([this](int height) {
if (!_inResize) {
resizeToWidth(width());
updateDesiredHeight();
}
}, lifetime());
}
rpl::producer<> InnerWidget::backRequest() const {
return _backClicks.events();
}
object_ptr<Ui::RpWidget> InnerWidget::setupContent(
not_null<RpWidget*> parent,
Origin origin) {
if (const auto user = _peer->asUser()) {
user->session().changes().peerFlagsValue(
user,
Data::PeerUpdate::Flag::FullInfo
) | rpl::on_next([=] {
auto &photos = user->session().api().peerPhoto();
if (const auto original = photos.nonPersonalPhoto(user)) {
// Preload it for the edit contact box.
_nonPersonalView = original->createMediaView();
const auto id = peerToUser(user->id);
original->load(Data::FileOriginFullUser{ id });
}
}, lifetime());
}
auto result = object_ptr<Ui::VerticalLayout>(parent);
const auto musicPeer = _sublist
? _sublist->sublistPeer().get()
: _peer.get();
AddSavedMusic(
result.data(),
_controller,
musicPeer,
_topBarColor.value());
if (const auto user = _peer->asUser()) {
AddUnofficialSecurityRiskWarning(result.data(), user);
}
auto stack = SectionStack(result.data());
if (_topic && _topic->creating()) {
stack.finalize();
return result;
}
BuildProfileDetailsSections(
stack,
_controller,
_peer,
_topic,
_sublist,
origin);
auto sharedTracker = Ui::MultiSlideTracker();
{
auto sharedMediaWidget = setupSharedMedia(
result.data(),
sharedTracker);
const auto raw = sharedMediaWidget.data();
_sharedMediaWrap = raw;
stack.addPlainSeparator();
stack.add(Section{
.widget = std::move(sharedMediaWidget),
.shown = raw->toggledValue(),
});
}
if (_topic || _sublist) {
stack.finalize();
return result;
}
if (auto manage = SetupChannelMembersAndManage(
_controller,
result.data(),
_peer)) {
const auto raw = static_cast<Ui::SlideWrap<Ui::RpWidget>*>(
manage.data());
stack.addPlainSeparator();
stack.add(Section{
.widget = std::move(manage),
.shown = raw->toggledValue(),
});
}
if (auto actions = SetupActions(_controller, result.data(), _peer)) {
stack.addPlainSeparator();
stack.add(Section{
.widget = std::move(actions),
.shown = rpl::single(true),
});
}
if ((_peer->isChat() || _peer->isMegagroup())
&& !_peer->isMonoforum()) {
stack.addPlainSeparator();
stack.add(makeMembersSection(result.data()));
}
stack.finalize();
return result;
}
Section InnerWidget::makeMembersSection(not_null<QWidget*> parent) {
auto wrap = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
parent,
object_ptr<Ui::VerticalLayout>(parent));
const auto raw = wrap.data();
const auto inner = raw->entity();
_members = inner->add(object_ptr<Members>(inner, _controller));
_members->scrollToRequests(
) | rpl::on_next([this](Ui::ScrollToRequest request) {
auto min = (request.ymin < 0)
? request.ymin
: MapFrom(this, _members, QPoint(0, request.ymin)).y();
auto max = (request.ymin < 0)
? MapFrom(this, _members, QPoint()).y()
: (request.ymax < 0)
? request.ymax
: MapFrom(this, _members, QPoint(0, request.ymax)).y();
_scrollToRequests.fire({ min, max });
}, _members->lifetime());
_members->onlineCountValue(
) | rpl::on_next([=](int count) {
_onlineCount.fire_copy(count);
}, _members->lifetime());
using namespace rpl::mappers;
raw->toggleOn(
_members->fullCountValue() | rpl::map(_1 > 0),
anim::type::instant);
return Section{
.widget = std::move(wrap),
.shown = raw->toggledValue(),
};
}
object_ptr<Ui::SlideWrap<Ui::RpWidget>> InnerWidget::setupSharedMedia(
not_null<RpWidget*> parent,
Ui::MultiSlideTracker &sharedTracker) {
using namespace rpl::mappers;
using MediaType = Media::Type;
const auto peer = _sublist ? _sublist->sublistPeer() : _peer;
auto content = object_ptr<Ui::VerticalLayout>(parent);
auto &tracker = sharedTracker;
auto addMediaButton = [&](
MediaType type,
const style::icon &icon) {
auto result = Media::AddButton(
content,
_controller,
peer,
_topic ? _topic->rootId() : MsgId(),
_sublist ? _sublist->sublistPeer()->id : PeerId(),
_migrated,
type,
tracker);
object_ptr<Profile::FloatingIcon>(
result,
icon,
st::infoSharedMediaButtonIconPosition);
};
auto addCommonGroupsButton = [&](
not_null<UserData*> user,
const style::icon &icon) {
auto result = Media::AddCommonGroupsButton(
content,
_controller,
user,
tracker);
object_ptr<Profile::FloatingIcon>(
result,
icon,
st::infoSharedMediaButtonIconPosition);
};
const auto addSimilarPeersButton = [&](
not_null<PeerData*> peer,
const style::icon &icon) {
auto result = Media::AddSimilarPeersButton(
content,
_controller,
peer,
tracker);
object_ptr<Profile::FloatingIcon>(
result,
icon,
st::infoSharedMediaButtonIconPosition);
};
auto addStoriesButton = [&](
not_null<PeerData*> peer,
const style::icon &icon) {
if (peer->isChat()) {
return;
}
auto result = Media::AddStoriesButton(
content,
_controller,
peer,
tracker);
object_ptr<Profile::FloatingIcon>(
result,
icon,
st::infoSharedMediaButtonIconPosition);
};
auto addSavedSublistButton = [&](
not_null<PeerData*> peer,
const style::icon &icon) {
auto result = Media::AddSavedSublistButton(
content,
_controller,
peer,
tracker);
object_ptr<Profile::FloatingIcon>(
result,
icon,
st::infoSharedMediaButtonIconPosition);
};
auto addPeerGiftsButton = [&](
not_null<PeerData*> peer,
const style::icon &icon) {
auto result = Media::AddPeerGiftsButton(
content,
_controller,
peer,
tracker);
object_ptr<Profile::FloatingIcon>(
result,
icon,
st::infoSharedMediaButtonIconPosition);
};
if (!_topic) {
addStoriesButton(peer, st::infoIconMediaStories);
addPeerGiftsButton(peer, st::infoIconMediaGifts);
addSavedSublistButton(peer, st::infoIconMediaSaved);
}
addMediaButton(MediaType::Photo, st::infoIconMediaPhoto);
addMediaButton(MediaType::Video, st::infoIconMediaVideo);
addMediaButton(MediaType::File, st::infoIconMediaFile);
addMediaButton(MediaType::MusicFile, st::infoIconMediaAudio);
addMediaButton(MediaType::Link, st::infoIconMediaLink);
addMediaButton(MediaType::Poll, st::infoIconMediaPoll);
addMediaButton(MediaType::RoundVoiceFile, st::infoIconMediaVoice);
addMediaButton(MediaType::GIF, st::infoIconMediaGif);
if (const auto bot = peer->asBot()) {
addCommonGroupsButton(bot, st::infoIconMediaGroup);
addSimilarPeersButton(bot, st::infoIconMediaBot);
} else if (const auto channel = peer->asBroadcast()) {
addSimilarPeersButton(channel, st::infoIconMediaChannel);
} else if (const auto user = peer->asUser()) {
addCommonGroupsButton(user, st::infoIconMediaGroup);
}
auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
parent,
object_ptr<Ui::VerticalLayout>(parent)
);
result->setDuration(
st::infoSlideDuration
)->toggleOn(
tracker.atLeastOneShownValue()
);
auto layout = result->entity();
layout->add(std::move(content));
return result;
}
int InnerWidget::countDesiredHeight() const {
return _content->height() + (_members
? (_members->desiredHeight() - _members->height())
: 0);
}
void InnerWidget::visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) {
setChildVisibleTopBottom(_content, visibleTop, visibleBottom);
}
void InnerWidget::saveState(not_null<Memento*> memento) {
if (_members) {
memento->setMembersState(_members->saveState());
}
}
void InnerWidget::restoreState(not_null<Memento*> memento) {
if (_members) {
_members->restoreState(memento->membersState());
}
if (_sharedMediaWrap) {
_sharedMediaWrap->finishAnimating();
}
}
rpl::producer<Ui::ScrollToRequest> InnerWidget::scrollToRequests() const {
return _scrollToRequests.events();
}
rpl::producer<int> InnerWidget::desiredHeightValue() const {
return _desiredHeight.events_starting_with(countDesiredHeight());
}
int InnerWidget::resizeGetHeight(int newWidth) {
_inResize = true;
auto guard = gsl::finally([&] { _inResize = false; });
_content->resizeToWidth(newWidth);
_content->moveToLeft(0, 0);
updateDesiredHeight();
return _content->heightNoMargins();
}
void InnerWidget::enableBackButton() {
_backToggles.force_assign(true);
}
void InnerWidget::showFinished() {
_showFinished.fire({});
}
bool InnerWidget::hasFlexibleTopBar() const {
return true;
}
base::weak_qptr<Ui::RpWidget> InnerWidget::createPinnedToTop(
not_null<Ui::RpWidget*> parent) {
const auto content = Ui::CreateChild<TopBar>(
parent,
TopBar::Descriptor{
.controller = _controller->parentController(),
.key = _controller->key(),
.wrap = _controller->wrapValue(),
.peer = _sublist ? _sublist->sublistPeer().get() : nullptr,
.backToggles = _backToggles.value(),
.showFinished = _showFinished.events(),
});
content->backRequest(
) | rpl::start_to_stream(_backClicks, content->lifetime());
content->setOnlineCount(_onlineCount.events());
_topBarColor = content->edgeColor();
return base::make_weak(not_null<Ui::RpWidget*>{ content });
}
base::weak_qptr<Ui::RpWidget> InnerWidget::createPinnedToBottom(
not_null<Ui::RpWidget*> parent) {
return nullptr;
}
} // namespace Profile
} // namespace Info