mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-07 23:53:58 +02:00
Add carousel animation for emoji fingerprint.
This commit is contained in:
parent
b1b2798be1
commit
29d87f692a
15 changed files with 644 additions and 103 deletions
|
@ -4967,6 +4967,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_confcall_not_accessible" = "This call is no longer accessible.";
|
"lng_confcall_not_accessible" = "This call is no longer accessible.";
|
||||||
"lng_confcall_participants_limit" = "This call reached the participants limit.";
|
"lng_confcall_participants_limit" = "This call reached the participants limit.";
|
||||||
"lng_confcall_sure_remove" = "Remove {user} from the call?";
|
"lng_confcall_sure_remove" = "Remove {user} from the call?";
|
||||||
|
"lng_confcall_e2e_badge" = "End-to-End Encrypted";
|
||||||
|
"lng_confcall_e2e_badge_small" = "E2E Encrypted";
|
||||||
|
|
||||||
"lng_no_mic_permission" = "Telegram needs microphone access so that you can make calls and record voice messages.";
|
"lng_no_mic_permission" = "Telegram needs microphone access so that you can make calls and record voice messages.";
|
||||||
|
|
||||||
|
|
|
@ -1316,7 +1316,7 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) {
|
||||||
user->madeAction(base::unixtime::now());
|
user->madeAction(base::unixtime::now());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClearMediaAsExpired(item);
|
item->clearMediaAsExpired();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Perhaps it was an unread mention!
|
// Perhaps it was an unread mention!
|
||||||
|
|
|
@ -1669,3 +1669,14 @@ groupCallLinkMenu: IconButton(confcallLinkMenu) {
|
||||||
color: groupCallMembersBgOver;
|
color: groupCallMembersBgOver;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
confcallFingerprintBottomSkip: 8px;
|
||||||
|
confcallFingerprintMargins: margins(8px, 5px, 8px, 5px);
|
||||||
|
confcallFingerprintTextMargins: margins(3px, 3px, 3px, 0px);
|
||||||
|
confcallFingerprintText: FlatLabel(defaultFlatLabel) {
|
||||||
|
textFg: groupCallMembersFg;
|
||||||
|
style: TextStyle(defaultTextStyle) {
|
||||||
|
font: font(10px semibold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
confcallFingerprintSkip: 2px;
|
||||||
|
|
|
@ -7,11 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "calls/calls_emoji_fingerprint.h"
|
#include "calls/calls_emoji_fingerprint.h"
|
||||||
|
|
||||||
|
#include "base/random.h"
|
||||||
#include "calls/calls_call.h"
|
#include "calls/calls_call.h"
|
||||||
#include "calls/calls_signal_bars.h"
|
#include "calls/calls_signal_bars.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
|
#include "ui/widgets/labels.h"
|
||||||
#include "ui/widgets/tooltip.h"
|
#include "ui/widgets/tooltip.h"
|
||||||
|
#include "ui/abstract_button.h"
|
||||||
#include "ui/emoji_config.h"
|
#include "ui/emoji_config.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "ui/rp_widget.h"
|
#include "ui/rp_widget.h"
|
||||||
|
@ -20,7 +23,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace Calls {
|
namespace Calls {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kTooltipShowTimeoutMs = 1000;
|
constexpr auto kTooltipShowTimeoutMs = crl::time(1000);
|
||||||
|
constexpr auto kCarouselOneDuration = crl::time(1000);// crl::time(100);
|
||||||
|
constexpr auto kStartTimeShift = crl::time(500);// crl::time(50);
|
||||||
|
constexpr auto kEmojiInFingerprint = 4;
|
||||||
|
constexpr auto kEmojiInCarousel = 10;
|
||||||
|
|
||||||
const ushort Data[] = {
|
const ushort Data[] = {
|
||||||
0xd83d, 0xde09, 0xd83d, 0xde0d, 0xd83d, 0xde1b, 0xd83d, 0xde2d, 0xd83d, 0xde31, 0xd83d, 0xde21,
|
0xd83d, 0xde09, 0xd83d, 0xde0d, 0xd83d, 0xde1b, 0xd83d, 0xde2d, 0xd83d, 0xde31, 0xd83d, 0xde21,
|
||||||
|
@ -109,8 +116,11 @@ const ushort Offsets[] = {
|
||||||
620, 622, 624, 626, 628, 630, 632, 634, 636, 638, 640, 641,
|
620, 622, 624, 626, 628, 630, 632, 634, 636, 638, 640, 641,
|
||||||
642, 643, 644, 646, 648, 650, 652, 654, 656, 658 };
|
642, 643, 644, 646, 648, 650, 652, 654, 656, 658 };
|
||||||
|
|
||||||
|
constexpr auto kEmojiCount = (base::array_size(Offsets) - 1);
|
||||||
|
|
||||||
uint64 ComputeEmojiIndex(bytes::const_span bytes) {
|
uint64 ComputeEmojiIndex(bytes::const_span bytes) {
|
||||||
Expects(bytes.size() == 8);
|
Expects(bytes.size() == 8);
|
||||||
|
|
||||||
return ((gsl::to_integer<uint64>(bytes[0]) & 0x7F) << 56)
|
return ((gsl::to_integer<uint64>(bytes[0]) & 0x7F) << 56)
|
||||||
| (gsl::to_integer<uint64>(bytes[1]) << 48)
|
| (gsl::to_integer<uint64>(bytes[1]) << 48)
|
||||||
| (gsl::to_integer<uint64>(bytes[2]) << 40)
|
| (gsl::to_integer<uint64>(bytes[2]) << 40)
|
||||||
|
@ -121,6 +131,17 @@ uint64 ComputeEmojiIndex(bytes::const_span bytes) {
|
||||||
| (gsl::to_integer<uint64>(bytes[7]));
|
| (gsl::to_integer<uint64>(bytes[7]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] EmojiPtr EmojiByIndex(int index) {
|
||||||
|
Expects(index >= 0 && index < kEmojiCount);
|
||||||
|
|
||||||
|
const auto offset = Offsets[index];
|
||||||
|
const auto size = Offsets[index + 1] - offset;
|
||||||
|
const auto string = QString::fromRawData(
|
||||||
|
reinterpret_cast<const QChar*>(Data + offset),
|
||||||
|
size);
|
||||||
|
return Ui::Emoji::Find(string);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
std::vector<EmojiPtr> ComputeEmojiFingerprint(not_null<Call*> call) {
|
std::vector<EmojiPtr> ComputeEmojiFingerprint(not_null<Call*> call) {
|
||||||
|
@ -133,22 +154,13 @@ std::vector<EmojiPtr> ComputeEmojiFingerprint(not_null<Call*> call) {
|
||||||
std::vector<EmojiPtr> ComputeEmojiFingerprint(
|
std::vector<EmojiPtr> ComputeEmojiFingerprint(
|
||||||
bytes::const_span fingerprint) {
|
bytes::const_span fingerprint) {
|
||||||
auto result = std::vector<EmojiPtr>();
|
auto result = std::vector<EmojiPtr>();
|
||||||
constexpr auto EmojiCount = (base::array_size(Offsets) - 1);
|
|
||||||
constexpr auto kPartSize = 8;
|
constexpr auto kPartSize = 8;
|
||||||
for (auto partOffset = 0
|
for (auto partOffset = 0
|
||||||
; partOffset != fingerprint.size()
|
; partOffset != fingerprint.size()
|
||||||
; partOffset += kPartSize) {
|
; partOffset += kPartSize) {
|
||||||
auto value = ComputeEmojiIndex(
|
const auto value = ComputeEmojiIndex(
|
||||||
fingerprint.subspan(partOffset, kPartSize));
|
fingerprint.subspan(partOffset, kPartSize));
|
||||||
auto index = value % EmojiCount;
|
result.push_back(EmojiByIndex(value % kEmojiCount));
|
||||||
auto offset = Offsets[index];
|
|
||||||
auto size = Offsets[index + 1] - offset;
|
|
||||||
auto string = QString::fromRawData(
|
|
||||||
reinterpret_cast<const QChar*>(Data + offset),
|
|
||||||
size);
|
|
||||||
auto emoji = Ui::Emoji::Find(string);
|
|
||||||
Assert(emoji != nullptr);
|
|
||||||
result.push_back(emoji);
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -294,4 +306,407 @@ base::unique_qptr<Ui::RpWidget> CreateFingerprintAndSignalBars(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FingerprintBadge SetupFingerprintBadge(
|
||||||
|
rpl::lifetime &on,
|
||||||
|
rpl::producer<QByteArray> fingerprint) {
|
||||||
|
struct State {
|
||||||
|
FingerprintBadgeState data;
|
||||||
|
Ui::Animations::Basic animation;
|
||||||
|
Fn<void(crl::time)> update;
|
||||||
|
rpl::event_stream<> repaints;
|
||||||
|
};
|
||||||
|
const auto state = on.make_state<State>();
|
||||||
|
|
||||||
|
state->update = [=](crl::time now) {
|
||||||
|
// speed-up-duration = 2 * one / speed.
|
||||||
|
const auto one = 1.;
|
||||||
|
const auto speedUpDuration = 2 * kCarouselOneDuration;
|
||||||
|
const auto speed0 = one / kCarouselOneDuration;
|
||||||
|
|
||||||
|
auto updated = false;
|
||||||
|
auto animating = false;
|
||||||
|
for (auto &entry : state->data.entries) {
|
||||||
|
if (!entry.time) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
animating = true;
|
||||||
|
if (entry.time >= now) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
updated = true;
|
||||||
|
const auto elapsed = (now - entry.time) * 1.;
|
||||||
|
entry.time = now;
|
||||||
|
|
||||||
|
Assert(!entry.emoji || entry.sliding.size() > 1);
|
||||||
|
const auto slideCount = entry.emoji
|
||||||
|
? (int(entry.sliding.size()) - 1) * one
|
||||||
|
: (kEmojiInCarousel + (elapsed / kCarouselOneDuration));
|
||||||
|
const auto finalPosition = slideCount * one;
|
||||||
|
const auto distance = finalPosition - entry.position;
|
||||||
|
|
||||||
|
const auto accelerate0 = speed0 - entry.speed;
|
||||||
|
const auto decelerate0 = speed0;
|
||||||
|
const auto acceleration0 = speed0 / speedUpDuration;
|
||||||
|
const auto taccelerate0 = accelerate0 / acceleration0;
|
||||||
|
const auto tdecelerate0 = decelerate0 / acceleration0;
|
||||||
|
const auto paccelerate0 = entry.speed * taccelerate0
|
||||||
|
+ acceleration0 * taccelerate0 * taccelerate0 / 2.;
|
||||||
|
const auto pdecelerate0 = 0
|
||||||
|
+ acceleration0 * tdecelerate0 * tdecelerate0 / 2.;
|
||||||
|
const auto ttozero = entry.speed / acceleration0;
|
||||||
|
if (paccelerate0 + pdecelerate0 <= distance) {
|
||||||
|
// We have time to accelerate to speed0,
|
||||||
|
// maybe go some time on speed0 and then decelerate to 0.
|
||||||
|
const auto uaccelerate0 = std::min(taccelerate0, elapsed);
|
||||||
|
const auto left = distance - paccelerate0 - pdecelerate0;
|
||||||
|
const auto tconstant = left / speed0;
|
||||||
|
const auto uconstant = std::min(
|
||||||
|
tconstant,
|
||||||
|
elapsed - uaccelerate0);
|
||||||
|
const auto udecelerate0 = std::min(
|
||||||
|
tdecelerate0,
|
||||||
|
elapsed - uaccelerate0 - uconstant);
|
||||||
|
if (udecelerate0 >= tdecelerate0) {
|
||||||
|
Assert(entry.emoji != nullptr);
|
||||||
|
entry = { .emoji = entry.emoji };
|
||||||
|
} else {
|
||||||
|
entry.position += entry.speed * uaccelerate0
|
||||||
|
+ acceleration0 * uaccelerate0 * uaccelerate0 / 2.
|
||||||
|
+ speed0 * uconstant
|
||||||
|
+ speed0 * udecelerate0
|
||||||
|
- acceleration0 * udecelerate0 * udecelerate0 / 2.;
|
||||||
|
entry.speed += acceleration0
|
||||||
|
* (uaccelerate0 - udecelerate0);
|
||||||
|
}
|
||||||
|
} else if (acceleration0 * ttozero * ttozero / 2 <= distance) {
|
||||||
|
// We have time to accelerate at least for some time >= 0,
|
||||||
|
// and then decelerate to 0 to make it to final position.
|
||||||
|
//
|
||||||
|
// peak = entry.speed + acceleration0 * t
|
||||||
|
// tdecelerate = peak / acceleration0
|
||||||
|
// distance = entry.speed * t
|
||||||
|
// + acceleration0 * t * t / 2
|
||||||
|
// + acceleration0 * tdecelerate * tdecelerate / 2
|
||||||
|
const auto det = entry.speed * entry.speed / 2
|
||||||
|
+ distance * acceleration0;
|
||||||
|
const auto t = std::max(
|
||||||
|
(sqrt(det) - entry.speed) / acceleration0,
|
||||||
|
0.);
|
||||||
|
|
||||||
|
const auto taccelerate = t;
|
||||||
|
const auto uaccelerate = std::min(taccelerate, elapsed);
|
||||||
|
const auto tdecelerate = t + (entry.speed / acceleration0);
|
||||||
|
const auto udecelerate = std::min(
|
||||||
|
tdecelerate,
|
||||||
|
elapsed - uaccelerate);
|
||||||
|
if (udecelerate >= tdecelerate) {
|
||||||
|
Assert(entry.emoji != nullptr);
|
||||||
|
entry = { .emoji = entry.emoji };
|
||||||
|
} else {
|
||||||
|
const auto topspeed = entry.speed
|
||||||
|
+ acceleration0 * taccelerate;
|
||||||
|
entry.position += entry.speed * uaccelerate
|
||||||
|
+ acceleration0 * uaccelerate * uaccelerate / 2.
|
||||||
|
+ topspeed * udecelerate
|
||||||
|
- acceleration0 * udecelerate * udecelerate / 2.;
|
||||||
|
entry.speed += acceleration0
|
||||||
|
* (uaccelerate - udecelerate);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We just need to decelerate to 0,
|
||||||
|
// faster than acceleration0.
|
||||||
|
Assert(entry.speed > 0);
|
||||||
|
const auto tdecelerate = 2 * distance / entry.speed;
|
||||||
|
const auto udecelerate = std::min(tdecelerate, elapsed);
|
||||||
|
if (udecelerate >= tdecelerate) {
|
||||||
|
Assert(entry.emoji != nullptr);
|
||||||
|
entry = { .emoji = entry.emoji };
|
||||||
|
} else {
|
||||||
|
const auto a = entry.speed / tdecelerate;
|
||||||
|
entry.position += entry.speed * udecelerate
|
||||||
|
- a * udecelerate * udecelerate / 2;
|
||||||
|
entry.speed -= a * udecelerate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.position >= kEmojiInCarousel) {
|
||||||
|
entry.position -= qFloor(entry.position / kEmojiInCarousel)
|
||||||
|
* kEmojiInCarousel;
|
||||||
|
}
|
||||||
|
while (entry.position >= 1.) {
|
||||||
|
Assert(!entry.sliding.empty());
|
||||||
|
entry.position -= 1.;
|
||||||
|
entry.sliding.erase(begin(entry.sliding));
|
||||||
|
if (entry.emoji && entry.sliding.size() < 2) {
|
||||||
|
entry = { .emoji = entry.emoji };
|
||||||
|
break;
|
||||||
|
} else if (entry.sliding.empty()) {
|
||||||
|
const auto index = (entry.added++) % kEmojiInCarousel;
|
||||||
|
entry.sliding.push_back(entry.carousel[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!entry.emoji
|
||||||
|
&& entry.position > 0.
|
||||||
|
&& entry.sliding.size() < 2) {
|
||||||
|
const auto index = (entry.added++) % kEmojiInCarousel;
|
||||||
|
entry.sliding.push_back(entry.carousel[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!animating) {
|
||||||
|
state->animation.stop();
|
||||||
|
} else if (updated) {
|
||||||
|
state->repaints.fire({});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
state->animation.init(state->update);
|
||||||
|
state->data.entries.resize(kEmojiInFingerprint);
|
||||||
|
|
||||||
|
const auto fillCarousel = [=](
|
||||||
|
int index,
|
||||||
|
base::BufferedRandom<uint32> &buffered) {
|
||||||
|
auto &entry = state->data.entries[index];
|
||||||
|
auto indices = std::vector<int>();
|
||||||
|
indices.reserve(kEmojiInCarousel);
|
||||||
|
auto count = kEmojiCount;
|
||||||
|
for (auto i = 0; i != kEmojiInCarousel; ++i, --count) {
|
||||||
|
auto index = base::RandomIndex(count, buffered);
|
||||||
|
for (const auto &already : indices) {
|
||||||
|
if (index >= already) {
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
indices.push_back(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.carousel.clear();
|
||||||
|
entry.carousel.reserve(kEmojiInCarousel);
|
||||||
|
for (const auto index : indices) {
|
||||||
|
entry.carousel.push_back(EmojiByIndex(index));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto startTo = [=](
|
||||||
|
int index,
|
||||||
|
EmojiPtr emoji,
|
||||||
|
crl::time now,
|
||||||
|
base::BufferedRandom<uint32> &buffered) {
|
||||||
|
auto &entry = state->data.entries[index];
|
||||||
|
if ((entry.emoji == emoji) && (emoji || entry.time)) {
|
||||||
|
return;
|
||||||
|
} else if (!entry.time) {
|
||||||
|
Assert(entry.sliding.empty());
|
||||||
|
|
||||||
|
if (entry.emoji) {
|
||||||
|
entry.sliding.push_back(entry.emoji);
|
||||||
|
} else if (emoji) {
|
||||||
|
// Just initialize if we get emoji right from the start.
|
||||||
|
entry.emoji = emoji;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
entry.time = now + index * kStartTimeShift;
|
||||||
|
|
||||||
|
fillCarousel(index, buffered);
|
||||||
|
}
|
||||||
|
entry.emoji = emoji;
|
||||||
|
if (entry.emoji) {
|
||||||
|
entry.sliding.push_back(entry.emoji);
|
||||||
|
} else {
|
||||||
|
const auto index = (entry.added++) % kEmojiInCarousel;
|
||||||
|
entry.sliding.push_back(entry.carousel[index]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::move(
|
||||||
|
fingerprint
|
||||||
|
) | rpl::start_with_next([=](const QByteArray &fingerprint) {
|
||||||
|
auto buffered = base::BufferedRandom<uint32>(
|
||||||
|
kEmojiInCarousel * kEmojiInFingerprint);
|
||||||
|
const auto now = crl::now();
|
||||||
|
const auto emoji = (fingerprint.size() >= 32)
|
||||||
|
? ComputeEmojiFingerprint(
|
||||||
|
bytes::make_span(fingerprint).subspan(0, 32))
|
||||||
|
: std::vector<EmojiPtr>();
|
||||||
|
state->update(now);
|
||||||
|
|
||||||
|
if (emoji.size() == kEmojiInFingerprint) {
|
||||||
|
for (auto i = 0; i != kEmojiInFingerprint; ++i) {
|
||||||
|
startTo(i, emoji[i], now, buffered);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (auto i = 0; i != kEmojiInFingerprint; ++i) {
|
||||||
|
startTo(i, nullptr, now, buffered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!state->animation.animating()) {
|
||||||
|
state->animation.start();
|
||||||
|
}
|
||||||
|
}, on);
|
||||||
|
|
||||||
|
return { .state = &state->data, .repaints = state->repaints.events() };
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetupFingerprintBadgeWidget(
|
||||||
|
not_null<Ui::RpWidget*> widget,
|
||||||
|
not_null<const FingerprintBadgeState*> state,
|
||||||
|
rpl::producer<> repaints) {
|
||||||
|
auto &lifetime = widget->lifetime();
|
||||||
|
|
||||||
|
const auto button = Ui::CreateChild<Ui::AbstractButton>(widget);
|
||||||
|
button->show();
|
||||||
|
|
||||||
|
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
|
button,
|
||||||
|
QString(),
|
||||||
|
st::confcallFingerprintText);
|
||||||
|
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
label->show();
|
||||||
|
|
||||||
|
const auto ratio = style::DevicePixelRatio();
|
||||||
|
const auto esize = Ui::Emoji::GetSizeNormal();
|
||||||
|
const auto size = esize / ratio;
|
||||||
|
widget->widthValue() | rpl::start_with_next([=](int width) {
|
||||||
|
static_assert(!(kEmojiInFingerprint % 2));
|
||||||
|
|
||||||
|
const auto available = width
|
||||||
|
- st::confcallFingerprintMargins.left()
|
||||||
|
- st::confcallFingerprintMargins.right()
|
||||||
|
- (kEmojiInFingerprint * size)
|
||||||
|
- (kEmojiInFingerprint - 2) * st::confcallFingerprintSkip
|
||||||
|
- st::confcallFingerprintTextMargins.left()
|
||||||
|
- st::confcallFingerprintTextMargins.right();
|
||||||
|
if (available <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
label->setText(tr::lng_confcall_e2e_badge(tr::now));
|
||||||
|
if (label->textMaxWidth() > available) {
|
||||||
|
label->setText(tr::lng_confcall_e2e_badge_small(tr::now));
|
||||||
|
}
|
||||||
|
const auto use = std::min(available, label->textMaxWidth());
|
||||||
|
label->resizeToWidth(use);
|
||||||
|
|
||||||
|
const auto ontheleft = kEmojiInFingerprint / 2;
|
||||||
|
const auto ontheside = ontheleft * size
|
||||||
|
+ (ontheleft - 1) * st::confcallFingerprintSkip;
|
||||||
|
const auto text = QRect(
|
||||||
|
(width - use) / 2,
|
||||||
|
(st::confcallFingerprintMargins.top()
|
||||||
|
+ st::confcallFingerprintTextMargins.top()),
|
||||||
|
use,
|
||||||
|
label->height());
|
||||||
|
const auto textOuter = text.marginsAdded(
|
||||||
|
st::confcallFingerprintTextMargins);
|
||||||
|
const auto withEmoji = QRect(
|
||||||
|
textOuter.x() - ontheside,
|
||||||
|
textOuter.y(),
|
||||||
|
textOuter.width() + ontheside * 2,
|
||||||
|
size);
|
||||||
|
const auto outer = withEmoji.marginsAdded(
|
||||||
|
st::confcallFingerprintMargins);
|
||||||
|
|
||||||
|
button->setGeometry(outer);
|
||||||
|
label->moveToLeft(text.x() - outer.x(), text.y() - outer.y(), width);
|
||||||
|
|
||||||
|
widget->resize(
|
||||||
|
width,
|
||||||
|
button->height() + st::confcallFingerprintBottomSkip);
|
||||||
|
}, lifetime);
|
||||||
|
|
||||||
|
const auto cache = lifetime.make_state<FingerprintBadgeCache>();
|
||||||
|
button->paintRequest() | rpl::start_with_next([=] {
|
||||||
|
auto p = QPainter(button);
|
||||||
|
|
||||||
|
const auto outer = button->rect();
|
||||||
|
const auto radius = outer.height() / 2.;
|
||||||
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
p.setBrush(st::groupCallMembersBg);
|
||||||
|
p.drawRoundedRect(outer, radius, radius);
|
||||||
|
p.setClipRect(outer);
|
||||||
|
|
||||||
|
const auto withEmoji = outer.marginsRemoved(
|
||||||
|
st::confcallFingerprintMargins);
|
||||||
|
p.translate(withEmoji.topLeft());
|
||||||
|
|
||||||
|
const auto text = label->geometry();
|
||||||
|
const auto textOuter = text.marginsAdded(
|
||||||
|
st::confcallFingerprintTextMargins);
|
||||||
|
const auto count = int(state->entries.size());
|
||||||
|
cache->entries.resize(count);
|
||||||
|
for (auto i = 0; i != count; ++i) {
|
||||||
|
PaintFingerprintEntry(
|
||||||
|
p,
|
||||||
|
state->entries[i],
|
||||||
|
cache->entries[i],
|
||||||
|
esize);
|
||||||
|
if (i + 1 == count / 2) {
|
||||||
|
p.translate(size + textOuter.width(), 0);
|
||||||
|
} else {
|
||||||
|
p.translate(size + st::confcallFingerprintSkip, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, lifetime);
|
||||||
|
|
||||||
|
std::move(repaints) | rpl::start_with_next([=] {
|
||||||
|
button->update();
|
||||||
|
}, lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PaintFingerprintEntry(
|
||||||
|
QPainter &p,
|
||||||
|
const FingerprintBadgeState::Entry &entry,
|
||||||
|
FingerprintBadgeCache::Entry &cache,
|
||||||
|
int esize) {
|
||||||
|
const auto stationary = !entry.time;
|
||||||
|
if (stationary) {
|
||||||
|
Ui::Emoji::Draw(p, entry.emoji, esize, 0, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto ratio = style::DevicePixelRatio();
|
||||||
|
const auto size = esize / ratio;
|
||||||
|
const auto add = 4;
|
||||||
|
const auto height = size + 2 * add;
|
||||||
|
const auto validateCache = [&](int index, EmojiPtr e) {
|
||||||
|
if (cache.emoji.size() <= index) {
|
||||||
|
cache.emoji.reserve(entry.carousel.size() + 2);
|
||||||
|
cache.emoji.resize(index + 1);
|
||||||
|
}
|
||||||
|
auto &emoji = cache.emoji[index];
|
||||||
|
if (emoji.ptr != e) {
|
||||||
|
emoji.ptr = e;
|
||||||
|
emoji.image = QImage(
|
||||||
|
QSize(size, height) * ratio,
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
emoji.image.setDevicePixelRatio(ratio);
|
||||||
|
emoji.image.fill(Qt::transparent);
|
||||||
|
auto q = QPainter(&emoji.image);
|
||||||
|
Ui::Emoji::Draw(q, e, esize, 0, add);
|
||||||
|
q.end();
|
||||||
|
|
||||||
|
//emoji.image = Images::Blur(
|
||||||
|
// std::move(emoji.image),
|
||||||
|
// false,
|
||||||
|
// Qt::Vertical);
|
||||||
|
}
|
||||||
|
return &emoji;
|
||||||
|
};
|
||||||
|
auto shift = entry.position * height - add;
|
||||||
|
p.translate(0, shift);
|
||||||
|
for (const auto &e : entry.sliding) {
|
||||||
|
const auto index = [&] {
|
||||||
|
const auto i = ranges::find(entry.carousel, e);
|
||||||
|
if (i != end(entry.carousel)) {
|
||||||
|
return int(i - begin(entry.carousel));
|
||||||
|
}
|
||||||
|
return int(entry.carousel.size())
|
||||||
|
+ ((e == entry.sliding.back()) ? 1 : 0);
|
||||||
|
}();
|
||||||
|
const auto entry = validateCache(index, e);
|
||||||
|
p.drawImage(0, 0, entry->image);
|
||||||
|
p.translate(0, -height);
|
||||||
|
shift -= height;
|
||||||
|
}
|
||||||
|
p.translate(0, -shift);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Calls
|
} // namespace Calls
|
||||||
|
|
|
@ -26,4 +26,45 @@ class Call;
|
||||||
not_null<QWidget*> parent,
|
not_null<QWidget*> parent,
|
||||||
not_null<Call*> call);
|
not_null<Call*> call);
|
||||||
|
|
||||||
|
struct FingerprintBadgeState {
|
||||||
|
struct Entry {
|
||||||
|
EmojiPtr emoji = nullptr;
|
||||||
|
std::vector<EmojiPtr> sliding;
|
||||||
|
std::vector<EmojiPtr> carousel;
|
||||||
|
crl::time time = 0;
|
||||||
|
float64 speed = 0.;
|
||||||
|
float64 position = 0.;
|
||||||
|
int added = 0;
|
||||||
|
};
|
||||||
|
std::vector<Entry> entries;
|
||||||
|
};
|
||||||
|
struct FingerprintBadge {
|
||||||
|
not_null<const FingerprintBadgeState*> state;
|
||||||
|
rpl::producer<> repaints;
|
||||||
|
};
|
||||||
|
FingerprintBadge SetupFingerprintBadge(
|
||||||
|
rpl::lifetime &on,
|
||||||
|
rpl::producer<QByteArray> fingerprint);
|
||||||
|
|
||||||
|
void SetupFingerprintBadgeWidget(
|
||||||
|
not_null<Ui::RpWidget*> widget,
|
||||||
|
not_null<const FingerprintBadgeState*> state,
|
||||||
|
rpl::producer<> repaints);
|
||||||
|
|
||||||
|
struct FingerprintBadgeCache {
|
||||||
|
struct Emoji {
|
||||||
|
EmojiPtr ptr = nullptr;
|
||||||
|
QImage image;
|
||||||
|
};
|
||||||
|
struct Entry {
|
||||||
|
std::vector<Emoji> emoji;
|
||||||
|
};
|
||||||
|
std::vector<Entry> entries;
|
||||||
|
};
|
||||||
|
void PaintFingerprintEntry(
|
||||||
|
QPainter &p,
|
||||||
|
const FingerprintBadgeState::Entry &entry,
|
||||||
|
FingerprintBadgeCache::Entry &cache,
|
||||||
|
int esize);
|
||||||
|
|
||||||
} // namespace Calls
|
} // namespace Calls
|
||||||
|
|
|
@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "calls/group/calls_volume_item.h"
|
#include "calls/group/calls_volume_item.h"
|
||||||
#include "calls/group/calls_group_members_row.h"
|
#include "calls/group/calls_group_members_row.h"
|
||||||
#include "calls/group/calls_group_viewport.h"
|
#include "calls/group/calls_group_viewport.h"
|
||||||
|
#include "calls/calls_emoji_fingerprint.h"
|
||||||
#include "calls/calls_instance.h"
|
#include "calls/calls_instance.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
|
@ -1745,6 +1746,9 @@ Members::Members(
|
||||||
, _listController(std::make_unique<Controller>(call, parent, mode))
|
, _listController(std::make_unique<Controller>(call, parent, mode))
|
||||||
, _layout(_scroll->setOwnedWidget(
|
, _layout(_scroll->setOwnedWidget(
|
||||||
object_ptr<Ui::VerticalLayout>(_scroll.data())))
|
object_ptr<Ui::VerticalLayout>(_scroll.data())))
|
||||||
|
, _fingerprint(call->conference()
|
||||||
|
? _layout->add(object_ptr<Ui::RpWidget>(_layout.get()))
|
||||||
|
: nullptr)
|
||||||
, _videoWrap(_layout->add(object_ptr<Ui::RpWidget>(_layout.get())))
|
, _videoWrap(_layout->add(object_ptr<Ui::RpWidget>(_layout.get())))
|
||||||
, _viewport(
|
, _viewport(
|
||||||
std::make_unique<Viewport>(
|
std::make_unique<Viewport>(
|
||||||
|
@ -1753,6 +1757,7 @@ Members::Members(
|
||||||
backend)) {
|
backend)) {
|
||||||
setupList();
|
setupList();
|
||||||
setupAddMember(call);
|
setupAddMember(call);
|
||||||
|
setupFingerprint();
|
||||||
setContent(_list);
|
setContent(_list);
|
||||||
setupFakeRoundCorners();
|
setupFakeRoundCorners();
|
||||||
_listController->setDelegate(static_cast<PeerListDelegate*>(this));
|
_listController->setDelegate(static_cast<PeerListDelegate*>(this));
|
||||||
|
@ -1854,6 +1859,8 @@ void Members::setupAddMember(not_null<GroupCall*> call) {
|
||||||
_canInviteByLink = canInviteByLinkByPeer(channel);
|
_canInviteByLink = canInviteByLinkByPeer(channel);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const auto baseIndex = _layout->count() - 2;
|
||||||
|
|
||||||
rpl::combine(
|
rpl::combine(
|
||||||
_canAddMembers.value(),
|
_canAddMembers.value(),
|
||||||
_canInviteByLink.value(),
|
_canInviteByLink.value(),
|
||||||
|
@ -1887,7 +1894,7 @@ void Members::setupAddMember(not_null<GroupCall*> call) {
|
||||||
addMember->resizeToWidth(_layout->width());
|
addMember->resizeToWidth(_layout->width());
|
||||||
delete _addMemberButton.current();
|
delete _addMemberButton.current();
|
||||||
_addMemberButton = addMember.data();
|
_addMemberButton = addMember.data();
|
||||||
_layout->insert(3, std::move(addMember));
|
_layout->insert(baseIndex, std::move(addMember));
|
||||||
if (conference) {
|
if (conference) {
|
||||||
auto shareLink = Settings::CreateButtonWithIcon(
|
auto shareLink = Settings::CreateButtonWithIcon(
|
||||||
_layout.get(),
|
_layout.get(),
|
||||||
|
@ -1901,7 +1908,7 @@ void Members::setupAddMember(not_null<GroupCall*> call) {
|
||||||
shareLink->resizeToWidth(_layout->width());
|
shareLink->resizeToWidth(_layout->width());
|
||||||
delete _shareLinkButton.current();
|
delete _shareLinkButton.current();
|
||||||
_shareLinkButton = shareLink.data();
|
_shareLinkButton = shareLink.data();
|
||||||
_layout->insert(4, std::move(shareLink));
|
_layout->insert(baseIndex + 1, std::move(shareLink));
|
||||||
}
|
}
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
|
@ -2000,6 +2007,23 @@ void Members::setupList() {
|
||||||
}, _scroll->lifetime());
|
}, _scroll->lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Members::setupFingerprint() {
|
||||||
|
if (const auto raw = _fingerprint) {
|
||||||
|
auto badge = SetupFingerprintBadge(
|
||||||
|
raw->lifetime(),
|
||||||
|
_call->emojiHashValue());
|
||||||
|
std::move(badge.repaints) | rpl::start_to_stream(
|
||||||
|
_fingerprintRepaints,
|
||||||
|
raw->lifetime());
|
||||||
|
_fingerprintState = badge.state;
|
||||||
|
|
||||||
|
SetupFingerprintBadgeWidget(
|
||||||
|
raw,
|
||||||
|
_fingerprintState,
|
||||||
|
_fingerprintRepaints.events());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Members::trackViewportGeometry() {
|
void Members::trackViewportGeometry() {
|
||||||
_call->videoEndpointLargeValue(
|
_call->videoEndpointLargeValue(
|
||||||
) | rpl::start_with_next([=](const VideoEndpoint &large) {
|
) | rpl::start_with_next([=](const VideoEndpoint &large) {
|
||||||
|
|
|
@ -25,6 +25,7 @@ class GroupCall;
|
||||||
|
|
||||||
namespace Calls {
|
namespace Calls {
|
||||||
class GroupCall;
|
class GroupCall;
|
||||||
|
struct FingerprintBadgeState;
|
||||||
} // namespace Calls
|
} // namespace Calls
|
||||||
|
|
||||||
namespace Calls::Group {
|
namespace Calls::Group {
|
||||||
|
@ -96,6 +97,7 @@ private:
|
||||||
void setupAddMember(not_null<GroupCall*> call);
|
void setupAddMember(not_null<GroupCall*> call);
|
||||||
void resizeToList();
|
void resizeToList();
|
||||||
void setupList();
|
void setupList();
|
||||||
|
void setupFingerprint();
|
||||||
void setupFakeRoundCorners();
|
void setupFakeRoundCorners();
|
||||||
|
|
||||||
void trackViewportGeometry();
|
void trackViewportGeometry();
|
||||||
|
@ -106,6 +108,9 @@ private:
|
||||||
object_ptr<Ui::ScrollArea> _scroll;
|
object_ptr<Ui::ScrollArea> _scroll;
|
||||||
std::unique_ptr<Controller> _listController;
|
std::unique_ptr<Controller> _listController;
|
||||||
not_null<Ui::VerticalLayout*> _layout;
|
not_null<Ui::VerticalLayout*> _layout;
|
||||||
|
const not_null<Ui::RpWidget*> _fingerprint;
|
||||||
|
rpl::event_stream<> _fingerprintRepaints;
|
||||||
|
const FingerprintBadgeState *_fingerprintState = nullptr;
|
||||||
const not_null<Ui::RpWidget*> _videoWrap;
|
const not_null<Ui::RpWidget*> _videoWrap;
|
||||||
std::unique_ptr<Viewport> _viewport;
|
std::unique_ptr<Viewport> _viewport;
|
||||||
rpl::variable<Ui::RpWidget*> _addMemberButton = nullptr;
|
rpl::variable<Ui::RpWidget*> _addMemberButton = nullptr;
|
||||||
|
|
|
@ -426,10 +426,12 @@ HistoryItem::HistoryItem(
|
||||||
} else if ((checked == MediaCheckResult::HasUnsupportedTimeToLive)
|
} else if ((checked == MediaCheckResult::HasUnsupportedTimeToLive)
|
||||||
|| (checked == MediaCheckResult::HasExpiredMediaTimeToLive)) {
|
|| (checked == MediaCheckResult::HasExpiredMediaTimeToLive)) {
|
||||||
createServiceFromMtp(data);
|
createServiceFromMtp(data);
|
||||||
|
setReactions(data.vreactions());
|
||||||
applyTTL(data);
|
applyTTL(data);
|
||||||
} else if (checked == MediaCheckResult::HasStoryMention) {
|
} else if (checked == MediaCheckResult::HasStoryMention) {
|
||||||
setMedia(*data.vmedia());
|
setMedia(*data.vmedia());
|
||||||
createServiceFromMtp(data);
|
createServiceFromMtp(data);
|
||||||
|
setReactions(data.vreactions());
|
||||||
applyTTL(data);
|
applyTTL(data);
|
||||||
} else {
|
} else {
|
||||||
createComponents(data);
|
createComponents(data);
|
||||||
|
@ -1830,8 +1832,18 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) {
|
||||||
auto updatedText = checkedMedia
|
auto updatedText = checkedMedia
|
||||||
? edition.textWithEntities
|
? edition.textWithEntities
|
||||||
: EnsureNonEmpty(edition.textWithEntities);
|
: EnsureNonEmpty(edition.textWithEntities);
|
||||||
|
auto serviceText = (!checkedMedia
|
||||||
|
&& edition.textWithEntities.empty()
|
||||||
|
&& edition.mtpMedia)
|
||||||
|
? prepareServiceTextForMessage(
|
||||||
|
*edition.mtpMedia,
|
||||||
|
edition.isMediaUnread)
|
||||||
|
: PreparedServiceText();
|
||||||
if (updatingSavedLocalEdit) {
|
if (updatingSavedLocalEdit) {
|
||||||
Get<HistoryMessageSavedMediaData>()->text = std::move(updatedText);
|
Get<HistoryMessageSavedMediaData>()->text = std::move(updatedText);
|
||||||
|
} else if (!serviceText.text.empty()) {
|
||||||
|
setServiceText(std::move(serviceText));
|
||||||
|
addToSharedMediaIndex();
|
||||||
} else {
|
} else {
|
||||||
setText(std::move(updatedText));
|
setText(std::move(updatedText));
|
||||||
addToSharedMediaIndex();
|
addToSharedMediaIndex();
|
||||||
|
@ -1920,6 +1932,8 @@ void HistoryItem::applyEdition(const MTPDmessageService &message) {
|
||||||
addToSharedMediaIndex();
|
addToSharedMediaIndex();
|
||||||
finishEdition(-1);
|
finishEdition(-1);
|
||||||
_flags &= ~MessageFlag::DisplayFromChecked;
|
_flags &= ~MessageFlag::DisplayFromChecked;
|
||||||
|
|
||||||
|
updateReactions(message.vreactions());
|
||||||
} else if (isService()) {
|
} else if (isService()) {
|
||||||
if (const auto reply = Get<HistoryMessageReply>()) {
|
if (const auto reply = Get<HistoryMessageReply>()) {
|
||||||
reply->clearData(this);
|
reply->clearData(this);
|
||||||
|
@ -1930,6 +1944,8 @@ void HistoryItem::applyEdition(const MTPDmessageService &message) {
|
||||||
applyServiceDateEdition(message);
|
applyServiceDateEdition(message);
|
||||||
finishEdition(-1);
|
finishEdition(-1);
|
||||||
_flags &= ~MessageFlag::DisplayFromChecked;
|
_flags &= ~MessageFlag::DisplayFromChecked;
|
||||||
|
|
||||||
|
updateReactions(message.vreactions());
|
||||||
}
|
}
|
||||||
const auto nowSublist = savedSublist();
|
const auto nowSublist = savedSublist();
|
||||||
if (wasSublist && nowSublist != wasSublist) {
|
if (wasSublist && nowSublist != wasSublist) {
|
||||||
|
@ -2091,6 +2107,31 @@ void HistoryItem::contributeToSlowmode(TimeId realDate) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HistoryItem::clearMediaAsExpired() {
|
||||||
|
const auto media = this->media();
|
||||||
|
if (!media->ttlSeconds()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (const auto document = media->document()) {
|
||||||
|
applyEditionToHistoryCleared();
|
||||||
|
auto text = (document->isVideoFile()
|
||||||
|
? tr::lng_ttl_video_expired
|
||||||
|
: document->isVoiceMessage()
|
||||||
|
? tr::lng_ttl_voice_expired
|
||||||
|
: document->isVideoMessage()
|
||||||
|
? tr::lng_ttl_round_expired
|
||||||
|
: tr::lng_message_empty)(tr::now, Ui::Text::WithEntities);
|
||||||
|
updateServiceText({ std::move(text) });
|
||||||
|
_flags |= MessageFlag::ReactionsAllowed;
|
||||||
|
} else if (media->photo()) {
|
||||||
|
applyEditionToHistoryCleared();
|
||||||
|
updateServiceText({
|
||||||
|
tr::lng_ttl_photo_expired(tr::now, Ui::Text::WithEntities)
|
||||||
|
});
|
||||||
|
_flags |= MessageFlag::ReactionsAllowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void HistoryItem::addToUnreadThings(HistoryUnreadThings::AddType type) {
|
void HistoryItem::addToUnreadThings(HistoryUnreadThings::AddType type) {
|
||||||
if (!isRegular()) {
|
if (!isRegular()) {
|
||||||
return;
|
return;
|
||||||
|
@ -4169,9 +4210,86 @@ void HistoryItem::refreshSentMedia(const MTPMessageMedia *media) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PreparedServiceText HistoryItem::prepareServiceTextForMessage(
|
||||||
|
const MTPMessageMedia &media,
|
||||||
|
bool unread) {
|
||||||
|
return media.match([&](const MTPDmessageMediaStory &data) {
|
||||||
|
return prepareStoryMentionText();
|
||||||
|
}, [&](const MTPDmessageMediaPhoto &data) -> PreparedServiceText {
|
||||||
|
if (unread) {
|
||||||
|
const auto ttl = data.vttl_seconds();
|
||||||
|
Assert(ttl != nullptr);
|
||||||
|
|
||||||
|
if (out()) {
|
||||||
|
return {
|
||||||
|
tr::lng_ttl_photo_sent(tr::now, Ui::Text::WithEntities)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
auto result = PreparedServiceText();
|
||||||
|
result.links.push_back(fromLink());
|
||||||
|
result.text = tr::lng_ttl_photo_received(
|
||||||
|
tr::now,
|
||||||
|
lt_from,
|
||||||
|
fromLinkText(), // Link 1.
|
||||||
|
Ui::Text::WithEntities);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
tr::lng_ttl_photo_expired(tr::now, Ui::Text::WithEntities)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [&](const MTPDmessageMediaDocument &data) -> PreparedServiceText {
|
||||||
|
if (unread) {
|
||||||
|
const auto ttl = data.vttl_seconds();
|
||||||
|
Assert(ttl != nullptr);
|
||||||
|
|
||||||
|
if (data.is_video()) {
|
||||||
|
if (out()) {
|
||||||
|
return {
|
||||||
|
tr::lng_ttl_video_sent(
|
||||||
|
tr::now,
|
||||||
|
Ui::Text::WithEntities)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
auto result = PreparedServiceText();
|
||||||
|
result.links.push_back(fromLink());
|
||||||
|
result.text = tr::lng_ttl_video_received(
|
||||||
|
tr::now,
|
||||||
|
lt_from,
|
||||||
|
fromLinkText(), // Link 1.
|
||||||
|
Ui::Text::WithEntities);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} else if (out()) {
|
||||||
|
auto text = (data.is_voice()
|
||||||
|
? tr::lng_ttl_voice_sent
|
||||||
|
: data.is_round()
|
||||||
|
? tr::lng_ttl_round_sent
|
||||||
|
: tr::lng_message_empty)(tr::now, Ui::Text::WithEntities);
|
||||||
|
return { std::move(text) };
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
auto text = (data.is_video()
|
||||||
|
? tr::lng_ttl_video_expired
|
||||||
|
: data.is_voice()
|
||||||
|
? tr::lng_ttl_voice_expired
|
||||||
|
: data.is_round()
|
||||||
|
? tr::lng_ttl_round_expired
|
||||||
|
: tr::lng_message_empty)(tr::now, Ui::Text::WithEntities);
|
||||||
|
return { std::move(text) };
|
||||||
|
}
|
||||||
|
}, [](const auto &) {
|
||||||
|
return PreparedServiceText();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void HistoryItem::createServiceFromMtp(const MTPDmessage &message) {
|
void HistoryItem::createServiceFromMtp(const MTPDmessage &message) {
|
||||||
AddComponents(HistoryServiceData::Bit());
|
AddComponents(HistoryServiceData::Bit());
|
||||||
|
|
||||||
|
_flags |= MessageFlag::ReactionsAllowed;
|
||||||
|
|
||||||
const auto unread = message.is_media_unread();
|
const auto unread = message.is_media_unread();
|
||||||
const auto media = message.vmedia();
|
const auto media = message.vmedia();
|
||||||
Assert(media != nullptr);
|
Assert(media != nullptr);
|
||||||
|
@ -4182,24 +4300,6 @@ void HistoryItem::createServiceFromMtp(const MTPDmessage &message) {
|
||||||
Assert(ttl != nullptr);
|
Assert(ttl != nullptr);
|
||||||
|
|
||||||
setSelfDestruct(HistoryServiceSelfDestruct::Type::Photo, *ttl);
|
setSelfDestruct(HistoryServiceSelfDestruct::Type::Photo, *ttl);
|
||||||
if (out()) {
|
|
||||||
setServiceText({
|
|
||||||
tr::lng_ttl_photo_sent(tr::now, Ui::Text::WithEntities)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
auto result = PreparedServiceText();
|
|
||||||
result.links.push_back(fromLink());
|
|
||||||
result.text = tr::lng_ttl_photo_received(
|
|
||||||
tr::now,
|
|
||||||
lt_from,
|
|
||||||
fromLinkText(), // Link 1.
|
|
||||||
Ui::Text::WithEntities);
|
|
||||||
setServiceText(std::move(result));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setServiceText({
|
|
||||||
tr::lng_ttl_photo_expired(tr::now, Ui::Text::WithEntities)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [&](const MTPDmessageMediaDocument &data) {
|
}, [&](const MTPDmessageMediaDocument &data) {
|
||||||
if (unread) {
|
if (unread) {
|
||||||
|
@ -4210,49 +4310,11 @@ void HistoryItem::createServiceFromMtp(const MTPDmessage &message) {
|
||||||
setSelfDestruct(
|
setSelfDestruct(
|
||||||
HistoryServiceSelfDestruct::Type::Video,
|
HistoryServiceSelfDestruct::Type::Video,
|
||||||
*ttl);
|
*ttl);
|
||||||
if (out()) {
|
|
||||||
setServiceText({
|
|
||||||
tr::lng_ttl_video_sent(
|
|
||||||
tr::now,
|
|
||||||
Ui::Text::WithEntities)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
auto result = PreparedServiceText();
|
|
||||||
result.links.push_back(fromLink());
|
|
||||||
result.text = tr::lng_ttl_video_received(
|
|
||||||
tr::now,
|
|
||||||
lt_from,
|
|
||||||
fromLinkText(), // Link 1.
|
|
||||||
Ui::Text::WithEntities);
|
|
||||||
setServiceText(std::move(result));
|
|
||||||
}
|
|
||||||
} else if (out()) {
|
|
||||||
auto text = (data.is_voice()
|
|
||||||
? tr::lng_ttl_voice_sent
|
|
||||||
: data.is_round()
|
|
||||||
? tr::lng_ttl_round_sent
|
|
||||||
: tr::lng_message_empty)(tr::now, Ui::Text::WithEntities);
|
|
||||||
setServiceText({ std::move(text) });
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
auto text = (data.is_video()
|
|
||||||
? tr::lng_ttl_video_expired
|
|
||||||
: data.is_voice()
|
|
||||||
? tr::lng_ttl_voice_expired
|
|
||||||
: data.is_round()
|
|
||||||
? tr::lng_ttl_round_expired
|
|
||||||
: tr::lng_message_empty)(tr::now, Ui::Text::WithEntities);
|
|
||||||
setServiceText({ std::move(text) });
|
|
||||||
}
|
}
|
||||||
}, [&](const MTPDmessageMediaStory &data) {
|
}, [](const auto &) {});
|
||||||
setServiceText(prepareStoryMentionText());
|
|
||||||
}, [](const auto &) {
|
|
||||||
Unexpected("Media type in HistoryItem::createServiceFromMtp()");
|
|
||||||
});
|
|
||||||
|
|
||||||
if (const auto reactions = message.vreactions()) {
|
setServiceText(prepareServiceTextForMessage(*media, unread));
|
||||||
updateReactions(reactions);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) {
|
void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) {
|
||||||
|
|
|
@ -380,6 +380,8 @@ public:
|
||||||
void updateReplyMarkup(HistoryMessageMarkupData &&markup);
|
void updateReplyMarkup(HistoryMessageMarkupData &&markup);
|
||||||
void contributeToSlowmode(TimeId realDate = 0);
|
void contributeToSlowmode(TimeId realDate = 0);
|
||||||
|
|
||||||
|
void clearMediaAsExpired();
|
||||||
|
|
||||||
void addToUnreadThings(HistoryUnreadThings::AddType type);
|
void addToUnreadThings(HistoryUnreadThings::AddType type);
|
||||||
void destroyHistoryEntry();
|
void destroyHistoryEntry();
|
||||||
[[nodiscard]] Storage::SharedMediaTypesMask sharedMediaTypes() const;
|
[[nodiscard]] Storage::SharedMediaTypesMask sharedMediaTypes() const;
|
||||||
|
@ -670,6 +672,10 @@ private:
|
||||||
[[nodiscard]] PreparedServiceText prepareCallScheduledText(
|
[[nodiscard]] PreparedServiceText prepareCallScheduledText(
|
||||||
TimeId scheduleDate);
|
TimeId scheduleDate);
|
||||||
|
|
||||||
|
[[nodiscard]] PreparedServiceText prepareServiceTextForMessage(
|
||||||
|
const MTPMessageMedia &media,
|
||||||
|
bool unread);
|
||||||
|
|
||||||
void flagSensitiveContent();
|
void flagSensitiveContent();
|
||||||
[[nodiscard]] PeerData *computeDisplayFrom() const;
|
[[nodiscard]] PeerData *computeDisplayFrom() const;
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ HistoryMessageEdition::HistoryMessageEdition(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
const MTPDmessage &message) {
|
const MTPDmessage &message) {
|
||||||
isEditHide = message.is_edit_hide();
|
isEditHide = message.is_edit_hide();
|
||||||
|
isMediaUnread = message.is_media_unread();
|
||||||
editDate = message.vedit_date().value_or(-1);
|
editDate = message.vedit_date().value_or(-1);
|
||||||
textWithEntities = TextWithEntities{
|
textWithEntities = TextWithEntities{
|
||||||
qs(message.vmessage()),
|
qs(message.vmessage()),
|
||||||
|
|
|
@ -20,6 +20,7 @@ struct HistoryMessageEdition {
|
||||||
const MTPDmessage &message);
|
const MTPDmessage &message);
|
||||||
|
|
||||||
bool isEditHide = false;
|
bool isEditHide = false;
|
||||||
|
bool isMediaUnread = false;
|
||||||
int editDate = 0;
|
int editDate = 0;
|
||||||
int views = -1;
|
int views = -1;
|
||||||
int forwards = -1;
|
int forwards = -1;
|
||||||
|
|
|
@ -1195,30 +1195,6 @@ void ShowTrialTranscribesToast(int left, TimeId until) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClearMediaAsExpired(not_null<HistoryItem*> item) {
|
|
||||||
if (const auto media = item->media()) {
|
|
||||||
if (!media->ttlSeconds()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (const auto document = media->document()) {
|
|
||||||
item->applyEditionToHistoryCleared();
|
|
||||||
auto text = (document->isVideoFile()
|
|
||||||
? tr::lng_ttl_video_expired
|
|
||||||
: document->isVoiceMessage()
|
|
||||||
? tr::lng_ttl_voice_expired
|
|
||||||
: document->isVideoMessage()
|
|
||||||
? tr::lng_ttl_round_expired
|
|
||||||
: tr::lng_message_empty)(tr::now, Ui::Text::WithEntities);
|
|
||||||
item->updateServiceText(PreparedServiceText{ std::move(text) });
|
|
||||||
} else if (media->photo()) {
|
|
||||||
item->applyEditionToHistoryCleared();
|
|
||||||
item->updateServiceText(PreparedServiceText{
|
|
||||||
tr::lng_ttl_photo_expired(tr::now, Ui::Text::WithEntities)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int ItemsForwardSendersCount(const HistoryItemsList &list) {
|
int ItemsForwardSendersCount(const HistoryItemsList &list) {
|
||||||
auto peers = base::flat_set<not_null<PeerData*>>();
|
auto peers = base::flat_set<not_null<PeerData*>>();
|
||||||
auto names = base::flat_set<QString>();
|
auto names = base::flat_set<QString>();
|
||||||
|
|
|
@ -257,7 +257,5 @@ ClickHandlerPtr JumpToStoryClickHandler(
|
||||||
|
|
||||||
void ShowTrialTranscribesToast(int left, TimeId until);
|
void ShowTrialTranscribesToast(int left, TimeId until);
|
||||||
|
|
||||||
void ClearMediaAsExpired(not_null<HistoryItem*> item);
|
|
||||||
|
|
||||||
[[nodiscard]] int ItemsForwardSendersCount(const HistoryItemsList &list);
|
[[nodiscard]] int ItemsForwardSendersCount(const HistoryItemsList &list);
|
||||||
[[nodiscard]] int ItemsForwardCaptionsCount(const HistoryItemsList &list);
|
[[nodiscard]] int ItemsForwardCaptionsCount(const HistoryItemsList &list);
|
||||||
|
|
|
@ -16,7 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "media/audio/media_audio.h"
|
#include "media/audio/media_audio.h"
|
||||||
#include "media/player/media_player_instance.h"
|
#include "media/player/media_player_instance.h"
|
||||||
#include "history/history_item_components.h"
|
#include "history/history_item_components.h"
|
||||||
#include "history/history_item_helpers.h" // ClearMediaAsExpired.
|
#include "history/history_item.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "core/click_handler_types.h" // kDocumentFilenameTooltipProperty.
|
#include "core/click_handler_types.h" // kDocumentFilenameTooltipProperty.
|
||||||
#include "history/view/history_view_element.h"
|
#include "history/view/history_view_element.h"
|
||||||
|
@ -330,7 +330,7 @@ Document::Document(
|
||||||
}
|
}
|
||||||
if (const auto item = data->message(fullId)) {
|
if (const auto item = data->message(fullId)) {
|
||||||
// Destroys this.
|
// Destroys this.
|
||||||
ClearMediaAsExpired(item);
|
item->clearMediaAsExpired();
|
||||||
}
|
}
|
||||||
}, *lifetime);
|
}, *lifetime);
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "ui/rect.h"
|
#include "ui/rect.h"
|
||||||
#include "history/history_item_components.h"
|
#include "history/history_item_components.h"
|
||||||
#include "history/history_item_helpers.h"
|
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/view/history_view_element.h"
|
#include "history/view/history_view_element.h"
|
||||||
|
@ -176,7 +175,7 @@ Gif::Gif(
|
||||||
if (!isOut) {
|
if (!isOut) {
|
||||||
if (const auto item = data->message(fullId)) {
|
if (const auto item = data->message(fullId)) {
|
||||||
// Destroys this.
|
// Destroys this.
|
||||||
ClearMediaAsExpired(item);
|
item->clearMediaAsExpired();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, *lifetime);
|
}, *lifetime);
|
||||||
|
|
Loading…
Add table
Reference in a new issue