mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-14 13:17:08 +02:00
Show similar channels under join message.
This commit is contained in:
parent
91fba41e2c
commit
36a8c49213
30 changed files with 814 additions and 74 deletions
|
@ -713,6 +713,8 @@ PRIVATE
|
|||
history/view/media/history_view_premium_gift.h
|
||||
history/view/media/history_view_service_box.cpp
|
||||
history/view/media/history_view_service_box.h
|
||||
history/view/media/history_view_similar_channels.cpp
|
||||
history/view/media/history_view_similar_channels.h
|
||||
history/view/media/history_view_slot_machine.cpp
|
||||
history/view/media/history_view_slot_machine.h
|
||||
history/view/media/history_view_sticker.cpp
|
||||
|
|
BIN
Telegram/Resources/icons/chat/mini_subscribers.png
Normal file
BIN
Telegram/Resources/icons/chat/mini_subscribers.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 261 B |
BIN
Telegram/Resources/icons/chat/mini_subscribers@2x.png
Normal file
BIN
Telegram/Resources/icons/chat/mini_subscribers@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 399 B |
BIN
Telegram/Resources/icons/chat/mini_subscribers@3x.png
Normal file
BIN
Telegram/Resources/icons/chat/mini_subscribers@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 557 B |
|
@ -1678,6 +1678,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_action_giveaway_results_some" = "Some winners of the giveaway was randomly selected by Telegram and received private messages with giftcodes.";
|
||||
"lng_action_giveaway_results_none" = "No winners of the giveaway could be selected.";
|
||||
|
||||
"lng_similar_channels_title" = "Similar channels";
|
||||
"lng_similar_channels_view_all" = "View all";
|
||||
|
||||
"lng_premium_gift_duration_months#one" = "for {count} month";
|
||||
"lng_premium_gift_duration_months#other" = "for {count} months";
|
||||
"lng_premium_gift_duration_years#one" = "for {count} year";
|
||||
|
|
|
@ -211,6 +211,23 @@ void ApplyBotsList(
|
|||
Data::PeerUpdate::Flag::FullInfo);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<not_null<ChannelData*>> ParseSimilar(
|
||||
not_null<ChannelData*> channel,
|
||||
const MTPmessages_Chats &chats) {
|
||||
auto result = std::vector<not_null<ChannelData*>>();
|
||||
chats.match([&](const auto &data) {
|
||||
const auto &list = data.vchats().v;
|
||||
result.reserve(list.size());
|
||||
for (const auto &chat : list) {
|
||||
const auto peer = channel->owner().processChat(chat);
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
result.push_back(channel);
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ChatParticipant::ChatParticipant(
|
||||
|
@ -559,6 +576,7 @@ void ChatParticipants::requestSelf(not_null<ChannelData*> channel) {
|
|||
UserId inviter = -1,
|
||||
TimeId inviteDate = 0,
|
||||
bool inviteViaRequest = false) {
|
||||
const auto dateChanged = (channel->inviteDate != inviteDate);
|
||||
channel->inviter = inviter;
|
||||
channel->inviteDate = inviteDate;
|
||||
channel->inviteViaRequest = inviteViaRequest;
|
||||
|
@ -569,6 +587,9 @@ void ChatParticipants::requestSelf(not_null<ChannelData*> channel) {
|
|||
} else {
|
||||
history->owner().histories().requestDialogEntry(history);
|
||||
}
|
||||
if (dateChanged) {
|
||||
loadSimilarChannels(channel);
|
||||
}
|
||||
}
|
||||
};
|
||||
_selfParticipantRequests.emplace(channel);
|
||||
|
@ -685,4 +706,35 @@ void ChatParticipants::unblock(
|
|||
_kickRequests.emplace(kick, requestId);
|
||||
}
|
||||
|
||||
void ChatParticipants::loadSimilarChannels(not_null<ChannelData*> channel) {
|
||||
if (!channel->isBroadcast() || _similar.contains(channel)) {
|
||||
return;
|
||||
}
|
||||
_similar[channel].requestId = _api.request(
|
||||
MTPchannels_GetChannelRecommendations(channel->inputChannel)
|
||||
).done([=](const MTPmessages_Chats &result) {
|
||||
_similar[channel] = {
|
||||
.list = ParseSimilar(channel, result),
|
||||
};
|
||||
_similarLoaded.fire_copy(channel);
|
||||
}).send();
|
||||
}
|
||||
|
||||
const std::vector<not_null<ChannelData*>> &ChatParticipants::similar(
|
||||
not_null<ChannelData*> channel) {
|
||||
const auto i = channel->isBroadcast()
|
||||
? _similar.find(channel)
|
||||
: end(_similar);
|
||||
if (i != end(_similar)) {
|
||||
return i->second.list;
|
||||
}
|
||||
static const auto empty = std::vector<not_null<ChannelData*>>();
|
||||
return empty;
|
||||
}
|
||||
|
||||
auto ChatParticipants::similarLoaded() const
|
||||
-> rpl::producer<not_null<ChannelData*>> {
|
||||
return _similarLoaded.events();
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
|
|
@ -120,7 +120,19 @@ public:
|
|||
not_null<ChannelData*> channel,
|
||||
not_null<PeerData*> participant);
|
||||
|
||||
[[nodiscard]] const std::vector<not_null<ChannelData*>> &similar(
|
||||
not_null<ChannelData*> channel);
|
||||
[[nodiscard]] auto similarLoaded() const
|
||||
-> rpl::producer<not_null<ChannelData*>>;
|
||||
|
||||
private:
|
||||
struct SimilarChannels {
|
||||
std::vector<not_null<ChannelData*>> list;
|
||||
mtpRequestId requestId = 0;
|
||||
};
|
||||
|
||||
void loadSimilarChannels(not_null<ChannelData*> channel);
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
using PeerRequests = base::flat_map<PeerData*, mtpRequestId>;
|
||||
|
@ -143,6 +155,9 @@ private:
|
|||
not_null<PeerData*>>;
|
||||
base::flat_map<KickRequest, mtpRequestId> _kickRequests;
|
||||
|
||||
base::flat_map<not_null<ChannelData*>, SimilarChannels> _similar;
|
||||
rpl::event_stream<not_null<ChannelData*>> _similarLoaded;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
|
|
|
@ -309,6 +309,8 @@ enum class MessageFlag : uint64 {
|
|||
|
||||
// If not set then we need to refresh _displayFrom value.
|
||||
DisplayFromChecked = (1ULL << 40),
|
||||
|
||||
ShowSimilarChannels = (1ULL << 41),
|
||||
};
|
||||
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
||||
using MessageFlags = base::flags<MessageFlag>;
|
||||
|
|
|
@ -37,6 +37,7 @@ namespace {
|
|||
constexpr auto kTopLayer = 2;
|
||||
constexpr auto kBottomLayer = 1;
|
||||
constexpr auto kNoneLayer = 0;
|
||||
constexpr auto kBlurRadius = 24;
|
||||
|
||||
[[nodiscard]] QImage CornerBadgeTTL(
|
||||
not_null<PeerData*> peer,
|
||||
|
@ -46,38 +47,17 @@ constexpr auto kNoneLayer = 0;
|
|||
if (!ttl) {
|
||||
return QImage();
|
||||
}
|
||||
constexpr auto kBlurRadius = 24;
|
||||
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto fullSize = photoSize;
|
||||
const auto blurredFull = Images::BlurLargeImage(
|
||||
peer->generateUserpicImage(view, fullSize * ratio, 0),
|
||||
kBlurRadius);
|
||||
const auto partRect = CornerBadgeTTLRect(fullSize);
|
||||
const auto &partSize = partRect.width();
|
||||
auto result = [&] {
|
||||
auto blurredPart = blurredFull.copy(
|
||||
blurredFull.width() - partSize * ratio,
|
||||
blurredFull.height() - partSize * ratio,
|
||||
partSize * ratio,
|
||||
partSize * ratio);
|
||||
blurredPart.setDevicePixelRatio(ratio);
|
||||
|
||||
constexpr auto kMinAcceptableContrast = 4.5;
|
||||
const auto averageColor = Ui::CountAverageColor(blurredPart);
|
||||
const auto contrast = Ui::CountContrast(
|
||||
averageColor,
|
||||
st::premiumButtonFg->c);
|
||||
if (contrast < kMinAcceptableContrast) {
|
||||
constexpr auto kDarkerBy = 0.2;
|
||||
auto painterPart = QPainter(&blurredPart);
|
||||
painterPart.setOpacity(kDarkerBy);
|
||||
painterPart.fillRect(
|
||||
QRect(QPoint(), partRect.size()),
|
||||
Qt::black);
|
||||
}
|
||||
return Images::Circle(std::move(blurredPart));
|
||||
}();
|
||||
const auto partSkip = fullSize - partSize;
|
||||
auto result = Images::Circle(BlurredDarkenedPart(
|
||||
peer->generateUserpicImage(view, fullSize * ratio, 0),
|
||||
QRect(
|
||||
QPoint(partSkip, partSkip) * ratio,
|
||||
QSize(partSize, partSize) * ratio)));
|
||||
result.setDevicePixelRatio(ratio);
|
||||
|
||||
auto q = QPainter(&result);
|
||||
PainterHighQualityEnabler hq(q);
|
||||
|
@ -125,6 +105,28 @@ QRect CornerBadgeTTLRect(int photoSize) {
|
|||
partSize);
|
||||
}
|
||||
|
||||
QImage BlurredDarkenedPart(QImage image, QRect part) {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
auto blurred = Images::BlurLargeImage(
|
||||
std::move(image),
|
||||
kBlurRadius).copy(part);
|
||||
|
||||
constexpr auto kMinAcceptableContrast = 4.5;
|
||||
const auto averageColor = Ui::CountAverageColor(blurred);
|
||||
const auto contrast = Ui::CountContrast(
|
||||
averageColor,
|
||||
st::premiumButtonFg->c);
|
||||
if (contrast < kMinAcceptableContrast) {
|
||||
constexpr auto kDarkerBy = 0.2;
|
||||
auto painterPart = QPainter(&blurred);
|
||||
painterPart.setOpacity(kDarkerBy);
|
||||
painterPart.fillRect(QRect(QPoint(), part.size()), Qt::black);
|
||||
}
|
||||
|
||||
blurred.setDevicePixelRatio(image.devicePixelRatio());
|
||||
return blurred;
|
||||
}
|
||||
|
||||
Row::CornerLayersManager::CornerLayersManager() = default;
|
||||
|
||||
bool Row::CornerLayersManager::isSameLayer(Layer layer) const {
|
||||
|
|
|
@ -39,6 +39,7 @@ class Entry;
|
|||
enum class SortMode;
|
||||
|
||||
[[nodiscard]] QRect CornerBadgeTTLRect(int photoSize);
|
||||
[[nodiscard]] QImage BlurredDarkenedPart(QImage image, QRect part);
|
||||
|
||||
class BasicRow {
|
||||
public:
|
||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "base/weak_ptr.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/effects/animations.h"
|
||||
|
||||
class QPainter;
|
||||
|
||||
|
|
|
@ -1428,7 +1428,8 @@ void HistoryInner::onTouchScrollTimer() {
|
|||
} else if (_touchScrollState == Ui::TouchScrollState::Auto || _touchScrollState == Ui::TouchScrollState::Acceleration) {
|
||||
int32 elapsed = int32(nowTime - _touchTime);
|
||||
QPoint delta = _touchSpeed * elapsed / 1000;
|
||||
bool hasScrolled = _widget->touchScroll(delta);
|
||||
bool hasScrolled = consumeScrollAction(delta)
|
||||
|| _widget->touchScroll(delta);
|
||||
|
||||
if (_touchSpeed.isNull() || !hasScrolled) {
|
||||
_touchScrollState = Ui::TouchScrollState::Manual;
|
||||
|
@ -1625,7 +1626,9 @@ void HistoryInner::mouseActionUpdate(const QPoint &screenPos) {
|
|||
|
||||
void HistoryInner::touchScrollUpdated(const QPoint &screenPos) {
|
||||
_touchPos = screenPos;
|
||||
_widget->touchScroll(_touchPos - _touchPrevPos);
|
||||
if (!consumeScrollAction(_touchPos - _touchPrevPos)) {
|
||||
_widget->touchScroll(_touchPos - _touchPrevPos);
|
||||
}
|
||||
touchUpdateSpeed();
|
||||
}
|
||||
|
||||
|
@ -3834,6 +3837,7 @@ void HistoryInner::mouseActionUpdate() {
|
|||
}
|
||||
|
||||
Qt::CursorShape cur = style::cur_default;
|
||||
_acceptsHorizontalScroll = dragState.horizontalScroll;
|
||||
if (_mouseAction == MouseAction::None) {
|
||||
_mouseCursorState = dragState.cursor;
|
||||
if (dragState.link) {
|
||||
|
@ -4447,6 +4451,17 @@ void HistoryInner::onParentGeometryChanged() {
|
|||
}
|
||||
}
|
||||
|
||||
bool HistoryInner::consumeScrollAction(QPoint delta) {
|
||||
const auto horizontal = std::abs(delta.x()) > std::abs(delta.y());
|
||||
if (!horizontal || !_acceptsHorizontalScroll || !Element::Moused()) {
|
||||
return false;
|
||||
}
|
||||
const auto position = mapPointToItem(
|
||||
mapFromGlobal(_mousePosition),
|
||||
Element::Moused());
|
||||
return Element::Moused()->consumeHorizontalScroll(position, delta.x());
|
||||
}
|
||||
|
||||
Fn<HistoryView::ElementDelegate*()> HistoryInner::elementDelegateFactory(
|
||||
FullMsgId itemId) const {
|
||||
const auto weak = base::make_weak(_controller);
|
||||
|
|
|
@ -203,6 +203,7 @@ public:
|
|||
bool tooltipWindowActive() const override;
|
||||
|
||||
void onParentGeometryChanged();
|
||||
bool consumeScrollAction(QPoint delta);
|
||||
|
||||
[[nodiscard]] Fn<HistoryView::ElementDelegate*()> elementDelegateFactory(
|
||||
FullMsgId itemId) const;
|
||||
|
@ -490,6 +491,7 @@ private:
|
|||
bool _recountedAfterPendingResizedItems = false;
|
||||
bool _useCornerReaction = false;
|
||||
bool _canHaveFromUserpicsSponsored = false;
|
||||
bool _acceptsHorizontalScroll = false;
|
||||
|
||||
QPoint _trippleClickPoint;
|
||||
base::Timer _trippleClickTimer;
|
||||
|
|
|
@ -317,6 +317,9 @@ public:
|
|||
[[nodiscard]] bool isFakeBotAbout() const {
|
||||
return _flags & MessageFlag::FakeBotAbout;
|
||||
}
|
||||
[[nodiscard]] bool showSimilarChannels() const {
|
||||
return _flags & MessageFlag::ShowSimilarChannels;
|
||||
}
|
||||
[[nodiscard]] bool isRegular() const;
|
||||
[[nodiscard]] bool isUploading() const;
|
||||
void sendFailed();
|
||||
|
|
|
@ -557,7 +557,7 @@ not_null<HistoryItem*> GenerateJoinedMessage(
|
|||
bool viaRequest) {
|
||||
return history->makeMessage(
|
||||
history->owner().nextLocalMessageId(),
|
||||
MessageFlag::Local,
|
||||
MessageFlag::Local | MessageFlag::ShowSimilarChannels,
|
||||
inviteDate,
|
||||
GenerateJoinedText(history, inviter, viaRequest));
|
||||
}
|
||||
|
|
|
@ -135,6 +135,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/chat/chat_theme.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/chat/continuous_scroll.h"
|
||||
#include "ui/widgets/elastic_scroll.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/item_text_options.h"
|
||||
#include "main/main_session.h"
|
||||
|
@ -271,6 +272,15 @@ HistoryWidget::HistoryWidget(
|
|||
update();
|
||||
}, lifetime());
|
||||
|
||||
base::install_event_filter(_scroll.data(), [=](not_null<QEvent*> e) {
|
||||
const auto consumed = (e->type() == QEvent::Wheel)
|
||||
&& _list
|
||||
&& _list->consumeScrollAction(
|
||||
Ui::ScrollDelta(static_cast<QWheelEvent*>(e.get())));
|
||||
return consumed
|
||||
? base::EventFilterResult::Cancel
|
||||
: base::EventFilterResult::Continue;
|
||||
});
|
||||
_scroll->scrolls(
|
||||
) | rpl::start_with_next([=] {
|
||||
handleScroll();
|
||||
|
|
|
@ -24,8 +24,8 @@ TextState::TextState(
|
|||
? CursorState::Text
|
||||
: CursorState::None)
|
||||
, link(state.link)
|
||||
, afterSymbol(state.afterSymbol)
|
||||
, symbol(state.symbol) {
|
||||
, symbol(state.symbol)
|
||||
, afterSymbol(state.afterSymbol) {
|
||||
}
|
||||
|
||||
TextState::TextState(
|
||||
|
@ -59,8 +59,8 @@ TextState::TextState(
|
|||
? CursorState::Text
|
||||
: CursorState::None)
|
||||
, link(state.link)
|
||||
, afterSymbol(state.afterSymbol)
|
||||
, symbol(state.symbol) {
|
||||
, symbol(state.symbol)
|
||||
, afterSymbol(state.afterSymbol) {
|
||||
}
|
||||
|
||||
TextState::TextState(std::nullptr_t, ClickHandlerPtr link)
|
||||
|
|
|
@ -50,10 +50,11 @@ struct TextState {
|
|||
FullMsgId itemId;
|
||||
CursorState cursor = CursorState::None;
|
||||
ClickHandlerPtr link;
|
||||
bool overMessageText = false;
|
||||
bool afterSymbol = false;
|
||||
bool customTooltip = false;
|
||||
uint16 symbol = 0;
|
||||
bool afterSymbol = false;
|
||||
bool overMessageText = false;
|
||||
bool customTooltip = false;
|
||||
bool horizontalScroll = false;
|
||||
QString customTooltipText;
|
||||
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/view/history_view_message.h"
|
||||
#include "history/view/media/history_view_media.h"
|
||||
#include "history/view/media/history_view_media_grouped.h"
|
||||
#include "history/view/media/history_view_similar_channels.h"
|
||||
#include "history/view/media/history_view_sticker.h"
|
||||
#include "history/view/media/history_view_large_emoji.h"
|
||||
#include "history/view/media/history_view_custom_emoji.h"
|
||||
|
@ -36,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "window/window_session_controller.h"
|
||||
#include "ui/effects/path_shift_gradient.h"
|
||||
#include "ui/effects/reaction_fly_animation.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/text/text_options.h"
|
||||
|
@ -723,6 +725,8 @@ void Element::refreshMedia(Element *replacing) {
|
|||
}
|
||||
}
|
||||
_media = media->createView(this, replacing);
|
||||
} else if (item->showSimilarChannels()) {
|
||||
_media = std::make_unique<SimilarChannels>(this);
|
||||
} else if (isOnlyCustomEmoji()
|
||||
&& Core::App().settings().largeEmoji()
|
||||
&& !item->isSponsored()) {
|
||||
|
|
|
@ -515,6 +515,7 @@ public:
|
|||
const Reactions::InlineList &reactions) const;
|
||||
void clearCustomEmojiRepaint() const;
|
||||
void hideSpoilers();
|
||||
void repaint() const;
|
||||
|
||||
[[nodiscard]] ClickHandlerPtr fromPhotoLink() const {
|
||||
return fromLink();
|
||||
|
@ -531,6 +532,10 @@ public:
|
|||
|
||||
void overrideMedia(std::unique_ptr<Media> media);
|
||||
|
||||
virtual bool consumeHorizontalScroll(QPoint position, int delta) {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual ~Element();
|
||||
|
||||
static void Hovered(Element *view);
|
||||
|
@ -546,8 +551,6 @@ public:
|
|||
static void ClearGlobal();
|
||||
|
||||
protected:
|
||||
void repaint() const;
|
||||
|
||||
void paintHighlight(
|
||||
Painter &p,
|
||||
const PaintContext &context,
|
||||
|
|
|
@ -52,10 +52,8 @@ void ValidateBackgroundEmoji(
|
|||
}
|
||||
const auto tag = Data::CustomEmojiSizeTag::Isolated;
|
||||
if (!data->emoji) {
|
||||
const auto repaint = crl::guard(view, [=] { view->repaint(); });
|
||||
const auto owner = &view->history()->owner();
|
||||
const auto repaint = crl::guard(view, [=] {
|
||||
view->history()->owner().requestViewRepaint(view);
|
||||
});
|
||||
data->emoji = owner->customEmojiManager().create(
|
||||
backgroundEmojiId,
|
||||
repaint,
|
||||
|
@ -779,7 +777,7 @@ void Reply::createRippleAnimation(
|
|||
Ui::RippleAnimation::RoundRectMask(
|
||||
size,
|
||||
st::messageQuoteStyle.radius),
|
||||
[=] { view->history()->owner().requestViewRepaint(view); });
|
||||
[=] { view->repaint(); });
|
||||
}
|
||||
|
||||
void Reply::saveRipplePoint(QPoint point) const {
|
||||
|
|
|
@ -411,6 +411,13 @@ QRect Service::innerGeometry() const {
|
|||
return countGeometry();
|
||||
}
|
||||
|
||||
bool Service::consumeHorizontalScroll(QPoint position, int delta) {
|
||||
if (const auto media = this->media()) {
|
||||
return media->consumeHorizontalScroll(position, delta);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QRect Service::countGeometry() const {
|
||||
auto result = QRect(0, 0, width(), height());
|
||||
if (delegate()->elementIsChatWide()) {
|
||||
|
@ -429,7 +436,8 @@ QSize Service::performCountCurrentSize(int newWidth) {
|
|||
return { newWidth, newHeight };
|
||||
}
|
||||
const auto media = this->media();
|
||||
if (media && media->hideServiceText()) {
|
||||
const auto mediaDisplayed = media && media->isDisplayed();
|
||||
if (mediaDisplayed && media->hideServiceText()) {
|
||||
newHeight += st::msgServiceMargin.top()
|
||||
+ media->resizeGetHeight(newWidth)
|
||||
+ st::msgServiceMargin.bottom();
|
||||
|
@ -448,8 +456,10 @@ QSize Service::performCountCurrentSize(int newWidth) {
|
|||
? minHeight()
|
||||
: textHeightFor(nwidth);
|
||||
newHeight += st::msgServicePadding.top() + st::msgServicePadding.bottom() + st::msgServiceMargin.top() + st::msgServiceMargin.bottom();
|
||||
if (media) {
|
||||
newHeight += st::msgServiceMargin.top() + media->resizeGetHeight(media->maxWidth());
|
||||
if (mediaDisplayed) {
|
||||
const auto mediaWidth = std::min(media->maxWidth(), nwidth);
|
||||
newHeight += st::msgServiceMargin.top()
|
||||
+ media->resizeGetHeight(mediaWidth);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -527,10 +537,11 @@ void Service::draw(Painter &p, const PaintContext &context) const {
|
|||
p.setTextPalette(st->serviceTextPalette());
|
||||
|
||||
const auto media = this->media();
|
||||
const auto onlyMedia = (media && media->hideServiceText());
|
||||
const auto mediaDisplayed = media && media->isDisplayed();
|
||||
const auto onlyMedia = (mediaDisplayed && media->hideServiceText());
|
||||
|
||||
if (!onlyMedia) {
|
||||
if (media) {
|
||||
if (mediaDisplayed) {
|
||||
height -= margin.top() + media->height();
|
||||
}
|
||||
const auto trect = QRect(g.left(), margin.top(), g.width(), height)
|
||||
|
@ -561,8 +572,8 @@ void Service::draw(Painter &p, const PaintContext &context) const {
|
|||
.fullWidthSelection = false,
|
||||
});
|
||||
}
|
||||
if (media) {
|
||||
const auto left = margin.left() + (g.width() - media->maxWidth()) / 2;
|
||||
if (mediaDisplayed) {
|
||||
const auto left = margin.left() + (g.width() - media->width()) / 2;
|
||||
const auto top = margin.top() + (onlyMedia ? 0 : (height + margin.top()));
|
||||
p.translate(left, top);
|
||||
media->draw(p, context.translated(-left, -top).withSelection({}));
|
||||
|
@ -576,6 +587,7 @@ void Service::draw(Painter &p, const PaintContext &context) const {
|
|||
|
||||
PointState Service::pointState(QPoint point) const {
|
||||
const auto media = this->media();
|
||||
const auto mediaDisplayed = media && media->isDisplayed();
|
||||
|
||||
auto g = countGeometry();
|
||||
if (g.width() < 1 || isHidden()) {
|
||||
|
@ -588,7 +600,7 @@ PointState Service::pointState(QPoint point) const {
|
|||
if (const auto bar = Get<UnreadBar>()) {
|
||||
g.setTop(g.top() + bar->height());
|
||||
}
|
||||
if (media) {
|
||||
if (mediaDisplayed) {
|
||||
const auto centerPadding = (g.width() - media->width()) / 2;
|
||||
const auto r = g - QMargins(centerPadding, 0, centerPadding, 0);
|
||||
if (!r.contains(point)) {
|
||||
|
@ -602,7 +614,8 @@ PointState Service::pointState(QPoint point) const {
|
|||
TextState Service::textState(QPoint point, StateRequest request) const {
|
||||
const auto item = data();
|
||||
const auto media = this->media();
|
||||
const auto onlyMedia = (media && media->hideServiceText());
|
||||
const auto mediaDisplayed = media && media->isDisplayed();
|
||||
const auto onlyMedia = (mediaDisplayed && media->hideServiceText());
|
||||
|
||||
auto result = TextState(item);
|
||||
|
||||
|
@ -622,8 +635,8 @@ TextState Service::textState(QPoint point, StateRequest request) const {
|
|||
}
|
||||
|
||||
if (onlyMedia) {
|
||||
return media->textState(point - QPoint(st::msgServiceMargin.left() + (g.width() - media->maxWidth()) / 2, st::msgServiceMargin.top()), request);
|
||||
} else if (media) {
|
||||
return media->textState(point - QPoint(st::msgServiceMargin.left() + (g.width() - media->width()) / 2, st::msgServiceMargin.top()), request);
|
||||
} else if (mediaDisplayed) {
|
||||
g.setHeight(g.height() - (st::msgServiceMargin.top() + media->height()));
|
||||
}
|
||||
auto trect = g.marginsAdded(-st::msgServicePadding);
|
||||
|
@ -656,8 +669,8 @@ TextState Service::textState(QPoint point, StateRequest request) const {
|
|||
result.link = same->lnk;
|
||||
}
|
||||
}
|
||||
} else if (media) {
|
||||
result = media->textState(point - QPoint(st::msgServiceMargin.left() + (g.width() - media->maxWidth()) / 2, st::msgServiceMargin.top() + g.height() + st::msgServiceMargin.top()), request);
|
||||
} else if (mediaDisplayed) {
|
||||
result = media->textState(point - QPoint(st::msgServiceMargin.left() + (g.width() - media->width()) / 2, st::msgServiceMargin.top() + g.height() + st::msgServiceMargin.top()), request);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -52,6 +52,8 @@ public:
|
|||
|
||||
QRect innerGeometry() const override;
|
||||
|
||||
bool consumeHorizontalScroll(QPoint position, int delta) override;
|
||||
|
||||
private:
|
||||
[[nodiscard]] QRect countGeometry() const;
|
||||
|
||||
|
|
|
@ -413,13 +413,12 @@ void Game::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
|
|||
if (!_ripple) {
|
||||
const auto full = QRect(0, 0, width(), height());
|
||||
const auto outer = full.marginsRemoved(inBubblePadding());
|
||||
const auto owner = &parent()->history()->owner();
|
||||
_ripple = std::make_unique<Ui::RippleAnimation>(
|
||||
st::defaultRippleAnimation,
|
||||
Ui::RippleAnimation::RoundRectMask(
|
||||
outer.size(),
|
||||
_st.radius),
|
||||
[=] { owner->requestViewRepaint(parent()); });
|
||||
[=] { repaint(); });
|
||||
}
|
||||
_ripple->add(_lastPoint);
|
||||
} else if (_ripple) {
|
||||
|
|
|
@ -14,7 +14,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_channel.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_session.h"
|
||||
#include "dialogs/ui/dialogs_stories_content.h"
|
||||
#include "dialogs/ui/dialogs_stories_list.h"
|
||||
#include "history/history.h"
|
||||
|
@ -348,9 +347,7 @@ void Giveaway::paintChannels(
|
|||
const auto &thumbnail = channel.thumbnail;
|
||||
const auto &geometry = channel.geometry;
|
||||
if (!_subscribedToThumbnails) {
|
||||
thumbnail->subscribeToUpdates([view = parent()] {
|
||||
view->history()->owner().requestViewRepaint(view);
|
||||
});
|
||||
thumbnail->subscribeToUpdates([=] { repaint(); });
|
||||
}
|
||||
|
||||
const auto colorIndex = channel.colorIndex;
|
||||
|
@ -487,13 +484,12 @@ void Giveaway::clickHandlerPressedChanged(
|
|||
}
|
||||
if (pressed) {
|
||||
if (!channel.ripple) {
|
||||
const auto owner = &parent()->history()->owner();
|
||||
channel.ripple = std::make_unique<Ui::RippleAnimation>(
|
||||
st::defaultRippleAnimation,
|
||||
Ui::RippleAnimation::RoundRectMask(
|
||||
channel.geometry.size(),
|
||||
channel.geometry.height() / 2),
|
||||
[=] { owner->requestViewRepaint(parent()); });
|
||||
[=] { repaint(); });
|
||||
}
|
||||
channel.ripple->add(_lastPoint - channel.geometry.topLeft());
|
||||
} else if (channel.ripple) {
|
||||
|
|
|
@ -198,10 +198,6 @@ SelectedQuote Media::selectedQuote(TextSelection selection) const {
|
|||
return {};
|
||||
}
|
||||
|
||||
bool Media::isDisplayed() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
QSize Media::countCurrentSize(int newWidth) {
|
||||
return QSize(qMin(newWidth, maxWidth()), minHeight());
|
||||
}
|
||||
|
@ -285,7 +281,7 @@ void Media::fillImageSpoiler(
|
|||
|
||||
void Media::createSpoilerLink(not_null<MediaSpoiler*> spoiler) {
|
||||
const auto weak = base::make_weak(this);
|
||||
spoiler->link = std::make_shared<LambdaClickHandler>([=](
|
||||
spoiler->link = std::make_shared<LambdaClickHandler>([weak, spoiler](
|
||||
const ClickContext &context) {
|
||||
const auto button = context.button;
|
||||
const auto media = weak.get();
|
||||
|
@ -295,15 +291,15 @@ void Media::createSpoilerLink(not_null<MediaSpoiler*> spoiler) {
|
|||
const auto view = media->parent();
|
||||
spoiler->revealed = true;
|
||||
spoiler->revealAnimation.start([=] {
|
||||
media->history()->owner().requestViewRepaint(view);
|
||||
view->repaint();
|
||||
}, 0., 1., st::fadeWrapDuration);
|
||||
media->history()->owner().requestViewRepaint(view);
|
||||
view->repaint();
|
||||
media->history()->owner().registerShownSpoiler(view);
|
||||
});
|
||||
}
|
||||
|
||||
void Media::repaint() const {
|
||||
history()->owner().requestViewRepaint(_parent);
|
||||
_parent->repaint();
|
||||
}
|
||||
|
||||
Ui::Text::String Media::createCaption(not_null<HistoryItem*> item) const {
|
||||
|
|
|
@ -96,7 +96,9 @@ public:
|
|||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual bool isDisplayed() const;
|
||||
[[nodiscard]] virtual bool isDisplayed() const {
|
||||
return true;
|
||||
}
|
||||
virtual void updateNeedBubbleState() {
|
||||
}
|
||||
[[nodiscard]] virtual bool hasTextForCopy() const {
|
||||
|
@ -335,6 +337,10 @@ public:
|
|||
virtual void parentTextUpdated() {
|
||||
}
|
||||
|
||||
virtual bool consumeHorizontalScroll(QPoint position, int delta) {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual ~Media() = default;
|
||||
|
||||
protected:
|
||||
|
|
|
@ -0,0 +1,496 @@
|
|||
/*
|
||||
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 "history/view/media/history_view_similar_channels.h"
|
||||
|
||||
#include "api/api_chat_participants.h"
|
||||
#include "apiwrap.h"
|
||||
#include "boxes/peer_lists_box.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_session.h"
|
||||
#include "dialogs/ui/dialogs_stories_content.h"
|
||||
#include "dialogs/ui/dialogs_stories_list.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/chat/chat_theme.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/painter.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
namespace HistoryView {
|
||||
namespace {
|
||||
|
||||
class SimilarChannelsController final : public PeerListController {
|
||||
public:
|
||||
SimilarChannelsController(
|
||||
not_null<Window::SessionController*> controller,
|
||||
std::vector<not_null<ChannelData*>> channels);
|
||||
|
||||
void prepare() override;
|
||||
void loadMoreRows() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
Main::Session &session() const override;
|
||||
|
||||
private:
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const std::vector<not_null<ChannelData*>> _channels;
|
||||
|
||||
};
|
||||
|
||||
SimilarChannelsController::SimilarChannelsController(
|
||||
not_null<Window::SessionController*> controller,
|
||||
std::vector<not_null<ChannelData*>> channels)
|
||||
: _controller(controller)
|
||||
, _channels(std::move(channels)) {
|
||||
}
|
||||
|
||||
void SimilarChannelsController::prepare() {
|
||||
for (const auto &channel : _channels) {
|
||||
auto row = std::make_unique<PeerListRow>(channel);
|
||||
if (const auto count = channel->membersCount(); count > 1) {
|
||||
row->setCustomStatus(tr::lng_chat_status_subscribers(
|
||||
tr::now,
|
||||
lt_count,
|
||||
count));
|
||||
}
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
|
||||
void SimilarChannelsController::loadMoreRows() {
|
||||
}
|
||||
|
||||
void SimilarChannelsController::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto other = ClickHandlerContext{
|
||||
.sessionWindow = _controller,
|
||||
.show = _controller->uiShow(),
|
||||
};
|
||||
row->peer()->openLink()->onClick({
|
||||
Qt::LeftButton,
|
||||
QVariant::fromValue(other)
|
||||
});
|
||||
}
|
||||
|
||||
Main::Session &SimilarChannelsController::session() const {
|
||||
return _channels.front()->session();
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> SimilarChannelsBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const std::vector<not_null<ChannelData*>> &channels) {
|
||||
const auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->setTitle(tr::lng_similar_channels_title());
|
||||
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
|
||||
};
|
||||
return Box<PeerListBox>(
|
||||
std::make_unique<SimilarChannelsController>(controller, channels),
|
||||
initBox);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SimilarChannels::SimilarChannels(not_null<Element*> parent)
|
||||
: Media(parent) {
|
||||
}
|
||||
|
||||
SimilarChannels::~SimilarChannels() {
|
||||
if (hasHeavyPart()) {
|
||||
unloadHeavyPart();
|
||||
parent()->checkHeavyPart();
|
||||
}
|
||||
}
|
||||
|
||||
void SimilarChannels::clickHandlerActiveChanged(
|
||||
const ClickHandlerPtr &p,
|
||||
bool active) {
|
||||
}
|
||||
|
||||
void SimilarChannels::clickHandlerPressedChanged(
|
||||
const ClickHandlerPtr &p,
|
||||
bool pressed) {
|
||||
for (auto &channel : _channels) {
|
||||
if (channel.link != p) {
|
||||
continue;
|
||||
}
|
||||
if (pressed) {
|
||||
if (!channel.ripple) {
|
||||
channel.ripple = std::make_unique<Ui::RippleAnimation>(
|
||||
st::defaultRippleAnimation,
|
||||
Ui::RippleAnimation::RoundRectMask(
|
||||
channel.geometry.size(),
|
||||
st::roundRadiusLarge),
|
||||
[=] { repaint(); });
|
||||
}
|
||||
channel.ripple->add(_lastPoint);
|
||||
} else if (channel.ripple) {
|
||||
channel.ripple->lastStop();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SimilarChannels::draw(Painter &p, const PaintContext &context) const {
|
||||
const auto large = Ui::BubbleCornerRounding::Large;
|
||||
const auto geometry = QRect(0, 0, width(), height());
|
||||
Ui::PaintBubble(
|
||||
p,
|
||||
Ui::SimpleBubble{
|
||||
.st = context.st,
|
||||
.geometry = geometry,
|
||||
.pattern = context.bubblesPattern,
|
||||
.patternViewport = context.viewport,
|
||||
.outerWidth = width(),
|
||||
.rounding = { large, large, large, large },
|
||||
});
|
||||
const auto stm = context.messageStyle();
|
||||
{
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
auto path = QPainterPath();
|
||||
const auto x = geometry.center().x();
|
||||
const auto y = geometry.y();
|
||||
const auto size = st::chatSimilarArrowSize;
|
||||
path.moveTo(x, y - size);
|
||||
path.lineTo(x + size, y);
|
||||
path.lineTo(x - size, y);
|
||||
path.lineTo(x, y - size);
|
||||
p.fillPath(path, stm->msgBg);
|
||||
}
|
||||
const auto photo = st::chatSimilarChannelPhoto;
|
||||
const auto padding = st::chatSimilarChannelPadding;
|
||||
p.setClipRect(geometry);
|
||||
_hasHeavyPart = 1;
|
||||
const auto drawOne = [&](const Channel &channel) {
|
||||
const auto geometry = channel.geometry.translated(-_scrollLeft, 0);
|
||||
const auto right = geometry.x() + geometry.width();
|
||||
if (right <= 0) {
|
||||
return;
|
||||
}
|
||||
if (!channel.subscribed) {
|
||||
channel.subscribed = true;
|
||||
const auto raw = channel.thumbnail.get();
|
||||
const auto view = parent();
|
||||
channel.thumbnail->subscribeToUpdates([=] {
|
||||
for (const auto &channel : _channels) {
|
||||
if (channel.thumbnail.get() == raw) {
|
||||
channel.participantsBgValid = false;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
auto cachedp = std::optional<Painter>();
|
||||
const auto cached = (geometry.x() < padding.left())
|
||||
|| (right > width() - padding.right());
|
||||
if (cached) {
|
||||
ensureCacheReady(geometry.size());
|
||||
_roundedCache.fill(Qt::transparent);
|
||||
cachedp.emplace(&_roundedCache);
|
||||
cachedp->translate(-geometry.topLeft());
|
||||
}
|
||||
const auto q = cachedp ? &*cachedp : &p;
|
||||
if (channel.ripple) {
|
||||
q->setOpacity(st::historyPollRippleOpacity);
|
||||
channel.ripple->paint(
|
||||
*q,
|
||||
geometry.x(),
|
||||
geometry.y(),
|
||||
width(),
|
||||
&stm->msgWaveformInactive->c);
|
||||
if (channel.ripple->empty()) {
|
||||
channel.ripple.reset();
|
||||
}
|
||||
q->setOpacity(1.);
|
||||
}
|
||||
q->drawImage(
|
||||
geometry.x() + padding.left(),
|
||||
geometry.y() + padding.top(),
|
||||
channel.thumbnail->image(st::chatSimilarChannelPhoto));
|
||||
if (!channel.participants.isEmpty()) {
|
||||
validateParticipansBg(channel);
|
||||
const auto participants = channel.participantsRect.translated(
|
||||
QPoint(-_scrollLeft, 0));
|
||||
q->drawImage(participants.topLeft(), channel.participantsBg);
|
||||
const auto badge = participants.marginsRemoved(
|
||||
st::chatSimilarBadgePadding);
|
||||
const auto &icon = st::chatSimilarBadgeIcon;
|
||||
const auto &font = st::chatSimilarBadgeFont;
|
||||
const auto position = st::chatSimilarBadgeIconPosition;
|
||||
const auto ascent = font->ascent;
|
||||
icon.paint(*q, badge.topLeft() + position, width());
|
||||
q->setFont(font);
|
||||
q->setPen(st::premiumButtonFg);
|
||||
q->drawText(
|
||||
badge.x() + position.x() + icon.width(),
|
||||
badge.y() + font->ascent,
|
||||
channel.participants);
|
||||
}
|
||||
q->setPen(stm->historyTextFg);
|
||||
channel.name.drawLeftElided(
|
||||
*q,
|
||||
geometry.x() + st::normalFont->spacew,
|
||||
geometry.y() + st::chatSimilarNameTop,
|
||||
(geometry.width() - 2 * st::normalFont->spacew),
|
||||
width(),
|
||||
2,
|
||||
style::al_top);
|
||||
if (cachedp) {
|
||||
q->setCompositionMode(QPainter::CompositionMode_DestinationIn);
|
||||
const auto corners = _roundedCorners.data();
|
||||
const auto side = st::bubbleRadiusLarge;
|
||||
q->drawImage(0, 0, corners[Images::kTopLeft]);
|
||||
q->drawImage(width() - side, 0, corners[Images::kTopRight]);
|
||||
q->drawImage(0, height() - side, corners[Images::kBottomLeft]);
|
||||
q->drawImage(
|
||||
QPoint(width() - side, height() - side),
|
||||
corners[Images::kBottomRight]);
|
||||
cachedp.reset();
|
||||
p.drawImage(geometry.topLeft(), _roundedCache);
|
||||
}
|
||||
};
|
||||
for (const auto &channel : _channels) {
|
||||
if (channel.geometry.x() >= _scrollLeft + width()) {
|
||||
break;
|
||||
}
|
||||
drawOne(channel);
|
||||
}
|
||||
p.setFont(st::chatSimilarTitle);
|
||||
p.drawTextLeft(
|
||||
st::chatSimilarTitlePosition.x(),
|
||||
st::chatSimilarTitlePosition.y(),
|
||||
width(),
|
||||
_title);
|
||||
if (!_hasViewAll) {
|
||||
return;
|
||||
}
|
||||
p.setFont(ClickHandler::showAsActive(_viewAllLink)
|
||||
? st::normalFont->underline()
|
||||
: st::normalFont);
|
||||
p.setPen(stm->textPalette.linkFg);
|
||||
const auto add = st::normalFont->ascent - st::chatSimilarTitle->ascent;
|
||||
p.drawTextRight(
|
||||
st::chatSimilarTitlePosition.x(),
|
||||
st::chatSimilarTitlePosition.y() + add,
|
||||
width(),
|
||||
_viewAll);
|
||||
p.setClipping(false);
|
||||
}
|
||||
|
||||
void SimilarChannels::validateParticipansBg(const Channel &channel) const {
|
||||
if (channel.participantsBgValid) {
|
||||
return;
|
||||
}
|
||||
channel.participantsBgValid = true;
|
||||
const auto photo = st::chatSimilarChannelPhoto;
|
||||
const auto width = channel.participantsRect.width();
|
||||
const auto height = channel.participantsRect.height();
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
auto result = QImage(
|
||||
channel.participantsRect.size() * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
auto color = Ui::CountAverageColor(
|
||||
channel.thumbnail->image(photo).copy(
|
||||
QRect(photo / 3, photo / 3, photo / 3, photo / 3)));
|
||||
|
||||
const auto lightness = color.lightness();
|
||||
if (!base::in_range(lightness, 160, 208)) {
|
||||
color = color.toHsl();
|
||||
color.setHsl(
|
||||
color.hue(),
|
||||
color.saturation(),
|
||||
std::clamp(lightness, 160, 208));
|
||||
color = color.toRgb();
|
||||
}
|
||||
|
||||
result.fill(color);
|
||||
result.setDevicePixelRatio(ratio);
|
||||
const auto radius = height / 2;
|
||||
auto corners = Images::CornersMask(radius);
|
||||
auto p = QPainter(&result);
|
||||
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
|
||||
p.drawImage(0, 0, corners[Images::kTopLeft]);
|
||||
p.drawImage(width - radius, 0, corners[Images::kTopRight]);
|
||||
p.drawImage(0, height - radius, corners[Images::kBottomLeft]);
|
||||
p.drawImage(
|
||||
width - radius,
|
||||
height - radius,
|
||||
corners[Images::kBottomRight]);
|
||||
p.end();
|
||||
channel.participantsBg = std::move(result);
|
||||
}
|
||||
|
||||
void SimilarChannels::ensureCacheReady(QSize size) const {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
if (_roundedCache.size() != size * ratio) {
|
||||
_roundedCache = QImage(
|
||||
size * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
_roundedCache.setDevicePixelRatio(ratio);
|
||||
}
|
||||
const auto radius = st::bubbleRadiusLarge;
|
||||
if (_roundedCorners.front().size() != QSize(radius, radius) * ratio) {
|
||||
_roundedCorners = Images::CornersMask(radius);
|
||||
}
|
||||
}
|
||||
|
||||
TextState SimilarChannels::textState(
|
||||
QPoint point,
|
||||
StateRequest request) const {
|
||||
auto result = TextState();
|
||||
result.horizontalScroll = (_scrollMax > 0);
|
||||
const auto skip = st::chatSimilarTitlePosition;
|
||||
const auto viewWidth = _hasViewAll ? (_viewAllWidth + 2 * skip.x()) : 0;
|
||||
const auto viewHeight = st::normalFont->height + 2 * skip.y();
|
||||
const auto viewLeft = width() - viewWidth;
|
||||
if (QRect(viewLeft, 0, viewWidth, viewHeight).contains(point)) {
|
||||
if (!_viewAllLink) {
|
||||
const auto channel = parent()->history()->peer->asChannel();
|
||||
Assert(channel != nullptr);
|
||||
_viewAllLink = std::make_shared<LambdaClickHandler>([=](
|
||||
ClickContext context) {
|
||||
Assert(channel != nullptr);
|
||||
const auto api = &channel->session().api();
|
||||
const auto &list = api->chatParticipants().similar(channel);
|
||||
if (list.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
if (const auto strong = my.sessionWindow.get()) {
|
||||
strong->show(SimilarChannelsBox(strong, list));
|
||||
}
|
||||
});
|
||||
}
|
||||
result.link = _viewAllLink;
|
||||
return result;
|
||||
}
|
||||
for (const auto &channel : _channels) {
|
||||
if (channel.geometry.translated(-_scrollLeft, 0).contains(point)) {
|
||||
result.link = channel.link;
|
||||
_lastPoint = point
|
||||
+ QPoint(_scrollLeft, 0)
|
||||
- channel.geometry.topLeft();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QSize SimilarChannels::countOptimalSize() {
|
||||
const auto channel = parent()->history()->peer->asChannel();
|
||||
Assert(channel != nullptr);
|
||||
|
||||
_channels.clear();
|
||||
const auto api = &channel->session().api();
|
||||
const auto similar = api->chatParticipants().similar(channel);
|
||||
if (similar.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
_channels.reserve(similar.size());
|
||||
auto x = st::chatSimilarPadding.left();
|
||||
auto y = st::chatSimilarPadding.top();
|
||||
const auto skip = st::chatSimilarSkip;
|
||||
const auto photo = st::chatSimilarChannelPhoto;
|
||||
const auto inner = QRect(0, 0, photo, photo);
|
||||
const auto outer = inner.marginsAdded(st::chatSimilarChannelPadding);
|
||||
for (const auto &channel : similar) {
|
||||
const auto participants = channel->membersCount();
|
||||
const auto count = (participants > 1)
|
||||
? Lang::FormatCountToShort(participants).string
|
||||
: QString();
|
||||
_channels.push_back({
|
||||
.geometry = QRect(QPoint(x, y), outer.size()),
|
||||
.name = Ui::Text::String(
|
||||
st::chatSimilarName,
|
||||
channel->name(),
|
||||
kDefaultTextOptions,
|
||||
st::chatSimilarChannelPhoto),
|
||||
.thumbnail = Dialogs::Stories::MakeUserpicThumbnail(channel),
|
||||
.link = channel->openLink(),
|
||||
.participants = count,
|
||||
});
|
||||
if (!count.isEmpty()) {
|
||||
const auto length = st::chatSimilarBadgeFont->width(count);
|
||||
const auto width = length + st::chatSimilarBadgeIcon.width();
|
||||
const auto delta = (outer.width() - width) / 2;
|
||||
const auto badge = QRect(
|
||||
x + delta,
|
||||
y + st::chatSimilarBadgeTop,
|
||||
outer.width() - 2 * delta,
|
||||
st::chatSimilarBadgeFont->height);
|
||||
_channels.back().participantsRect = badge.marginsAdded(
|
||||
st::chatSimilarBadgePadding);
|
||||
}
|
||||
x += outer.width() + skip;
|
||||
}
|
||||
_title = tr::lng_similar_channels_title(tr::now);
|
||||
_titleWidth = st::chatSimilarTitle->width(_title);
|
||||
_viewAll = tr::lng_similar_channels_view_all(tr::now);
|
||||
_viewAllWidth = st::normalFont->width(_viewAll);
|
||||
const auto count = int(_channels.size());
|
||||
const auto desired = (count ? (x - skip) : x)
|
||||
- st::chatSimilarPadding.left();
|
||||
const auto full = QRect(0, 0, desired, outer.height());
|
||||
const auto bubble = full.marginsAdded(st::chatSimilarPadding);
|
||||
_fullWidth = bubble.width();
|
||||
const auto titleSkip = st::chatSimilarTitlePosition.x();
|
||||
const auto min = _titleWidth + 2 * titleSkip;
|
||||
const auto limited = std::max(
|
||||
std::min(_fullWidth, st::chatSimilarWidthMax),
|
||||
min);
|
||||
if (limited > _fullWidth) {
|
||||
const auto shift = (limited - _fullWidth) / 2;
|
||||
for (auto &channel : _channels) {
|
||||
channel.geometry.translate(shift, 0);
|
||||
}
|
||||
}
|
||||
return { limited, bubble.height() };
|
||||
}
|
||||
|
||||
QSize SimilarChannels::countCurrentSize(int newWidth) {
|
||||
_scrollMax = std::max(_fullWidth - newWidth, 0);
|
||||
_scrollLeft = std::clamp(_scrollLeft, uint32(), _scrollMax);
|
||||
_hasViewAll = (_scrollMax != 0) ? 1 : 0;
|
||||
return { newWidth, minHeight() };
|
||||
}
|
||||
|
||||
bool SimilarChannels::hasHeavyPart() const {
|
||||
return _hasHeavyPart != 0;
|
||||
}
|
||||
|
||||
void SimilarChannels::unloadHeavyPart() {
|
||||
_hasHeavyPart = 0;
|
||||
for (const auto &channel : _channels) {
|
||||
channel.subscribed = false;
|
||||
channel.thumbnail->subscribeToUpdates(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool SimilarChannels::consumeHorizontalScroll(QPoint position, int delta) {
|
||||
if (_scrollMax == 0) {
|
||||
return false;
|
||||
}
|
||||
const auto left = _scrollLeft;
|
||||
_scrollLeft = std::clamp(
|
||||
int(_scrollLeft) - delta,
|
||||
0,
|
||||
int(_scrollMax));
|
||||
if (_scrollLeft == left) {
|
||||
return false;
|
||||
}
|
||||
repaint();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
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 "history/view/media/history_view_media.h"
|
||||
|
||||
namespace Dialogs::Stories {
|
||||
class Thumbnail;
|
||||
} // namespace Dialogs::Stories
|
||||
|
||||
namespace Ui {
|
||||
class RippleAnimation;
|
||||
} // namespace Ui
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
class SimilarChannels final : public Media {
|
||||
public:
|
||||
explicit SimilarChannels(not_null<Element*> parent);
|
||||
~SimilarChannels();
|
||||
|
||||
void draw(Painter &p, const PaintContext &context) const override;
|
||||
TextState textState(QPoint point, StateRequest request) const override;
|
||||
|
||||
void clickHandlerActiveChanged(
|
||||
const ClickHandlerPtr &p,
|
||||
bool active) override;
|
||||
void clickHandlerPressedChanged(
|
||||
const ClickHandlerPtr &p,
|
||||
bool pressed) override;
|
||||
|
||||
bool toggleSelectionByHandlerClick(
|
||||
const ClickHandlerPtr &p) const override {
|
||||
return false;
|
||||
}
|
||||
bool dragItemByHandler(const ClickHandlerPtr &p) const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool needsBubble() const override {
|
||||
return false;
|
||||
}
|
||||
bool customInfoLayout() const override {
|
||||
return true;
|
||||
}
|
||||
bool isDisplayed() const override {
|
||||
return !_channels.empty();
|
||||
}
|
||||
|
||||
void unloadHeavyPart() override;
|
||||
bool hasHeavyPart() const override;
|
||||
|
||||
bool consumeHorizontalScroll(QPoint position, int delta) override;
|
||||
|
||||
private:
|
||||
using Thumbnail = Dialogs::Stories::Thumbnail;
|
||||
struct Channel {
|
||||
QRect geometry;
|
||||
Ui::Text::String name;
|
||||
std::shared_ptr<Thumbnail> thumbnail;
|
||||
ClickHandlerPtr link;
|
||||
QString participants;
|
||||
QRect participantsRect;
|
||||
mutable QImage participantsBg;
|
||||
mutable std::unique_ptr<Ui::RippleAnimation> ripple;
|
||||
mutable bool subscribed = false;
|
||||
mutable bool participantsBgValid = false;
|
||||
};
|
||||
|
||||
void ensureCacheReady(QSize size) const;
|
||||
void validateParticipansBg(const Channel &channel) const;
|
||||
|
||||
QSize countOptimalSize() override;
|
||||
QSize countCurrentSize(int newWidth) override;
|
||||
|
||||
QString _title, _viewAll;
|
||||
mutable QImage _roundedCache;
|
||||
mutable std::array<QImage, 4> _roundedCorners;
|
||||
mutable QPoint _lastPoint;
|
||||
int _titleWidth = 0;
|
||||
int _viewAllWidth = 0;
|
||||
int _fullWidth = 0;
|
||||
uint32 _scrollLeft : 15 = 0;
|
||||
uint32 _scrollMax : 15 = 0;
|
||||
uint32 _hasViewAll : 1 = 0;
|
||||
mutable uint32 _hasHeavyPart : 1 = 0;
|
||||
|
||||
std::vector<Channel> _channels;
|
||||
mutable ClickHandlerPtr _viewAllLink;
|
||||
|
||||
};
|
||||
|
||||
} // namespace HistoryView
|
|
@ -977,3 +977,23 @@ chatGiveawayCountriesSkip: 16px;
|
|||
chatGiveawayDateTop: 6px;
|
||||
chatGiveawayDateSkip: 4px;
|
||||
chatGiveawayBottomSkip: 16px;
|
||||
|
||||
chatSimilarRadius: 12px;
|
||||
chatSimilarArrowSize: 6px;
|
||||
chatSimilarTitle: semiboldFont;
|
||||
chatSimilarTitlePosition: point(15px, 9px);
|
||||
chatSimilarPadding: margins(8px, 32px, 8px, 4px);
|
||||
chatSimilarChannelPadding: margins(8px, 5px, 8px, 37px);
|
||||
chatSimilarChannelPhoto: 50px;
|
||||
chatSimilarBadgePadding: margins(2px, 0px, 3px, 1px);
|
||||
chatSimilarBadgeTop: 43px;
|
||||
chatSimilarBadgeIcon: icon{{ "chat/mini_subscribers", premiumButtonFg }};
|
||||
chatSimilarBadgeIconPosition: point(0px, 1px);
|
||||
chatSimilarBadgeFont: font(10px bold);
|
||||
chatSimilarNameTop: 59px;
|
||||
chatSimilarName: TextStyle(defaultTextStyle) {
|
||||
font: font(12px);
|
||||
lineHeight: 14px;
|
||||
}
|
||||
chatSimilarWidthMax: 424px;
|
||||
chatSimilarSkip: 12px;
|
||||
|
|
Loading…
Add table
Reference in a new issue