mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-18 23:27:09 +02:00
Support custom send button for paid.
This commit is contained in:
parent
c6fd8bcb99
commit
17cf354c58
6 changed files with 215 additions and 60 deletions
Telegram/SourceFiles
chat_helpers
history
ui/controls
|
@ -149,6 +149,7 @@ EmojiButton {
|
|||
|
||||
SendButton {
|
||||
inner: IconButton;
|
||||
stars: RoundButton;
|
||||
record: icon;
|
||||
recordOver: icon;
|
||||
round: icon;
|
||||
|
@ -1293,6 +1294,12 @@ historySend: SendButton {
|
|||
icon: historySendIcon;
|
||||
iconOver: historySendIconOver;
|
||||
}
|
||||
stars: RoundButton(defaultActiveButton) {
|
||||
height: 28px;
|
||||
padding: margins(0px, 0px, 6px, 0px);
|
||||
textTop: 5px;
|
||||
width: -8px;
|
||||
}
|
||||
record: historyRecordVoice;
|
||||
recordOver: historyRecordVoiceOver;
|
||||
round: historyRecordRound;
|
||||
|
|
|
@ -491,6 +491,11 @@ HistoryWidget::HistoryWidget(
|
|||
moveFieldControls();
|
||||
}, lifetime());
|
||||
|
||||
_send->widthValue() | rpl::skip(1) | rpl::start_with_next([=] {
|
||||
updateFieldSize();
|
||||
moveFieldControls();
|
||||
}, _send->lifetime());
|
||||
|
||||
_keyboard->sendCommandRequests(
|
||||
) | rpl::start_with_next([=](Bot::SendCommandRequest r) {
|
||||
sendBotCommand(r);
|
||||
|
@ -810,6 +815,7 @@ HistoryWidget::HistoryWidget(
|
|||
}) | rpl::start_with_next([=](Data::PeerUpdate::Flags flags) {
|
||||
if (flags & PeerUpdateFlag::Rights) {
|
||||
updateFieldPlaceholder();
|
||||
updateSendButtonType();
|
||||
_preview->checkNow(false);
|
||||
|
||||
const auto was = (_sendAs != nullptr);
|
||||
|
@ -839,6 +845,7 @@ HistoryWidget::HistoryWidget(
|
|||
}
|
||||
if (flags & PeerUpdateFlag::StarsPerMessage) {
|
||||
updateFieldPlaceholder();
|
||||
updateSendButtonType();
|
||||
}
|
||||
if (flags & PeerUpdateFlag::BotStartToken) {
|
||||
updateControlsVisibility();
|
||||
|
@ -4138,7 +4145,7 @@ void HistoryWidget::checkReplyReturns() {
|
|||
}
|
||||
|
||||
void HistoryWidget::cancelInlineBot() {
|
||||
auto &textWithTags = _field->getTextWithTags();
|
||||
const auto &textWithTags = _field->getTextWithTags();
|
||||
if (textWithTags.text.size() > _inlineBotUsername.size() + 2) {
|
||||
setFieldText(
|
||||
{ '@' + _inlineBotUsername + ' ', TextWithTags::Tags() },
|
||||
|
@ -5134,19 +5141,27 @@ void HistoryWidget::updateSendButtonType() {
|
|||
using Type = Ui::SendButton::Type;
|
||||
|
||||
const auto type = computeSendButtonType();
|
||||
_send->setType(type);
|
||||
|
||||
// This logic is duplicated in RepliesWidget.
|
||||
const auto disabledBySlowmode = _peer
|
||||
&& _peer->slowmodeApplied()
|
||||
&& (_history->latestSendingMessage() != nullptr);
|
||||
|
||||
const auto delay = [&] {
|
||||
return (type != Type::Cancel && type != Type::Save && _peer)
|
||||
? _peer->slowmodeSecondsLeft()
|
||||
: 0;
|
||||
}();
|
||||
_send->setSlowmodeDelay(delay);
|
||||
const auto perMessage = _peer ? _peer->starsPerMessageChecked() : 0;
|
||||
const auto stars = perMessage
|
||||
? perMessage * ComputeSendingMessagesCount(_history, {
|
||||
.forward = &_forwardPanel->items(),
|
||||
.text = &_field->getTextWithTags(),
|
||||
})
|
||||
: 0;
|
||||
_send->setState({
|
||||
.type = (delay > 0) ? Type::Slowmode : type,
|
||||
.slowmodeDelay = delay,
|
||||
.starsToSend = stars,
|
||||
});
|
||||
_send->setDisabled(disabledBySlowmode
|
||||
&& (type == Type::Send
|
||||
|| type == Type::Record
|
||||
|
|
|
@ -2164,6 +2164,10 @@ void ComposeControls::initSendButton() {
|
|||
_recordAvailability = value;
|
||||
updateSendButtonType();
|
||||
}, _send->lifetime());
|
||||
|
||||
_send->widthValue() | rpl::skip(1) | rpl::start_with_next([=] {
|
||||
updateControlsGeometry(_wrap->size());
|
||||
}, _send->lifetime());
|
||||
}
|
||||
|
||||
void ComposeControls::initSendAsButton(not_null<PeerData*> peer) {
|
||||
|
@ -2550,14 +2554,18 @@ SendMenu::Details ComposeControls::sendButtonMenuDetails() const {
|
|||
void ComposeControls::updateSendButtonType() {
|
||||
using Type = Ui::SendButton::Type;
|
||||
const auto type = computeSendButtonType();
|
||||
_send->setType(type);
|
||||
|
||||
const auto delay = [&] {
|
||||
return (type != Type::Cancel && type != Type::Save)
|
||||
? _slowmodeSecondsLeft.current()
|
||||
: 0;
|
||||
}();
|
||||
_send->setSlowmodeDelay(delay);
|
||||
const auto peer = _history ? _history->peer.get() : nullptr;
|
||||
const auto stars = peer ? peer->starsPerMessageChecked() : 0;
|
||||
_send->setState({
|
||||
.type = type,
|
||||
.slowmodeDelay = delay,
|
||||
.starsToSend = stars,
|
||||
});
|
||||
_send->setDisabled(_sendDisabledBySlowmode.current()
|
||||
&& (type == Type::Send
|
||||
|| type == Type::Record
|
||||
|
|
|
@ -524,6 +524,7 @@ public:
|
|||
ListenWrap(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const style::RecordBar &st,
|
||||
std::shared_ptr<Ui::SendButton> send,
|
||||
not_null<Main::Session*> session,
|
||||
not_null<Ui::RoundVideoResult*> data,
|
||||
const style::font &font);
|
||||
|
@ -551,6 +552,7 @@ private:
|
|||
const not_null<Ui::RpWidget*> _parent;
|
||||
|
||||
const style::RecordBar &_st;
|
||||
const std::shared_ptr<Ui::SendButton> _send;
|
||||
const not_null<Main::Session*> _session;
|
||||
const not_null<DocumentData*> _document;
|
||||
const std::unique_ptr<VoiceData> _voiceData;
|
||||
|
@ -585,11 +587,13 @@ private:
|
|||
ListenWrap::ListenWrap(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const style::RecordBar &st,
|
||||
std::shared_ptr<Ui::SendButton> send,
|
||||
not_null<Main::Session*> session,
|
||||
not_null<Ui::RoundVideoResult*> data,
|
||||
const style::font &font)
|
||||
: _parent(parent)
|
||||
, _st(st)
|
||||
, _send(send)
|
||||
, _session(session)
|
||||
, _document(DummyDocument(&session->data()))
|
||||
, _voiceData(ProcessCaptureResult(data->waveform))
|
||||
|
@ -614,14 +618,18 @@ void ListenWrap::init() {
|
|||
}) | rpl::distinct_until_changed();
|
||||
_delete->showOn(std::move(deleteShow));
|
||||
|
||||
_parent->sizeValue(
|
||||
) | rpl::start_with_next([=](QSize size) {
|
||||
rpl::combine(
|
||||
_parent->sizeValue(),
|
||||
_send->widthValue()
|
||||
) | rpl::start_with_next([=](QSize size, int send) {
|
||||
_waveformBgRect = QRect({ 0, 0 }, size)
|
||||
.marginsRemoved(st::historyRecordWaveformBgMargins);
|
||||
{
|
||||
const auto m = _st.remove.width + _waveformBgRect.height() / 2;
|
||||
const auto skip = _waveformBgRect.height() / 2;
|
||||
const auto left = _st.remove.width + skip;
|
||||
const auto right = send + skip;
|
||||
_waveformBgFinalCenterRect = _waveformBgRect.marginsRemoved(
|
||||
style::margins(m, 0, m, 0));
|
||||
style::margins(left, 0, right, 0));
|
||||
}
|
||||
{
|
||||
const auto &play = _playPauseSt.playOuter;
|
||||
|
@ -651,7 +659,7 @@ void ListenWrap::init() {
|
|||
const auto deleteIconLeft = remove.iconPosition.x();
|
||||
const auto bgRectRight = anim::interpolate(
|
||||
deleteIconLeft,
|
||||
remove.width,
|
||||
_send->width(),
|
||||
_isShowAnimation ? progress : 1.);
|
||||
const auto bgRectLeft = anim::interpolate(
|
||||
_parent->width() - deleteIconLeft - _waveformBgRect.height(),
|
||||
|
@ -1973,6 +1981,7 @@ void VoiceRecordBar::stopRecording(StopType type, bool ttlBeforeHide) {
|
|||
_listen = std::make_unique<ListenWrap>(
|
||||
this,
|
||||
_st,
|
||||
_send,
|
||||
&_show->session(),
|
||||
&_data,
|
||||
_cancelFont);
|
||||
|
@ -2006,6 +2015,7 @@ void VoiceRecordBar::stopRecording(StopType type, bool ttlBeforeHide) {
|
|||
_listen = std::make_unique<ListenWrap>(
|
||||
this,
|
||||
_st,
|
||||
_send,
|
||||
&_show->session(),
|
||||
&_data,
|
||||
_cancelFont);
|
||||
|
|
|
@ -7,10 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "ui/controls/send_button.h"
|
||||
|
||||
#include "lang/lang_tag.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_credits.h" // starIconEmoji
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
@ -22,47 +25,58 @@ constexpr int kWideScale = 5;
|
|||
SendButton::SendButton(QWidget *parent, const style::SendButton &st)
|
||||
: RippleButton(parent, st.inner.ripple)
|
||||
, _st(st) {
|
||||
resize(_st.inner.width, _st.inner.height);
|
||||
updateSize();
|
||||
}
|
||||
|
||||
void SendButton::setType(Type type) {
|
||||
Expects(isSlowmode() || type != Type::Slowmode);
|
||||
|
||||
if (isSlowmode() && type != Type::Slowmode) {
|
||||
_afterSlowmodeType = type;
|
||||
void SendButton::setState(State state) {
|
||||
if (_state == state) {
|
||||
return;
|
||||
}
|
||||
if (_type != type) {
|
||||
const auto hasSlowmode = (_state.slowmodeDelay > 0);
|
||||
const auto hasSlowmodeChanged = hasSlowmode != (state.slowmodeDelay > 0);
|
||||
auto withSameSlowmode = state;
|
||||
withSameSlowmode.slowmodeDelay = _state.slowmodeDelay;
|
||||
const auto animate = hasSlowmodeChanged
|
||||
|| (!hasSlowmode && withSameSlowmode != _state);
|
||||
if (animate) {
|
||||
_contentFrom = grabContent();
|
||||
_type = type;
|
||||
_a_typeChanged.stop();
|
||||
}
|
||||
if (_state.slowmodeDelay != state.slowmodeDelay) {
|
||||
const auto seconds = state.slowmodeDelay;
|
||||
const auto minutes = seconds / 60;
|
||||
_slowmodeDelayText = seconds
|
||||
? u"%1:%2"_q.arg(minutes).arg(seconds % 60, 2, 10, QChar('0'))
|
||||
: QString();
|
||||
}
|
||||
if (!state.starsToSend || state.type != Type::Send) {
|
||||
_starsToSendText = Text::String();
|
||||
} else if (_starsToSendText.isEmpty()
|
||||
|| _state.starsToSend != state.starsToSend) {
|
||||
_starsToSendText.setMarkedText(
|
||||
_st.stars.style,
|
||||
Text::IconEmoji(&st::starIconEmoji).append(
|
||||
Lang::FormatCountToShort(state.starsToSend).string),
|
||||
kMarkupTextOptions);
|
||||
}
|
||||
_state = state;
|
||||
if (animate) {
|
||||
_stateChangeFromWidth = width();
|
||||
_stateChangeAnimation.stop();
|
||||
updateSize();
|
||||
_contentTo = grabContent();
|
||||
_a_typeChanged.start(
|
||||
[=] { update(); },
|
||||
_stateChangeAnimation.start(
|
||||
[=] { updateSize(); update(); },
|
||||
0.,
|
||||
1.,
|
||||
st::universalDuration);
|
||||
setPointerCursor(_type != Type::Slowmode);
|
||||
setPointerCursor(_state.type != Type::Slowmode);
|
||||
updateSize();
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void SendButton::setSlowmodeDelay(int seconds) {
|
||||
Expects(seconds >= 0 && seconds < kSlowmodeDelayLimit);
|
||||
|
||||
if (_slowmodeDelay == seconds) {
|
||||
return;
|
||||
}
|
||||
_slowmodeDelay = seconds;
|
||||
_slowmodeDelayText = isSlowmode()
|
||||
? u"%1:%2"_q.arg(seconds / 60).arg(seconds % 60, 2, 10, QChar('0'))
|
||||
: QString();
|
||||
setType(isSlowmode() ? Type::Slowmode : _afterSlowmodeType);
|
||||
update();
|
||||
}
|
||||
|
||||
void SendButton::finishAnimating() {
|
||||
_a_typeChanged.stop();
|
||||
_stateChangeAnimation.stop();
|
||||
update();
|
||||
}
|
||||
|
||||
|
@ -70,26 +84,60 @@ void SendButton::paintEvent(QPaintEvent *e) {
|
|||
auto p = QPainter(this);
|
||||
|
||||
auto over = (isDown() || isOver());
|
||||
auto changed = _a_typeChanged.value(1.);
|
||||
auto changed = _stateChangeAnimation.value(1.);
|
||||
if (changed < 1.) {
|
||||
PainterHighQualityEnabler hq(p);
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
|
||||
p.setOpacity(1. - changed);
|
||||
auto targetRect = QRect((1 - kWideScale) / 2 * width(), (1 - kWideScale) / 2 * height(), kWideScale * width(), kWideScale * height());
|
||||
auto hiddenWidth = anim::interpolate(0, (1 - kWideScale) / 2 * width(), changed);
|
||||
auto hiddenHeight = anim::interpolate(0, (1 - kWideScale) / 2 * height(), changed);
|
||||
p.drawPixmap(targetRect.marginsAdded(QMargins(hiddenWidth, hiddenHeight, hiddenWidth, hiddenHeight)), _contentFrom);
|
||||
const auto fromSize = _contentFrom.size() / (kWideScale * ratio);
|
||||
const auto fromShift = QPoint(
|
||||
(width() - fromSize.width()) / 2,
|
||||
(height() - fromSize.height()) / 2);
|
||||
auto fromRect = QRect(
|
||||
(1 - kWideScale) / 2 * fromSize.width(),
|
||||
(1 - kWideScale) / 2 * fromSize.height(),
|
||||
kWideScale * fromSize.width(),
|
||||
kWideScale * fromSize.height()
|
||||
).translated(fromShift);
|
||||
auto hiddenWidth = anim::interpolate(0, (1 - kWideScale) / 2 * fromSize.width(), changed);
|
||||
auto hiddenHeight = anim::interpolate(0, (1 - kWideScale) / 2 * fromSize.height(), changed);
|
||||
p.drawPixmap(
|
||||
fromRect.marginsAdded(
|
||||
{ hiddenWidth, hiddenHeight, hiddenWidth, hiddenHeight }),
|
||||
_contentFrom);
|
||||
|
||||
p.setOpacity(changed);
|
||||
const auto toSize = _contentTo.size() / (kWideScale * ratio);
|
||||
const auto toShift = QPoint(
|
||||
(width() - toSize.width()) / 2,
|
||||
(height() - toSize.height()) / 2);
|
||||
auto toRect = QRect(
|
||||
(1 - kWideScale) / 2 * toSize.width(),
|
||||
(1 - kWideScale) / 2 * toSize.height(),
|
||||
kWideScale * toSize.width(),
|
||||
kWideScale * toSize.height()
|
||||
).translated(toShift);
|
||||
auto shownWidth = anim::interpolate((1 - kWideScale) / 2 * width(), 0, changed);
|
||||
auto shownHeight = anim::interpolate((1 - kWideScale) / 2 * height(), 0, changed);
|
||||
p.drawPixmap(targetRect.marginsAdded(QMargins(shownWidth, shownHeight, shownWidth, shownHeight)), _contentTo);
|
||||
auto shownHeight = anim::interpolate((1 - kWideScale) / 2 * toSize.height(), 0, changed);
|
||||
p.drawPixmap(
|
||||
toRect.marginsAdded(
|
||||
{ shownWidth, shownHeight, shownWidth, shownHeight }),
|
||||
_contentTo);
|
||||
return;
|
||||
}
|
||||
switch (_type) {
|
||||
switch (_state.type) {
|
||||
case Type::Record: paintRecord(p, over); break;
|
||||
case Type::Round: paintRound(p, over); break;
|
||||
case Type::Save: paintSave(p, over); break;
|
||||
case Type::Cancel: paintCancel(p, over); break;
|
||||
case Type::Send: paintSend(p, over); break;
|
||||
case Type::Send:
|
||||
if (_starsToSendText.isEmpty()) {
|
||||
paintSend(p, over);
|
||||
} else {
|
||||
paintStarsToSend(p, over);
|
||||
}
|
||||
break;
|
||||
case Type::Schedule: paintSchedule(p, over); break;
|
||||
case Type::Slowmode: paintSlowmode(p); break;
|
||||
}
|
||||
|
@ -152,6 +200,23 @@ void SendButton::paintSend(QPainter &p, bool over) {
|
|||
}
|
||||
}
|
||||
|
||||
void SendButton::paintStarsToSend(QPainter &p, bool over) {
|
||||
const auto geometry = starsGeometry();
|
||||
{
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(over ? _st.stars.textBgOver : _st.stars.textBg);
|
||||
const auto radius = geometry.rounded.height() / 2;
|
||||
p.drawRoundedRect(geometry.rounded, radius, radius);
|
||||
}
|
||||
p.setPen(over ? _st.stars.textFgOver : _st.stars.textFg);
|
||||
_starsToSendText.draw(p, {
|
||||
.position = geometry.inner.topLeft(),
|
||||
.outerWidth = width(),
|
||||
.availableWidth = geometry.inner.width(),
|
||||
});
|
||||
}
|
||||
|
||||
void SendButton::paintSchedule(QPainter &p, bool over) {
|
||||
{
|
||||
PainterHighQualityEnabler hq(p);
|
||||
|
@ -178,8 +243,40 @@ void SendButton::paintSlowmode(QPainter &p) {
|
|||
style::al_center);
|
||||
}
|
||||
|
||||
bool SendButton::isSlowmode() const {
|
||||
return (_slowmodeDelay > 0);
|
||||
SendButton::StarsGeometry SendButton::starsGeometry() const {
|
||||
const auto &st = _st.stars;
|
||||
const auto inner = QRect(
|
||||
0,
|
||||
0,
|
||||
_starsToSendText.maxWidth(),
|
||||
st.style.font->height);
|
||||
const auto rounded = inner.marginsAdded(QMargins(
|
||||
st.padding.left() - st.width / 2,
|
||||
st.padding.top() + st.textTop,
|
||||
st.padding.right() - st.width / 2,
|
||||
st.height - st.padding.top() - st.textTop - st.style.font->height));
|
||||
const auto add = (_st.inner.height - rounded.height()) / 2;
|
||||
const auto outer = rounded.marginsAdded(QMargins(
|
||||
add,
|
||||
add,
|
||||
add,
|
||||
_st.inner.height - add - rounded.height()));
|
||||
const auto shift = -outer.topLeft();
|
||||
return {
|
||||
.inner = inner.translated(shift),
|
||||
.rounded = rounded.translated(shift),
|
||||
.outer = outer.translated(shift),
|
||||
};
|
||||
}
|
||||
|
||||
void SendButton::updateSize() {
|
||||
const auto finalWidth = _starsToSendText.isEmpty()
|
||||
? _st.inner.width
|
||||
: starsGeometry().outer.width();
|
||||
const auto progress = _stateChangeAnimation.value(1.);
|
||||
resize(
|
||||
anim::interpolate(_stateChangeFromWidth, finalWidth, progress),
|
||||
_st.inner.height);
|
||||
}
|
||||
|
||||
QPixmap SendButton::grabContent() {
|
||||
|
@ -195,7 +292,7 @@ QPixmap SendButton::grabContent() {
|
|||
(kWideScale - 1) / 2 * height(),
|
||||
GrabWidget(this));
|
||||
}
|
||||
return Ui::PixmapFromImage(std::move(result));
|
||||
return PixmapFromImage(std::move(result));
|
||||
}
|
||||
|
||||
QImage SendButton::prepareRippleMask() const {
|
||||
|
|
|
@ -30,11 +30,21 @@ public:
|
|||
Cancel,
|
||||
Slowmode,
|
||||
};
|
||||
struct State {
|
||||
Type type = Type::Send;
|
||||
int slowmodeDelay = 0;
|
||||
int starsToSend = 0;
|
||||
|
||||
friend inline constexpr auto operator<=>(State, State) = default;
|
||||
friend inline constexpr bool operator==(State, State) = default;
|
||||
};
|
||||
[[nodiscard]] Type type() const {
|
||||
return _type;
|
||||
return _state.type;
|
||||
}
|
||||
void setType(Type state);
|
||||
void setSlowmodeDelay(int seconds);
|
||||
[[nodiscard]] State state() const {
|
||||
return _state;
|
||||
}
|
||||
void setState(State state);
|
||||
void finishAnimating();
|
||||
|
||||
protected:
|
||||
|
@ -44,8 +54,15 @@ protected:
|
|||
QPoint prepareRippleStartPosition() const override;
|
||||
|
||||
private:
|
||||
struct StarsGeometry {
|
||||
QRect inner;
|
||||
QRect rounded;
|
||||
QRect outer;
|
||||
};
|
||||
[[nodiscard]] QPixmap grabContent();
|
||||
[[nodiscard]] bool isSlowmode() const;
|
||||
void updateSize();
|
||||
|
||||
[[nodiscard]] StarsGeometry starsGeometry() const;
|
||||
|
||||
void paintRecord(QPainter &p, bool over);
|
||||
void paintRound(QPainter &p, bool over);
|
||||
|
@ -54,17 +71,18 @@ private:
|
|||
void paintSend(QPainter &p, bool over);
|
||||
void paintSchedule(QPainter &p, bool over);
|
||||
void paintSlowmode(QPainter &p);
|
||||
void paintStarsToSend(QPainter &p, bool over);
|
||||
|
||||
const style::SendButton &_st;
|
||||
|
||||
Type _type = Type::Send;
|
||||
Type _afterSlowmodeType = Type::Send;
|
||||
State _state;
|
||||
QPixmap _contentFrom, _contentTo;
|
||||
|
||||
Ui::Animations::Simple _a_typeChanged;
|
||||
Ui::Animations::Simple _stateChangeAnimation;
|
||||
int _stateChangeFromWidth = 0;
|
||||
|
||||
int _slowmodeDelay = 0;
|
||||
QString _slowmodeDelayText;
|
||||
Ui::Text::String _starsToSendText;
|
||||
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue