From 66e7f05df19a6374d33fa4563a0d973a9ebd4c0c Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 6 Apr 2021 13:59:14 +0400 Subject: [PATCH] Improve scheduled voice chat top bar design. --- Telegram/Resources/langs/lang.strings | 5 + Telegram/SourceFiles/calls/calls.style | 5 + .../calls/calls_choose_join_as.cpp | 6 +- .../SourceFiles/calls/calls_group_call.cpp | 4 +- .../SourceFiles/calls/calls_group_menu.cpp | 48 +++--- .../SourceFiles/calls/calls_group_panel.cpp | 23 ++- .../SourceFiles/calls/calls_group_panel.h | 3 + Telegram/SourceFiles/calls/calls_top_bar.cpp | 23 ++- Telegram/SourceFiles/history/history.cpp | 11 +- .../SourceFiles/ui/chat/group_call_bar.cpp | 155 ++++++++++++++++-- Telegram/SourceFiles/ui/chat/group_call_bar.h | 30 ++++ .../window/window_session_controller.cpp | 2 + 12 files changed, 260 insertions(+), 55 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b34f39161a..ddcf605b97 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1992,6 +1992,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_leave" = "Leave"; "lng_group_call_leave_title" = "Leave voice chat"; "lng_group_call_leave_sure" = "Are you sure you want to leave this voice chat?"; +"lng_group_call_close" = "Close"; +"lng_group_call_close_sure" = "Voice chat is scheduled. You can cancel it or just close this panel."; +"lng_group_call_also_cancel" = "Cancel voice chat"; "lng_group_call_leave_to_other_sure" = "Do you want to leave your active voice chat and join a voice chat in this group?"; "lng_group_call_create_sure" = "Do you really want to start a voice chat in this group?"; "lng_group_call_create_sure_channel" = "Are you sure you want to start a voice chat in this channel as your personal account?"; @@ -2022,6 +2025,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_copy_speaker_link" = "Copy Speaker Link"; "lng_group_call_copy_listener_link" = "Copy Listener Link"; "lng_group_call_end" = "End Voice Chat"; +"lng_group_call_cancel" = "Cancel Voice Chat"; "lng_group_call_join" = "Join"; "lng_group_call_join_confirm" = "Do you want to join the voice chat {chat}?"; "lng_group_call_invite_done_user" = "You invited {user} to the voice chat."; @@ -2069,6 +2073,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_starts_tomorrow" = "tomorrow at {time}"; "lng_group_call_starts_date" = "{date} at {time}"; "lng_group_call_starts_in" = "Starts in"; +"lng_group_call_start_now" = "Start Now"; "lng_group_call_set_reminder" = "Set Reminder"; "lng_group_call_cancel_reminder" = "Cancel Reminder"; "lng_group_call_join_as_personal" = "personal account"; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 4c07ab9248..f768fb6f3f 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -720,6 +720,11 @@ groupCallTopBarJoin: RoundButton(defaultActiveButton) { height: 26px; textTop: 4px; } +groupCallTopBarOpen: RoundButton(groupCallTopBarJoin) { + ripple: RippleAnimation(defaultRippleAnimation) { + color: shadowFg; + } +} groupCallBox: Box(defaultBox) { button: RoundButton(defaultBoxButton) { textFg: groupCallActiveFg; diff --git a/Telegram/SourceFiles/calls/calls_choose_join_as.cpp b/Telegram/SourceFiles/calls/calls_choose_join_as.cpp index fc57655151..9f967d563c 100644 --- a/Telegram/SourceFiles/calls/calls_choose_join_as.cpp +++ b/Telegram/SourceFiles/calls/calls_choose_join_as.cpp @@ -152,17 +152,17 @@ void ScheduleGroupCallBox( const auto now = base::unixtime::now(); const auto duration = (date - now); if (duration >= 24 * 60 * 60) { - return tr::lng_signin_reset_days( + return tr::lng_group_call_duration_days( tr::now, lt_count, duration / (24 * 60 * 60)); } else if (duration >= 60 * 60) { - return tr::lng_signin_reset_hours( + return tr::lng_group_call_duration_hours( tr::now, lt_count, duration / (60 * 60)); } - return tr::lng_signin_reset_minutes( + return tr::lng_group_call_duration_minutes( tr::now, lt_count, std::max(duration / 60, 1)); diff --git a/Telegram/SourceFiles/calls/calls_group_call.cpp b/Telegram/SourceFiles/calls/calls_group_call.cpp index d7dbf8eff3..361909e139 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/calls_group_call.cpp @@ -234,9 +234,9 @@ GroupCall::GroupCall( return not_null{ real }; }) | rpl::take( 1 - ) | rpl::start_with_next([=](not_null call) { + ) | rpl::start_with_next([=](not_null real) { subscribeToReal(real); - _realChanges.fire_copy(call); + _realChanges.fire_copy(real); }, _lifetime); } if (_id) { diff --git a/Telegram/SourceFiles/calls/calls_group_menu.cpp b/Telegram/SourceFiles/calls/calls_group_menu.cpp index 43c6205cea..0879bcfc52 100644 --- a/Telegram/SourceFiles/calls/calls_group_menu.cpp +++ b/Telegram/SourceFiles/calls/calls_group_menu.cpp @@ -514,15 +514,6 @@ base::unique_qptr MakeRecordingAction( std::move(callback)); } -base::unique_qptr MakeFinishAction( - not_null menu, - Fn callback) { - return MakeAttentionAction( - menu, - tr::lng_group_call_end(tr::now), - std::move(callback)); -} - } // namespace void LeaveBox( @@ -530,16 +521,25 @@ void LeaveBox( not_null call, bool discardChecked, BoxContext context) { - box->setTitle(tr::lng_group_call_leave_title()); + const auto scheduled = (call->scheduleDate() != 0); + if (!scheduled) { + box->setTitle(tr::lng_group_call_leave_title()); + } const auto inCall = (context == BoxContext::GroupCallPanel); - box->addRow(object_ptr( - box.get(), - tr::lng_group_call_leave_sure(), - (inCall ? st::groupCallBoxLabel : st::boxLabel))); + box->addRow( + object_ptr( + box.get(), + (scheduled + ? tr::lng_group_call_close_sure() + : tr::lng_group_call_leave_sure()), + (inCall ? st::groupCallBoxLabel : st::boxLabel)), + scheduled ? st::boxPadding : st::boxRowPadding); const auto discard = call->peer()->canManageGroupCall() ? box->addRow(object_ptr( box.get(), - tr::lng_group_call_end(), + (scheduled + ? tr::lng_group_call_also_cancel() + : tr::lng_group_call_also_end()), discardChecked, (inCall ? st::groupCallCheckbox : st::defaultBoxCheckbox), (inCall ? st::groupCallCheck : st::defaultCheck)), @@ -550,7 +550,10 @@ void LeaveBox( st::boxRowPadding.bottom())) : nullptr; const auto weak = base::make_weak(call.get()); - box->addButton(tr::lng_group_call_leave(), [=] { + auto label = scheduled + ? tr::lng_group_call_close() + : tr::lng_group_call_leave(); + box->addButton(std::move(label), [=] { const auto discardCall = (discard && discard->checked()); box->closeBox(); @@ -603,7 +606,8 @@ void FillMenu( const auto addEditJoinAs = call->showChooseJoinAs(); const auto addEditTitle = peer->canManageGroupCall(); - const auto addEditRecording = peer->canManageGroupCall(); + const auto addEditRecording = peer->canManageGroupCall() + && !real->scheduleDate(); if (addEditJoinAs) { menu->addAction(MakeJoinAsAction( menu->menu(), @@ -660,7 +664,7 @@ void FillMenu( showBox(Box(SettingsBox, strong)); } }); - menu->addAction(MakeFinishAction(menu->menu(), [=] { + const auto finish = [=] { if (const auto strong = weak.get()) { showBox(Box( LeaveBox, @@ -668,7 +672,13 @@ void FillMenu( true, BoxContext::GroupCallPanel)); } - })); + }; + menu->addAction(MakeAttentionAction( + menu->menu(), + (real->scheduleDate() + ? tr::lng_group_call_cancel(tr::now) + : tr::lng_group_call_end(tr::now)), + finish)); } base::unique_qptr MakeAttentionAction( diff --git a/Telegram/SourceFiles/calls/calls_group_panel.cpp b/Telegram/SourceFiles/calls/calls_group_panel.cpp index c4f61d3523..cd86348a1d 100644 --- a/Telegram/SourceFiles/calls/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_group_panel.cpp @@ -266,7 +266,7 @@ Panel::Panel(not_null call) Core::App().appDeactivatedValue(), Ui::CallMuteButtonState{ .text = (_call->scheduleDate() - ? "Start Now" // #TODO voice chats + ? tr::lng_group_call_start_now(tr::now) : tr::lng_group_call_connecting(tr::now)), .type = (_call->scheduleDate() ? Ui::CallMuteButtonType::ScheduledCanStart @@ -451,7 +451,20 @@ void Panel::initControls() { }); _settings->setText(tr::lng_group_call_settings()); - _hangup->setText(tr::lng_group_call_leave()); + const auto scheduled = (_call->scheduleDate() != 0); + _hangup->setText(scheduled + ? tr::lng_group_call_close() + : tr::lng_group_call_leave()); + if (scheduled) { + _call->real( + ) | rpl::map([=](not_null real) { + return real->scheduleDateValue(); + }) | rpl::flatten_latest() | rpl::filter([](TimeId date) { + return (date == 0); + }) | rpl::take(1) | rpl::start_with_next([=] { + _hangup->setText(tr::lng_group_call_leave()); + }, _callLifetime); + } _call->stateValue( ) | rpl::filter([](State state) { @@ -497,10 +510,10 @@ void Panel::setupRealMuteButtonState(not_null real) { _mute->setState(Ui::CallMuteButtonState{ .text = (scheduleDate ? (canManage - ? "Start Now" // #TODO voice chats + ? tr::lng_group_call_start_now(tr::now) : scheduleStartSubscribed - ? "Cancel Reminder" - : "Set Reminder") + ? tr::lng_group_call_cancel_reminder(tr::now) + : tr::lng_group_call_set_reminder(tr::now)) : state == GroupCall::InstanceState::Disconnected ? tr::lng_group_call_connecting(tr::now) : mute == MuteState::ForceMuted diff --git a/Telegram/SourceFiles/calls/calls_group_panel.h b/Telegram/SourceFiles/calls/calls_group_panel.h index 493245f7b2..3f8b374783 100644 --- a/Telegram/SourceFiles/calls/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/calls_group_panel.h @@ -122,6 +122,9 @@ private: object_ptr _menu = { nullptr }; object_ptr _joinAsToggle = { nullptr }; object_ptr _members = { nullptr }; + object_ptr _startsIn = { nullptr }; + object_ptr _countdown = { nullptr }; + object_ptr _startsWhen = { nullptr }; ChooseJoinAsProcess _joinAsProcess; object_ptr _settings; diff --git a/Telegram/SourceFiles/calls/calls_top_bar.cpp b/Telegram/SourceFiles/calls/calls_top_bar.cpp index 982b834a9f..f598bb20b3 100644 --- a/Telegram/SourceFiles/calls/calls_top_bar.cpp +++ b/Telegram/SourceFiles/calls/calls_top_bar.cpp @@ -57,8 +57,13 @@ constexpr auto kHideBlobsDuration = crl::time(500); constexpr auto kBlobLevelDuration = crl::time(250); constexpr auto kBlobUpdateInterval = crl::time(100); -auto BarStateFromMuteState(MuteState state, GroupCall::InstanceState instanceState) { - return (instanceState == GroupCall::InstanceState::Disconnected) +auto BarStateFromMuteState( + MuteState state, + GroupCall::InstanceState instanceState, + TimeId scheduledDate) { + return scheduledDate + ? BarState::ForceMuted + : (instanceState == GroupCall::InstanceState::Disconnected) ? BarState::Connecting : (state == MuteState::ForceMuted || state == MuteState::RaisedHand) ? BarState::ForceMuted @@ -293,19 +298,27 @@ void TopBar::initControls() { _call ? mapToState(_call->muted()) : _groupCall->muted(), - GroupCall::InstanceState::Connected)); + GroupCall::InstanceState::Connected, + _call ? TimeId(0) : _groupCall->scheduleDate())); using namespace rpl::mappers; auto muted = _call ? rpl::combine( _call->mutedValue() | rpl::map(mapToState), - rpl::single(GroupCall::InstanceState::Connected) + rpl::single(GroupCall::InstanceState::Connected), + rpl::single(TimeId(0)) ) | rpl::type_erased() : rpl::combine( (_groupCall->mutedValue() | MapPushToTalkToActive() | rpl::distinct_until_changed() | rpl::type_erased()), - _groupCall->instanceStateValue() + _groupCall->instanceStateValue(), + rpl::single( + _groupCall->scheduleDate() + ) | rpl::then(_groupCall->real( + ) | rpl::map([](not_null call) { + return call->scheduleDateValue(); + }) | rpl::flatten_latest()) ) | rpl::filter(_2 != GroupCall::InstanceState::TransitionToRtc); std::move( muted diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 4415d1af35..2724ce769f 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1025,12 +1025,15 @@ void History::applyServiceChanges( } } break; - case mtpc_messageActionGroupCall: { - const auto &d = action.c_messageActionGroupCall(); + case mtpc_messageActionGroupCall: + case mtpc_messageActionGroupCallScheduled: { + const auto &call = (action.type() == mtpc_messageActionGroupCall) + ? action.c_messageActionGroupCall().vcall() + : action.c_messageActionGroupCallScheduled().vcall(); if (const auto channel = peer->asChannel()) { - channel->setGroupCall(d.vcall()); + channel->setGroupCall(call); } else if (const auto chat = peer->asChat()) { - chat->setGroupCall(d.vcall()); + chat->setGroupCall(call); } } break; } diff --git a/Telegram/SourceFiles/ui/chat/group_call_bar.cpp b/Telegram/SourceFiles/ui/chat/group_call_bar.cpp index b7a3150526..f405a06b59 100644 --- a/Telegram/SourceFiles/ui/chat/group_call_bar.cpp +++ b/Telegram/SourceFiles/ui/chat/group_call_bar.cpp @@ -21,16 +21,84 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { +GroupCallScheduledLeft::GroupCallScheduledLeft(TimeId date) +: _date(date) +, _datePrecise(computePreciseDate()) +, _timer([=] { update(); }) { + update(); + base::unixtime::updates( + ) | rpl::start_with_next([=] { + restart(); + }, _lifetime); +} + +crl::time GroupCallScheduledLeft::computePreciseDate() const { + return crl::now() + (_date - base::unixtime::now()) * crl::time(1000); +} + +void GroupCallScheduledLeft::setDate(TimeId date) { + if (_date == date) { + return; + } + _date = date; + restart(); +} + +void GroupCallScheduledLeft::restart() { + _datePrecise = computePreciseDate(); + _timer.cancel(); + update(); +} + +rpl::producer GroupCallScheduledLeft::text() const { + return _text.value(); +} + +void GroupCallScheduledLeft::update() { + const auto now = crl::now(); + const auto duration = (_datePrecise - now); + const auto left = crl::time(std::round(std::abs(duration) / 1000.)); + constexpr auto kDay = 24 * 60 * 60; + if (left >= kDay) { + const auto days = ((left / kDay) + 1); + _text = tr::lng_group_call_duration_days( + tr::now, + lt_count, + (duration < 0) ? (-days) : days); + } else { + const auto hours = left / (60 * 60); + const auto minutes = (left % (60 * 60)) / 60; + const auto seconds = (left % 60); + if (hours > 0) { + _text = (duration < 0 ? u"\x2212%1:%2:%3"_q : u"%1:%2:%3"_q) + .arg(hours, 2, 10, QChar('0')) + .arg(minutes, 2, 10, QChar('0')) + .arg(seconds, 2, 10, QChar('0')); + } else { + _text = (duration < 0 && left > 0 ? u"\x2212%1:%2"_q : u"%1:%2"_q) + .arg(minutes, 2, 10, QChar('0')) + .arg(seconds, 2, 10, QChar('0')); + } + } + if (left >= kDay) { + _timer.callOnce((left % kDay) * crl::time(1000)); + } else { + const auto fraction = (std::abs(duration) + 500) % 1000; + if (fraction < 400 || fraction > 600) { + const auto next = std::abs(duration) % 1000; + _timer.callOnce((duration < 0) ? (1000 - next) : next); + } else if (!_timer.isActive()) { + _timer.callEach(1000); + } + } +} + GroupCallBar::GroupCallBar( not_null parent, rpl::producer content, rpl::producer &&hideBlobs) : _wrap(parent, object_ptr(parent)) , _inner(_wrap.entity()) -, _join(std::make_unique( - _inner.get(), - tr::lng_group_call_join(), - st::groupCallTopBarJoin)) , _shadow(std::make_unique(_wrap.parentWidget())) , _userpics(std::make_unique( st::historyGroupCallUserpics, @@ -55,6 +123,7 @@ GroupCallBar::GroupCallBar( _content = content; _userpics->update(_content.users, !_wrap.isHidden()); _inner->update(); + refreshScheduledProcess(); }, lifetime()); std::move( @@ -76,6 +145,54 @@ GroupCallBar::GroupCallBar( GroupCallBar::~GroupCallBar() = default; +void GroupCallBar::refreshOpenBrush() { + Expects(_open != nullptr); + + const auto width = _open->width(); + if (_openBrushForWidth == width) { + return; + } + auto gradient = QLinearGradient(QPoint(width, 0), QPoint(-width, 0)); + gradient.setStops(QGradientStops{ + { 0.0, st::groupCallForceMutedBar1->c }, + { .35, st::groupCallForceMutedBar2->c }, + { 1.0, st::groupCallForceMutedBar3->c } + }); + _openBrushOverride = QBrush(std::move(gradient)); + _openBrushForWidth = width; + _open->setBrushOverride(_openBrushOverride); +} + +void GroupCallBar::refreshScheduledProcess() { + const auto date = _content.scheduleDate; + if (!date) { + if (_scheduledProcess) { + _scheduledProcess = nullptr; + _open = nullptr; + _join = std::make_unique( + _inner.get(), + tr::lng_group_call_join(), + st::groupCallTopBarJoin); + setupRightButton(_join.get()); + } + return; + } else if (!_scheduledProcess) { + _scheduledProcess = std::make_unique(date); + _join = nullptr; + _open = std::make_unique( + _inner.get(), + _scheduledProcess->text(), + st::groupCallTopBarOpen); + setupRightButton(_open.get()); + _open->widthValue( + ) | rpl::start_with_next([=] { + refreshOpenBrush(); + }, _open->lifetime()); + } else { + _scheduledProcess->setDate(date); + } +} + void GroupCallBar::setupInner() { _inner->resize(0, st::historyReplyHeight); _inner->paintRequest( @@ -102,17 +219,6 @@ void GroupCallBar::setupInner() { return rpl::empty_value(); }) | rpl::start_to_stream(_barClicks, _inner->lifetime()); - rpl::combine( - _inner->widthValue(), - _join->widthValue() - ) | rpl::start_with_next([=](int outerWidth, int) { - // Skip shadow of the bar above. - const auto top = (st::historyReplyHeight - - st::lineWidth - - _join->height()) / 2 + st::lineWidth; - _join->moveToRight(top, top, outerWidth); - }, _join->lifetime()); - _wrap.geometryValue( ) | rpl::start_with_next([=](QRect rect) { updateShadowGeometry(rect); @@ -120,6 +226,21 @@ void GroupCallBar::setupInner() { }, _inner->lifetime()); } +void GroupCallBar::setupRightButton(not_null button) { + rpl::combine( + _inner->widthValue(), + button->widthValue() + ) | rpl::start_with_next([=](int outerWidth, int) { + // Skip shadow of the bar above. + const auto top = (st::historyReplyHeight + - st::lineWidth + - button->height()) / 2 + st::lineWidth; + button->moveToRight(top, top, outerWidth); + }, button->lifetime()); + + button->clicks() | rpl::start_to_stream(_joinClicks, button->lifetime()); +} + void GroupCallBar::paint(Painter &p) { p.fillRect(_inner->rect(), st::historyComposeAreaBg); @@ -131,7 +252,7 @@ void GroupCallBar::paint(Painter &p) { p.setPen(st::defaultMessageBar.textFg); p.setFont(font); - const auto available = _join->x() - left; + const auto available = (_join ? _join->x() : _open->x()) - left; const auto titleWidth = font->width(_content.title); p.drawTextLeft( left, @@ -279,7 +400,7 @@ rpl::producer<> GroupCallBar::barClicks() const { } rpl::producer<> GroupCallBar::joinClicks() const { - return _join->clicks() | rpl::to_empty; + return _joinClicks.events() | rpl::to_empty; } } // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/group_call_bar.h b/Telegram/SourceFiles/ui/chat/group_call_bar.h index e1074dac6c..92dd142e2a 100644 --- a/Telegram/SourceFiles/ui/chat/group_call_bar.h +++ b/Telegram/SourceFiles/ui/chat/group_call_bar.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/slide_wrap.h" #include "ui/effects/animations.h" #include "base/object_ptr.h" +#include "base/timer.h" class Painter; @@ -28,6 +29,27 @@ struct GroupCallBarContent { std::vector users; }; +class GroupCallScheduledLeft final { +public: + explicit GroupCallScheduledLeft(TimeId date); + + void setDate(TimeId date); + + [[nodiscard]] rpl::producer text() const; + +private: + [[nodiscard]] crl::time computePreciseDate() const; + void restart(); + void update(); + + rpl::variable _text; + TimeId _date = 0; + crl::time _datePrecise = 0; + base::Timer _timer; + rpl::lifetime _lifetime; + +}; + class GroupCallBar final { public: GroupCallBar( @@ -57,15 +79,22 @@ public: private: using User = GroupCallUser; + void refreshOpenBrush(); + void refreshScheduledProcess(); void updateShadowGeometry(QRect wrapGeometry); void updateControlsGeometry(QRect wrapGeometry); void updateUserpics(); void setupInner(); + void setupRightButton(not_null button); void paint(Painter &p); SlideWrap<> _wrap; not_null _inner; std::unique_ptr _join; + std::unique_ptr _open; + rpl::event_stream _joinClicks; + QBrush _openBrushOverride; + int _openBrushForWidth = 0; std::unique_ptr _shadow; rpl::event_stream<> _barClicks; Fn _shadowGeometryPostprocess; @@ -73,6 +102,7 @@ private: bool _forceHidden = false; GroupCallBarContent _content; + std::unique_ptr _scheduledProcess; std::unique_ptr _userpics; }; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 4d37527dd8..7099e61c1a 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -1003,6 +1003,8 @@ void SessionController::startOrJoinGroupCall( && calls.inGroupCall()) { if (calls.currentGroupCall()->peer() == peer) { calls.activateCurrentCall(joinHash); + } else if (calls.currentGroupCall()->scheduleDate()) { + calls.startOrJoinGroupCall(peer, joinHash); } else { askConfirmation( tr::lng_group_call_leave_to_other_sure(tr::now),