mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-02-23 16:56:55 +00:00
Implement live location view.
This commit is contained in:
parent
ef8c07e6eb
commit
b9b7d9e337
BIN
Telegram/Resources/icons/chat/live_location_long.png
Normal file
BIN
Telegram/Resources/icons/chat/live_location_long.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 416 B |
BIN
Telegram/Resources/icons/chat/live_location_long@2x.png
Normal file
BIN
Telegram/Resources/icons/chat/live_location_long@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 913 B |
BIN
Telegram/Resources/icons/chat/live_location_long@3x.png
Normal file
BIN
Telegram/Resources/icons/chat/live_location_long@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
@ -3092,6 +3092,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_unread_bar_some" = "Unread messages";
|
||||
|
||||
"lng_maps_point" = "Location";
|
||||
"lng_live_location" = "Live Location";
|
||||
"lng_live_location_now" = "updated just now";
|
||||
"lng_live_location_minutes#one" = "updated {count} minute ago";
|
||||
"lng_live_location_minutes#other" = "updated {count} minutes ago";
|
||||
"lng_live_location_hours#one" = "updated {count} hour ago";
|
||||
"lng_live_location_hours#other" = "updated {count} hours ago";
|
||||
"lng_live_location_today" = "updated today at {time}";
|
||||
"lng_live_location_yesterday" = "updated yesterday at {time}";
|
||||
"lng_live_location_date_time" = "updated {date} at {time}";
|
||||
"lng_save_photo" = "Save image";
|
||||
"lng_save_video" = "Save video";
|
||||
"lng_save_audio_file" = "Save audio file";
|
||||
|
@ -1311,8 +1311,9 @@ std::unique_ptr<HistoryView::Media> MediaContact::createView(
|
||||
|
||||
MediaLocation::MediaLocation(
|
||||
not_null<HistoryItem*> parent,
|
||||
const LocationPoint &point)
|
||||
: MediaLocation(parent, point, QString(), QString()) {
|
||||
const LocationPoint &point,
|
||||
TimeId livePeriod)
|
||||
: MediaLocation({}, parent, point, livePeriod, QString(), QString()) {
|
||||
}
|
||||
|
||||
MediaLocation::MediaLocation(
|
||||
@ -1320,17 +1321,30 @@ MediaLocation::MediaLocation(
|
||||
const LocationPoint &point,
|
||||
const QString &title,
|
||||
const QString &description)
|
||||
: MediaLocation({}, parent, point, TimeId(), title, description) {
|
||||
}
|
||||
|
||||
MediaLocation::MediaLocation(
|
||||
PrivateTag,
|
||||
not_null<HistoryItem*> parent,
|
||||
const LocationPoint &point,
|
||||
TimeId livePeriod,
|
||||
const QString &title,
|
||||
const QString &description)
|
||||
: Media(parent)
|
||||
, _point(point)
|
||||
, _location(parent->history()->owner().location(point))
|
||||
, _livePeriod(livePeriod)
|
||||
, _title(title)
|
||||
, _description(description) {
|
||||
}
|
||||
|
||||
std::unique_ptr<Media> MediaLocation::clone(not_null<HistoryItem*> parent) {
|
||||
return std::make_unique<MediaLocation>(
|
||||
PrivateTag(),
|
||||
parent,
|
||||
_point,
|
||||
_livePeriod,
|
||||
_title,
|
||||
_description);
|
||||
}
|
||||
@ -1339,8 +1353,14 @@ CloudImage *MediaLocation::location() const {
|
||||
return _location;
|
||||
}
|
||||
|
||||
QString MediaLocation::typeString() const {
|
||||
return _livePeriod
|
||||
? tr::lng_live_location(tr::now)
|
||||
: tr::lng_maps_point(tr::now);
|
||||
}
|
||||
|
||||
ItemPreview MediaLocation::toPreview(ToPreviewOptions options) const {
|
||||
const auto type = tr::lng_maps_point(tr::now);
|
||||
const auto type = typeString();
|
||||
const auto hasMiniImages = false;
|
||||
const auto text = TextWithEntities{ .text = _title };
|
||||
return {
|
||||
@ -1349,9 +1369,7 @@ ItemPreview MediaLocation::toPreview(ToPreviewOptions options) const {
|
||||
}
|
||||
|
||||
TextWithEntities MediaLocation::notificationText() const {
|
||||
return WithCaptionNotificationText(
|
||||
tr::lng_maps_point(tr::now),
|
||||
{ .text = _title });
|
||||
return WithCaptionNotificationText(typeString(), { .text = _title });
|
||||
}
|
||||
|
||||
QString MediaLocation::pinnedTextSubstring() const {
|
||||
@ -1360,7 +1378,7 @@ QString MediaLocation::pinnedTextSubstring() const {
|
||||
|
||||
TextForMimeData MediaLocation::clipboardText() const {
|
||||
auto result = TextForMimeData::Simple(
|
||||
u"[ "_q + tr::lng_maps_point(tr::now) + u" ]\n"_q);
|
||||
u"[ "_q + typeString() + u" ]\n"_q);
|
||||
auto titleResult = TextUtilities::ParseEntities(
|
||||
_title,
|
||||
Ui::WebpageTextTitleOptions().flags);
|
||||
@ -1389,7 +1407,14 @@ std::unique_ptr<HistoryView::Media> MediaLocation::createView(
|
||||
not_null<HistoryView::Element*> message,
|
||||
not_null<HistoryItem*> realParent,
|
||||
HistoryView::Element *replacing) {
|
||||
return std::make_unique<HistoryView::Location>(
|
||||
return _livePeriod
|
||||
? std::make_unique<HistoryView::Location>(
|
||||
message,
|
||||
_location,
|
||||
_point,
|
||||
replacing,
|
||||
_livePeriod)
|
||||
: std::make_unique<HistoryView::Location>(
|
||||
message,
|
||||
_location,
|
||||
_point,
|
||||
|
@ -331,10 +331,14 @@ private:
|
||||
};
|
||||
|
||||
class MediaLocation final : public Media {
|
||||
struct PrivateTag {
|
||||
};
|
||||
|
||||
public:
|
||||
MediaLocation(
|
||||
not_null<HistoryItem*> parent,
|
||||
const LocationPoint &point);
|
||||
const LocationPoint &point,
|
||||
TimeId livePeriod = 0);
|
||||
MediaLocation(
|
||||
not_null<HistoryItem*> parent,
|
||||
const LocationPoint &point,
|
||||
@ -356,9 +360,21 @@ public:
|
||||
not_null<HistoryItem*> realParent,
|
||||
HistoryView::Element *replacing = nullptr) override;
|
||||
|
||||
MediaLocation(
|
||||
PrivateTag,
|
||||
not_null<HistoryItem*> parent,
|
||||
const LocationPoint &point,
|
||||
TimeId livePeriod,
|
||||
const QString &title,
|
||||
const QString &description);
|
||||
|
||||
private:
|
||||
|
||||
[[nodiscard]] QString typeString() const;
|
||||
|
||||
LocationPoint _point;
|
||||
not_null<CloudImage*> _location;
|
||||
TimeId _livePeriod = 0;
|
||||
QString _title;
|
||||
QString _description;
|
||||
|
||||
|
@ -224,10 +224,12 @@ std::unique_ptr<Data::Media> HistoryItem::CreateMedia(
|
||||
return nullptr;
|
||||
});
|
||||
}, [&](const MTPDmessageMediaGeoLive &media) -> Result {
|
||||
const auto period = media.vperiod().v;
|
||||
return media.vgeo().match([&](const MTPDgeoPoint &point) -> Result {
|
||||
return std::make_unique<Data::MediaLocation>(
|
||||
item,
|
||||
Data::LocationPoint(point));
|
||||
Data::LocationPoint(point),
|
||||
media.vperiod().v);
|
||||
}, [](const MTPDgeoPointEmpty &) -> Result {
|
||||
return nullptr;
|
||||
});
|
||||
|
@ -7,12 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "history/view/media/history_view_location.h"
|
||||
|
||||
#include "base/unixtime.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_location_manager.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/text/text_options.h"
|
||||
@ -24,6 +26,98 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
namespace HistoryView {
|
||||
namespace {
|
||||
|
||||
constexpr auto kUntilOffPeriod = std::numeric_limits<TimeId>::max();
|
||||
constexpr auto kLiveElapsedPartOpacity = 0.2;
|
||||
|
||||
[[nodiscard]] TimeId ResolveUpdateDate(not_null<Element*> view) {
|
||||
const auto item = view->data();
|
||||
const auto edited = item->Get<HistoryMessageEdited>();
|
||||
return edited ? edited->date : item->date();
|
||||
}
|
||||
|
||||
[[nodiscard]] QString RemainingTimeText(
|
||||
not_null<Element*> view,
|
||||
TimeId period) {
|
||||
if (period == kUntilOffPeriod) {
|
||||
return QString(1, QChar(0x221E));
|
||||
}
|
||||
const auto elapsed = base::unixtime::now() - view->data()->date();
|
||||
const auto remaining = std::clamp(period - elapsed, 0, period);
|
||||
if (remaining < 10) {
|
||||
return tr::lng_seconds_tiny(tr::now, lt_count, remaining);
|
||||
} else if (remaining < 600) {
|
||||
return tr::lng_minutes_tiny(tr::now, lt_count, remaining / 60);
|
||||
} else if (remaining < 3600) {
|
||||
return QString::number(remaining / 60);
|
||||
} else if (remaining < 86400) {
|
||||
return tr::lng_hours_tiny(tr::now, lt_count, remaining / 3600);
|
||||
}
|
||||
return tr::lng_days_tiny(tr::now, lt_count, remaining / 86400);
|
||||
}
|
||||
|
||||
[[nodiscard]] float64 RemainingTimeProgress(
|
||||
not_null<Element*> view,
|
||||
TimeId period) {
|
||||
if (period == kUntilOffPeriod) {
|
||||
return 1.;
|
||||
} else if (period < 1) {
|
||||
return 0.;
|
||||
}
|
||||
const auto elapsed = base::unixtime::now() - view->data()->date();
|
||||
return std::clamp(period - elapsed, 0, period) / float64(period);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct Location::Live {
|
||||
explicit Live(TimeId period) : period(period) {
|
||||
}
|
||||
|
||||
base::Timer updateStatusTimer;
|
||||
base::Timer updateRemainingTimer;
|
||||
QImage previous;
|
||||
QImage previousCache;
|
||||
Ui::BubbleRounding previousRounding;
|
||||
Ui::Animations::Simple crossfade;
|
||||
TimeId period = 0;
|
||||
int thumbnailHeight = 0;
|
||||
};
|
||||
|
||||
Location::Location(
|
||||
not_null<Element*> parent,
|
||||
not_null<Data::CloudImage*> data,
|
||||
Data::LocationPoint point,
|
||||
Element *replacing,
|
||||
TimeId livePeriod)
|
||||
: Media(parent)
|
||||
, _data(data)
|
||||
, _live(CreateLiveTracker(parent, livePeriod))
|
||||
, _title(st::msgMinWidth)
|
||||
, _description(st::msgMinWidth)
|
||||
, _link(std::make_shared<LocationClickHandler>(point)) {
|
||||
if (_live) {
|
||||
_title.setText(
|
||||
st::webPageTitleStyle,
|
||||
tr::lng_live_location(tr::now),
|
||||
Ui::WebpageTextTitleOptions());
|
||||
_live->updateStatusTimer.setCallback([=] {
|
||||
updateLiveStatus();
|
||||
checkLiveFinish();
|
||||
});
|
||||
_live->updateRemainingTimer.setCallback([=] {
|
||||
checkLiveFinish();
|
||||
});
|
||||
updateLiveStatus();
|
||||
if (const auto media = replacing ? replacing->media() : nullptr) {
|
||||
_live->previous = media->locationTakeImage();
|
||||
if (!_live->previous.isNull()) {
|
||||
history()->owner().registerHeavyViewPart(_parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Location::Location(
|
||||
not_null<Element*> parent,
|
||||
@ -53,18 +147,106 @@ Location::Location(
|
||||
}
|
||||
|
||||
Location::~Location() {
|
||||
if (_media) {
|
||||
_media = nullptr;
|
||||
if (hasHeavyPart()) {
|
||||
unloadHeavyPart();
|
||||
_parent->checkHeavyPart();
|
||||
}
|
||||
}
|
||||
|
||||
void Location::checkLiveFinish() {
|
||||
Expects(_live != nullptr);
|
||||
|
||||
const auto now = base::unixtime::now();
|
||||
const auto item = _parent->data();
|
||||
const auto start = item->date();
|
||||
if (_live->period != kUntilOffPeriod && now - start >= _live->period) {
|
||||
_live = nullptr;
|
||||
item->history()->owner().requestViewResize(_parent);
|
||||
} else {
|
||||
_parent->repaint();
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Location::Live> Location::CreateLiveTracker(
|
||||
not_null<Element*> parent,
|
||||
TimeId period) {
|
||||
if (!period) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto now = base::unixtime::now();
|
||||
const auto date = parent->data()->date();
|
||||
return (now < date || now - date < period)
|
||||
? std::make_unique<Live>(period)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
void Location::updateLiveStatus() {
|
||||
const auto date = ResolveUpdateDate(_parent);
|
||||
const auto now = base::unixtime::now();
|
||||
const auto elapsed = now - date;
|
||||
auto next = TimeId();
|
||||
const auto text = [&] {
|
||||
if (elapsed < 60) {
|
||||
next = 60 - elapsed;
|
||||
return tr::lng_live_location_now(tr::now);
|
||||
} else if (const auto minutes = elapsed / 60; minutes < 60) {
|
||||
next = 60 - (elapsed % 60);
|
||||
return tr::lng_live_location_minutes(tr::now, lt_count, minutes);
|
||||
} else if (const auto hours = elapsed / 3600; hours < 12) {
|
||||
next = 3600 - (elapsed % 3600);
|
||||
return tr::lng_live_location_hours(tr::now, lt_count, hours);
|
||||
}
|
||||
const auto dateFull = base::unixtime::parse(date);
|
||||
const auto nowFull = base::unixtime::parse(now);
|
||||
const auto nextTomorrow = [&] {
|
||||
const auto tomorrow = nowFull.date().addDays(1);
|
||||
next = nowFull.secsTo(QDateTime(tomorrow, QTime(0, 0)));
|
||||
};
|
||||
const auto locale = QLocale();
|
||||
const auto format = QLocale::ShortFormat;
|
||||
if (dateFull.date() == nowFull.date()) {
|
||||
nextTomorrow();
|
||||
const auto time = locale.toString(dateFull.time(), format);
|
||||
return tr::lng_live_location_today(tr::now, lt_time, time);
|
||||
} else if (dateFull.date().addDays(1) == nowFull.date()) {
|
||||
nextTomorrow();
|
||||
const auto time = locale.toString(dateFull.time(), format);
|
||||
return tr::lng_live_location_yesterday(tr::now, lt_time, time);
|
||||
}
|
||||
return tr::lng_live_location_date_time(
|
||||
tr::now,
|
||||
lt_date,
|
||||
locale.toString(dateFull.date(), format),
|
||||
lt_time,
|
||||
locale.toString(dateFull.time(), format));
|
||||
}();
|
||||
_description.setMarkedText(
|
||||
st::webPageDescriptionStyle,
|
||||
{ text },
|
||||
Ui::WebpageTextDescriptionOptions());
|
||||
if (next > 0 && next < 86400) {
|
||||
_live->updateStatusTimer.callOnce(next * crl::time(1000));
|
||||
}
|
||||
}
|
||||
|
||||
QImage Location::locationTakeImage() {
|
||||
if (_media && !_media->isNull()) {
|
||||
return *_media;
|
||||
} else if (_live && !_live->previous.isNull()) {
|
||||
return _live->previous;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void Location::unloadHeavyPart() {
|
||||
_media = nullptr;
|
||||
if (_live) {
|
||||
_live->previous = QImage();
|
||||
}
|
||||
}
|
||||
|
||||
bool Location::hasHeavyPart() const {
|
||||
return (_media != nullptr);
|
||||
return (_media != nullptr) || (_live && !_live->previous.isNull());
|
||||
}
|
||||
|
||||
void Location::ensureMediaCreated() const {
|
||||
@ -99,11 +281,17 @@ QSize Location::countOptimalSize() {
|
||||
}
|
||||
if (!_title.isEmpty() || !_description.isEmpty()) {
|
||||
minHeight += st::mediaInBubbleSkip;
|
||||
if (_live) {
|
||||
if (isBubbleBottom()) {
|
||||
minHeight += st::msgPadding.bottom();
|
||||
}
|
||||
} else {
|
||||
if (isBubbleTop()) {
|
||||
minHeight += st::msgPadding.top();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { maxWidth, minHeight };
|
||||
}
|
||||
|
||||
@ -128,6 +316,9 @@ QSize Location::countCurrentSize(int newWidth) {
|
||||
std::min(newWidth, st::maxMediaSize));
|
||||
accumulate_max(newWidth, minWidth);
|
||||
accumulate_max(newHeight, st::minPhotoSize);
|
||||
if (_live) {
|
||||
_live->thumbnailHeight = newHeight;
|
||||
}
|
||||
if (_parent->hasBubble()) {
|
||||
if (!_title.isEmpty()) {
|
||||
newHeight += qMin(_title.countHeight(newWidth - st::msgPadding.left() - st::msgPadding.right()), st::webPageTitleFont->height * 2);
|
||||
@ -137,11 +328,17 @@ QSize Location::countCurrentSize(int newWidth) {
|
||||
}
|
||||
if (!_title.isEmpty() || !_description.isEmpty()) {
|
||||
newHeight += st::mediaInBubbleSkip;
|
||||
if (_live) {
|
||||
if (isBubbleBottom()) {
|
||||
newHeight += st::msgPadding.bottom();
|
||||
}
|
||||
} else {
|
||||
if (isBubbleTop()) {
|
||||
newHeight += st::msgPadding.top();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { newWidth, newHeight };
|
||||
}
|
||||
|
||||
@ -156,21 +353,28 @@ TextSelection Location::fromDescriptionSelection(
|
||||
}
|
||||
|
||||
void Location::draw(Painter &p, const PaintContext &context) const {
|
||||
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
|
||||
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
|
||||
return;
|
||||
}
|
||||
auto paintx = 0, painty = 0, paintw = width(), painth = height();
|
||||
bool bubble = _parent->hasBubble();
|
||||
const auto st = context.st;
|
||||
const auto stm = context.messageStyle();
|
||||
|
||||
const auto hasText = !_title.isEmpty() || !_description.isEmpty();
|
||||
const auto rounding = adjustedBubbleRounding(
|
||||
hasText ? RectPart::FullTop : RectPart());
|
||||
if (bubble) {
|
||||
if (hasText) {
|
||||
if (isBubbleTop()) {
|
||||
const auto rounding = adjustedBubbleRounding(_live
|
||||
? RectPart::FullBottom
|
||||
: hasText
|
||||
? RectPart::FullTop
|
||||
: RectPart());
|
||||
const auto paintText = [&] {
|
||||
if (_live) {
|
||||
painty += st::mediaInBubbleSkip;
|
||||
} else if (!hasText) {
|
||||
return;
|
||||
} else if (isBubbleTop()) {
|
||||
painty += st::msgPadding.top();
|
||||
}
|
||||
}
|
||||
|
||||
auto textw = width() - st::msgPadding.left() - st::msgPadding.right();
|
||||
|
||||
@ -180,24 +384,45 @@ void Location::draw(Painter &p, const PaintContext &context) const {
|
||||
painty += qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height);
|
||||
}
|
||||
if (!_description.isEmpty()) {
|
||||
if (_live) {
|
||||
p.setPen(stm->msgDateFg);
|
||||
}
|
||||
_description.drawLeftElided(p, paintx + st::msgPadding.left(), painty, textw, width(), 3, style::al_left, 0, -1, 0, false, toDescriptionSelection(context.selection));
|
||||
painty += qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height);
|
||||
}
|
||||
if (!_title.isEmpty() || !_description.isEmpty()) {
|
||||
if (!_live) {
|
||||
painty += st::mediaInBubbleSkip;
|
||||
}
|
||||
painth -= painty;
|
||||
}
|
||||
auto rthumb = QRect(paintx, painty, paintw, painth);
|
||||
};
|
||||
if (!_live) {
|
||||
paintText();
|
||||
}
|
||||
const auto thumbh = _live ? _live->thumbnailHeight : painth;
|
||||
auto rthumb = QRect(paintx, painty, paintw, thumbh);
|
||||
if (!bubble) {
|
||||
fillImageShadow(p, rthumb, rounding, context);
|
||||
}
|
||||
|
||||
ensureMediaCreated();
|
||||
validateImageCache(rthumb.size(), rounding);
|
||||
if (!_imageCache.isNull()) {
|
||||
const auto paintPrevious = _live && !_live->previous.isNull();
|
||||
auto opacity = _imageCache.isNull() ? 0. : 1.;
|
||||
if (paintPrevious) {
|
||||
opacity = _live->crossfade.value(opacity);
|
||||
if (opacity < 1.) {
|
||||
p.drawImage(rthumb.topLeft(), _live->previousCache);
|
||||
if (opacity > 0.) {
|
||||
p.setOpacity(opacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!_imageCache.isNull() && opacity > 0.) {
|
||||
p.drawImage(rthumb.topLeft(), _imageCache);
|
||||
} else if (!bubble) {
|
||||
if (opacity < 1.) {
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
} else if (!bubble && !paintPrevious) {
|
||||
Ui::PaintBubble(
|
||||
p,
|
||||
Ui::SimpleBubble{
|
||||
@ -223,8 +448,12 @@ void Location::draw(Painter &p, const PaintContext &context) const {
|
||||
if (context.selected()) {
|
||||
fillImageOverlay(p, rthumb, rounding, context);
|
||||
}
|
||||
|
||||
if (_parent->media() == this) {
|
||||
if (_live) {
|
||||
painty += _live->thumbnailHeight;
|
||||
painth -= _live->thumbnailHeight;
|
||||
paintLiveRemaining(p, context, { paintx, painty, paintw, painth });
|
||||
paintText();
|
||||
} else if (_parent->media() == this) {
|
||||
auto fullRight = paintx + paintw;
|
||||
auto fullBottom = height();
|
||||
_parent->drawInfo(
|
||||
@ -244,25 +473,114 @@ void Location::draw(Painter &p, const PaintContext &context) const {
|
||||
}
|
||||
}
|
||||
|
||||
void Location::paintLiveRemaining(
|
||||
QPainter &p,
|
||||
const PaintContext &context,
|
||||
QRect bottom) const {
|
||||
const auto size = st::liveLocationRemainingSize;
|
||||
const auto skip = (bottom.height() - size) / 2;
|
||||
const auto rect = QRect(
|
||||
bottom.x() + bottom.width() - size - skip,
|
||||
bottom.y() + skip,
|
||||
size,
|
||||
size);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto stm = context.messageStyle();
|
||||
const auto color = stm->msgServiceFg->c;
|
||||
const auto untiloff = (_live->period == kUntilOffPeriod);
|
||||
const auto progress = RemainingTimeProgress(_parent, _live->period);
|
||||
const auto part = 1. / 360;
|
||||
const auto full = (progress >= 1. - part);
|
||||
auto elapsed = color;
|
||||
if (!full) {
|
||||
elapsed.setAlphaF(elapsed.alphaF() * kLiveElapsedPartOpacity);
|
||||
}
|
||||
auto pen = QPen(elapsed);
|
||||
const auto stroke = style::ConvertScaleExact(2.);
|
||||
pen.setWidthF(stroke);
|
||||
p.setPen(pen);
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.drawEllipse(rect);
|
||||
|
||||
if (untiloff) {
|
||||
stm->liveLocationLongIcon.paintInCenter(p, rect);
|
||||
} else {
|
||||
if (!full && progress > part) {
|
||||
auto pen = QPen(color);
|
||||
pen.setWidthF(stroke);
|
||||
p.setPen(pen);
|
||||
p.drawArc(rect, 90 * 16, int(base::SafeRound(360 * 16 * progress)));
|
||||
}
|
||||
|
||||
p.setPen(stm->msgServiceFg);
|
||||
p.setFont(st::semiboldFont);
|
||||
const auto text = RemainingTimeText(_parent, _live->period);
|
||||
p.drawText(rect, text, style::al_center);
|
||||
const auto each = std::clamp(_live->period / 360, 1, 86400);
|
||||
_live->updateRemainingTimer.callOnce(each * crl::time(1000));
|
||||
}
|
||||
}
|
||||
|
||||
void Location::validateImageCache(
|
||||
QSize outer,
|
||||
Ui::BubbleRounding rounding) const {
|
||||
Expects(_media != nullptr);
|
||||
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
if ((_imageCache.size() == (outer * ratio)
|
||||
&& _imageCacheRounding == rounding)
|
||||
|| _media->isNull()) {
|
||||
if (_live && !_live->previous.isNull()) {
|
||||
validateImageCache(
|
||||
_live->previous,
|
||||
_live->previousCache,
|
||||
_live->previousRounding,
|
||||
outer,
|
||||
rounding);
|
||||
}
|
||||
validateImageCache(
|
||||
*_media,
|
||||
_imageCache,
|
||||
_imageCacheRounding,
|
||||
outer,
|
||||
rounding);
|
||||
checkLiveCrossfadeStart();
|
||||
}
|
||||
|
||||
void Location::checkLiveCrossfadeStart() const {
|
||||
if (!_live
|
||||
|| _live->previous.isNull()
|
||||
|| !_media
|
||||
|| _media->isNull()
|
||||
|| _live->crossfade.animating()) {
|
||||
return;
|
||||
}
|
||||
_imageCache = Images::Round(
|
||||
_media->scaled(
|
||||
_live->crossfade.start([=] {
|
||||
if (!_live->crossfade.animating()) {
|
||||
_live->previous = QImage();
|
||||
_live->previousCache = QImage();
|
||||
}
|
||||
_parent->repaint();
|
||||
}, 0., 1., st::fadeWrapDuration);
|
||||
}
|
||||
|
||||
void Location::validateImageCache(
|
||||
const QImage &source,
|
||||
QImage &cache,
|
||||
Ui::BubbleRounding &cacheRounding,
|
||||
QSize outer,
|
||||
Ui::BubbleRounding rounding) const {
|
||||
if (source.isNull()) {
|
||||
return;
|
||||
}
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
if (cache.size() == (outer * ratio) && cacheRounding == rounding) {
|
||||
return;
|
||||
}
|
||||
cache = Images::Round(
|
||||
source.scaled(
|
||||
outer * ratio,
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation),
|
||||
MediaRoundingMask(rounding));
|
||||
_imageCache.setDevicePixelRatio(ratio);
|
||||
_imageCacheRounding = rounding;
|
||||
cache.setDevicePixelRatio(ratio);
|
||||
cacheRounding = rounding;
|
||||
}
|
||||
|
||||
TextState Location::textState(QPoint point, StateRequest request) const {
|
||||
@ -275,12 +593,14 @@ TextState Location::textState(QPoint point, StateRequest request) const {
|
||||
auto paintx = 0, painty = 0, paintw = width(), painth = height();
|
||||
bool bubble = _parent->hasBubble();
|
||||
|
||||
if (bubble) {
|
||||
if (!_title.isEmpty() || !_description.isEmpty()) {
|
||||
if (isBubbleTop()) {
|
||||
auto checkText = [&] {
|
||||
if (_live) {
|
||||
painty += st::mediaInBubbleSkip;
|
||||
} else if (_title.isEmpty() && _description.isEmpty()) {
|
||||
return false;
|
||||
} else if (isBubbleTop()) {
|
||||
painty += st::msgPadding.top();
|
||||
}
|
||||
}
|
||||
|
||||
auto textw = width() - st::msgPadding.left() - st::msgPadding.right();
|
||||
|
||||
@ -292,7 +612,7 @@ TextState Location::textState(QPoint point, StateRequest request) const {
|
||||
textw,
|
||||
width(),
|
||||
request.forText()));
|
||||
return result;
|
||||
return true;
|
||||
} else if (point.y() >= painty + titleh) {
|
||||
symbolAdd += _title.length();
|
||||
}
|
||||
@ -306,6 +626,8 @@ TextState Location::textState(QPoint point, StateRequest request) const {
|
||||
textw,
|
||||
width(),
|
||||
request.forText()));
|
||||
result.symbol += symbolAdd;
|
||||
return true;
|
||||
} else if (point.y() >= painty + descriptionh) {
|
||||
symbolAdd += _description.length();
|
||||
}
|
||||
@ -315,11 +637,22 @@ TextState Location::textState(QPoint point, StateRequest request) const {
|
||||
painty += st::mediaInBubbleSkip;
|
||||
}
|
||||
painth -= painty;
|
||||
return false;
|
||||
};
|
||||
if (!_live && checkText()) {
|
||||
return result;
|
||||
}
|
||||
if (QRect(paintx, painty, paintw, painth).contains(point) && _data) {
|
||||
const auto thumbh = _live ? _live->thumbnailHeight : painth;
|
||||
if (QRect(paintx, painty, paintw, thumbh).contains(point) && _data) {
|
||||
result.link = _link;
|
||||
}
|
||||
if (_parent->media() == this) {
|
||||
if (_live) {
|
||||
painty += _live->thumbnailHeight;
|
||||
painth -= _live->thumbnailHeight;
|
||||
if (checkText()) {
|
||||
return result;
|
||||
}
|
||||
} else if (_parent->media() == this) {
|
||||
auto fullRight = paintx + paintw;
|
||||
auto fullBottom = height();
|
||||
const auto bottomInfoResult = _parent->bottomInfoTextState(
|
||||
|
@ -22,8 +22,14 @@ public:
|
||||
not_null<Element*> parent,
|
||||
not_null<Data::CloudImage*> data,
|
||||
Data::LocationPoint point,
|
||||
const QString &title = QString(),
|
||||
const QString &description = QString());
|
||||
Element *replacing = nullptr,
|
||||
TimeId livePeriod = 0);
|
||||
Location(
|
||||
not_null<Element*> parent,
|
||||
not_null<Data::CloudImage*> data,
|
||||
Data::LocationPoint point,
|
||||
const QString &title,
|
||||
const QString &description);
|
||||
~Location();
|
||||
|
||||
void draw(Painter &p, const PaintContext &context) const override;
|
||||
@ -58,15 +64,33 @@ public:
|
||||
return isRoundedInBubbleBottom();
|
||||
}
|
||||
|
||||
QImage locationTakeImage() override;
|
||||
|
||||
void unloadHeavyPart() override;
|
||||
bool hasHeavyPart() const override;
|
||||
|
||||
private:
|
||||
struct Live;
|
||||
[[nodiscard]] static std::unique_ptr<Live> CreateLiveTracker(
|
||||
not_null<Element*> parent,
|
||||
TimeId period);
|
||||
|
||||
void ensureMediaCreated() const;
|
||||
|
||||
void validateImageCache(
|
||||
QSize outer,
|
||||
Ui::BubbleRounding rounding) const;
|
||||
void validateImageCache(
|
||||
const QImage &source,
|
||||
QImage &cache,
|
||||
Ui::BubbleRounding &cacheRounding,
|
||||
QSize outer,
|
||||
Ui::BubbleRounding rounding) const;
|
||||
|
||||
void paintLiveRemaining(
|
||||
QPainter &p,
|
||||
const PaintContext &context,
|
||||
QRect bottom) const;
|
||||
|
||||
QSize countOptimalSize() override;
|
||||
QSize countCurrentSize(int newWidth) override;
|
||||
@ -79,7 +103,12 @@ private:
|
||||
[[nodiscard]] int fullWidth() const;
|
||||
[[nodiscard]] int fullHeight() const;
|
||||
|
||||
void checkLiveCrossfadeStart() const;
|
||||
void updateLiveStatus();
|
||||
void checkLiveFinish();
|
||||
|
||||
const not_null<Data::CloudImage*> _data;
|
||||
mutable std::unique_ptr<Live> _live;
|
||||
mutable std::shared_ptr<QImage> _media;
|
||||
Ui::Text::String _title, _description;
|
||||
ClickHandlerPtr _link;
|
||||
|
@ -356,6 +356,10 @@ std::unique_ptr<StickerPlayer> Media::stickerTakePlayer(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QImage Media::locationTakeImage() {
|
||||
return QImage();
|
||||
}
|
||||
|
||||
TextState Media::getStateGrouped(
|
||||
const QRect &geometry,
|
||||
RectParts sides,
|
||||
|
@ -190,6 +190,7 @@ public:
|
||||
virtual std::unique_ptr<StickerPlayer> stickerTakePlayer(
|
||||
not_null<DocumentData*> data,
|
||||
const Lottie::ColorReplacements *replacements);
|
||||
virtual QImage locationTakeImage();
|
||||
virtual void checkAnimation() {
|
||||
}
|
||||
|
||||
|
@ -1070,3 +1070,9 @@ chatIntroWidth: 224px;
|
||||
chatIntroTitleMargin: margins(11px, 16px, 11px, 4px);
|
||||
chatIntroMargin: margins(11px, 0px, 11px, 0px);
|
||||
chatIntroStickerPadding: margins(10px, 8px, 10px, 16px);
|
||||
|
||||
liveLocationLongInIcon: icon {{ "chat/live_location_long", msgInServiceFg }};
|
||||
liveLocationLongInIconSelected: icon {{ "chat/live_location_long", msgInServiceFgSelected }};
|
||||
liveLocationLongOutIcon: icon {{ "chat/live_location_long", msgOutServiceFg }};
|
||||
liveLocationLongOutIconSelected: icon {{ "chat/live_location_long", msgOutServiceFgSelected }};
|
||||
liveLocationRemainingSize: 28px;
|
||||
|
@ -543,6 +543,12 @@ ChatStyle::ChatStyle(rpl::producer<ColorIndicesCompressed> colorIndices) {
|
||||
st::historyVoiceMessageInTTLSelected,
|
||||
st::historyVoiceMessageOutTTL,
|
||||
st::historyVoiceMessageOutTTLSelected);
|
||||
make(
|
||||
&MessageStyle::liveLocationLongIcon,
|
||||
st::liveLocationLongInIcon,
|
||||
st::liveLocationLongInIconSelected,
|
||||
st::liveLocationLongOutIcon,
|
||||
st::liveLocationLongOutIconSelected);
|
||||
|
||||
updateDarkValue();
|
||||
}
|
||||
|
@ -92,6 +92,7 @@ struct MessageStyle {
|
||||
style::icon historyTranscribeLock = { Qt::Uninitialized };
|
||||
style::icon historyTranscribeHide = { Qt::Uninitialized };
|
||||
style::icon historyVoiceMessageTTL = { Qt::Uninitialized };
|
||||
style::icon liveLocationLongIcon = { Qt::Uninitialized };
|
||||
std::array<
|
||||
std::unique_ptr<Text::QuotePaintCache>,
|
||||
kColorPatternsCount> quoteCache;
|
||||
|
@ -1510,22 +1510,82 @@ mac:
|
||||
""")
|
||||
|
||||
if buildQt6:
|
||||
stage('qt_6_2_8', """
|
||||
qt6version = '6.2.8' if mac else '6.7.0'
|
||||
qt6version_ = '6_2_8' if mac else '6_7_0'
|
||||
arg0 = 'qt_' + qt6version_
|
||||
arg1 = qt6version
|
||||
|
||||
stage('qt_' + qt6version_, """
|
||||
win:
|
||||
git clone -b v{1} https://github.com/qt/qt5.git {0}
|
||||
mac:
|
||||
git clone -b v6.2.8-lts-lgpl https://github.com/qt/qt5.git qt_6_2_8
|
||||
cd qt_6_2_8
|
||||
git clone -b v{1}-lts-lgpl https://github.com/qt/qt5.git {0}
|
||||
common:
|
||||
cd {0}
|
||||
git submodule update --init --recursive qtbase qtimageformats qtsvg
|
||||
depends:patches/qtbase_6.2.8/*.patch
|
||||
depends:patches/qtbase_{1}/*.patch
|
||||
cd qtbase
|
||||
find ../../patches/qtbase_6.2.8 -type f -print0 | sort -z | xargs -0 git apply -v
|
||||
win:
|
||||
for /r %%i in (..\\..\\patches\\qtbase_{1}\\*) do git apply %%i -v
|
||||
cd ..
|
||||
sed -i.bak 's/tqtc-//' {qtimageformats,qtsvg}/dependencies.yaml
|
||||
|
||||
SET CONFIGURATIONS=-debug
|
||||
release:
|
||||
SET CONFIGURATIONS=-debug-and-release
|
||||
win:
|
||||
""".format(arg0, arg1) + removeDir("\"%LIBS_DIR%\\Qt-\"" + qt6version) + """
|
||||
SET ANGLE_DIR=%LIBS_DIR%\\tg_angle
|
||||
SET ANGLE_LIBS_DIR=%ANGLE_DIR%\\out
|
||||
SET MOZJPEG_DIR=%LIBS_DIR%\\mozjpeg
|
||||
SET OPENSSL_DIR=%LIBS_DIR%\\openssl3
|
||||
SET OPENSSL_LIBS_DIR=%OPENSSL_DIR%\\out
|
||||
SET ZLIB_LIBS_DIR=%LIBS_DIR%\\zlib
|
||||
SET WEBP_DIR=%LIBS_DIR%\\libwebp
|
||||
configure -prefix "%LIBS_DIR%\\Qt-{1}" ^
|
||||
%CONFIGURATIONS% ^
|
||||
-force-debug-info ^
|
||||
-opensource ^
|
||||
-confirm-license ^
|
||||
-static ^
|
||||
-static-runtime ^
|
||||
-opengl es2 -no-angle ^
|
||||
-I "%ANGLE_DIR%\\include" ^
|
||||
-D "KHRONOS_STATIC=" ^
|
||||
-D "DESKTOP_APP_QT_STATIC_ANGLE=" ^
|
||||
QMAKE_LIBS_OPENGL_ES2_DEBUG="%ANGLE_LIBS_DIR%\\Debug\\tg_angle.lib %ZLIB_LIBS_DIR%\\Debug\\zlibstaticd.lib d3d9.lib dxgi.lib dxguid.lib" ^
|
||||
QMAKE_LIBS_OPENGL_ES2_RELEASE="%ANGLE_LIBS_DIR%\\Release\\tg_angle.lib %ZLIB_LIBS_DIR%\\Release\\zlibstatic.lib d3d9.lib dxgi.lib dxguid.lib" ^
|
||||
-egl ^
|
||||
QMAKE_LIBS_EGL_DEBUG="%ANGLE_LIBS_DIR%\\Debug\\tg_angle.lib %ZLIB_LIBS_DIR%\\Debug\\zlibstaticd.lib d3d9.lib dxgi.lib dxguid.lib Gdi32.lib User32.lib" ^
|
||||
QMAKE_LIBS_EGL_RELEASE="%ANGLE_LIBS_DIR%\\Release\\tg_angle.lib %ZLIB_LIBS_DIR%\\Release\\zlibstatic.lib d3d9.lib dxgi.lib dxguid.lib Gdi32.lib User32.lib" ^
|
||||
-openssl-linked ^
|
||||
-I "%OPENSSL_DIR%\\include" ^
|
||||
OPENSSL_LIBS_DEBUG="%OPENSSL_LIBS_DIR%.dbg\\libssl.lib %OPENSSL_LIBS_DIR%.dbg\\libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib" ^
|
||||
OPENSSL_LIBS_RELEASE="%OPENSSL_LIBS_DIR%\\libssl.lib %OPENSSL_LIBS_DIR%\\libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib" ^
|
||||
-I "%MOZJPEG_DIR%" ^
|
||||
LIBJPEG_LIBS_DEBUG="%MOZJPEG_DIR%\\Debug\\jpeg-static.lib" ^
|
||||
LIBJPEG_LIBS_RELEASE="%MOZJPEG_DIR%\\Release\\jpeg-static.lib" ^
|
||||
-system-webp ^
|
||||
-I "%WEBP_DIR%\\src" ^
|
||||
-L "%WEBP_DIR%\\out\\release-static\\$X8664\\lib" ^
|
||||
-mp ^
|
||||
-no-feature-netlistmgr ^
|
||||
-nomake examples ^
|
||||
-nomake tests ^
|
||||
-platform win32-msvc
|
||||
|
||||
jom -j%NUMBER_OF_PROCESSORS%
|
||||
jom -j%NUMBER_OF_PROCESSORS% install
|
||||
|
||||
mac:
|
||||
find ../../patches/qtbase_{1} -type f -print0 | sort -z | xargs -0 git apply -v
|
||||
cd ..
|
||||
sed -i.bak 's/tqtc-//' {{qtimageformats,qtsvg}}/dependencies.yaml
|
||||
|
||||
CONFIGURATIONS=-debug
|
||||
release:
|
||||
CONFIGURATIONS=-debug-and-release
|
||||
mac:
|
||||
./configure -prefix "$USED_PREFIX/Qt-6.2.8" \
|
||||
./configure -prefix "$USED_PREFIX/Qt-{1}" \
|
||||
$CONFIGURATIONS \
|
||||
-force-debug-info \
|
||||
-opensource \
|
||||
@ -1546,7 +1606,7 @@ mac:
|
||||
|
||||
ninja
|
||||
ninja install
|
||||
""")
|
||||
""".format(arg0, arg1))
|
||||
|
||||
stage('tg_owt', """
|
||||
git clone https://github.com/desktop-app/tg_owt.git
|
||||
|
Loading…
Reference in New Issue
Block a user