mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-14 13:17:08 +02:00
Added new viewer widget for voice messages with ttl.
This commit is contained in:
parent
f8caa02f10
commit
37067f17e2
10 changed files with 379 additions and 34 deletions
|
@ -406,6 +406,8 @@ PRIVATE
|
|||
chat_helpers/tabbed_section.h
|
||||
chat_helpers/tabbed_selector.cpp
|
||||
chat_helpers/tabbed_selector.h
|
||||
chat_helpers/ttl_media_layer_widget.cpp
|
||||
chat_helpers/ttl_media_layer_widget.h
|
||||
core/application.cpp
|
||||
core/application.h
|
||||
core/base_integration.cpp
|
||||
|
|
|
@ -1719,6 +1719,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_ttl_voice_expired" = "Voice message expired";
|
||||
"lng_ttl_round_sent" = "You sent a self-destructing video message.";
|
||||
"lng_ttl_round_expired" = "Round message expired";
|
||||
"lng_ttl_voice_tooltip_in" = "This voice message can only be played once.";
|
||||
"lng_ttl_voice_tooltip_out" = "This message will disappear once **{user}** plays it once.";
|
||||
"lng_ttl_voice_close_in" = "Delete and close";
|
||||
|
||||
"lng_profile_add_more_after_create" = "You will be able to add more members after you create the group.";
|
||||
"lng_profile_camera_title" = "Capture yourself";
|
||||
|
|
|
@ -1266,3 +1266,17 @@ dragDropColor: windowActiveTextFg;
|
|||
dragMargin: margins(0px, 10px, 0px, 10px);
|
||||
dragPadding: margins(20px, 10px, 20px, 10px);
|
||||
dragHeight: 72px;
|
||||
|
||||
ttlMediaImportantTooltipLabel: FlatLabel(defaultImportantTooltipLabel) {
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(14px);
|
||||
}
|
||||
}
|
||||
ttlMediaButton: RoundButton(defaultActiveButton) {
|
||||
textBg: shadowFg;
|
||||
textBgOver: shadowFg;
|
||||
ripple: universalRippleAnimation;
|
||||
height: 31px;
|
||||
textTop: 6px;
|
||||
}
|
||||
ttlMediaButtonBottomSkip: 14px;
|
||||
|
|
273
Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
Normal file
273
Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
Normal file
|
@ -0,0 +1,273 @@
|
|||
/*
|
||||
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 "chat_helpers/ttl_media_layer_widget.h"
|
||||
|
||||
#include "base/event_filter.h"
|
||||
#include "data/data_session.h"
|
||||
#include "editor/editor_layer_widget.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/view/media/history_view_document.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mainwidget.h"
|
||||
#include "media/audio/media_audio.h"
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/chat/chat_theme.h"
|
||||
#include "ui/effects/path_shift_gradient.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/tooltip.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
|
||||
namespace ChatHelpers {
|
||||
namespace {
|
||||
|
||||
class PreviewDelegate final : public HistoryView::DefaultElementDelegate {
|
||||
public:
|
||||
PreviewDelegate(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Ui::ChatStyle*> st,
|
||||
Fn<void()> update);
|
||||
|
||||
bool elementAnimationsPaused() override;
|
||||
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
|
||||
HistoryView::Context elementContext() override;
|
||||
bool elementIsChatWide() override;
|
||||
|
||||
private:
|
||||
const not_null<QWidget*> _parent;
|
||||
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
|
||||
|
||||
};
|
||||
|
||||
PreviewDelegate::PreviewDelegate(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Ui::ChatStyle*> st,
|
||||
Fn<void()> update)
|
||||
: _parent(parent)
|
||||
, _pathGradient(HistoryView::MakePathShiftGradient(st, update)) {
|
||||
}
|
||||
|
||||
bool PreviewDelegate::elementAnimationsPaused() {
|
||||
return _parent->window()->isActiveWindow();
|
||||
}
|
||||
|
||||
not_null<Ui::PathShiftGradient*> PreviewDelegate::elementPathShiftGradient() {
|
||||
return _pathGradient.get();
|
||||
}
|
||||
|
||||
HistoryView::Context PreviewDelegate::elementContext() {
|
||||
return HistoryView::Context::TTLViewer;
|
||||
}
|
||||
|
||||
bool PreviewDelegate::elementIsChatWide() {
|
||||
return true;
|
||||
}
|
||||
|
||||
class PreviewWrap final : public Ui::RpWidget {
|
||||
public:
|
||||
PreviewWrap(not_null<Ui::RpWidget*> parent, not_null<HistoryItem*> item);
|
||||
~PreviewWrap();
|
||||
|
||||
[[nodiscard]] rpl::producer<> closeRequests() const;
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
[[nodiscard]] QRect elementRect() const;
|
||||
|
||||
const not_null<HistoryItem*> _item;
|
||||
const std::unique_ptr<Ui::ChatTheme> _theme;
|
||||
const std::unique_ptr<Ui::ChatStyle> _style;
|
||||
const std::unique_ptr<PreviewDelegate> _delegate;
|
||||
std::unique_ptr<HistoryView::Element> _element;
|
||||
rpl::lifetime _elementLifetime;
|
||||
|
||||
rpl::event_stream<> _closeRequests;
|
||||
|
||||
};
|
||||
|
||||
PreviewWrap::PreviewWrap(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<HistoryItem*> item)
|
||||
: RpWidget(parent)
|
||||
, _item(item)
|
||||
, _theme(Window::Theme::DefaultChatThemeOn(lifetime()))
|
||||
, _style(std::make_unique<Ui::ChatStyle>(
|
||||
item->history()->session().colorIndicesValue()))
|
||||
, _delegate(std::make_unique<PreviewDelegate>(
|
||||
parent,
|
||||
_style.get(),
|
||||
[=] { update(elementRect()); })) {
|
||||
_style->apply(_theme.get());
|
||||
|
||||
const auto session = &_item->history()->session();
|
||||
session->data().viewRepaintRequest(
|
||||
) | rpl::start_with_next([=](not_null<const HistoryView::Element*> view) {
|
||||
if (view == _element.get()) {
|
||||
update(elementRect());
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
const auto closeCallback = [=] { _closeRequests.fire({}); };
|
||||
|
||||
{
|
||||
const auto close = Ui::CreateChild<Ui::RoundButton>(
|
||||
this,
|
||||
item->out()
|
||||
? tr::lng_close()
|
||||
: tr::lng_ttl_voice_close_in(),
|
||||
st::ttlMediaButton);
|
||||
close->setFullRadius(true);
|
||||
close->setClickedCallback(closeCallback);
|
||||
close->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
|
||||
sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &s) {
|
||||
close->moveToLeft(
|
||||
(s.width() - close->width()) / 2,
|
||||
s.height() - close->height() - st::ttlMediaButtonBottomSkip);
|
||||
}, close->lifetime());
|
||||
}
|
||||
|
||||
QWidget::setAttribute(Qt::WA_OpaquePaintEvent, false);
|
||||
_element = _item->createView(_delegate.get());
|
||||
|
||||
{
|
||||
_element->initDimensions();
|
||||
widthValue(
|
||||
) | rpl::filter([=](int width) {
|
||||
return width > st::msgMinWidth;
|
||||
}) | rpl::start_with_next([=](int width) {
|
||||
_element->resizeGetHeight(width);
|
||||
}, _elementLifetime);
|
||||
}
|
||||
|
||||
{
|
||||
auto text = item->out()
|
||||
? tr::lng_ttl_voice_tooltip_out(
|
||||
lt_user,
|
||||
rpl::single(
|
||||
item->history()->peer->name()
|
||||
) | rpl::map(Ui::Text::RichLangValue),
|
||||
Ui::Text::RichLangValue)
|
||||
: tr::lng_ttl_voice_tooltip_in(Ui::Text::RichLangValue);
|
||||
const auto tooltip = Ui::CreateChild<Ui::ImportantTooltip>(
|
||||
this,
|
||||
object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
|
||||
this,
|
||||
Ui::MakeNiceTooltipLabel(
|
||||
parent,
|
||||
std::move(text),
|
||||
st::dialogsStoriesTooltipMaxWidth,
|
||||
st::ttlMediaImportantTooltipLabel),
|
||||
st::defaultImportantTooltip.padding),
|
||||
st::dialogsStoriesTooltip);
|
||||
tooltip->toggleFast(true);
|
||||
sizeValue(
|
||||
) | rpl::filter(
|
||||
[](const QSize &s) { return !s.isNull(); }
|
||||
) | rpl::take(1) | rpl::start_with_next([=](const QSize &s) {
|
||||
if (s.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
auto area = elementRect();
|
||||
area.setWidth(_element->media()
|
||||
? _element->media()->width()
|
||||
: _element->width());
|
||||
tooltip->pointAt(area, RectPart::Top, [=](QSize size) {
|
||||
return QPoint{
|
||||
(area.width() - size.width()) / 2,
|
||||
(s.height() - size.height() * 2 - _element->height()) / 2
|
||||
- st::defaultImportantTooltip.padding.top(),
|
||||
};
|
||||
});
|
||||
}, tooltip->lifetime());
|
||||
}
|
||||
|
||||
HistoryView::TTLVoiceStops(
|
||||
item->fullId()
|
||||
) | rpl::start_with_next(closeCallback, lifetime());
|
||||
}
|
||||
|
||||
QRect PreviewWrap::elementRect() const {
|
||||
return QRect(
|
||||
(width() - _element->width()) / 2,
|
||||
(height() - _element->height()) / 2,
|
||||
_element->width(),
|
||||
_element->height());
|
||||
}
|
||||
|
||||
rpl::producer<> PreviewWrap::closeRequests() const {
|
||||
return _closeRequests.events();
|
||||
}
|
||||
|
||||
PreviewWrap::~PreviewWrap() {
|
||||
_elementLifetime.destroy();
|
||||
_element = nullptr;
|
||||
}
|
||||
|
||||
void PreviewWrap::paintEvent(QPaintEvent *e) {
|
||||
if (!_element) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto p = Painter(this);
|
||||
const auto r = rect();
|
||||
|
||||
auto context = _theme->preparePaintContext(
|
||||
_style.get(),
|
||||
r,
|
||||
e->rect(),
|
||||
!window()->isActiveWindow());
|
||||
context.outbg = _element->hasOutLayout();
|
||||
|
||||
p.translate(
|
||||
(r.width() - _element->width()) / 2,
|
||||
(r.height() - _element->height()) / 2);
|
||||
_element->draw(p, context);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ShowTTLMediaLayerWidget(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item) {
|
||||
const auto parent = controller->content();
|
||||
const auto show = controller->uiShow();
|
||||
auto preview = base::make_unique_q<PreviewWrap>(parent, item);
|
||||
preview->closeRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
show->hideLayer();
|
||||
}, preview->lifetime());
|
||||
auto layer = std::make_unique<Editor::LayerWidget>(
|
||||
parent,
|
||||
std::move(preview));
|
||||
layer->lifetime().add([] { ::Media::Player::instance()->stop(); });
|
||||
base::install_event_filter(layer.get(), [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::KeyPress) {
|
||||
const auto k = static_cast<QKeyEvent*>(e.get());
|
||||
if (k->key() == Qt::Key_Escape) {
|
||||
show->hideLayer();
|
||||
}
|
||||
return base::EventFilterResult::Cancel;
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
controller->showLayer(std::move(layer), Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
} // namespace ChatHelpers
|
22
Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.h
Normal file
22
Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
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
|
||||
|
||||
class HistoryItem;
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace ChatHelpers {
|
||||
|
||||
void ShowTTLMediaLayerWidget(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item);
|
||||
|
||||
} // namespace ChatHelpers
|
|
@ -9,7 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "base/options.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/abstract_box.h" // Ui::show().
|
||||
#include "chat_helpers/ttl_media_layer_widget.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "core/mime_type.h"
|
||||
|
@ -17,17 +18,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_document_media.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/view/media/history_view_gif.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "history/view/media/history_view_gif.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "platform/platform_file_utilities.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/chat/chat_theme.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "boxes/abstract_box.h" // Ui::show().
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
#include <QtCore/QBuffer>
|
||||
|
@ -298,6 +299,12 @@ void ResolveDocument(
|
|||
|| document->isVoiceMessage()
|
||||
|| document->isVideoMessage()) {
|
||||
::Media::Player::instance()->playPause({ document, msgId });
|
||||
if (controller
|
||||
&& item
|
||||
&& item->media()
|
||||
&& item->media()->ttlSeconds()) {
|
||||
ChatHelpers::ShowTTLMediaLayerWidget(controller, item);
|
||||
}
|
||||
} else {
|
||||
showDocument();
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ enum class Context : char {
|
|||
AdminLog,
|
||||
ContactPreview,
|
||||
SavedSublist,
|
||||
TTLViewer,
|
||||
};
|
||||
|
||||
enum class OnlyEmojiAndSpaces : char {
|
||||
|
|
|
@ -2005,6 +2005,7 @@ bool Message::hasFromPhoto() const {
|
|||
return !item->out() && !item->history()->peer->isUser();
|
||||
} break;
|
||||
case Context::ContactPreview:
|
||||
case Context::TTLViewer:
|
||||
return false;
|
||||
}
|
||||
Unexpected("Context in Message::hasFromPhoto.");
|
||||
|
@ -3200,9 +3201,10 @@ bool Message::hasFromName() const {
|
|||
return false;
|
||||
} break;
|
||||
case Context::ContactPreview:
|
||||
case Context::TTLViewer:
|
||||
return false;
|
||||
}
|
||||
Unexpected("Context in Message::hasFromPhoto.");
|
||||
Unexpected("Context in Message::hasFromName.");
|
||||
}
|
||||
|
||||
bool Message::displayFromName() const {
|
||||
|
|
|
@ -134,7 +134,11 @@ void DrawCornerBadgeTTL(
|
|||
});
|
||||
animate(animate);
|
||||
});
|
||||
const auto weak = std::weak_ptr(lifetime);
|
||||
return [=](QPainter &p, QRect r, QColor c) {
|
||||
if (weak.expired()) {
|
||||
return;
|
||||
}
|
||||
(state->idle ? state->idle : state->start)->paintInCenter(p, r, c);
|
||||
};
|
||||
}
|
||||
|
@ -326,41 +330,36 @@ Document::Document(
|
|||
}
|
||||
|
||||
if ((_data->isVoiceMessage() || isRound)
|
||||
&& IsVoiceOncePlayable(_parent->data())) {
|
||||
_parent->data()->removeFromSharedMediaIndex();
|
||||
setDocumentLinks(_data, realParent, [=] {
|
||||
_openl = nullptr;
|
||||
|
||||
&& _parent->data()->media()->ttlSeconds()) {
|
||||
const auto fullId = _realParent->fullId();
|
||||
if (_parent->delegate()->elementContext() == Context::TTLViewer) {
|
||||
auto lifetime = std::make_shared<rpl::lifetime>();
|
||||
rpl::merge(
|
||||
::Media::Player::instance()->updatedNotifier(
|
||||
) | rpl::filter([=](::Media::Player::TrackState state) {
|
||||
using State = ::Media::Player::State;
|
||||
const auto badState = state.state == State::Stopped
|
||||
|| state.state == State::StoppedAtEnd
|
||||
|| state.state == State::StoppedAtError
|
||||
|| state.state == State::StoppedAtStart;
|
||||
return (state.id.contextId() != _realParent->fullId())
|
||||
&& !badState;
|
||||
}) | rpl::to_empty,
|
||||
::Media::Player::instance()->tracksFinished(
|
||||
) | rpl::filter([=](AudioMsgId::Type type) {
|
||||
return (type == AudioMsgId::Type::Voice);
|
||||
}) | rpl::to_empty,
|
||||
::Media::Player::instance()->stops(AudioMsgId::Type::Voice)
|
||||
) | rpl::start_with_next([=]() mutable {
|
||||
_drawTtl = nullptr;
|
||||
const auto item = _parent->data();
|
||||
TTLVoiceStops(fullId) | rpl::start_with_next([=]() mutable {
|
||||
if (lifetime) {
|
||||
base::take(lifetime)->destroy();
|
||||
}
|
||||
// Destroys this.
|
||||
ClearMediaAsExpired(item);
|
||||
}, *lifetime);
|
||||
_drawTtl = CreateTtlPaintCallback(lifetime, [=] { repaint(); });
|
||||
} else if (!_parent->data()->out()) {
|
||||
_parent->data()->removeFromSharedMediaIndex();
|
||||
setDocumentLinks(_data, realParent, [=] {
|
||||
_openl = nullptr;
|
||||
|
||||
return false;
|
||||
});
|
||||
auto lifetime = std::make_shared<rpl::lifetime>();
|
||||
TTLVoiceStops(fullId) | rpl::start_with_next([=]() mutable {
|
||||
const auto item = _parent->data();
|
||||
if (lifetime) {
|
||||
base::take(lifetime)->destroy();
|
||||
}
|
||||
// Destroys this.
|
||||
ClearMediaAsExpired(item);
|
||||
}, *lifetime);
|
||||
|
||||
return false;
|
||||
});
|
||||
} else {
|
||||
setDocumentLinks(_data, realParent);
|
||||
}
|
||||
} else {
|
||||
setDocumentLinks(_data, realParent);
|
||||
}
|
||||
|
@ -918,7 +917,8 @@ void Document::draw(
|
|||
.highlight = highlightRequest ? &*highlightRequest : nullptr,
|
||||
});
|
||||
}
|
||||
if (_parent->data()->media() && _parent->data()->media()->ttlSeconds()) {
|
||||
if ((_parent->data()->media() && _parent->data()->media()->ttlSeconds())
|
||||
&& _openl) {
|
||||
const auto &fg = context.outbg
|
||||
? st::historyFileOutIconFg
|
||||
: st::historyFileInIconFg;
|
||||
|
@ -1739,4 +1739,23 @@ bool DrawThumbnailAsSongCover(
|
|||
return true;
|
||||
}
|
||||
|
||||
rpl::producer<> TTLVoiceStops(FullMsgId fullId) {
|
||||
return rpl::merge(
|
||||
::Media::Player::instance()->updatedNotifier(
|
||||
) | rpl::filter([=](::Media::Player::TrackState state) {
|
||||
using State = ::Media::Player::State;
|
||||
const auto badState = state.state == State::Stopped
|
||||
|| state.state == State::StoppedAtEnd
|
||||
|| state.state == State::StoppedAtError
|
||||
|| state.state == State::StoppedAtStart;
|
||||
return (state.id.contextId() != fullId) && !badState;
|
||||
}) | rpl::to_empty,
|
||||
::Media::Player::instance()->tracksFinished(
|
||||
) | rpl::filter([=](AudioMsgId::Type type) {
|
||||
return (type == AudioMsgId::Type::Voice);
|
||||
}) | rpl::to_empty,
|
||||
::Media::Player::instance()->stops(AudioMsgId::Type::Voice)
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
||||
|
|
|
@ -181,4 +181,6 @@ bool DrawThumbnailAsSongCover(
|
|||
const QRect &rect,
|
||||
bool selected = false);
|
||||
|
||||
rpl::producer<> TTLVoiceStops(FullMsgId fullId);
|
||||
|
||||
} // namespace HistoryView
|
||||
|
|
Loading…
Add table
Reference in a new issue