Implement jump-to-start / end by long press in CalendarBox.

This commit is contained in:
John Preston 2021-11-15 13:09:44 +04:00
parent 15dc6064ef
commit 4414369fc8
4 changed files with 165 additions and 75 deletions

View file

@ -78,7 +78,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_month_day_year" = "{month} {day}, {year}";
"lng_month_year" = "{month} {year}";
"lng_calendar_beginning" = "Beginning";
"lng_calendar_select_days" = "Select days";
"lng_calendar_start_tip" = "Press and hold to jump to the start.";
"lng_calendar_end_tip" = "Press and hold to jump to the end.";
"lng_box_ok" = "OK";
"lng_box_done" = "Done";

View file

@ -19,6 +19,8 @@ namespace {
constexpr auto kDaysInWeek = 7;
constexpr auto kMaxDaysForScroll = kDaysInWeek * 1000;
constexpr auto kTooltipDelay = crl::time(1000);
constexpr auto kJumpDelay = 2 * crl::time(1000);
} // namespace
@ -26,9 +28,9 @@ class CalendarBox::Context {
public:
Context(QDate month, QDate highlighted);
void setBeginningButton(bool enabled);
[[nodiscard]] bool hasBeginningButton() const {
return _beginningButton;
void setHasSelection(bool has);
[[nodiscard]] bool hasSelection() const {
return _hasSelection;
}
void setMinDate(QDate date);
@ -63,9 +65,6 @@ public:
[[nodiscard]] bool isEnabled(int index) const {
return (index >= _minDayIndex) && (index <= _maxDayIndex);
}
[[nodiscard]] bool atBeginning() const {
return _highlighted == _min;
}
[[nodiscard]] rpl::producer<QDate> monthValue() const {
return _month.value();
@ -80,7 +79,7 @@ private:
static int DaysShiftForMonth(QDate month, QDate min);
static int RowsCountForMonth(QDate month, QDate min, QDate max);
bool _beginningButton = false;
bool _hasSelection = false;
rpl::variable<QDate> _month;
QDate _min, _max;
@ -102,8 +101,8 @@ CalendarBox::Context::Context(QDate month, QDate highlighted)
showMonth(month);
}
void CalendarBox::Context::setBeginningButton(bool enabled) {
_beginningButton = enabled;
void CalendarBox::Context::setHasSelection(bool has) {
_hasSelection = has;
}
void CalendarBox::Context::setMinDate(QDate date) {
@ -245,7 +244,6 @@ public:
[[nodiscard]] int countMaxHeight() const;
void setDateChosenCallback(Fn<void(QDate)> callback);
void selectBeginning();
~Inner();
@ -479,10 +477,6 @@ void CalendarBox::Inner::setDateChosenCallback(Fn<void(QDate)> callback) {
_dateChosenCallback = std::move(callback);
}
void CalendarBox::Inner::selectBeginning() {
_dateChosenCallback(_context->dateFromIndex(_context->minDayIndex()));
}
CalendarBox::Inner::~Inner() = default;
class CalendarBox::Title final : public RpWidget {
@ -575,9 +569,10 @@ CalendarBox::CalendarBox(QWidget*, CalendarBoxArgs &&args)
, _previous(this, st::calendarPrevious)
, _next(this, st::calendarNext)
, _callback(std::move(args.callback.value()))
, _finalize(std::move(args.finalize)) {
, _finalize(std::move(args.finalize))
, _jumpTimer([=] { jump(_jumpButton); }) {
_title->setAttribute(Qt::WA_TransparentForMouseEvents);
_context->setBeginningButton(args.hasBeginningButton);
_context->setHasSelection(args.allowsSelection);
_context->setMinDate(args.minDate);
_context->setMaxDate(args.maxDate);
@ -587,6 +582,60 @@ CalendarBox::CalendarBox(QWidget*, CalendarBoxArgs &&args)
}) | rpl::start_with_next([=] {
processScroll();
}, lifetime());
const auto setupJumps = [&](
not_null<IconButton*> button,
not_null<bool*> enabled) {
button->events(
) | rpl::filter([=] {
return *enabled;
}) | rpl::start_with_next([=](not_null<QEvent*> e) {
const auto type = e->type();
if (type == QEvent::MouseMove
&& !(static_cast<QMouseEvent*>(e.get())->buttons()
& Qt::LeftButton)) {
showJumpTooltip(button);
} else if (type == QEvent::Leave) {
Ui::Tooltip::Hide();
} else if (type == QEvent::MouseButtonPress
&& (static_cast<QMouseEvent*>(e.get())->button()
== Qt::LeftButton)) {
jumpAfterDelay(button);
} else if (type == QEvent::MouseButtonRelease
&& (static_cast<QMouseEvent*>(e.get())->button()
== Qt::LeftButton)) {
_jumpTimer.cancel();
}
}, lifetime());
};
setupJumps(_previous.data(), &_previousEnabled);
setupJumps(_next.data(), &_nextEnabled);
}
void CalendarBox::showJumpTooltip(not_null<IconButton*> button) {
_tooltipButton = button;
Ui::Tooltip::Show(kTooltipDelay, this);
}
void CalendarBox::jumpAfterDelay(not_null<IconButton*> button) {
_jumpButton = button;
_jumpTimer.callOnce(kJumpDelay);
Ui::Tooltip::Hide();
}
void CalendarBox::jump(QPointer<IconButton> button) {
const auto jumpToIndex = [&](int index) {
_watchScroll = false;
_context->showMonth(_context->dateFromIndex(index));
setExactScroll();
};
if (_jumpButton == _previous.data() && _previousEnabled) {
jumpToIndex(_context->minDayIndex());
} else if (_jumpButton == _next.data() && _nextEnabled) {
jumpToIndex(_context->maxDayIndex());
}
_jumpButton = nullptr;
_jumpTimer.cancel();
}
void CalendarBox::prepare() {
@ -606,9 +655,8 @@ void CalendarBox::prepare() {
if (_finalize) {
_finalize(this);
}
if (!_context->atBeginning() && _context->hasBeginningButton()) {
addLeftButton(tr::lng_calendar_beginning(), [=] {
_inner->selectBeginning();
if (_context->hasSelection()) {
addLeftButton(tr::lng_calendar_select_days(), [=] {
});
}
}
@ -666,16 +714,40 @@ void CalendarBox::processScroll() {
_watchScroll = true;
}
QString CalendarBox::tooltipText() const {
if (_tooltipButton == _previous.data()) {
return tr::lng_calendar_start_tip(tr::now);
} else if (_tooltipButton == _next.data()) {
return tr::lng_calendar_end_tip(tr::now);
}
return QString();
}
QPoint CalendarBox::tooltipPos() const {
return QCursor::pos();
}
bool CalendarBox::tooltipWindowActive() const {
return window()->isActiveWindow();
}
void CalendarBox::monthChanged(QDate month) {
setDimensions(_st.width, st::calendarTitleHeight + _st.daysHeight + _inner->countMaxHeight());
auto previousEnabled = isPreviousEnabled();
_previous->setIconOverride(previousEnabled ? nullptr : &st::calendarPreviousDisabled);
_previous->setRippleColorOverride(previousEnabled ? nullptr : &st::boxBg);
_previous->setCursor(previousEnabled ? style::cur_pointer : style::cur_default);
auto nextEnabled = isNextEnabled();
_next->setIconOverride(nextEnabled ? nullptr : &st::calendarNextDisabled);
_next->setRippleColorOverride(nextEnabled ? nullptr : &st::boxBg);
_next->setCursor(nextEnabled ? style::cur_pointer : style::cur_default);
_previousEnabled = isPreviousEnabled();
_previous->setIconOverride(_previousEnabled ? nullptr : &st::calendarPreviousDisabled);
_previous->setRippleColorOverride(_previousEnabled ? nullptr : &st::boxBg);
_previous->setCursor(_previousEnabled ? style::cur_pointer : style::cur_default);
if (!_previousEnabled) {
_previous->clearState();
}
_nextEnabled = isNextEnabled();
_next->setIconOverride(_nextEnabled ? nullptr : &st::calendarNextDisabled);
_next->setRippleColorOverride(_nextEnabled ? nullptr : &st::boxBg);
_next->setCursor(_nextEnabled ? style::cur_pointer : style::cur_default);
if (!_nextEnabled) {
_next->clearState();
}
}
void CalendarBox::resizeEvent(QResizeEvent *e) {
@ -700,7 +772,9 @@ void CalendarBox::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape) {
e->ignore();
} else if (e->key() == Qt::Key_Home) {
_inner->selectBeginning();
jump(_previous.data());
} else if (e->key() == Qt::Key_End) {
jump(_next.data());
} else if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Up) {
goPreviousMonth();
} else if (e->key() == Qt::Key_Right || e->key() == Qt::Key_Down) {

View file

@ -8,7 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "ui/layers/box_content.h"
#include "ui/widgets/tooltip.h"
#include "base/required.h"
#include "base/timer.h"
namespace style {
struct CalendarSizes;
@ -35,10 +37,10 @@ struct CalendarBoxArgs {
const style::CalendarSizes &st = st::defaultCalendarSizes;
QDate minDate;
QDate maxDate;
bool hasBeginningButton = false;
bool allowsSelection = false;
};
class CalendarBox final : public BoxContent {
class CalendarBox final : public BoxContent, private AbstractTooltipShower {
public:
CalendarBox(QWidget*, CalendarBoxArgs &&args);
~CalendarBox();
@ -60,6 +62,14 @@ private:
void setExactScroll();
void processScroll();
void showJumpTooltip(not_null<IconButton*> button);
void jumpAfterDelay(not_null<IconButton*> button);
void jump(QPointer<IconButton> button);
QString tooltipText() const override;
QPoint tooltipPos() const override;
bool tooltipWindowActive() const override;
const style::CalendarSizes &_st;
class Context;
@ -74,11 +84,17 @@ private:
object_ptr<Title> _title;
object_ptr<IconButton> _previous;
object_ptr<IconButton> _next;
bool _previousEnabled = false;
bool _nextEnabled = false;
Fn<void(QDate date)> _callback;
FnMut<void(not_null<CalendarBox*>)> _finalize;
bool _watchScroll = false;
QPointer<IconButton> _tooltipButton;
QPointer<IconButton> _jumpButton;
base::Timer _jumpTimer;
};
} // namespace Ui

View file

@ -1147,63 +1147,61 @@ void SessionController::startOrJoinGroupCall(
}
void SessionController::showJumpToDate(Dialogs::Key chat, QDate requestedDate) {
const auto history = chat.history();
if (!history) {
return;
}
const auto currentPeerDate = [&] {
if (const auto history = chat.history()) {
if (history->scrollTopItem) {
return history->scrollTopItem->dateTime().date();
} else if (history->loadedAtTop()
&& !history->isEmpty()
&& history->peer->migrateFrom()) {
if (const auto migrated = history->owner().historyLoaded(history->peer->migrateFrom())) {
if (migrated->scrollTopItem) {
// We're up in the migrated history.
// So current date is the date of first message here.
return history->blocks.front()->messages.front()->dateTime().date();
}
if (history->scrollTopItem) {
return history->scrollTopItem->dateTime().date();
} else if (history->loadedAtTop()
&& !history->isEmpty()
&& history->peer->migrateFrom()) {
if (const auto migrated = history->owner().historyLoaded(history->peer->migrateFrom())) {
if (migrated->scrollTopItem) {
// We're up in the migrated history.
// So current date is the date of first message here.
return history->blocks.front()->messages.front()->dateTime().date();
}
} else if (const auto item = history->lastMessage()) {
return base::unixtime::parse(item->date()).date();
}
} else if (const auto item = history->lastMessage()) {
return base::unixtime::parse(item->date()).date();
}
return QDate();
}();
const auto maxPeerDate = [](Dialogs::Key chat) {
if (auto history = chat.history()) {
if (const auto channel = history->peer->migrateTo()) {
history = channel->owner().historyLoaded(channel);
}
if (const auto item = history ? history->lastMessage() : nullptr) {
return base::unixtime::parse(item->date()).date();
}
const auto maxPeerDate = [&] {
const auto check = history->peer->migrateTo()
? history->owner().historyLoaded(history->peer->migrateTo())
: history;
if (const auto item = check ? check->lastMessage() : nullptr) {
return base::unixtime::parse(item->date()).date();
}
return QDate::currentDate();
};
const auto minPeerDate = [](Dialogs::Key chat) {
return QDate();
}();
const auto minPeerDate = [&] {
const auto startDate = [] {
// Telegram was launched in August 2013 :)
return QDate(2013, 8, 1);
};
if (const auto history = chat.history()) {
if (const auto chat = history->peer->migrateFrom()) {
if (const auto history = chat->owner().historyLoaded(chat)) {
if (history->loadedAtTop()) {
if (!history->isEmpty()) {
return history->blocks.front()->messages.front()->dateTime().date();
}
} else {
return startDate();
if (const auto chat = history->peer->migrateFrom()) {
if (const auto history = chat->owner().historyLoaded(chat)) {
if (history->loadedAtTop()) {
if (!history->isEmpty()) {
return history->blocks.front()->messages.front()->dateTime().date();
}
} else {
return startDate();
}
}
if (history->loadedAtTop()) {
if (!history->isEmpty()) {
return history->blocks.front()->messages.front()->dateTime().date();
}
return QDate::currentDate();
}
}
if (history->loadedAtTop()) {
if (!history->isEmpty()) {
return history->blocks.front()->messages.front()->dateTime().date();
}
return QDate::currentDate();
}
return startDate();
};
}();
const auto highlighted = !requestedDate.isNull()
? requestedDate
: !currentPeerDate.isNull()
@ -1215,9 +1213,9 @@ void SessionController::showJumpToDate(Dialogs::Key chat, QDate requestedDate) {
.callback = [=](const QDate &date) {
session().api().jumpToDate(chat, date);
},
.minDate = minPeerDate(chat),
.maxDate = maxPeerDate(chat),
.hasBeginningButton = true,
.minDate = minPeerDate,
.maxDate = maxPeerDate,
.allowsSelection = history->peer->isUser(),
}));
}