mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/telegramdesktop/tdesktop
synced 2026-09-20 08:03:45 +08:00
Added rate call screen with stars to call panel.
This commit is contained in:
@@ -478,6 +478,8 @@ PRIVATE
|
||||
calls/calls_panel.h
|
||||
calls/calls_panel_background.cpp
|
||||
calls/calls_panel_background.h
|
||||
calls/calls_rate_call.cpp
|
||||
calls/calls_rate_call.h
|
||||
calls/calls_signal_bars.cpp
|
||||
calls/calls_signal_bars.h
|
||||
calls/calls_top_bar.cpp
|
||||
|
||||
BIN
Telegram/Resources/animations/call_rate.tgs
Normal file
BIN
Telegram/Resources/animations/call_rate.tgs
Normal file
Binary file not shown.
BIN
Telegram/Resources/icons/calls/call_ended.png
Normal file
BIN
Telegram/Resources/icons/calls/call_ended.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 371 B |
BIN
Telegram/Resources/icons/calls/call_ended@2x.png
Normal file
BIN
Telegram/Resources/icons/calls/call_ended@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 720 B |
BIN
Telegram/Resources/icons/calls/call_ended@3x.png
Normal file
BIN
Telegram/Resources/icons/calls/call_ended@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
@@ -6440,6 +6440,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_call_rate_label" = "Please rate the quality of your call";
|
||||
"lng_call_rate_comment" = "Comment (optional)";
|
||||
"lng_call_rate_title" = "Rate this call";
|
||||
"lng_call_rate_description" = "Please rate the quality of this call.";
|
||||
"lng_call_ended_title" = "Call ended";
|
||||
|
||||
"lng_call_start" = "Start Call";
|
||||
"lng_call_start_video" = "Start Video";
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<file alias="writing.tgs">../../animations/writing.tgs</file>
|
||||
<file alias="hours.tgs">../../animations/hours.tgs</file>
|
||||
<file alias="phone.tgs">../../animations/phone.tgs</file>
|
||||
<file alias="call_rate.tgs">../../animations/call_rate.tgs</file>
|
||||
<file alias="chat_link.tgs">../../animations/chat_link.tgs</file>
|
||||
<file alias="diamond.tgs">../../animations/diamond.tgs</file>
|
||||
<file alias="collectible_username.tgs">../../animations/collectible_username.tgs</file>
|
||||
|
||||
@@ -455,6 +455,46 @@ callRatingComment: InputField(defaultInputField) {
|
||||
}
|
||||
callRatingCommentTop: 8px;
|
||||
|
||||
callRateWidth: 300px;
|
||||
callRateHeight: 152px;
|
||||
callRateRadius: 28px;
|
||||
callRateSkip: 16px;
|
||||
callRateTitleTop: 24px;
|
||||
callRateDescriptionTop: 50px;
|
||||
callRateStarsTop: 90px;
|
||||
callRateStarsHeight: 100px;
|
||||
callRateStarSize: 37px;
|
||||
callRateStarSkip: 4px;
|
||||
callRateBurstSize: 133px;
|
||||
callRateShowShift: 24px;
|
||||
callRateStarShowShift: 30px;
|
||||
callRateTitle: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 40px;
|
||||
textFg: callNameFg;
|
||||
align: align(top);
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(15px semibold);
|
||||
}
|
||||
}
|
||||
callRateDescription: FlatLabel(callRateTitle) {
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(15px);
|
||||
}
|
||||
}
|
||||
callRateStarRipple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: callMuteRipple;
|
||||
}
|
||||
callRateEndedIcon: icon {{ "calls/call_ended", callNameFg }};
|
||||
callRateEndedSkip: 4px;
|
||||
callRateCloseWidth: 358px;
|
||||
callRateCloseHeight: 52px;
|
||||
callRateCloseRadius: 8px;
|
||||
callRateCloseSkip: 24px;
|
||||
callRateCloseFont: font(18px semibold);
|
||||
callRateCloseRipple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: callIconActiveRipple;
|
||||
}
|
||||
|
||||
callDebugPadding: margins(24px, 0px, 24px, 0px);
|
||||
callDebugLabel: FlatLabel(defaultFlatLabel) {
|
||||
margin: callDebugPadding;
|
||||
|
||||
@@ -622,7 +622,78 @@ crl::time Call::getDurationMs() const {
|
||||
return _startTime ? (crl::now() - _startTime) : 0;
|
||||
}
|
||||
|
||||
void Call::takeRatingToPanel() {
|
||||
Expects(_ratingRequested);
|
||||
|
||||
_ratingInPanel = true;
|
||||
}
|
||||
|
||||
void Call::setRating(int rating) {
|
||||
_rating = rating;
|
||||
}
|
||||
|
||||
void Call::finishRating() {
|
||||
if (!_ratingInPanel) {
|
||||
return;
|
||||
}
|
||||
_ratingInPanel = false;
|
||||
_ratingRequested = false;
|
||||
if (_rating > 0 && _id && _accessHash) {
|
||||
const auto session = &_user->session();
|
||||
session->api().request(MTPphone_SetCallRating(
|
||||
MTP_flags(0),
|
||||
MTP_inputPhoneCall(
|
||||
MTP_long(_id),
|
||||
MTP_long(_accessHash)),
|
||||
MTP_int(_rating),
|
||||
MTP_string()
|
||||
)).done([=](const MTPUpdates &updates) {
|
||||
session->api().applyUpdates(updates);
|
||||
}).send();
|
||||
}
|
||||
_delegate->callFinished(this);
|
||||
}
|
||||
|
||||
void Call::showRatingBox() {
|
||||
Expects(_ratingRequested);
|
||||
|
||||
_ratingRequested = false;
|
||||
|
||||
const auto window = Core::App().windowFor(::Window::SeparateId(_user));
|
||||
const auto session = &_user->session();
|
||||
const auto callId = _id;
|
||||
const auto callAccessHash = _accessHash;
|
||||
auto owned = Box<Ui::RateCallBox>(Core::App().settings().sendSubmitWay());
|
||||
const auto box = window
|
||||
? window->show(std::move(owned))
|
||||
: Ui::show(std::move(owned));
|
||||
const auto sender = box->lifetime().make_state<MTP::Sender>(
|
||||
&session->mtp());
|
||||
box->sends(
|
||||
) | rpl::take(
|
||||
1 // Instead of keeping requestId.
|
||||
) | rpl::on_next([=](const Ui::RateCallBox::Result &r) {
|
||||
sender->request(MTPphone_SetCallRating(
|
||||
MTP_flags(0),
|
||||
MTP_inputPhoneCall(
|
||||
MTP_long(callId),
|
||||
MTP_long(callAccessHash)),
|
||||
MTP_int(r.rating),
|
||||
MTP_string(r.comment)
|
||||
)).done([=](const MTPUpdates &updates) {
|
||||
session->api().applyUpdates(updates);
|
||||
box->closeBox();
|
||||
}).fail([=] {
|
||||
box->closeBox();
|
||||
}).send();
|
||||
}, box->lifetime());
|
||||
}
|
||||
|
||||
void Call::hangup(Data::GroupCall *migrateCall, const QString &migrateSlug) {
|
||||
if (_ratingInPanel) {
|
||||
finishRating();
|
||||
return;
|
||||
}
|
||||
const auto state = _state.current();
|
||||
if (state == State::Busy
|
||||
|| state == State::MigrationHangingUp) {
|
||||
@@ -807,36 +878,10 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
|
||||
}
|
||||
}
|
||||
if (data.is_need_rating() && _id && _accessHash) {
|
||||
const auto window = Core::App().windowFor(
|
||||
::Window::SeparateId(_user));
|
||||
const auto session = &_user->session();
|
||||
const auto callId = _id;
|
||||
const auto callAccessHash = _accessHash;
|
||||
auto owned = Box<Ui::RateCallBox>(
|
||||
Core::App().settings().sendSubmitWay());
|
||||
const auto box = window
|
||||
? window->show(std::move(owned))
|
||||
: Ui::show(std::move(owned));
|
||||
const auto sender = box->lifetime().make_state<MTP::Sender>(
|
||||
&session->mtp());
|
||||
box->sends(
|
||||
) | rpl::take(
|
||||
1 // Instead of keeping requestId.
|
||||
) | rpl::on_next([=](const Ui::RateCallBox::Result &r) {
|
||||
sender->request(MTPphone_SetCallRating(
|
||||
MTP_flags(0),
|
||||
MTP_inputPhoneCall(
|
||||
MTP_long(callId),
|
||||
MTP_long(callAccessHash)),
|
||||
MTP_int(r.rating),
|
||||
MTP_string(r.comment)
|
||||
)).done([=](const MTPUpdates &updates) {
|
||||
session->api().applyUpdates(updates);
|
||||
box->closeBox();
|
||||
}).fail([=] {
|
||||
box->closeBox();
|
||||
}).send();
|
||||
}, box->lifetime());
|
||||
_ratingRequested = true;
|
||||
}
|
||||
if (const auto duration = data.vduration()) {
|
||||
_discardedDuration = duration->v;
|
||||
}
|
||||
const auto reason = data.vreason();
|
||||
if (reason
|
||||
@@ -854,6 +899,9 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
|
||||
} else {
|
||||
setState(State::EndedByOtherDevice);
|
||||
}
|
||||
if (_ratingRequested && !_ratingInPanel) {
|
||||
showRatingBox();
|
||||
}
|
||||
} return true;
|
||||
|
||||
case mtpc_phoneCallAccepted: {
|
||||
|
||||
@@ -228,6 +228,19 @@ public:
|
||||
crl::time getDurationMs() const;
|
||||
float64 getWaitingSoundPeakValue() const;
|
||||
|
||||
[[nodiscard]] bool ratingRequested() const {
|
||||
return _ratingRequested;
|
||||
}
|
||||
[[nodiscard]] bool ratingInPanel() const {
|
||||
return _ratingInPanel;
|
||||
}
|
||||
[[nodiscard]] int discardedDuration() const {
|
||||
return _discardedDuration;
|
||||
}
|
||||
void takeRatingToPanel();
|
||||
void setRating(int rating);
|
||||
void finishRating();
|
||||
|
||||
void applyUserConfirmation();
|
||||
void answer();
|
||||
void hangup(
|
||||
@@ -290,6 +303,7 @@ private:
|
||||
= MTP_phoneCallDiscardReasonDisconnect(),
|
||||
Data::GroupCall *migrateCall = nullptr);
|
||||
void finishByMigration(const QString &slug);
|
||||
void showRatingBox();
|
||||
void startOutgoing();
|
||||
void startIncoming();
|
||||
void startWaitingTrack();
|
||||
@@ -362,6 +376,10 @@ private:
|
||||
|
||||
CallId _id = 0;
|
||||
uint64 _accessHash = 0;
|
||||
int _rating = 0;
|
||||
int _discardedDuration = 0;
|
||||
bool _ratingRequested = false;
|
||||
bool _ratingInPanel = false;
|
||||
uint64 _keyFingerprint = 0;
|
||||
|
||||
CallId _conferenceId = 0;
|
||||
|
||||
@@ -99,6 +99,9 @@ DhConfig Instance::Delegate::getDhConfig() const {
|
||||
|
||||
void Instance::Delegate::callFinished(not_null<Call*> call) {
|
||||
crl::on_main(call, [=] {
|
||||
if (call->ratingInPanel()) {
|
||||
return;
|
||||
}
|
||||
_instance->destroyCall(call);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "boxes/peers/replace_boost_box.h" // CreateUserpicsWithMoreBadge
|
||||
#include "calls/calls_panel_background.h"
|
||||
#include "calls/calls_rate_call.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
@@ -79,6 +80,7 @@ namespace {
|
||||
|
||||
constexpr auto kHideControlsTimeout = 5 * crl::time(1000);
|
||||
constexpr auto kHideControlsQuickTimeout = 2 * crl::time(1000);
|
||||
constexpr auto kNameFadeDuration = crl::time(70);
|
||||
|
||||
[[nodiscard]] QByteArray BatterySvg(
|
||||
const QSize &s,
|
||||
@@ -332,6 +334,8 @@ void Panel::initWindow() {
|
||||
_answerHangupRedial->y(),
|
||||
buttonsWidth,
|
||||
_answerHangupRedial->height()).contains(widgetPoint)
|
||||
|| (_rateCall && _rateCall->geometry().contains(widgetPoint))
|
||||
|| (_rateClose && _rateClose->geometry().contains(widgetPoint))
|
||||
|| (!_outgoingPreviewInBody
|
||||
&& _outgoingVideoBubble->geometry().contains(widgetPoint));
|
||||
if (inControls) {
|
||||
@@ -651,6 +655,16 @@ void Panel::refreshIncomingGeometry() {
|
||||
|
||||
void Panel::reinitWithCall(Call *call) {
|
||||
_callLifetime.destroy();
|
||||
_rateNameFade.stop();
|
||||
_rateCall = nullptr;
|
||||
_rateClose = nullptr;
|
||||
_rateNameSwapped = false;
|
||||
_endedDuration = 0;
|
||||
_rateEndedIconPosition = QPoint();
|
||||
_name->setOpacity(1.);
|
||||
_bodySt = _outgoingPreviewInBody
|
||||
? &st::callBodyWithPreview
|
||||
: &st::callBodyLayout;
|
||||
_call = call;
|
||||
const auto guard = gsl::finally([&] {
|
||||
updateControlsShown();
|
||||
@@ -1017,10 +1031,14 @@ void Panel::showControls() {
|
||||
|
||||
widget()->showChildren();
|
||||
_pinOnTop->setVisible(!_fullScreenOrMaximized.current());
|
||||
_decline->setVisible(_decline->toggled());
|
||||
_cancel->setVisible(_cancel->toggled());
|
||||
_screencast->setVisible(_screencast->toggled());
|
||||
_addPeople->setVisible(_addPeople->toggled());
|
||||
if (_rateCall) {
|
||||
hideControlsForRating();
|
||||
} else {
|
||||
_decline->setVisible(_decline->toggled());
|
||||
_cancel->setVisible(_cancel->toggled());
|
||||
_screencast->setVisible(_screencast->toggled());
|
||||
_addPeople->setVisible(_addPeople->toggled());
|
||||
}
|
||||
|
||||
const auto shown = !_incomingFrameSize.isEmpty();
|
||||
_incoming->widget()->setVisible(shown);
|
||||
@@ -1129,6 +1147,97 @@ void Panel::refreshOutgoingPreviewInBody(State state) {
|
||||
updateControlsGeometry();
|
||||
}
|
||||
|
||||
void Panel::showRateCall() {
|
||||
Expects(_call != nullptr);
|
||||
Expects(_rateCall == nullptr);
|
||||
|
||||
_call->takeRatingToPanel();
|
||||
const auto local = int(_call->getDurationMs() / 1000);
|
||||
_endedDuration = local ? local : _call->discardedDuration();
|
||||
_bodySt = &st::callBodyWithPreview;
|
||||
|
||||
_rateCall = base::make_unique_q<RateCall>(widget());
|
||||
_rateCall->ratingValue(
|
||||
) | rpl::on_next([=](int rating) {
|
||||
if (_call) {
|
||||
_call->setRating(rating);
|
||||
}
|
||||
}, _rateCall->lifetime());
|
||||
_rateClose = base::make_unique_q<EndCloseButton>(widget());
|
||||
|
||||
hideControlsForRating();
|
||||
updateControlsGeometry();
|
||||
updateTextColors();
|
||||
updateStatusText(_call->state());
|
||||
|
||||
_rateCall->show();
|
||||
_rateCall->showAnimated();
|
||||
_rateClose->switchToClose(hangupCircleRect(), [=] { finishRateCall(); });
|
||||
|
||||
_rateNameFade.start([=] {
|
||||
const auto value = _rateNameFade.value(1.);
|
||||
if (!_rateNameSwapped && value >= 0.5) {
|
||||
_rateNameSwapped = true;
|
||||
_name->setText(tr::lng_call_ended_title(tr::now));
|
||||
updateControlsGeometry();
|
||||
}
|
||||
_name->setOpacity(std::abs(2. * value - 1.));
|
||||
}, 0., 1., 2 * kNameFadeDuration);
|
||||
|
||||
widget()->update();
|
||||
}
|
||||
|
||||
void Panel::finishRateCall() {
|
||||
if (_call) {
|
||||
_call->finishRating();
|
||||
}
|
||||
}
|
||||
|
||||
void Panel::hideControlsForRating() {
|
||||
_answerHangupRedial->hide();
|
||||
_camera->hide();
|
||||
if (_startVideo) {
|
||||
_startVideo->hide();
|
||||
}
|
||||
_decline->hide(anim::type::instant);
|
||||
_cancel->hide(anim::type::instant);
|
||||
_screencast->hide(anim::type::instant);
|
||||
_mute->hide(anim::type::instant);
|
||||
_addPeople->hide(anim::type::instant);
|
||||
if (_fingerprint) {
|
||||
_fingerprint->hide();
|
||||
}
|
||||
if (_remoteAudioMute) {
|
||||
_remoteAudioMute->hide();
|
||||
}
|
||||
if (_remoteLowBattery) {
|
||||
_remoteLowBattery->hide();
|
||||
}
|
||||
hideButtonTooltip();
|
||||
}
|
||||
|
||||
QRect Panel::hangupCircleRect() const {
|
||||
const auto &st = st::callHangup;
|
||||
return QRect(
|
||||
_answerHangupRedial->x() + st.bgPosition.x(),
|
||||
_answerHangupRedial->y() + st.bgPosition.y(),
|
||||
st.bgSize,
|
||||
st.bgSize);
|
||||
}
|
||||
|
||||
QRect Panel::rateCloseRect() const {
|
||||
const auto circle = hangupCircleRect();
|
||||
const auto width = std::min(
|
||||
st::callRateCloseWidth,
|
||||
widget()->width() - 2 * st::callRateCloseSkip);
|
||||
const auto height = st::callRateCloseHeight;
|
||||
return QRect(
|
||||
(widget()->width() - width) / 2,
|
||||
circle.y() + (circle.height() - height) / 2,
|
||||
width,
|
||||
height);
|
||||
}
|
||||
|
||||
void Panel::createPinOnTop() {
|
||||
const auto wrap = _window->controlsWrap();
|
||||
_pinOnTop = base::make_unique_q<Ui::IconButton>(
|
||||
@@ -1225,6 +1334,7 @@ void Panel::updateControlsShown() {
|
||||
const auto shown = !_incoming
|
||||
|| _incoming->widget()->isHidden()
|
||||
|| _controlsShownForce
|
||||
|| _rateCall
|
||||
|| !_hideControlsRequested;
|
||||
if (_controlsShown != shown) {
|
||||
_controlsShown = shown;
|
||||
@@ -1322,7 +1432,11 @@ void Panel::updateControlsGeometry() {
|
||||
st::callOutgoingPreviewMax.height(),
|
||||
bodyPreviewHeightMax,
|
||||
}));
|
||||
const auto rateHeight = _rateCall
|
||||
? (st::callRateSkip + st::callRateHeight)
|
||||
: 0;
|
||||
const auto contentHeight = bodyContentHeight
|
||||
+ rateHeight
|
||||
+ (_outgoingPreviewInBody ? bodyPreviewSize.height() : 0);
|
||||
const auto remainingHeight = std::max(available - contentHeight, 0);
|
||||
const auto skipHeight = remainingHeight
|
||||
@@ -1351,6 +1465,14 @@ void Panel::updateControlsGeometry() {
|
||||
_bodyTop + _bodySt->nameTop);
|
||||
updateStatusGeometry();
|
||||
|
||||
if (_rateCall) {
|
||||
_rateCall->setGeometry(
|
||||
(widget()->width() - st::callRateWidth) / 2,
|
||||
_bodyTop + bodyContentHeight + st::callRateSkip,
|
||||
st::callRateWidth,
|
||||
RateCall::Height());
|
||||
}
|
||||
|
||||
if (_remoteAudioMute) {
|
||||
_remoteAudioMute->moveToLeft(
|
||||
(widget()->width() - _remoteAudioMute->width()) / 2,
|
||||
@@ -1383,6 +1505,9 @@ void Panel::updateControlsGeometry() {
|
||||
}
|
||||
|
||||
updateHangupGeometry();
|
||||
if (_rateClose) {
|
||||
_rateClose->setGeometry(rateCloseRect());
|
||||
}
|
||||
updateButtonTooltipGeometry();
|
||||
}
|
||||
|
||||
@@ -1430,11 +1555,19 @@ void Panel::updateStatusGeometry() {
|
||||
if (widget()->size().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
const auto &icon = st::callRateEndedIcon;
|
||||
const auto showIcon = (_rateCall && _endedDuration > 0);
|
||||
const auto skip = showIcon
|
||||
? (icon.width() + st::callRateEndedSkip)
|
||||
: 0;
|
||||
_status->resizeToNaturalWidth(
|
||||
widget()->width() - 2 * st::callInnerPadding);
|
||||
_status->moveToLeft(
|
||||
(widget()->width() - _status->width()) / 2,
|
||||
_bodyTop + _bodySt->statusTop);
|
||||
widget()->width() - 2 * st::callInnerPadding - skip);
|
||||
const auto top = _bodyTop + _bodySt->statusTop;
|
||||
const auto left = (widget()->width() - _status->width() - skip) / 2;
|
||||
_status->moveToLeft(left + skip, top);
|
||||
_rateEndedIconPosition = showIcon
|
||||
? QPoint(left, top + (_status->height() - icon.height()) / 2)
|
||||
: QPoint();
|
||||
}
|
||||
|
||||
auto Panel::bottomButtons() const
|
||||
@@ -1585,6 +1718,18 @@ void Panel::paint(QRect clip) {
|
||||
}
|
||||
}
|
||||
|
||||
if (_rateCall && !_rateEndedIconPosition.isNull()) {
|
||||
const auto fg = st::callName.textFg;
|
||||
const auto color = (_background
|
||||
? _background->textColorOverride(fg)
|
||||
: std::optional<QColor>()).value_or(fg->c);
|
||||
st::callRateEndedIcon.paint(
|
||||
p,
|
||||
_rateEndedIconPosition,
|
||||
widget()->width(),
|
||||
color);
|
||||
}
|
||||
|
||||
if (_incoming && _incoming->widget()->isHidden()) {
|
||||
_call->videoIncoming()->markFrameShown();
|
||||
}
|
||||
@@ -1592,7 +1737,9 @@ void Panel::paint(QRect clip) {
|
||||
|
||||
bool Panel::handleClose() const {
|
||||
if (_call) {
|
||||
if (_call->state() == Call::State::WaitingUserConfirmation
|
||||
if (_call->ratingInPanel()) {
|
||||
_call->finishRating();
|
||||
} else if (_call->state() == Call::State::WaitingUserConfirmation
|
||||
|| _call->state() == Call::State::Busy
|
||||
|| _call->state() == Call::State::Starting
|
||||
|| _call->state() == Call::State::WaitingIncoming) {
|
||||
@@ -1631,6 +1778,13 @@ void Panel::stateChanged(State state) {
|
||||
const auto isWaitingUser = (state == State::WaitingUserConfirmation);
|
||||
_window->togglePowerSaveBlocker(!isBusy && !isWaitingUser);
|
||||
|
||||
if (!_rateCall
|
||||
&& _call->ratingRequested()
|
||||
&& !window()->isHidden()
|
||||
&& (state == State::Ended || state == State::EndedByOtherDevice)) {
|
||||
showRateCall();
|
||||
}
|
||||
|
||||
if ((state != State::HangingUp)
|
||||
&& (state != State::MigrationHangingUp)
|
||||
&& (state != State::Ended)
|
||||
@@ -1720,6 +1874,9 @@ void Panel::refreshAnswerHangupRedialLabel() {
|
||||
|
||||
void Panel::updateStatusText(State state) {
|
||||
auto statusText = [this, state]() -> QString {
|
||||
if (_rateCall && _endedDuration > 0) {
|
||||
return Ui::FormatDurationText(_endedDuration);
|
||||
}
|
||||
switch (state) {
|
||||
case State::Starting:
|
||||
case State::WaitingInit:
|
||||
@@ -1757,15 +1914,31 @@ void Panel::updateStatusText(State state) {
|
||||
}
|
||||
|
||||
void Panel::updateTextColors() {
|
||||
const auto statusFg = _rateCall
|
||||
? st::callName.textFg
|
||||
: st::callStatus.textFg;
|
||||
if (!_background) {
|
||||
_name->setTextColorOverride(std::nullopt);
|
||||
_status->setTextColorOverride(std::nullopt);
|
||||
_status->setTextColorOverride(_rateCall
|
||||
? std::make_optional(statusFg->c)
|
||||
: std::nullopt);
|
||||
if (_rateCall) {
|
||||
_rateCall->setTextColorOverride(std::nullopt);
|
||||
}
|
||||
return;
|
||||
}
|
||||
_name->setTextColorOverride(
|
||||
_background->textColorOverride(st::callName.textFg));
|
||||
_status->setTextColorOverride(
|
||||
_background->textColorOverride(st::callStatus.textFg));
|
||||
auto status = _background->textColorOverride(statusFg);
|
||||
if (_rateCall && !status) {
|
||||
status = statusFg->c;
|
||||
}
|
||||
_status->setTextColorOverride(status);
|
||||
if (_rateCall) {
|
||||
_rateCall->setTextColorOverride(
|
||||
_background->textColorOverride(st::callRateTitle.textFg));
|
||||
_rateCall->setCardOverlay(_background->cardOverlay());
|
||||
}
|
||||
}
|
||||
|
||||
void Panel::startDurationUpdateTimer(crl::time currentDuration) {
|
||||
|
||||
@@ -67,6 +67,8 @@ class Userpic;
|
||||
class SignalBars;
|
||||
class VideoBubble;
|
||||
class PanelBackground;
|
||||
class RateCall;
|
||||
class EndCloseButton;
|
||||
struct DeviceSelection;
|
||||
struct ConferencePanelMigration;
|
||||
|
||||
@@ -158,6 +160,11 @@ private:
|
||||
void refreshIncomingGeometry();
|
||||
|
||||
void refreshOutgoingPreviewInBody(State state);
|
||||
void showRateCall();
|
||||
void finishRateCall();
|
||||
void hideControlsForRating();
|
||||
[[nodiscard]] QRect hangupCircleRect() const;
|
||||
[[nodiscard]] QRect rateCloseRect() const;
|
||||
void createPinOnTop();
|
||||
[[nodiscard]] QRect pinOnTopRect() const;
|
||||
void toggleFullScreen(bool fullscreen);
|
||||
@@ -206,6 +213,12 @@ private:
|
||||
bool _buttonLabelsShown = true;
|
||||
base::unique_qptr<Ui::FlatLabel> _name;
|
||||
base::unique_qptr<Ui::FlatLabel> _status;
|
||||
base::unique_qptr<RateCall> _rateCall;
|
||||
base::unique_qptr<EndCloseButton> _rateClose;
|
||||
Ui::Animations::Simple _rateNameFade;
|
||||
QPoint _rateEndedIconPosition;
|
||||
int _endedDuration = 0;
|
||||
bool _rateNameSwapped = false;
|
||||
base::unique_qptr<Ui::RpWidget> _conferenceParticipants;
|
||||
base::unique_qptr<Ui::RpWidget> _fingerprint;
|
||||
base::unique_qptr<Ui::PaddingWrap<Ui::FlatLabel>> _remoteAudioMute;
|
||||
|
||||
@@ -20,6 +20,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/top_background_gradient.h"
|
||||
|
||||
namespace Calls {
|
||||
namespace {
|
||||
|
||||
constexpr auto kDarkOverlayOpacity = 0.14;
|
||||
constexpr auto kDarkenAboveLightness = 0.3;
|
||||
|
||||
} // namespace
|
||||
|
||||
PanelBackground::PanelBackground(
|
||||
not_null<PeerData*> peer,
|
||||
@@ -217,6 +223,13 @@ std::optional<QColor> PanelBackground::textColorOverride(
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
QColor PanelBackground::cardOverlay() const {
|
||||
const auto base = edgeColor().value_or(st::callBgOpaque->c);
|
||||
return (base.lightnessF() > kDarkenAboveLightness)
|
||||
? QColor(0, 0, 0, anim::interpolate(0, 255, kDarkOverlayOpacity))
|
||||
: st::callIconBg->c;
|
||||
}
|
||||
|
||||
rpl::lifetime &PanelBackground::lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ public:
|
||||
[[nodiscard]] std::optional<QColor> textColorOverride(
|
||||
const style::color &defaultColor) const;
|
||||
|
||||
[[nodiscard]] QColor cardOverlay() const;
|
||||
|
||||
private:
|
||||
void updateBrush(
|
||||
QSize widgetSize,
|
||||
|
||||
528
Telegram/SourceFiles/calls/calls_rate_call.cpp
Normal file
528
Telegram/SourceFiles/calls/calls_rate_call.cpp
Normal file
@@ -0,0 +1,528 @@
|
||||
/*
|
||||
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 "calls/calls_rate_call.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "lottie/lottie_icon.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "styles/style_calls.h"
|
||||
|
||||
#include <QtGui/QPainterPathStroker>
|
||||
|
||||
namespace Calls {
|
||||
namespace {
|
||||
|
||||
constexpr auto kBurstMinRating = 4;
|
||||
constexpr auto kShowDuration = crl::time(250);
|
||||
constexpr auto kStarShowDelay = crl::time(16);
|
||||
constexpr auto kSelectDuration = crl::time(250);
|
||||
constexpr auto kCardShowScaleFrom = 0.7;
|
||||
constexpr auto kStarShowScaleFrom = 0.3;
|
||||
constexpr auto kStarPressedScale = 0.8;
|
||||
constexpr auto kDarkOpacity = 0.14;
|
||||
constexpr auto kRippleOpacity = 0.3;
|
||||
constexpr auto kPathSize = 96.;
|
||||
constexpr auto kPathStroke = 6.;
|
||||
constexpr auto kPathMiterLimit = 4.;
|
||||
constexpr auto kCloseTextShadeOpacity = 0.15;
|
||||
constexpr auto kCloseRippleOpacity = 0.12;
|
||||
constexpr auto kMaxBursts = 5;
|
||||
|
||||
struct StarPoint {
|
||||
float64 x = 0.;
|
||||
float64 y = 0.;
|
||||
float64 inX = 0.;
|
||||
float64 inY = 0.;
|
||||
float64 outX = 0.;
|
||||
float64 outY = 0.;
|
||||
};
|
||||
|
||||
const StarPoint kStarPath[] = {
|
||||
{ 46.080, 69.940, 0.855, -0.588, 0.000, 0.000 },
|
||||
{ 32.727, 79.118, 0.000, 0.000, -2.274, 1.563 },
|
||||
{ 25.770, 77.844, 1.568, 2.267, -0.860, -1.243 },
|
||||
{ 25.094, 73.591, -0.432, 1.447, 0.000, 0.000 },
|
||||
{ 29.725, 58.097, 0.000, 0.000, 0.296, -0.992 },
|
||||
{ 28.848, 55.405, 0.825, 0.629, 0.000, 0.000 },
|
||||
{ 15.963, 45.583, 0.000, 0.000, -2.194, -1.673 },
|
||||
{ 15.029, 38.595, -1.678, 2.187, 0.920, -1.199 },
|
||||
{ 18.879, 36.640, -1.514, 0.037, 0.000, 0.000 },
|
||||
{ 35.095, 36.242, 0.000, 0.000, 1.038, -0.025 },
|
||||
{ 37.392, 34.578, -0.345, 0.976, 0.000, 0.000 },
|
||||
{ 42.783, 19.330, 0.000, 0.000, 0.918, -2.597 },
|
||||
{ 49.162, 16.285, -2.605, -0.915, 1.429, 0.502 },
|
||||
{ 52.218, 19.330, -0.503, -1.424, 0.000, 0.000 },
|
||||
{ 57.608, 34.578, 0.000, 0.000, 0.345, 0.976 },
|
||||
{ 59.905, 36.242, -1.038, -0.025, 0.000, 0.000 },
|
||||
{ 76.121, 36.640, 0.000, 0.000, 2.762, 0.068 },
|
||||
{ 80.998, 41.746, 0.068, -2.752, -0.037, 1.509 },
|
||||
{ 79.037, 45.583, 1.203, -0.917, 0.000, 0.000 },
|
||||
{ 66.152, 55.405, 0.000, 0.000, -0.825, 0.629 },
|
||||
{ 65.275, 58.097, -0.296, -0.992, 0.000, 0.000 },
|
||||
{ 69.906, 73.591, 0.000, 0.000, 0.789, 2.639 },
|
||||
{ 66.541, 79.792, 2.647, -0.786, -1.452, 0.431 },
|
||||
{ 62.273, 79.118, 1.247, 0.857, 0.000, 0.000 },
|
||||
{ 48.920, 69.940, 0.000, 0.000, -0.855, -0.588 },
|
||||
};
|
||||
|
||||
[[nodiscard]] QPainterPath StarPath(int size) {
|
||||
const auto scale = size / kPathSize;
|
||||
const auto map = [&](float64 x, float64 y) {
|
||||
return QPointF(x * scale, y * scale);
|
||||
};
|
||||
const auto count = int(std::size(kStarPath));
|
||||
auto result = QPainterPath();
|
||||
result.moveTo(map(kStarPath[0].x, kStarPath[0].y));
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
const auto &from = kStarPath[i];
|
||||
const auto &to = kStarPath[(i + 1) % count];
|
||||
result.cubicTo(
|
||||
map(from.x + from.outX, from.y + from.outY),
|
||||
map(to.x + to.inX, to.y + to.inY),
|
||||
map(to.x, to.y));
|
||||
}
|
||||
result.closeSubpath();
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage StarImage(int size, QColor color, bool filled) {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
auto result = QImage(
|
||||
QSize(size, size) * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(ratio);
|
||||
result.fill(Qt::transparent);
|
||||
|
||||
auto p = QPainter(&result);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto path = StarPath(size);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(color);
|
||||
if (filled) {
|
||||
p.drawPath(path);
|
||||
} else {
|
||||
auto stroker = QPainterPathStroker();
|
||||
stroker.setWidth(kPathStroke * size / kPathSize);
|
||||
stroker.setJoinStyle(Qt::MiterJoin);
|
||||
stroker.setMiterLimit(kPathMiterLimit);
|
||||
p.drawPath(stroker.createStroke(path).intersected(path));
|
||||
}
|
||||
p.end();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RateCall::RateCall(QWidget *parent)
|
||||
: RpWidget(parent)
|
||||
, _title(st::callRateTitle.minWidth)
|
||||
, _description(st::callRateDescription.minWidth) {
|
||||
_title.setText(st::callRateTitle.style, tr::lng_call_rate_title(tr::now));
|
||||
_description.setText(
|
||||
st::callRateDescription.style,
|
||||
tr::lng_call_rate_description(tr::now));
|
||||
_cardOverlay = QColor(0, 0, 0, anim::interpolate(0, 255, kDarkOpacity));
|
||||
setMouseTracking(true);
|
||||
resize(st::callRateWidth, Height());
|
||||
}
|
||||
|
||||
RateCall::~RateCall() = default;
|
||||
|
||||
int RateCall::Height() {
|
||||
return st::callRateStarsTop + st::callRateStarsHeight;
|
||||
}
|
||||
|
||||
void RateCall::showAnimated() {
|
||||
_showAnimation.stop();
|
||||
_showAnimation = {};
|
||||
_showAnimation.start(
|
||||
[=] { update(); },
|
||||
0.,
|
||||
1.,
|
||||
kShowDuration + (kStarsCount - 1) * kStarShowDelay,
|
||||
anim::linear);
|
||||
update();
|
||||
}
|
||||
|
||||
void RateCall::setCardOverlay(QColor color) {
|
||||
if (_cardOverlay == color) {
|
||||
return;
|
||||
}
|
||||
_cardOverlay = color;
|
||||
update();
|
||||
}
|
||||
|
||||
void RateCall::setTextColorOverride(std::optional<QColor> color) {
|
||||
if (_textColorOverride == color) {
|
||||
return;
|
||||
}
|
||||
_textColorOverride = color;
|
||||
_starOutline = _starFilled = QImage();
|
||||
update();
|
||||
}
|
||||
|
||||
int RateCall::rating() const {
|
||||
return _rating.current();
|
||||
}
|
||||
|
||||
rpl::producer<int> RateCall::ratingValue() const {
|
||||
return _rating.value();
|
||||
}
|
||||
|
||||
QColor RateCall::textColor() const {
|
||||
return _textColorOverride.value_or(st::callRateTitle.textFg->c);
|
||||
}
|
||||
|
||||
QRect RateCall::cardRect() const {
|
||||
return QRect(
|
||||
(width() - st::callRateWidth) / 2,
|
||||
0,
|
||||
st::callRateWidth,
|
||||
st::callRateHeight);
|
||||
}
|
||||
|
||||
QRect RateCall::starRect(int index) const {
|
||||
const auto single = st::callRateStarSize;
|
||||
const auto full = kStarsCount * single
|
||||
+ (kStarsCount - 1) * st::callRateStarSkip;
|
||||
const auto left = (width() - full) / 2
|
||||
+ index * (single + st::callRateStarSkip);
|
||||
return QRect(left, st::callRateStarsTop, single, single);
|
||||
}
|
||||
|
||||
int RateCall::starByPosition(QPoint position) const {
|
||||
for (auto i = 0; i != kStarsCount; ++i) {
|
||||
if (starRect(i).contains(position)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
float64 RateCall::cardShown() const {
|
||||
const auto total = kShowDuration + (kStarsCount - 1) * kStarShowDelay;
|
||||
const auto passed = _showAnimation.value(1.) * total;
|
||||
return anim::sineInOut(1., std::clamp(passed / kShowDuration, 0., 1.));
|
||||
}
|
||||
|
||||
float64 RateCall::starShown(int index) const {
|
||||
const auto total = kShowDuration + (kStarsCount - 1) * kStarShowDelay;
|
||||
const auto passed = _showAnimation.value(1.) * total
|
||||
- index * kStarShowDelay;
|
||||
return anim::sineInOut(1., std::clamp(passed / kShowDuration, 0., 1.));
|
||||
}
|
||||
|
||||
const QImage &RateCall::starImage(bool filled) const {
|
||||
const auto color = textColor();
|
||||
if (_starColor != color) {
|
||||
_starColor = color;
|
||||
_starOutline = _starFilled = QImage();
|
||||
}
|
||||
auto &image = filled ? _starFilled : _starOutline;
|
||||
if (image.isNull()) {
|
||||
image = StarImage(st::callRateStarSize, color, filled);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
void RateCall::paintEvent(QPaintEvent *e) {
|
||||
auto p = QPainter(this);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
|
||||
paintCard(p, cardShown());
|
||||
for (auto i = 0; i != kStarsCount; ++i) {
|
||||
paintStar(p, i, starShown(i));
|
||||
}
|
||||
paintBurst(p);
|
||||
}
|
||||
|
||||
void RateCall::paintCard(QPainter &p, float64 shown) {
|
||||
if (shown <= 0.) {
|
||||
return;
|
||||
}
|
||||
const auto card = cardRect();
|
||||
const auto scale = kCardShowScaleFrom
|
||||
+ (1. - kCardShowScaleFrom) * shown;
|
||||
|
||||
p.save();
|
||||
p.setOpacity(shown);
|
||||
p.translate(0, anim::interpolate(st::callRateShowShift, 0, shown));
|
||||
p.translate(card.center());
|
||||
p.scale(scale, scale);
|
||||
p.translate(-card.center());
|
||||
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(_cardOverlay);
|
||||
p.drawRoundedRect(card, st::callRateRadius, st::callRateRadius);
|
||||
|
||||
p.setPen(textColor());
|
||||
_title.draw(p, {
|
||||
.position = QPoint(card.x(), card.y() + st::callRateTitleTop),
|
||||
.outerWidth = width(),
|
||||
.availableWidth = card.width(),
|
||||
.align = style::al_top,
|
||||
});
|
||||
_description.draw(p, {
|
||||
.position = QPoint(card.x(), card.y() + st::callRateDescriptionTop),
|
||||
.outerWidth = width(),
|
||||
.availableWidth = card.width(),
|
||||
.align = style::al_top,
|
||||
});
|
||||
p.restore();
|
||||
}
|
||||
|
||||
void RateCall::paintStar(QPainter &p, int index, float64 shown) {
|
||||
if (shown <= 0.) {
|
||||
return;
|
||||
}
|
||||
auto &star = _stars[index];
|
||||
const auto rect = starRect(index);
|
||||
if (star.ripple && !star.ripple->empty()) {
|
||||
auto color = textColor();
|
||||
color.setAlphaF(color.alphaF() * kRippleOpacity);
|
||||
p.setOpacity(shown);
|
||||
star.ripple->paint(p, rect.x(), rect.y(), width(), &color);
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
const auto filled = star.filled.value(star.filledTo);
|
||||
const auto scale = star.pressed.value(star.pressedTo)
|
||||
* (kStarShowScaleFrom + (1. - kStarShowScaleFrom) * shown);
|
||||
|
||||
p.save();
|
||||
p.translate(0, anim::interpolate(st::callRateStarShowShift, 0, shown));
|
||||
p.translate(rect.center());
|
||||
p.scale(scale, scale);
|
||||
p.translate(-rect.center());
|
||||
if (filled < 1.) {
|
||||
p.setOpacity(shown * (1. - filled));
|
||||
p.drawImage(rect.topLeft(), starImage(false));
|
||||
}
|
||||
if (filled > 0.) {
|
||||
p.setOpacity(shown * filled);
|
||||
p.drawImage(rect.topLeft(), starImage(true));
|
||||
}
|
||||
p.restore();
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
|
||||
void RateCall::paintBurst(QPainter &p) {
|
||||
const auto size = st::callRateBurstSize;
|
||||
for (const auto &burst : _bursts) {
|
||||
if (!burst.icon || !burst.icon->animating()) {
|
||||
continue;
|
||||
}
|
||||
burst.icon->paint(
|
||||
p,
|
||||
burst.center.x() - size / 2,
|
||||
burst.center.y() - size / 2);
|
||||
}
|
||||
}
|
||||
|
||||
void RateCall::setOver(int index) {
|
||||
if (_over == index) {
|
||||
return;
|
||||
}
|
||||
_over = index;
|
||||
setCursor((_over >= 0) ? style::cur_pointer : style::cur_default);
|
||||
}
|
||||
|
||||
void RateCall::applyPreview(int preview) {
|
||||
if (_preview == preview) {
|
||||
return;
|
||||
}
|
||||
_preview = preview;
|
||||
const auto shown = _preview ? _preview : _rating.current();
|
||||
for (auto i = 0; i != kStarsCount; ++i) {
|
||||
auto &star = _stars[i];
|
||||
const auto filled = (i < shown) ? 1. : 0.;
|
||||
const auto pressed = (_preview && (i < _preview))
|
||||
? kStarPressedScale
|
||||
: 1.;
|
||||
if (star.filledTo != filled) {
|
||||
star.filled.start(
|
||||
[=] { update(); },
|
||||
star.filled.value(star.filledTo),
|
||||
filled,
|
||||
kSelectDuration);
|
||||
star.filledTo = filled;
|
||||
}
|
||||
if (star.pressedTo != pressed) {
|
||||
star.pressed.start(
|
||||
[=] { update(); },
|
||||
star.pressed.value(star.pressedTo),
|
||||
pressed,
|
||||
kSelectDuration);
|
||||
star.pressedTo = pressed;
|
||||
}
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void RateCall::commit(int index) {
|
||||
_rating = index + 1;
|
||||
applyPreview(0);
|
||||
if (_rating.current() >= kBurstMinRating) {
|
||||
startBurst(index);
|
||||
}
|
||||
}
|
||||
|
||||
void RateCall::startBurst(int index) {
|
||||
const auto free = ranges::find_if(_bursts, [](const Burst &burst) {
|
||||
return !burst.icon || !burst.icon->animating();
|
||||
});
|
||||
const auto burst = (free != end(_bursts))
|
||||
? &*free
|
||||
: (int(_bursts.size()) < kMaxBursts)
|
||||
? &_bursts.emplace_back()
|
||||
: &*ranges::min_element(_bursts, ranges::less(), &Burst::started);
|
||||
if (!burst->icon) {
|
||||
burst->icon = Lottie::MakeIcon({
|
||||
.name = u"call_rate"_q,
|
||||
.sizeOverride = QSize(
|
||||
st::callRateBurstSize,
|
||||
st::callRateBurstSize),
|
||||
});
|
||||
if (!burst->icon->valid()) {
|
||||
burst->icon = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
burst->center = starRect(index).center();
|
||||
burst->started = crl::now();
|
||||
const auto last = burst->icon->framesCount() - 1;
|
||||
burst->icon->animate([=] { update(); }, 0, last);
|
||||
update();
|
||||
}
|
||||
|
||||
void RateCall::mouseMoveEvent(QMouseEvent *e) {
|
||||
setOver(starByPosition(e->pos()));
|
||||
}
|
||||
|
||||
void RateCall::mousePressEvent(QMouseEvent *e) {
|
||||
if (e->button() != Qt::LeftButton) {
|
||||
return;
|
||||
}
|
||||
setOver(starByPosition(e->pos()));
|
||||
if (_over < 0) {
|
||||
return;
|
||||
}
|
||||
_pressed = _over;
|
||||
auto &star = _stars[_pressed];
|
||||
const auto rect = starRect(_pressed);
|
||||
if (!star.ripple) {
|
||||
star.ripple = std::make_unique<Ui::RippleAnimation>(
|
||||
st::callRateStarRipple,
|
||||
Ui::RippleAnimation::EllipseMask(rect.size()),
|
||||
[=] { update(); });
|
||||
}
|
||||
star.ripple->add(e->pos() - rect.topLeft());
|
||||
applyPreview(_pressed + 1);
|
||||
}
|
||||
|
||||
void RateCall::mouseReleaseEvent(QMouseEvent *e) {
|
||||
if (e->button() != Qt::LeftButton) {
|
||||
return;
|
||||
}
|
||||
const auto pressed = std::exchange(_pressed, -1);
|
||||
if (pressed < 0) {
|
||||
return;
|
||||
} else if (const auto &ripple = _stars[pressed].ripple) {
|
||||
ripple->lastStop();
|
||||
}
|
||||
setOver(starByPosition(e->pos()));
|
||||
if (_over == pressed) {
|
||||
commit(pressed);
|
||||
} else {
|
||||
applyPreview(0);
|
||||
}
|
||||
}
|
||||
|
||||
void RateCall::leaveEventHook(QEvent *e) {
|
||||
setOver(-1);
|
||||
}
|
||||
|
||||
EndCloseButton::EndCloseButton(QWidget *parent)
|
||||
: RippleButton(parent, st::callRateCloseRipple)
|
||||
, _text(tr::lng_close(tr::now)) {
|
||||
setDisabled(true);
|
||||
hide();
|
||||
}
|
||||
|
||||
void EndCloseButton::switchToClose(QRect hangupCircle, Fn<void()> close) {
|
||||
_from = hangupCircle;
|
||||
setClickedCallback(std::move(close));
|
||||
_animation.start([=] { update(); }, 0., 1., st::callPanelDuration);
|
||||
_animation.setFinishedCallback([=] { setDisabled(false); });
|
||||
show();
|
||||
update();
|
||||
}
|
||||
|
||||
void EndCloseButton::prepareFrame() {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
if (_frame.size() != size() * ratio) {
|
||||
_frame = QImage(size() * ratio, QImage::Format_ARGB32_Premultiplied);
|
||||
_frame.setDevicePixelRatio(ratio);
|
||||
}
|
||||
_frame.fill(Qt::transparent);
|
||||
|
||||
auto p = QPainter(&_frame);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::callIconBgActive);
|
||||
p.drawRoundedRect(
|
||||
rect(),
|
||||
st::callRateCloseRadius,
|
||||
st::callRateCloseRadius);
|
||||
|
||||
auto ripple = st::callIconFgActive->c;
|
||||
ripple.setAlphaF(ripple.alphaF() * kCloseRippleOpacity);
|
||||
paintRipple(p, 0, 0, &ripple);
|
||||
|
||||
p.setFont(st::callRateCloseFont);
|
||||
p.setCompositionMode(QPainter::CompositionMode_DestinationOut);
|
||||
p.setPen(QColor(0, 0, 0, 255));
|
||||
p.drawText(rect(), Qt::AlignCenter, _text);
|
||||
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
auto shade = st::callIconFgActive->c;
|
||||
shade.setAlphaF(shade.alphaF() * kCloseTextShadeOpacity);
|
||||
p.setPen(shade);
|
||||
p.drawText(rect(), Qt::AlignCenter, _text);
|
||||
}
|
||||
|
||||
void EndCloseButton::paintEvent(QPaintEvent *e) {
|
||||
auto p = QPainter(this);
|
||||
|
||||
const auto shown = _animation.value(1.);
|
||||
if (shown < 1.) {
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto circle = _from.translated(-pos());
|
||||
p.setOpacity(1. - shown);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::callHangupBg);
|
||||
p.drawEllipse(circle);
|
||||
st::callHangup.button.icon.paintInCenter(
|
||||
p,
|
||||
circle,
|
||||
st::callIconFg->c);
|
||||
}
|
||||
|
||||
prepareFrame();
|
||||
p.setOpacity(shown);
|
||||
p.drawImage(0, 0, _frame);
|
||||
}
|
||||
|
||||
QImage EndCloseButton::prepareRippleMask() const {
|
||||
return Ui::RippleAnimation::RoundRectMask(
|
||||
size(),
|
||||
st::callRateCloseRadius);
|
||||
}
|
||||
|
||||
} // namespace Calls
|
||||
116
Telegram/SourceFiles/calls/calls_rate_call.h
Normal file
116
Telegram/SourceFiles/calls/calls_rate_call.h
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
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 "ui/effects/animations.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/text/text.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
|
||||
namespace Lottie {
|
||||
class Icon;
|
||||
} // namespace Lottie
|
||||
|
||||
namespace Ui {
|
||||
class RippleAnimation;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Calls {
|
||||
|
||||
class RateCall final : public Ui::RpWidget {
|
||||
public:
|
||||
explicit RateCall(QWidget *parent);
|
||||
~RateCall();
|
||||
|
||||
void showAnimated();
|
||||
void setTextColorOverride(std::optional<QColor> color);
|
||||
void setCardOverlay(QColor color);
|
||||
|
||||
[[nodiscard]] int rating() const;
|
||||
[[nodiscard]] rpl::producer<int> ratingValue() const;
|
||||
|
||||
[[nodiscard]] static int Height();
|
||||
|
||||
private:
|
||||
static constexpr auto kStarsCount = 5;
|
||||
|
||||
struct Burst {
|
||||
std::unique_ptr<Lottie::Icon> icon;
|
||||
QPoint center;
|
||||
crl::time started = 0;
|
||||
};
|
||||
|
||||
struct Star {
|
||||
std::unique_ptr<Ui::RippleAnimation> ripple;
|
||||
Ui::Animations::Simple filled;
|
||||
Ui::Animations::Simple pressed;
|
||||
float64 filledTo = 0.;
|
||||
float64 pressedTo = 1.;
|
||||
};
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void leaveEventHook(QEvent *e) override;
|
||||
|
||||
void paintCard(QPainter &p, float64 shown);
|
||||
void paintStar(QPainter &p, int index, float64 shown);
|
||||
void paintBurst(QPainter &p);
|
||||
|
||||
void setOver(int index);
|
||||
void applyPreview(int preview);
|
||||
void commit(int index);
|
||||
void startBurst(int index);
|
||||
|
||||
[[nodiscard]] QRect cardRect() const;
|
||||
[[nodiscard]] QRect starRect(int index) const;
|
||||
[[nodiscard]] int starByPosition(QPoint position) const;
|
||||
[[nodiscard]] QColor textColor() const;
|
||||
[[nodiscard]] float64 cardShown() const;
|
||||
[[nodiscard]] float64 starShown(int index) const;
|
||||
[[nodiscard]] const QImage &starImage(bool filled) const;
|
||||
|
||||
Ui::Text::String _title;
|
||||
Ui::Text::String _description;
|
||||
std::array<Star, kStarsCount> _stars;
|
||||
std::vector<Burst> _bursts;
|
||||
Ui::Animations::Simple _showAnimation;
|
||||
std::optional<QColor> _textColorOverride;
|
||||
QColor _cardOverlay;
|
||||
mutable QImage _starOutline;
|
||||
mutable QImage _starFilled;
|
||||
mutable QColor _starColor;
|
||||
rpl::variable<int> _rating = 0;
|
||||
int _preview = 0;
|
||||
int _over = -1;
|
||||
int _pressed = -1;
|
||||
|
||||
};
|
||||
|
||||
class EndCloseButton final : public Ui::RippleButton {
|
||||
public:
|
||||
explicit EndCloseButton(QWidget *parent);
|
||||
|
||||
void switchToClose(QRect hangupCircle, Fn<void()> close);
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
QImage prepareRippleMask() const override;
|
||||
|
||||
void prepareFrame();
|
||||
|
||||
const QString _text;
|
||||
QRect _from;
|
||||
Ui::Animations::Simple _animation;
|
||||
QImage _frame;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Calls
|
||||
Reference in New Issue
Block a user