Added initial implementation of swipe action to dialogs.

This commit is contained in:
23rd 2025-03-16 12:11:49 +03:00
parent c29d78ac0d
commit e28d29f276
23 changed files with 362 additions and 20 deletions

View file

@ -688,6 +688,8 @@ PRIVATE
dialogs/dialogs_search_from_controllers.h
dialogs/dialogs_search_tags.cpp
dialogs/dialogs_search_tags.h
dialogs/dialogs_swipe_action.cpp
dialogs/dialogs_swipe_action.h
dialogs/dialogs_widget.cpp
dialogs/dialogs_widget.h
editor/color_picker.cpp

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1180,6 +1180,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_restart_now" = "Restart";
"lng_settings_restart_later" = "Later";
"lng_settings_swipe_mute" = "Mute";
"lng_settings_swipe_unmute" = "Unmute";
"lng_settings_swipe_pin" = "Pin";
"lng_settings_swipe_unpin" = "Unpin";
"lng_settings_swipe_read" = "Read";
"lng_settings_swipe_unread" = "Unread";
"lng_settings_swipe_archive" = "Archive";
"lng_settings_swipe_unarchive" = "Unarchive";
"lng_settings_swipe_delete" = "Delete";
"lng_settings_swipe_disabled" = "Change folder";
"lng_sessions_header" = "This device";
"lng_sessions_other_header" = "Active Devices";
"lng_sessions_other_desc" = "You can log in to Telegram from other mobile, tablet and desktop devices, using the same phone number. All your data will be instantly synchronized.";

View file

@ -49,5 +49,16 @@
<file alias="star_reaction_effect1.tgs">../../animations/star_reaction/effect1.tgs</file>
<file alias="star_reaction_effect2.tgs">../../animations/star_reaction/effect2.tgs</file>
<file alias="star_reaction_effect3.tgs">../../animations/star_reaction/effect3.tgs</file>
<file alias="swipe_archive.tgs">../../animations/swipe_action/archive.tgs</file>
<file alias="swipe_unarchive.tgs">../../animations/swipe_action/unarchive.tgs</file>
<file alias="swipe_delete.tgs">../../animations/swipe_action/delete.tgs</file>
<file alias="swipe_disabled.tgs">../../animations/swipe_action/disabled.tgs</file>
<file alias="swipe_mute.tgs">../../animations/swipe_action/mute.tgs</file>
<file alias="swipe_unmute.tgs">../../animations/swipe_action/unmute.tgs</file>
<file alias="swipe_pin.tgs">../../animations/swipe_action/pin.tgs</file>
<file alias="swipe_unpin.tgs">../../animations/swipe_action/unpin.tgs</file>
<file alias="swipe_read.tgs">../../animations/swipe_action/read.tgs</file>
<file alias="swipe_unread.tgs">../../animations/swipe_action/unread.tgs</file>
</qresource>
</RCC>

View file

@ -789,3 +789,5 @@ dialogsPopularAppsPadding: margins(10px, 8px, 10px, 12px);
dialogsPopularAppsAbout: FlatLabel(boxDividerLabel) {
minWidth: 128px;
}
dialogsSwipeActionSize: 20px;

View file

@ -54,6 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "base/options.h"
#include "lang/lang_keys.h"
#include "lottie/lottie_icon.h"
#include "mainwindow.h"
#include "mainwidget.h"
#include "storage/storage_account.h"
@ -808,6 +809,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
const auto ms = crl::now();
const auto childListShown = _childListShown.current();
auto context = Ui::PaintContext{
.swipeContext = _swipeContext,
.st = _st,
.topicJumpCache = _topicJumpCache.get(),
.folder = _openedFolder,
@ -4992,4 +4994,74 @@ rpl::producer<UserId> InnerWidget::openBotMainAppRequests() const {
return _openBotMainAppRequests.events();
}
void InnerWidget::setSwipeContextData(Ui::Controls::SwipeContextData data) {
_swipeContext.data = std::move(data);
if (_swipeContext.data.msgBareId) {
constexpr auto kStartAnimateThreshold = 0.32;
constexpr auto kResetAnimateThreshold = 0.24;
if (_swipeContext.data.ratio > kStartAnimateThreshold) {
if (_swipeContext.icon
&& !_swipeContext.icon->frameIndex()
&& !_swipeContext.icon->animating()) {
_swipeContext.icon->animate(
[=] { update(); },
0,
_swipeContext.icon->framesCount());
}
} else if (_swipeContext.data.ratio < kResetAnimateThreshold) {
if (_swipeContext.icon && _swipeContext.icon->frameIndex()) {
_swipeContext.icon->jumpTo(0, [=] { update(); });
}
}
update();
}
}
int64 InnerWidget::calcSwipeKey(int top) {
top -= dialogsOffset();
for (auto it = _shownList->begin(); it != _shownList->end(); ++it) {
const auto row = it->get();
const auto from = row->top();
const auto to = from + row->height();
if (top >= from && top < to) {
if (const auto peer = row->key().peer()) {
return peer->id.value;
}
return 0;
}
}
return 0;
}
void InnerWidget::prepareSwipeAction(
int64 key,
Dialogs::Ui::SwipeDialogAction action) {
if (key) {
auto name = (action == Dialogs::Ui::SwipeDialogAction::Mute)
? u"swipe_mute"_q
: (action == Dialogs::Ui::SwipeDialogAction::Pin)
? u"swipe_pin"_q
: (action == Dialogs::Ui::SwipeDialogAction::Read)
? u"swipe_read"_q
: (action == Dialogs::Ui::SwipeDialogAction::Archive)
? u"swipe_archive"_q
: (action == Dialogs::Ui::SwipeDialogAction::Delete)
? u"swipe_delete"_q
: u"swipe_disabled"_q;
_swipeLottieIcon = Lottie::MakeIcon({
.name = std::move(name),
.sizeOverride = Size(st::dialogsSwipeActionSize),
});
_swipeContext.icon = _swipeLottieIcon.get();
_swipeContext.action = action;
} else {
if (_swipeContext.icon) {
_swipeContext = {};
}
if (_swipeLottieIcon) {
_swipeLottieIcon.reset();
}
}
}
} // namespace Dialogs

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/object_ptr.h"
#include "base/timer.h"
#include "dialogs/dialogs_key.h"
#include "dialogs/ui/dialogs_swipe_context.h"
#include "data/data_messages.h"
#include "ui/dragging_scroll_manager.h"
#include "ui/effects/animations.h"
@ -25,6 +26,10 @@ namespace MTP {
class Error;
} // namespace MTP
namespace Lottie {
class Icon;
} // namespace Lottie
namespace Main {
class Session;
} // namespace Main
@ -34,6 +39,9 @@ class IconButton;
class PopupMenu;
class FlatLabel;
struct ScrollToRequest;
namespace Controls {
enum class SwipeDialogAction;
} // namespace Controls
} // namespace Ui
namespace Window {
@ -208,6 +216,10 @@ public:
[[nodiscard]] rpl::producer<UserId> openBotMainAppRequests() const;
void setSwipeContextData(Ui::Controls::SwipeContextData data);
[[nodiscard]] int64 calcSwipeKey(int top);
void prepareSwipeAction(int64 key, Dialogs::Ui::SwipeDialogAction);
protected:
void visibleTopBottomUpdated(
int visibleTop,
@ -611,6 +623,9 @@ private:
rpl::event_stream<> _refreshHashtagsRequests;
rpl::event_stream<UserId> _openBotMainAppRequests;
Dialogs::Ui::SwipeContext _swipeContext;
std::unique_ptr<Lottie::Icon> _swipeLottieIcon = nullptr;
RowDescriptor _chatPreviewRow;
bool _chatPreviewScheduled = false;
std::optional<QPoint> _chatPreviewTouchGlobal;

View file

@ -0,0 +1,29 @@
/*
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 "dialogs/dialogs_swipe_action.h"
#include "dialogs/ui/dialogs_swipe_context.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "history/history.h"
#include "main/main_session.h"
#include "menu/menu_mute.h"
namespace Dialogs {
void PerformSwipeDialogAction(
not_null<PeerData*> peer,
Ui::SwipeDialogAction action) {
if (action == Dialogs::Ui::SwipeDialogAction::Mute) {
const auto history = peer->owner().history(peer);
MuteMenu::ThreadDescriptor(history).updateMutePeriod(
std::numeric_limits<TimeId>::max());
}
}
} // namespace Dialogs

View 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 PeerData;
namespace Dialogs::Ui {
enum class SwipeDialogAction;
} // namespace Dialogs::Ui
namespace Dialogs {
void PerformSwipeDialogAction(
not_null<PeerData*> peer,
Ui::SwipeDialogAction action);
} // namespace Dialogs

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "dialogs/dialogs_widget.h"
#include "base/call_delayed.h"
#include "base/qt/qt_key_modifiers.h"
#include "base/options.h"
#include "dialogs/ui/chat_search_in.h"
@ -15,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/ui/dialogs_suggestions.h"
#include "dialogs/dialogs_inner_widget.h"
#include "dialogs/dialogs_search_from_controllers.h"
#include "dialogs/dialogs_swipe_action.h"
#include "dialogs/dialogs_key.h"
#include "history/history.h"
#include "history/history_item.h"
@ -688,32 +690,64 @@ void Widget::setupSwipeBack() {
Ui::Controls::SetupSwipeHandler(_inner, _scroll.data(), [=](
Ui::Controls::SwipeContextData data) {
if (data.translation != 0) {
if (!_swipeBackData.callback) {
_swipeBackData = Ui::Controls::SetupSwipeBack(
this,
[]() -> std::pair<QColor, QColor> {
return {
st::historyForwardChooseBg->c,
st::historyForwardChooseFg->c,
};
},
_swipeBackMirrored,
_swipeBackIconMirrored);
if (data.translation < 0) {
if (_inner) {
_inner->setSwipeContextData(std::move(data));
}
} else {
if (!_swipeBackData.callback) {
_swipeBackData = Ui::Controls::SetupSwipeBack(
this,
[]() -> std::pair<QColor, QColor> {
return {
st::historyForwardChooseBg->c,
st::historyForwardChooseFg->c,
};
},
_swipeBackMirrored,
_swipeBackIconMirrored);
}
_swipeBackData.callback(data);
}
_swipeBackData.callback(data);
return;
} else {
if (_swipeBackData.lifetime) {
_swipeBackData = {};
}
if (_inner) {
_inner->setSwipeContextData({});
_inner->update();
}
}
}, [=](int, Qt::LayoutDirection direction) {
}, [=](int top, Qt::LayoutDirection direction) {
_swipeBackIconMirrored = false;
_swipeBackMirrored = false;
if (_childListShown.current()) {
return Ui::Controls::SwipeHandlerFinishData();
}
const auto isRightToLeft = direction == Qt::RightToLeft;
if (!isRightToLeft && _inner) {
if (const auto key = _inner->calcSwipeKey(top)) {
const auto action = Dialogs::Ui::SwipeDialogAction::Mute;
_inner->prepareSwipeAction(key, action);
return Ui::Controls::SwipeHandlerFinishData{
.callback = [=, session = &session()] {
auto callback = [=, peerId = PeerId(key)] {
const auto peer = session->data().peer(peerId);
PerformSwipeDialogAction(peer, action);
};
base::call_delayed(
st::slideWrapDuration,
session,
std::move(callback));
},
.msgBareId = key,
.speedRatio = 1.,
.reachRatioDuration = crl::time(st::slideWrapDuration),
.provideReachOutRatio = true,
};
}
}
if (controller()->openedFolder().current()) {
if (!isRightToLeft) {
return Ui::Controls::SwipeHandlerFinishData();

View file

@ -28,12 +28,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_unread_things.h"
#include "history/view/history_view_item_preview.h"
#include "history/view/history_view_send_action.h"
#include "lang/lang_instance.h"
#include "lang/lang_keys.h"
#include "lottie/lottie_icon.h"
#include "main/main_session.h"
#include "storage/localstorage.h"
#include "support/support_helper.h"
#include "ui/empty_userpic.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/power_saving.h"
#include "ui/text/format_values.h"
#include "ui/text/text_options.h"
@ -66,6 +69,54 @@ const auto kPsaBadgePrefix = "cloud_lng_badge_psa_";
return !history->isForum();
}
[[nodiscard]] QString SwipeActionText(Dialogs::Ui::SwipeDialogAction type) {
return (type == Dialogs::Ui::SwipeDialogAction::Archive)
? tr::lng_settings_swipe_archive(tr::now)
: (type == Dialogs::Ui::SwipeDialogAction::Delete)
? tr::lng_settings_swipe_delete(tr::now)
: (type == Dialogs::Ui::SwipeDialogAction::Read)
? tr::lng_settings_swipe_read(tr::now)
: (type == Dialogs::Ui::SwipeDialogAction::Pin)
? tr::lng_settings_swipe_pin(tr::now)
: tr::lng_settings_swipe_mute(tr::now);
}
const style::font &SwipeActionFont(
Dialogs::Ui::SwipeDialogAction action,
int availableWidth) {
struct Entry final {
Dialogs::Ui::SwipeDialogAction action;
QString langId;
style::font font;
};
static auto Fonts = std::vector<Entry>();
for (auto &entry : Fonts) {
if (entry.action == action) {
if (entry.langId == Lang::GetInstance().id()) {
return entry.font;
}
}
}
constexpr auto kNormalFontSize = 13;
constexpr auto kMinFontSize = 5;
for (auto i = kNormalFontSize; i >= kMinFontSize; --i) {
auto font = style::font(
style::ConvertScale(i, style::Scale()),
st::semiboldFont->flags(),
st::semiboldFont->family());
if (font->width(SwipeActionText(action)) <= availableWidth
|| i == kMinFontSize) {
Fonts.emplace_back(Entry{
.action = action,
.langId = Lang::GetInstance().id(),
.font = std::move(font),
});
return Fonts.back().font;
}
}
Unexpected("SwipeActionFont: can't find font.");
}
void PaintRowTopRight(
QPainter &p,
const QString &text,
@ -344,11 +395,25 @@ void PaintRow(
draft = nullptr;
}
const auto history = entry->asHistory();
const auto thread = entry->asThread();
const auto sublist = entry->asSublist();
auto bg = context.active
? st::dialogsBgActive
: context.selected
? st::dialogsBgOver
: context.currentBg;
auto swipeTranslation = 0;
if (history
&& history->peer->id.value == context.swipeContext.data.msgBareId) {
if (context.swipeContext.data.translation != 0) {
swipeTranslation = context.swipeContext.data.translation * -2;
}
}
if (swipeTranslation) {
p.translate(-swipeTranslation, 0);
}
p.fillRect(geometry, bg);
if (!(flags & Flag::TopicJumpRipple)) {
auto ripple = context.active
@ -357,10 +422,6 @@ void PaintRow(
row->paintRipple(p, 0, 0, context.width, &ripple->c);
}
const auto history = entry->asHistory();
const auto thread = entry->asThread();
const auto sublist = entry->asSublist();
if (flags & Flag::SavedMessages) {
EmptyUserpic::PaintSavedMessages(
p,
@ -821,6 +882,54 @@ void PaintRow(
+ (tag->width() / style::DevicePixelRatio());
}
}
if (swipeTranslation) {
p.translate(swipeTranslation, 0);
const auto swipeActionRect = QRect(
geometry.x() + geometry.width() - swipeTranslation,
geometry.y(),
swipeTranslation,
geometry.height());
p.setClipRegion(swipeActionRect);
p.fillRect(swipeActionRect, st::attentionButtonFg);
if (context.swipeContext.data.reachRatio) {
p.setPen(Qt::NoPen);
p.setBrush(st::windowBgActive);
const auto r = swipeTranslation
* context.swipeContext.data.reachRatio;
const auto offset = st::dialogsSwipeActionSize
+ st::dialogsSwipeActionSize / 2.;
p.drawEllipse(QPointF(geometry.width() - offset, offset), r, r);
}
const auto iconOffset = (geometry.height()
- st::dialogsSwipeActionSize) / 2;
const auto topTranslation = iconOffset / 2.;
p.translate(0, -topTranslation);
if (context.swipeContext.icon) {
context.swipeContext.icon->paint(
p,
rect::right(geometry)
- iconOffset
- st::dialogsSwipeActionSize,
iconOffset,
st::premiumButtonFg->c);
}
{
p.setPen(st::premiumButtonFg);
p.setBrush(Qt::NoBrush);
const auto left = rect::right(geometry)
- iconOffset * 2
- st::dialogsSwipeActionSize;
const auto availableWidth = geometry.width() - left;
p.setFont(
SwipeActionFont(context.swipeContext.action, availableWidth));
p.drawText(
QRect(left, 0, availableWidth, geometry.height()),
SwipeActionText(context.swipeContext.action),
style::al_bottom);
}
p.translate(0, topTranslation);
p.setClipRegion(QRegion());
}
}
} // namespace

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "dialogs/ui/dialogs_swipe_context.h"
#include "ui/cached_round_corners.h"
namespace style {
@ -18,9 +19,6 @@ namespace st {
extern const style::DialogRow &defaultDialogRow;
} // namespace st
namespace Ui {
} // namespace Ui
namespace Data {
class Forum;
class Folder;
@ -57,6 +55,7 @@ struct TopicJumpCache {
struct PaintContext {
RightButton *rightButton = nullptr;
std::vector<QImage*> *chatsFilterTags = nullptr;
SwipeContext swipeContext;
not_null<const style::DialogRow*> st;
TopicJumpCache *topicJumpCache = nullptr;
Data::Folder *folder = nullptr;

View file

@ -0,0 +1,35 @@
/*
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/controls/swipe_handler_data.h"
namespace Lottie {
class Icon;
} // namespace Lottie
namespace Dialogs::Ui {
using namespace ::Ui;
enum class SwipeDialogAction {
Mute,
Pin,
Read,
Archive,
Delete,
Disabled,
};
struct SwipeContext {
::Ui::Controls::SwipeContextData data;
Lottie::Icon *icon = nullptr;
SwipeDialogAction action;
};
} // namespace Dialogs::Ui

View file

@ -103,6 +103,7 @@ PRIVATE
dialogs/ui/chat_search_in.h
dialogs/ui/dialogs_stories_list.cpp
dialogs/ui/dialogs_stories_list.h
dialogs/ui/dialogs_swipe_context.h
dialogs/ui/top_peers_strip.cpp
dialogs/ui/top_peers_strip.h