Compare commits

..

41 Commits

Author SHA1 Message Date
John Preston
486424af4f Beta version 2.5.2: Update cmake_helpers. 2020-12-25 18:57:21 +04:00
John Preston
f3614d6402 Beta version 2.5.2: Add in-app changelog. 2020-12-25 18:30:41 +04:00
John Preston
71151f6bf6 Beta version 2.5.2.
- Fix possible crash in video calls.
- Fix possible crash in connecting to voice chats.
- Use different audio module code on Windows in calls.
2020-12-25 16:45:18 +04:00
John Preston
2c0ef9c4e9 Improve connecting animation in voice chats. 2020-12-25 16:37:46 +04:00
John Preston
930e971881 Use more modern audio backend in calls on Windows. 2020-12-25 16:11:51 +04:00
John Preston
a576025d4f Always show invited at the end of voice chat. 2020-12-25 15:44:17 +04:00
John Preston
bcd2560e8f Reuse the code for userpics in Calls::TopBar. 2020-12-25 14:10:08 +04:00
John Preston
aede42b0b6 Update submodules. 2020-12-25 14:10:08 +04:00
Ilya Fedin
56728a066e Fix blurry tray icon with svg themes
QIcon::actualSize doesn't work as expected with svg themes, get actual pixmap and check its size instead.
2020-12-24 22:46:09 +03:00
John Preston
1951b7a8a1 Fix possible infinite recursion in video calls. 2020-12-24 14:38:46 +04:00
John Preston
0dc0f588c4 Don't offer sending .pdf-s as photos. 2020-12-24 13:52:38 +04:00
John Preston
7d22c631ca Fix voice chat members context menu. 2020-12-24 13:30:05 +04:00
John Preston
cf5cc3646a Fix multi-pin bar render after theme switch. 2020-12-24 07:59:34 +04:00
Ilya Fedin
375820d5cf glib was missed in stage-packages in snap 2020-12-24 07:48:00 +04:00
Ilya Fedin
3955543699 Remove QtSvg from snap in telegram part
Since it was needed only for lxqt-qtplugin
2020-12-24 07:48:00 +04:00
Ilya Fedin
c03da00e37 Fix getting version tag in snap action 2020-12-24 07:48:00 +04:00
Ilya Fedin
edcd462fb9 Use ibus portal in snap
It's supported since snapd 2.46 that was released in August
2020-12-24 07:48:00 +04:00
Ilya Fedin
e99558abeb Remove linux LastUserInputTime dependency since it's only in lib_base 2020-12-24 07:47:13 +04:00
Ilya Fedin
feff514a07 Update openal in snap to 1.21.0
And remove unneeded dependencies
2020-12-23 20:31:01 +04:00
John Preston
b1b25b0df9 Version 2.5.1.
- Fix crash in voice calls.
2020-12-23 15:01:31 +04:00
John Preston
dfee8238c6 Fix crash in legacy groups speaking typings handling. 2020-12-23 14:45:37 +04:00
John Preston
b7216c40fc Version 2.5.
- Turn any of your group chats into a hop-on, hop-off conference call.
- Get up to several thousand participants in each voice chat.
- Control the number of speakers with flexible admin tools.
2020-12-23 11:59:50 +04:00
John Preston
670d618439 Workaround tg_owt/openal conflict in a patched OpenAL on macOS. 2020-12-22 23:38:38 +04:00
John Preston
d16bc36bae Update API scheme. 2020-12-22 22:39:30 +04:00
John Preston
31417fd005 Fix highlighting of self row in voice chat. 2020-12-22 20:28:13 +04:00
John Preston
ae6decf70b Support ctrl+m/ctrl+w in voice chat panel. 2020-12-22 20:28:13 +04:00
John Preston
c80da25450 Show better tooltip for force muted in voice chat. 2020-12-22 19:38:02 +04:00
John Preston
7fd09084fd Use separate keys for message links and links. 2020-12-22 19:16:54 +04:00
John Preston
d7496f9824 Fix possible crash in app shutdown. 2020-12-22 19:16:54 +04:00
John Preston
f94280be7f Use langpack strings in a better way. 2020-12-22 19:16:54 +04:00
Ilya Fedin
0ff6c555b1 Use Platform::IsWayland in linux_gdk_helper 2020-12-22 18:37:52 +04:00
Ilya Fedin
596c7892c7 Use desktop-app::external_qt_static_plugins 2020-12-22 17:36:25 +04:00
John Preston
902e0fc8fb Remove dll loading in harfbuzz on Windows. 2020-12-22 14:29:51 +04:00
23rd
91e97b3d65 Fixed paint of group with wide thumbs in media viewer.
Fixed #8392.
2020-12-22 12:57:22 +03:00
John Preston
92bc278052 Allow inviting contacts to voice chats. 2020-12-22 12:33:06 +04:00
23rd
16c7ec5b05 Fixed stack overflow crash in applying draft at end of voice recording.
Regression was introduced in 50ed60f443.
2020-12-22 11:24:56 +03:00
23rd
348712059b Moved date and progress text formatting to tg_ui:ui/text/format_values. 2020-12-22 09:11:04 +03:00
23rd
b3f6fe1c10 Removed Enter key from box for clearing history.
Fixed #9781.
2020-12-22 08:37:36 +03:00
23rd
055ce1ee24 Fixed editing of last message on Up arrow key when message is forwarded.
Fixed #9782.
2020-12-22 07:56:51 +03:00
23rd
c14313d64a Replaced bezier circles in record button with blobs. 2020-12-21 14:03:04 +03:00
23rd
25665167fa Updated Qt version in README.md. 2020-12-19 18:59:50 +03:00
89 changed files with 2156 additions and 1741 deletions

View File

@@ -51,6 +51,7 @@ jobs:
- name: Clone.
uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: recursive
- name: First set up.

View File

@@ -32,7 +32,7 @@ Version **1.8.15** was the last that supports older systems
## Third-party
* Qt 5.12.8, 5.6.2 and 5.3.2 slightly patched ([LGPL](http://doc.qt.io/qt-5/lgpl.html))
* Qt 5.15.2, 5.6.2 and 5.3.2 slightly patched ([LGPL](http://doc.qt.io/qt-5/lgpl.html))
* OpenSSL 1.1.1 and 1.0.1 ([OpenSSL License](https://www.openssl.org/source/license.html))
* WebRTC ([New BSD License](https://github.com/desktop-app/tg_owt/blob/master/LICENSE))
* zlib 1.2.11 ([zlib License](http://www.zlib.net/zlib_license.html))

View File

@@ -60,6 +60,7 @@ PRIVATE
desktop-app::external_rlottie
desktop-app::external_zlib
desktop-app::external_minizip
desktop-app::external_qt_static_plugins
desktop-app::external_qt
desktop-app::external_qr_code_generator
desktop-app::external_crash_reports
@@ -71,9 +72,6 @@ PRIVATE
if (LINUX)
target_link_libraries(Telegram
PRIVATE
desktop-app::external_nimf_qt5
desktop-app::external_qt5ct_support
desktop-app::external_xcb_screensaver
desktop-app::external_xcb
desktop-app::external_glib
)
@@ -83,28 +81,19 @@ if (LINUX)
PRIVATE
desktop-app::external_statusnotifieritem
desktop-app::external_dbusmenu_qt
desktop-app::external_fcitx_qt5
desktop-app::external_fcitx5_qt5
desktop-app::external_hime_qt
)
endif()
if (NOT DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
target_link_libraries(Telegram
if (DESKTOP_APP_USE_PACKAGED
AND NOT DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION
AND Qt5WaylandClient_VERSION VERSION_LESS 5.13.0)
find_package(PkgConfig REQUIRED)
pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client)
target_include_directories(Telegram
PRIVATE
desktop-app::external_materialdecoration
${WAYLAND_CLIENT_INCLUDE_DIRS}
)
if (DESKTOP_APP_USE_PACKAGED
AND Qt5WaylandClient_VERSION VERSION_LESS 5.13.0)
find_package(PkgConfig REQUIRED)
pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client)
target_include_directories(Telegram
PRIVATE
${WAYLAND_CLIENT_INCLUDE_DIRS}
)
endif()
endif()
if (NOT TDESKTOP_DISABLE_GTK_INTEGRATION)
@@ -235,6 +224,8 @@ PRIVATE
boxes/peer_list_box.h
boxes/peer_list_controllers.cpp
boxes/peer_list_controllers.h
boxes/peer_lists_box.cpp
boxes/peer_lists_box.h
boxes/passcode_box.cpp
boxes/passcode_box.h
boxes/photo_crop_box.cpp
@@ -1098,7 +1089,6 @@ PRIVATE
mainwidget.h
mainwindow.cpp
mainwindow.h
qt_static_plugins.cpp
settings.cpp
settings.h
stdafx.h

View File

@@ -1498,6 +1498,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_add_to_group" = "Add to group";
"lng_context_copy_link" = "Copy Link";
"lng_context_copy_message_link" = "Copy Message Link";
"lng_context_copy_post_link" = "Copy Post Link";
"lng_context_copy_email" = "Copy Email Address";
"lng_context_copy_hashtag" = "Copy Hashtag";
@@ -1835,6 +1836,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_invited_status" = "invited";
"lng_group_call_invite_title" = "Invite members";
"lng_group_call_invite_button" = "Invite";
"lng_group_call_add_to_group_one" = "{user} isn't a member of «{group}» yet. Add them to the group?";
"lng_group_call_add_to_group_some" = "Some of those users aren't members of «{group}» yet. Add them to the group?";
"lng_group_call_add_to_group_all" = "Those users aren't members of «{group}» yet. Add them to the group?";
"lng_group_call_invite_members" = "Group members";
"lng_group_call_invite_search_results" = "Search results";
"lng_group_call_new_muted" = "Mute new members";
"lng_group_call_speakers" = "Speakers";
"lng_group_call_microphone" = "Microphone";

View File

@@ -63,7 +63,7 @@ inputMediaPhoto#b3ba0635 flags:# id:InputPhoto ttl_seconds:flags.0?int = InputMe
inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia;
inputMediaContact#f8ab7dfb phone_number:string first_name:string last_name:string vcard:string = InputMedia;
inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true force_file:flags.4?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector<DocumentAttribute> stickers:flags.0?Vector<InputDocument> ttl_seconds:flags.1?int = InputMedia;
inputMediaDocument#23ab23d2 flags:# id:InputDocument ttl_seconds:flags.0?int = InputMedia;
inputMediaDocument#33473058 flags:# id:InputDocument ttl_seconds:flags.0?int query:flags.1?string = InputMedia;
inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string = InputMedia;
inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia;
inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia;
@@ -1195,7 +1195,7 @@ groupCall#55903081 flags:# join_muted:flags.1?true can_change_join_muted:flags.2
inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall;
groupCallParticipant#56b087c9 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true user_id:int date:int active_date:flags.3?int source:int = GroupCallParticipant;
groupCallParticipant#56b087c9 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true user_id:int date:int active_date:flags.3?int source:int = GroupCallParticipant;
phone.groupCall#66ab0bfc call:GroupCall participants:Vector<GroupCallParticipant> participants_next_offset:string users:Vector<User> = phone.GroupCall;

View File

@@ -9,7 +9,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="2.4.15.0" />
Version="2.5.2.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram FZ-LLC</PublisherDisplayName>

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,4,15,0
PRODUCTVERSION 2,4,15,0
FILEVERSION 2,5,2,0
PRODUCTVERSION 2,5,2,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "2.4.15.0"
VALUE "FileVersion", "2.5.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.4.15.0"
VALUE "ProductVersion", "2.5.2.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,4,15,0
PRODUCTVERSION 2,4,15,0
FILEVERSION 2,5,2,0
PRODUCTVERSION 2,5,2,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "2.4.15.0"
VALUE "FileVersion", "2.5.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.4.15.0"
VALUE "ProductVersion", "2.5.2.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -182,7 +182,8 @@ void SendExistingDocument(
return MTP_inputMediaDocument(
MTP_flags(0),
document->mtpInput(),
MTPint());
MTPint(), // ttl_seconds
MTPstring()); // query
};
SendExistingMedia(
std::move(message),

View File

@@ -928,6 +928,9 @@ void Updates::handleSendActionUpdate(
const auto isSpeakingInCall = (action.type()
== mtpc_speakingInGroupCallAction);
if (isSpeakingInCall) {
if (!peer->isChat() && !peer->isChannel()) {
return;
}
const auto call = peer->groupCall();
const auto now = crl::now();
if (call) {
@@ -943,8 +946,8 @@ void Updates::handleSendActionUpdate(
: (channel->flags() & MTPDchannel::Flag::f_call_active);
if (active) {
_pendingSpeakingCallMembers.emplace(
channel).first->second[userId] = now;
session().api().requestFullPeer(channel);
peer).first->second[userId] = now;
session().api().requestFullPeer(peer);
}
}
}

View File

@@ -3458,7 +3458,8 @@ void ApiWrap::checkForUnreadMentions(
void ApiWrap::addChatParticipants(
not_null<PeerData*> peer,
const std::vector<not_null<UserData*>> &users) {
const std::vector<not_null<UserData*>> &users,
Fn<void(bool)> done) {
if (const auto chat = peer->asChat()) {
for (const auto user : users) {
request(MTPmessages_AddChatUser(
@@ -3467,8 +3468,10 @@ void ApiWrap::addChatParticipants(
MTP_int(kForwardMessagesOnAdd)
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
if (done) done(true);
}).fail([=](const RPCError &error) {
ShowAddParticipantsError(error.type(), peer, { 1, user });
if (done) done(false);
}).afterDelay(crl::time(5)).send();
}
} else if (const auto channel = peer->asChannel()) {
@@ -3480,14 +3483,17 @@ void ApiWrap::addChatParticipants(
auto list = QVector<MTPInputUser>();
list.reserve(qMin(int(users.size()), int(kMaxUsersPerInvite)));
const auto send = [&] {
const auto callback = base::take(done);
request(MTPchannels_InviteToChannel(
channel->inputChannel,
MTP_vector<MTPInputUser>(list)
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
requestParticipantsCountDelayed(channel);
if (callback) callback(true);
}).fail([=](const RPCError &error) {
ShowAddParticipantsError(error.type(), peer, users);
if (callback) callback(false);
}).afterDelay(crl::time(5)).send();
};
for (const auto user : users) {
@@ -4639,7 +4645,8 @@ void ApiWrap::uploadAlbumMedia(
fields.vid(),
fields.vaccess_hash(),
fields.vfile_reference()),
MTP_int(data.vttl_seconds().value_or_empty()));
MTP_int(data.vttl_seconds().value_or_empty()),
MTPstring()); // query
sendAlbumWithUploaded(item, groupId, media);
} break;
}

View File

@@ -366,7 +366,8 @@ public:
Fn<void()> callbackNotModified = nullptr);
void addChatParticipants(
not_null<PeerData*> peer,
const std::vector<not_null<UserData*>> &users);
const std::vector<not_null<UserData*>> &users,
Fn<void(bool)> done = nullptr);
rpl::producer<SendAction> sendActions() const {
return _sendActions.events();

View File

@@ -632,7 +632,7 @@ void GroupInfoBox::submit() {
not_null<PeerListBox*> box) {
auto create = [box, title, weak] {
if (weak) {
auto rows = box->peerListCollectSelectedRows();
auto rows = box->collectSelectedRows();
if (!rows.empty()) {
weak->createGroup(box, title, rows);
}
@@ -643,7 +643,8 @@ void GroupInfoBox::submit() {
};
Ui::show(
Box<PeerListBox>(
std::make_unique<AddParticipantsBoxController>(_navigation),
std::make_unique<AddParticipantsBoxController>(
&_navigation->session()),
std::move(initBox)),
Ui::LayerOption::KeepOther);
}

View File

@@ -801,7 +801,10 @@ void DeleteMessagesBox::resizeEvent(QResizeEvent *e) {
void DeleteMessagesBox::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
deleteAndClear();
// Don't make the clearing history so easy.
if (!_wipeHistoryPeer) {
deleteAndClear();
}
} else {
BoxContent::keyPressEvent(e);
}

View File

@@ -33,38 +33,36 @@ namespace {
class PrivacyExceptionsBoxController : public ChatsListBoxController {
public:
PrivacyExceptionsBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<Main::Session*> session,
rpl::producer<QString> title,
const std::vector<not_null<PeerData*>> &selected);
Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override;
std::vector<not_null<PeerData*>> getResult() const;
protected:
void prepareViewHook() override;
std::unique_ptr<Row> createRow(not_null<History*> history) override;
private:
not_null<Window::SessionNavigation*> _navigation;
const not_null<Main::Session*> _session;
rpl::producer<QString> _title;
std::vector<not_null<PeerData*>> _selected;
};
PrivacyExceptionsBoxController::PrivacyExceptionsBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<Main::Session*> session,
rpl::producer<QString> title,
const std::vector<not_null<PeerData*>> &selected)
: ChatsListBoxController(navigation)
, _navigation(navigation)
: ChatsListBoxController(session)
, _session(session)
, _title(std::move(title))
, _selected(selected) {
}
Main::Session &PrivacyExceptionsBoxController::session() const {
return _navigation->session();
return *_session;
}
void PrivacyExceptionsBoxController::prepareViewHook() {
@@ -72,10 +70,6 @@ void PrivacyExceptionsBoxController::prepareViewHook() {
delegate()->peerListAddSelectedPeers(_selected);
}
std::vector<not_null<PeerData*>> PrivacyExceptionsBoxController::getResult() const {
return delegate()->peerListCollectSelectedRows();
}
void PrivacyExceptionsBoxController::rowClicked(not_null<PeerListRow*> row) {
const auto peer = row->peer();
@@ -146,13 +140,13 @@ void EditPrivacyBox::editExceptions(
Exception exception,
Fn<void()> done) {
auto controller = std::make_unique<PrivacyExceptionsBoxController>(
_window,
&_window->session(),
_controller->exceptionBoxTitle(exception),
exceptions(exception));
auto initBox = [=, controller = controller.get()](
not_null<PeerListBox*> box) {
box->addButton(tr::lng_settings_save(), crl::guard(this, [=] {
exceptions(exception) = controller->getResult();
exceptions(exception) = box->collectSelectedRows();
const auto type = [&] {
switch (exception) {
case Exception::Always: return Exception::Never;

View File

@@ -320,7 +320,7 @@ void EditExceptions(
const auto include = (options & Flag::Contacts) != Flags(0);
const auto rules = data->current();
auto controller = std::make_unique<EditFilterChatsListController>(
window,
&window->session(),
(include
? tr::lng_filters_include_title()
: tr::lng_filters_exclude_title()),
@@ -331,7 +331,7 @@ void EditExceptions(
auto initBox = [=](not_null<PeerListBox*> box) {
box->setCloseByOutsideClick(false);
box->addButton(tr::lng_settings_save(), crl::guard(context, [=] {
const auto peers = box->peerListCollectSelectedRows();
const auto peers = box->collectSelectedRows();
const auto rules = data->current();
auto &&histories = ranges::view::all(
peers

View File

@@ -68,7 +68,6 @@ public:
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
int peerListSelectedRowsCount() override;
std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override;
void peerListScrollToTop() override;
void peerListAddSelectedPeerInBunch(
not_null<PeerData*> peer) override;
@@ -209,11 +208,6 @@ int TypeDelegate::peerListSelectedRowsCount() {
return 0;
}
auto TypeDelegate::peerListCollectSelectedRows()
-> std::vector<not_null<PeerData*>> {
return {};
}
void TypeDelegate::peerListScrollToTop() {
}
@@ -347,13 +341,13 @@ void PaintFilterChatsTypeIcon(
}
EditFilterChatsListController::EditFilterChatsListController(
not_null<Window::SessionNavigation*> navigation,
not_null<Main::Session*> session,
rpl::producer<QString> title,
Flags options,
Flags selected,
const base::flat_set<not_null<History*>> &peers)
: ChatsListBoxController(navigation)
, _navigation(navigation)
: ChatsListBoxController(session)
, _session(session)
, _title(std::move(title))
, _peers(peers)
, _options(options)
@@ -361,7 +355,7 @@ EditFilterChatsListController::EditFilterChatsListController(
}
Main::Session &EditFilterChatsListController::session() const {
return _navigation->session();
return *_session;
}
void EditFilterChatsListController::rowClicked(not_null<PeerListRow*> row) {

View File

@@ -41,7 +41,7 @@ public:
using Flags = Data::ChatFilter::Flags;
EditFilterChatsListController(
not_null<Window::SessionNavigation*> navigation,
not_null<Main::Session*> session,
rpl::producer<QString> title,
Flags options,
Flags selected,
@@ -64,7 +64,7 @@ private:
void updateTitle();
const not_null<Window::SessionNavigation*> _navigation;
const not_null<Main::Session*> _session;
rpl::producer<QString> _title;
base::flat_set<not_null<History*>> _peers;
Flags _options;

View File

@@ -399,7 +399,7 @@ int PeerListBox::peerListSelectedRowsCount() {
return _select ? _select->entity()->getItemsCount() : 0;
}
auto PeerListBox::peerListCollectSelectedRows()
auto PeerListBox::collectSelectedRows()
-> std::vector<not_null<PeerData*>> {
auto result = std::vector<not_null<PeerData*>>();
auto items = _select
@@ -982,6 +982,18 @@ void PeerListContent::setAboveWidget(object_ptr<TWidget> widget) {
}
}
void PeerListContent::setAboveSearchWidget(object_ptr<TWidget> widget) {
_aboveSearchWidget = std::move(widget);
if (_aboveSearchWidget) {
_aboveSearchWidget->setParent(this);
}
}
void PeerListContent::setHideEmpty(bool hide) {
_hideEmpty = hide;
resizeToWidth(width());
}
void PeerListContent::setBelowWidget(object_ptr<TWidget> widget) {
_belowWidget = std::move(widget);
if (_belowWidget) {
@@ -990,6 +1002,9 @@ void PeerListContent::setBelowWidget(object_ptr<TWidget> widget) {
}
int PeerListContent::labelHeight() const {
if (_hideEmpty && !shownRowsCount()) {
return 0;
}
auto computeLabelHeight = [](auto &label) {
if (!label) {
return 0;
@@ -1082,34 +1097,45 @@ void PeerListContent::paintEvent(QPaintEvent *e) {
}
int PeerListContent::resizeGetHeight(int newWidth) {
const auto rowsCount = shownRowsCount();
const auto hideAll = !rowsCount && _hideEmpty;
_aboveHeight = 0;
if (_aboveWidget) {
_aboveWidget->resizeToWidth(newWidth);
_aboveWidget->moveToLeft(0, 0, newWidth);
if (showingSearch()) {
if (hideAll || showingSearch()) {
_aboveWidget->hide();
} else {
_aboveWidget->show();
_aboveHeight = _aboveWidget->height();
}
}
const auto rowsCount = shownRowsCount();
if (_aboveSearchWidget) {
_aboveSearchWidget->resizeToWidth(newWidth);
_aboveSearchWidget->moveToLeft(0, 0, newWidth);
if (hideAll || !showingSearch()) {
_aboveSearchWidget->hide();
} else {
_aboveSearchWidget->show();
_aboveHeight = _aboveSearchWidget->height();
}
}
const auto labelTop = rowsTop() + qMax(1, shownRowsCount()) * _rowHeight;
const auto labelWidth = newWidth - 2 * st::contactsPadding.left();
if (_description) {
_description->resizeToWidth(labelWidth);
_description->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth);
_description->setVisible(!showingSearch());
_description->setVisible(!hideAll && !showingSearch());
}
if (_searchNoResults) {
_searchNoResults->resizeToWidth(labelWidth);
_searchNoResults->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth);
_searchNoResults->setVisible(showingSearch() && _filterResults.empty() && !_controller->isSearchLoading());
_searchNoResults->setVisible(!hideAll && showingSearch() && _filterResults.empty() && !_controller->isSearchLoading());
}
if (_searchLoading) {
_searchLoading->resizeToWidth(labelWidth);
_searchLoading->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth);
_searchLoading->setVisible(showingSearch() && _filterResults.empty() && _controller->isSearchLoading());
_searchLoading->setVisible(!hideAll && showingSearch() && _filterResults.empty() && _controller->isSearchLoading());
}
const auto label = labelHeight();
const auto belowTop = (label > 0 || rowsCount > 0)
@@ -1119,7 +1145,7 @@ int PeerListContent::resizeGetHeight(int newWidth) {
if (_belowWidget) {
_belowWidget->resizeToWidth(newWidth);
_belowWidget->moveToLeft(0, belowTop, newWidth);
if (showingSearch()) {
if (hideAll || showingSearch()) {
_belowWidget->hide();
} else {
_belowWidget->show();
@@ -1203,33 +1229,56 @@ void PeerListContent::mousePressReleased(Qt::MouseButton button) {
}
}
void PeerListContent::contextMenuEvent(QContextMenuEvent *e) {
void PeerListContent::showRowMenu(
not_null<PeerListRow*> row,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) {
showRowMenu(findRowIndex(row), QCursor::pos(), std::move(destroyed));
}
bool PeerListContent::showRowMenu(
RowIndex index,
QPoint globalPos,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) {
if (_contextMenu) {
_contextMenu->deleteLater();
_contextMenu->setDestroyedCallback(nullptr);
_contextMenu = nullptr;
}
setContexted(Selected());
if (e->reason() == QContextMenuEvent::Mouse) {
handleMouseMove(e->globalPos());
}
setContexted(_selected);
if (_pressButton != Qt::LeftButton) {
mousePressReleased(_pressButton);
}
if (const auto row = getRow(_contexted.index)) {
_contextMenu = _controller->rowContextMenu(this, row);
if (_contextMenu) {
_contextMenu->setDestroyedCallback(crl::guard(
this,
[this] {
setContexted(Selected());
handleMouseMove(QCursor::pos());
}));
_contextMenu->popup(e->globalPos());
e->accept();
}
const auto row = getRow(index);
if (!row) {
return false;
}
_contextMenu = _controller->rowContextMenu(this, row);
const auto raw = _contextMenu.get();
if (!raw) {
return false;
}
setContexted({ index, false });
raw->setDestroyedCallback(crl::guard(
this,
[=] {
setContexted(Selected());
handleMouseMove(QCursor::pos());
if (destroyed) {
destroyed(raw);
}
}));
raw->popup(globalPos);
return true;
}
void PeerListContent::contextMenuEvent(QContextMenuEvent *e) {
if (e->reason() == QContextMenuEvent::Mouse) {
handleMouseMove(e->globalPos());
}
if (showRowMenu(_selected.index, e->globalPos())) {
e->accept();
}
}
@@ -1351,15 +1400,18 @@ crl::time PeerListContent::paintRow(
return (refreshStatusAt - ms);
}
void PeerListContent::selectSkip(int direction) {
if (_pressed.index.value >= 0) {
return;
PeerListContent::SkipResult PeerListContent::selectSkip(int direction) {
if (hasPressed()) {
return { _selected.index.value, _selected.index.value };
}
_mouseSelection = false;
_lastMousePosition = std::nullopt;
auto newSelectedIndex = _selected.index.value + direction;
auto result = SkipResult();
result.shouldMoveTo = newSelectedIndex;
auto rowsCount = shownRowsCount();
auto index = 0;
auto firstEnabled = -1, lastEnabled = -1;
@@ -1415,14 +1467,36 @@ void PeerListContent::selectSkip(int direction) {
}
update();
_selectedIndex = _selected.index.value;
result.reallyMovedTo = _selected.index.value;
return result;
}
void PeerListContent::selectSkipPage(int height, int direction) {
auto rowsToSkip = height / _rowHeight;
if (!rowsToSkip) return;
if (!rowsToSkip) {
return;
}
selectSkip(rowsToSkip * direction);
}
rpl::producer<int> PeerListContent::selectedIndexValue() const {
return _selectedIndex.value();
}
bool PeerListContent::hasSelection() const {
return _selected.index.value >= 0;
}
bool PeerListContent::hasPressed() const {
return _pressed.index.value >= 0;
}
void PeerListContent::clearSelection() {
setSelected(Selected());
}
void PeerListContent::loadProfilePhotos() {
if (_visibleTop >= _visibleBottom) return;
@@ -1569,14 +1643,17 @@ void PeerListContent::setSearchQuery(
clearSearchRows();
}
void PeerListContent::submitted() {
bool PeerListContent::submitted() {
if (const auto row = getRow(_selected.index)) {
_controller->rowClicked(row);
return true;
} else if (showingSearch()) {
if (const auto row = getRow(RowIndex(0))) {
_controller->rowClicked(row);
return true;
}
}
return false;
}
void PeerListContent::visibleTopBottomUpdated(
@@ -1590,11 +1667,14 @@ void PeerListContent::visibleTopBottomUpdated(
void PeerListContent::setSelected(Selected selected) {
updateRow(_selected.index);
if (_selected != selected) {
_selected = selected;
updateRow(_selected.index);
setCursor(_selected.action ? style::cur_pointer : style::cur_default);
if (_selected == selected) {
return;
}
_selected = selected;
updateRow(_selected.index);
setCursor(_selected.action ? style::cur_pointer : style::cur_default);
_selectedIndex = _selected.index.value;
}
void PeerListContent::setContexted(Selected contexted) {

View File

@@ -254,10 +254,12 @@ class PeerListDelegate {
public:
virtual void peerListSetTitle(rpl::producer<QString> title) = 0;
virtual void peerListSetAdditionalTitle(rpl::producer<QString> title) = 0;
virtual void peerListSetHideEmpty(bool hide) = 0;
virtual void peerListSetDescription(object_ptr<Ui::FlatLabel> description) = 0;
virtual void peerListSetSearchLoading(object_ptr<Ui::FlatLabel> loading) = 0;
virtual void peerListSetSearchNoResults(object_ptr<Ui::FlatLabel> noResults) = 0;
virtual void peerListSetAboveWidget(object_ptr<TWidget> aboveWidget) = 0;
virtual void peerListSetAboveSearchWidget(object_ptr<TWidget> aboveWidget) = 0;
virtual void peerListSetBelowWidget(object_ptr<TWidget> belowWidget) = 0;
virtual void peerListSetSearchMode(PeerListSearchMode mode) = 0;
virtual void peerListAppendRow(std::unique_ptr<PeerListRow> row) = 0;
@@ -298,8 +300,10 @@ public:
peerListFinishSelectedRowsBunch();
}
virtual void peerListShowRowMenu(
not_null<PeerListRow*> row,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) = 0;
virtual int peerListSelectedRowsCount() = 0;
virtual std::vector<not_null<PeerData*>> peerListCollectSelectedRows() = 0;
virtual std::unique_ptr<PeerListState> peerListSaveState() const = 0;
virtual void peerListRestoreState(
std::unique_ptr<PeerListState> state) = 0;
@@ -499,13 +503,20 @@ public:
QWidget *parent,
not_null<PeerListController*> controller);
void selectSkip(int direction);
struct SkipResult {
int shouldMoveTo = 0;
int reallyMovedTo = 0;
};
SkipResult selectSkip(int direction);
void selectSkipPage(int height, int direction);
[[nodiscard]] rpl::producer<int> selectedIndexValue() const;
[[nodiscard]] bool hasSelection() const;
[[nodiscard]] bool hasPressed() const;
void clearSelection();
void searchQueryChanged(QString query);
void submitted();
bool submitted();
// Interface for the controller.
void appendRow(std::unique_ptr<PeerListRow> row);
@@ -525,7 +536,9 @@ public:
void setSearchLoading(object_ptr<Ui::FlatLabel> loading);
void setSearchNoResults(object_ptr<Ui::FlatLabel> noResults);
void setAboveWidget(object_ptr<TWidget> widget);
void setAboveSearchWidget(object_ptr<TWidget> widget);
void setBelowWidget(object_ptr<TWidget> width);
void setHideEmpty(bool hide);
void refreshRows();
void setSearchMode(PeerListSearchMode mode);
@@ -547,6 +560,10 @@ public:
std::unique_ptr<PeerListState> saveState() const;
void restoreState(std::unique_ptr<PeerListState> state);
void showRowMenu(
not_null<PeerListRow*> row,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed);
auto scrollToRequests() const {
return _scrollToRequests.events();
}
@@ -630,6 +647,11 @@ private:
RowIndex findRowIndex(not_null<PeerListRow*> row, RowIndex hint = RowIndex());
QRect getActiveActionRect(not_null<PeerListRow*> row, RowIndex index) const;
bool showRowMenu(
RowIndex index,
QPoint globalPos,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr);
crl::time paintRow(Painter &p, crl::time ms, RowIndex index);
void addRowEntry(not_null<PeerListRow*> row);
@@ -667,6 +689,7 @@ private:
Selected _selected;
Selected _pressed;
Selected _contexted;
rpl::variable<int> _selectedIndex = -1;
bool _mouseSelection = false;
std::optional<QPoint> _lastMousePosition;
Qt::MouseButton _pressButton = Qt::LeftButton;
@@ -685,7 +708,9 @@ private:
int _aboveHeight = 0;
int _belowHeight = 0;
bool _hideEmpty = false;
object_ptr<TWidget> _aboveWidget = { nullptr };
object_ptr<TWidget> _aboveSearchWidget = { nullptr };
object_ptr<TWidget> _belowWidget = { nullptr };
object_ptr<Ui::FlatLabel> _description = { nullptr };
object_ptr<Ui::FlatLabel> _searchNoResults = { nullptr };
@@ -703,6 +728,9 @@ public:
_content = content;
}
void peerListSetHideEmpty(bool hide) override {
_content->setHideEmpty(hide);
}
void peerListAppendRow(
std::unique_ptr<PeerListRow> row) override {
_content->appendRow(std::move(row));
@@ -767,6 +795,9 @@ public:
void peerListSetAboveWidget(object_ptr<TWidget> aboveWidget) override {
_content->setAboveWidget(std::move(aboveWidget));
}
void peerListSetAboveSearchWidget(object_ptr<TWidget> aboveWidget) override {
_content->setAboveSearchWidget(std::move(aboveWidget));
}
void peerListSetBelowWidget(object_ptr<TWidget> belowWidget) override {
_content->setBelowWidget(std::move(belowWidget));
}
@@ -804,6 +835,11 @@ public:
std::unique_ptr<PeerListState> state) override {
_content->restoreState(std::move(state));
}
void peerListShowRowMenu(
not_null<PeerListRow*> row,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) override {
_content->showRowMenu(row, std::move(destroyed));
}
protected:
not_null<PeerListContent*> content() const {
@@ -824,6 +860,8 @@ public:
std::unique_ptr<PeerListController> controller,
Fn<void(not_null<PeerListBox*>)> init);
[[nodiscard]] std::vector<not_null<PeerData*>> collectSelectedRows();
void peerListSetTitle(rpl::producer<QString> title) override {
setTitle(std::move(title));
}
@@ -840,7 +878,6 @@ public:
anim::type animated) override;
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
int peerListSelectedRowsCount() override;
std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override;
void peerListScrollToTop() override;
protected:

View File

@@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "history/history.h"
#include "dialogs/dialogs_main_list.h"
#include "window/window_session_controller.h"
#include "window/window_session_controller.h" // onShowAddContact()
#include "facades.h"
#include "styles/style_boxes.h"
#include "styles/style_profile.h"
@@ -115,7 +115,8 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
[=] { controller->widget()->onShowAddContact(); });
};
return Box<PeerListBox>(
std::make_unique<ContactsBoxController>(controller),
std::make_unique<ContactsBoxController>(
&sessionController->session()),
std::move(delegate));
}
@@ -159,9 +160,9 @@ void PeerListRowWithLink::paintAction(
}
PeerListGlobalSearchController::PeerListGlobalSearchController(
not_null<Window::SessionNavigation*> navigation)
: _navigation(navigation)
, _api(&_navigation->session().mtp()) {
not_null<Main::Session*> session)
: _session(session)
, _api(&session->mtp()) {
_timer.setCallback([this] { searchOnServer(); });
}
@@ -210,8 +211,8 @@ void PeerListGlobalSearchController::searchDone(
auto &contacts = result.c_contacts_found();
auto query = _query;
if (requestId) {
_navigation->session().data().processUsers(contacts.vusers());
_navigation->session().data().processChats(contacts.vchats());
_session->data().processUsers(contacts.vusers());
_session->data().processChats(contacts.vchats());
auto it = _queries.find(requestId);
if (it != _queries.cend()) {
query = it->second;
@@ -221,7 +222,7 @@ void PeerListGlobalSearchController::searchDone(
}
const auto feedList = [&](const MTPVector<MTPPeer> &list) {
for (const auto &mtpPeer : list.v) {
const auto peer = _navigation->session().data().peerLoaded(
const auto peer = _session->data().peerLoaded(
peerFromMTP(mtpPeer));
if (peer) {
delegate()->peerListSearchAddRow(peer);
@@ -246,9 +247,9 @@ ChatsListBoxController::Row::Row(not_null<History*> history)
}
ChatsListBoxController::ChatsListBoxController(
not_null<Window::SessionNavigation*> navigation)
not_null<Main::Session*> session)
: ChatsListBoxController(
std::make_unique<PeerListGlobalSearchController>(navigation)) {
std::make_unique<PeerListGlobalSearchController>(session)) {
}
ChatsListBoxController::ChatsListBoxController(
@@ -354,21 +355,21 @@ bool ChatsListBoxController::appendRow(not_null<History*> history) {
}
ContactsBoxController::ContactsBoxController(
not_null<Window::SessionNavigation*> navigation)
: PeerListController(
std::make_unique<PeerListGlobalSearchController>(navigation))
, _navigation(navigation) {
not_null<Main::Session*> session)
: ContactsBoxController(
session,
std::make_unique<PeerListGlobalSearchController>(session)) {
}
ContactsBoxController::ContactsBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<Main::Session*> session,
std::unique_ptr<PeerListSearchController> searchController)
: PeerListController(std::move(searchController))
, _navigation(navigation) {
, _session(session) {
}
Main::Session &ContactsBoxController::session() const {
return _navigation->session();
return *_session;
}
void ContactsBoxController::prepare() {
@@ -435,26 +436,24 @@ bool ContactsBoxController::appendRow(not_null<UserData*> user) {
return false;
}
std::unique_ptr<PeerListRow> ContactsBoxController::createRow(not_null<UserData*> user) {
std::unique_ptr<PeerListRow> ContactsBoxController::createRow(
not_null<UserData*> user) {
return std::make_unique<PeerListRow>(user);
}
void AddBotToGroupBoxController::Start(
not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> bot) {
void AddBotToGroupBoxController::Start(not_null<UserData*> bot) {
auto initBox = [=](not_null<PeerListBox*> box) {
box->addButton(tr::lng_cancel(), [box] { box->closeBox(); });
};
Ui::show(Box<PeerListBox>(
std::make_unique<AddBotToGroupBoxController>(navigation, bot),
std::make_unique<AddBotToGroupBoxController>(bot),
std::move(initBox)));
}
AddBotToGroupBoxController::AddBotToGroupBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> bot)
: ChatsListBoxController(SharingBotGame(bot)
? std::make_unique<PeerListGlobalSearchController>(navigation)
? std::make_unique<PeerListGlobalSearchController>(&bot->session())
: nullptr)
, _bot(bot) {
}
@@ -572,15 +571,15 @@ void AddBotToGroupBoxController::prepareViewHook() {
}
ChooseRecipientBoxController::ChooseRecipientBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<Main::Session*> session,
FnMut<void(not_null<PeerData*>)> callback)
: ChatsListBoxController(navigation)
, _navigation(navigation)
: ChatsListBoxController(session)
, _session(session)
, _callback(std::move(callback)) {
}
Main::Session &ChooseRecipientBoxController::session() const {
return _navigation->session();
return *_session;
}
void ChooseRecipientBoxController::prepareViewHook() {

View File

@@ -32,7 +32,6 @@ class History;
namespace Window {
class SessionController;
class SessionNavigation;
} // namespace Window
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareContactsBox(
@@ -65,8 +64,7 @@ private:
class PeerListGlobalSearchController : public PeerListSearchController {
public:
PeerListGlobalSearchController(
not_null<Window::SessionNavigation*> navigation);
explicit PeerListGlobalSearchController(not_null<Main::Session*> session);
void searchQuery(const QString &query) override;
bool isLoading() override;
@@ -79,7 +77,7 @@ private:
void searchOnServer();
void searchDone(const MTPcontacts_Found &result, mtpRequestId requestId);
const not_null<Window::SessionNavigation*> _navigation;
const not_null<Main::Session*> _session;
MTP::Sender _api;
base::Timer _timer;
QString _query;
@@ -104,7 +102,7 @@ public:
};
ChatsListBoxController(not_null<Window::SessionNavigation*> navigation);
ChatsListBoxController(not_null<Main::Session*> session);
ChatsListBoxController(
std::unique_ptr<PeerListSearchController> searchController);
@@ -127,15 +125,15 @@ private:
class ContactsBoxController : public PeerListController {
public:
explicit ContactsBoxController(not_null<Main::Session*> session);
ContactsBoxController(
not_null<Window::SessionNavigation*> navigation);
ContactsBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<Main::Session*> session,
std::unique_ptr<PeerListSearchController> searchController);
Main::Session &session() const override;
[[nodiscard]] Main::Session &session() const override;
void prepare() override final;
std::unique_ptr<PeerListRow> createSearchRow(not_null<PeerData*> peer) override final;
[[nodiscard]] std::unique_ptr<PeerListRow> createSearchRow(
not_null<PeerData*> peer) override final;
void rowClicked(not_null<PeerListRow*> row) override;
protected:
@@ -150,7 +148,7 @@ private:
void checkForEmptyRows();
bool appendRow(not_null<UserData*> user);
const not_null<Window::SessionNavigation*> _navigation;
const not_null<Main::Session*> _session;
};
@@ -158,13 +156,9 @@ class AddBotToGroupBoxController
: public ChatsListBoxController
, public base::has_weak_ptr {
public:
static void Start(
not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> bot);
static void Start(not_null<UserData*> bot);
AddBotToGroupBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> bot);
explicit AddBotToGroupBoxController(not_null<UserData*> bot);
Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override;
@@ -186,7 +180,7 @@ private:
void shareBotGame(not_null<PeerData*> chat);
void addBotToGroup(not_null<PeerData*> chat);
not_null<UserData*> _bot;
const not_null<UserData*> _bot;
};
@@ -195,7 +189,7 @@ class ChooseRecipientBoxController
, public base::has_weak_ptr {
public:
ChooseRecipientBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<Main::Session*> session,
FnMut<void(not_null<PeerData*>)> callback);
Main::Session &session() const override;
@@ -210,7 +204,7 @@ protected:
std::unique_ptr<Row> createRow(not_null<History*> history) override;
private:
const not_null<Window::SessionNavigation*> _navigation;
const not_null<Main::Session*> _session;
FnMut<void(not_null<PeerData*>)> _callback;
};

View File

@@ -0,0 +1,429 @@
/*
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 "boxes/peer_lists_box.h"
#include "lang/lang_keys.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/widgets/multi_select.h"
#include "ui/widgets/scroll_area.h"
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_peer.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
PeerListsBox::PeerListsBox(
QWidget*,
std::vector<std::unique_ptr<PeerListController>> controllers,
Fn<void(not_null<PeerListsBox*>)> init)
: _lists(makeLists(std::move(controllers)))
, _init(std::move(init)) {
Expects(!_lists.empty());
}
auto PeerListsBox::collectSelectedRows()
-> std::vector<not_null<PeerData*>> {
auto result = std::vector<not_null<PeerData*>>();
auto items = _select
? _select->entity()->getItems()
: QVector<uint64>();
if (!items.empty()) {
result.reserve(items.size());
const auto session = &firstController()->session();
for (const auto itemId : items) {
const auto foreign = [&] {
for (const auto &list : _lists) {
if (list.controller->isForeignRow(itemId)) {
return true;
}
}
return false;
}();
if (!foreign) {
result.push_back(session->data().peer(itemId));
}
}
}
return result;
}
PeerListsBox::List PeerListsBox::makeList(
std::unique_ptr<PeerListController> controller) {
auto delegate = std::make_unique<Delegate>(this, controller.get());
return {
std::move(controller),
std::move(delegate),
};
}
std::vector<PeerListsBox::List> PeerListsBox::makeLists(
std::vector<std::unique_ptr<PeerListController>> controllers) {
auto result = std::vector<List>();
result.reserve(controllers.size());
for (auto &controller : controllers) {
result.push_back(makeList(std::move(controller)));
}
return result;
}
not_null<PeerListController*> PeerListsBox::firstController() const {
return _lists.front().controller.get();
}
void PeerListsBox::createMultiSelect() {
Expects(_select == nullptr);
auto entity = object_ptr<Ui::MultiSelect>(
this,
(firstController()->selectSt()
? *firstController()->selectSt()
: st::defaultMultiSelect),
tr::lng_participant_filter());
_select.create(this, std::move(entity));
_select->heightValue(
) | rpl::start_with_next(
[this] { updateScrollSkips(); },
lifetime());
_select->entity()->setSubmittedCallback([=](Qt::KeyboardModifiers) {
for (const auto &list : _lists) {
if (list.content->submitted()) {
break;
}
}
});
_select->entity()->setQueryChangedCallback([=](const QString &query) {
searchQueryChanged(query);
});
_select->entity()->setItemRemovedCallback([=](uint64 itemId) {
for (const auto &list : _lists) {
if (list.controller->handleDeselectForeignRow(itemId)) {
return;
}
}
const auto session = &firstController()->session();
if (const auto peer = session->data().peerLoaded(itemId)) {
const auto id = peer->id;
for (const auto &list : _lists) {
if (const auto row = list.delegate->peerListFindRow(id)) {
list.content->changeCheckState(
row,
false,
anim::type::normal);
update();
}
list.controller->itemDeselectedHook(peer);
}
}
});
_select->resizeToWidth(firstController()->contentWidth());
_select->moveToLeft(0, 0);
}
int PeerListsBox::getTopScrollSkip() const {
auto result = 0;
if (_select && !_select->isHidden()) {
result += _select->height();
}
return result;
}
void PeerListsBox::updateScrollSkips() {
// If we show / hide the search field scroll top is fixed.
// If we resize search field by bubbles scroll bottom is fixed.
setInnerTopSkip(getTopScrollSkip(), _scrollBottomFixed);
if (!_select->animating()) {
_scrollBottomFixed = true;
}
}
void PeerListsBox::prepare() {
auto rows = setInnerWidget(
object_ptr<Ui::VerticalLayout>(this),
st::boxScroll);
for (auto &list : _lists) {
const auto content = rows->add(object_ptr<PeerListContent>(
rows,
list.controller.get()));
list.content = content;
list.delegate->setContent(content);
list.controller->setDelegate(list.delegate.get());
content->scrollToRequests(
) | rpl::start_with_next([=](Ui::ScrollToRequest request) {
const auto skip = content->y();
onScrollToY(
skip + request.ymin,
(request.ymax >= 0) ? (skip + request.ymax) : request.ymax);
}, lifetime());
content->selectedIndexValue(
) | rpl::filter([=](int index) {
return (index >= 0);
}) | rpl::start_with_next([=] {
for (const auto &list : _lists) {
if (list.content && list.content != content) {
list.content->clearSelection();
}
}
}, lifetime());
}
rows->resizeToWidth(firstController()->contentWidth());
setDimensions(firstController()->contentWidth(), st::boxMaxListHeight);
if (_select) {
_select->finishAnimating();
Ui::SendPendingMoveResizeEvents(_select);
_scrollBottomFixed = true;
onScrollToY(0);
}
if (_init) {
_init(this);
}
}
void PeerListsBox::keyPressEvent(QKeyEvent *e) {
const auto skipRows = [&](int rows) {
if (rows == 0) {
return;
}
for (const auto &list : _lists) {
if (list.content->hasPressed()) {
return;
}
}
const auto from = begin(_lists), till = end(_lists);
auto i = from;
for (; i != till; ++i) {
if (i->content->hasSelection()) {
break;
}
}
if (i == till && rows < 0) {
return;
}
if (rows > 0) {
if (i == till) {
i = from;
}
for (; i != till; ++i) {
const auto result = i->content->selectSkip(rows);
if (result.shouldMoveTo - result.reallyMovedTo >= rows) {
continue;
} else if (result.reallyMovedTo >= result.shouldMoveTo) {
return;
} else {
rows = result.shouldMoveTo - result.reallyMovedTo;
}
}
} else {
for (++i; i != from;) {
const auto result = (--i)->content->selectSkip(rows);
if (result.shouldMoveTo - result.reallyMovedTo <= rows) {
continue;
} else if (result.reallyMovedTo <= result.shouldMoveTo) {
return;
} else {
rows = result.shouldMoveTo - result.reallyMovedTo;
}
}
}
};
const auto rowsInPage = [&] {
const auto rowHeight = firstController()->computeListSt().item.height;
return height() / rowHeight;
};
if (e->key() == Qt::Key_Down) {
skipRows(1);
} else if (e->key() == Qt::Key_Up) {
skipRows(-1);
} else if (e->key() == Qt::Key_PageDown) {
skipRows(rowsInPage());
} else if (e->key() == Qt::Key_PageUp) {
skipRows(-rowsInPage());
} else if (e->key() == Qt::Key_Escape && _select && !_select->entity()->getQuery().isEmpty()) {
_select->entity()->clearQuery();
} else {
BoxContent::keyPressEvent(e);
}
}
void PeerListsBox::searchQueryChanged(const QString &query) {
onScrollToY(0);
for (const auto &list : _lists) {
list.content->searchQueryChanged(query);
}
}
void PeerListsBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
if (_select) {
_select->resizeToWidth(width());
_select->moveToLeft(0, 0);
updateScrollSkips();
}
for (const auto &list : _lists) {
list.content->resizeToWidth(width());
}
}
void PeerListsBox::paintEvent(QPaintEvent *e) {
Painter p(this);
const auto &bg = (firstController()->listSt()
? *firstController()->listSt()
: st::peerListBox).bg;
for (const auto rect : e->region()) {
p.fillRect(rect, bg);
}
}
void PeerListsBox::setInnerFocus() {
if (!_select || !_select->toggled()) {
_lists.front().content->setFocus();
} else {
_select->entity()->setInnerFocus();
}
}
PeerListsBox::Delegate::Delegate(
not_null<PeerListsBox*> box,
not_null<PeerListController*> controller)
: _box(box)
, _controller(controller) {
}
void PeerListsBox::Delegate::peerListSetTitle(rpl::producer<QString> title) {
}
void PeerListsBox::Delegate::peerListSetAdditionalTitle(
rpl::producer<QString> title) {
}
void PeerListsBox::Delegate::peerListSetRowChecked(
not_null<PeerListRow*> row,
bool checked) {
if (checked) {
_box->addSelectItem(row, anim::type::normal);
PeerListContentDelegate::peerListSetRowChecked(row, checked);
peerListUpdateRow(row);
// This call deletes row from _searchRows.
_box->_select->entity()->clearQuery();
} else {
// The itemRemovedCallback will call changeCheckState() here.
_box->_select->entity()->removeItem(row->id());
peerListUpdateRow(row);
}
}
void PeerListsBox::Delegate::peerListSetForeignRowChecked(
not_null<PeerListRow*> row,
bool checked,
anim::type animated) {
if (checked) {
_box->addSelectItem(row, animated);
// This call deletes row from _searchRows.
_box->_select->entity()->clearQuery();
} else {
// The itemRemovedCallback will call changeCheckState() here.
_box->_select->entity()->removeItem(row->id());
}
}
void PeerListsBox::Delegate::peerListScrollToTop() {
_box->onScrollToY(0);
}
void PeerListsBox::Delegate::peerListSetSearchMode(PeerListSearchMode mode) {
PeerListContentDelegate::peerListSetSearchMode(mode);
_box->setSearchMode(mode);
}
void PeerListsBox::setSearchMode(PeerListSearchMode mode) {
auto selectVisible = (mode != PeerListSearchMode::Disabled);
if (selectVisible && !_select) {
createMultiSelect();
_select->toggle(!selectVisible, anim::type::instant);
}
if (_select) {
_select->toggle(selectVisible, anim::type::normal);
_scrollBottomFixed = false;
setInnerFocus();
}
}
void PeerListsBox::Delegate::peerListFinishSelectedRowsBunch() {
Expects(_box->_select != nullptr);
_box->_select->entity()->finishItemsBunch();
}
bool PeerListsBox::Delegate::peerListIsRowChecked(
not_null<PeerListRow*> row) {
return _box->_select
? _box->_select->entity()->hasItem(row->id())
: false;
}
int PeerListsBox::Delegate::peerListSelectedRowsCount() {
return _box->_select ? _box->_select->entity()->getItemsCount() : 0;
}
void PeerListsBox::addSelectItem(
not_null<PeerData*> peer,
anim::type animated) {
addSelectItem(
peer->id,
peer->shortName(),
PaintUserpicCallback(peer, false),
animated);
}
void PeerListsBox::addSelectItem(
not_null<PeerListRow*> row,
anim::type animated) {
addSelectItem(
row->id(),
row->generateShortName(),
row->generatePaintUserpicCallback(),
animated);
}
void PeerListsBox::addSelectItem(
uint64 itemId,
const QString &text,
Ui::MultiSelect::PaintRoundImage paintUserpic,
anim::type animated) {
if (!_select) {
createMultiSelect();
_select->hide(anim::type::instant);
}
const auto &activeBg = (firstController()->selectSt()
? *firstController()->selectSt()
: st::defaultMultiSelect).item.textActiveBg;
if (animated == anim::type::instant) {
_select->entity()->addItemInBunch(
itemId,
text,
activeBg,
std::move(paintUserpic));
} else {
_select->entity()->addItem(
itemId,
text,
activeBg,
std::move(paintUserpic));
}
}

View File

@@ -0,0 +1,101 @@
/*
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/peer_list_box.h"
class PeerListsBox : public Ui::BoxContent {
public:
PeerListsBox(
QWidget*,
std::vector<std::unique_ptr<PeerListController>> controllers,
Fn<void(not_null<PeerListsBox*>)> init);
[[nodiscard]] std::vector<not_null<PeerData*>> collectSelectedRows();
protected:
void prepare() override;
void setInnerFocus() override;
void keyPressEvent(QKeyEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override;
private:
class Delegate final : public PeerListContentDelegate {
public:
Delegate(
not_null<PeerListsBox*> box,
not_null<PeerListController*> controller);
void peerListSetTitle(rpl::producer<QString> title) override;
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
void peerListSetSearchMode(PeerListSearchMode mode) override;
void peerListSetRowChecked(
not_null<PeerListRow*> row,
bool checked) override;
void peerListSetForeignRowChecked(
not_null<PeerListRow*> row,
bool checked,
anim::type animated) override;
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
int peerListSelectedRowsCount() override;
void peerListScrollToTop() override;
void peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) override {
_box->addSelectItem(peer, anim::type::instant);
}
void peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) override {
_box->addSelectItem(row, anim::type::instant);
}
void peerListFinishSelectedRowsBunch() override;
private:
const not_null<PeerListsBox*> _box;
const not_null<PeerListController*> _controller;
};
struct List {
std::unique_ptr<PeerListController> controller;
std::unique_ptr<Delegate> delegate;
PeerListContent *content = nullptr;
};
friend class Delegate;
[[nodiscard]] List makeList(
std::unique_ptr<PeerListController> controller);
[[nodiscard]] std::vector<List> makeLists(
std::vector<std::unique_ptr<PeerListController>> controllers);
[[nodiscard]] not_null<PeerListController*> firstController() const;
void addSelectItem(
not_null<PeerData*> peer,
anim::type animated);
void addSelectItem(
not_null<PeerListRow*> row,
anim::type animated);
void addSelectItem(
uint64 itemId,
const QString &text,
PaintRoundImageCallback paintUserpic,
anim::type animated);
void setSearchMode(PeerListSearchMode mode);
void createMultiSelect();
int getTopScrollSkip() const;
void updateScrollSkips();
void searchQueryChanged(const QString &query);
object_ptr<Ui::SlideWrap<Ui::MultiSelect>> _select = { nullptr };
std::vector<List> _lists;
Fn<void(PeerListsBox*)> _init;
bool _scrollBottomFixed = false;
};

View File

@@ -51,28 +51,21 @@ base::flat_set<not_null<UserData*>> GetAlreadyInFromPeer(PeerData *peer) {
} // namespace
AddParticipantsBoxController::AddParticipantsBoxController(
not_null<Window::SessionNavigation*> navigation)
: ContactsBoxController(
navigation,
std::make_unique<PeerListGlobalSearchController>(navigation)) {
not_null<Main::Session*> session)
: ContactsBoxController(session) {
}
AddParticipantsBoxController::AddParticipantsBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer)
: AddParticipantsBoxController(
navigation,
peer,
GetAlreadyInFromPeer(peer)) {
}
AddParticipantsBoxController::AddParticipantsBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
base::flat_set<not_null<UserData*>> &&alreadyIn)
: ContactsBoxController(
navigation,
std::make_unique<PeerListGlobalSearchController>(navigation))
: ContactsBoxController(&peer->session())
, _peer(peer)
, _alreadyIn(std::move(alreadyIn)) {
subscribeToMigration();
@@ -179,7 +172,7 @@ bool AddParticipantsBoxController::inviteSelectedUsers(
not_null<PeerListBox*> box) const {
Expects(_peer != nullptr);
const auto rows = box->peerListCollectSelectedRows();
const auto rows = box->collectSelectedRows();
const auto users = ranges::view::all(
rows
) | ranges::view::transform([](not_null<PeerData*> peer) {
@@ -198,9 +191,7 @@ bool AddParticipantsBoxController::inviteSelectedUsers(
void AddParticipantsBoxController::Start(
not_null<Window::SessionNavigation*> navigation,
not_null<ChatData*> chat) {
auto controller = std::make_unique<AddParticipantsBoxController>(
navigation,
chat);
auto controller = std::make_unique<AddParticipantsBoxController>(chat);
const auto weak = controller.get();
auto initBox = [=](not_null<PeerListBox*> box) {
box->addButton(tr::lng_participant_invite(), [=] {
@@ -223,7 +214,6 @@ void AddParticipantsBoxController::Start(
base::flat_set<not_null<UserData*>> &&alreadyIn,
bool justCreated) {
auto controller = std::make_unique<AddParticipantsBoxController>(
navigation,
channel,
std::move(alreadyIn));
const auto weak = controller.get();

View File

@@ -27,16 +27,16 @@ public:
not_null<ChannelData*> channel,
base::flat_set<not_null<UserData*>> &&alreadyIn);
explicit AddParticipantsBoxController(
not_null<Window::SessionNavigation*> navigation);
explicit AddParticipantsBoxController(not_null<Main::Session*> session);
explicit AddParticipantsBoxController(not_null<PeerData*> peer);
AddParticipantsBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer);
AddParticipantsBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
base::flat_set<not_null<UserData*>> &&alreadyIn);
[[nodiscard]] not_null<PeerData*> peer() const {
return _peer;
}
void rowClicked(not_null<PeerListRow*> row) override;
void itemDeselectedHook(not_null<PeerData*> peer) override;

View File

@@ -9,6 +9,7 @@ using "ui/basic.style";
using "ui/widgets/widgets.style";
using "ui/layers/layers.style";
using "ui/chat/chat.style"; // GroupCallUserpics
using "window/window.style";
CallSignalBars {
@@ -477,11 +478,17 @@ groupCallMembersListItem: PeerListItem(defaultPeerListItem) {
groupCallMembersList: PeerList(defaultPeerList) {
bg: groupCallMembersBg;
about: FlatLabel(defaultPeerListAbout) {
textFg: groupCallMemberInactiveStatus;
textFg: groupCallMemberNotJoinedStatus;
}
item: groupCallMembersListItem;
}
groupCallInviteDividerLabel: FlatLabel(defaultFlatLabel) {
textFg: groupCallMemberNotJoinedStatus;
}
groupCallInviteDividerPadding: margins(17px, 7px, 17px, 7px);
groupCallInviteMembersList: PeerList(groupCallMembersList) {
padding: margins(0px, 10px, 0px, 10px);
item: PeerListItem(groupCallMembersListItem) {
statusFg: groupCallMemberNotJoinedStatus;
statusFgOver: groupCallMemberNotJoinedStatus;
@@ -599,9 +606,12 @@ groupCallButtonSkip: 43px;
groupCallButtonBottomSkip: 145px;
groupCallMuteBottomSkip: 160px;
groupCallTopBarUserpicSize: 28px;
groupCallTopBarUserpicShift: 8px;
groupCallTopBarUserpicStroke: 2px;
groupCallTopBarUserpics: GroupCallUserpics {
size: 28px;
shift: 8px;
stroke: 2px;
align: align(left);
}
groupCallTopBarJoin: RoundButton(defaultActiveButton) {
width: -26px;
height: 26px;

View File

@@ -368,10 +368,10 @@ void Call::setupOutgoingVideo() {
_errors.fire({ ErrorType::NoCamera });
_videoOutgoing->setState(Webrtc::VideoState::Inactive);
} else if (_state.current() != State::Established
&& state != started
&& !_videoCapture) {
&& (state != Webrtc::VideoState::Inactive)
&& (started == Webrtc::VideoState::Inactive)) {
_errors.fire({ ErrorType::NotStartedCall });
_videoOutgoing->setState(started);
_videoOutgoing->setState(Webrtc::VideoState::Inactive);
} else if (state != Webrtc::VideoState::Inactive
&& _instance
&& !_instance->supportsVideo()) {

View File

@@ -243,6 +243,9 @@ private:
void prepareRows(not_null<Data::GroupCall*> real);
//void repaintByTimer();
[[nodiscard]] base::unique_qptr<Ui::PopupMenu> createRowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row);
void setupListChangeViewers(not_null<GroupCall*> call);
void subscribeToChanges(not_null<Data::GroupCall*> real);
void updateRow(
@@ -638,10 +641,7 @@ MembersController::MembersController(
}
MembersController::~MembersController() {
if (_menu) {
_menu->setDestroyedCallback(nullptr);
_menu = nullptr;
}
base::take(_menu);
}
void MembersController::setupListChangeViewers(not_null<GroupCall*> call) {
@@ -750,7 +750,21 @@ void MembersController::updateRow(
if (row->speaking()) {
delegate()->peerListPrependRow(std::move(row));
} else {
static constexpr auto kInvited = Row::State::Invited;
const auto reorder = [&] {
const auto count = delegate()->peerListFullRowsCount();
if (!count) {
return false;
}
const auto row = delegate()->peerListRowAt(count - 1).get();
return (static_cast<Row*>(row)->state() == kInvited);
}();
delegate()->peerListAppendRow(std::move(row));
if (reorder) {
delegate()->peerListPartitionRows([](const PeerListRow &row) {
return static_cast<const Row&>(row).state() != kInvited;
});
}
}
delegate()->peerListRefreshRows();
}
@@ -1002,29 +1016,20 @@ auto MembersController::kickMemberRequests() const
}
void MembersController::rowClicked(not_null<PeerListRow*> row) {
if (_menu) {
_menu->setDestroyedCallback(nullptr);
_menu->deleteLater();
_menu = nullptr;
}
_menu = rowContextMenu(_menuParent, row);
if (const auto raw = _menu.get()) {
raw->setDestroyedCallback([=] {
if (_menu && _menu.get() != raw) {
return;
}
auto saved = base::take(_menu);
for (const auto peer : base::take(_menuCheckRowsAfterHidden)) {
if (const auto row = findRow(peer->asUser())) {
if (row->speaking()) {
checkSpeakingRowPosition(row);
}
delegate()->peerListShowRowMenu(row, [=](not_null<Ui::PopupMenu*> menu) {
if (!_menu || _menu.get() != menu) {
return;
}
auto saved = base::take(_menu);
for (const auto peer : base::take(_menuCheckRowsAfterHidden)) {
if (const auto row = findRow(peer->asUser())) {
if (row->speaking()) {
checkSpeakingRowPosition(row);
}
}
_menu = std::move(saved);
});
raw->popup(QCursor::pos());
}
}
_menu = std::move(saved);
});
}
void MembersController::rowActionClicked(
@@ -1035,6 +1040,23 @@ void MembersController::rowActionClicked(
base::unique_qptr<Ui::PopupMenu> MembersController::rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) {
auto result = createRowContextMenu(parent, row);
if (result) {
// First clear _menu value, so that we don't check row positions yet.
base::take(_menu);
// Here unique_qptr is used like a shared pointer, where
// not the last destroyed pointer destroys the object, but the first.
_menu = base::unique_qptr<Ui::PopupMenu>(result.get());
}
return result;
}
base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) {
Expects(row->peer()->isUser());
if (row->peer()->isSelf()) {
@@ -1125,7 +1147,9 @@ base::unique_qptr<Ui::PopupMenu> MembersController::rowContextMenu(
_kickMemberRequests.fire_copy(user);
});
if (_peer->canManageGroupCall() && (!admin || mute)) {
if ((muteState != Row::State::Invited)
&& _peer->canManageGroupCall()
&& (!admin || mute)) {
result->addAction(
(mute
? tr::lng_group_call_context_mute(tr::now)
@@ -1410,6 +1434,9 @@ void GroupMembers::peerListSetTitle(rpl::producer<QString> title) {
void GroupMembers::peerListSetAdditionalTitle(rpl::producer<QString> title) {
}
void GroupMembers::peerListSetHideEmpty(bool hide) {
}
bool GroupMembers::peerListIsRowChecked(not_null<PeerListRow*> row) {
return false;
}
@@ -1421,10 +1448,6 @@ int GroupMembers::peerListSelectedRowsCount() {
return 0;
}
std::vector<not_null<PeerData*>> GroupMembers::peerListCollectSelectedRows() {
return {};
}
void GroupMembers::peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) {
Unexpected("Item selection in Calls::GroupMembers.");
}

View File

@@ -57,10 +57,10 @@ private:
// PeerListContentDelegate interface.
void peerListSetTitle(rpl::producer<QString> title) override;
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
void peerListSetHideEmpty(bool hide) override;
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
int peerListSelectedRowsCount() override;
void peerListScrollToTop() override;
std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override;
void peerListAddSelectedPeerInBunch(
not_null<PeerData*> peer) override;
void peerListAddSelectedRowInBunch(

View File

@@ -29,6 +29,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#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 "app.h"
#include "apiwrap.h" // api().kickParticipant.
#include "styles/style_calls.h"
@@ -51,8 +54,7 @@ class InviteController final : public ParticipantsBoxController {
public:
InviteController(
not_null<PeerData*> peer,
base::flat_set<not_null<UserData*>> alreadyIn,
int fullInCount);
base::flat_set<not_null<UserData*>> alreadyIn);
void prepare() override;
@@ -63,34 +65,81 @@ public:
void itemDeselectedHook(not_null<PeerData*> peer) override;
std::variant<int, not_null<UserData*>> inviteSelectedUsers(
not_null<PeerListBox*> box,
not_null<GroupCall*> call) const;
[[nodiscard]] auto peersWithRows() const
-> not_null<const base::flat_set<not_null<UserData*>>*>;
[[nodiscard]] rpl::producer<not_null<UserData*>> rowAdded() const;
[[nodiscard]] bool hasRowFor(not_null<PeerData*> peer) const;
private:
[[nodiscard]] int alreadyInCount() const;
[[nodiscard]] bool isAlreadyIn(not_null<UserData*> user) const;
[[nodiscard]] int fullCount() const;
std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user) const override;
not_null<PeerData*> _peer;
const base::flat_set<not_null<UserData*>> _alreadyIn;
const int _fullInCount = 0;
mutable base::flat_set<not_null<UserData*>> _skippedUsers;
mutable base::flat_set<not_null<UserData*>> _inGroup;
rpl::event_stream<not_null<UserData*>> _rowAdded;
};
class InviteContactsController final : public AddParticipantsBoxController {
public:
InviteContactsController(
not_null<PeerData*> peer,
base::flat_set<not_null<UserData*>> alreadyIn,
not_null<const base::flat_set<not_null<UserData*>>*> inGroup,
rpl::producer<not_null<UserData*>> discoveredInGroup);
private:
void prepareViewHook() override;
std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user) override;
const not_null<const base::flat_set<not_null<UserData*>>*> _inGroup;
rpl::producer<not_null<UserData*>> _discoveredInGroup;
rpl::lifetime _lifetime;
};
[[nodiscard]] object_ptr<Ui::RpWidget> CreateSectionSubtitle(
QWidget *parent,
rpl::producer<QString> text) {
auto result = object_ptr<Ui::FixedHeightWidget>(
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<Ui::FlatLabel>(
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<PeerData*> peer,
base::flat_set<not_null<UserData*>> alreadyIn,
int fullInCount)
base::flat_set<not_null<UserData*>> alreadyIn)
: ParticipantsBoxController(CreateTag{}, nullptr, peer, Role::Members)
, _peer(peer)
, _alreadyIn(std::move(alreadyIn))
, _fullInCount(std::max(fullInCount, int(_alreadyIn.size()))) {
_skippedUsers.emplace(peer->session().user());
, _alreadyIn(std::move(alreadyIn)) {
SubscribeToMigration(
_peer,
lifetime(),
@@ -98,8 +147,14 @@ InviteController::InviteController(
}
void InviteController::prepare() {
delegate()->peerListSetHideEmpty(true);
ParticipantsBoxController::prepare();
delegate()->peerListSetTitle(tr::lng_group_call_invite_title());
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<PeerListRow*> row) {
@@ -115,44 +170,71 @@ base::unique_qptr<Ui::PopupMenu> InviteController::rowContextMenu(
void InviteController::itemDeselectedHook(not_null<PeerData*> peer) {
}
int InviteController::alreadyInCount() const {
return std::max(_fullInCount, int(_alreadyIn.size()));
bool InviteController::hasRowFor(not_null<PeerData*> peer) const {
return (delegate()->peerListFindRow(peer->id) != nullptr);
}
bool InviteController::isAlreadyIn(not_null<UserData*> user) const {
return _alreadyIn.contains(user);
}
int InviteController::fullCount() const {
return alreadyInCount() + delegate()->peerListSelectedRowsCount();
}
std::unique_ptr<PeerListRow> InviteController::createRow(
not_null<UserData*> user) const {
if (user->isSelf() || user->isBot()) {
_skippedUsers.emplace(user);
return nullptr;
}
auto result = std::make_unique<PeerListRow>(user);
_rowAdded.fire_copy(user);
_inGroup.emplace(user);
if (isAlreadyIn(user)) {
result->setDisabledState(PeerListRow::State::DisabledChecked);
}
return result;
}
std::variant<int, not_null<UserData*>> InviteController::inviteSelectedUsers(
not_null<PeerListBox*> box,
not_null<GroupCall*> call) const {
const auto rows = box->peerListCollectSelectedRows();
const auto users = ranges::view::all(
rows
) | ranges::view::transform([](not_null<PeerData*> peer) {
Expects(peer->isUser());
Expects(!peer->isSelf());
auto InviteController::peersWithRows() const
-> not_null<const base::flat_set<not_null<UserData*>>*> {
return &_inGroup;
}
return not_null<UserData*>(peer->asUser());
}) | ranges::to_vector;
return call->inviteUsers(users);
rpl::producer<not_null<UserData*>> InviteController::rowAdded() const {
return _rowAdded.events();
}
InviteContactsController::InviteContactsController(
not_null<PeerData*> peer,
base::flat_set<not_null<UserData*>> alreadyIn,
not_null<const base::flat_set<not_null<UserData*>>*> inGroup,
rpl::producer<not_null<UserData*>> 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<UserData*> user) {
if (auto row = delegate()->peerListFindRow(user->id)) {
delegate()->peerListRemoveRow(row);
}
}, _lifetime);
}
std::unique_ptr<PeerListRow> InviteContactsController::createRow(
not_null<UserData*> user) {
return _inGroup->contains(user)
? nullptr
: AddParticipantsBoxController::createRow(user);
}
} // namespace
@@ -197,6 +279,21 @@ void LeaveGroupCallBox(
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
void GroupCallConfirmBox(
not_null<Ui::GenericBox*> box,
const QString &text,
rpl::producer<QString> button,
Fn<void()> callback) {
box->addRow(
object_ptr<Ui::FlatLabel>(
box.get(),
text,
st::groupCallBoxLabel),
st::boxPadding);
box->addButton(std::move(button), callback);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
GroupPanel::GroupPanel(not_null<GroupCall*> call)
: _call(call)
, _peer(call->peer())
@@ -219,6 +316,7 @@ GroupPanel::GroupPanel(not_null<GroupCall*> call)
, _hangup(widget(), st::groupCallHangup) {
_layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox);
_settings->setColorOverrides(_mute->colorOverrides());
_layerBg->setHideByBackgroundClick(true);
SubscribeToMigration(
_peer,
@@ -240,6 +338,14 @@ bool GroupPanel::isActive() const {
&& !(_window->windowState() & Qt::WindowMinimized);
}
void GroupPanel::minimize() {
_window->setWindowState(_window->windowState() | Qt::WindowMinimized);
}
void GroupPanel::close() {
_window->close();
}
void GroupPanel::showAndActivate() {
if (_window->isHidden()) {
_window->show();
@@ -362,7 +468,7 @@ void GroupPanel::initControls() {
});
_settings->setText(tr::lng_menu_settings());
_hangup->setText(tr::lng_box_leave());
_hangup->setText(tr::lng_group_call_leave());
_members->desiredHeightValue(
) | rpl::start_with_next([=] {
@@ -460,52 +566,131 @@ void GroupPanel::addMembers() {
alreadyIn.emplace(_peer->session().user());
auto controller = std::make_unique<InviteController>(
_peer,
std::move(alreadyIn),
real->fullCount());
alreadyIn);
controller->setStyleOverrides(
&st::groupCallInviteMembersList,
&st::groupCallMultiSelect);
const auto weak = base::make_weak(_call);
auto initBox = [=, controller = controller.get()](
not_null<PeerListBox*> box) {
box->addButton(tr::lng_group_call_invite_button(), [=] {
if (const auto call = weak.get()) {
const auto result = controller->inviteSelectedUsers(box, call);
auto contactsController = std::make_unique<InviteContactsController>(
_peer,
std::move(alreadyIn),
controller->peersWithRows(),
controller->rowAdded());
contactsController->setStyleOverrides(
&st::groupCallInviteMembersList,
&st::groupCallMultiSelect);
if (const auto user = std::get_if<not_null<UserData*>>(&result)) {
Ui::Toast::Show(
widget(),
Ui::Toast::Config{
.text = tr::lng_group_call_invite_done_user(
tr::now,
lt_user,
Ui::Text::Bold((*user)->firstName),
Ui::Text::WithEntities),
.st = &st::defaultToast,
});
} else if (const auto count = std::get_if<int>(&result)) {
if (*count > 0) {
Ui::Toast::Show(
widget(),
Ui::Toast::Config{
.text = tr::lng_group_call_invite_done_many(
tr::now,
lt_count,
*count,
Ui::Text::RichLangValue),
.st = &st::defaultToast,
});
}
} else {
Unexpected("Result in GroupCall::inviteUsers.");
}
const auto weak = base::make_weak(_call);
const auto invite = [=](const std::vector<not_null<UserData*>> &users) {
const auto call = weak.get();
if (!call) {
return;
}
const auto result = call->inviteUsers(users);
if (const auto user = std::get_if<not_null<UserData*>>(&result)) {
Ui::Toast::Show(
widget(),
Ui::Toast::Config{
.text = tr::lng_group_call_invite_done_user(
tr::now,
lt_user,
Ui::Text::Bold((*user)->firstName),
Ui::Text::WithEntities),
.st = &st::defaultToast,
});
} else if (const auto count = std::get_if<int>(&result)) {
if (*count > 0) {
Ui::Toast::Show(
widget(),
Ui::Toast::Config{
.text = tr::lng_group_call_invite_done_many(
tr::now,
lt_count,
*count,
Ui::Text::RichLangValue),
.st = &st::defaultToast,
});
}
box->closeBox();
} else {
Unexpected("Result in GroupCall::inviteUsers.");
}
};
const auto inviteWithAdd = [=](
const std::vector<not_null<UserData*>> &users,
const std::vector<not_null<UserData*>> &nonMembers,
Fn<void()> finish) {
_peer->session().api().addChatParticipants(
_peer,
nonMembers,
[=](bool) { invite(users); finish(); });
};
const auto inviteWithConfirmation = [=](
const std::vector<not_null<UserData*>> &users,
const std::vector<not_null<UserData*>> &nonMembers,
Fn<void()> 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<QPointer<Ui::GenericBox>>();
const auto finishWithConfirm = [=] {
if (*shared) {
(*shared)->closeBox();
}
finish();
};
auto box = Box(
GroupCallConfirmBox,
text,
tr::lng_participant_invite(),
[=] { inviteWithAdd(users, nonMembers, finishWithConfirm); });
*shared = box.data();
_layerBg->showBox(std::move(box));
};
auto initBox = [=, controller = controller.get()](
not_null<PeerListsBox*> 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::view::all(
rows
) | ranges::view::transform([](not_null<PeerData*> peer) {
return not_null<UserData*>(peer->asUser());
}) | ranges::to_vector;
const auto nonMembers = ranges::view::all(
users
) | ranges::view::filter([&](not_null<UserData*> 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(); });
};
_layerBg->showBox(Box<PeerListBox>(std::move(controller), initBox));
auto controllers = std::vector<std::unique_ptr<PeerListController>>();
controllers.push_back(std::move(controller));
controllers.push_back(std::move(contactsController));
_layerBg->showBox(Box<PeerListsBox>(std::move(controllers), initBox));
}
void GroupPanel::kickMember(not_null<UserData*> user) {

View File

@@ -68,6 +68,8 @@ public:
~GroupPanel();
[[nodiscard]] bool isActive() const;
void minimize();
void close();
void showAndActivate();
void closeBeforeDestroy();

View File

@@ -486,6 +486,25 @@ bool Instance::activateCurrentCall() {
return false;
}
bool Instance::minimizeCurrentActiveCall() {
if (inCall() && _currentCallPanel->isActive()) {
_currentCallPanel->minimize();
return true;
} else if (inGroupCall() && _currentGroupCallPanel->isActive()) {
_currentGroupCallPanel->minimize();
return true;
}
return false;
}
bool Instance::closeCurrentActiveCall() {
if (inGroupCall() && _currentGroupCallPanel->isActive()) {
_currentGroupCallPanel->close();
return true;
}
return false;
}
Call *Instance::currentCall() const {
return _currentCall.get();
}

View File

@@ -55,6 +55,8 @@ public:
[[nodiscard]] bool hasActivePanel(
not_null<Main::Session*> session) const;
bool activateCurrentCall();
bool minimizeCurrentActiveCall();
bool closeCurrentActiveCall();
auto getVideoCapture()
-> std::shared_ptr<tgcalls::VideoCaptureInterface> override;
void requestPermissionsOrFail(Fn<void()> onSuccess, bool video = true);

View File

@@ -221,6 +221,10 @@ void Panel::showAndActivate() {
_window->setFocus();
}
void Panel::minimize() {
_window->setWindowState(_window->windowState() | Qt::WindowMinimized);
}
void Panel::replaceCall(not_null<Call*> call) {
reinitWithCall(call);
updateControlsGeometry();

View File

@@ -53,6 +53,7 @@ public:
[[nodiscard]] bool isActive() const;
void showAndActivate();
void minimize();
void replaceCall(not_null<Call*> call);
void closeBeforeDestroy();

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/paint/blobs_linear.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/chat/group_call_userpics.h" // Ui::GroupCallUser.
#include "ui/chat/group_call_bar.h" // Ui::GroupCallBarContent.
#include "ui/layers/generic_box.h"
#include "ui/wrap/padding_wrap.h"
@@ -32,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h"
#include "app.h"
#include "styles/style_calls.h"
#include "styles/style_chat.h" // style::GroupCallUserpics
#include "styles/style_layers.h"
namespace Calls {
@@ -165,7 +167,7 @@ void DebugInfoBox::updateText() {
} // namespace
struct TopBar::User {
Ui::GroupCallBarContent::User data;
Ui::GroupCallUser data;
};
class Mute final : public Ui::IconButton {
@@ -238,6 +240,12 @@ TopBar::TopBar(
: RpWidget(parent)
, _call(call)
, _groupCall(groupCall)
, _userpics(call
? nullptr
: std::make_unique<Ui::GroupCallUserpics>(
st::groupCallTopBarUserpics,
rpl::single(true),
[=] { updateUserpics(); }))
, _durationLabel(_call
? object_ptr<Ui::LabelSimple>(this, st::callBarLabel)
: object_ptr<Ui::LabelSimple>(nullptr))
@@ -267,7 +275,7 @@ void TopBar::initControls() {
call->setMuted(!call->muted());
} else if (const auto group = _groupCall.get()) {
if (group->muted() == MuteState::ForceMuted) {
Ui::Toast::Show(tr::lng_group_call_force_muted(tr::now));
Ui::Toast::Show(tr::lng_group_call_force_muted_sub(tr::now));
} else {
group->setMuted((group->muted() == MuteState::Muted)
? MuteState::Active
@@ -551,35 +559,31 @@ void TopBar::subscribeToMembersChanges(not_null<GroupCall*> call) {
) | rpl::map([=](not_null<Data::GroupCall*> real) {
return HistoryView::GroupCallTracker::ContentByCall(
real,
HistoryView::UserpicsInRowStyle{
.size = st::groupCallTopBarUserpicSize,
.shift = st::groupCallTopBarUserpicShift,
.stroke = st::groupCallTopBarUserpicStroke,
});
st::groupCallTopBarUserpics.size);
}) | rpl::flatten_latest(
) | rpl::filter([=](const Ui::GroupCallBarContent &content) {
if (_users.size() != content.users.size()) {
return true;
}
for (auto i = 0, count = int(_users.size()); i != count; ++i) {
if (_users[i].data.userpicKey != content.users[i].userpicKey
|| _users[i].data.id != content.users[i].id) {
if (_users[i].userpicKey != content.users[i].userpicKey
|| _users[i].id != content.users[i].id) {
return true;
}
}
return false;
}) | rpl::start_with_next([=](const Ui::GroupCallBarContent &content) {
const auto sizeChanged = (_users.size() != content.users.size());
_users = ranges::view::all(
content.users
) | ranges::view::transform([](const auto &user) {
return User{ user };
}) | ranges::to_vector;
generateUserpicsInRow();
if (sizeChanged) {
updateControlsGeometry();
_users = content.users;
for (auto &user : _users) {
user.speaking = false;
}
update();
_userpics->update(_users, !isHidden());
}, lifetime());
_userpics->widthValue(
) | rpl::start_with_next([=](int width) {
_userpicsWidth = width;
updateControlsGeometry();
}, lifetime());
call->peer()->session().changes().peerUpdates(
@@ -591,41 +595,10 @@ void TopBar::subscribeToMembersChanges(not_null<GroupCall*> call) {
}) | rpl::start_with_next([=] {
updateInfoLabels();
}, lifetime());
}
void TopBar::generateUserpicsInRow() {
const auto count = int(_users.size());
if (!count) {
_userpics = QImage();
return;
}
const auto limit = std::min(count, kMaxUsersInBar);
const auto single = st::groupCallTopBarUserpicSize;
const auto shift = st::groupCallTopBarUserpicShift;
const auto width = single + (limit - 1) * (single - shift);
if (_userpics.width() != width * cIntRetinaFactor()) {
_userpics = QImage(
QSize(width, single) * cIntRetinaFactor(),
QImage::Format_ARGB32_Premultiplied);
}
_userpics.fill(Qt::transparent);
_userpics.setDevicePixelRatio(cRetinaFactor());
auto q = Painter(&_userpics);
auto hq = PainterHighQualityEnabler(q);
auto pen = QPen(Qt::transparent);
pen.setWidth(st::groupCallTopBarUserpicStroke);
auto x = (count - 1) * (single - shift);
for (auto i = count; i != 0;) {
q.setCompositionMode(QPainter::CompositionMode_SourceOver);
q.drawImage(x, 0, _users[--i].data.userpic);
q.setCompositionMode(QPainter::CompositionMode_Source);
q.setBrush(Qt::NoBrush);
q.setPen(pen);
q.drawEllipse(x, 0, single, single);
x -= single - shift;
}
void TopBar::updateUserpics() {
update(_mute->width(), 0, _userpics->maxWidth(), height());
}
void TopBar::updateInfoLabels() {
@@ -688,8 +661,13 @@ void TopBar::updateControlsGeometry() {
_durationLabel->moveToLeft(left, st::callBarLabelTop);
left += _durationLabel->width() + st::callBarSkip;
}
if (!_userpics.isNull()) {
left += _userpics.width() / _userpics.devicePixelRatio();
if (_userpicsWidth) {
const auto single = st::groupCallTopBarUserpics.size;
const auto skip = anim::interpolate(
0,
st::callBarSkip,
std::min(_userpicsWidth, single) / float64(single));
left += _userpicsWidth + skip;
}
if (_signalBars) {
_signalBars->moveToLeft(left, (height() - _signalBars->height()) / 2);
@@ -741,11 +719,10 @@ void TopBar::paintEvent(QPaintEvent *e) {
: (_muted ? st::callBarBgMuted : st::callBarBg);
p.fillRect(e->rect(), std::move(brush));
if (!_userpics.isNull()) {
const auto imageSize = _userpics.size()
/ _userpics.devicePixelRatio();
const auto top = (height() - imageSize.height()) / 2;
p.drawImage(_mute->width(), top, _userpics);
if (_userpicsWidth) {
const auto size = st::groupCallTopBarUserpics.size;
const auto top = (height() - size) / 2;
_userpics->paint(p, _mute->width(), top, size);
}
}

View File

@@ -20,6 +20,8 @@ class IconButton;
class AbstractButton;
class LabelSimple;
class FlatLabel;
struct GroupCallUser;
class GroupCallUserpics;
} // namespace Ui
namespace Main {
@@ -66,14 +68,15 @@ private:
void setMuted(bool mute);
void subscribeToMembersChanges(not_null<GroupCall*> call);
void generateUserpicsInRow();
void updateUserpics();
const base::weak_ptr<Call> _call;
const base::weak_ptr<GroupCall> _groupCall;
bool _muted = false;
std::vector<User> _users;
QImage _userpics;
std::vector<Ui::GroupCallUser> _users;
std::unique_ptr<Ui::GroupCallUserpics> _userpics;
int _userpicsWidth = 0;
object_ptr<Ui::LabelSimple> _durationLabel;
object_ptr<SignalBars> _signalBars;
object_ptr<Ui::FlatLabel> _fullInfoLabel;

View File

@@ -915,18 +915,25 @@ bool Application::closeActiveWindow() {
if (hideMediaView()) {
return true;
}
if (const auto window = activeWindow()) {
window->close();
return true;
if (!calls().closeCurrentActiveCall()) {
if (const auto window = activeWindow()) {
if (window->widget()->isVisible()
&& window->widget()->isActive()) {
window->close();
return true;
}
}
}
return false;
}
bool Application::minimizeActiveWindow() {
hideMediaView();
if (const auto window = activeWindow()) {
window->minimize();
return true;
if (!calls().minimizeCurrentActiveCall()) {
if (const auto window = activeWindow()) {
window->minimize();
return true;
}
}
return false;
}

View File

@@ -75,6 +75,14 @@ std::map<int, const char*> BetaLogs() {
"- Fix freeze on secondary screen disconnect.\n"
},
{
2005002,
"- Fix possible crash in video calls.\n"
"- Fix possible crash in connecting to voice chats.\n"
"- Use different audio module code on Windows in calls.\n"
}
};
};

View File

@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Telegram Desktop"_cs;
constexpr auto AppFile = "Telegram"_cs;
constexpr auto AppVersion = 2004015;
constexpr auto AppVersionStr = "2.4.15";
constexpr auto AppVersion = 2005002;
constexpr auto AppVersionStr = "2.5.2";
constexpr auto AppBetaVersion = true;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -33,7 +33,7 @@ GroupCall::GroupCall(
uint64 accessHash)
: _id(id)
, _accessHash(accessHash)
, _peer(peer) // #TODO calls migration
, _peer(peer)
, _speakingByActiveFinishTimer([=] { checkFinishSpeakingByActive(); }) {
}
@@ -312,36 +312,6 @@ void GroupCall::applyParticipantsSlice(
}
}
void GroupCall::applyParticipantsMutes(
const MTPDupdateGroupCallParticipants &update) {
for (const auto &participant : update.vparticipants().v) {
participant.match([&](const MTPDgroupCallParticipant &data) {
if (data.is_left()) {
return;
}
const auto userId = data.vuser_id().v;
const auto user = _peer->owner().user(userId);
const auto i = ranges::find(
_participants,
user,
&Participant::user);
if (i != end(_participants)) {
const auto was = *i;
i->muted = data.is_muted();
i->canSelfUnmute = !i->muted || data.is_can_self_unmute();
if (!i->canSelfUnmute) {
i->speaking = false;
_speakingByActiveFinishes.remove(i->user);
}
_participantUpdates.fire({
.was = was,
.now = *i,
});
}
});
}
}
void GroupCall::applyLastSpoke(
uint32 ssrc,
LastSpokeTimes when,
@@ -563,17 +533,29 @@ bool GroupCall::inCall() const {
void GroupCall::applyUpdate(const MTPDupdateGroupCallParticipants &update) {
const auto version = update.vversion().v;
if (version < _version) {
return;
} else if (version == _version) {
applyParticipantsMutes(update);
return;
} else if (version != _version + 1) {
applyParticipantsMutes(update);
reload();
const auto applyUpdate = [&] {
if (version < _version) {
return false;
}
auto versionShouldIncrement = false;
for (const auto &participant : update.vparticipants().v) {
const auto versioned = participant.match([&](
const MTPDgroupCallParticipant &data) {
return data.is_versioned();
});
if (versioned) {
versionShouldIncrement = true;
break;
}
}
return versionShouldIncrement
? (version == _version + 1)
: (version == _version);
}();
if (!applyUpdate) {
return;
}
_version = update.vversion().v;
_version = version;
applyUpdateChecked(update);
}

View File

@@ -91,8 +91,6 @@ private:
void applyParticipantsSlice(
const QVector<MTPGroupCallParticipant> &list,
ApplySliceSource sliceSource);
void applyParticipantsMutes(
const MTPDupdateGroupCallParticipants &update);
void requestUnknownParticipants();
void changePeerEmptyCallFlag();
void checkFinishSpeakingByActive();

View File

@@ -1647,7 +1647,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
addDocumentActions(lnkDocument->document());
}
if (item && item->hasDirectLink() && isUponSelected != 2 && isUponSelected != -2) {
_menu->addAction(item->history()->peer->isMegagroup() ? tr::lng_context_copy_link(tr::now) : tr::lng_context_copy_post_link(tr::now), [=] {
_menu->addAction(item->history()->peer->isMegagroup() ? tr::lng_context_copy_message_link(tr::now) : tr::lng_context_copy_post_link(tr::now), [=] {
HistoryView::CopyPostLink(session, itemId, HistoryView::Context::History);
});
}
@@ -1788,7 +1788,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
QGuiApplication::clipboard()->setText(text);
});
} else if (item && item->hasDirectLink() && isUponSelected != 2 && isUponSelected != -2) {
_menu->addAction(item->history()->peer->isMegagroup() ? tr::lng_context_copy_link(tr::now) : tr::lng_context_copy_post_link(tr::now), [=] {
_menu->addAction(item->history()->peer->isMegagroup() ? tr::lng_context_copy_message_link(tr::now) : tr::lng_context_copy_post_link(tr::now), [=] {
HistoryView::CopyPostLink(session, itemId, HistoryView::Context::History);
});
}

View File

@@ -776,7 +776,8 @@ bool HistoryItem::canBeEditedFromHistory() const {
}
if ((IsServerMsgId(id) || isScheduled())
&& !serviceMsg()
&& (out() || history()->peer->isSelf())) {
&& (out() || history()->peer->isSelf())
&& !Has<HistoryMessageForwarded>()) {
return true;
}
return false;

View File

@@ -1449,7 +1449,7 @@ void HistoryWidget::activate() {
updateHistoryGeometry();
}
}
if (App::wnd()) App::wnd()->setInnerFocus();
controller()->widget()->setInnerFocus();
}
void HistoryWidget::setInnerFocus() {
@@ -3347,7 +3347,7 @@ void HistoryWidget::doneShow() {
_groupCallBar->finishAnimating();
}
checkHistoryActivation();
App::wnd()->setInnerFocus();
controller()->widget()->setInnerFocus();
_preserveScrollTop = false;
}

View File

@@ -1389,8 +1389,9 @@ void VoiceRecordBar::hideFast() {
void VoiceRecordBar::stopRecording(StopType type) {
using namespace ::Media::Capture;
if (type == StopType::Cancel) {
_cancelRequests.fire({});
instance()->stop();
instance()->stop(crl::guard(this, [=](Result &&data) {
_cancelRequests.fire({});
}));
return;
}
instance()->stop(crl::guard(this, [=](Result &&data) {

View File

@@ -7,631 +7,56 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/controls/history_view_voice_record_button.h"
#include "ui/paint/blobs.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h"
#include <QMatrix>
namespace HistoryView::Controls {
namespace {
constexpr auto kSegmentsCount = 12;
constexpr auto kMajorDegreeOffset = 360 / kSegmentsCount;
constexpr auto kSixtyDegrees = 60;
constexpr auto kMaxLevel = 1800.;
constexpr auto kBlobAlpha = 76. / 255.;
constexpr auto kBlobMaxSpeed = 5.0;
constexpr auto kLevelDuration = 100. + 500. * 0.33;
constexpr auto kBlobsScaleEnterDuration = crl::time(250);
constexpr auto kEnterIdleAnimationDuration = crl::time(1200);
constexpr auto kRotationSpeed = 0.36 * 0.1;
constexpr auto kRandomAdditionFactor = 0.15;
constexpr auto kIdleRadiusGlobalFactor = 0.56;
constexpr auto kIdleRadiusFactor = 0.15 * 0.5;
constexpr auto kOpacityMajor = 0.30;
constexpr auto kOpacityMinor = 0.15;
constexpr auto kIdleRotationSpeed = 0.2;
constexpr auto kIdleRotateDiff = 0.1 * kIdleRotationSpeed;
constexpr auto kWaveAngle = 0.03;
constexpr auto kAnimationSpeedMajor = 1.5 - 0.65;
constexpr auto kAnimationSpeedMinor = 1.5 - 0.45;
constexpr auto kAnimationSpeedCircle = 1.5 - 0.25;
constexpr auto kAmplitudeDiffFactorMax = 500. - 100.;
constexpr auto kAmplitudeDiffFactorMajor = 300. - 100.;
constexpr auto kAmplitudeDiffFactorMinor = 400. - 100.;
constexpr auto kFlingDistanceFactorMajor = 8 * 16;
constexpr auto kFlingDistanceFactorMinor = 20 * 16;
constexpr auto kFlingInAnimationDurationMajor = 200;
constexpr auto kFlingInAnimationDurationMinor = 350;
constexpr auto kFlingOutAnimationDurationMajor = 220;
constexpr auto kFlingOutAnimationDurationMinor = 380;
constexpr auto kSineWaveSpeedMajor = 0.02 * 0.2;
constexpr auto kSineWaveSpeedMinor = 0.026 * 0.2;
constexpr auto kSmallWaveRadius = 0.55;
constexpr auto kFlingDistance = 0.50;
constexpr auto kMinDivider = 100.;
constexpr auto kMaxAmplitude = 1800.;
constexpr auto kZeroPoint = QPointF(0, 0);
template <typename Number>
void Normalize(Number &value, Number right) {
if (value >= right) {
value -= right;
}
}
float64 RandomAdditional() {
return (rand_value<int>() % 100 / 100.);
}
void PerformAnimation(
rpl::producer<crl::time> &&animationTicked,
Fn<void(float64)> &&applyValue,
Fn<void()> &&finishCallback,
float64 duration,
float64 from,
float64 to,
rpl::lifetime &lifetime) {
lifetime.destroy();
const auto animValue =
lifetime.make_state<anim::value>(from, to);
const auto animStarted = crl::now();
std::move(
animationTicked
) | rpl::start_with_next([=,
applyValue = std::move(applyValue),
finishCallback = std::move(finishCallback),
&lifetime](crl::time now) mutable {
const auto dt = anim::Disabled()
? 1.
: ((now - animStarted) / duration);
if (dt >= 1.) {
animValue->finish();
applyValue(animValue->current());
lifetime.destroy();
if (finishCallback) {
finishCallback();
}
} else {
animValue->update(dt, anim::linear);
applyValue(animValue->current());
}
}, lifetime);
auto Blobs() {
return std::vector<Ui::Paint::Blobs::BlobData>{
{
.segmentsCount = 9,
.minScale = 0.605229,
.minRadius = (float)st::historyRecordMinorBlobMinRadius,
.maxRadius = (float)st::historyRecordMinorBlobMaxRadius,
.speedScale = 1.,
.alpha = kBlobAlpha,
.maxSpeed = kBlobMaxSpeed,
},
{
.segmentsCount = 12,
.minScale = 0.553943,
.minRadius = (float)st::historyRecordMajorBlobMinRadius,
.maxRadius = (float)st::historyRecordMajorBlobMaxRadius,
.speedScale = 1.,
.alpha = kBlobAlpha,
.maxSpeed = kBlobMaxSpeed,
},
};
}
} // namespace
class ContinuousValue {
public:
ContinuousValue() = default;
ContinuousValue(float64 duration) : _duration(duration) {
}
void start(float64 to, float64 duration) {
_to = to;
_delta = (_to - _cur) / duration;
}
void start(float64 to) {
start(to, _duration);
}
void reset() {
_to = _cur = _delta = 0.;
}
float64 current() const {
return _cur;
}
float64 to() const {
return _to;
}
float64 delta() const {
return _delta;
}
void update(crl::time dt, Fn<void(float64 &)> &&callback = nullptr) {
if (_to != _cur) {
_cur += _delta * dt;
if ((_to != _cur) && ((_delta > 0) == (_cur > _to))) {
_cur = _to;
}
if (callback) {
callback(_cur);
}
}
}
private:
float64 _duration = 0.;
float64 _to = 0.;
float64 _cur = 0.;
float64 _delta = 0.;
};
class CircleBezier final {
public:
CircleBezier(int n);
void computeRandomAdditionals();
void paintCircle(
Painter &p,
const QColor &c,
float64 radius,
float64 cubicBezierFactor,
float64 idleStateDiff,
float64 radiusDiff,
float64 randomFactor);
private:
struct Points {
QPointF point;
QPointF control;
};
const int _segmentsCount;
const float64 _segmentLength;
std::vector<float64> _randomAdditionals;
};
class Wave final {
public:
Wave(
rpl::producer<crl::time> animationTicked,
int n,
float64 rotationOffset,
float64 amplitudeRadius,
float64 amplitudeWaveDiff,
float64 fling,
int flingDistanceFactor,
int flingInAnimationDuration,
int flingOutAnimationDuration,
float64 amplitudeDiffSpeed,
float64 amplitudeDiffFactor,
bool isDirectionClockwise);
void setValue(float64 to);
void tick(float64 circleRadius, crl::time dt);
void reset();
void paint(Painter &p, QColor c);
private:
void initEnterIdleAnimation(rpl::producer<crl::time> animationTicked);
void initFlingAnimation(rpl::producer<crl::time> animationTicked);
const std::unique_ptr<CircleBezier> _circleBezier;
const float _rotationOffset;
const float64 _idleGlobalRadius;
const float64 _amplitudeRadius;
const float64 _amplitudeWaveDiff;
const float64 _randomAdditions;
const float64 _fling;
const int _flingDistanceFactor;
const int _flingInAnimationDuration;
const int _flingOutAnimationDuration;
const float64 _amplitudeInAnimationDuration;
const float64 _amplitudeOutAnimationDuration;
const int _directionClockwise;
bool _incRandomAdditionals = false;
bool _isIdle = true;
bool _wasFling = false;
float64 _flingRadius = 0.;
float64 _idleRadius = 0.;
float64 _idleRotation = 0.;
float64 _lastRadius = 0.;
float64 _rotation = 0.;
float64 _sineAngleMax = 0.;
float64 _waveAngle = 0.;
float64 _waveDiff = 0.;
ContinuousValue _levelValue;
rpl::event_stream<float64> _flingAnimationRequests;
rpl::event_stream<> _enterIdleAnimationRequests;
rpl::lifetime _animationEnterIdleLifetime;
rpl::lifetime _animationFlingLifetime;
rpl::lifetime _lifetime;
};
class RecordCircle final {
public:
RecordCircle(rpl::producer<crl::time> animationTicked);
void reset();
void setAmplitude(float64 value);
void paint(Painter &p, QColor c);
private:
const std::unique_ptr<Wave> _majorWave;
const std::unique_ptr<Wave> _minorWave;
crl::time _lastUpdateTime = 0;
ContinuousValue _levelValue;
};
CircleBezier::CircleBezier(int n)
: _segmentsCount(n)
, _segmentLength((4.0 / 3.0) * std::tan(M_PI / (2 * n)))
, _randomAdditionals(n) {
}
void CircleBezier::computeRandomAdditionals() {
ranges::generate(_randomAdditionals, RandomAdditional);
}
void CircleBezier::paintCircle(
Painter &p,
const QColor &c,
float64 radius,
float64 cubicBezierFactor,
float64 idleStateDiff,
float64 radiusDiff,
float64 randomFactor) {
PainterHighQualityEnabler hq(p);
const auto r1 = radius - idleStateDiff / 2. - radiusDiff / 2.;
const auto r2 = radius + radiusDiff / 2. + idleStateDiff / 2.;
const auto l = _segmentLength * std::max(r1, r2) * cubicBezierFactor;
auto m = QMatrix();
const auto preparePoints = [&](int i, bool isStart) -> Points {
Normalize(i, _segmentsCount);
const auto randomAddition = randomFactor * _randomAdditionals[i];
const auto r = ((i % 2 == 0) ? r1 : r2) + randomAddition;
m.reset();
m.rotate(360. / _segmentsCount * i);
const auto sign = isStart ? 1 : -1;
return {
(isStart && i) ? QPointF() : m.map(QPointF(0, -r)),
m.map(QPointF(sign * (l + randomAddition * _segmentLength), -r)),
};
};
const auto &[startPoint, _] = preparePoints(0, true);
auto path = QPainterPath();
path.moveTo(startPoint);
for (auto i = 0; i < _segmentsCount; i++) {
const auto &[_, startControl] = preparePoints(i, true);
const auto &[end, endControl] = preparePoints(i + 1, false);
path.cubicTo(startControl, endControl, end);
}
p.setBrush(Qt::NoBrush);
auto pen = QPen(Qt::NoPen);
pen.setCapStyle(Qt::RoundCap);
pen.setJoinStyle(Qt::RoundJoin);
p.setPen(pen);
p.fillPath(path, c);
p.drawPath(path);
}
Wave::Wave(
rpl::producer<crl::time> animationTicked,
int n,
float64 rotationOffset,
float64 amplitudeRadius,
float64 amplitudeWaveDiff,
float64 fling,
int flingDistanceFactor,
int flingInAnimationDuration,
int flingOutAnimationDuration,
float64 amplitudeDiffSpeed,
float64 amplitudeDiffFactor,
bool isDirectionClockwise)
: _circleBezier(std::make_unique<CircleBezier>(n))
, _rotationOffset(rotationOffset)
, _idleGlobalRadius(st::historyRecordRadiusDiffMin * kIdleRadiusGlobalFactor)
, _amplitudeRadius(amplitudeRadius)
, _amplitudeWaveDiff(amplitudeWaveDiff)
, _randomAdditions(st::historyRecordRandomAddition * kRandomAdditionFactor)
, _fling(fling)
, _flingDistanceFactor(flingDistanceFactor)
, _flingInAnimationDuration(flingInAnimationDuration)
, _flingOutAnimationDuration(flingOutAnimationDuration)
, _amplitudeInAnimationDuration(kMinDivider
+ amplitudeDiffFactor * amplitudeDiffSpeed)
, _amplitudeOutAnimationDuration(kMinDivider
+ kAmplitudeDiffFactorMax * amplitudeDiffSpeed)
, _directionClockwise(isDirectionClockwise ? 1 : -1)
, _rotation(rotationOffset) {
initEnterIdleAnimation(rpl::duplicate(animationTicked));
initFlingAnimation(std::move(animationTicked));
}
void Wave::reset() {
_incRandomAdditionals = false;
_isIdle = true;
_wasFling = false;
_flingRadius = 0.;
_idleRadius = 0.;
_idleRotation = 0.;
_lastRadius = 0.;
_rotation = 0.;
_sineAngleMax = 0.;
_waveAngle = 0.;
_waveDiff = 0.;
_levelValue.reset();
}
void Wave::setValue(float64 to) {
const auto duration = (to <= _levelValue.current())
? _amplitudeOutAnimationDuration
: _amplitudeInAnimationDuration;
_levelValue.start(to, duration);
const auto idle = to < 0.1;
if (_isIdle != idle && idle) {
_enterIdleAnimationRequests.fire({});
}
_isIdle = idle;
if (!_isIdle) {
_animationEnterIdleLifetime.destroy();
}
}
void Wave::initEnterIdleAnimation(rpl::producer<crl::time> animationTicked) {
_enterIdleAnimationRequests.events(
) | rpl::start_with_next([=] {
const auto &k = kSixtyDegrees;
const auto rotation = _rotation;
const auto rotationTo = std::round(rotation / k) * k
+ _rotationOffset;
const auto waveDiff = _waveDiff;
auto applyValue = [=](float64 v) {
_rotation = rotationTo + (rotation - rotationTo) * v;
_waveDiff = 1. + (waveDiff - 1.) * v;
_waveAngle = std::acos(_waveDiff * _directionClockwise);
};
PerformAnimation(
rpl::duplicate(animationTicked),
std::move(applyValue),
nullptr,
kEnterIdleAnimationDuration,
1,
0,
_animationEnterIdleLifetime);
}, _lifetime);
}
void Wave::initFlingAnimation(rpl::producer<crl::time> animationTicked) {
_flingAnimationRequests.events(
) | rpl::start_with_next([=](float64 delta) {
const auto fling = _fling * 2;
const auto flingDistance = delta
* _amplitudeRadius
* _flingDistanceFactor
* fling;
const auto applyValue = [=](float64 v) {
_flingRadius = v;
};
auto finishCallback = [=] {
PerformAnimation(
rpl::duplicate(animationTicked),
applyValue,
nullptr,
_flingOutAnimationDuration * fling,
flingDistance,
0,
_animationFlingLifetime);
};
PerformAnimation(
rpl::duplicate(animationTicked),
applyValue,
std::move(finishCallback),
_flingInAnimationDuration * fling,
_flingRadius,
flingDistance,
_animationFlingLifetime);
}, _lifetime);
}
void Wave::tick(float64 circleRadius, crl::time dt) {
auto amplitudeCallback = [&](float64 &value) {
if (std::abs(value - _levelValue.to()) * _amplitudeRadius
< (st::historyRecordRandomAddition / 2)) {
if (!_wasFling) {
_flingAnimationRequests.fire_copy(_levelValue.delta());
_wasFling = true;
}
} else {
_wasFling = false;
}
};
_levelValue.update(dt, std::move(amplitudeCallback));
_idleRadius = circleRadius * kIdleRadiusFactor;
{
const auto to = _levelValue.to();
const auto delta = (_sineAngleMax - to);
if (std::abs(delta) - 0.25 < 0) {
_sineAngleMax = to;
} else {
_sineAngleMax -= 0.25 * ((delta < 0) ? -1 : 1);
}
}
if (!_isIdle) {
_rotation += dt
* (kRotationSpeed * 4. * std::min(_levelValue.current() / .5, 1.)
+ kRotationSpeed * 0.5);
Normalize(_rotation, 360.);
} else {
_idleRotation += kIdleRotateDiff * dt;
Normalize(_idleRotation, 360.);
}
_lastRadius = circleRadius;
if (!_isIdle) {
_waveAngle += (_amplitudeWaveDiff * _sineAngleMax) * dt;
_waveDiff = std::cos(_waveAngle) * _directionClockwise;
if ((_waveDiff != 0) && ((_waveDiff > 0) == _incRandomAdditionals)) {
_circleBezier->computeRandomAdditionals();
_incRandomAdditionals = !_incRandomAdditionals;
}
}
}
void Wave::paint(Painter &p, QColor c) {
const auto amplitude = _levelValue.current();
const auto waveAmplitude = std::min(amplitude / .3, 1.);
const auto radiusDiff = st::historyRecordRadiusDiffMin
+ st::historyRecordRadiusDiff * kWaveAngle * _levelValue.to();
const auto diffFactor = 0.35 * waveAmplitude * _waveDiff;
const auto radius = (_lastRadius + _amplitudeRadius * amplitude)
+ _idleGlobalRadius
+ (_flingRadius * waveAmplitude);
const auto cubicBezierFactor = 1.
+ std::abs(diffFactor) * waveAmplitude
+ (1. - waveAmplitude) * kIdleRadiusFactor;
const auto circleRadiusDiff = std::max(
radiusDiff * diffFactor,
st::historyRecordLevelMainRadius - radius);
p.rotate((_rotation + _idleRotation) * _directionClockwise);
_circleBezier->paintCircle(
p,
c,
radius,
cubicBezierFactor,
_idleRadius * (1. - waveAmplitude),
circleRadiusDiff,
waveAmplitude * _waveDiff * _randomAdditions);
p.rotate(0);
}
RecordCircle::RecordCircle(rpl::producer<crl::time> animationTicked)
: _majorWave(std::make_unique<Wave>(
rpl::duplicate(animationTicked),
kSegmentsCount,
kMajorDegreeOffset,
st::historyRecordMajorAmplitudeRadius,
kSineWaveSpeedMajor,
0.,
kFlingDistanceFactorMajor,
kFlingInAnimationDurationMajor,
kFlingOutAnimationDurationMajor,
kAnimationSpeedMajor,
kAmplitudeDiffFactorMajor,
true))
, _minorWave(std::make_unique<Wave>(
std::move(animationTicked),
kSegmentsCount,
0,
st::historyRecordMinorAmplitudeRadius
+ st::historyRecordMinorAmplitudeRadius * kSmallWaveRadius,
kSineWaveSpeedMinor,
kFlingDistance,
kFlingDistanceFactorMinor,
kFlingInAnimationDurationMinor,
kFlingOutAnimationDurationMinor,
kAnimationSpeedMinor,
kAmplitudeDiffFactorMinor,
false))
, _levelValue(kMinDivider
+ kAmplitudeDiffFactorMax * kAnimationSpeedCircle) {
}
void RecordCircle::reset() {
_majorWave->reset();
_minorWave->reset();
_levelValue.reset();
}
void RecordCircle::setAmplitude(float64 value) {
const auto to = std::min(kMaxAmplitude, value) / kMaxAmplitude;
_levelValue.start(to);
_majorWave->setValue(to);
_minorWave->setValue(to);
}
void RecordCircle::paint(Painter &p, QColor c) {
const auto dt = crl::now() - _lastUpdateTime;
_levelValue.update(dt);
const auto &mainRadius = st::historyRecordLevelMainRadiusAmplitude;
const auto radius = (st::historyRecordLevelMainRadius
+ (anim::Disabled() ? 0 : mainRadius * _levelValue.current()));
if (!anim::Disabled()) {
_majorWave->tick(radius, dt);
_minorWave->tick(radius, dt);
_lastUpdateTime = crl::now();
const auto opacity = p.opacity();
p.setOpacity(kOpacityMajor);
_majorWave->paint(p, c);
p.setOpacity(kOpacityMinor);
_minorWave->paint(p, c);
p.setOpacity(opacity);
}
p.setPen(Qt::NoPen);
p.setBrush(c);
p.drawEllipse(kZeroPoint, radius, radius);
}
VoiceRecordButton::VoiceRecordButton(
not_null<Ui::RpWidget*> parent,
rpl::producer<> leaveWindowEventProducer)
: AbstractButton(parent)
, _recordCircle(std::make_unique<RecordCircle>(
_recordAnimationTicked.events()))
, _center(st::historyRecordLevelMaxRadius)
, _recordingAnimation([=](crl::time now) {
if (!anim::Disabled()) {
update();
}
_recordAnimationTicked.fire_copy(now);
return true;
}) {
const auto h = st::historyRecordLevelMaxRadius * 2;
resize(h, h);
, _blobs(std::make_unique<Ui::Paint::Blobs>(
Blobs(),
kLevelDuration,
kMaxLevel))
, _center(_blobs->maxRadius()) {
resize(_center * 2, _center * 2);
std::move(
leaveWindowEventProducer
) | rpl::start_with_next([=] {
@@ -643,45 +68,71 @@ VoiceRecordButton::VoiceRecordButton(
VoiceRecordButton::~VoiceRecordButton() = default;
void VoiceRecordButton::requestPaintLevel(quint16 level) {
_recordCircle->setAmplitude(level);
if (_blobsHideLastTime) {
return;
}
_blobs->setLevel(level);
update();
}
void VoiceRecordButton::init() {
const auto hasProgress = [](auto value) { return value != 0.; };
const auto stateChangedAnimation =
lifetime().make_state<Ui::Animations::Simple>();
const auto currentState = lifetime().make_state<Type>(_state.current());
rpl::single(
anim::Disabled()
) | rpl::then(
anim::Disables()
) | rpl::start_with_next([=](bool hide) {
if (hide) {
_blobs->setLevel(0.);
}
_blobsHideLastTime = hide ? crl::now() : 0;
if (!hide && !_animation.animating()) {
_animation.start();
}
}, lifetime());
const auto &mainRadiusMin = st::historyRecordMainBlobMinRadius;
const auto mainRadiusDiff = st::historyRecordMainBlobMaxRadius
- mainRadiusMin;
paintRequest(
) | rpl::start_with_next([=](const QRect &clip) {
Painter p(this);
const auto progress = _showProgress.current();
const auto complete = (progress == 1.);
const auto hideProgress = _blobsHideLastTime
? 1. - std::clamp(
((crl::now() - _blobsHideLastTime)
/ (float64)kBlobsScaleEnterDuration),
0.,
1.)
: 1.;
const auto showProgress = _showProgress.current();
const auto complete = (showProgress == 1.);
p.translate(_center, _center);
if (!complete) {
p.scale(progress, progress);
}
PainterHighQualityEnabler hq(p);
const auto color = anim::color(
const auto brush = QBrush(anim::color(
st::historyRecordVoiceFgInactive,
st::historyRecordVoiceFgActive,
_colorProgress.current());
_recordCircle->paint(p, color);
p.resetTransform();
_colorProgress));
_blobs->paint(p, brush, showProgress * hideProgress);
const auto radius = (mainRadiusMin
+ (mainRadiusDiff * _blobs->currentLevel())) * showProgress;
p.setPen(Qt::NoPen);
p.setBrush(brush);
p.drawEllipse(QPointF(), radius, radius);
if (!complete) {
p.setOpacity(progress);
p.setOpacity(showProgress);
}
// Paint icon.
{
const auto stateProgress = stateChangedAnimation->value(0.);
const auto stateProgress = _stateChangedAnimation.value(0.);
const auto scale = (std::cos(M_PI * 2 * stateProgress) + 1.) * .5;
p.translate(_center, _center);
if (scale < 1.) {
p.scale(scale, scale);
}
@@ -701,21 +152,33 @@ void VoiceRecordButton::init() {
}
}, lifetime());
_animation.init([=](crl::time now) {
if (const auto &last = _blobsHideLastTime; (last > 0)
&& (now - last >= kBlobsScaleEnterDuration)) {
_animation.stop();
return false;
}
_blobs->updateLevel(now - _lastUpdateTime);
_lastUpdateTime = now;
update();
return true;
});
rpl::merge(
shownValue(),
_showProgress.value(
) | rpl::map(hasProgress) | rpl::distinct_until_changed()
) | rpl::map(rpl::mappers::_1 != 0.) | rpl::distinct_until_changed()
) | rpl::start_with_next([=](bool show) {
setVisible(show);
setMouseTracking(show);
if (!show) {
_recordingAnimation.stop();
_animation.stop();
_showProgress = 0.;
_recordCircle->reset();
_blobs->resetLevel();
_state = Type::Record;
} else {
if (!_recordingAnimation.animating()) {
_recordingAnimation.start();
if (!_animation.animating()) {
_animation.start();
}
}
}, lifetime());
@@ -736,7 +199,7 @@ void VoiceRecordButton::init() {
update();
};
const auto duration = st::historyRecordVoiceDuration * 2;
stateChangedAnimation->start(std::move(callback), 0., to, duration);
_stateChangedAnimation.start(std::move(callback), 0., to, duration);
}, lifetime());
}
@@ -757,8 +220,15 @@ rpl::producer<bool> VoiceRecordButton::actives() const {
});
}
rpl::producer<> VoiceRecordButton::clicks() const {
return Ui::AbstractButton::clicks(
) | rpl::to_empty | rpl::filter([=] {
return inCircle(mapFromGlobal(QCursor::pos()));
});
}
bool VoiceRecordButton::inCircle(const QPoint &localPos) const {
const auto &radii = st::historyRecordLevelMaxRadius;
const auto &radii = st::historyRecordMainBlobMaxRadius;
const auto dx = std::abs(localPos.x() - _center);
if (dx > radii) {
return false;
@@ -778,6 +248,9 @@ void VoiceRecordButton::requestPaintProgress(float64 progress) {
}
void VoiceRecordButton::requestPaintColor(float64 progress) {
if (_colorProgress == progress) {
return;
}
_colorProgress = progress;
update();
}

View File

@@ -11,9 +11,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/animations.h"
#include "ui/rp_widget.h"
namespace HistoryView::Controls {
namespace Ui {
namespace Paint {
class Blobs;
} // namespace Paint
} // namespace Ui
class RecordCircle;
namespace HistoryView::Controls {
class VoiceRecordButton final : public Ui::AbstractButton {
public:
@@ -34,25 +38,28 @@ public:
void requestPaintLevel(quint16 level);
[[nodiscard]] rpl::producer<bool> actives() const;
[[nodiscard]] rpl::producer<> clicks() const;
[[nodiscard]] bool inCircle(const QPoint &localPos) const;
private:
void init();
rpl::event_stream<crl::time> _recordAnimationTicked;
std::unique_ptr<RecordCircle> _recordCircle;
std::unique_ptr<Ui::Paint::Blobs> _blobs;
crl::time _lastUpdateTime = 0;
crl::time _blobsHideLastTime = 0;
const int _center;
rpl::variable<float64> _showProgress = 0.;
rpl::variable<float64> _colorProgress = 0.;
float64 _colorProgress = 0.;
rpl::variable<bool> _inCircle = false;
rpl::variable<Type> _state = Type::Record;
// This can animate for a very long time (like in music playing),
// so it should be a Basic, not a Simple animation.
Ui::Animations::Basic _recordingAnimation;
Ui::Animations::Basic _animation;
Ui::Animations::Simple _stateChangedAnimation;
};
} // namespace HistoryView::Controls

View File

@@ -287,7 +287,7 @@ void AddPostLinkAction(
: Context::History;
menu->addAction(
(item->history()->peer->isMegagroup()
? tr::lng_context_copy_link
? tr::lng_context_copy_message_link
: tr::lng_context_copy_post_link)(tr::now),
[=] { CopyPostLink(session, itemId, context); });
}

View File

@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_group_call.h"
#include "main/main_session.h"
#include "ui/chat/group_call_bar.h"
#include "ui/chat/group_call_userpics.h"
#include "ui/painter.h"
#include "calls/calls_group_call.h"
#include "calls/calls_instance.h"
@@ -24,7 +25,7 @@ namespace HistoryView {
void GenerateUserpicsInRow(
QImage &result,
const std::vector<UserpicInRow> &list,
const UserpicsInRowStyle &st,
const style::GroupCallUserpics &st,
int maxElements) {
const auto count = int(list.size());
if (!count) {
@@ -67,7 +68,7 @@ GroupCallTracker::GroupCallTracker(not_null<PeerData*> peer)
rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
not_null<Data::GroupCall*> call,
const UserpicsInRowStyle &st) {
int userpicSize) {
struct State {
std::vector<UserpicInRow> userpics;
Ui::GroupCallBarContent current;
@@ -130,7 +131,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
static const auto RegenerateUserpics = [](
not_null<State*> state,
not_null<Data::GroupCall*> call,
const UserpicsInRowStyle &st,
int userpicSize,
bool force = false) {
const auto result = FillMissingUserpics(state, call) || force;
if (!result) {
@@ -141,7 +142,9 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
state->someUserpicsNotLoaded = false;
for (auto &userpic : state->userpics) {
userpic.peer->loadUserpic();
const auto pic = userpic.peer->genUserpic(userpic.view, st.size);
const auto pic = userpic.peer->genUserpic(
userpic.view,
userpicSize);
userpic.uniqueKey = userpic.peer->userpicUniqueKey(userpic.view);
state->current.users.push_back({
.userpic = pic.toImage(),
@@ -161,7 +164,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
not_null<State*> state,
not_null<Data::GroupCall*> call,
not_null<UserData*> user,
const UserpicsInRowStyle &st) {
int userpicSize) {
const auto i = ranges::find(
state->userpics,
user,
@@ -170,7 +173,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
return false;
}
state->userpics.erase(i);
RegenerateUserpics(state, call, st, true);
RegenerateUserpics(state, call, userpicSize, true);
return true;
};
@@ -178,7 +181,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
not_null<State*> state,
not_null<Data::GroupCall*> call,
not_null<UserData*> user,
const UserpicsInRowStyle &st) {
int userpicSize) {
Expects(state->userpics.size() <= kLimit);
const auto &participants = call->participants();
@@ -237,7 +240,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
}
Assert(state->userpics.size() <= kLimit);
}
RegenerateUserpics(state, call, st, true);
RegenerateUserpics(state, call, userpicSize, true);
return true;
};
@@ -262,12 +265,12 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
) | rpl::start_with_next([=](const ParticipantUpdate &update) {
const auto user = update.now ? update.now->user : update.was->user;
if (!update.now) {
if (RemoveUserpic(state, call, user, st)) {
if (RemoveUserpic(state, call, user, userpicSize)) {
pushNext();
}
} else if (update.now->speaking
&& (!update.was || !update.was->speaking)) {
if (CheckPushToFront(state, call, user, st)) {
if (CheckPushToFront(state, call, user, userpicSize)) {
pushNext();
}
} else {
@@ -287,7 +290,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
updateSpeakingState = false;
}
}
if (RegenerateUserpics(state, call, st)
if (RegenerateUserpics(state, call, userpicSize)
|| updateSpeakingState) {
pushNext();
}
@@ -296,7 +299,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
call->participantsSliceAdded(
) | rpl::filter([=] {
return RegenerateUserpics(state, call, st);
return RegenerateUserpics(state, call, userpicSize);
}) | rpl::start_with_next(pushNext, lifetime);
call->peer()->session().downloaderTaskFinished(
@@ -306,14 +309,14 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
for (const auto &userpic : state->userpics) {
if (userpic.peer->userpicUniqueKey(userpic.view)
!= userpic.uniqueKey) {
RegenerateUserpics(state, call, st, true);
RegenerateUserpics(state, call, userpicSize, true);
pushNext();
return;
}
}
}, lifetime);
RegenerateUserpics(state, call, st);
RegenerateUserpics(state, call, userpicSize);
call->fullCountValue(
) | rpl::start_with_next([=](int count) {
@@ -345,12 +348,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::content() const {
} else if (!call->fullCount() && !call->participantsLoaded()) {
call->reload();
}
const auto st = UserpicsInRowStyle{
.size = st::historyGroupCallUserpicSize,
.shift = st::historyGroupCallUserpicShift,
.stroke = st::historyGroupCallUserpicStroke,
};
return ContentByCall(call, st);
return ContentByCall(call, st::historyGroupCallUserpics.size);
}) | rpl::flatten_latest();
}

View File

@@ -18,6 +18,10 @@ class GroupCall;
class CloudImageView;
} // namespace Data
namespace style {
struct GroupCallUserpics;
} // namespace style
namespace HistoryView {
struct UserpicInRow {
@@ -27,16 +31,10 @@ struct UserpicInRow {
mutable InMemoryKey uniqueKey;
};
struct UserpicsInRowStyle {
int size = 0;
int shift = 0;
int stroke = 0;
};
void GenerateUserpicsInRow(
QImage &result,
const std::vector<UserpicInRow> &list,
const UserpicsInRowStyle &st,
const style::GroupCallUserpics &st,
int maxElements = 0);
class GroupCallTracker final {
@@ -48,7 +46,7 @@ public:
[[nodiscard]] static rpl::producer<Ui::GroupCallBarContent> ContentByCall(
not_null<Data::GroupCall*> call,
const UserpicsInRowStyle &st);
int userpicSize);
private:
const not_null<PeerData*> _peer;

View File

@@ -795,8 +795,8 @@ void Message::paintCommentsButton(
auto &list = _comments->userpics;
const auto limit = HistoryMessageViews::kMaxRecentRepliers;
const auto count = std::min(int(views->recentRepliers.size()), limit);
const auto single = st::historyCommentsUserpicSize;
const auto shift = st::historyCommentsUserpicOverlap;
const auto single = st::historyCommentsUserpics.size;
const auto shift = st::historyCommentsUserpics.shift;
const auto regenerate = [&] {
if (list.size() != count) {
return true;
@@ -828,12 +828,11 @@ void Message::paintCommentsButton(
while (list.size() > count) {
list.pop_back();
}
const auto st = UserpicsInRowStyle{
.size = single,
.shift = shift,
.stroke = st::historyCommentsUserpicStroke,
};
GenerateUserpicsInRow(_comments->cachedUserpics, list, st, limit);
GenerateUserpicsInRow(
_comments->cachedUserpics,
list,
st::historyCommentsUserpics,
limit);
}
p.drawImage(
left,
@@ -2135,8 +2134,8 @@ int Message::minWidthForMedia() const {
const auto views = data()->Get<HistoryMessageViews>();
if (data()->repliesAreComments() && !views->replies.text.isEmpty()) {
const auto limit = HistoryMessageViews::kMaxRecentRepliers;
const auto single = st::historyCommentsUserpicSize;
const auto shift = st::historyCommentsUserpicOverlap;
const auto single = st::historyCommentsUserpics.size;
const auto shift = st::historyCommentsUserpics.shift;
const auto added = single
+ (limit - 1) * (single - shift)
+ st::historyCommentsSkipLeft

View File

@@ -258,10 +258,6 @@ int InnerWidget::peerListSelectedRowsCount() {
return 0;
}
std::vector<not_null<PeerData*>> InnerWidget::peerListCollectSelectedRows() {
return {};
}
void InnerWidget::peerListScrollToTop() {
_scrollToRequests.fire({ -1, -1 });
}

View File

@@ -52,7 +52,6 @@ private:
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
int peerListSelectedRowsCount() override;
std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override;
void peerListScrollToTop() override;
void peerListAddSelectedPeerInBunch(
not_null<PeerData*> peer) override;

View File

@@ -57,7 +57,6 @@ public:
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
int peerListSelectedRowsCount() override;
std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override;
void peerListScrollToTop() override;
void peerListAddSelectedPeerInBunch(
not_null<PeerData*> peer) override;
@@ -130,11 +129,6 @@ int ListDelegate::peerListSelectedRowsCount() {
return 0;
}
auto ListDelegate::peerListCollectSelectedRows()
-> std::vector<not_null<PeerData*>> {
return {};
}
void ListDelegate::peerListScrollToTop() {
}

View File

@@ -493,7 +493,7 @@ void ActionsFiller::addInviteToGroupAction(
_wrap,
tr::lng_profile_invite_to_group(),
CanInviteBotToGroupValue(user),
[=] { AddBotToGroupBoxController::Start(controller, user); });
[=] { AddBotToGroupBoxController::Start(user); });
}
void ActionsFiller::addShareContactAction(not_null<UserData*> user) {

View File

@@ -423,10 +423,6 @@ int Members::peerListSelectedRowsCount() {
return 0;
}
std::vector<not_null<PeerData*>> Members::peerListCollectSelectedRows() {
return {};
}
void Members::peerListScrollToTop() {
_scrollToRequests.fire({ -1, -1 });
}

View File

@@ -63,7 +63,6 @@ private:
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
int peerListSelectedRowsCount() override;
std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override;
void peerListScrollToTop() override;
void peerListAddSelectedPeerInBunch(
not_null<PeerData*> peer) override;

View File

@@ -193,9 +193,6 @@ private:
GroupThumbs::Thumb::Thumb(Key key, Fn<void()> handler)
: _key(key) {
_link = std::make_shared<LambdaClickHandler>(std::move(handler));
_fullWidth = std::min(
wantedPixSize().width(),
st::mediaviewGroupWidthMax);
validateImage();
}
@@ -208,9 +205,6 @@ GroupThumbs::Thumb::Thumb(
, _photoMedia(photo->createMediaView())
, _origin(origin) {
_link = std::make_shared<LambdaClickHandler>(std::move(handler));
_fullWidth = std::min(
wantedPixSize().width(),
st::mediaviewGroupWidthMax);
_photoMedia->wanted(Data::PhotoSize::Thumbnail, origin);
validateImage();
}
@@ -224,9 +218,6 @@ GroupThumbs::Thumb::Thumb(
, _documentMedia(document->createMediaView())
, _origin(origin) {
_link = std::make_shared<LambdaClickHandler>(std::move(handler));
_fullWidth = std::min(
wantedPixSize().width(),
st::mediaviewGroupWidthMax);
_documentMedia->thumbnailWanted(origin);
validateImage();
}
@@ -257,7 +248,8 @@ void GroupThumbs::Thumb::validateImage() {
const auto originalHeight = _image->height();
const auto takeWidth = originalWidth * st::mediaviewGroupWidthMax
/ pixSize.width();
const auto original = _image->original();
auto original = _image->original();
original.setDevicePixelRatio(cRetinaFactor());
_full = App::pixmapFromImageInPlace(original.copy(
(originalWidth - takeWidth) / 2,
0,
@@ -274,6 +266,9 @@ void GroupThumbs::Thumb::validateImage() {
pixSize.height() * cIntRetinaFactor(),
Images::Option::Smooth);
}
_fullWidth = std::min(
wantedPixSize().width(),
st::mediaviewGroupWidthMax);
}
int GroupThumbs::Thumb::leftToUpdate() const {

View File

@@ -606,28 +606,10 @@ void OverlayWidget::updateDocSize() {
return;
}
if (_document->loading()) {
quint64 ready = _document->loadOffset(), total = _document->size;
QString readyStr, totalStr, mb;
if (total >= 1024 * 1024) { // more than 1 mb
qint64 readyTenthMb = (ready * 10 / (1024 * 1024)), totalTenthMb = (total * 10 / (1024 * 1024));
readyStr = QString::number(readyTenthMb / 10) + '.' + QString::number(readyTenthMb % 10);
totalStr = QString::number(totalTenthMb / 10) + '.' + QString::number(totalTenthMb % 10);
mb = qsl("MB");
} else if (total >= 1024) {
qint64 readyKb = (ready / 1024), totalKb = (total / 1024);
readyStr = QString::number(readyKb);
totalStr = QString::number(totalKb);
mb = qsl("KB");
} else {
readyStr = QString::number(ready);
totalStr = QString::number(total);
mb = qsl("B");
}
_docSize = tr::lng_media_save_progress(tr::now, lt_ready, readyStr, lt_total, totalStr, lt_mb, mb);
} else {
_docSize = Ui::FormatSizeText(_document->size);
}
const auto size = _document->size;
_docSize = _document->loading()
? Ui::FormatProgressText(_document->loadOffset(), size)
: Ui::FormatSizeText(size);
_docSizeWidth = st::mediaviewFont->width(_docSize);
int32 maxw = st::mediaviewFileSize.width() - st::mediaviewFileIconSize - st::mediaviewFilePadding * 3;
if (_docSizeWidth > maxw) {
@@ -739,13 +721,7 @@ void OverlayWidget::updateControls() {
}
return dNow;
}();
if (d.date() == dNow.date()) {
_dateText = tr::lng_mediaview_today(tr::now, lt_time, d.time().toString(cTimeFormat()));
} else if (d.date().addDays(1) == dNow.date()) {
_dateText = tr::lng_mediaview_yesterday(tr::now, lt_time, d.time().toString(cTimeFormat()));
} else {
_dateText = tr::lng_mediaview_date_time(tr::now, lt_date, d.date().toString(qsl("dd.MM.yy")), lt_time, d.time().toString(cTimeFormat()));
}
_dateText = Ui::FormatDateTime(d, cTimeFormat());
if (!_fromName.isEmpty()) {
_fromNameLabel.setText(st::mediaviewTextStyle, _fromName, Ui::NameTextOptions());
_nameNav = myrtlrect(st::mediaviewTextLeft, height() - st::mediaviewTextTop, qMin(_fromNameLabel.maxWidth(), width() / 3), st::mediaviewFont->height);

View File

@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/linux/linux_gdk_helper.h"
#include "platform/linux/linux_libs.h"
#include "base/platform/base_platform_info.h"
#include "base/platform/linux/base_xcb_utilities_linux.h"
extern "C" {
@@ -78,17 +79,19 @@ bool GdkHelperLoaded() {
void XSetTransientForHint(GdkWindow *window, quintptr winId) {
if (gdk_helper_loaded == GtkLoaded::Gtk2) {
xcb_change_property(
base::Platform::XCB::GetConnectionFromQt(),
XCB_PROP_MODE_REPLACE,
gdk_x11_drawable_get_xid(window),
XCB_ATOM_WM_TRANSIENT_FOR,
XCB_ATOM_WINDOW,
32,
1,
&winId);
if (!IsWayland()) {
xcb_change_property(
base::Platform::XCB::GetConnectionFromQt(),
XCB_PROP_MODE_REPLACE,
gdk_x11_drawable_get_xid(window),
XCB_ATOM_WM_TRANSIENT_FOR,
XCB_ATOM_WINDOW,
32,
1,
&winId);
}
} else if (gdk_helper_loaded == GtkLoaded::Gtk3) {
if (gdk_is_x11_window_check(window)) {
if (!IsWayland() && gdk_is_x11_window_check(window)) {
xcb_change_property(
base::Platform::XCB::GetConnectionFromQt(),
XCB_PROP_MODE_REPLACE,

View File

@@ -209,6 +209,10 @@ QIcon TrayIconGen(int counter, bool muted) {
48,
};
static const auto dprSize = [](const QImage &image) {
return image.size() / image.devicePixelRatio();
};
for (const auto iconSize : iconSizes) {
auto &currentImageBack = TrayIconImageBack[iconSize];
const auto desiredSize = QSize(iconSize, iconSize);
@@ -221,11 +225,17 @@ QIcon TrayIconGen(int counter, bool muted) {
systemIcon = QIcon::fromTheme(iconName);
}
if (systemIcon.actualSize(desiredSize) == desiredSize) {
currentImageBack = systemIcon
.pixmap(desiredSize)
.toImage();
} else {
// We can't use QIcon::actualSize here
// since it works incorrectly with svg icon themes
currentImageBack = systemIcon
.pixmap(desiredSize)
.toImage();
const auto firstAttemptSize = dprSize(currentImageBack);
// if current icon theme is not a svg one, Qt can return
// a pixmap that less in size even if there are a bigger one
if (firstAttemptSize.width() < desiredSize.width()) {
const auto availableSizes = systemIcon.availableSizes();
const auto biggestSize = ranges::max_element(
@@ -233,18 +243,17 @@ QIcon TrayIconGen(int counter, bool muted) {
std::less<>(),
&QSize::width);
currentImageBack = systemIcon
.pixmap(*biggestSize)
.toImage();
if ((*biggestSize).width() > firstAttemptSize.width()) {
currentImageBack = systemIcon
.pixmap(*biggestSize)
.toImage();
}
}
} else {
currentImageBack = Core::App().logo();
}
const auto currentImageBackSize = currentImageBack.size()
/ currentImageBack.devicePixelRatio();
if (currentImageBackSize != desiredSize) {
if (dprSize(currentImageBack) != desiredSize) {
currentImageBack = currentImageBack.scaled(
desiredSize * currentImageBack.devicePixelRatio(),
Qt::IgnoreAspectRatio,
@@ -331,17 +340,33 @@ std::unique_ptr<QTemporaryFile> TrayIconFile(
static const auto templateName = AppRuntimeDirectory()
+ kTrayIconFilename.utf16();
static const auto dprSize = [](const QPixmap &pixmap) {
return pixmap.size() / pixmap.devicePixelRatio();
};
static const auto desiredSize = QSize(22, 22);
static const auto scalePixmap = [=](const QPixmap &pixmap) {
if (dprSize(pixmap) != desiredSize) {
return pixmap.scaled(
desiredSize * pixmap.devicePixelRatio(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
} else {
return pixmap;
}
};
auto ret = std::make_unique<QTemporaryFile>(
templateName,
parent);
ret->open();
if (icon.actualSize(desiredSize) == desiredSize) {
icon.pixmap(desiredSize).save(ret.get());
} else {
const auto firstAttempt = icon.pixmap(desiredSize);
const auto firstAttemptSize = dprSize(firstAttempt);
if (firstAttemptSize.width() < desiredSize.width()) {
const auto availableSizes = icon.availableSizes();
const auto biggestSize = ranges::max_element(
@@ -349,14 +374,13 @@ std::unique_ptr<QTemporaryFile> TrayIconFile(
std::less<>(),
&QSize::width);
const auto iconPixmap = icon.pixmap(*biggestSize);
iconPixmap
.scaled(
desiredSize * iconPixmap.devicePixelRatio(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation)
.save(ret.get());
if ((*biggestSize).width() > firstAttemptSize.width()) {
scalePixmap(icon.pixmap(*biggestSize)).save(ret.get());
} else {
scalePixmap(firstAttempt).save(ret.get());
}
} else {
scalePixmap(firstAttempt).save(ret.get());
}
ret->close();

View File

@@ -42,7 +42,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
#include <xcb/xcb.h>
#include <xcb/screensaver.h>
#include <glib.h>

View File

@@ -1,63 +0,0 @@
/*
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 <QtCore/QtPlugin>
#ifndef DESKTOP_APP_USE_PACKAGED
Q_IMPORT_PLUGIN(QWebpPlugin)
#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
Q_IMPORT_PLUGIN(QJpegPlugin)
Q_IMPORT_PLUGIN(QGifPlugin)
#endif // Qt 5.8.0
#ifdef Q_OS_WIN
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
#elif defined Q_OS_MAC // Q_OS_WIN
Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin)
Q_IMPORT_PLUGIN(QGenericEnginePlugin)
#elif defined Q_OS_UNIX // Q_OS_WIN | Q_OS_MAC
Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)
Q_IMPORT_PLUGIN(QGenericEnginePlugin)
Q_IMPORT_PLUGIN(QComposePlatformInputContextPlugin)
Q_IMPORT_PLUGIN(QSvgIconPlugin)
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
Q_IMPORT_PLUGIN(QConnmanEnginePlugin)
Q_IMPORT_PLUGIN(QNetworkManagerEnginePlugin)
Q_IMPORT_PLUGIN(QIbusPlatformInputContextPlugin)
Q_IMPORT_PLUGIN(QXdgDesktopPortalThemePlugin)
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
#ifndef DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION
Q_IMPORT_PLUGIN(QWaylandEglClientBufferPlugin)
Q_IMPORT_PLUGIN(QWaylandIviShellIntegrationPlugin)
Q_IMPORT_PLUGIN(QWaylandWlShellIntegrationPlugin)
Q_IMPORT_PLUGIN(QWaylandXdgShellIntegrationPlugin)
Q_IMPORT_PLUGIN(QWaylandBradientDecorationPlugin)
Q_IMPORT_PLUGIN(QWaylandIntegrationPlugin)
Q_IMPORT_PLUGIN(QWaylandEglPlatformIntegrationPlugin)
#endif // !DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION
#endif // Q_OS_WIN | Q_OS_MAC | Q_OS_UNIX
#endif // !DESKTOP_APP_USE_PACKAGED
#if defined Q_OS_UNIX && !defined Q_OS_MAC
#if !defined DESKTOP_APP_USE_PACKAGED || defined DESKTOP_APP_USE_PACKAGED_LAZY
Q_IMPORT_PLUGIN(NimfInputContextPlugin)
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
Q_IMPORT_PLUGIN(QFcitxPlatformInputContextPlugin)
Q_IMPORT_PLUGIN(QFcitx5PlatformInputContextPlugin)
Q_IMPORT_PLUGIN(QHimePlatformInputContextPlugin)
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
#ifndef DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION
Q_IMPORT_PLUGIN(QWaylandMaterialDecorationPlugin)
#endif // !DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION
#endif // !DESKTOP_APP_USE_PACKAGED || DESKTOP_APP_USE_PACKAGED_LAZY
#if !defined DESKTOP_APP_USE_PACKAGED || defined DESKTOP_APP_USE_PACKAGED_LAZY_PLATFORMTHEMES
Q_IMPORT_PLUGIN(Qt5CTPlatformThemePlugin)
Q_IMPORT_PLUGIN(Qt5CTStylePlugin)
#endif // !DESKTOP_APP_USE_PACKAGED || DESKTOP_APP_USE_PACKAGED_LAZY_PLATFORMTHEMES
#endif // Q_OS_UNIX && !Q_OS_MAC

View File

@@ -51,8 +51,7 @@ class BlockPeerBoxController
: public ChatsListBoxController
, private base::Subscriber {
public:
explicit BlockPeerBoxController(
not_null<Window::SessionNavigation*> navigation);
explicit BlockPeerBoxController(not_null<Main::Session*> session);
Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override;
@@ -72,19 +71,19 @@ protected:
private:
void updateIsBlocked(not_null<PeerListRow*> row, PeerData *peer) const;
const not_null<Window::SessionNavigation*> _navigation;
const not_null<Main::Session*> _session;
Fn<void(not_null<PeerData*> peer)> _blockPeerCallback;
};
BlockPeerBoxController::BlockPeerBoxController(
not_null<Window::SessionNavigation*> navigation)
: ChatsListBoxController(navigation)
, _navigation(navigation) {
not_null<Main::Session*> session)
: ChatsListBoxController(session)
, _session(session) {
}
Main::Session &BlockPeerBoxController::session() const {
return _navigation->session();
return *_session;
}
void BlockPeerBoxController::prepareViewHook() {
@@ -294,7 +293,8 @@ void BlockedBoxController::handleBlockedEvent(not_null<PeerData*> user) {
void BlockedBoxController::BlockNewPeer(
not_null<Window::SessionController*> window) {
auto controller = std::make_unique<BlockPeerBoxController>(window);
auto controller = std::make_unique<BlockPeerBoxController>(
&window->session());
auto initBox = [=, controller = controller.get()](
not_null<PeerListBox*> box) {
controller->setBlockPeerCallback([=](not_null<PeerData*> peer) {

View File

@@ -38,7 +38,9 @@ bool HasExtensionFrom(const QString &file, const QStringList &extensions) {
bool ValidPhotoForAlbum(
const PreparedFileInformation::Image &image,
const QString &mime) {
if (image.animated || Core::IsMimeSticker(mime)) {
if (image.animated
|| Core::IsMimeSticker(mime)
|| (mime == u"application/pdf"_q)) {
return false;
}
const auto width = image.data.width();

View File

@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/toast/toast.h"
#include "ui/widgets/input_fields.h"
#include "ui/chat/attach/attach_prepare.h"
#include "ui/text/format_values.h"
#include "ui/text/text_entity.h"
#include "ui/text/text_options.h"
#include "chat_helpers/message_field.h"
@@ -135,29 +136,6 @@ void EditInfoBox::setInnerFocus() {
_field->setFocusFast();
}
QString FormatDateTime(TimeId value) {
const auto now = QDateTime::currentDateTime();
const auto date = base::unixtime::parse(value);
if (date.date() == now.date()) {
return tr::lng_mediaview_today(
tr::now,
lt_time,
date.time().toString(cTimeFormat()));
} else if (date.date().addDays(1) == now.date()) {
return tr::lng_mediaview_yesterday(
tr::now,
lt_time,
date.time().toString(cTimeFormat()));
} else {
return tr::lng_mediaview_date_time(
tr::now,
lt_date,
date.date().toString(qsl("dd.MM.yy")),
lt_time,
date.time().toString(cTimeFormat()));
}
}
uint32 OccupationTag() {
return uint32(Core::Sandbox::Instance().installationTag() & 0xFFFFFFFF);
}
@@ -484,7 +462,10 @@ rpl::producer<QString> Helper::infoLabelValue(
return infoValue(
user
) | rpl::map([](const Support::UserInfo &info) {
return info.author + ", " + FormatDateTime(info.date);
const auto time = Ui::FormatDateTime(
base::unixtime::parse(info.date),
cTimeFormat());
return info.author + ", " + time;
});
}

View File

@@ -18,6 +18,13 @@ MessageBar {
duration: int;
}
GroupCallUserpics {
size: pixels;
shift: pixels;
stroke: pixels;
align: align;
}
defaultMessageBar: MessageBar {
title: semiboldTextStyle;
titleFg: windowActiveTextFg;
@@ -345,14 +352,12 @@ historyRecordFont: font(13px);
historyRecordDurationSkip: 12px;
historyRecordDurationFg: historyComposeAreaFg;
historyRecordLevelMainRadius: 23px;
historyRecordLevelMainRadiusAmplitude: 14px;
historyRecordMajorAmplitudeRadius: 14px;
historyRecordMinorAmplitudeRadius: 7px;
historyRecordRandomAddition: 8px;
historyRecordRadiusDiff: 50px;
historyRecordRadiusDiffMin: 10px;
historyRecordLevelMaxRadius: 70px;
historyRecordMainBlobMinRadius: 23px;
historyRecordMainBlobMaxRadius: 37px;
historyRecordMinorBlobMinRadius: 40px;
historyRecordMinorBlobMaxRadius: 47px;
historyRecordMajorBlobMinRadius: 43px;
historyRecordMajorBlobMaxRadius: 50px;
historyRecordTextStyle: TextStyle(defaultTextStyle) {
font: historyRecordFont;
@@ -741,10 +746,13 @@ historyPollInChosenSelected: icon {{ "poll_select_check", historyFileInIconFgSel
historyCommentsButtonHeight: 40px;
historyCommentsSkipLeft: 9px;
historyCommentsSkipText: 10px;
historyCommentsUserpicSize: 25px;
historyCommentsUserpicStroke: 2px;
historyCommentsUserpicOverlap: 6px;
historyCommentsSkipRight: 8px;
historyCommentsUserpics: GroupCallUserpics {
size: 25px;
shift: 6px;
stroke: 2px;
align: align(left);
}
boxAttachEmoji: IconButton(historyAttachEmoji) {
width: 30px;
@@ -800,9 +808,12 @@ historyCommentsOpenOutSelected: icon {{ "history_comments_open", msgFileThumbLin
historySlowmodeCounterMargins: margins(0px, 0px, 10px, 0px);
historyGroupCallUserpicSize: 32px;
historyGroupCallUserpicShift: 12px;
historyGroupCallUserpicStroke: 4px;
historyGroupCallUserpics: GroupCallUserpics {
size: 32px;
shift: 12px;
stroke: 4px;
align: align(top);
}
historyGroupCallBlobMinRadius: 23px;
historyGroupCallBlobMaxRadius: 25px;

View File

@@ -7,12 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "ui/chat/group_call_bar.h"
#include "ui/chat/message_bar.h"
#include "ui/chat/group_call_userpics.h"
#include "ui/widgets/shadow.h"
#include "ui/widgets/buttons.h"
#include "ui/paint/blobs.h"
#include "lang/lang_keys.h"
#include "base/openssl_help.h"
#include "styles/style_chat.h"
#include "styles/style_calls.h"
#include "styles/style_info.h" // st::topBarArrowPadding, like TopBarWidget.
@@ -21,71 +19,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QtEvents>
namespace Ui {
namespace {
constexpr auto kDuration = 160;
constexpr auto kMaxUserpics = 4;
constexpr auto kWideScale = 5;
constexpr auto kBlobsEnterDuration = crl::time(250);
constexpr auto kLevelDuration = 100. + 500. * 0.23;
constexpr auto kBlobScale = 0.605;
constexpr auto kMinorBlobFactor = 0.9f;
constexpr auto kUserpicMinScale = 0.8;
constexpr auto kMaxLevel = 1.;
constexpr auto kSendRandomLevelInterval = crl::time(100);
auto Blobs()->std::array<Ui::Paint::Blobs::BlobData, 2> {
return { {
{
.segmentsCount = 6,
.minScale = kBlobScale * kMinorBlobFactor,
.minRadius = st::historyGroupCallBlobMinRadius * kMinorBlobFactor,
.maxRadius = st::historyGroupCallBlobMaxRadius * kMinorBlobFactor,
.speedScale = 1.,
.alpha = .5,
},
{
.segmentsCount = 8,
.minScale = kBlobScale,
.minRadius = (float)st::historyGroupCallBlobMinRadius,
.maxRadius = (float)st::historyGroupCallBlobMaxRadius,
.speedScale = 1.,
.alpha = .2,
},
} };
}
} // namespace
struct GroupCallBar::BlobsAnimation {
BlobsAnimation(
std::vector<Ui::Paint::Blobs::BlobData> blobDatas,
float levelDuration,
float maxLevel)
: blobs(std::move(blobDatas), levelDuration, maxLevel) {
}
Ui::Paint::Blobs blobs;
crl::time lastTime = 0;
crl::time lastSpeakingUpdateTime = 0;
float64 enter = 0.;
};
struct GroupCallBar::Userpic {
User data;
std::pair<uint64, uint64> cacheKey;
crl::time speakingStarted = 0;
QImage cache;
Animations::Simple leftAnimation;
Animations::Simple shownAnimation;
std::unique_ptr<BlobsAnimation> blobsAnimation;
int left = 0;
bool positionInited = false;
bool topMost = false;
bool hiding = false;
bool cacheMasked = false;
};
GroupCallBar::GroupCallBar(
not_null<QWidget*> parent,
@@ -98,16 +31,13 @@ GroupCallBar::GroupCallBar(
tr::lng_group_call_join(),
st::groupCallTopBarJoin))
, _shadow(std::make_unique<PlainShadow>(_wrap.parentWidget()))
, _randomSpeakingTimer([=] { sendRandomLevels(); }) {
, _userpics(std::make_unique<GroupCallUserpics>(
st::historyGroupCallUserpics,
std::move(hideBlobs),
[=] { updateUserpics(); })) {
_wrap.hide(anim::type::instant);
_shadow->hide();
const auto limit = kMaxUserpics;
const auto single = st::historyGroupCallUserpicSize;
const auto shift = st::historyGroupCallUserpicShift;
// + 1 * single for the blobs.
_maxUserpicsWidth = 2 * single + (limit - 1) * (single - shift);
_wrap.entity()->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
QPainter(_wrap.entity()).fillRect(clip, st::historyPinnedBg);
@@ -122,7 +52,7 @@ GroupCallBar::GroupCallBar(
copy
) | rpl::start_with_next([=](GroupCallBarContent &&content) {
_content = content;
updateUserpicsFromContent();
_userpics->update(_content.users, !_wrap.isHidden());
_inner->update();
}, lifetime());
@@ -140,53 +70,10 @@ GroupCallBar::GroupCallBar(
_wrap.toggle(false, anim::type::normal);
}, lifetime());
style::PaletteChanged(
) | rpl::start_with_next([=] {
for (auto &userpic : _userpics) {
userpic.cache = QImage();
}
}, lifetime());
_speakingAnimation.init([=](crl::time now) {
if (const auto &last = _speakingAnimationHideLastTime; (last > 0)
&& (now - last >= kBlobsEnterDuration)) {
_speakingAnimation.stop();
}
for (auto &userpic : _userpics) {
if (const auto blobs = userpic.blobsAnimation.get()) {
blobs->blobs.updateLevel(now - blobs->lastTime);
blobs->lastTime = now;
}
}
updateUserpics();
});
rpl::combine(
rpl::single(anim::Disabled()) | rpl::then(anim::Disables()),
std::move(hideBlobs)
) | rpl::start_with_next([=](bool animDisabled, bool deactivated) {
const auto hide = animDisabled || deactivated;
if (!(hide && _speakingAnimationHideLastTime)) {
_speakingAnimationHideLastTime = hide ? crl::now() : 0;
}
_skipLevelUpdate = hide;
for (auto &userpic : _userpics) {
if (const auto blobs = userpic.blobsAnimation.get()) {
blobs->blobs.setLevel(0.);
}
}
if (!hide && !_speakingAnimation.animating()) {
_speakingAnimation.start();
}
_skipLevelUpdate = hide;
}, lifetime());
setupInner();
}
GroupCallBar::~GroupCallBar() {
}
GroupCallBar::~GroupCallBar() = default;
void GroupCallBar::setupInner() {
_inner->resize(0, st::historyReplyHeight);
@@ -252,142 +139,11 @@ void GroupCallBar::paint(Painter &p) {
? tr::lng_group_call_members(tr::now, lt_count, _content.count)
: tr::lng_group_call_no_members(tr::now)));
const auto size = st::historyGroupCallUserpics.size;
// Skip shadow of the bar above.
paintUserpics(p);
}
void GroupCallBar::paintUserpics(Painter &p) {
const auto top = (st::historyReplyHeight
- st::lineWidth
- st::historyGroupCallUserpicSize) / 2 + st::lineWidth;
const auto middle = _inner->width() / 2;
const auto size = st::historyGroupCallUserpicSize;
const auto factor = style::DevicePixelRatio();
const auto &minScale = kUserpicMinScale;
for (auto &userpic : ranges::view::reverse(_userpics)) {
const auto shown = userpic.shownAnimation.value(
userpic.hiding ? 0. : 1.);
if (shown == 0.) {
continue;
}
validateUserpicCache(userpic);
p.setOpacity(shown);
const auto left = middle + userpic.leftAnimation.value(userpic.left);
const auto blobs = userpic.blobsAnimation.get();
const auto shownScale = 0.5 + shown / 2.;
const auto scale = shownScale * (!blobs
? 1.
: (minScale
+ (1. - minScale) * (_speakingAnimationHideLastTime
? (1. - blobs->blobs.currentLevel())
: blobs->blobs.currentLevel())));
if (blobs) {
auto hq = PainterHighQualityEnabler(p);
const auto shift = QPointF(left + size / 2., top + size / 2.);
p.translate(shift);
blobs->blobs.paint(p, st::windowActiveTextFg);
p.translate(-shift);
p.setOpacity(1.);
}
if (std::abs(scale - 1.) < 0.001) {
const auto skip = ((kWideScale - 1) / 2) * size * factor;
p.drawImage(
QRect(left, top, size, size),
userpic.cache,
QRect(skip, skip, size * factor, size * factor));
} else {
auto hq = PainterHighQualityEnabler(p);
auto target = QRect(
left + (1 - kWideScale) / 2 * size,
top + (1 - kWideScale) / 2 * size,
kWideScale * size,
kWideScale * size);
auto shrink = anim::interpolate(
(1 - kWideScale) / 2 * size,
0,
scale);
auto margins = QMargins(shrink, shrink, shrink, shrink);
p.drawImage(target.marginsAdded(margins), userpic.cache);
}
}
p.setOpacity(1.);
const auto hidden = [](const Userpic &userpic) {
return userpic.hiding && !userpic.shownAnimation.animating();
};
_userpics.erase(ranges::remove_if(_userpics, hidden), end(_userpics));
}
bool GroupCallBar::needUserpicCacheRefresh(Userpic &userpic) {
if (userpic.cache.isNull()) {
return true;
} else if (userpic.hiding) {
return false;
} else if (userpic.cacheKey != userpic.data.userpicKey) {
return true;
}
const auto shouldBeMasked = !userpic.topMost;
if (userpic.cacheMasked == shouldBeMasked || !shouldBeMasked) {
return true;
}
return !userpic.leftAnimation.animating();
}
void GroupCallBar::ensureBlobsAnimation(Userpic &userpic) {
if (userpic.blobsAnimation) {
return;
}
userpic.blobsAnimation = std::make_unique<BlobsAnimation>(
Blobs() | ranges::to_vector,
kLevelDuration,
kMaxLevel);
userpic.blobsAnimation->lastTime = crl::now();
}
void GroupCallBar::sendRandomLevels() {
if (_skipLevelUpdate) {
return;
}
for (auto &userpic : _userpics) {
if (const auto blobs = userpic.blobsAnimation.get()) {
const auto value = 30 + (openssl::RandomValue<uint32>() % 70);
userpic.blobsAnimation->blobs.setLevel(float64(value) / 100.);
}
}
}
void GroupCallBar::validateUserpicCache(Userpic &userpic) {
if (!needUserpicCacheRefresh(userpic)) {
return;
}
const auto factor = style::DevicePixelRatio();
const auto size = st::historyGroupCallUserpicSize;
const auto shift = st::historyGroupCallUserpicShift;
const auto full = QSize(size, size) * kWideScale * factor;
if (userpic.cache.isNull()) {
userpic.cache = QImage(full, QImage::Format_ARGB32_Premultiplied);
userpic.cache.setDevicePixelRatio(factor);
}
userpic.cacheKey = userpic.data.userpicKey;
userpic.cacheMasked = !userpic.topMost;
userpic.cache.fill(Qt::transparent);
{
Painter p(&userpic.cache);
const auto skip = (kWideScale - 1) / 2 * size;
p.drawImage(skip, skip, userpic.data.userpic);
if (userpic.cacheMasked) {
auto hq = PainterHighQualityEnabler(p);
auto pen = QPen(Qt::transparent);
pen.setWidth(st::historyGroupCallUserpicStroke);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.setBrush(Qt::transparent);
p.setPen(pen);
p.drawEllipse(skip - size + shift, skip, size, size);
}
}
const auto top = (st::historyReplyHeight - st::lineWidth - size) / 2
+ st::lineWidth;
_userpics->paint(p, _inner->width() / 2, top, size);
}
void GroupCallBar::updateControlsGeometry(QRect wrapGeometry) {
@@ -413,127 +169,14 @@ void GroupCallBar::updateShadowGeometry(QRect wrapGeometry) {
: regular);
}
void GroupCallBar::updateUserpicsFromContent() {
const auto idFromUserpic = [](const Userpic &userpic) {
return userpic.data.id;
};
// Use "topMost" as "willBeHidden" flag.
for (auto &userpic : _userpics) {
userpic.topMost = true;
}
for (const auto &user : _content.users) {
const auto i = ranges::find(_userpics, user.id, idFromUserpic);
if (i == end(_userpics)) {
_userpics.push_back(Userpic{ user });
toggleUserpic(_userpics.back(), true);
continue;
}
i->topMost = false;
if (i->hiding) {
toggleUserpic(*i, true);
}
i->data = user;
// Put this one after the last we are not hiding.
for (auto j = end(_userpics) - 1; j != i; --j) {
if (!j->topMost) {
ranges::rotate(i, i + 1, j + 1);
break;
}
}
}
// Hide the ones that "willBeHidden" (currently having "topMost" flag).
// Set correct real values of "topMost" flag.
const auto userpicsBegin = begin(_userpics);
const auto userpicsEnd = end(_userpics);
auto markedTopMost = userpicsEnd;
auto hasBlobs = false;
for (auto i = userpicsBegin; i != userpicsEnd; ++i) {
auto &userpic = *i;
if (userpic.data.speaking) {
ensureBlobsAnimation(userpic);
hasBlobs = true;
} else {
userpic.blobsAnimation = nullptr;
}
if (userpic.topMost) {
toggleUserpic(userpic, false);
userpic.topMost = false;
} else if (markedTopMost == userpicsEnd) {
userpic.topMost = true;
markedTopMost = i;
}
}
if (markedTopMost != userpicsEnd && markedTopMost != userpicsBegin) {
// Bring the topMost userpic to the very beginning, above all hiding.
std::rotate(userpicsBegin, markedTopMost, markedTopMost + 1);
}
updateUserpicsPositions();
if (!hasBlobs) {
_randomSpeakingTimer.cancel();
_speakingAnimation.stop();
} else if (!_randomSpeakingTimer.isActive()) {
_randomSpeakingTimer.callEach(kSendRandomLevelInterval);
_speakingAnimation.start();
}
if (_wrap.isHidden()) {
for (auto &userpic : _userpics) {
userpic.shownAnimation.stop();
userpic.leftAnimation.stop();
}
}
}
void GroupCallBar::toggleUserpic(Userpic &userpic, bool shown) {
userpic.hiding = !shown;
userpic.shownAnimation.start(
[=] { updateUserpics(); },
shown ? 0. : 1.,
shown ? 1. : 0.,
kDuration);
}
void GroupCallBar::updateUserpicsPositions() {
const auto shownCount = ranges::count(_userpics, false, &Userpic::hiding);
if (!shownCount) {
return;
}
const auto single = st::historyGroupCallUserpicSize;
const auto shift = st::historyGroupCallUserpicShift;
// + 1 * single for the blobs.
const auto fullWidth = single + (shownCount - 1) * (single - shift);
auto left = (-fullWidth / 2);
for (auto &userpic : _userpics) {
if (userpic.hiding) {
continue;
}
if (!userpic.positionInited) {
userpic.positionInited = true;
userpic.left = left;
} else if (userpic.left != left) {
userpic.leftAnimation.start(
[=] { updateUserpics(); },
userpic.left,
left,
kDuration);
userpic.left = left;
}
left += (single - shift);
}
}
void GroupCallBar::updateUserpics() {
const auto widget = _wrap.entity();
const auto middle = widget->width() / 2;
_wrap.entity()->update(
(middle - _maxUserpicsWidth / 2),
const auto width = _userpics->maxWidth();
widget->update(
(middle - width / 2),
0,
_maxUserpicsWidth,
width,
widget->height());
}

View File

@@ -10,7 +10,6 @@ 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;
@@ -18,17 +17,13 @@ namespace Ui {
class PlainShadow;
class RoundButton;
struct GroupCallUser;
class GroupCallUserpics;
struct GroupCallBarContent {
struct User {
QImage userpic;
std::pair<uint64, uint64> userpicKey = {};
int32 id = 0;
bool speaking = false;
};
int count = 0;
bool shown = false;
std::vector<User> users;
std::vector<GroupCallUser> users;
};
class GroupCallBar final {
@@ -58,24 +53,13 @@ public:
}
private:
using User = GroupCallBarContent::User;
struct BlobsAnimation;
struct Userpic;
using User = GroupCallUser;
void updateShadowGeometry(QRect wrapGeometry);
void updateControlsGeometry(QRect wrapGeometry);
void updateUserpicsFromContent();
void updateUserpics();
void setupInner();
void paint(Painter &p);
void paintUserpics(Painter &p);
void toggleUserpic(Userpic &userpic, bool shown);
void updateUserpics();
void updateUserpicsPositions();
void validateUserpicCache(Userpic &userpic);
[[nodiscard]] bool needUserpicCacheRefresh(Userpic &userpic);
void ensureBlobsAnimation(Userpic &userpic);
void sendRandomLevels();
SlideWrap<> _wrap;
not_null<RpWidget*> _inner;
@@ -83,17 +67,11 @@ private:
std::unique_ptr<PlainShadow> _shadow;
rpl::event_stream<> _barClicks;
Fn<QRect(QRect)> _shadowGeometryPostprocess;
std::vector<Userpic> _userpics;
base::Timer _randomSpeakingTimer;
Ui::Animations::Basic _speakingAnimation;
int _maxUserpicsWidth = 0;
bool _shouldBeShown = false;
bool _forceHidden = false;
bool _skipLevelUpdate = false;
crl::time _speakingAnimationHideLastTime = 0;
GroupCallBarContent _content;
std::unique_ptr<GroupCallUserpics> _userpics;
};

View File

@@ -0,0 +1,416 @@
/*
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 "ui/chat/group_call_userpics.h"
#include "ui/paint/blobs.h"
#include "base/openssl_help.h"
#include "styles/style_chat.h"
namespace Ui {
namespace {
constexpr auto kDuration = 160;
constexpr auto kMaxUserpics = 4;
constexpr auto kWideScale = 5;
constexpr auto kBlobsEnterDuration = crl::time(250);
constexpr auto kLevelDuration = 100. + 500. * 0.23;
constexpr auto kBlobScale = 0.605;
constexpr auto kMinorBlobFactor = 0.9f;
constexpr auto kUserpicMinScale = 0.8;
constexpr auto kMaxLevel = 1.;
constexpr auto kSendRandomLevelInterval = crl::time(100);
auto Blobs()->std::array<Ui::Paint::Blobs::BlobData, 2> {
return { {
{
.segmentsCount = 6,
.minScale = kBlobScale * kMinorBlobFactor,
.minRadius = st::historyGroupCallBlobMinRadius * kMinorBlobFactor,
.maxRadius = st::historyGroupCallBlobMaxRadius * kMinorBlobFactor,
.speedScale = 1.,
.alpha = .5,
},
{
.segmentsCount = 8,
.minScale = kBlobScale,
.minRadius = (float)st::historyGroupCallBlobMinRadius,
.maxRadius = (float)st::historyGroupCallBlobMaxRadius,
.speedScale = 1.,
.alpha = .2,
},
} };
}
} // namespace
struct GroupCallUserpics::BlobsAnimation {
BlobsAnimation(
std::vector<Ui::Paint::Blobs::BlobData> blobDatas,
float levelDuration,
float maxLevel)
: blobs(std::move(blobDatas), levelDuration, maxLevel) {
}
Ui::Paint::Blobs blobs;
crl::time lastTime = 0;
crl::time lastSpeakingUpdateTime = 0;
float64 enter = 0.;
};
struct GroupCallUserpics::Userpic {
User data;
std::pair<uint64, uint64> cacheKey;
crl::time speakingStarted = 0;
QImage cache;
Animations::Simple leftAnimation;
Animations::Simple shownAnimation;
std::unique_ptr<BlobsAnimation> blobsAnimation;
int left = 0;
bool positionInited = false;
bool topMost = false;
bool hiding = false;
bool cacheMasked = false;
};
GroupCallUserpics::GroupCallUserpics(
const style::GroupCallUserpics &st,
rpl::producer<bool> &&hideBlobs,
Fn<void()> repaint)
: _st(st)
, _randomSpeakingTimer([=] { sendRandomLevels(); })
, _repaint(std::move(repaint)) {
const auto limit = kMaxUserpics;
const auto single = _st.size;
const auto shift = _st.shift;
// + 1 * single for the blobs.
_maxWidth = 2 * single + (limit - 1) * (single - shift);
style::PaletteChanged(
) | rpl::start_with_next([=] {
for (auto &userpic : _list) {
userpic.cache = QImage();
}
}, lifetime());
_speakingAnimation.init([=](crl::time now) {
if (const auto &last = _speakingAnimationHideLastTime; (last > 0)
&& (now - last >= kBlobsEnterDuration)) {
_speakingAnimation.stop();
}
for (auto &userpic : _list) {
if (const auto blobs = userpic.blobsAnimation.get()) {
blobs->blobs.updateLevel(now - blobs->lastTime);
blobs->lastTime = now;
}
}
if (const auto onstack = _repaint) {
onstack();
}
});
rpl::combine(
rpl::single(anim::Disabled()) | rpl::then(anim::Disables()),
std::move(hideBlobs)
) | rpl::start_with_next([=](bool animDisabled, bool deactivated) {
const auto hide = animDisabled || deactivated;
if (!(hide && _speakingAnimationHideLastTime)) {
_speakingAnimationHideLastTime = hide ? crl::now() : 0;
}
_skipLevelUpdate = hide;
for (auto &userpic : _list) {
if (const auto blobs = userpic.blobsAnimation.get()) {
blobs->blobs.setLevel(0.);
}
}
if (!hide && !_speakingAnimation.animating()) {
_speakingAnimation.start();
}
_skipLevelUpdate = hide;
}, lifetime());
}
GroupCallUserpics::~GroupCallUserpics() = default;
void GroupCallUserpics::paint(Painter &p, int x, int y, int size) {
const auto factor = style::DevicePixelRatio();
const auto &minScale = kUserpicMinScale;
for (auto &userpic : ranges::view::reverse(_list)) {
const auto shown = userpic.shownAnimation.value(
userpic.hiding ? 0. : 1.);
if (shown == 0.) {
continue;
}
validateCache(userpic);
p.setOpacity(shown);
const auto left = x + userpic.leftAnimation.value(userpic.left);
const auto blobs = userpic.blobsAnimation.get();
const auto shownScale = 0.5 + shown / 2.;
const auto scale = shownScale * (!blobs
? 1.
: (minScale
+ (1. - minScale) * (_speakingAnimationHideLastTime
? (1. - blobs->blobs.currentLevel())
: blobs->blobs.currentLevel())));
if (blobs) {
auto hq = PainterHighQualityEnabler(p);
const auto shift = QPointF(left + size / 2., y + size / 2.);
p.translate(shift);
blobs->blobs.paint(p, st::windowActiveTextFg);
p.translate(-shift);
p.setOpacity(1.);
}
if (std::abs(scale - 1.) < 0.001) {
const auto skip = ((kWideScale - 1) / 2) * size * factor;
p.drawImage(
QRect(left, y, size, size),
userpic.cache,
QRect(skip, skip, size * factor, size * factor));
} else {
auto hq = PainterHighQualityEnabler(p);
auto target = QRect(
left + (1 - kWideScale) / 2 * size,
y + (1 - kWideScale) / 2 * size,
kWideScale * size,
kWideScale * size);
auto shrink = anim::interpolate(
(1 - kWideScale) / 2 * size,
0,
scale);
auto margins = QMargins(shrink, shrink, shrink, shrink);
p.drawImage(target.marginsAdded(margins), userpic.cache);
}
}
p.setOpacity(1.);
const auto hidden = [](const Userpic &userpic) {
return userpic.hiding && !userpic.shownAnimation.animating();
};
_list.erase(ranges::remove_if(_list, hidden), end(_list));
}
int GroupCallUserpics::maxWidth() const {
return _maxWidth;
}
rpl::producer<int> GroupCallUserpics::widthValue() const {
return _width.value();
}
bool GroupCallUserpics::needCacheRefresh(Userpic &userpic) {
if (userpic.cache.isNull()) {
return true;
} else if (userpic.hiding) {
return false;
} else if (userpic.cacheKey != userpic.data.userpicKey) {
return true;
}
const auto shouldBeMasked = !userpic.topMost;
if (userpic.cacheMasked == shouldBeMasked || !shouldBeMasked) {
return true;
}
return !userpic.leftAnimation.animating();
}
void GroupCallUserpics::ensureBlobsAnimation(Userpic &userpic) {
if (userpic.blobsAnimation) {
return;
}
userpic.blobsAnimation = std::make_unique<BlobsAnimation>(
Blobs() | ranges::to_vector,
kLevelDuration,
kMaxLevel);
userpic.blobsAnimation->lastTime = crl::now();
}
void GroupCallUserpics::sendRandomLevels() {
if (_skipLevelUpdate) {
return;
}
for (auto &userpic : _list) {
if (const auto blobs = userpic.blobsAnimation.get()) {
const auto value = 30 + (openssl::RandomValue<uint32>() % 70);
userpic.blobsAnimation->blobs.setLevel(float64(value) / 100.);
}
}
}
void GroupCallUserpics::validateCache(Userpic &userpic) {
if (!needCacheRefresh(userpic)) {
return;
}
const auto factor = style::DevicePixelRatio();
const auto size = _st.size;
const auto shift = _st.shift;
const auto full = QSize(size, size) * kWideScale * factor;
if (userpic.cache.isNull()) {
userpic.cache = QImage(full, QImage::Format_ARGB32_Premultiplied);
userpic.cache.setDevicePixelRatio(factor);
}
userpic.cacheKey = userpic.data.userpicKey;
userpic.cacheMasked = !userpic.topMost;
userpic.cache.fill(Qt::transparent);
{
Painter p(&userpic.cache);
const auto skip = (kWideScale - 1) / 2 * size;
p.drawImage(skip, skip, userpic.data.userpic);
if (userpic.cacheMasked) {
auto hq = PainterHighQualityEnabler(p);
auto pen = QPen(Qt::transparent);
pen.setWidth(_st.stroke);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.setBrush(Qt::transparent);
p.setPen(pen);
p.drawEllipse(skip - size + shift, skip, size, size);
}
}
}
void GroupCallUserpics::update(
const std::vector<GroupCallUser> &users,
bool visible) {
const auto idFromUserpic = [](const Userpic &userpic) {
return userpic.data.id;
};
// Use "topMost" as "willBeHidden" flag.
for (auto &userpic : _list) {
userpic.topMost = true;
}
for (const auto &user : users) {
const auto i = ranges::find(_list, user.id, idFromUserpic);
if (i == end(_list)) {
_list.push_back(Userpic{ user });
toggle(_list.back(), true);
continue;
}
i->topMost = false;
if (i->hiding) {
toggle(*i, true);
}
i->data = user;
// Put this one after the last we are not hiding.
for (auto j = end(_list) - 1; j != i; --j) {
if (!j->topMost) {
ranges::rotate(i, i + 1, j + 1);
break;
}
}
}
// Hide the ones that "willBeHidden" (currently having "topMost" flag).
// Set correct real values of "topMost" flag.
const auto userpicsBegin = begin(_list);
const auto userpicsEnd = end(_list);
auto markedTopMost = userpicsEnd;
auto hasBlobs = false;
for (auto i = userpicsBegin; i != userpicsEnd; ++i) {
auto &userpic = *i;
if (userpic.data.speaking) {
ensureBlobsAnimation(userpic);
hasBlobs = true;
} else {
userpic.blobsAnimation = nullptr;
}
if (userpic.topMost) {
toggle(userpic, false);
userpic.topMost = false;
} else if (markedTopMost == userpicsEnd) {
userpic.topMost = true;
markedTopMost = i;
}
}
if (markedTopMost != userpicsEnd && markedTopMost != userpicsBegin) {
// Bring the topMost userpic to the very beginning, above all hiding.
std::rotate(userpicsBegin, markedTopMost, markedTopMost + 1);
}
updatePositions();
if (!hasBlobs) {
_randomSpeakingTimer.cancel();
_speakingAnimation.stop();
} else if (!_randomSpeakingTimer.isActive()) {
_randomSpeakingTimer.callEach(kSendRandomLevelInterval);
_speakingAnimation.start();
}
if (!visible) {
for (auto &userpic : _list) {
userpic.shownAnimation.stop();
userpic.leftAnimation.stop();
}
}
recountAndRepaint();
}
void GroupCallUserpics::toggle(Userpic &userpic, bool shown) {
userpic.hiding = !shown;
userpic.shownAnimation.start(
[=] { recountAndRepaint(); },
shown ? 0. : 1.,
shown ? 1. : 0.,
kDuration);
}
void GroupCallUserpics::updatePositions() {
const auto shownCount = ranges::count(_list, false, &Userpic::hiding);
if (!shownCount) {
return;
}
const auto single = _st.size;
const auto shift = _st.shift;
// + 1 * single for the blobs.
const auto fullWidth = single + (shownCount - 1) * (single - shift);
auto left = (_st.align & Qt::AlignLeft)
? 0
: (_st.align & Qt::AlignHCenter)
? (-fullWidth / 2)
: -fullWidth;
for (auto &userpic : _list) {
if (userpic.hiding) {
continue;
}
if (!userpic.positionInited) {
userpic.positionInited = true;
userpic.left = left;
} else if (userpic.left != left) {
userpic.leftAnimation.start(
_repaint,
userpic.left,
left,
kDuration);
userpic.left = left;
}
left += (single - shift);
}
}
void GroupCallUserpics::recountAndRepaint() {
auto width = 0;
auto maxShown = 0.;
for (const auto &userpic : _list) {
const auto shown = userpic.shownAnimation.value(
userpic.hiding ? 0. : 1.);
if (shown > maxShown) {
maxShown = shown;
}
width += anim::interpolate(0, _st.size - _st.shift, shown);
}
_width = width + anim::interpolate(0, _st.shift, maxShown);
if (_repaint) {
_repaint();
}
}
} // namespace Ui

View File

@@ -0,0 +1,73 @@
/*
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/timer.h"
namespace style {
struct GroupCallUserpics;
} // namespace style
namespace Ui {
struct GroupCallUser {
QImage userpic;
std::pair<uint64, uint64> userpicKey = {};
int32 id = 0;
bool speaking = false;
};
class GroupCallUserpics final {
public:
GroupCallUserpics(
const style::GroupCallUserpics &st,
rpl::producer<bool> &&hideBlobs,
Fn<void()> repaint);
~GroupCallUserpics();
void update(
const std::vector<GroupCallUser> &users,
bool visible);
void paint(Painter &p, int x, int y, int size);
[[nodiscard]] int maxWidth() const;
[[nodiscard]] rpl::producer<int> widthValue() const;
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;
}
private:
using User = GroupCallUser;
struct BlobsAnimation;
struct Userpic;
void toggle(Userpic &userpic, bool shown);
void updatePositions();
void validateCache(Userpic &userpic);
[[nodiscard]] bool needCacheRefresh(Userpic &userpic);
void ensureBlobsAnimation(Userpic &userpic);
void sendRandomLevels();
void recountAndRepaint();
const style::GroupCallUserpics &_st;
std::vector<Userpic> _list;
base::Timer _randomSpeakingTimer;
Fn<void()> _repaint;
Ui::Animations::Basic _speakingAnimation;
int _maxWidth = 0;
bool _skipLevelUpdate = false;
crl::time _speakingAnimationHideLastTime = 0;
rpl::variable<int> _width;
rpl::lifetime _lifetime;
};
} // namespace Ui

View File

@@ -45,6 +45,11 @@ MessageBar::MessageBar(not_null<QWidget*> parent, const style::MessageBar &st)
: _st(st)
, _widget(parent) {
setup();
style::PaletteChanged(
) | rpl::start_with_next([=] {
_topBarGradient = _bottomBarGradient = QPixmap();
}, _widget.lifetime());
}
void MessageBar::setup() {

View File

@@ -13,24 +13,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui {
QString FormatSizeText(qint64 size) {
if (size >= 1024 * 1024) { // more than 1 mb
qint64 sizeTenthMb = (size * 10 / (1024 * 1024));
return QString::number(sizeTenthMb / 10) + '.' + QString::number(sizeTenthMb % 10) + u" MB"_q;
}
if (size >= 1024) {
qint64 sizeTenthKb = (size * 10 / 1024);
return QString::number(sizeTenthKb / 10) + '.' + QString::number(sizeTenthKb % 10) + u" KB"_q;
}
return QString::number(size) + u" B"_q;
}
namespace {
QString FormatDownloadText(qint64 ready, qint64 total) {
QString FormatTextWithReadyAndTotal(
tr::phrase<lngtag_ready, lngtag_total, lngtag_mb> phrase,
qint64 ready,
qint64 total) {
QString readyStr, totalStr, mb;
if (total >= 1024 * 1024) { // more than 1 mb
qint64 readyTenthMb = (ready * 10 / (1024 * 1024)), totalTenthMb = (total * 10 / (1024 * 1024));
readyStr = QString::number(readyTenthMb / 10) + '.' + QString::number(readyTenthMb % 10);
totalStr = QString::number(totalTenthMb / 10) + '.' + QString::number(totalTenthMb % 10);
const qint64 readyTenthMb = (ready * 10 / (1024 * 1024));
const qint64 totalTenthMb = (total * 10 / (1024 * 1024));
readyStr = QString::number(readyTenthMb / 10)
+ '.'
+ QString::number(readyTenthMb % 10);
totalStr = QString::number(totalTenthMb / 10)
+ '.'
+ QString::number(totalTenthMb % 10);
mb = u"MB"_q;
} else if (total >= 1024) {
qint64 readyKb = (ready / 1024), totalKb = (total / 1024);
@@ -42,7 +40,61 @@ QString FormatDownloadText(qint64 ready, qint64 total) {
totalStr = QString::number(total);
mb = u"B"_q;
}
return tr::lng_save_downloaded(tr::now, lt_ready, readyStr, lt_total, totalStr, lt_mb, mb);
return phrase(tr::now, lt_ready, readyStr, lt_total, totalStr, lt_mb, mb);
}
} // namespace
QString FormatSizeText(qint64 size) {
if (size >= 1024 * 1024) { // more than 1 mb
const qint64 sizeTenthMb = (size * 10 / (1024 * 1024));
return QString::number(sizeTenthMb / 10)
+ '.'
+ QString::number(sizeTenthMb % 10) + u" MB"_q;
}
if (size >= 1024) {
const qint64 sizeTenthKb = (size * 10 / 1024);
return QString::number(sizeTenthKb / 10)
+ '.'
+ QString::number(sizeTenthKb % 10) + u" KB"_q;
}
return QString::number(size) + u" B"_q;
}
QString FormatDownloadText(qint64 ready, qint64 total) {
return FormatTextWithReadyAndTotal(
tr::lng_save_downloaded,
ready,
total);
}
QString FormatProgressText(qint64 ready, qint64 total) {
return FormatTextWithReadyAndTotal(
tr::lng_media_save_progress,
ready,
total);
}
QString FormatDateTime(QDateTime date, QString format) {
const auto now = QDateTime::currentDateTime();
if (date.date() == now.date()) {
return tr::lng_mediaview_today(
tr::now,
lt_time,
date.time().toString(format));
} else if (date.date().addDays(1) == now.date()) {
return tr::lng_mediaview_yesterday(
tr::now,
lt_time,
date.time().toString(format));
} else {
return tr::lng_mediaview_date_time(
tr::now,
lt_date,
date.date().toString(u"dd.MM.yy"_q),
lt_time,
date.time().toString(format));
}
}
QString FormatDurationText(qint64 duration) {

View File

@@ -15,6 +15,8 @@ inline constexpr auto FileStatusSizeFailed = 0x7FFFFFF2;
[[nodiscard]] QString FormatSizeText(qint64 size);
[[nodiscard]] QString FormatDownloadText(qint64 ready, qint64 total);
[[nodiscard]] QString FormatProgressText(qint64 ready, qint64 total);
[[nodiscard]] QString FormatDateTime(QDateTime date, QString format);
[[nodiscard]] QString FormatDurationText(qint64 duration);
[[nodiscard]] QString FormatDurationWords(qint64 duration);
[[nodiscard]] QString FormatDurationAndSizeText(qint64 duration, qint64 size);

View File

@@ -483,7 +483,7 @@ void Filler::addUserActions(not_null<UserData*> user) {
using AddBotToGroup = AddBotToGroupBoxController;
_addAction(
tr::lng_profile_invite_to_group(tr::now),
[=] { AddBotToGroup::Start(controller, user); });
[=] { AddBotToGroup::Start(user); });
}
addPollAction(user);
if (user->canExportChatHistory()) {
@@ -517,7 +517,7 @@ void Filler::addChatActions(not_null<ChatData*> chat) {
}
if (chat->canAddMembers()) {
_addAction(
tr::lng_profile_add_participant(tr::now),
tr::lng_channel_add_members(tr::now),
[=] { AddChatMembers(controller, chat); });
}
addPollAction(chat);
@@ -567,7 +567,9 @@ void Filler::addChannelActions(not_null<ChannelData*> channel) {
}
if (channel->canAddMembers()) {
_addAction(
tr::lng_channel_add_members(tr::now),
(channel->isMegagroup()
? tr::lng_channel_add_members(tr::now)
: tr::lng_channel_add_users(tr::now)),
[=] { PeerMenuAddChannelMembers(navigation, channel); });
}
addPollAction(channel);
@@ -803,7 +805,7 @@ void PeerMenuShareContactBox(
};
*weak = Ui::show(Box<PeerListBox>(
std::make_unique<ChooseRecipientBoxController>(
navigation,
&navigation->session(),
std::move(callback)),
[](not_null<PeerListBox*> box) {
box->addButton(tr::lng_cancel(), [=] {
@@ -1010,7 +1012,7 @@ QPointer<Ui::RpWidget> ShowForwardMessagesBox(
};
*weak = Ui::show(Box<PeerListBox>(
std::make_unique<ChooseRecipientBoxController>(
navigation,
&navigation->session(),
std::move(callback)),
std::move(initBox)), Ui::LayerOption::KeepOther);
return weak->data();

View File

@@ -194,7 +194,7 @@ void SessionNavigation::showPeerByLinkResolved(
const auto user = peer->asUser();
if (user && user->isBot() && !info.startToken.isEmpty()) {
user->botInfo->shareGameShortName = info.startToken;
AddBotToGroupBoxController::Start(this, user);
AddBotToGroupBoxController::Start(user);
} else {
crl::on_main(this, [=] {
showPeerHistory(peer->id, params);
@@ -207,7 +207,7 @@ void SessionNavigation::showPeerByLinkResolved(
&& !user->botInfo->cantJoinGroups
&& !info.startToken.isEmpty()) {
user->botInfo->startGroupToken = info.startToken;
AddBotToGroupBoxController::Start(this, user);
AddBotToGroupBoxController::Start(user);
} else if (user && user->isBot()) {
// Always open bot chats, even from mention links.
crl::on_main(this, [=] {

View File

@@ -1,7 +1,7 @@
AppVersion 2004015
AppVersionStrMajor 2.4
AppVersionStrSmall 2.4.15
AppVersionStr 2.4.15
AppVersion 2005002
AppVersionStrMajor 2.5
AppVersionStrSmall 2.5.2
AppVersionStr 2.5.2
BetaChannel 1
AlphaVersion 0
AppVersionOriginal 2.4.15.beta
AppVersionOriginal 2.5.2.beta

View File

@@ -80,6 +80,8 @@ PRIVATE
ui/chat/attach/attach_single_media_preview.h
ui/chat/group_call_bar.cpp
ui/chat/group_call_bar.h
ui/chat/group_call_userpics.cpp
ui/chat/group_call_userpics.h
ui/chat/message_bar.cpp
ui/chat/message_bar.h
ui/chat/pinned_bar.cpp

View File

@@ -1,3 +1,19 @@
2.5.2 beta (25.12.20)
- Fix possible crash in video calls.
- Fix possible crash in connecting to voice chats.
- Use different audio module code on Windows in calls.
2.5.1 (23.12.20)
- Fix crash in voice calls.
2.5 (23.12.20)
- Turn any of your group chats into a hop-on, hop-off conference call.
- Get up to several thousand participants in each voice chat.
- Control the number of speakers with flexible admin tools.
2.4.15 beta (19.12.20)
- Improve design of voice chats.

2
cmake

Submodule cmake updated: 783efab5aa...2208358765

View File

@@ -33,7 +33,7 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath***
cd ThirdParty
git clone https://github.com/desktop-app/patches.git
cd patches
git checkout f22ccc5
git checkout 41ead72
cd ../
git clone https://chromium.googlesource.com/external/gyp
cd gyp
@@ -63,7 +63,7 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath***
git clone https://github.com/desktop-app/patches.git
cd patches
git checkout f22ccc5
git checkout 41ead72
cd ..
git clone https://github.com/desktop-app/lzma.git

View File

@@ -240,10 +240,8 @@ Go to ***BuildPath*** and run
sudo make install
cd ..
git clone https://github.com/kcat/openal-soft.git
cd openal-soft
git checkout 3970252da9
cd build
git clone --branch capture_with_webrtc https://github.com/telegramdesktop/openal-soft.git
cd openal-soft/build
CFLAGS=$UNGUARDED CPPFLAGS=$UNGUARDED cmake -D CMAKE_INSTALL_PREFIX:PATH=/usr/local/macos -D ALSOFT_EXAMPLES=OFF -D LIBTYPE:STRING=STATIC -D CMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.12 ..
make $MAKE_THREADS_CNT
sudo make install

View File

@@ -20,6 +20,8 @@ apps:
environment:
# Tell glib to use portals on file associations handling.
GTK_USE_PORTAL: 1
# Use sandboxed ibus api
IBUS_USE_PORTAL: 1
plugs:
- alsa
- audio-playback
@@ -75,7 +77,6 @@ parts:
- liblzma-dev
- libopus-dev
- libpulse-dev
- libqt5svg5-dev
- libqt5waylandclient5-dev
- libssl-dev
- libxcb1-dev
@@ -87,11 +88,11 @@ parts:
- qt5-image-formats-plugins
- qtwayland5
- libasound2
- libglib2.0-0
- libgtk-3-0
- liblzma5
- libopus0
- libpulse0
- libqt5svg5
- libqt5waylandclient5
- libssl1.1
- libxcb1
@@ -174,16 +175,6 @@ parts:
after:
- mozjpeg
# Qt checks that ibus-daemon binary is present, otherwise doesn't work
ibus:
plugin: nil
stage-packages:
- ibus
stage:
- -./usr/lib/$SNAPCRAFT_ARCH_TRIPLET/libjpeg.so.8.2.2
after:
- mozjpeg
ffmpeg:
plugin: nil
build-packages:
@@ -223,18 +214,14 @@ parts:
openal:
source: https://github.com/kcat/openal-soft.git
source-depth: 1
source-tag: openal-soft-1.20.1
source-tag: openal-soft-1.21.0
plugin: cmake
build-packages:
- libasound2-dev
- libpulse-dev
- libsndio-dev
- portaudio19-dev
stage-packages:
- libasound2
- libpulse0
- libportaudio2
- libsndio7.0
cmake-parameters:
- -DCMAKE_BUILD_TYPE=Release
- -DCMAKE_INSTALL_PREFIX=/usr