diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 364653131..d4cd29b93 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2694,6 +2694,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_call_box_clear_all" = "Clear All"; "lng_call_box_clear_sure" = "Are you sure you want to completely clear your calls log?"; "lng_call_box_clear_button" = "Clear"; +"lng_call_box_groupcalls_subtitle" = "Active video chats"; "lng_call_outgoing" = "Outgoing call"; "lng_call_video_outgoing" = "Outgoing video call"; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 608738171..684c5acbf 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -369,6 +369,10 @@ callCameraReDial: IconButton(callReDial) { icon: icon {{ "calls/call_camera_active", menuIconFg }}; iconOver: icon {{ "calls/call_camera_active", menuIconFgOver }}; } +callGroupCall: IconButton(callCameraReDial) { + icon: icon {{ "top_bar_group_call", menuIconFg }}; + iconOver: icon {{ "top_bar_group_call", menuIconFgOver }}; +} callRatingPadding: margins(24px, 12px, 24px, 0px); callRatingStar: IconButton { diff --git a/Telegram/SourceFiles/calls/calls_box_controller.cpp b/Telegram/SourceFiles/calls/calls_box_controller.cpp index aaec89cf6..82bdfe359 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.cpp +++ b/Telegram/SourceFiles/calls/calls_box_controller.cpp @@ -25,6 +25,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/data_media_types.h" #include "data/data_user.h" +#include "data/data_peer_values.h" // Data::ChannelHasActiveCall. +#include "data/data_group_call.h" +#include "data/data_channel.h" #include "boxes/delete_messages_box.h" #include "base/unixtime.h" #include "api/api_updates.h" @@ -40,8 +43,201 @@ namespace { constexpr auto kFirstPageCount = 20; constexpr auto kPerPageCount = 100; +class GroupCallRow final : public PeerListRow { +public: + GroupCallRow(not_null peer); + + void rightActionAddRipple( + QPoint point, + Fn updateCallback) override; + void rightActionStopLastRipple() override; + + int paintNameIconGetWidth( + Painter &p, + Fn repaint, + crl::time now, + int nameLeft, + int nameTop, + int nameWidth, + int availableWidth, + int outerWidth, + bool selected) override { + return 0; + } + QSize rightActionSize() const override { + return peer()->isChannel() ? QSize(_st.width, _st.height) : QSize(); + } + QMargins rightActionMargins() const override { + return QMargins( + 0, + 0, + st::defaultPeerListItem.photoPosition.x(), + 0); + } + void rightActionPaint( + Painter &p, + int x, + int y, + int outerWidth, + bool selected, + bool actionSelected) override; + +private: + const style::IconButton &_st; + std::unique_ptr _actionRipple; + +}; + +GroupCallRow::GroupCallRow(not_null peer) +: PeerListRow(peer) +, _st(st::callGroupCall) { + if (const auto channel = peer->asChannel()) { + const auto status = (channel->isMegagroup() + ? (channel->isPublic() + ? tr::lng_create_public_channel_title + : tr::lng_create_private_channel_title) + : (channel->isPublic() + ? tr::lng_create_public_group_title + : tr::lng_create_private_group_title))(tr::now); + setCustomStatus(status.toLower()); + } +} + +void GroupCallRow::rightActionPaint( + Painter &p, + int x, + int y, + int outerWidth, + bool selected, + bool actionSelected) { + auto size = rightActionSize(); + if (_actionRipple) { + _actionRipple->paint( + p, + x + _st.rippleAreaPosition.x(), + y + _st.rippleAreaPosition.y(), + outerWidth); + if (_actionRipple->empty()) { + _actionRipple.reset(); + } + } + _st.icon.paintInCenter( + p, + style::rtlrect(x, y, size.width(), size.height(), outerWidth)); +} + +void GroupCallRow::rightActionAddRipple( + QPoint point, + Fn updateCallback) { + if (!_actionRipple) { + auto mask = Ui::RippleAnimation::EllipseMask( + QSize(_st.rippleAreaSize, _st.rippleAreaSize)); + _actionRipple = std::make_unique( + _st.ripple, + std::move(mask), + std::move(updateCallback)); + } + _actionRipple->add(point - _st.rippleAreaPosition); +} + +void GroupCallRow::rightActionStopLastRipple() { + if (_actionRipple) { + _actionRipple->lastStop(); + } +} + } // namespace +namespace GroupCalls { + +ListController::ListController(not_null window) +: _window(window) { + setStyleOverrides(&st::peerListSingleRow); +} + +Main::Session &ListController::session() const { + return _window->session(); +} + +void ListController::prepare() { + const auto removeRow = [=](not_null peer) { + const auto it = _groupCalls.find(peer->id); + if (it != end(_groupCalls)) { + const auto &row = it->second; + delegate()->peerListRemoveRow(row); + _groupCalls.erase(it); + } + }; + const auto createRow = [=](not_null peer) { + const auto it = _groupCalls.find(peer->id); + if (it == end(_groupCalls)) { + auto row = std::make_unique(peer); + _groupCalls.emplace(peer->id, row.get()); + delegate()->peerListAppendRow(std::move(row)); + } + }; + + const auto processPeer = [=](PeerData *peer) { + if (!peer) { + return; + } + const auto channel = peer->asChannel(); + if (channel && Data::ChannelHasActiveCall(channel)) { + createRow(peer); + } else { + removeRow(peer); + } + }; + const auto finishProcess = [=] { + delegate()->peerListRefreshRows(); + _fullCount = delegate()->peerListFullRowsCount(); + }; + + session().changes().peerUpdates( + Data::PeerUpdate::Flag::GroupCall + ) | rpl::start_with_next([=](const Data::PeerUpdate &update) { + processPeer(update.peer); + finishProcess(); + }, lifetime()); + + { + auto count = 0; + const auto list = session().data().chatsList(nullptr); + for (const auto &key : list->pinned()->order()) { + processPeer(key.peer()); + } + for (const auto &key : list->indexed()->all()) { + if (count > kFirstPageCount) { + break; + } + processPeer(key->key().peer()); + count++; + } + finishProcess(); + } +} + +rpl::producer ListController::shownValue() const { + return _fullCount.value( + ) | rpl::map(rpl::mappers::_1 > 0) | rpl::distinct_until_changed(); +} + +void ListController::rowClicked(not_null row) { + const auto window = _window; + const auto peer = row->peer(); + crl::on_main(window, [=, peer = row->peer()] { + window->showPeerHistory( + peer, + Window::SectionShow::Way::ClearStack); + }); +} + +void ListController::rowRightActionClicked(not_null row) { + _window->startOrJoinGroupCall(row->peer()); +} + +} // namespace GroupCalls + class BoxController::Row : public PeerListRow { public: Row(not_null item); diff --git a/Telegram/SourceFiles/calls/calls_box_controller.h b/Telegram/SourceFiles/calls/calls_box_controller.h index c6f9f5077..c636ceba5 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.h +++ b/Telegram/SourceFiles/calls/calls_box_controller.h @@ -15,6 +15,27 @@ class SessionController; } // namespace Window namespace Calls { +namespace GroupCalls { + +class ListController : public PeerListController { +public: + explicit ListController(not_null window); + + [[nodiscard]] rpl::producer shownValue() const; + + Main::Session &session() const override; + void prepare() override; + void rowClicked(not_null row) override; + void rowRightActionClicked(not_null row) override; + +private: + const not_null _window; + base::flat_map> _groupCalls; + rpl::variable _fullCount; + +}; + +} // namespace GroupCalls class BoxController : public PeerListController { public: @@ -34,6 +55,7 @@ private: void receivedCalls(const QVector &result); void refreshAbout(); + class GroupCallRow; class Row; Row *rowForItem(not_null item); diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index 5d1021418..1b2cb3838 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -96,16 +96,40 @@ constexpr auto kPlayStatusLimit = 2; void ShowCallsBox(not_null window) { struct State { State(not_null window) - : callsController(window) { + : callsController(window) + , groupCallsController(window) { } Calls::BoxController callsController; PeerListContentDelegateSimple callsDelegate; + Calls::GroupCalls::ListController groupCallsController; + PeerListContentDelegateSimple groupCallsDelegate; + base::unique_qptr menu; }; window->show(Box([=](not_null box) { const auto state = box->lifetime().make_state(window); + + const auto groupCalls = box->addRow( + object_ptr>( + box, + object_ptr(box)), + {}); + groupCalls->hide(anim::type::instant); + groupCalls->toggleOn(state->groupCallsController.shownValue()); + + Settings::AddSubsectionTitle( + groupCalls->entity(), + tr::lng_call_box_groupcalls_subtitle()); + state->groupCallsDelegate.setContent(groupCalls->entity()->add( + object_ptr(box, &state->groupCallsController), + {})); + state->groupCallsController.setDelegate(&state->groupCallsDelegate); + Settings::AddSkip(groupCalls->entity()); + Settings::AddDivider(groupCalls->entity()); + Settings::AddSkip(groupCalls->entity()); + const auto content = box->addRow( object_ptr(box, &state->callsController), {});