From 254b02ad6b817c8937cc3f641e115ed24f09de6e Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 7 Jun 2021 16:01:41 +0400 Subject: [PATCH] Fix Release build on Windows. --- Telegram/CMakeLists.txt | 2 + .../group/calls_group_invite_controller.cpp | 305 ++++++++++++ .../group/calls_group_invite_controller.h | 82 ++++ .../calls/group/calls_group_panel.cpp | 455 +----------------- .../group/ui/calls_group_scheduled_labels.cpp | 136 ++++++ .../group/ui/calls_group_scheduled_labels.h | 27 ++ Telegram/cmake/td_ui.cmake | 2 + 7 files changed, 562 insertions(+), 447 deletions(-) create mode 100644 Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp create mode 100644 Telegram/SourceFiles/calls/group/calls_group_invite_controller.h create mode 100644 Telegram/SourceFiles/calls/group/ui/calls_group_scheduled_labels.cpp create mode 100644 Telegram/SourceFiles/calls/group/ui/calls_group_scheduled_labels.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 678e9debba..2383d41ebf 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -277,6 +277,8 @@ PRIVATE calls/group/calls_group_call.cpp calls/group/calls_group_call.h calls/group/calls_group_common.h + calls/group/calls_group_invite_controller.cpp + calls/group/calls_group_invite_controller.h calls/group/calls_group_members.cpp calls/group/calls_group_members.h calls/group/calls_group_members_row.cpp diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp new file mode 100644 index 0000000000..6ad1c9efc5 --- /dev/null +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp @@ -0,0 +1,305 @@ +/* +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 "calls/group/calls_group_invite_controller.h" + +#include "calls/group/calls_group_call.h" +#include "calls/group/calls_group_menu.h" +#include "boxes/peer_lists_box.h" +#include "data/data_user.h" +#include "data/data_channel.h" +#include "data/data_session.h" +#include "data/data_group_call.h" +#include "main/main_session.h" +#include "ui/text/text_utilities.h" +#include "ui/layers/generic_box.h" +#include "ui/widgets/labels.h" +#include "apiwrap.h" +#include "lang/lang_keys.h" +#include "styles/style_calls.h" + +namespace Calls::Group { +namespace { + +[[nodiscard]] object_ptr CreateSectionSubtitle( + QWidget *parent, + rpl::producer text) { + auto result = object_ptr( + parent, + st::searchedBarHeight); + + const auto raw = result.data(); + raw->paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + auto p = QPainter(raw); + p.fillRect(clip, st::groupCallMembersBgOver); + }, raw->lifetime()); + + const auto label = Ui::CreateChild( + raw, + std::move(text), + st::groupCallBoxLabel); + raw->widthValue( + ) | rpl::start_with_next([=](int width) { + const auto padding = st::groupCallInviteDividerPadding; + const auto available = width - padding.left() - padding.right(); + label->resizeToNaturalWidth(available); + label->moveToLeft(padding.left(), padding.top(), width); + }, label->lifetime()); + + return result; +} + +} // namespace + +InviteController::InviteController( + not_null peer, + base::flat_set> alreadyIn) +: ParticipantsBoxController(CreateTag{}, nullptr, peer, Role::Members) +, _peer(peer) +, _alreadyIn(std::move(alreadyIn)) { + SubscribeToMigration( + _peer, + lifetime(), + [=](not_null channel) { _peer = channel; }); +} + +void InviteController::prepare() { + delegate()->peerListSetHideEmpty(true); + ParticipantsBoxController::prepare(); + delegate()->peerListSetAboveWidget(CreateSectionSubtitle( + nullptr, + tr::lng_group_call_invite_members())); + delegate()->peerListSetAboveSearchWidget(CreateSectionSubtitle( + nullptr, + tr::lng_group_call_invite_members())); +} + +void InviteController::rowClicked(not_null row) { + delegate()->peerListSetRowChecked(row, !row->checked()); +} + +base::unique_qptr InviteController::rowContextMenu( + QWidget *parent, + not_null row) { + return nullptr; +} + +void InviteController::itemDeselectedHook(not_null peer) { +} + +bool InviteController::hasRowFor(not_null peer) const { + return (delegate()->peerListFindRow(peer->id.value) != nullptr); +} + +bool InviteController::isAlreadyIn(not_null user) const { + return _alreadyIn.contains(user); +} + +std::unique_ptr InviteController::createRow( + not_null participant) const { + const auto user = participant->asUser(); + if (!user + || user->isSelf() + || user->isBot() + || (user->flags() & MTPDuser::Flag::f_deleted)) { + return nullptr; + } + auto result = std::make_unique(user); + _rowAdded.fire_copy(user); + _inGroup.emplace(user); + if (isAlreadyIn(user)) { + result->setDisabledState(PeerListRow::State::DisabledChecked); + } + return result; +} + +auto InviteController::peersWithRows() const +-> not_null>*> { + return &_inGroup; +} + +rpl::producer> InviteController::rowAdded() const { + return _rowAdded.events(); +} + +InviteContactsController::InviteContactsController( + not_null peer, + base::flat_set> alreadyIn, + not_null>*> inGroup, + rpl::producer> discoveredInGroup) +: AddParticipantsBoxController(peer, std::move(alreadyIn)) +, _inGroup(inGroup) +, _discoveredInGroup(std::move(discoveredInGroup)) { +} + +void InviteContactsController::prepareViewHook() { + AddParticipantsBoxController::prepareViewHook(); + + delegate()->peerListSetAboveWidget(CreateSectionSubtitle( + nullptr, + tr::lng_contacts_header())); + delegate()->peerListSetAboveSearchWidget(CreateSectionSubtitle( + nullptr, + tr::lng_group_call_invite_search_results())); + + std::move( + _discoveredInGroup + ) | rpl::start_with_next([=](not_null user) { + if (auto row = delegate()->peerListFindRow(user->id.value)) { + delegate()->peerListRemoveRow(row); + } + }, _lifetime); +} + +std::unique_ptr InviteContactsController::createRow( + not_null user) { + return _inGroup->contains(user) + ? nullptr + : AddParticipantsBoxController::createRow(user); +} + +object_ptr PrepareInviteBox( + not_null call, + Fn showToast) { + const auto real = call->lookupReal(); + if (!real) { + return nullptr; + } + const auto peer = call->peer(); + auto alreadyIn = peer->owner().invitedToCallUsers(real->id()); + for (const auto &participant : real->participants()) { + if (const auto user = participant.peer->asUser()) { + alreadyIn.emplace(user); + } + } + alreadyIn.emplace(peer->session().user()); + auto controller = std::make_unique(peer, alreadyIn); + controller->setStyleOverrides( + &st::groupCallInviteMembersList, + &st::groupCallMultiSelect); + + auto contactsController = std::make_unique( + peer, + std::move(alreadyIn), + controller->peersWithRows(), + controller->rowAdded()); + contactsController->setStyleOverrides( + &st::groupCallInviteMembersList, + &st::groupCallMultiSelect); + + const auto weak = base::make_weak(call.get()); + const auto invite = [=](const std::vector> &users) { + const auto call = weak.get(); + if (!call) { + return; + } + const auto result = call->inviteUsers(users); + if (const auto user = std::get_if>(&result)) { + showToast(tr::lng_group_call_invite_done_user( + tr::now, + lt_user, + Ui::Text::Bold((*user)->firstName), + Ui::Text::WithEntities)); + } else if (const auto count = std::get_if(&result)) { + if (*count > 0) { + showToast(tr::lng_group_call_invite_done_many( + tr::now, + lt_count, + *count, + Ui::Text::RichLangValue)); + } + } else { + Unexpected("Result in GroupCall::inviteUsers."); + } + }; + const auto inviteWithAdd = [=]( + const std::vector> &users, + const std::vector> &nonMembers, + Fn finish) { + peer->session().api().addChatParticipants( + peer, + nonMembers, + [=](bool) { invite(users); finish(); }); + }; + const auto inviteWithConfirmation = [=]( + not_null parentBox, + const std::vector> &users, + const std::vector> &nonMembers, + Fn finish) { + if (nonMembers.empty()) { + invite(users); + finish(); + return; + } + const auto name = peer->name; + const auto text = (nonMembers.size() == 1) + ? tr::lng_group_call_add_to_group_one( + tr::now, + lt_user, + nonMembers.front()->shortName(), + lt_group, + name) + : (nonMembers.size() < users.size()) + ? tr::lng_group_call_add_to_group_some(tr::now, lt_group, name) + : tr::lng_group_call_add_to_group_all(tr::now, lt_group, name); + const auto shared = std::make_shared>(); + const auto finishWithConfirm = [=] { + if (*shared) { + (*shared)->closeBox(); + } + finish(); + }; + const auto done = [=] { + inviteWithAdd(users, nonMembers, finishWithConfirm); + }; + auto box = ConfirmBox({ + .text = { text }, + .button = tr::lng_participant_invite(), + .callback = done, + }); + *shared = box.data(); + parentBox->getDelegate()->showBox( + std::move(box), + Ui::LayerOption::KeepOther, + anim::type::normal); + }; + auto initBox = [=, controller = controller.get()]( + not_null box) { + box->setTitle(tr::lng_group_call_invite_title()); + box->addButton(tr::lng_group_call_invite_button(), [=] { + const auto rows = box->collectSelectedRows(); + + const auto users = ranges::views::all( + rows + ) | ranges::views::transform([](not_null peer) { + return not_null(peer->asUser()); + }) | ranges::to_vector; + + const auto nonMembers = ranges::views::all( + users + ) | ranges::views::filter([&](not_null user) { + return !controller->hasRowFor(user); + }) | ranges::to_vector; + + const auto finish = [box = Ui::MakeWeak(box)]() { + if (box) { + box->closeBox(); + } + }; + inviteWithConfirmation(box, users, nonMembers, finish); + }); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + }; + + auto controllers = std::vector>(); + controllers.push_back(std::move(controller)); + controllers.push_back(std::move(contactsController)); + return Box(std::move(controllers), initBox); +} + +} // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h new file mode 100644 index 0000000000..e1e10ff4e7 --- /dev/null +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h @@ -0,0 +1,82 @@ +/* +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 "boxes/peers/edit_participants_box.h" +#include "boxes/peers/add_participants_box.h" + +namespace Calls { +class GroupCall; +} // namespace Calls + +namespace Calls::Group { + +class InviteController final : public ParticipantsBoxController { +public: + InviteController( + not_null peer, + base::flat_set> alreadyIn); + + void prepare() override; + + void rowClicked(not_null row) override; + base::unique_qptr rowContextMenu( + QWidget *parent, + not_null row) override; + + void itemDeselectedHook(not_null peer) override; + + [[nodiscard]] auto peersWithRows() const + -> not_null>*>; + [[nodiscard]] rpl::producer> rowAdded() const; + + [[nodiscard]] bool hasRowFor(not_null peer) const; + +private: + [[nodiscard]] bool isAlreadyIn(not_null user) const; + + std::unique_ptr createRow( + not_null participant) const override; + + not_null _peer; + const base::flat_set> _alreadyIn; + mutable base::flat_set> _inGroup; + rpl::event_stream> _rowAdded; + +}; + +class InviteContactsController final : public AddParticipantsBoxController { +public: + InviteContactsController( + not_null peer, + base::flat_set> alreadyIn, + not_null>*> inGroup, + rpl::producer> discoveredInGroup); + +private: + void prepareViewHook() override; + + std::unique_ptr createRow( + not_null user) override; + + bool needsInviteLinkButton() override { + return false; + } + + const not_null>*> _inGroup; + rpl::producer> _discoveredInGroup; + + rpl::lifetime _lifetime; + +}; + +[[nodiscard]] object_ptr PrepareInviteBox( + not_null call, + Fn showToast); + +} // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 5da7747e0a..45fc8e9658 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/group/calls_group_menu.h" #include "calls/group/calls_group_viewport.h" #include "calls/group/calls_group_toasts.h" +#include "calls/group/calls_group_invite_controller.h" +#include "calls/group/ui/calls_group_scheduled_labels.h" #include "calls/group/ui/desktop_capture_choose_source.h" #include "ui/platform/ui_platform_window_title.h" #include "ui/platform/ui_platform_utility.h" @@ -43,10 +45,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "main/main_session.h" #include "base/event_filter.h" -#include "boxes/peers/edit_participants_box.h" -#include "boxes/peers/add_participants_box.h" -#include "boxes/peer_lists_box.h" -#include "boxes/confirm_box.h" #include "base/unixtime.h" #include "base/qt_signal_producer.h" #include "base/timer_rpl.h" @@ -72,318 +70,6 @@ constexpr auto kStartNoConfirmation = TimeId(10); constexpr auto kControlsBackgroundOpacity = 0.8; constexpr auto kOverrideActiveColorBgAlpha = 172; -class InviteController final : public ParticipantsBoxController { -public: - InviteController( - not_null peer, - base::flat_set> alreadyIn); - - void prepare() override; - - void rowClicked(not_null row) override; - base::unique_qptr rowContextMenu( - QWidget *parent, - not_null row) override; - - void itemDeselectedHook(not_null peer) override; - - [[nodiscard]] auto peersWithRows() const - -> not_null>*>; - [[nodiscard]] rpl::producer> rowAdded() const; - - [[nodiscard]] bool hasRowFor(not_null peer) const; - -private: - [[nodiscard]] bool isAlreadyIn(not_null user) const; - - std::unique_ptr createRow( - not_null participant) const override; - - not_null _peer; - const base::flat_set> _alreadyIn; - mutable base::flat_set> _inGroup; - rpl::event_stream> _rowAdded; - -}; - -class InviteContactsController final : public AddParticipantsBoxController { -public: - InviteContactsController( - not_null peer, - base::flat_set> alreadyIn, - not_null>*> inGroup, - rpl::producer> discoveredInGroup); - -private: - void prepareViewHook() override; - - std::unique_ptr createRow( - not_null user) override; - - bool needsInviteLinkButton() override { - return false; - } - - const not_null>*> _inGroup; - rpl::producer> _discoveredInGroup; - - rpl::lifetime _lifetime; - -}; - -[[nodiscard]] rpl::producer StartsWhenText( - rpl::producer date) { - return std::move( - date - ) | rpl::map([](TimeId date) -> rpl::producer { - const auto parsedDate = base::unixtime::parse(date); - const auto dateDay = QDateTime(parsedDate.date(), QTime(0, 0)); - const auto previousDay = QDateTime( - parsedDate.date().addDays(-1), - QTime(0, 0)); - const auto now = QDateTime::currentDateTime(); - const auto kDay = int64(24 * 60 * 60); - const auto tillTomorrow = int64(now.secsTo(previousDay)); - const auto tillToday = tillTomorrow + kDay; - const auto tillAfter = tillToday + kDay; - - const auto time = parsedDate.time().toString( - QLocale::system().timeFormat(QLocale::ShortFormat)); - auto exact = tr::lng_group_call_starts_short_date( - lt_date, - rpl::single(langDayOfMonthFull(dateDay.date())), - lt_time, - rpl::single(time) - ) | rpl::type_erased(); - auto tomorrow = tr::lng_group_call_starts_short_tomorrow( - lt_time, - rpl::single(time)); - auto today = tr::lng_group_call_starts_short_today( - lt_time, - rpl::single(time)); - - auto todayAndAfter = rpl::single( - std::move(today) - ) | rpl::then(base::timer_once( - std::min(tillAfter, kDay) * crl::time(1000) - ) | rpl::map([=] { - return rpl::duplicate(exact); - })) | rpl::flatten_latest() | rpl::type_erased(); - - auto tomorrowAndAfter = rpl::single( - std::move(tomorrow) - ) | rpl::then(base::timer_once( - std::min(tillToday, kDay) * crl::time(1000) - ) | rpl::map([=] { - return rpl::duplicate(todayAndAfter); - })) | rpl::flatten_latest() | rpl::type_erased(); - - auto full = rpl::single( - rpl::duplicate(exact) - ) | rpl::then(base::timer_once( - tillTomorrow * crl::time(1000) - ) | rpl::map([=] { - return rpl::duplicate(tomorrowAndAfter); - })) | rpl::flatten_latest() | rpl::type_erased(); - - if (tillTomorrow > 0) { - return full; - } else if (tillToday > 0) { - return tomorrowAndAfter; - } else if (tillAfter > 0) { - return todayAndAfter; - } else { - return exact; - } - }) | rpl::flatten_latest(); -} - -[[nodiscard]] object_ptr CreateGradientLabel( - QWidget *parent, - rpl::producer text) { - struct State { - QBrush brush; - QPainterPath path; - }; - auto result = object_ptr(parent); - const auto raw = result.data(); - const auto state = raw->lifetime().make_state(); - - std::move( - text - ) | rpl::start_with_next([=](const QString &text) { - state->path = QPainterPath(); - const auto &font = st::groupCallCountdownFont; - state->path.addText(0, font->ascent, font->f, text); - const auto width = font->width(text); - raw->resize(width, font->height); - auto gradient = QLinearGradient(QPoint(width, 0), QPoint()); - gradient.setStops(QGradientStops{ - { 0.0, st::groupCallForceMutedBar1->c }, - { .7, st::groupCallForceMutedBar2->c }, - { 1.0, st::groupCallForceMutedBar3->c } - }); - state->brush = QBrush(std::move(gradient)); - raw->update(); - }, raw->lifetime()); - - raw->paintRequest( - ) | rpl::start_with_next([=] { - auto p = QPainter(raw); - auto hq = PainterHighQualityEnabler(p); - const auto skip = st::groupCallWidth / 20; - const auto available = parent->width() - 2 * skip; - const auto full = raw->width(); - if (available > 0 && full > available) { - const auto scale = available / float64(full); - const auto shift = raw->rect().center(); - p.translate(shift); - p.scale(scale, scale); - p.translate(-shift); - } - p.setPen(Qt::NoPen); - p.setBrush(state->brush); - p.drawPath(state->path); - }, raw->lifetime()); - return result; -} - -[[nodiscard]] object_ptr CreateSectionSubtitle( - QWidget *parent, - rpl::producer text) { - auto result = object_ptr( - parent, - st::searchedBarHeight); - - const auto raw = result.data(); - raw->paintRequest( - ) | rpl::start_with_next([=](QRect clip) { - auto p = QPainter(raw); - p.fillRect(clip, st::groupCallMembersBgOver); - }, raw->lifetime()); - - const auto label = Ui::CreateChild( - raw, - std::move(text), - st::groupCallBoxLabel); - raw->widthValue( - ) | rpl::start_with_next([=](int width) { - const auto padding = st::groupCallInviteDividerPadding; - const auto available = width - padding.left() - padding.right(); - label->resizeToNaturalWidth(available); - label->moveToLeft(padding.left(), padding.top(), width); - }, label->lifetime()); - - return result; -} - -InviteController::InviteController( - not_null peer, - base::flat_set> alreadyIn) -: ParticipantsBoxController(CreateTag{}, nullptr, peer, Role::Members) -, _peer(peer) -, _alreadyIn(std::move(alreadyIn)) { - SubscribeToMigration( - _peer, - lifetime(), - [=](not_null channel) { _peer = channel; }); -} - -void InviteController::prepare() { - delegate()->peerListSetHideEmpty(true); - ParticipantsBoxController::prepare(); - delegate()->peerListSetAboveWidget(CreateSectionSubtitle( - nullptr, - tr::lng_group_call_invite_members())); - delegate()->peerListSetAboveSearchWidget(CreateSectionSubtitle( - nullptr, - tr::lng_group_call_invite_members())); -} - -void InviteController::rowClicked(not_null row) { - delegate()->peerListSetRowChecked(row, !row->checked()); -} - -base::unique_qptr InviteController::rowContextMenu( - QWidget *parent, - not_null row) { - return nullptr; -} - -void InviteController::itemDeselectedHook(not_null peer) { -} - -bool InviteController::hasRowFor(not_null peer) const { - return (delegate()->peerListFindRow(peer->id.value) != nullptr); -} - -bool InviteController::isAlreadyIn(not_null user) const { - return _alreadyIn.contains(user); -} - -std::unique_ptr InviteController::createRow( - not_null participant) const { - const auto user = participant->asUser(); - if (!user - || user->isSelf() - || user->isBot() - || (user->flags() & MTPDuser::Flag::f_deleted)) { - return nullptr; - } - auto result = std::make_unique(user); - _rowAdded.fire_copy(user); - _inGroup.emplace(user); - if (isAlreadyIn(user)) { - result->setDisabledState(PeerListRow::State::DisabledChecked); - } - return result; -} - -auto InviteController::peersWithRows() const --> not_null>*> { - return &_inGroup; -} - -rpl::producer> InviteController::rowAdded() const { - return _rowAdded.events(); -} - -InviteContactsController::InviteContactsController( - not_null peer, - base::flat_set> alreadyIn, - not_null>*> inGroup, - rpl::producer> discoveredInGroup) -: AddParticipantsBoxController(peer, std::move(alreadyIn)) -, _inGroup(inGroup) -, _discoveredInGroup(std::move(discoveredInGroup)) { -} - -void InviteContactsController::prepareViewHook() { - AddParticipantsBoxController::prepareViewHook(); - - delegate()->peerListSetAboveWidget(CreateSectionSubtitle( - nullptr, - tr::lng_contacts_header())); - delegate()->peerListSetAboveSearchWidget(CreateSectionSubtitle( - nullptr, - tr::lng_group_call_invite_search_results())); - - std::move( - _discoveredInGroup - ) | rpl::start_with_next([=](not_null user) { - if (auto row = delegate()->peerListFindRow(user->id.value)) { - delegate()->peerListRemoveRow(row); - } - }, _lifetime); -} - -std::unique_ptr InviteContactsController::createRow( - not_null user) { - return _inGroup->contains(user) - ? nullptr - : AddParticipantsBoxController::createRow(user); -} - } // namespace struct Panel::ControlsBackgroundNarrow { @@ -918,7 +604,7 @@ void Panel::setupScheduledLabels(rpl::producer date) { date = std::move(date) | rpl::take_while(_1 != 0); _startsWhen.create( widget(), - StartsWhenText(rpl::duplicate(date)), + Ui::StartsWhenText(rpl::duplicate(date)), st::groupCallStartsWhen); auto countdownCreated = std::move( date @@ -927,7 +613,7 @@ void Panel::setupScheduledLabels(rpl::producer date) { return rpl::empty_value(); }) | rpl::start_spawning(widget()->lifetime()); - _countdown = CreateGradientLabel(widget(), rpl::duplicate( + _countdown = Ui::CreateGradientLabel(widget(), rpl::duplicate( countdownCreated ) | rpl::map([=] { return _countdownData->text( @@ -1489,137 +1175,12 @@ void Panel::showMainMenu() { } void Panel::addMembers() { - const auto real = _call->lookupReal(); - if (!real) { - return; - } - auto alreadyIn = _peer->owner().invitedToCallUsers(real->id()); - for (const auto &participant : real->participants()) { - if (const auto user = participant.peer->asUser()) { - alreadyIn.emplace(user); - } - } - alreadyIn.emplace(_peer->session().user()); - auto controller = std::make_unique( - _peer, - alreadyIn); - controller->setStyleOverrides( - &st::groupCallInviteMembersList, - &st::groupCallMultiSelect); - - auto contactsController = std::make_unique( - _peer, - std::move(alreadyIn), - controller->peersWithRows(), - controller->rowAdded()); - contactsController->setStyleOverrides( - &st::groupCallInviteMembersList, - &st::groupCallMultiSelect); - - const auto weak = base::make_weak(_call.get()); - const auto invite = [=](const std::vector> &users) { - const auto call = weak.get(); - if (!call) { - return; - } - const auto result = call->inviteUsers(users); - if (const auto user = std::get_if>(&result)) { - showToast(tr::lng_group_call_invite_done_user( - tr::now, - lt_user, - Ui::Text::Bold((*user)->firstName), - Ui::Text::WithEntities)); - } else if (const auto count = std::get_if(&result)) { - if (*count > 0) { - showToast(tr::lng_group_call_invite_done_many( - tr::now, - lt_count, - *count, - Ui::Text::RichLangValue)); - } - } else { - Unexpected("Result in GroupCall::inviteUsers."); - } + const auto showToastCallback = [=](TextWithEntities &&text) { + showToast(std::move(text)); }; - const auto inviteWithAdd = [=]( - const std::vector> &users, - const std::vector> &nonMembers, - Fn finish) { - _peer->session().api().addChatParticipants( - _peer, - nonMembers, - [=](bool) { invite(users); finish(); }); - }; - const auto inviteWithConfirmation = [=]( - const std::vector> &users, - const std::vector> &nonMembers, - Fn finish) { - if (nonMembers.empty()) { - invite(users); - finish(); - return; - } - const auto name = _peer->name; - const auto text = (nonMembers.size() == 1) - ? tr::lng_group_call_add_to_group_one( - tr::now, - lt_user, - nonMembers.front()->shortName(), - lt_group, - name) - : (nonMembers.size() < users.size()) - ? tr::lng_group_call_add_to_group_some(tr::now, lt_group, name) - : tr::lng_group_call_add_to_group_all(tr::now, lt_group, name); - const auto shared = std::make_shared>(); - const auto finishWithConfirm = [=] { - if (*shared) { - (*shared)->closeBox(); - } - finish(); - }; - const auto done = [=] { - inviteWithAdd(users, nonMembers, finishWithConfirm); - }; - auto box = ConfirmBox({ - .text = { text }, - .button = tr::lng_participant_invite(), - .callback = done, - }); - *shared = box.data(); + if (auto box = PrepareInviteBox(_call, showToastCallback)) { _layerBg->showBox(std::move(box)); - }; - auto initBox = [=, controller = controller.get()]( - not_null box) { - box->setTitle(tr::lng_group_call_invite_title()); - box->addButton(tr::lng_group_call_invite_button(), [=] { - const auto rows = box->collectSelectedRows(); - - const auto users = ranges::views::all( - rows - ) | ranges::views::transform([](not_null peer) { - return not_null(peer->asUser()); - }) | ranges::to_vector; - - const auto nonMembers = ranges::views::all( - users - ) | ranges::views::filter([&](not_null user) { - return !controller->hasRowFor(user); - }) | ranges::to_vector; - - const auto finish = [box = Ui::MakeWeak(box)]() { - if (box) { - box->closeBox(); - } - }; - inviteWithConfirmation(users, nonMembers, finish); - }); - box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); - }; - - auto controllers = std::vector>(); - controllers.push_back(std::move(controller)); - controllers.push_back(std::move(contactsController)); - _layerBg->showBox(Box(std::move(controllers), initBox)); + } } void Panel::kickParticipant(not_null participantPeer) { diff --git a/Telegram/SourceFiles/calls/group/ui/calls_group_scheduled_labels.cpp b/Telegram/SourceFiles/calls/group/ui/calls_group_scheduled_labels.cpp new file mode 100644 index 0000000000..aa793b6e61 --- /dev/null +++ b/Telegram/SourceFiles/calls/group/ui/calls_group_scheduled_labels.cpp @@ -0,0 +1,136 @@ +/* +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 "calls/group/ui/calls_group_scheduled_labels.h" + +#include "ui/rp_widget.h" +#include "lang/lang_keys.h" +#include "base/unixtime.h" +#include "base/timer_rpl.h" +#include "styles/style_calls.h" + +#include + +namespace Calls::Group::Ui { + +rpl::producer StartsWhenText(rpl::producer date) { + return std::move( + date + ) | rpl::map([](TimeId date) -> rpl::producer { + const auto parsedDate = base::unixtime::parse(date); + const auto dateDay = QDateTime(parsedDate.date(), QTime(0, 0)); + const auto previousDay = QDateTime( + parsedDate.date().addDays(-1), + QTime(0, 0)); + const auto now = QDateTime::currentDateTime(); + const auto kDay = int64(24 * 60 * 60); + const auto tillTomorrow = int64(now.secsTo(previousDay)); + const auto tillToday = tillTomorrow + kDay; + const auto tillAfter = tillToday + kDay; + + const auto time = parsedDate.time().toString( + QLocale::system().timeFormat(QLocale::ShortFormat)); + auto exact = tr::lng_group_call_starts_short_date( + lt_date, + rpl::single(langDayOfMonthFull(dateDay.date())), + lt_time, + rpl::single(time) + ) | rpl::type_erased(); + auto tomorrow = tr::lng_group_call_starts_short_tomorrow( + lt_time, + rpl::single(time)); + auto today = tr::lng_group_call_starts_short_today( + lt_time, + rpl::single(time)); + + auto todayAndAfter = rpl::single( + std::move(today) + ) | rpl::then(base::timer_once( + std::min(tillAfter, kDay) * crl::time(1000) + ) | rpl::map([=] { + return rpl::duplicate(exact); + })) | rpl::flatten_latest() | rpl::type_erased(); + + auto tomorrowAndAfter = rpl::single( + std::move(tomorrow) + ) | rpl::then(base::timer_once( + std::min(tillToday, kDay) * crl::time(1000) + ) | rpl::map([=] { + return rpl::duplicate(todayAndAfter); + })) | rpl::flatten_latest() | rpl::type_erased(); + + auto full = rpl::single( + rpl::duplicate(exact) + ) | rpl::then(base::timer_once( + tillTomorrow * crl::time(1000) + ) | rpl::map([=] { + return rpl::duplicate(tomorrowAndAfter); + })) | rpl::flatten_latest() | rpl::type_erased(); + + if (tillTomorrow > 0) { + return full; + } else if (tillToday > 0) { + return tomorrowAndAfter; + } else if (tillAfter > 0) { + return todayAndAfter; + } else { + return exact; + } + }) | rpl::flatten_latest(); +} + +object_ptr CreateGradientLabel( + QWidget *parent, + rpl::producer text) { + struct State { + QBrush brush; + QPainterPath path; + }; + auto result = object_ptr(parent); + const auto raw = result.data(); + const auto state = raw->lifetime().make_state(); + + std::move( + text + ) | rpl::start_with_next([=](const QString &text) { + state->path = QPainterPath(); + const auto &font = st::groupCallCountdownFont; + state->path.addText(0, font->ascent, font->f, text); + const auto width = font->width(text); + raw->resize(width, font->height); + auto gradient = QLinearGradient(QPoint(width, 0), QPoint()); + gradient.setStops(QGradientStops{ + { 0.0, st::groupCallForceMutedBar1->c }, + { .7, st::groupCallForceMutedBar2->c }, + { 1.0, st::groupCallForceMutedBar3->c } + }); + state->brush = QBrush(std::move(gradient)); + raw->update(); + }, raw->lifetime()); + + raw->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(raw); + auto hq = PainterHighQualityEnabler(p); + const auto skip = st::groupCallWidth / 20; + const auto available = parent->width() - 2 * skip; + const auto full = raw->width(); + if (available > 0 && full > available) { + const auto scale = available / float64(full); + const auto shift = raw->rect().center(); + p.translate(shift); + p.scale(scale, scale); + p.translate(-shift); + } + p.setPen(Qt::NoPen); + p.setBrush(state->brush); + p.drawPath(state->path); + }, raw->lifetime()); + return result; +} + +} // namespace Calls::Group::Ui diff --git a/Telegram/SourceFiles/calls/group/ui/calls_group_scheduled_labels.h b/Telegram/SourceFiles/calls/group/ui/calls_group_scheduled_labels.h new file mode 100644 index 0000000000..d09a57b84d --- /dev/null +++ b/Telegram/SourceFiles/calls/group/ui/calls_group_scheduled_labels.h @@ -0,0 +1,27 @@ +/* +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 "base/object_ptr.h" + +namespace Ui { +class RpWidget; +} // namespace Ui + +namespace Calls::Group::Ui { + +using namespace ::Ui; + +[[nodiscard]] rpl::producer StartsWhenText( + rpl::producer date); + +[[nodiscard]] object_ptr CreateGradientLabel( + QWidget *parent, + rpl::producer text); + +} // namespace Calls::Group::Ui diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 3ecc52983b..c497b3482f 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -47,6 +47,8 @@ nice_target_sources(td_ui ${src_loc} PRIVATE ${style_files} + calls/group/ui/calls_group_scheduled_labels.cpp + calls/group/ui/calls_group_scheduled_labels.h calls/group/ui/desktop_capture_choose_source.cpp calls/group/ui/desktop_capture_choose_source.h