Improved view style of contacts.

This commit is contained in:
23rd 2024-02-22 05:04:35 +03:00 committed by John Preston
parent 52c779bffa
commit 93d1a187ca
4 changed files with 425 additions and 119 deletions

View file

@ -4724,6 +4724,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_boosts_prepaid_giveaway_status#one" = "{count} subscription {duration}";
"lng_boosts_prepaid_giveaway_status#other" = "{count} subscriptions {duration}";
"lng_contact_add" = "Add";
"lng_contact_send_message" = "message";
// Wnd specific
"lng_wnd_choose_program_menu" = "Choose Default Program...";

View file

@ -1253,7 +1253,7 @@ const SharedContact *MediaContact::sharedContact() const {
}
TextWithEntities MediaContact::notificationText() const {
return tr::lng_in_dlg_contact(tr::now, Ui::Text::WithEntities);
return Ui::Text::Colorized(tr::lng_in_dlg_contact(tr::now));
}
QString MediaContact::pinnedTextSubstring() const {

View file

@ -7,28 +7,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/media/history_view_contact.h"
#include "core/click_handler_types.h" // ClickHandlerContext
#include "lang/lang_keys.h"
#include "layout/layout_selection.h"
#include "mainwindow.h"
#include "boxes/add_contact_box.h"
#include "history/history_item_components.h"
#include "history/history_item.h"
#include "history/history.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h"
#include "window/window_session_controller.h"
#include "ui/empty_userpic.h"
#include "ui/chat/chat_style.h"
#include "ui/text/format_values.h" // Ui::FormatPhone
#include "ui/text/text_options.h"
#include "ui/painter.h"
#include "core/click_handler_types.h" // ClickHandlerContext
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/data_media_types.h"
#include "data/data_cloud_file.h"
#include "history/history.h"
#include "history/history_item_components.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_reply.h"
#include "history/view/media/history_view_media_common.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "styles/style_boxes.h"
#include "styles/style_chat.h"
#include "ui/chat/chat_style.h"
#include "ui/empty_userpic.h"
#include "ui/painter.h"
#include "ui/power_saving.h"
#include "ui/rect.h"
#include "ui/text/format_values.h" // Ui::FormatPhone
#include "ui/text/text_options.h"
#include "window/window_session_controller.h"
namespace HistoryView {
namespace {
@ -81,17 +80,32 @@ Contact::Contact(
const QString &last,
const QString &phone)
: Media(parent)
, _userId(userId)
, _fname(first)
, _lname(last)
, _phone(Ui::FormatPhone(phone)) {
, _st(st::historyPagePreview)
, _pixh(st::contactsPhotoSize)
, _userId(userId) {
history()->owner().registerContactView(userId, parent);
_name.setText(
st::semiboldTextStyle,
tr::lng_full_name(tr::now, lt_first_name, first, lt_last_name, last).trimmed(),
Ui::NameTextOptions());
_phonew = st::normalFont->width(_phone);
_nameLine.setText(
st::webPageTitleStyle,
tr::lng_full_name(
tr::now,
lt_first_name,
first,
lt_last_name,
last).trimmed(),
Ui::WebpageTextTitleOptions());
_phoneLine.setText(
st::webPageDescriptionStyle,
Ui::FormatPhone(QString(phone).replace(QChar('+'), QString())),
Ui::WebpageTextTitleOptions());
#if 0 // No info.
_infoLine.setText(
st::webPageDescriptionStyle,
phone,
Ui::WebpageTextTitleOptions());
#endif
}
Contact::~Contact() {
@ -111,121 +125,300 @@ void Contact::updateSharedContactUserId(UserId userId) {
}
QSize Contact::countOptimalSize() {
const auto item = _parent->data();
auto maxWidth = st::msgFileMinWidth;
_contact = _userId
? item->history()->owner().userLoaded(_userId)
? _parent->data()->history()->owner().userLoaded(_userId)
: nullptr;
if (_contact) {
_contact->loadUserpic();
} else {
const auto full = _name.toString();
const auto full = _nameLine.toString();
_photoEmpty = std::make_unique<Ui::EmptyUserpic>(
Ui::EmptyUserpic::UserpicColor(Data::DecideColorIndex(_userId
? peerFromUser(_userId)
: Data::FakePeerIdForJustName(full))),
full);
}
if (_contact && _contact->isContact()) {
_linkl = SendMessageClickHandler(_contact);
_link = tr::lng_profile_send_message(tr::now).toUpper();
} else if (_userId) {
_linkl = AddContactClickHandler(_parent->data());
_link = tr::lng_profile_add_contact(tr::now).toUpper();
}
_linkw = _link.isEmpty() ? 0 : st::semiboldFont->width(_link);
const auto &st = _userId ? st::msgFileThumbLayout : st::msgFileLayout;
const auto tleft = st.padding.left() + st.thumbSize + st.thumbSkip;
const auto tright = st.padding.right();
if (_userId) {
accumulate_max(maxWidth, tleft + _phonew + tright);
_buttons.clear();
if (_contact) {
const auto message = tr::lng_contact_send_message(tr::now).toUpper();
_buttons.push_back({
message,
st::semiboldFont->width(message),
SendMessageClickHandler(_contact),
});
if (!_contact->isContact()) {
const auto add = tr::lng_contact_add(tr::now).toUpper();
_buttons.push_back({
add,
st::semiboldFont->width(add),
AddContactClickHandler(_parent->data()),
});
}
_mainButton.link = _buttons.front().link;
} else {
accumulate_max(maxWidth, tleft + _phonew + _parent->skipBlockWidth() + st::msgPadding.right());
#if 0 // Can't view contact.
const auto view = tr::lng_profile_add_contact(tr::now).toUpper();
_buttons.push_back({
view,
st::semiboldFont->width(view),
AddContactClickHandler(_parent->data()),
});
#endif
_mainButton.link = nullptr;
}
accumulate_max(maxWidth, tleft + _name.maxWidth() + tright);
accumulate_min(maxWidth, st::msgMaxWidth);
auto minHeight = st.padding.top() + st.thumbSize + st.padding.bottom();
if (_parent->bottomInfoIsWide()) {
minHeight += st::msgDateFont->height - st::msgDateDelta.y();
const auto padding = inBubblePadding() + innerMargin();
const auto full = Rect(currentSize());
const auto outer = full - inBubblePadding();
const auto inner = outer - innerMargin();
const auto lineLeft = inner.left() + _pixh + inner.left() - outer.left();
const auto lineHeight = UnitedLineHeight();
auto maxWidth = _parent->skipBlockWidth();
auto minHeight = 0;
auto textMinHeight = 0;
if (!_nameLine.isEmpty()) {
accumulate_max(maxWidth, lineLeft + _nameLine.maxWidth());
textMinHeight += 1 * lineHeight;
}
if (!isBubbleTop()) {
minHeight -= st::msgFileTopMinus;
if (!_phoneLine.isEmpty()) {
accumulate_max(maxWidth, lineLeft + _phoneLine.maxWidth());
textMinHeight += 1 * lineHeight;
}
if (!_infoLine.isEmpty()) {
accumulate_max(maxWidth, lineLeft + _infoLine.maxWidth());
textMinHeight += std::min(_infoLine.minHeight(), 1 * lineHeight);
}
minHeight = std::max(textMinHeight, st::contactsPhotoSize);
if (!_buttons.empty()) {
auto buttonsWidth = rect::m::sum::h(st::historyPageButtonPadding);
for (const auto &button : _buttons) {
buttonsWidth += button.width;
}
accumulate_max(maxWidth, buttonsWidth);
}
maxWidth += rect::m::sum::h(padding);
minHeight += rect::m::sum::v(padding);
return { maxWidth, minHeight };
}
void Contact::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
auto paintw = width();
if (width() < rect::m::sum::h(st::msgPadding) + 1) {
return;
}
const auto st = context.st;
const auto sti = context.imageStyle();
const auto stm = context.messageStyle();
accumulate_min(paintw, maxWidth());
const auto bubble = st::msgPadding;
const auto full = Rect(currentSize());
const auto outer = full - inBubblePadding();
const auto inner = outer - innerMargin();
auto tshift = inner.top();
const auto &st = _userId ? st::msgFileThumbLayout : st::msgFileLayout;
const auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus;
const auto nameleft = st.padding.left() + st.thumbSize + st.thumbSkip;
const auto nametop = st.nameTop - topMinus;
const auto nameright = st.padding.right();
const auto statustop = st.statusTop - topMinus;
const auto linkshift = st::msgDateFont->height / 2;
const auto linktop = st.linkTop - topMinus - linkshift;
if (_userId) {
QRect rthumb(style::rtlrect(st.padding.left(), st.padding.top() - topMinus, st.thumbSize, st.thumbSize, paintw));
if (_contact) {
const auto was = !_userpic.null();
_contact->paintUserpic(p, _userpic, rthumb.x(), rthumb.y(), st.thumbSize);
if (!was && !_userpic.null()) {
history()->owner().registerHeavyViewPart(_parent);
const auto selected = context.selected();
const auto view = parent();
const auto colorIndex = _contact
? _contact->colorIndex()
: Data::DecideColorIndex(
Data::FakePeerIdForJustName(_nameLine.toString()));
const auto cache = context.outbg
? stm->replyCache[st->colorPatternIndex(colorIndex)].get()
: st->coloredReplyCache(selected, colorIndex).get();
const auto backgroundEmojiId = _contact
? _contact->backgroundEmojiId()
: DocumentId();
const auto backgroundEmoji = backgroundEmojiId
? st->backgroundEmojiData(backgroundEmojiId).get()
: nullptr;
const auto backgroundEmojiCache = backgroundEmoji
? &backgroundEmoji->caches[Ui::BackgroundEmojiData::CacheIndex(
selected,
context.outbg,
true,
colorIndex + 1)]
: nullptr;
Ui::Text::ValidateQuotePaintCache(*cache, _st);
Ui::Text::FillQuotePaint(p, outer, *cache, _st);
if (backgroundEmoji) {
ValidateBackgroundEmoji(
backgroundEmojiId,
backgroundEmoji,
backgroundEmojiCache,
cache,
view);
if (!backgroundEmojiCache->frames[0].isNull()) {
const auto end = rect::bottom(inner) + _st.padding.bottom();
const auto r = outer
- QMargins(0, 0, 0, rect::bottom(outer) - end);
FillBackgroundEmoji(p, r, false, *backgroundEmojiCache);
}
}
if (_mainButton.ripple) {
_mainButton.ripple->paint(
p,
outer.x(),
outer.y(),
width(),
&cache->bg);
if (_mainButton.ripple->empty()) {
_mainButton.ripple = nullptr;
}
}
{
const auto left = inner.left();
const auto top = tshift;
if (_userId) {
if (_contact) {
const auto was = !_userpic.null();
_contact->paintUserpic(p, _userpic, left, top, _pixh);
if (!was && !_userpic.null()) {
history()->owner().registerHeavyViewPart(_parent);
}
} else {
_photoEmpty->paintCircle(p, left, top, _pixh, _pixh);
}
} else {
_photoEmpty->paintCircle(p, st.padding.left(), st.padding.top() - topMinus, paintw, st.thumbSize);
_photoEmpty->paintCircle(p, left, top, _pixh, _pixh);
}
if (context.selected()) {
PainterHighQualityEnabler hq(p);
auto hq = PainterHighQualityEnabler(p);
p.setBrush(p.textPalette().selectOverlay);
p.setPen(Qt::NoPen);
p.drawEllipse(rthumb);
p.drawEllipse(left, top, _pixh, _pixh);
}
bool over = ClickHandler::showAsActive(_linkl);
p.setFont(over ? st::semiboldFont->underline() : st::semiboldFont);
p.setPen(stm->msgFileThumbLinkFg);
p.drawTextLeft(nameleft, linktop, paintw, _link, _linkw);
} else {
_photoEmpty->paintCircle(p, st.padding.left(), st.padding.top() - topMinus, paintw, st.thumbSize);
}
const auto namewidth = paintw - nameleft - nameright;
p.setFont(st::semiboldFont);
p.setPen(stm->historyFileNameFg);
_name.drawLeftElided(p, nameleft, nametop, namewidth, paintw);
const auto lineHeight = UnitedLineHeight();
const auto lineLeft = inner.left() + _pixh + inner.left() - outer.left();
const auto lineWidth = rect::right(inner) - lineLeft;
p.setFont(st::normalFont);
p.setPen(stm->mediaFg);
p.drawTextLeft(nameleft, statustop, paintw, _phone);
{
p.setPen(cache->icon);
p.setTextPalette(context.outbg
? stm->semiboldPalette
: st->coloredTextPalette(selected, colorIndex));
const auto endskip = _nameLine.hasSkipBlock()
? _parent->skipBlockWidth()
: 0;
_nameLine.drawLeftElided(
p,
lineLeft,
tshift,
lineWidth,
width(),
1,
style::al_left,
0,
-1,
endskip,
false,
context.selection);
tshift += lineHeight;
p.setTextPalette(stm->textPalette);
}
p.setPen(stm->historyTextFg);
{
tshift += st::lineWidth * 3; // Additional skip.
const auto endskip = _phoneLine.hasSkipBlock()
? _parent->skipBlockWidth()
: 0;
_phoneLine.drawLeftElided(
p,
lineLeft,
tshift,
lineWidth,
width(),
1,
style::al_left,
0,
-1,
endskip,
false,
toTitleSelection(context.selection));
tshift += 1 * lineHeight;
}
if (!_infoLine.isEmpty()) {
tshift += st::lineWidth * 3; // Additional skip.
const auto endskip = _infoLine.hasSkipBlock()
? _parent->skipBlockWidth()
: 0;
_parent->prepareCustomEmojiPaint(p, context, _infoLine);
_infoLine.draw(p, {
.position = { lineLeft, tshift },
.outerWidth = width(),
.availableWidth = lineWidth,
.spoiler = Ui::Text::DefaultSpoilerCache(),
.now = context.now,
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
.selection = toDescriptionSelection(context.selection),
.elisionHeight = (1 * lineHeight),
.elisionRemoveFromEnd = endskip,
});
tshift += (1 * lineHeight);
}
if (!_buttons.empty()) {
p.setFont(st::semiboldFont);
p.setPen(cache->icon);
const auto end = rect::bottom(inner) + _st.padding.bottom();
const auto line = st::historyPageButtonLine;
auto color = cache->icon;
color.setAlphaF(color.alphaF() * 0.3);
const auto top = end + st::historyPageButtonPadding.top();
const auto buttonWidth = inner.width() / float64(_buttons.size());
p.fillRect(inner.x(), end, inner.width(), line, color);
for (auto i = 0; i < _buttons.size(); i++) {
const auto &button = _buttons[i];
const auto left = inner.x() + i * buttonWidth;
if (button.ripple) {
button.ripple->paint(p, left, end, buttonWidth, &cache->bg);
if (button.ripple->empty()) {
_buttons[i].ripple = nullptr;
}
}
p.drawText(
left + (buttonWidth - button.width) / 2,
top + st::semiboldFont->ascent,
button.text);
}
}
}
TextState Contact::textState(QPoint point, StateRequest request) const {
auto result = TextState(_parent);
if (_userId) {
const auto &st = _userId ? st::msgFileThumbLayout : st::msgFileLayout;
const auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus;
const auto nameleft = st.padding.left() + st.thumbSize + st.thumbSkip;
const auto linkshift = st::msgDateFont->height / 2;
const auto linktop = st.linkTop - topMinus - linkshift;
if (style::rtlrect(nameleft, linktop, _linkw, st::semiboldFont->height, width()).contains(point)) {
result.link = _linkl;
return result;
const auto full = Rect(currentSize());
const auto outer = full - inBubblePadding();
const auto inner = outer - innerMargin();
_lastPoint = point;
if (_buttons.size() > 1) {
const auto end = rect::bottom(inner) + _st.padding.bottom();
const auto line = st::historyPageButtonLine;
const auto bWidth = inner.width() / float64(_buttons.size());
const auto bHeight = rect::bottom(outer) - end;
for (auto i = 0; i < _buttons.size(); i++) {
const auto left = inner.x() + i * bWidth;
if (QRectF(left, end, bWidth, bHeight).contains(point)) {
result.link = _buttons[i].link;
return result;
}
}
}
if (QRect(0, 0, width(), height()).contains(point) && _contact) {
result.link = _contact->openLink();
if (outer.contains(point)) {
result.link = _mainButton.link;
return result;
}
return result;
@ -239,4 +432,100 @@ bool Contact::hasHeavyPart() const {
return !_userpic.null();
}
void Contact::clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed) {
const auto full = Rect(currentSize());
const auto outer = full - inBubblePadding();
const auto inner = outer - innerMargin();
const auto end = rect::bottom(inner) + _st.padding.bottom();
if ((_lastPoint.y() < end) || (_buttons.size() <= 1)) {
if (p != _mainButton.link) {
return;
}
if (pressed) {
if (!_mainButton.ripple) {
const auto owner = &parent()->history()->owner();
_mainButton.ripple = std::make_unique<Ui::RippleAnimation>(
st::defaultRippleAnimation,
Ui::RippleAnimation::RoundRectMask(
outer.size(),
_st.radius),
[=] { owner->requestViewRepaint(parent()); });
}
_mainButton.ripple->add(_lastPoint - outer.topLeft());
} else if (_mainButton.ripple) {
_mainButton.ripple->lastStop();
}
return;
} else if (_buttons.empty()) {
return;
}
const auto bWidth = inner.width() / float64(_buttons.size());
const auto bHeight = rect::bottom(outer) - end;
for (auto i = 0; i < _buttons.size(); i++) {
const auto &button = _buttons[i];
if (p != button.link) {
continue;
}
if (pressed) {
if (!button.ripple) {
const auto owner = &parent()->history()->owner();
_buttons[i].ripple = std::make_unique<Ui::RippleAnimation>(
st::defaultRippleAnimation,
Ui::RippleAnimation::MaskByDrawer(
QSize(bWidth, bHeight),
false,
[=](QPainter &p) {
p.drawRect(0, 0, bWidth, bHeight);
}),
[=] { owner->requestViewRepaint(parent()); });
}
button.ripple->add(_lastPoint
- QPoint(inner.x() + i * bWidth, end));
} else if (button.ripple) {
button.ripple->lastStop();
}
}
}
QMargins Contact::inBubblePadding() const {
return {
st::msgPadding.left(),
isBubbleTop() ? st::msgPadding.left() : 0,
st::msgPadding.right(),
isBubbleBottom() ? (st::msgPadding.left() + bottomInfoPadding()) : 0
};
}
QMargins Contact::innerMargin() const {
const auto button = _buttons.empty() ? 0 : st::historyPageButtonHeight;
return _st.padding + QMargins(0, 0, 0, button);
}
int Contact::bottomInfoPadding() const {
if (!isBubbleBottom()) {
return 0;
}
auto result = st::msgDateFont->height;
// We use padding greater than st::msgPadding.bottom() in the
// bottom of the bubble so that the left line looks pretty.
// but if we have bottom skip because of the info display
// we don't need that additional padding so we replace it
// back with st::msgPadding.bottom() instead of left().
result += st::msgPadding.bottom() - st::msgPadding.left();
return result;
}
TextSelection Contact::toTitleSelection(TextSelection selection) const {
return UnshiftItemSelection(selection, _nameLine);
}
TextSelection Contact::toDescriptionSelection(TextSelection selection) const {
return UnshiftItemSelection(toTitleSelection(selection), _phoneLine);
}
} // namespace HistoryView

View file

@ -12,11 +12,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui {
class EmptyUserpic;
class RippleAnimation;
} // namespace Ui
namespace HistoryView {
class Contact : public Media {
class Contact final : public Media {
public:
Contact(
not_null<Element*> parent,
@ -29,7 +30,8 @@ public:
void draw(Painter &p, const PaintContext &context) const override;
TextState textState(QPoint point, StateRequest request) const override;
bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
bool toggleSelectionByHandlerClick(
const ClickHandlerPtr &p) const override {
return true;
}
bool dragItemByHandler(const ClickHandlerPtr &p) const override {
@ -43,16 +45,6 @@ public:
return false;
}
const QString &fname() const {
return _fname;
}
const QString &lname() const {
return _lname;
}
const QString &phone() const {
return _phone;
}
// Should be called only by Data::Session.
void updateSharedContactUserId(UserId userId) override;
@ -62,18 +54,40 @@ public:
private:
QSize countOptimalSize() override;
void clickHandlerPressedChanged(
const ClickHandlerPtr &p, bool pressed) override;
[[nodiscard]] QMargins inBubblePadding() const;
[[nodiscard]] QMargins innerMargin() const;
[[nodiscard]] int bottomInfoPadding() const;
[[nodiscard]] TextSelection toTitleSelection(
TextSelection selection) const;
[[nodiscard]] TextSelection toDescriptionSelection(
TextSelection selection) const;
const style::QuoteStyle &_st;
const int _pixh;
UserId _userId = 0;
UserData *_contact = nullptr;
int _phonew = 0;
QString _fname, _lname, _phone;
Ui::Text::String _name;
Ui::Text::String _nameLine;
Ui::Text::String _phoneLine;
Ui::Text::String _infoLine;
struct Button {
QString text;
int width = 0;
ClickHandlerPtr link;
mutable std::unique_ptr<Ui::RippleAnimation> ripple;
};
std::vector<Button> _buttons;
Button _mainButton;
std::unique_ptr<Ui::EmptyUserpic> _photoEmpty;
mutable Ui::PeerUserpicView _userpic;
ClickHandlerPtr _linkl;
int _linkw = 0;
QString _link;
mutable QPoint _lastPoint;
};