Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0684db9bd8 | ||
|
|
db7b61a77b | ||
|
|
d392633b90 | ||
|
|
76e08af26a | ||
|
|
b23f16e6e4 | ||
|
|
23156d523c | ||
|
|
04b0e2e9e6 | ||
|
|
ace5740125 | ||
|
|
bc67b79023 | ||
|
|
528c98af67 | ||
|
|
311a2f2753 | ||
|
|
3defb06783 | ||
|
|
5708b5e849 | ||
|
|
1db1328a91 | ||
|
|
2e9d6d73c3 | ||
|
|
38dd5ab837 | ||
|
|
83ab670c50 | ||
|
|
5621e41529 | ||
|
|
056cab6268 | ||
|
|
61d0d240aa | ||
|
|
33ae4c2802 | ||
|
|
2c806b11d7 | ||
|
|
199434c7a2 | ||
|
|
c65c554d88 | ||
|
|
5d16359a5a | ||
|
|
fd9ad04d15 | ||
|
|
0c8febce9c | ||
|
|
4659cc50f2 | ||
|
|
2fddeb478b | ||
|
|
4ffe1d3acc | ||
|
|
cdf0512515 | ||
|
|
971e188063 | ||
|
|
a909c1a813 | ||
|
|
4fc2b1f1a3 | ||
|
|
fb04f33ae8 | ||
|
|
b2c87e7a73 | ||
|
|
86a33ceea1 | ||
|
|
a5d8d7a550 | ||
|
|
11723aedff | ||
|
|
fe5de8f009 | ||
|
|
6b68d001ae | ||
|
|
ae0b9141dd | ||
|
|
12e306dd7b | ||
|
|
508762cd2c |
3
.github/workflows/win.yml
vendored
3
.github/workflows/win.yml
vendored
@@ -128,7 +128,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Find any version of Python 2."
|
||||
p=`ls /c/hostedtoolcache/windows/python | grep 2 | tail -1`
|
||||
p=`ls /c/hostedtoolcache/windows/python | grep "^2" | tail -1`
|
||||
if [ -z "$p" ]; then
|
||||
echo "Python 2 is not found."
|
||||
exit 1
|
||||
@@ -409,6 +409,7 @@ jobs:
|
||||
-D TDESKTOP_API_TEST=ON ^
|
||||
-D DESKTOP_APP_USE_PACKAGED=OFF ^
|
||||
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^
|
||||
-D DESKTOP_APP_NO_PDB=ON ^
|
||||
%TDESKTOP_BUILD_DEFINE% ^
|
||||
-DCMAKE_SYSTEM_VERSION=%SDK%
|
||||
|
||||
|
||||
@@ -1966,6 +1966,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_copy_listener_link" = "Copy Listener Link";
|
||||
"lng_group_call_end" = "End Voice Chat";
|
||||
"lng_group_call_join" = "Join";
|
||||
"lng_group_call_join_confirm" = "Do you want to join the voice chat {chat}?";
|
||||
"lng_group_call_invite_done_user" = "You invited {user} to the voice chat.";
|
||||
"lng_group_call_invite_done_many#one" = "You invited **{count} member** to the voice chat.";
|
||||
"lng_group_call_invite_done_many#other" = "You invited **{count} members** to the voice chat.";
|
||||
@@ -2011,7 +2012,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_recording_start_button" = "Start";
|
||||
"lng_group_call_is_recorded" = "Voice chat is being recorded.";
|
||||
"lng_group_call_can_speak_here" = "You can now speak.";
|
||||
"lng_group_call_can_speak" = "You can now speak in **{chat}**.";
|
||||
"lng_group_call_can_speak" = "You can now speak in {chat}.";
|
||||
"lng_group_call_title_changed" = "Voice chat title changed to {title}";
|
||||
"lng_group_call_join_as_changed" = "Members of this voice chat will now see you as {name}";
|
||||
|
||||
"lng_no_mic_permission" = "Telegram needs access to your microphone so that you can make calls and record voice messages.";
|
||||
|
||||
|
||||
@@ -1203,7 +1203,7 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked;
|
||||
stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats;
|
||||
|
||||
groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall;
|
||||
groupCall#c0c2052e flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true id:long access_hash:long participants_count:int params:flags.0?DataJSON title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int version:int = GroupCall;
|
||||
groupCall#c0c2052e flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true id:long access_hash:long participants_count:int params:flags.0?DataJSON title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int version:int = GroupCall;
|
||||
|
||||
inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall;
|
||||
|
||||
@@ -1572,7 +1572,7 @@ channels.inviteToChannel#199f3a6c channel:InputChannel users:Vector<InputUser> =
|
||||
channels.deleteChannel#c0111fe3 channel:InputChannel = Updates;
|
||||
channels.exportMessageLink#e63fadeb flags:# grouped:flags.0?true thread:flags.1?true channel:InputChannel id:int = ExportedMessageLink;
|
||||
channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates;
|
||||
channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true for_groupcall:flags.2?true = messages.Chats;
|
||||
channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true = messages.Chats;
|
||||
channels.editBanned#72796912 channel:InputChannel user_id:InputUser banned_rights:ChatBannedRights = Updates;
|
||||
channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector<InputUser> max_id:long min_id:long limit:int = channels.AdminLogResults;
|
||||
channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="2.6.2.0" />
|
||||
Version="2.6.4.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,6,2,0
|
||||
PRODUCTVERSION 2,6,2,0
|
||||
FILEVERSION 2,6,4,0
|
||||
PRODUCTVERSION 2,6,4,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.6.2.0"
|
||||
VALUE "FileVersion", "2.6.4.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.6.2.0"
|
||||
VALUE "ProductVersion", "2.6.4.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,6,2,0
|
||||
PRODUCTVERSION 2,6,2,0
|
||||
FILEVERSION 2,6,4,0
|
||||
PRODUCTVERSION 2,6,4,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.6.2.0"
|
||||
VALUE "FileVersion", "2.6.4.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.6.2.0"
|
||||
VALUE "ProductVersion", "2.6.4.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -65,7 +65,10 @@ void SendProgressManager::update(
|
||||
SendProgressType type,
|
||||
int progress) {
|
||||
const auto peer = history->peer;
|
||||
if (peer->isSelf() || (peer->isChannel() && !peer->isMegagroup())) {
|
||||
if (peer->isSelf()
|
||||
|| (peer->isChannel()
|
||||
&& !peer->isMegagroup()
|
||||
&& type != SendProgressType::Speaking)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -278,11 +278,25 @@ void Updates::checkLastUpdate(bool afterSleep) {
|
||||
void Updates::feedUpdateVector(
|
||||
const MTPVector<MTPUpdate> &updates,
|
||||
bool skipMessageIds) {
|
||||
for (const auto &update : updates.v) {
|
||||
if (skipMessageIds && update.type() == mtpc_updateMessageID) {
|
||||
auto list = updates.v;
|
||||
const auto needsSorting = ranges::contains(
|
||||
list,
|
||||
mtpc_updateGroupCallParticipants,
|
||||
&MTPUpdate::type);
|
||||
if (needsSorting) {
|
||||
ranges::stable_sort(list, std::less<>(), [](const MTPUpdate &entry) {
|
||||
if (entry.type() == mtpc_updateGroupCallParticipants) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
for (const auto &entry : std::as_const(list)) {
|
||||
if (skipMessageIds && entry.type() == mtpc_updateMessageID) {
|
||||
continue;
|
||||
}
|
||||
feedUpdate(update);
|
||||
feedUpdate(entry);
|
||||
}
|
||||
session().data().sendHistoryChangeNotifications();
|
||||
}
|
||||
@@ -557,10 +571,10 @@ void Updates::feedDifference(
|
||||
}
|
||||
|
||||
void Updates::differenceFail(const MTP::Error &error) {
|
||||
LOG(("RPC Error in getDifference: %1 %2: %3"
|
||||
).arg(error.code()
|
||||
).arg(error.type()
|
||||
).arg(error.description()));
|
||||
LOG(("RPC Error in getDifference: %1 %2: %3").arg(
|
||||
QString::number(error.code()),
|
||||
error.type(),
|
||||
error.description()));
|
||||
failDifferenceStartTimerFor(nullptr);
|
||||
}
|
||||
|
||||
|
||||
@@ -2600,7 +2600,7 @@ void ApiWrap::clearWebPageRequests() {
|
||||
void ApiWrap::resolveWebPages() {
|
||||
auto ids = QVector<MTPInputMessage>(); // temp_req_id = -1
|
||||
using IndexAndMessageIds = QPair<int32, QVector<MTPInputMessage>>;
|
||||
using MessageIdsByChannel = QMap<ChannelData*, IndexAndMessageIds>;
|
||||
using MessageIdsByChannel = base::flat_map<ChannelData*, IndexAndMessageIds>;
|
||||
MessageIdsByChannel idsByChannel; // temp_req_id = -index - 2
|
||||
|
||||
ids.reserve(_webPagesPending.size());
|
||||
@@ -2617,18 +2617,18 @@ void ApiWrap::resolveWebPages() {
|
||||
auto channel = item->history()->peer->asChannel();
|
||||
auto channelMap = idsByChannel.find(channel);
|
||||
if (channelMap == idsByChannel.cend()) {
|
||||
channelMap = idsByChannel.insert(
|
||||
channelMap = idsByChannel.emplace(
|
||||
channel,
|
||||
IndexAndMessageIds(
|
||||
idsByChannel.size(),
|
||||
QVector<MTPInputMessage>(
|
||||
1,
|
||||
MTP_inputMessageID(MTP_int(item->id)))));
|
||||
MTP_inputMessageID(MTP_int(item->id))))).first;
|
||||
} else {
|
||||
channelMap.value().second.push_back(
|
||||
channelMap->second.second.push_back(
|
||||
MTP_inputMessageID(MTP_int(item->id)));
|
||||
}
|
||||
i.value() = -channelMap.value().first - 2;
|
||||
i.value() = -channelMap->second.first - 2;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -2648,10 +2648,10 @@ void ApiWrap::resolveWebPages() {
|
||||
}
|
||||
QVector<mtpRequestId> reqsByIndex(idsByChannel.size(), 0);
|
||||
for (auto i = idsByChannel.cbegin(), e = idsByChannel.cend(); i != e; ++i) {
|
||||
reqsByIndex[i.value().first] = request(MTPchannels_GetMessages(
|
||||
i.key()->inputChannel,
|
||||
MTP_vector<MTPInputMessage>(i.value().second)
|
||||
)).done([=, channel = i.key()](
|
||||
reqsByIndex[i->second.first] = request(MTPchannels_GetMessages(
|
||||
i->first->inputChannel,
|
||||
MTP_vector<MTPInputMessage>(i->second.second)
|
||||
)).done([=, channel = i->first](
|
||||
const MTPmessages_Messages &result,
|
||||
mtpRequestId requestId) {
|
||||
gotWebPages(channel, result, requestId);
|
||||
|
||||
@@ -111,7 +111,7 @@ confirmInviteUserName: FlatLabel(defaultFlatLabel) {
|
||||
confirmInviteUserNameTop: 227px;
|
||||
|
||||
confirmPhoneAboutLabel: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 282px;
|
||||
minWidth: 272px;
|
||||
}
|
||||
confirmPhoneCodeField: InputField(defaultInputField) {
|
||||
}
|
||||
|
||||
@@ -464,7 +464,6 @@ void Rows::showMenu(int index) {
|
||||
Fn<void()> callback) {
|
||||
return _menu->addAction(text, std::move(callback));
|
||||
};
|
||||
const auto id = row->data.id;
|
||||
if (canShare(row)) {
|
||||
addAction(tr::lng_proxy_edit_share(tr::now), [=] { share(row); });
|
||||
}
|
||||
|
||||
@@ -695,8 +695,6 @@ bool PasscodeBox::handleCustomCheckError(const MTP::Error &error) {
|
||||
|
||||
void PasscodeBox::sendClearCloudPassword(
|
||||
const Core::CloudPasswordResult &check) {
|
||||
const auto newPasswordData = QByteArray();
|
||||
const auto newPasswordHash = QByteArray();
|
||||
const auto hint = QString();
|
||||
const auto email = QString();
|
||||
const auto flags = MTPDaccount_passwordInputSettings::Flag::f_new_algo
|
||||
|
||||
@@ -566,7 +566,6 @@ int32 StickerSetBox::Inner::stickerFromGlobalPos(const QPoint &p) const {
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
|
||||
QRect r(e->rect());
|
||||
Painter p(this);
|
||||
|
||||
if (_elements.empty()) {
|
||||
|
||||
@@ -792,9 +792,10 @@ groupCallBoxLabel: FlatLabel(boxLabel) {
|
||||
textFg: groupCallMembersFg;
|
||||
}
|
||||
groupCallJoinAsLabel: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 282px;
|
||||
minWidth: 272px;
|
||||
textFg: groupCallMembersFg;
|
||||
}
|
||||
groupCallJoinAsWidth: 330px;
|
||||
groupCallJoinAsTextTop: 4px;
|
||||
groupCallJoinAsNameTop: 23px;
|
||||
groupCallJoinAsPadding: margins(12px, 8px, 12px, 7px);
|
||||
|
||||
@@ -142,7 +142,7 @@ uint64 ComputeFingerprint(bytes::const_span authKey) {
|
||||
}
|
||||
|
||||
[[nodiscard]] QVector<MTPstring> CollectVersionsForApi() {
|
||||
return WrapVersions(tgcalls::Meta::Versions() | ranges::action::reverse);
|
||||
return WrapVersions(tgcalls::Meta::Versions() | ranges::actions::reverse);
|
||||
}
|
||||
|
||||
[[nodiscard]] Webrtc::VideoState StartVideoState(bool enabled) {
|
||||
|
||||
@@ -12,12 +12,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_group_call.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_account.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "apiwrap.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_calls.h"
|
||||
|
||||
@@ -111,10 +114,12 @@ void ChooseJoinAsBox(
|
||||
Context context,
|
||||
JoinInfo info,
|
||||
Fn<void(JoinInfo)> done) {
|
||||
box->setWidth(st::groupCallJoinAsWidth);
|
||||
box->setTitle([&] {
|
||||
switch (context) {
|
||||
case Context::Create: return tr::lng_group_call_start_as_header();
|
||||
case Context::Join: return tr::lng_group_call_join_as_header();
|
||||
case Context::Join:
|
||||
case Context::JoinWithConfirm: return tr::lng_group_call_join_as_header();
|
||||
case Context::Switch: return tr::lng_group_call_display_as_header();
|
||||
}
|
||||
Unexpected("Context in ChooseJoinAsBox.");
|
||||
@@ -172,7 +177,7 @@ void ChooseJoinAsProcess::start(
|
||||
Fn<void(object_ptr<Ui::BoxContent>)> showBox,
|
||||
Fn<void(QString)> showToast,
|
||||
Fn<void(JoinInfo)> done,
|
||||
PeerData *currentJoinAs) {
|
||||
PeerData *changingJoinAsFrom) {
|
||||
Expects(done != nullptr);
|
||||
|
||||
const auto session = &peer->session();
|
||||
@@ -231,26 +236,18 @@ void ChooseJoinAsProcess::start(
|
||||
}
|
||||
return list;
|
||||
});
|
||||
const auto selectedId = peer->groupCallDefaultJoinAs();
|
||||
if (list.empty()) {
|
||||
_request->showToast(Lang::Hard::ServerError());
|
||||
return;
|
||||
} else if (list.size() == 1
|
||||
&& list.front() == self
|
||||
&& (!peer->isChannel()
|
||||
|| !peer->asChannel()->amAnonymous()
|
||||
|| (peer->isBroadcast() && !peer->canWrite()))) {
|
||||
info.possibleJoinAs = std::move(list);
|
||||
finish(info);
|
||||
return;
|
||||
}
|
||||
info.joinAs = [&]() -> not_null<PeerData*> {
|
||||
const auto selectedId = peer->groupCallDefaultJoinAs();
|
||||
if (!selectedId) {
|
||||
return self;
|
||||
}
|
||||
const auto loaded = session->data().peerLoaded(selectedId);
|
||||
return (currentJoinAs && ranges::contains(list, not_null{ currentJoinAs }))
|
||||
? not_null(currentJoinAs)
|
||||
const auto loaded = selectedId
|
||||
? session->data().peerLoaded(selectedId)
|
||||
: nullptr;
|
||||
return (changingJoinAsFrom
|
||||
&& ranges::contains(list, not_null{ changingJoinAsFrom }))
|
||||
? not_null(changingJoinAsFrom)
|
||||
: (loaded && ranges::contains(list, not_null{ loaded }))
|
||||
? not_null(loaded)
|
||||
: ranges::contains(list, self)
|
||||
@@ -259,6 +256,42 @@ void ChooseJoinAsProcess::start(
|
||||
}();
|
||||
info.possibleJoinAs = std::move(list);
|
||||
|
||||
const auto onlyByMe = (info.possibleJoinAs.size() == 1)
|
||||
&& (info.possibleJoinAs.front() == self)
|
||||
&& (!peer->isChannel()
|
||||
|| !peer->asChannel()->amAnonymous()
|
||||
|| (peer->isBroadcast() && !peer->canWrite()));
|
||||
|
||||
// We already joined this voice chat, just rejoin with the same.
|
||||
const auto byAlreadyUsed = selectedId
|
||||
&& (info.joinAs->id == selectedId);
|
||||
|
||||
if (!changingJoinAsFrom && (onlyByMe || byAlreadyUsed)) {
|
||||
if (context != Context::JoinWithConfirm) {
|
||||
finish(info);
|
||||
return;
|
||||
}
|
||||
const auto real = peer->groupCall();
|
||||
const auto name = (real && !real->title().isEmpty())
|
||||
? real->title()
|
||||
: peer->name;
|
||||
auto box = Box<::ConfirmBox>(
|
||||
tr::lng_group_call_join_confirm(
|
||||
tr::now,
|
||||
lt_chat,
|
||||
Ui::Text::Bold(name),
|
||||
Ui::Text::WithEntities),
|
||||
tr::lng_group_call_join(tr::now),
|
||||
crl::guard(&_request->guard, [=] { finish(info); }));
|
||||
box->boxClosing(
|
||||
) | rpl::start_with_next([=] {
|
||||
_request = nullptr;
|
||||
}, _request->lifetime);
|
||||
|
||||
_request->box = box.data();
|
||||
_request->showBox(std::move(box));
|
||||
return;
|
||||
}
|
||||
auto box = Box(
|
||||
ChooseJoinAsBox,
|
||||
context,
|
||||
|
||||
@@ -28,6 +28,7 @@ public:
|
||||
enum class Context {
|
||||
Create,
|
||||
Join,
|
||||
JoinWithConfirm,
|
||||
Switch,
|
||||
};
|
||||
|
||||
@@ -37,7 +38,7 @@ public:
|
||||
Fn<void(object_ptr<Ui::BoxContent>)> showBox,
|
||||
Fn<void(QString)> showToast,
|
||||
Fn<void(JoinInfo)> done,
|
||||
PeerData *currentJoinAs = nullptr);
|
||||
PeerData *changingJoinAsFrom = nullptr);
|
||||
|
||||
private:
|
||||
struct ChannelsListRequest {
|
||||
|
||||
@@ -272,11 +272,6 @@ void GroupCall::setState(State state) {
|
||||
|
||||
if (state == State::Joined) {
|
||||
stopConnectingSound();
|
||||
if (!_hadJoinedState) {
|
||||
_hadJoinedState = true;
|
||||
applyGlobalShortcutChanges();
|
||||
_delegate->groupCallPlaySound(Delegate::GroupCallSound::Started);
|
||||
}
|
||||
if (const auto call = _peer->groupCall(); call && call->id() == _id) {
|
||||
call->setInCall();
|
||||
}
|
||||
@@ -426,6 +421,11 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
|
||||
LOG(("Call Info: Requesting join payload."));
|
||||
|
||||
_joinAs = as;
|
||||
if (const auto chat = _peer->asChat()) {
|
||||
chat->setGroupCallDefaultJoinAs(_joinAs->id);
|
||||
} else if (const auto channel = _peer->asChannel()) {
|
||||
channel->setGroupCallDefaultJoinAs(_joinAs->id);
|
||||
}
|
||||
|
||||
const auto weak = base::make_weak(this);
|
||||
_instance->emitJoinPayload([=](tgcalls::GroupJoinPayload payload) {
|
||||
@@ -474,6 +474,7 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
|
||||
applyMeInCallLocally();
|
||||
maybeSendMutedUpdate(wasMuteState);
|
||||
_peer->session().api().applyUpdates(updates);
|
||||
checkFirstTimeJoined();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
const auto type = error.type();
|
||||
LOG(("Call Error: Could not join, error: %1").arg(type));
|
||||
@@ -542,7 +543,7 @@ void GroupCall::applyMeInCallLocally() {
|
||||
| Flag::f_volume_by_admin // Self volume can only be set by admin.
|
||||
| ((muted() != MuteState::Active) ? Flag::f_muted : Flag(0))
|
||||
| (raisedHandRating > 0 ? Flag::f_raise_hand_rating : Flag(0));
|
||||
call->applyUpdateChecked(
|
||||
call->applyLocalUpdate(
|
||||
MTP_updateGroupCallParticipants(
|
||||
inputCall(),
|
||||
MTP_vector<MTPGroupCallParticipant>(
|
||||
@@ -587,7 +588,7 @@ void GroupCall::applyParticipantLocally(
|
||||
| (participant->raisedHandRating
|
||||
? Flag::f_raise_hand_rating
|
||||
: Flag(0));
|
||||
_peer->groupCall()->applyUpdateChecked(
|
||||
_peer->groupCall()->applyLocalUpdate(
|
||||
MTP_updateGroupCallParticipants(
|
||||
inputCall(),
|
||||
MTP_vector<MTPGroupCallParticipant>(
|
||||
@@ -885,18 +886,22 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) {
|
||||
handleOtherParticipants(data);
|
||||
return;
|
||||
}
|
||||
if (data.is_left() && data.vsource().v == _mySsrc) {
|
||||
// I was removed from the call, rejoin.
|
||||
LOG(("Call Info: Rejoin after got 'left' with my ssrc."));
|
||||
setState(State::Joining);
|
||||
rejoin();
|
||||
} else if (!data.is_left() && data.vsource().v != _mySsrc) {
|
||||
if (data.is_left()) {
|
||||
if (data.vsource().v == _mySsrc) {
|
||||
// I was removed from the call, rejoin.
|
||||
LOG(("Call Info: Rejoin after got 'left' with my ssrc."));
|
||||
setState(State::Joining);
|
||||
rejoin();
|
||||
}
|
||||
return;
|
||||
} else if (data.vsource().v != _mySsrc) {
|
||||
// I joined from another device, hangup.
|
||||
LOG(("Call Info: Hangup after '!left' with ssrc %1, my %2."
|
||||
).arg(data.vsource().v
|
||||
).arg(_mySsrc));
|
||||
_mySsrc = 0;
|
||||
hangup();
|
||||
return;
|
||||
}
|
||||
if (data.is_muted() && !data.is_can_self_unmute()) {
|
||||
setMuted(data.vraise_hand_rating().value_or_empty()
|
||||
@@ -925,12 +930,12 @@ void GroupCall::changeTitle(const QString &title) {
|
||||
return;
|
||||
}
|
||||
|
||||
real->setTitle(title);
|
||||
_api.request(MTPphone_EditGroupCallTitle(
|
||||
inputCall(),
|
||||
MTP_string(title)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_peer->session().api().applyUpdates(result);
|
||||
_titleChanged.fire({});
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
}).send();
|
||||
}
|
||||
@@ -1303,9 +1308,24 @@ void GroupCall::setInstanceConnected(
|
||||
if (nowCanSpeak) {
|
||||
notifyAboutAllowedToSpeak();
|
||||
}
|
||||
if (!_hadJoinedState && state() == State::Joined) {
|
||||
checkFirstTimeJoined();
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::checkFirstTimeJoined() {
|
||||
if (_hadJoinedState || state() != State::Joined) {
|
||||
return;
|
||||
}
|
||||
_hadJoinedState = true;
|
||||
applyGlobalShortcutChanges();
|
||||
_delegate->groupCallPlaySound(Delegate::GroupCallSound::Started);
|
||||
}
|
||||
|
||||
void GroupCall::notifyAboutAllowedToSpeak() {
|
||||
if (!_hadJoinedState) {
|
||||
return;
|
||||
}
|
||||
_delegate->groupCallPlaySound(
|
||||
Delegate::GroupCallSound::AllowedToSpeak);
|
||||
_allowedToSpeakNotifications.fire({});
|
||||
@@ -1369,11 +1389,6 @@ void GroupCall::sendSelfUpdate(SendUpdateType type) {
|
||||
}).send();
|
||||
}
|
||||
|
||||
auto GroupCall::instanceStateValue() const -> rpl::producer<InstanceState> {
|
||||
using namespace rpl::mappers;
|
||||
return _instanceState.value();
|
||||
}
|
||||
|
||||
void GroupCall::setCurrentAudioDevice(bool input, const QString &deviceId) {
|
||||
if (input) {
|
||||
_mediaDevices->switchToAudioInput(deviceId);
|
||||
|
||||
@@ -158,7 +158,12 @@ public:
|
||||
TransitionToRtc,
|
||||
Connected,
|
||||
};
|
||||
[[nodiscard]] rpl::producer<InstanceState> instanceStateValue() const;
|
||||
[[nodiscard]] InstanceState instanceState() const {
|
||||
return _instanceState.current();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<InstanceState> instanceStateValue() const {
|
||||
return _instanceState.value();
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<LevelUpdate> levelUpdates() const {
|
||||
return _levelUpdates.events();
|
||||
@@ -169,6 +174,9 @@ public:
|
||||
[[nodiscard]] rpl::producer<> allowedToSpeakNotifications() const {
|
||||
return _allowedToSpeakNotifications.events();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<> titleChanged() const {
|
||||
return _titleChanged.events();
|
||||
}
|
||||
static constexpr auto kSpeakLevelThreshold = 0.2;
|
||||
|
||||
void setCurrentAudioDevice(bool input, const QString &deviceId);
|
||||
@@ -242,6 +250,7 @@ private:
|
||||
|
||||
void checkGlobalShortcutAvailability();
|
||||
void checkJoined();
|
||||
void checkFirstTimeJoined();
|
||||
void notifyAboutAllowedToSpeak();
|
||||
|
||||
void playConnectingSound();
|
||||
@@ -304,6 +313,7 @@ private:
|
||||
base::flat_map<uint32, Data::LastSpokeTimes> _lastSpoke;
|
||||
rpl::event_stream<Group::RejoinEvent> _rejoinEvents;
|
||||
rpl::event_stream<> _allowedToSpeakNotifications;
|
||||
rpl::event_stream<> _titleChanged;
|
||||
base::Timer _lastSpokeCheckTimer;
|
||||
base::Timer _checkJoinedTimer;
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_calls.h"
|
||||
|
||||
namespace Calls {
|
||||
namespace Calls::Group {
|
||||
namespace {
|
||||
|
||||
constexpr auto kBlobsEnterDuration = crl::time(250);
|
||||
@@ -47,6 +47,7 @@ constexpr auto kMinorBlobFactor = 0.9f;
|
||||
constexpr auto kUserpicMinScale = 0.8;
|
||||
constexpr auto kMaxLevel = 1.;
|
||||
constexpr auto kWideScale = 5;
|
||||
constexpr auto kKeepRaisedHandStatusDuration = 3 * crl::time(1000);
|
||||
|
||||
const auto kSpeakerThreshold = std::vector<float>{
|
||||
Group::kDefaultVolume * 0.1f / Group::kMaxVolume,
|
||||
@@ -89,6 +90,7 @@ public:
|
||||
virtual bool rowIsMe(not_null<PeerData*> participantPeer) = 0;
|
||||
virtual bool rowCanMuteMembers() = 0;
|
||||
virtual void rowUpdateRow(not_null<Row*> row) = 0;
|
||||
virtual void rowScheduleRaisedHandStatusRemove(not_null<Row*> row) = 0;
|
||||
virtual void rowPaintIcon(
|
||||
Painter &p,
|
||||
QRect rect,
|
||||
@@ -115,6 +117,7 @@ public:
|
||||
void updateState(const Data::GroupCall::Participant *participant);
|
||||
void updateLevel(float level);
|
||||
void updateBlobAnimation(crl::time now);
|
||||
void clearRaisedHandStatus();
|
||||
[[nodiscard]] State state() const {
|
||||
return _state;
|
||||
}
|
||||
@@ -254,6 +257,7 @@ private:
|
||||
int _volume = Group::kDefaultVolume;
|
||||
bool _sounding = false;
|
||||
bool _speaking = false;
|
||||
bool _raisedHandStatus = false;
|
||||
bool _skipLevelUpdate = false;
|
||||
|
||||
};
|
||||
@@ -291,6 +295,7 @@ public:
|
||||
bool rowIsMe(not_null<PeerData*> participantPeer) override;
|
||||
bool rowCanMuteMembers() override;
|
||||
void rowUpdateRow(not_null<Row*> row) override;
|
||||
void rowScheduleRaisedHandStatusRemove(not_null<Row*> row) override;
|
||||
void rowPaintIcon(
|
||||
Painter &p,
|
||||
QRect rect,
|
||||
@@ -335,6 +340,7 @@ private:
|
||||
|
||||
[[nodiscard]] Data::GroupCall *resolvedRealCall() const;
|
||||
void appendInvitedUsers();
|
||||
void scheduleRaisedHandStatusRemove();
|
||||
|
||||
const base::weak_ptr<GroupCall> _call;
|
||||
not_null<PeerData*> _peer;
|
||||
@@ -348,13 +354,14 @@ private:
|
||||
rpl::event_stream<VolumeRequest> _changeVolumeRequests;
|
||||
rpl::event_stream<not_null<PeerData*>> _kickParticipantRequests;
|
||||
rpl::variable<int> _fullCount = 1;
|
||||
rpl::variable<int> _fullCountMin = 0;
|
||||
rpl::variable<int> _fullCountMax = std::numeric_limits<int>::max();
|
||||
|
||||
not_null<QWidget*> _menuParent;
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
base::flat_set<not_null<PeerData*>> _menuCheckRowsAfterHidden;
|
||||
|
||||
base::flat_map<PeerListRowId, crl::time> _raisedHandStatusRemoveAt;
|
||||
base::Timer _raisedHandStatusRemoveTimer;
|
||||
|
||||
base::flat_map<uint32, not_null<Row*>> _soundingRowBySsrc;
|
||||
Ui::Animations::Basic _soundingAnimation;
|
||||
|
||||
@@ -479,6 +486,15 @@ void Row::setSounding(bool sounding) {
|
||||
}
|
||||
}
|
||||
|
||||
void Row::clearRaisedHandStatus() {
|
||||
if (!_raisedHandStatus) {
|
||||
return;
|
||||
}
|
||||
_raisedHandStatus = false;
|
||||
refreshStatus();
|
||||
_delegate->rowUpdateRow(this);
|
||||
}
|
||||
|
||||
void Row::setState(State state) {
|
||||
if (_state == state) {
|
||||
return;
|
||||
@@ -486,10 +502,16 @@ void Row::setState(State state) {
|
||||
const auto wasActive = (_state == State::Active);
|
||||
const auto wasMuted = (_state == State::Muted)
|
||||
|| (_state == State::RaisedHand);
|
||||
const auto wasRaisedHand = (_state == State::RaisedHand);
|
||||
_state = state;
|
||||
const auto nowActive = (_state == State::Active);
|
||||
const auto nowMuted = (_state == State::Muted)
|
||||
|| (_state == State::RaisedHand);
|
||||
const auto nowRaisedHand = (_state == State::RaisedHand);
|
||||
if (!wasRaisedHand && nowRaisedHand) {
|
||||
_raisedHandStatus = true;
|
||||
_delegate->rowScheduleRaisedHandStatusRemove(this);
|
||||
}
|
||||
if (nowActive != wasActive) {
|
||||
_activeAnimation.start(
|
||||
[=] { _delegate->rowUpdateRow(this); },
|
||||
@@ -710,10 +732,10 @@ void Row::paintStatusText(
|
||||
const auto &font = st::normalFont;
|
||||
const auto about = (_state == State::Inactive
|
||||
|| _state == State::Muted
|
||||
|| _state == State::RaisedHand)
|
||||
|| (_state == State::RaisedHand && !_raisedHandStatus))
|
||||
? _aboutText
|
||||
: QString();
|
||||
if (_aboutText.isEmpty()
|
||||
if (about.isEmpty()
|
||||
&& _state != State::Invited
|
||||
&& _state != State::MutedByMe) {
|
||||
p.save();
|
||||
@@ -743,8 +765,8 @@ void Row::paintStatusText(
|
||||
outerWidth,
|
||||
(_state == State::MutedByMe
|
||||
? tr::lng_group_call_muted_by_me_status(tr::now)
|
||||
: !_aboutText.isEmpty()
|
||||
? font->m.elidedText(_aboutText, Qt::ElideRight, availableWidth)
|
||||
: !about.isEmpty()
|
||||
? font->m.elidedText(about, Qt::ElideRight, availableWidth)
|
||||
: _delegate->rowIsMe(peer())
|
||||
? tr::lng_status_connecting(tr::now)
|
||||
: tr::lng_group_call_invited_status(tr::now)));
|
||||
@@ -802,7 +824,7 @@ void Row::refreshStatus() {
|
||||
? u"%1% %2"_q
|
||||
.arg(std::round(_volume / 100.))
|
||||
.arg(tr::lng_group_call_active(tr::now))
|
||||
: (_state == State::RaisedHand)
|
||||
: _raisedHandStatus
|
||||
? tr::lng_group_call_raised_hand_status(tr::now)
|
||||
: tr::lng_group_call_inactive(tr::now)),
|
||||
_speaking);
|
||||
@@ -833,6 +855,7 @@ MembersController::MembersController(
|
||||
: _call(call)
|
||||
, _peer(call->peer())
|
||||
, _menuParent(menuParent)
|
||||
, _raisedHandStatusRemoveTimer([=] { scheduleRaisedHandStatusRemove(); })
|
||||
, _inactiveCrossLine(st::groupCallMemberInactiveCrossLine)
|
||||
, _coloredCrossLine(st::groupCallMemberColoredCrossLine) {
|
||||
setupListChangeViewers(call);
|
||||
@@ -929,19 +952,11 @@ void MembersController::setupListChangeViewers(not_null<GroupCall*> call) {
|
||||
delegate()->peerListRefreshRows();
|
||||
});
|
||||
if (const auto row = findRow(event.wasJoinAs)) {
|
||||
if (row->state() != Row::State::Invited) {
|
||||
if (const auto min = _fullCountMin.current()) {
|
||||
_fullCountMin = min - 1;
|
||||
}
|
||||
}
|
||||
removeRow(row);
|
||||
}
|
||||
if (findRow(event.nowJoinAs)) {
|
||||
return;
|
||||
} else if (auto row = createRowForMe()) {
|
||||
if (row->state() != Row::State::Invited) {
|
||||
_fullCountMin = _fullCountMin.current() + 1;
|
||||
}
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
}
|
||||
}, _lifetime);
|
||||
@@ -951,13 +966,7 @@ void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
_realCallRawValue = real;
|
||||
_realId = real->id();
|
||||
|
||||
_fullCount = rpl::combine(
|
||||
real->fullCountValue(),
|
||||
_fullCountMin.value(),
|
||||
_fullCountMax.value()
|
||||
) | rpl::map([](int value, int min, int max) {
|
||||
return std::max(std::clamp(value, min, max), 1);
|
||||
});
|
||||
_fullCount = real->fullCountValue();
|
||||
|
||||
real->participantsSliceAdded(
|
||||
) | rpl::start_with_next([=] {
|
||||
@@ -978,9 +987,6 @@ void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
if (isMe(participantPeer)) {
|
||||
updateRow(row, nullptr);
|
||||
} else {
|
||||
if (const auto min = _fullCountMin.current()) {
|
||||
_fullCountMin = min - 1;
|
||||
}
|
||||
removeRow(row);
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
@@ -1018,37 +1024,41 @@ void MembersController::appendInvitedUsers() {
|
||||
void MembersController::updateRow(
|
||||
const std::optional<Data::GroupCall::Participant> &was,
|
||||
const Data::GroupCall::Participant &now) {
|
||||
auto reorderIfInvitedBeforeIndex = 0;
|
||||
auto countChange = 0;
|
||||
auto reorderIfInvitedBefore = 0;
|
||||
auto checkPosition = (Row*)nullptr;
|
||||
auto addedToBottom = (Row*)nullptr;
|
||||
if (const auto row = findRow(now.peer)) {
|
||||
if (row->state() == Row::State::Invited) {
|
||||
reorderIfInvitedBeforeIndex = row->absoluteIndex();
|
||||
countChange = 1;
|
||||
reorderIfInvitedBefore = row->absoluteIndex();
|
||||
}
|
||||
updateRow(row, &now);
|
||||
if ((now.speaking && (!was || !was->speaking))
|
||||
|| (now.raisedHandRating != (was ? was->raisedHandRating : 0))
|
||||
|| (!now.canSelfUnmute && was && was->canSelfUnmute)) {
|
||||
checkRowPosition(row);
|
||||
checkPosition = row;
|
||||
}
|
||||
} else if (auto row = createRow(now)) {
|
||||
if (row->speaking()) {
|
||||
delegate()->peerListPrependRow(std::move(row));
|
||||
} else {
|
||||
reorderIfInvitedBeforeIndex = delegate()->peerListFullRowsCount();
|
||||
reorderIfInvitedBefore = delegate()->peerListFullRowsCount();
|
||||
if (now.raisedHandRating != 0) {
|
||||
checkPosition = row.get();
|
||||
} else {
|
||||
addedToBottom = row.get();
|
||||
}
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
countChange = 1;
|
||||
}
|
||||
static constexpr auto kInvited = Row::State::Invited;
|
||||
const auto reorder = [&] {
|
||||
const auto count = reorderIfInvitedBeforeIndex;
|
||||
const auto count = reorderIfInvitedBefore;
|
||||
if (count <= 0) {
|
||||
return false;
|
||||
}
|
||||
const auto row = delegate()->peerListRowAt(
|
||||
reorderIfInvitedBeforeIndex - 1).get();
|
||||
reorderIfInvitedBefore - 1).get();
|
||||
return (static_cast<Row*>(row)->state() == kInvited);
|
||||
}();
|
||||
if (reorder) {
|
||||
@@ -1056,12 +1066,25 @@ void MembersController::updateRow(
|
||||
return static_cast<const Row&>(row).state() != kInvited;
|
||||
});
|
||||
}
|
||||
if (countChange) {
|
||||
const auto fullCountMin = _fullCountMin.current() + countChange;
|
||||
if (_fullCountMax.current() < fullCountMin) {
|
||||
_fullCountMax = fullCountMin;
|
||||
if (checkPosition) {
|
||||
checkRowPosition(checkPosition);
|
||||
} else if (addedToBottom) {
|
||||
const auto real = resolvedRealCall();
|
||||
if (real && real->joinedToTop()) {
|
||||
const auto proj = [&](const PeerListRow &other) {
|
||||
const auto &real = static_cast<const Row&>(other);
|
||||
return real.speaking()
|
||||
? 2
|
||||
: (&real == addedToBottom)
|
||||
? 1
|
||||
: 0;
|
||||
};
|
||||
delegate()->peerListSortRows([&](
|
||||
const PeerListRow &a,
|
||||
const PeerListRow &b) {
|
||||
return proj(a) > proj(b);
|
||||
});
|
||||
}
|
||||
_fullCountMin = fullCountMin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1163,7 +1186,7 @@ void MembersController::checkRowPosition(not_null<Row*> row) {
|
||||
// All force muted at the bottom, but 'row' still above others.
|
||||
? (&real == row.get() ? 1ULL : 0ULL)
|
||||
// All not force-muted lie between raised hands and speaking.
|
||||
: (std::numeric_limits<uint64>::max() - 2);
|
||||
: (kTop - 2);
|
||||
};
|
||||
const auto projForOther = [&](const PeerListRow &other) {
|
||||
const auto &real = static_cast<const Row&>(other);
|
||||
@@ -1218,11 +1241,6 @@ void MembersController::updateRow(
|
||||
_soundingAnimation.stop();
|
||||
}
|
||||
|
||||
if (!participant && wasInChat) {
|
||||
if (const auto min = _fullCountMin.current()) {
|
||||
_fullCountMin = min - 1;
|
||||
}
|
||||
}
|
||||
delegate()->peerListUpdateRow(row);
|
||||
}
|
||||
|
||||
@@ -1268,7 +1286,6 @@ void MembersController::prepare() {
|
||||
; real && call && real->id() == call->id()) {
|
||||
prepareRows(real);
|
||||
} else if (auto row = createRowForMe()) {
|
||||
_fullCountMin = (row->state() == Row::State::Invited) ? 0 : 1;
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
@@ -1289,7 +1306,6 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
|
||||
auto foundMe = false;
|
||||
auto changed = false;
|
||||
const auto &participants = real->participants();
|
||||
auto fullCountMin = 0;
|
||||
auto count = delegate()->peerListFullRowsCount();
|
||||
for (auto i = 0; i != count;) {
|
||||
auto row = delegate()->peerListRowAt(i);
|
||||
@@ -1304,7 +1320,6 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
|
||||
participantPeer,
|
||||
&Data::GroupCall::Participant::peer);
|
||||
if (contains) {
|
||||
++fullCountMin;
|
||||
++i;
|
||||
} else {
|
||||
changed = true;
|
||||
@@ -1323,9 +1338,6 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
|
||||
? createRow(*i)
|
||||
: createRowForMe();
|
||||
if (row) {
|
||||
if (row->state() != Row::State::Invited) {
|
||||
++fullCountMin;
|
||||
}
|
||||
changed = true;
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
}
|
||||
@@ -1333,20 +1345,12 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
|
||||
}
|
||||
for (const auto &participant : participants) {
|
||||
if (auto row = createRow(participant)) {
|
||||
++fullCountMin;
|
||||
changed = true;
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
delegate()->peerListRefreshRows();
|
||||
if (_fullCountMax.current() < fullCountMin) {
|
||||
_fullCountMax = fullCountMin;
|
||||
}
|
||||
_fullCountMin = fullCountMin;
|
||||
if (real->participantsLoaded()) {
|
||||
_fullCountMax = fullCountMin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1378,6 +1382,44 @@ void MembersController::rowUpdateRow(not_null<Row*> row) {
|
||||
delegate()->peerListUpdateRow(row);
|
||||
}
|
||||
|
||||
void MembersController::rowScheduleRaisedHandStatusRemove(
|
||||
not_null<Row*> row) {
|
||||
const auto id = row->peer()->id;
|
||||
const auto when = crl::now() + kKeepRaisedHandStatusDuration;
|
||||
const auto i = _raisedHandStatusRemoveAt.find(id);
|
||||
if (i != _raisedHandStatusRemoveAt.end()) {
|
||||
i->second = when;
|
||||
} else {
|
||||
_raisedHandStatusRemoveAt.emplace(id, when);
|
||||
}
|
||||
scheduleRaisedHandStatusRemove();
|
||||
}
|
||||
|
||||
void MembersController::scheduleRaisedHandStatusRemove() {
|
||||
auto waiting = crl::time(0);
|
||||
const auto now = crl::now();
|
||||
for (auto i = begin(_raisedHandStatusRemoveAt)
|
||||
; i != end(_raisedHandStatusRemoveAt);) {
|
||||
if (i->second <= now) {
|
||||
if (const auto row = delegate()->peerListFindRow(i->first)) {
|
||||
static_cast<Row*>(row)->clearRaisedHandStatus();
|
||||
}
|
||||
i = _raisedHandStatusRemoveAt.erase(i);
|
||||
} else {
|
||||
if (!waiting || waiting > (i->second - now)) {
|
||||
waiting = i->second - now;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
if (waiting > 0) {
|
||||
if (!_raisedHandStatusRemoveTimer.isActive()
|
||||
|| _raisedHandStatusRemoveTimer.remainingTime() > waiting) {
|
||||
_raisedHandStatusRemoveTimer.callOnce(waiting);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MembersController::rowPaintIcon(
|
||||
Painter &p,
|
||||
QRect rect,
|
||||
@@ -1747,8 +1789,7 @@ std::unique_ptr<Row> MembersController::createInvitedRow(
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
GroupMembers::GroupMembers(
|
||||
Members::Members(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<GroupCall*> call)
|
||||
: RpWidget(parent)
|
||||
@@ -1762,25 +1803,25 @@ GroupMembers::GroupMembers(
|
||||
_listController->setDelegate(static_cast<PeerListDelegate*>(this));
|
||||
}
|
||||
|
||||
auto GroupMembers::toggleMuteRequests() const
|
||||
auto Members::toggleMuteRequests() const
|
||||
-> rpl::producer<Group::MuteRequest> {
|
||||
return static_cast<MembersController*>(
|
||||
_listController.get())->toggleMuteRequests();
|
||||
}
|
||||
|
||||
auto GroupMembers::changeVolumeRequests() const
|
||||
auto Members::changeVolumeRequests() const
|
||||
-> rpl::producer<Group::VolumeRequest> {
|
||||
return static_cast<MembersController*>(
|
||||
_listController.get())->changeVolumeRequests();
|
||||
}
|
||||
|
||||
auto GroupMembers::kickParticipantRequests() const
|
||||
auto Members::kickParticipantRequests() const
|
||||
-> rpl::producer<not_null<PeerData*>> {
|
||||
return static_cast<MembersController*>(
|
||||
_listController.get())->kickParticipantRequests();
|
||||
}
|
||||
|
||||
int GroupMembers::desiredHeight() const {
|
||||
int Members::desiredHeight() const {
|
||||
const auto top = _addMember ? _addMember->height() : 0;
|
||||
auto count = [&] {
|
||||
if (const auto call = _call.get()) {
|
||||
@@ -1798,7 +1839,7 @@ int GroupMembers::desiredHeight() const {
|
||||
+ (use ? st::lineWidth : 0);
|
||||
}
|
||||
|
||||
rpl::producer<int> GroupMembers::desiredHeightValue() const {
|
||||
rpl::producer<int> Members::desiredHeightValue() const {
|
||||
const auto controller = static_cast<MembersController*>(
|
||||
_listController.get());
|
||||
return rpl::combine(
|
||||
@@ -1810,12 +1851,28 @@ rpl::producer<int> GroupMembers::desiredHeightValue() const {
|
||||
});
|
||||
}
|
||||
|
||||
void GroupMembers::setupAddMember(not_null<GroupCall*> call) {
|
||||
void Members::setupAddMember(not_null<GroupCall*> call) {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto peer = call->peer();
|
||||
if (peer->isBroadcast()) {
|
||||
_canAddMembers = false;
|
||||
if (const auto channel = peer->asBroadcast()) {
|
||||
_canAddMembers = rpl::single(
|
||||
false
|
||||
) | rpl::then(peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::GroupCall
|
||||
) | rpl::map([=] {
|
||||
return peer->groupCall();
|
||||
}) | rpl::filter([=](Data::GroupCall *real) {
|
||||
const auto call = _call.get();
|
||||
return call && real && (real->id() == call->id());
|
||||
}) | rpl::take(
|
||||
1
|
||||
) | rpl::map([=] {
|
||||
return Data::PeerFlagValue(
|
||||
channel,
|
||||
MTPDchannel::Flag::f_username);
|
||||
}) | rpl::flatten_latest());
|
||||
} else {
|
||||
_canAddMembers = Data::CanWriteValue(peer.get());
|
||||
SubscribeToMigration(
|
||||
@@ -1851,12 +1908,12 @@ void GroupMembers::setupAddMember(not_null<GroupCall*> call) {
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
rpl::producer<int> GroupMembers::fullCountValue() const {
|
||||
rpl::producer<int> Members::fullCountValue() const {
|
||||
return static_cast<MembersController*>(
|
||||
_listController.get())->fullCountValue();
|
||||
}
|
||||
|
||||
void GroupMembers::setupList() {
|
||||
void Members::setupList() {
|
||||
_listController->setStyleOverrides(&st::groupCallMembersList);
|
||||
_list = _scroll->setOwnedWidget(object_ptr<ListWidget>(
|
||||
this,
|
||||
@@ -1877,11 +1934,11 @@ void GroupMembers::setupList() {
|
||||
updateControlsGeometry();
|
||||
}
|
||||
|
||||
void GroupMembers::resizeEvent(QResizeEvent *e) {
|
||||
void Members::resizeEvent(QResizeEvent *e) {
|
||||
updateControlsGeometry();
|
||||
}
|
||||
|
||||
void GroupMembers::resizeToList() {
|
||||
void Members::resizeToList() {
|
||||
if (!_list) {
|
||||
return;
|
||||
}
|
||||
@@ -1898,7 +1955,7 @@ void GroupMembers::resizeToList() {
|
||||
}
|
||||
}
|
||||
|
||||
void GroupMembers::updateControlsGeometry() {
|
||||
void Members::updateControlsGeometry() {
|
||||
if (!_list) {
|
||||
return;
|
||||
}
|
||||
@@ -1912,7 +1969,7 @@ void GroupMembers::updateControlsGeometry() {
|
||||
_list->resizeToWidth(width());
|
||||
}
|
||||
|
||||
void GroupMembers::setupFakeRoundCorners() {
|
||||
void Members::setupFakeRoundCorners() {
|
||||
const auto size = st::roundRadiusLarge;
|
||||
const auto full = 3 * size;
|
||||
const auto imagePartSize = size * cIntRetinaFactor();
|
||||
@@ -1975,40 +2032,40 @@ void GroupMembers::setupFakeRoundCorners() {
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void GroupMembers::peerListSetTitle(rpl::producer<QString> title) {
|
||||
void Members::peerListSetTitle(rpl::producer<QString> title) {
|
||||
}
|
||||
|
||||
void GroupMembers::peerListSetAdditionalTitle(rpl::producer<QString> title) {
|
||||
void Members::peerListSetAdditionalTitle(rpl::producer<QString> title) {
|
||||
}
|
||||
|
||||
void GroupMembers::peerListSetHideEmpty(bool hide) {
|
||||
void Members::peerListSetHideEmpty(bool hide) {
|
||||
}
|
||||
|
||||
bool GroupMembers::peerListIsRowChecked(not_null<PeerListRow*> row) {
|
||||
bool Members::peerListIsRowChecked(not_null<PeerListRow*> row) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void GroupMembers::peerListScrollToTop() {
|
||||
void Members::peerListScrollToTop() {
|
||||
}
|
||||
|
||||
int GroupMembers::peerListSelectedRowsCount() {
|
||||
int Members::peerListSelectedRowsCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GroupMembers::peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) {
|
||||
Unexpected("Item selection in Calls::GroupMembers.");
|
||||
void Members::peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) {
|
||||
Unexpected("Item selection in Calls::Members.");
|
||||
}
|
||||
|
||||
void GroupMembers::peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) {
|
||||
Unexpected("Item selection in Calls::GroupMembers.");
|
||||
void Members::peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) {
|
||||
Unexpected("Item selection in Calls::Members.");
|
||||
}
|
||||
|
||||
void GroupMembers::peerListFinishSelectedRowsBunch() {
|
||||
void Members::peerListFinishSelectedRowsBunch() {
|
||||
}
|
||||
|
||||
void GroupMembers::peerListSetDescription(
|
||||
void Members::peerListSetDescription(
|
||||
object_ptr<Ui::FlatLabel> description) {
|
||||
description.destroy();
|
||||
}
|
||||
|
||||
} // namespace Calls
|
||||
} // namespace Calls::Group
|
||||
|
||||
@@ -19,19 +19,19 @@ class GroupCall;
|
||||
} // namespace Data
|
||||
|
||||
namespace Calls {
|
||||
class GroupCall;
|
||||
} // namespace Calls
|
||||
|
||||
namespace Calls::Group {
|
||||
|
||||
namespace Group {
|
||||
struct VolumeRequest;
|
||||
struct MuteRequest;
|
||||
} // namespace Group
|
||||
|
||||
class GroupCall;
|
||||
|
||||
class GroupMembers final
|
||||
class Members final
|
||||
: public Ui::RpWidget
|
||||
, private PeerListContentDelegate {
|
||||
public:
|
||||
GroupMembers(
|
||||
Members(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<GroupCall*> call);
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Calls::Group {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxGroupCallLength = 40;
|
||||
|
||||
void EditGroupCallTitleBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
const QString &placeholder,
|
||||
@@ -42,6 +44,7 @@ void EditGroupCallTitleBox(
|
||||
st::groupCallField,
|
||||
rpl::single(placeholder),
|
||||
title));
|
||||
input->setMaxLength(kMaxGroupCallLength);
|
||||
box->setFocusCallback([=] {
|
||||
input->setFocusFast();
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "calls/calls_group_settings.h"
|
||||
#include "calls/calls_group_menu.h"
|
||||
#include "ui/platform/ui_platform_window_title.h"
|
||||
#include "ui/platform/ui_platform_utility.h"
|
||||
#include "ui/controls/call_mute_button.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/window.h"
|
||||
@@ -23,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/toasts/common_toasts.h"
|
||||
#include "ui/special_buttons.h"
|
||||
#include "info/profile/info_profile_values.h" // Info::Profile::Value.
|
||||
#include "core/application.h"
|
||||
@@ -48,10 +50,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
namespace Calls {
|
||||
namespace Calls::Group {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSpacePushToTalkDelay = crl::time(250);
|
||||
constexpr auto kRecordingAnimationDuration = crl::time(1200);
|
||||
constexpr auto kRecordingOpacity = 0.6;
|
||||
|
||||
class InviteController final : public ParticipantsBoxController {
|
||||
public:
|
||||
@@ -246,7 +250,7 @@ std::unique_ptr<PeerListRow> InviteContactsController::createRow(
|
||||
|
||||
} // namespace
|
||||
|
||||
GroupPanel::GroupPanel(not_null<GroupCall*> call)
|
||||
Panel::Panel(not_null<GroupCall*> call)
|
||||
: _call(call)
|
||||
, _peer(call->peer())
|
||||
, _window(std::make_unique<Ui::Window>(Core::App().getModalParent()))
|
||||
@@ -281,6 +285,8 @@ GroupPanel::GroupPanel(not_null<GroupCall*> call)
|
||||
initControls();
|
||||
initLayout();
|
||||
showAndActivate();
|
||||
setupJoinAsChangedToasts();
|
||||
setupTitleChangedToasts();
|
||||
|
||||
call->allowedToSpeakNotifications(
|
||||
) | rpl::start_with_next([=] {
|
||||
@@ -299,21 +305,21 @@ GroupPanel::GroupPanel(not_null<GroupCall*> call)
|
||||
.text = tr::lng_group_call_can_speak(
|
||||
tr::now,
|
||||
lt_chat,
|
||||
Ui::Text::WithEntities(name),
|
||||
Ui::Text::RichLangValue),
|
||||
Ui::Text::Bold(name),
|
||||
Ui::Text::WithEntities),
|
||||
.st = &st::defaultToast,
|
||||
});
|
||||
}
|
||||
}, widget()->lifetime());
|
||||
}
|
||||
|
||||
GroupPanel::~GroupPanel() {
|
||||
Panel::~Panel() {
|
||||
if (_menu) {
|
||||
_menu.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void GroupPanel::setupRealCallViewers(not_null<GroupCall*> call) {
|
||||
void Panel::setupRealCallViewers(not_null<GroupCall*> call) {
|
||||
const auto peer = call->peer();
|
||||
peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
@@ -329,21 +335,21 @@ void GroupPanel::setupRealCallViewers(not_null<GroupCall*> call) {
|
||||
}, _window->lifetime());
|
||||
}
|
||||
|
||||
bool GroupPanel::isActive() const {
|
||||
bool Panel::isActive() const {
|
||||
return _window->isActiveWindow()
|
||||
&& _window->isVisible()
|
||||
&& !(_window->windowState() & Qt::WindowMinimized);
|
||||
}
|
||||
|
||||
void GroupPanel::minimize() {
|
||||
void Panel::minimize() {
|
||||
_window->setWindowState(_window->windowState() | Qt::WindowMinimized);
|
||||
}
|
||||
|
||||
void GroupPanel::close() {
|
||||
void Panel::close() {
|
||||
_window->close();
|
||||
}
|
||||
|
||||
void GroupPanel::showAndActivate() {
|
||||
void Panel::showAndActivate() {
|
||||
if (_window->isHidden()) {
|
||||
_window->show();
|
||||
}
|
||||
@@ -356,7 +362,7 @@ void GroupPanel::showAndActivate() {
|
||||
_window->setFocus();
|
||||
}
|
||||
|
||||
void GroupPanel::migrate(not_null<ChannelData*> channel) {
|
||||
void Panel::migrate(not_null<ChannelData*> channel) {
|
||||
_peer = channel;
|
||||
_peerLifetime.destroy();
|
||||
subscribeToPeerChanges();
|
||||
@@ -364,7 +370,7 @@ void GroupPanel::migrate(not_null<ChannelData*> channel) {
|
||||
refreshTitle();
|
||||
}
|
||||
|
||||
void GroupPanel::subscribeToPeerChanges() {
|
||||
void Panel::subscribeToPeerChanges() {
|
||||
Info::Profile::NameValue(
|
||||
_peer
|
||||
) | rpl::start_with_next([=](const TextWithEntities &name) {
|
||||
@@ -372,7 +378,7 @@ void GroupPanel::subscribeToPeerChanges() {
|
||||
}, _peerLifetime);
|
||||
}
|
||||
|
||||
void GroupPanel::initWindow() {
|
||||
void Panel::initWindow() {
|
||||
_window->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
_window->setAttribute(Qt::WA_NoSystemBackground);
|
||||
_window->setWindowIcon(
|
||||
@@ -405,13 +411,17 @@ void GroupPanel::initWindow() {
|
||||
0,
|
||||
widget()->width(),
|
||||
st::groupCallMembersTop);
|
||||
return titleRect.contains(widgetPoint)
|
||||
return (titleRect.contains(widgetPoint)
|
||||
&& (!_menuToggle || !_menuToggle->geometry().contains(widgetPoint))
|
||||
&& (!_menu || !_menu->geometry().contains(widgetPoint))
|
||||
&& (!_recordingMark || !_recordingMark->geometry().contains(widgetPoint))
|
||||
&& (!_joinAsToggle || !_joinAsToggle->geometry().contains(widgetPoint)))
|
||||
? (Flag::Move | Flag::Maximize)
|
||||
: Flag::None;
|
||||
});
|
||||
}
|
||||
|
||||
void GroupPanel::initWidget() {
|
||||
void Panel::initWidget() {
|
||||
widget()->setMouseTracking(true);
|
||||
|
||||
widget()->paintRequest(
|
||||
@@ -429,7 +439,7 @@ void GroupPanel::initWidget() {
|
||||
}, widget()->lifetime());
|
||||
}
|
||||
|
||||
void GroupPanel::endCall() {
|
||||
void Panel::endCall() {
|
||||
if (!_call) {
|
||||
return;
|
||||
} else if (!_call->peer()->canManageGroupCall()) {
|
||||
@@ -437,13 +447,13 @@ void GroupPanel::endCall() {
|
||||
return;
|
||||
}
|
||||
_layerBg->showBox(Box(
|
||||
Group::LeaveBox,
|
||||
LeaveBox,
|
||||
_call,
|
||||
false,
|
||||
Group::BoxContext::GroupCallPanel));
|
||||
BoxContext::GroupCallPanel));
|
||||
}
|
||||
|
||||
void GroupPanel::initControls() {
|
||||
void Panel::initControls() {
|
||||
_mute->clicks(
|
||||
) | rpl::filter([=](Qt::MouseButton button) {
|
||||
return (button == Qt::LeftButton) && (_call != nullptr);
|
||||
@@ -462,11 +472,11 @@ void GroupPanel::initControls() {
|
||||
_hangup->setClickedCallback([=] { endCall(); });
|
||||
_settings->setClickedCallback([=] {
|
||||
if (_call) {
|
||||
_layerBg->showBox(Box(Group::SettingsBox, _call));
|
||||
_layerBg->showBox(Box(SettingsBox, _call));
|
||||
}
|
||||
});
|
||||
|
||||
_settings->setText(tr::lng_menu_settings());
|
||||
_settings->setText(tr::lng_group_call_settings());
|
||||
_hangup->setText(tr::lng_group_call_leave());
|
||||
|
||||
_members->desiredHeightValue(
|
||||
@@ -477,7 +487,7 @@ void GroupPanel::initControls() {
|
||||
initWithCall(_call);
|
||||
}
|
||||
|
||||
void GroupPanel::initWithCall(GroupCall *call) {
|
||||
void Panel::initWithCall(GroupCall *call) {
|
||||
_callLifetime.destroy();
|
||||
_call = call;
|
||||
if (!_call) {
|
||||
@@ -504,14 +514,14 @@ void GroupPanel::initWithCall(GroupCall *call) {
|
||||
}, _callLifetime);
|
||||
|
||||
_members->toggleMuteRequests(
|
||||
) | rpl::start_with_next([=](Group::MuteRequest request) {
|
||||
) | rpl::start_with_next([=](MuteRequest request) {
|
||||
if (_call) {
|
||||
_call->toggleMute(request);
|
||||
}
|
||||
}, _callLifetime);
|
||||
|
||||
_members->changeVolumeRequests(
|
||||
) | rpl::start_with_next([=](Group::VolumeRequest request) {
|
||||
) | rpl::start_with_next([=](VolumeRequest request) {
|
||||
if (_call) {
|
||||
_call->changeVolume(request);
|
||||
}
|
||||
@@ -524,10 +534,27 @@ void GroupPanel::initWithCall(GroupCall *call) {
|
||||
}
|
||||
}, _callLifetime);
|
||||
|
||||
const auto showBox = [=](object_ptr<Ui::BoxContent> next) {
|
||||
_layerBg->showBox(std::move(next));
|
||||
};
|
||||
const auto showToast = [=](QString text) {
|
||||
Ui::Toast::Show(widget(), text);
|
||||
};
|
||||
auto [shareLinkCallback, shareLinkLifetime] = ShareInviteLinkAction(
|
||||
_peer,
|
||||
showBox,
|
||||
showToast);
|
||||
auto shareLink = std::move(shareLinkCallback);
|
||||
_members->lifetime().add(std::move(shareLinkLifetime));
|
||||
|
||||
_members->addMembersRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (_call) {
|
||||
addMembers();
|
||||
if (_peer->isBroadcast() && _peer->asChannel()->hasUsername()) {
|
||||
shareLink();
|
||||
} else {
|
||||
addMembers();
|
||||
}
|
||||
}
|
||||
}, _callLifetime);
|
||||
|
||||
@@ -573,15 +600,61 @@ void GroupPanel::initWithCall(GroupCall *call) {
|
||||
}, _callLifetime);
|
||||
}
|
||||
|
||||
void GroupPanel::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
void Panel::setupJoinAsChangedToasts() {
|
||||
_call->rejoinEvents(
|
||||
) | rpl::filter([](RejoinEvent event) {
|
||||
return (event.wasJoinAs != event.nowJoinAs);
|
||||
}) | rpl::map([=] {
|
||||
return _call->stateValue() | rpl::filter([](State state) {
|
||||
return (state == State::Joined);
|
||||
}) | rpl::take(1);
|
||||
}) | rpl::flatten_latest() | rpl::start_with_next([=] {
|
||||
Ui::ShowMultilineToast(Ui::MultilineToastArgs{
|
||||
.text = tr::lng_group_call_join_as_changed(
|
||||
tr::now,
|
||||
lt_name,
|
||||
Ui::Text::Bold(_call->joinAs()->name),
|
||||
Ui::Text::WithEntities),
|
||||
.parentOverride = widget(),
|
||||
});
|
||||
}, widget()->lifetime());
|
||||
}
|
||||
|
||||
void Panel::setupTitleChangedToasts() {
|
||||
_call->titleChanged(
|
||||
) | rpl::filter([=] {
|
||||
return _peer->groupCall() && _peer->groupCall()->id() == _call->id();
|
||||
}) | rpl::map([=] {
|
||||
return _peer->groupCall()->title().isEmpty()
|
||||
? _peer->name
|
||||
: _peer->groupCall()->title();
|
||||
}) | rpl::start_with_next([=](const QString &title) {
|
||||
Ui::ShowMultilineToast(Ui::MultilineToastArgs{
|
||||
.text = tr::lng_group_call_title_changed(
|
||||
tr::now,
|
||||
lt_title,
|
||||
Ui::Text::Bold(title),
|
||||
Ui::Text::WithEntities),
|
||||
.parentOverride = widget(),
|
||||
});
|
||||
}, widget()->lifetime());
|
||||
}
|
||||
|
||||
void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
_titleText = real->titleValue();
|
||||
|
||||
const auto validateRecordingMark = [=](bool recording) {
|
||||
if (!recording && _recordingMark) {
|
||||
_recordingMark.destroy();
|
||||
} else if (recording && !_recordingMark) {
|
||||
struct State {
|
||||
Ui::Animations::Simple animation;
|
||||
base::Timer timer;
|
||||
bool opaque = true;
|
||||
};
|
||||
_recordingMark.create(widget());
|
||||
_recordingMark->show();
|
||||
const auto state = _recordingMark->lifetime().make_state<State>();
|
||||
const auto size = st::groupCallRecordingMark;
|
||||
const auto skip = st::groupCallRecordingMarkSkip;
|
||||
_recordingMark->resize(size + 2 * skip, size + 2 * skip);
|
||||
@@ -590,12 +663,27 @@ void GroupPanel::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
widget(),
|
||||
tr::lng_group_call_is_recorded(tr::now));
|
||||
});
|
||||
const auto animate = [=] {
|
||||
const auto opaque = state->opaque;
|
||||
state->opaque = !opaque;
|
||||
state->animation.start(
|
||||
[=] { _recordingMark->update(); },
|
||||
opaque ? 1. : kRecordingOpacity,
|
||||
opaque ? kRecordingOpacity : 1.,
|
||||
kRecordingAnimationDuration);
|
||||
};
|
||||
state->timer.setCallback(animate);
|
||||
state->timer.callEach(kRecordingAnimationDuration);
|
||||
animate();
|
||||
|
||||
_recordingMark->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = QPainter(_recordingMark.data());
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::groupCallMemberMutedIcon);
|
||||
p.setOpacity(state->animation.value(
|
||||
state->opaque ? 1. : kRecordingOpacity));
|
||||
p.drawEllipse(skip, skip, size, size);
|
||||
}, _recordingMark->lifetime());
|
||||
}
|
||||
@@ -633,7 +721,7 @@ void GroupPanel::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
rpl::single(
|
||||
_call->joinAs()
|
||||
) | rpl::then(_call->rejoinEvents(
|
||||
) | rpl::map([](const Group::RejoinEvent &event) {
|
||||
) | rpl::map([](const RejoinEvent &event) {
|
||||
return event.nowJoinAs;
|
||||
})) | rpl::start_with_next([=](not_null<PeerData*> joinAs) {
|
||||
auto joinAsToggle = object_ptr<Ui::UserpicButton>(
|
||||
@@ -647,6 +735,7 @@ void GroupPanel::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
_joinAsToggle->setClickedCallback([=] {
|
||||
chooseJoinAs();
|
||||
});
|
||||
updateControlsGeometry();
|
||||
}, widget()->lifetime());
|
||||
} else {
|
||||
_menuToggle.destroy();
|
||||
@@ -655,9 +744,9 @@ void GroupPanel::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
updateControlsGeometry();
|
||||
}
|
||||
|
||||
void GroupPanel::chooseJoinAs() {
|
||||
const auto context = Group::ChooseJoinAsProcess::Context::Switch;
|
||||
const auto callback = [=](Group::JoinInfo info) {
|
||||
void Panel::chooseJoinAs() {
|
||||
const auto context = ChooseJoinAsProcess::Context::Switch;
|
||||
const auto callback = [=](JoinInfo info) {
|
||||
if (_call) {
|
||||
_call->rejoinAs(info);
|
||||
}
|
||||
@@ -677,12 +766,12 @@ void GroupPanel::chooseJoinAs() {
|
||||
_call->joinAs());
|
||||
}
|
||||
|
||||
void GroupPanel::showMainMenu() {
|
||||
void Panel::showMainMenu() {
|
||||
if (_menu || !_call) {
|
||||
return;
|
||||
}
|
||||
_menu.create(widget(), st::groupCallDropdownMenu);
|
||||
Group::FillMenu(
|
||||
FillMenu(
|
||||
_menu.data(),
|
||||
_peer,
|
||||
_call,
|
||||
@@ -724,7 +813,7 @@ void GroupPanel::showMainMenu() {
|
||||
}
|
||||
}
|
||||
|
||||
void GroupPanel::addMembers() {
|
||||
void Panel::addMembers() {
|
||||
const auto real = _peer->groupCall();
|
||||
if (!_call || !real || real->id() != _call->id()) {
|
||||
return;
|
||||
@@ -824,7 +913,7 @@ void GroupPanel::addMembers() {
|
||||
finish();
|
||||
};
|
||||
auto box = Box(
|
||||
Group::ConfirmBox,
|
||||
ConfirmBox,
|
||||
TextWithEntities{ text },
|
||||
tr::lng_participant_invite(),
|
||||
[=] { inviteWithAdd(users, nonMembers, finishWithConfirm); });
|
||||
@@ -865,7 +954,7 @@ void GroupPanel::addMembers() {
|
||||
_layerBg->showBox(Box<PeerListsBox>(std::move(controllers), initBox));
|
||||
}
|
||||
|
||||
void GroupPanel::kickMember(not_null<UserData*> user) {
|
||||
void Panel::kickMember(not_null<UserData*> user) {
|
||||
_layerBg->showBox(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
@@ -888,7 +977,7 @@ void GroupPanel::kickMember(not_null<UserData*> user) {
|
||||
}));
|
||||
}
|
||||
|
||||
void GroupPanel::kickMemberSure(not_null<UserData*> user) {
|
||||
void Panel::kickMemberSure(not_null<UserData*> user) {
|
||||
if (const auto chat = _peer->asChat()) {
|
||||
chat->session().api().kickParticipant(chat, user);
|
||||
} else if (const auto channel = _peer->asChannel()) {
|
||||
@@ -906,26 +995,33 @@ void GroupPanel::kickMemberSure(not_null<UserData*> user) {
|
||||
}
|
||||
}
|
||||
|
||||
void GroupPanel::initLayout() {
|
||||
void Panel::initLayout() {
|
||||
initGeometry();
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
_controls->raise();
|
||||
|
||||
Ui::Platform::TitleControlsLayoutChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
// _menuToggle geometry depends on _controls arrangement.
|
||||
crl::on_main(widget(), [=] { updateControlsGeometry(); });
|
||||
}, widget()->lifetime());
|
||||
|
||||
#endif // !Q_OS_MAC
|
||||
}
|
||||
|
||||
void GroupPanel::showControls() {
|
||||
void Panel::showControls() {
|
||||
Expects(_call != nullptr);
|
||||
|
||||
widget()->showChildren();
|
||||
}
|
||||
|
||||
void GroupPanel::closeBeforeDestroy() {
|
||||
void Panel::closeBeforeDestroy() {
|
||||
_window->close();
|
||||
initWithCall(nullptr);
|
||||
}
|
||||
|
||||
void GroupPanel::initGeometry() {
|
||||
void Panel::initGeometry() {
|
||||
const auto center = Core::App().getPointForCallPanelCenter();
|
||||
const auto rect = QRect(0, 0, st::groupCallWidth, st::groupCallHeight);
|
||||
_window->setGeometry(rect.translated(center - rect.center()));
|
||||
@@ -934,7 +1030,7 @@ void GroupPanel::initGeometry() {
|
||||
updateControlsGeometry();
|
||||
}
|
||||
|
||||
QRect GroupPanel::computeTitleRect() const {
|
||||
QRect Panel::computeTitleRect() const {
|
||||
const auto skip = st::groupCallTitleTop;
|
||||
const auto remove = skip + (_menuToggle
|
||||
? (_menuToggle->width() + st::groupCallMenuTogglePosition.x())
|
||||
@@ -943,7 +1039,7 @@ QRect GroupPanel::computeTitleRect() const {
|
||||
: 0);
|
||||
const auto width = widget()->width();
|
||||
#ifdef Q_OS_MAC
|
||||
return QRect(70, 0, width - skip - 70, 28);
|
||||
return QRect(70, 0, width - remove - 70, 28);
|
||||
#else // Q_OS_MAC
|
||||
const auto controls = _controls->geometry();
|
||||
const auto right = controls.x() + controls.width() + skip;
|
||||
@@ -953,7 +1049,7 @@ QRect GroupPanel::computeTitleRect() const {
|
||||
#endif // !Q_OS_MAC
|
||||
}
|
||||
|
||||
void GroupPanel::updateControlsGeometry() {
|
||||
void Panel::updateControlsGeometry() {
|
||||
if (widget()->size().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -1011,7 +1107,7 @@ void GroupPanel::updateControlsGeometry() {
|
||||
}
|
||||
}
|
||||
|
||||
void GroupPanel::refreshTitle() {
|
||||
void Panel::refreshTitle() {
|
||||
if (!_title) {
|
||||
auto text = rpl::combine(
|
||||
Info::Profile::NameValue(_peer),
|
||||
@@ -1036,7 +1132,9 @@ void GroupPanel::refreshTitle() {
|
||||
widget(),
|
||||
tr::lng_group_call_members(
|
||||
lt_count_decimal,
|
||||
_members->fullCountValue() | tr::to_count()),
|
||||
_members->fullCountValue() | rpl::map([](int value) {
|
||||
return (value > 0) ? float64(value) : 1.;
|
||||
})),
|
||||
st::groupCallSubtitleLabel);
|
||||
_subtitle->show();
|
||||
_subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
@@ -1052,7 +1150,7 @@ void GroupPanel::refreshTitle() {
|
||||
top);
|
||||
}
|
||||
|
||||
void GroupPanel::refreshTitleGeometry() {
|
||||
void Panel::refreshTitleGeometry() {
|
||||
if (!_title) {
|
||||
return;
|
||||
}
|
||||
@@ -1091,7 +1189,7 @@ void GroupPanel::refreshTitleGeometry() {
|
||||
}
|
||||
}
|
||||
|
||||
void GroupPanel::paint(QRect clip) {
|
||||
void Panel::paint(QRect clip) {
|
||||
Painter p(widget());
|
||||
|
||||
auto region = QRegion(clip);
|
||||
@@ -1100,7 +1198,7 @@ void GroupPanel::paint(QRect clip) {
|
||||
}
|
||||
}
|
||||
|
||||
bool GroupPanel::handleClose() {
|
||||
bool Panel::handleClose() {
|
||||
if (_call) {
|
||||
_window->hide();
|
||||
return true;
|
||||
@@ -1108,8 +1206,8 @@ bool GroupPanel::handleClose() {
|
||||
return false;
|
||||
}
|
||||
|
||||
not_null<Ui::RpWidget*> GroupPanel::widget() const {
|
||||
not_null<Ui::RpWidget*> Panel::widget() const {
|
||||
return _window->body();
|
||||
}
|
||||
|
||||
} // namespace Calls
|
||||
} // namespace Calls::Group
|
||||
|
||||
@@ -48,17 +48,14 @@ struct CallSignalBars;
|
||||
struct CallBodyLayout;
|
||||
} // namespace style
|
||||
|
||||
namespace Calls {
|
||||
namespace Calls::Group {
|
||||
|
||||
class Userpic;
|
||||
class SignalBars;
|
||||
class Members;
|
||||
|
||||
class GroupMembers;
|
||||
|
||||
class GroupPanel final {
|
||||
class Panel final {
|
||||
public:
|
||||
GroupPanel(not_null<GroupCall*> call);
|
||||
~GroupPanel();
|
||||
Panel(not_null<GroupCall*> call);
|
||||
~Panel();
|
||||
|
||||
[[nodiscard]] bool isActive() const;
|
||||
void minimize();
|
||||
@@ -79,6 +76,8 @@ private:
|
||||
void initWithCall(GroupCall *call);
|
||||
void initLayout();
|
||||
void initGeometry();
|
||||
void setupJoinAsChangedToasts();
|
||||
void setupTitleChangedToasts();
|
||||
|
||||
bool handleClose();
|
||||
|
||||
@@ -119,9 +118,9 @@ private:
|
||||
object_ptr<Ui::IconButton> _menuToggle = { nullptr };
|
||||
object_ptr<Ui::DropdownMenu> _menu = { nullptr };
|
||||
object_ptr<Ui::AbstractButton> _joinAsToggle = { nullptr };
|
||||
object_ptr<GroupMembers> _members;
|
||||
object_ptr<Members> _members;
|
||||
rpl::variable<QString> _titleText;
|
||||
Group::ChooseJoinAsProcess _joinAsProcess;
|
||||
ChooseJoinAsProcess _joinAsProcess;
|
||||
|
||||
object_ptr<Ui::CallButton> _settings;
|
||||
std::unique_ptr<Ui::CallMuteButton> _mute;
|
||||
@@ -131,4 +130,4 @@ private:
|
||||
|
||||
};
|
||||
|
||||
} // namespace Calls
|
||||
} // namespace Calls::Group
|
||||
|
||||
@@ -216,14 +216,6 @@ void SettingsBox(
|
||||
const auto weakBox = Ui::MakeWeak(box);
|
||||
|
||||
struct State {
|
||||
State(not_null<Main::Session*> session) : session(session) {
|
||||
}
|
||||
~State() {
|
||||
session->api().request(linkListenerRequestId).cancel();
|
||||
session->api().request(linkSpeakerRequestId).cancel();
|
||||
}
|
||||
|
||||
not_null<Main::Session*> session;
|
||||
rpl::event_stream<QString> outputNameStream;
|
||||
rpl::event_stream<QString> inputNameStream;
|
||||
std::unique_ptr<Webrtc::AudioInputTester> micTester;
|
||||
@@ -231,14 +223,10 @@ void SettingsBox(
|
||||
float micLevel = 0.;
|
||||
Ui::Animations::Simple micLevelAnimation;
|
||||
base::Timer levelUpdateTimer;
|
||||
std::optional<QString> linkSpeaker;
|
||||
QString linkListener;
|
||||
bool generatingLink = false;
|
||||
mtpRequestId linkListenerRequestId = 0;
|
||||
mtpRequestId linkSpeakerRequestId = 0;
|
||||
};
|
||||
const auto peer = call->peer();
|
||||
const auto state = box->lifetime().make_state<State>(&peer->session());
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
const auto real = peer->groupCall();
|
||||
const auto id = call->id();
|
||||
const auto goodReal = (real && real->id() == id);
|
||||
@@ -538,74 +526,25 @@ void SettingsBox(
|
||||
//AddDivider(layout);
|
||||
//AddSkip(layout);
|
||||
|
||||
if (!peer->canManageGroupCall()) {
|
||||
state->linkSpeaker = QString();
|
||||
}
|
||||
|
||||
auto shareLink = Fn<void()>();
|
||||
if (peer->isChannel()
|
||||
&& peer->asChannel()->hasUsername()
|
||||
&& goodReal) {
|
||||
const auto input = real->input();
|
||||
const auto shareReady = [=] {
|
||||
if (!state->linkSpeaker.has_value()
|
||||
|| state->linkListener.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
const auto showToast = crl::guard(box, [=](QString text) {
|
||||
Ui::Toast::Show(
|
||||
box->getDelegate()->outerContainer(),
|
||||
text);
|
||||
});
|
||||
box->getDelegate()->show(ShareInviteLinkBox(
|
||||
peer,
|
||||
*state->linkSpeaker,
|
||||
state->linkListener,
|
||||
showToast));
|
||||
return true;
|
||||
};
|
||||
shareLink = [=] {
|
||||
if (shareReady() || state->generatingLink) {
|
||||
return;
|
||||
}
|
||||
state->generatingLink = true;
|
||||
|
||||
state->linkListenerRequestId = peer->session().api().request(
|
||||
MTPphone_ExportGroupCallInvite(
|
||||
MTP_flags(0),
|
||||
input
|
||||
)
|
||||
).done(crl::guard(box, [=](
|
||||
const MTPphone_ExportedGroupCallInvite &result) {
|
||||
state->linkListenerRequestId = 0;
|
||||
result.match([&](
|
||||
const MTPDphone_exportedGroupCallInvite &data) {
|
||||
state->linkListener = qs(data.vlink());
|
||||
shareReady();
|
||||
});
|
||||
})).send();
|
||||
|
||||
if (!state->linkSpeaker.has_value()) {
|
||||
using Flag = MTPphone_ExportGroupCallInvite::Flag;
|
||||
state->linkSpeakerRequestId = peer->session().api().request(
|
||||
MTPphone_ExportGroupCallInvite(
|
||||
MTP_flags(Flag::f_can_self_unmute),
|
||||
input
|
||||
)).done(crl::guard(box, [=](
|
||||
const MTPphone_ExportedGroupCallInvite &result) {
|
||||
state->linkSpeakerRequestId = 0;
|
||||
result.match([&](
|
||||
const MTPDphone_exportedGroupCallInvite &data) {
|
||||
state->linkSpeaker = qs(data.vlink());
|
||||
shareReady();
|
||||
});
|
||||
})).fail([=] {
|
||||
state->linkSpeakerRequestId = 0;
|
||||
state->linkSpeaker = QString();
|
||||
shareReady();
|
||||
}).send();
|
||||
}
|
||||
};
|
||||
const auto showBox = crl::guard(box, [=](
|
||||
object_ptr<Ui::BoxContent> next) {
|
||||
box->getDelegate()->show(std::move(next));
|
||||
});
|
||||
const auto showToast = crl::guard(box, [=](QString text) {
|
||||
Ui::Toast::Show(
|
||||
box->getDelegate()->outerContainer(),
|
||||
text);
|
||||
});
|
||||
auto [shareLinkCallback, shareLinkLifetime] = ShareInviteLinkAction(
|
||||
peer,
|
||||
showBox,
|
||||
showToast);
|
||||
shareLink = std::move(shareLinkCallback);
|
||||
box->lifetime().add(std::move(shareLinkLifetime));
|
||||
} else {
|
||||
const auto lookupLink = [=] {
|
||||
if (const auto group = peer->asMegagroup()) {
|
||||
@@ -698,4 +637,85 @@ void SettingsBox(
|
||||
});
|
||||
}
|
||||
|
||||
std::pair<Fn<void()>, rpl::lifetime> ShareInviteLinkAction(
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void(object_ptr<Ui::BoxContent>)> showBox,
|
||||
Fn<void(QString)> showToast) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
struct State {
|
||||
State(not_null<Main::Session*> session) : session(session) {
|
||||
}
|
||||
~State() {
|
||||
session->api().request(linkListenerRequestId).cancel();
|
||||
session->api().request(linkSpeakerRequestId).cancel();
|
||||
}
|
||||
|
||||
not_null<Main::Session*> session;
|
||||
std::optional<QString> linkSpeaker;
|
||||
QString linkListener;
|
||||
mtpRequestId linkListenerRequestId = 0;
|
||||
mtpRequestId linkSpeakerRequestId = 0;
|
||||
bool generatingLink = false;
|
||||
};
|
||||
const auto state = lifetime.make_state<State>(&peer->session());
|
||||
if (!peer->canManageGroupCall()) {
|
||||
state->linkSpeaker = QString();
|
||||
}
|
||||
|
||||
const auto shareReady = [=] {
|
||||
if (!state->linkSpeaker.has_value()
|
||||
|| state->linkListener.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
showBox(ShareInviteLinkBox(
|
||||
peer,
|
||||
*state->linkSpeaker,
|
||||
state->linkListener,
|
||||
showToast));
|
||||
return true;
|
||||
};
|
||||
auto callback = [=] {
|
||||
const auto real = peer->groupCall();
|
||||
if (shareReady() || state->generatingLink || !real) {
|
||||
return;
|
||||
}
|
||||
state->generatingLink = true;
|
||||
|
||||
state->linkListenerRequestId = peer->session().api().request(
|
||||
MTPphone_ExportGroupCallInvite(
|
||||
MTP_flags(0),
|
||||
real->input()
|
||||
)
|
||||
).done([=](const MTPphone_ExportedGroupCallInvite &result) {
|
||||
state->linkListenerRequestId = 0;
|
||||
result.match([&](
|
||||
const MTPDphone_exportedGroupCallInvite &data) {
|
||||
state->linkListener = qs(data.vlink());
|
||||
shareReady();
|
||||
});
|
||||
}).send();
|
||||
|
||||
if (!state->linkSpeaker.has_value()) {
|
||||
using Flag = MTPphone_ExportGroupCallInvite::Flag;
|
||||
state->linkSpeakerRequestId = peer->session().api().request(
|
||||
MTPphone_ExportGroupCallInvite(
|
||||
MTP_flags(Flag::f_can_self_unmute),
|
||||
real->input()
|
||||
)).done([=](const MTPphone_ExportedGroupCallInvite &result) {
|
||||
state->linkSpeakerRequestId = 0;
|
||||
result.match([&](
|
||||
const MTPDphone_exportedGroupCallInvite &data) {
|
||||
state->linkSpeaker = qs(data.vlink());
|
||||
shareReady();
|
||||
});
|
||||
}).fail([=] {
|
||||
state->linkSpeakerRequestId = 0;
|
||||
state->linkSpeaker = QString();
|
||||
shareReady();
|
||||
}).send();
|
||||
}
|
||||
};
|
||||
return { std::move(callback), std::move(lifetime) };
|
||||
}
|
||||
|
||||
} // namespace Calls::Group
|
||||
|
||||
@@ -19,4 +19,9 @@ void SettingsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<GroupCall*> call);
|
||||
|
||||
[[nodiscard]] std::pair<Fn<void()>, rpl::lifetime> ShareInviteLinkAction(
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void(object_ptr<Ui::BoxContent>)> showBox,
|
||||
Fn<void(QString)> showToast);
|
||||
|
||||
} // namespace Calls::Group
|
||||
|
||||
@@ -65,8 +65,11 @@ void Instance::startOutgoingCall(not_null<UserData*> user, bool video) {
|
||||
|
||||
void Instance::startOrJoinGroupCall(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &joinHash) {
|
||||
const auto context = peer->groupCall()
|
||||
const QString &joinHash,
|
||||
bool confirmNeeded) {
|
||||
const auto context = confirmNeeded
|
||||
? Group::ChooseJoinAsProcess::Context::JoinWithConfirm
|
||||
: peer->groupCall()
|
||||
? Group::ChooseJoinAsProcess::Context::Join
|
||||
: Group::ChooseJoinAsProcess::Context::Create;
|
||||
_chooseJoinAs.start(peer, context, [=](object_ptr<Ui::BoxContent> box) {
|
||||
@@ -224,7 +227,7 @@ void Instance::createGroupCall(
|
||||
destroyGroupCall(raw);
|
||||
}, raw->lifetime());
|
||||
|
||||
_currentGroupCallPanel = std::make_unique<GroupPanel>(raw);
|
||||
_currentGroupCallPanel = std::make_unique<Group::Panel>(raw);
|
||||
_currentGroupCall = std::move(call);
|
||||
_currentGroupCallChanges.fire_copy(raw);
|
||||
}
|
||||
@@ -320,9 +323,9 @@ void Instance::handleUpdate(
|
||||
}, [&](const MTPDupdatePhoneCallSignalingData &data) {
|
||||
handleSignalingData(session, data);
|
||||
}, [&](const MTPDupdateGroupCall &data) {
|
||||
handleGroupCallUpdate(session, data.vcall());
|
||||
handleGroupCallUpdate(session, update);
|
||||
}, [&](const MTPDupdateGroupCallParticipants &data) {
|
||||
handleGroupCallUpdate(session, data);
|
||||
handleGroupCallUpdate(session, update);
|
||||
}, [](const auto &) {
|
||||
Unexpected("Update type in Calls::Instance::handleUpdate.");
|
||||
});
|
||||
@@ -407,33 +410,45 @@ void Instance::handleCallUpdate(
|
||||
|
||||
void Instance::handleGroupCallUpdate(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPGroupCall &call) {
|
||||
const auto callId = call.match([](const auto &data) {
|
||||
return data.vid().v;
|
||||
const MTPUpdate &update) {
|
||||
const auto callId = update.match([](const MTPDupdateGroupCall &data) {
|
||||
return data.vcall().match([](const auto &data) {
|
||||
return data.vid().v;
|
||||
});
|
||||
}, [](const MTPDupdateGroupCallParticipants &data) {
|
||||
return data.vcall().match([&](const MTPDinputGroupCall &data) {
|
||||
return data.vid().v;
|
||||
});
|
||||
}, [](const auto &) -> uint64 {
|
||||
Unexpected("Type in Instance::handleGroupCallUpdate.");
|
||||
});
|
||||
if (const auto existing = session->data().groupCall(callId)) {
|
||||
existing->applyUpdate(call);
|
||||
}
|
||||
if (_currentGroupCall
|
||||
&& (&_currentGroupCall->peer()->session() == session)) {
|
||||
_currentGroupCall->handleUpdate(call);
|
||||
existing->enqueueUpdate(update);
|
||||
} else {
|
||||
applyGroupCallUpdateChecked(session, update);
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::handleGroupCallUpdate(
|
||||
void Instance::applyGroupCallUpdateChecked(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDupdateGroupCallParticipants &update) {
|
||||
const auto callId = update.vcall().match([](const auto &data) {
|
||||
return data.vid().v;
|
||||
const MTPUpdate &update) {
|
||||
if (!_currentGroupCall
|
||||
|| (&_currentGroupCall->peer()->session() != session)) {
|
||||
return;
|
||||
}
|
||||
|
||||
update.match([&](const MTPDupdateGroupCall &data) {
|
||||
_currentGroupCall->handleUpdate(data.vcall());
|
||||
}, [&](const MTPDupdateGroupCallParticipants &data) {
|
||||
const auto callId = data.vcall().match([](const auto &data) {
|
||||
return data.vid().v;
|
||||
});
|
||||
if (_currentGroupCall->id() == callId) {
|
||||
_currentGroupCall->handleUpdate(data);
|
||||
}
|
||||
}, [](const auto &) {
|
||||
Unexpected("Type in Instance::applyGroupCallUpdateChecked.");
|
||||
});
|
||||
if (const auto existing = session->data().groupCall(callId)) {
|
||||
existing->applyUpdate(update);
|
||||
}
|
||||
if (_currentGroupCall
|
||||
&& (&_currentGroupCall->peer()->session() == session)
|
||||
&& (_currentGroupCall->id() == callId)) {
|
||||
_currentGroupCall->handleUpdate(update);
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::handleSignalingData(
|
||||
|
||||
@@ -26,12 +26,12 @@ class Session;
|
||||
|
||||
namespace Calls::Group {
|
||||
struct JoinInfo;
|
||||
class Panel;
|
||||
} // namespace Calls::Group
|
||||
|
||||
namespace Calls {
|
||||
|
||||
class Panel;
|
||||
class GroupPanel;
|
||||
|
||||
class Instance
|
||||
: private Call::Delegate
|
||||
@@ -45,10 +45,17 @@ public:
|
||||
void startOutgoingCall(not_null<UserData*> user, bool video);
|
||||
void startOrJoinGroupCall(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &joinHash = QString());
|
||||
const QString &joinHash = QString(),
|
||||
bool confirmNeeded = false);
|
||||
void handleUpdate(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPUpdate &update);
|
||||
|
||||
// Called by Data::GroupCall when it is appropriate by the 'version'.
|
||||
void applyGroupCallUpdateChecked(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPUpdate &update);
|
||||
|
||||
void showInfoPanel(not_null<Call*> call);
|
||||
void showInfoPanel(not_null<GroupCall*> call);
|
||||
[[nodiscard]] Call *currentCall() const;
|
||||
@@ -129,10 +136,7 @@ private:
|
||||
const MTPDupdatePhoneCallSignalingData &data);
|
||||
void handleGroupCallUpdate(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPGroupCall &call);
|
||||
void handleGroupCallUpdate(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDupdateGroupCallParticipants &update);
|
||||
const MTPUpdate &update);
|
||||
|
||||
DhConfig _dhConfig;
|
||||
|
||||
@@ -146,7 +150,7 @@ private:
|
||||
|
||||
std::unique_ptr<GroupCall> _currentGroupCall;
|
||||
rpl::event_stream<GroupCall*> _currentGroupCallChanges;
|
||||
std::unique_ptr<GroupPanel> _currentGroupCallPanel;
|
||||
std::unique_ptr<Group::Panel> _currentGroupCallPanel;
|
||||
|
||||
base::flat_map<QString, std::unique_ptr<Media::Audio::Track>> _tracks;
|
||||
|
||||
|
||||
@@ -406,7 +406,6 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
mrows.push_back({ i->second });
|
||||
}
|
||||
} else if (_channel && _channel->isMegagroup()) {
|
||||
QMultiMap<int32, UserData*> ordered;
|
||||
if (_channel->lastParticipantsRequestNeeded()) {
|
||||
_channel->session().api().requestLastParticipants(_channel);
|
||||
} else {
|
||||
@@ -437,7 +436,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
} else if (_type == Type::BotCommands) {
|
||||
bool listAllSuggestions = _filter.isEmpty();
|
||||
bool hasUsername = _filter.indexOf('@') > 0;
|
||||
QMap<UserData*, bool> bots;
|
||||
base::flat_map<UserData*, bool> bots;
|
||||
int32 cnt = 0;
|
||||
if (_chat) {
|
||||
if (_chat->noParticipantInfo()) {
|
||||
@@ -452,7 +451,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
if (user->botInfo->commands.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
bots.insert(user, true);
|
||||
bots.emplace(user, true);
|
||||
cnt += user->botInfo->commands.size();
|
||||
}
|
||||
}
|
||||
@@ -461,7 +460,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
_user->session().api().requestFullPeer(_user);
|
||||
}
|
||||
cnt = _user->botInfo->commands.size();
|
||||
bots.insert(_user, true);
|
||||
bots.emplace(_user, true);
|
||||
} else if (_channel && _channel->isMegagroup()) {
|
||||
if (_channel->mgInfo->bots.empty()) {
|
||||
if (!_channel->mgInfo->botStatus) {
|
||||
@@ -477,7 +476,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
if (user->botInfo->commands.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
bots.insert(user, true);
|
||||
bots.emplace(user, true);
|
||||
cnt += user->botInfo->commands.size();
|
||||
}
|
||||
}
|
||||
@@ -511,9 +510,9 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!bots.isEmpty()) {
|
||||
for (QMap<UserData*, bool>::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) {
|
||||
UserData *user = i.key();
|
||||
if (!bots.empty()) {
|
||||
for (auto i = bots.cbegin(), e = bots.cend(); i != e; ++i) {
|
||||
UserData *user = i->first;
|
||||
for (int32 j = 0, l = user->botInfo->commands.size(); j < l; ++j) {
|
||||
if (!listAllSuggestions) {
|
||||
QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? user->botInfo->commands.at(j).command + '@' + user->username : user->botInfo->commands.at(j).command;
|
||||
|
||||
@@ -348,7 +348,7 @@ QImage TabbedPanel::grabForAnimation() {
|
||||
_a_show = base::take(showAnimation);
|
||||
_showAnimation = base::take(showAnimationData);
|
||||
_a_opacity = base::take(opacityAnimation);
|
||||
_cache = base::take(_cache);
|
||||
_cache = base::take(cache);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1022,7 +1022,7 @@ void Application::unregisterLeaveSubscription(not_null<QWidget*> widget) {
|
||||
#ifdef Q_OS_MAC
|
||||
_leaveSubscriptions = std::move(
|
||||
_leaveSubscriptions
|
||||
) | ranges::action::remove_if([&](const LeaveSubscription &subscription) {
|
||||
) | ranges::actions::remove_if([&](const LeaveSubscription &subscription) {
|
||||
auto pointer = subscription.pointer.data();
|
||||
return !pointer || (pointer == widget);
|
||||
});
|
||||
|
||||
@@ -117,7 +117,22 @@ std::map<int, const char*> BetaLogs() {
|
||||
{
|
||||
2006002,
|
||||
"- Fix text disappearing because of cloud drafts sync.\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
2006003,
|
||||
"- Fix audio device selection in voice chats.\n"
|
||||
|
||||
"- Fix blinking self profile photo "
|
||||
"in case the profile photo privacy is used.\n"
|
||||
|
||||
"- Fix voice chat admin menu on macOS.\n"
|
||||
},
|
||||
{
|
||||
2006004,
|
||||
"- Fix freeze in voice chats.\n"
|
||||
|
||||
"- Make default interface scale 110% on macOS Retina screens.\n"
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -786,7 +786,6 @@ void LastCrashedWindow::updateControls() {
|
||||
h += _networkSettings.height() + padding;
|
||||
}
|
||||
|
||||
QRect scr(QApplication::primaryScreen()->availableGeometry());
|
||||
QSize s(2 * padding + QFontMetrics(_label.font()).horizontalAdvance(qsl("Last time Telegram Desktop was not closed properly.")) + padding + _networkSettings.width(), h);
|
||||
if (s == size()) {
|
||||
resizeEvent(0);
|
||||
|
||||
@@ -94,7 +94,7 @@ QString filedialogDefaultName(
|
||||
const auto nameBase = (dir.endsWith('/') ? dir : (dir + '/'))
|
||||
+ base;
|
||||
name = nameBase + extension;
|
||||
for (int i = 0; QFileInfo(name).exists(); ++i) {
|
||||
for (int i = 0; QFileInfo::exists(name); ++i) {
|
||||
name = nameBase + qsl(" (%1)").arg(i + 2) + extension;
|
||||
}
|
||||
}
|
||||
@@ -115,7 +115,7 @@ QString filedialogNextFilename(
|
||||
const auto dir = directory.absolutePath();
|
||||
const auto nameBase = (dir.endsWith('/') ? dir : (dir + '/')) + prefix;
|
||||
auto result = nameBase + extension;
|
||||
for (int i = 0; result.toLower() != cur.toLower() && QFileInfo(result).exists(); ++i) {
|
||||
for (int i = 0; result.toLower() != cur.toLower() && QFileInfo::exists(result); ++i) {
|
||||
result = nameBase + qsl(" (%1)").arg(i + 2) + extension;
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -209,7 +209,11 @@ void Sandbox::setupScreenScale() {
|
||||
LOG(("Environmental variables: QT_SCREEN_SCALE_FACTORS='%1'").arg(qEnvironmentVariable("QT_SCREEN_SCALE_FACTORS")));
|
||||
}
|
||||
style::SetDevicePixelRatio(int(ratio));
|
||||
cSetScreenScale(style::kScaleDefault);
|
||||
if (Platform::IsMac() && ratio == 2.) {
|
||||
cSetScreenScale(110); // 110% for Retina screens by default.
|
||||
} else {
|
||||
cSetScreenScale(style::kScaleDefault);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -250,7 +250,6 @@ QString ExtractFilename(const QString &url) {
|
||||
|
||||
bool UnpackUpdate(const QString &filepath) {
|
||||
QFile input(filepath);
|
||||
QByteArray packed;
|
||||
if (!input.open(QIODevice::ReadOnly)) {
|
||||
LOG(("Update Error: cant read updates file!"));
|
||||
return false;
|
||||
|
||||
@@ -517,7 +517,7 @@ QString translitLetterRusEng(QChar letter, QChar next, int32 &toSkip) {
|
||||
fastLetterRusEng.insert(QString::fromUtf8("ч").at(0), qsl("ch"));
|
||||
fastLetterRusEng.insert(QString::fromUtf8("ш").at(0), qsl("sh"));
|
||||
fastLetterRusEng.insert(QString::fromUtf8("щ").at(0), qsl("sch"));
|
||||
fastLetterRusEng.insert(QString::fromUtf8("ъ").at(0), qsl(""));
|
||||
fastLetterRusEng.insert(QString::fromUtf8("ъ").at(0), QString());
|
||||
fastLetterRusEng.insert(QString::fromUtf8("э").at(0), qsl("e"));
|
||||
fastLetterRusEng.insert(QString::fromUtf8("ю").at(0), qsl("yu"));
|
||||
fastLetterRusEng.insert(QString::fromUtf8("я").at(0), qsl("ya"));
|
||||
@@ -526,7 +526,7 @@ QString translitLetterRusEng(QChar letter, QChar next, int32 &toSkip) {
|
||||
fastLetterRusEng.insert(QString::fromUtf8("и").at(0), qsl("i"));
|
||||
fastLetterRusEng.insert(QString::fromUtf8("к").at(0), qsl("k"));
|
||||
fastLetterRusEng.insert(QString::fromUtf8("ы").at(0), qsl("y"));
|
||||
fastLetterRusEng.insert(QString::fromUtf8("ь").at(0), qsl(""));
|
||||
fastLetterRusEng.insert(QString::fromUtf8("ь").at(0), QString());
|
||||
}
|
||||
QHash<QChar, QString>::const_iterator j = fastLetterRusEng.constFind(letter);
|
||||
if (j != fastLetterRusEng.cend()) {
|
||||
|
||||
@@ -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 = 2006002;
|
||||
constexpr auto AppVersionStr = "2.6.2";
|
||||
constexpr auto AppVersion = 2006004;
|
||||
constexpr auto AppVersionStr = "2.6.4";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -213,7 +213,7 @@ QString FileNameUnsafe(
|
||||
}
|
||||
QString nameBase = path + nameStart;
|
||||
name = nameBase + extension;
|
||||
for (int i = 0; QFileInfo(name).exists(); ++i) {
|
||||
for (int i = 0; QFileInfo::exists(name); ++i) {
|
||||
name = nameBase + QString(" (%1)").arg(i + 2) + extension;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,9 +22,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kRequestPerPage = 30;
|
||||
constexpr auto kRequestPerPage = 50;
|
||||
constexpr auto kSpeakingAfterActive = crl::time(6000);
|
||||
constexpr auto kActiveAfterJoined = crl::time(1000);
|
||||
constexpr auto kWaitForUpdatesTimeout = 3 * crl::time(1000);
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -35,6 +36,7 @@ GroupCall::GroupCall(
|
||||
: _id(id)
|
||||
, _accessHash(accessHash)
|
||||
, _peer(peer)
|
||||
, _reloadByQueuedUpdatesTimer([=] { reload(); })
|
||||
, _speakingByActiveFinishTimer([=] { checkFinishSpeakingByActive(); }) {
|
||||
}
|
||||
|
||||
@@ -71,10 +73,7 @@ auto GroupCall::participants() const
|
||||
void GroupCall::requestParticipants() {
|
||||
if (_participantsRequestId || _reloadRequestId) {
|
||||
return;
|
||||
} else if (_participants.size() >= _fullCount.current() && _allReceived) {
|
||||
return;
|
||||
} else if (_allReceived) {
|
||||
reload();
|
||||
} else if (_allParticipantsLoaded) {
|
||||
return;
|
||||
}
|
||||
_participantsRequestId = api().request(MTPphone_GetGroupParticipants(
|
||||
@@ -91,26 +90,29 @@ void GroupCall::requestParticipants() {
|
||||
applyParticipantsSlice(
|
||||
data.vparticipants().v,
|
||||
ApplySliceSource::SliceLoaded);
|
||||
_fullCount = data.vcount().v;
|
||||
if (!_allReceived
|
||||
&& (data.vparticipants().v.size() < kRequestPerPage)) {
|
||||
_allReceived = true;
|
||||
}
|
||||
if (_allReceived) {
|
||||
_fullCount = _participants.size();
|
||||
setServerParticipantsCount(data.vcount().v);
|
||||
if (data.vparticipants().v.isEmpty()) {
|
||||
_allParticipantsLoaded = true;
|
||||
}
|
||||
computeParticipantsCount();
|
||||
_participantsSliceAdded.fire({});
|
||||
_participantsRequestId = 0;
|
||||
processQueuedUpdates();
|
||||
});
|
||||
_participantsSliceAdded.fire({});
|
||||
_participantsRequestId = 0;
|
||||
changePeerEmptyCallFlag();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_fullCount = _participants.size();
|
||||
_allReceived = true;
|
||||
setServerParticipantsCount(_participants.size());
|
||||
_allParticipantsLoaded = true;
|
||||
computeParticipantsCount();
|
||||
_participantsRequestId = 0;
|
||||
changePeerEmptyCallFlag();
|
||||
processQueuedUpdates();
|
||||
}).send();
|
||||
}
|
||||
|
||||
void GroupCall::setServerParticipantsCount(int count) {
|
||||
_serverParticipantsCount = count;
|
||||
changePeerEmptyCallFlag();
|
||||
}
|
||||
|
||||
void GroupCall::changePeerEmptyCallFlag() {
|
||||
const auto chat = _peer->asChat();
|
||||
const auto channel = _peer->asChannel();
|
||||
@@ -118,7 +120,7 @@ void GroupCall::changePeerEmptyCallFlag() {
|
||||
constexpr auto channelFlag = MTPDchannel::Flag::f_call_not_empty;
|
||||
if (_peer->groupCall() != this) {
|
||||
return;
|
||||
} else if (fullCount() > 0) {
|
||||
} else if (_serverParticipantsCount > 0) {
|
||||
if (chat && !(chat->flags() & chatFlag)) {
|
||||
chat->addFlags(chatFlag);
|
||||
chat->session().changes().peerUpdated(
|
||||
@@ -152,7 +154,7 @@ rpl::producer<int> GroupCall::fullCountValue() const {
|
||||
}
|
||||
|
||||
bool GroupCall::participantsLoaded() const {
|
||||
return _allReceived;
|
||||
return _allParticipantsLoaded;
|
||||
}
|
||||
|
||||
PeerData *GroupCall::participantPeerBySsrc(uint32 ssrc) const {
|
||||
@@ -169,49 +171,181 @@ auto GroupCall::participantUpdated() const
|
||||
return _participantUpdates.events();
|
||||
}
|
||||
|
||||
void GroupCall::applyUpdate(const MTPGroupCall &update) {
|
||||
applyCall(update, false);
|
||||
void GroupCall::enqueueUpdate(const MTPUpdate &update) {
|
||||
update.match([&](const MTPDupdateGroupCall &updateData) {
|
||||
updateData.vcall().match([&](const MTPDgroupCall &data) {
|
||||
const auto version = data.vversion().v;
|
||||
if (!_version || _version == version) {
|
||||
applyUpdate(update);
|
||||
} else if (_version < version) {
|
||||
_queuedUpdates.emplace(std::pair{ version, false }, update);
|
||||
}
|
||||
}, [&](const MTPDgroupCallDiscarded &data) {
|
||||
applyUpdate(update);
|
||||
});
|
||||
}, [&](const MTPDupdateGroupCallParticipants &updateData) {
|
||||
const auto version = updateData.vversion().v;
|
||||
const auto proj = [](const MTPGroupCallParticipant &data) {
|
||||
return data.match([&](const MTPDgroupCallParticipant &data) {
|
||||
return data.is_versioned();
|
||||
});
|
||||
};
|
||||
const auto increment = ranges::contains(
|
||||
updateData.vparticipants().v,
|
||||
true,
|
||||
proj);
|
||||
const auto required = increment ? (version - 1) : version;
|
||||
if (_version == required) {
|
||||
applyUpdate(update);
|
||||
} else if (_version < required) {
|
||||
_queuedUpdates.emplace(std::pair{ version, increment }, update);
|
||||
}
|
||||
}, [](const auto &) {
|
||||
Unexpected("Type in GroupCall::enqueueUpdate.");
|
||||
});
|
||||
processQueuedUpdates();
|
||||
}
|
||||
|
||||
void GroupCall::applyCall(const MTPGroupCall &call, bool force) {
|
||||
call.match([&](const MTPDgroupCall &data) {
|
||||
if (!_version) {
|
||||
_version = data.vversion().v;
|
||||
}
|
||||
const auto title = qs(data.vtitle().value_or_empty());
|
||||
const auto recordDate = data.vrecord_start_date().value_or_empty();
|
||||
const auto changed = (_joinMuted != data.is_join_muted())
|
||||
|| (_fullCount.current() != data.vparticipants_count().v)
|
||||
|| (_canChangeJoinMuted != data.is_can_change_join_muted())
|
||||
|| (_title.current() != title)
|
||||
|| (_recordStartDate.current() != recordDate);
|
||||
if (!force && !changed) {
|
||||
return;
|
||||
} else if (!force && _version > data.vversion().v) {
|
||||
reload();
|
||||
return;
|
||||
}
|
||||
_joinMuted = data.is_join_muted();
|
||||
_canChangeJoinMuted = data.is_can_change_join_muted();
|
||||
_fullCount = data.vparticipants_count().v;
|
||||
_title = title;
|
||||
_recordStartDate = recordDate;
|
||||
changePeerEmptyCallFlag();
|
||||
}, [&](const MTPDgroupCallDiscarded &data) {
|
||||
const auto id = _id;
|
||||
const auto peer = _peer;
|
||||
crl::on_main(&peer->session(), [=] {
|
||||
if (peer->groupCall() && peer->groupCall()->id() == id) {
|
||||
if (const auto chat = peer->asChat()) {
|
||||
chat->clearGroupCall();
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
channel->clearGroupCall();
|
||||
}
|
||||
void GroupCall::discard() {
|
||||
const auto id = _id;
|
||||
const auto peer = _peer;
|
||||
crl::on_main(&peer->session(), [=] {
|
||||
if (peer->groupCall() && peer->groupCall()->id() == id) {
|
||||
if (const auto chat = peer->asChat()) {
|
||||
chat->clearGroupCall();
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
channel->clearGroupCall();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void GroupCall::processFullCall(const MTPphone_GroupCall &call) {
|
||||
call.match([&](const MTPDphone_groupCall &data) {
|
||||
_peer->owner().processUsers(data.vusers());
|
||||
_peer->owner().processChats(data.vchats());
|
||||
const auto &participants = data.vparticipants().v;
|
||||
const auto nextOffset = qs(data.vparticipants_next_offset());
|
||||
data.vcall().match([&](const MTPDgroupCall &data) {
|
||||
if (data.vversion().v == _version
|
||||
&& data.vparticipants_count().v == _serverParticipantsCount
|
||||
&& (_serverParticipantsCount >= _participants.size())
|
||||
&& (!_allParticipantsLoaded
|
||||
|| _serverParticipantsCount == _participants.size())) {
|
||||
return;
|
||||
}
|
||||
_participants.clear();
|
||||
_speakingByActiveFinishes.clear();
|
||||
_participantPeerBySsrc.clear();
|
||||
_allParticipantsLoaded = false;
|
||||
|
||||
applyParticipantsSlice(
|
||||
participants,
|
||||
ApplySliceSource::SliceLoaded);
|
||||
_nextOffset = nextOffset;
|
||||
|
||||
applyCallFields(data);
|
||||
|
||||
_participantsSliceAdded.fire({});
|
||||
}, [&](const MTPDgroupCallDiscarded &data) {
|
||||
discard();
|
||||
});
|
||||
processQueuedUpdates();
|
||||
});
|
||||
}
|
||||
|
||||
void GroupCall::applyCallFields(const MTPDgroupCall &data) {
|
||||
_version = data.vversion().v;
|
||||
if (!_version) {
|
||||
LOG(("API Error: Got zero version in groupCall."));
|
||||
_version = 1;
|
||||
}
|
||||
_joinMuted = data.is_join_muted();
|
||||
_canChangeJoinMuted = data.is_can_change_join_muted();
|
||||
_joinedToTop = !data.is_join_date_asc();
|
||||
setServerParticipantsCount(data.vparticipants_count().v);
|
||||
changePeerEmptyCallFlag();
|
||||
_title = qs(data.vtitle().value_or_empty());
|
||||
_recordStartDate = data.vrecord_start_date().value_or_empty();
|
||||
_allParticipantsLoaded
|
||||
= (_serverParticipantsCount == _participants.size());
|
||||
computeParticipantsCount();
|
||||
processQueuedUpdates();
|
||||
}
|
||||
|
||||
void GroupCall::applyLocalUpdate(
|
||||
const MTPDupdateGroupCallParticipants &update) {
|
||||
applyParticipantsSlice(
|
||||
update.vparticipants().v,
|
||||
ApplySliceSource::UpdateReceived);
|
||||
}
|
||||
|
||||
void GroupCall::applyUpdate(const MTPUpdate &update) {
|
||||
update.match([&](const MTPDupdateGroupCall &data) {
|
||||
data.vcall().match([&](const MTPDgroupCall &data) {
|
||||
applyCallFields(data);
|
||||
}, [&](const MTPDgroupCallDiscarded &data) {
|
||||
discard();
|
||||
});
|
||||
}, [&](const MTPDupdateGroupCallParticipants &data) {
|
||||
_version = data.vversion().v;
|
||||
if (!_version) {
|
||||
LOG(("API Error: "
|
||||
"Got zero version in updateGroupCallParticipants."));
|
||||
_version = 1;
|
||||
}
|
||||
applyParticipantsSlice(
|
||||
data.vparticipants().v,
|
||||
ApplySliceSource::UpdateReceived);
|
||||
}, [](const auto &) {
|
||||
Unexpected("Type in GroupCall::processQueuedUpdates.");
|
||||
});
|
||||
Core::App().calls().applyGroupCallUpdateChecked(
|
||||
&_peer->session(),
|
||||
update);
|
||||
}
|
||||
|
||||
void GroupCall::processQueuedUpdates() {
|
||||
if (!_version) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto size = _queuedUpdates.size();
|
||||
while (!_queuedUpdates.empty()) {
|
||||
const auto &entry = _queuedUpdates.front();
|
||||
const auto version = entry.first.first;
|
||||
const auto versionIncremented = entry.first.second;
|
||||
if ((version < _version)
|
||||
|| (version == _version && versionIncremented)) {
|
||||
_queuedUpdates.erase(_queuedUpdates.begin());
|
||||
} else if (version == _version
|
||||
|| (version == _version + 1 && versionIncremented)) {
|
||||
const auto update = entry.second;
|
||||
_queuedUpdates.erase(_queuedUpdates.begin());
|
||||
applyUpdate(update);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (_queuedUpdates.empty()) {
|
||||
const auto server = _serverParticipantsCount;
|
||||
const auto local = int(_participants.size());
|
||||
if (server < local
|
||||
|| (_allParticipantsLoaded && server > local)) {
|
||||
reload();
|
||||
}
|
||||
} else if (_queuedUpdates.size() != size
|
||||
|| !_reloadByQueuedUpdatesTimer.isActive()) {
|
||||
_reloadByQueuedUpdatesTimer.callOnce(kWaitForUpdatesTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::computeParticipantsCount() {
|
||||
_fullCount = _allParticipantsLoaded
|
||||
? int(_participants.size())
|
||||
: std::max(int(_participants.size()), _serverParticipantsCount);
|
||||
}
|
||||
|
||||
void GroupCall::reload() {
|
||||
if (_reloadRequestId) {
|
||||
return;
|
||||
@@ -219,22 +353,14 @@ void GroupCall::reload() {
|
||||
api().request(_participantsRequestId).cancel();
|
||||
_participantsRequestId = 0;
|
||||
}
|
||||
|
||||
_queuedUpdates.clear();
|
||||
_reloadByQueuedUpdatesTimer.cancel();
|
||||
|
||||
_reloadRequestId = api().request(
|
||||
MTPphone_GetGroupCall(input())
|
||||
).done([=](const MTPphone_GroupCall &result) {
|
||||
result.match([&](const MTPDphone_groupCall &data) {
|
||||
_peer->owner().processUsers(data.vusers());
|
||||
_peer->owner().processChats(data.vchats());
|
||||
_participants.clear();
|
||||
_speakingByActiveFinishes.clear();
|
||||
_participantPeerBySsrc.clear();
|
||||
applyParticipantsSlice(
|
||||
data.vparticipants().v,
|
||||
ApplySliceSource::SliceLoaded);
|
||||
applyCall(data.vcall(), true);
|
||||
_allReceived = (_fullCount.current() == _participants.size());
|
||||
_participantsSliceAdded.fire({});
|
||||
});
|
||||
processFullCall(result);
|
||||
_reloadRequestId = 0;
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_reloadRequestId = 0;
|
||||
@@ -248,7 +374,6 @@ void GroupCall::applyParticipantsSlice(
|
||||
const auto now = base::unixtime::now();
|
||||
const auto speakingAfterActive = TimeId(kSpeakingAfterActive / 1000);
|
||||
|
||||
auto changedCount = _fullCount.current();
|
||||
for (const auto &participant : list) {
|
||||
participant.match([&](const MTPDgroupCallParticipant &data) {
|
||||
const auto participantPeerId = peerFromMTP(data.vpeer());
|
||||
@@ -270,8 +395,8 @@ void GroupCall::applyParticipantsSlice(
|
||||
_participantUpdates.fire(std::move(update));
|
||||
}
|
||||
}
|
||||
if (changedCount > _participants.size()) {
|
||||
--changedCount;
|
||||
if (_serverParticipantsCount > 0) {
|
||||
--_serverParticipantsCount;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -334,7 +459,7 @@ void GroupCall::applyParticipantsSlice(
|
||||
*i = value;
|
||||
}
|
||||
if (data.is_just_joined()) {
|
||||
++changedCount;
|
||||
++_serverParticipantsCount;
|
||||
}
|
||||
if (sliceSource != ApplySliceSource::SliceLoaded) {
|
||||
_participantUpdates.fire({
|
||||
@@ -345,8 +470,8 @@ void GroupCall::applyParticipantsSlice(
|
||||
});
|
||||
}
|
||||
if (sliceSource == ApplySliceSource::UpdateReceived) {
|
||||
_fullCount = changedCount;
|
||||
changePeerEmptyCallFlag();
|
||||
computeParticipantsCount();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,6 +663,7 @@ void GroupCall::requestUnknownParticipants() {
|
||||
).done([=](const MTPphone_GroupParticipants &result) {
|
||||
result.match([&](const MTPDphone_groupParticipants &data) {
|
||||
_peer->owner().processUsers(data.vusers());
|
||||
_peer->owner().processChats(data.vchats());
|
||||
applyParticipantsSlice(
|
||||
data.vparticipants().v,
|
||||
ApplySliceSource::UnknownLoaded);
|
||||
@@ -601,41 +727,6 @@ bool GroupCall::inCall() const {
|
||||
&& (current->state() == Calls::GroupCall::State::Joined);
|
||||
}
|
||||
|
||||
void GroupCall::applyUpdate(const MTPDupdateGroupCallParticipants &update) {
|
||||
const auto version = update.vversion().v;
|
||||
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 = version;
|
||||
applyUpdateChecked(update);
|
||||
}
|
||||
|
||||
void GroupCall::applyUpdateChecked(
|
||||
const MTPDupdateGroupCallParticipants &update) {
|
||||
applyParticipantsSlice(
|
||||
update.vparticipants().v,
|
||||
ApplySliceSource::UpdateReceived);
|
||||
}
|
||||
|
||||
void GroupCall::setJoinMutedLocally(bool muted) {
|
||||
_joinMuted = muted;
|
||||
}
|
||||
@@ -648,6 +739,10 @@ bool GroupCall::canChangeJoinMuted() const {
|
||||
return _canChangeJoinMuted;
|
||||
}
|
||||
|
||||
bool GroupCall::joinedToTop() const {
|
||||
return _joinedToTop;
|
||||
}
|
||||
|
||||
ApiWrap &GroupCall::api() const {
|
||||
return _peer->session().api();
|
||||
}
|
||||
|
||||
@@ -82,10 +82,10 @@ public:
|
||||
[[nodiscard]] rpl::producer<> participantsSliceAdded();
|
||||
[[nodiscard]] rpl::producer<ParticipantUpdate> participantUpdated() const;
|
||||
|
||||
void applyUpdate(const MTPGroupCall &update);
|
||||
void applyUpdate(const MTPDupdateGroupCallParticipants &update);
|
||||
void applyUpdateChecked(
|
||||
void enqueueUpdate(const MTPUpdate &update);
|
||||
void applyLocalUpdate(
|
||||
const MTPDupdateGroupCallParticipants &update);
|
||||
|
||||
void applyLastSpoke(uint32 ssrc, LastSpokeTimes when, crl::time now);
|
||||
void applyActiveUpdate(
|
||||
PeerId participantPeerId,
|
||||
@@ -99,10 +99,12 @@ public:
|
||||
|
||||
void setInCall();
|
||||
void reload();
|
||||
void processFullCall(const MTPphone_GroupCall &call);
|
||||
|
||||
void setJoinMutedLocally(bool muted);
|
||||
[[nodiscard]] bool joinMuted() const;
|
||||
[[nodiscard]] bool canChangeJoinMuted() const;
|
||||
[[nodiscard]] bool joinedToTop() const;
|
||||
|
||||
private:
|
||||
enum class ApplySliceSource {
|
||||
@@ -112,14 +114,19 @@ private:
|
||||
};
|
||||
[[nodiscard]] ApiWrap &api() const;
|
||||
|
||||
void discard();
|
||||
[[nodiscard]] bool inCall() const;
|
||||
void applyCall(const MTPGroupCall &call, bool force);
|
||||
void applyParticipantsSlice(
|
||||
const QVector<MTPGroupCallParticipant> &list,
|
||||
ApplySliceSource sliceSource);
|
||||
void requestUnknownParticipants();
|
||||
void changePeerEmptyCallFlag();
|
||||
void checkFinishSpeakingByActive();
|
||||
void applyCallFields(const MTPDgroupCall &data);
|
||||
void applyUpdate(const MTPUpdate &update);
|
||||
void setServerParticipantsCount(int count);
|
||||
void computeParticipantsCount();
|
||||
void processQueuedUpdates();
|
||||
|
||||
const uint64 _id = 0;
|
||||
const uint64 _accessHash = 0;
|
||||
@@ -130,11 +137,15 @@ private:
|
||||
mtpRequestId _reloadRequestId = 0;
|
||||
rpl::variable<QString> _title;
|
||||
|
||||
base::flat_map<std::pair<int,bool>, MTPUpdate> _queuedUpdates;
|
||||
base::Timer _reloadByQueuedUpdatesTimer;
|
||||
|
||||
std::vector<Participant> _participants;
|
||||
base::flat_map<uint32, not_null<PeerData*>> _participantPeerBySsrc;
|
||||
base::flat_map<not_null<PeerData*>, crl::time> _speakingByActiveFinishes;
|
||||
base::Timer _speakingByActiveFinishTimer;
|
||||
QString _nextOffset;
|
||||
int _serverParticipantsCount = 0;
|
||||
rpl::variable<int> _fullCount = 0;
|
||||
rpl::variable<TimeId> _recordStartDate = 0;
|
||||
|
||||
@@ -147,7 +158,8 @@ private:
|
||||
|
||||
bool _joinMuted = false;
|
||||
bool _canChangeJoinMuted = true;
|
||||
bool _allReceived = false;
|
||||
bool _allParticipantsLoaded = false;
|
||||
bool _joinedToTop = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -488,10 +488,12 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
|
||||
: result->nameOrPhone;
|
||||
|
||||
result->setName(fname, lname, pname, uname);
|
||||
if (const auto photo = data.vphoto()) {
|
||||
result->setPhoto(*photo);
|
||||
} else {
|
||||
result->setPhoto(MTP_userProfilePhotoEmpty());
|
||||
if (!minimal || data.is_apply_min_photo()) {
|
||||
if (const auto photo = data.vphoto()) {
|
||||
result->setPhoto(*photo);
|
||||
} else {
|
||||
result->setPhoto(MTP_userProfilePhotoEmpty());
|
||||
}
|
||||
}
|
||||
if (const auto accessHash = data.vaccess_hash()) {
|
||||
result->setAccessHash(accessHash->v);
|
||||
|
||||
@@ -1093,7 +1093,7 @@ std::vector<not_null<DocumentData*>> Stickers::getListByEmoji(
|
||||
}
|
||||
}
|
||||
|
||||
ranges::action::sort(
|
||||
ranges::actions::sort(
|
||||
result,
|
||||
std::greater<>(),
|
||||
&StickerWithDate::date);
|
||||
|
||||
@@ -782,7 +782,7 @@ void InnerWidget::paintPeerSearchResult(
|
||||
QRect tr(nameleft, st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip, namewidth, st::dialogsTextFont->height);
|
||||
p.setFont(st::dialogsTextFont);
|
||||
QString username = peer->userName();
|
||||
if (!active && username.toLower().startsWith(_peerSearchQuery)) {
|
||||
if (!active && username.startsWith(_peerSearchQuery, Qt::CaseInsensitive)) {
|
||||
auto first = '@' + username.mid(0, _peerSearchQuery.size());
|
||||
auto second = username.mid(_peerSearchQuery.size());
|
||||
auto w = st::dialogsTextFont->width(first);
|
||||
|
||||
@@ -2628,14 +2628,14 @@ MessageIdsList HistoryInner::getSelectedItems() const {
|
||||
auto result = ranges::make_subrange(
|
||||
_selected.begin(),
|
||||
_selected.end()
|
||||
) | view::filter([](const auto &selected) {
|
||||
) | views::filter([](const auto &selected) {
|
||||
const auto item = selected.first;
|
||||
return item && item->toHistoryMessage() && (item->id > 0);
|
||||
}) | view::transform([](const auto &selected) {
|
||||
}) | views::transform([](const auto &selected) {
|
||||
return selected.first->fullId();
|
||||
}) | to_vector;
|
||||
|
||||
result |= action::sort(ordered_less{}, [](const FullMsgId &msgId) {
|
||||
result |= actions::sort(ordered_less{}, [](const FullMsgId &msgId) {
|
||||
return msgId.channel ? msgId.msg : (msgId.msg - ServerMaxMsgId);
|
||||
});
|
||||
return result;
|
||||
|
||||
@@ -1183,8 +1183,6 @@ void ComposeControls::initAutocomplete() {
|
||||
}
|
||||
};
|
||||
const auto insertMention = [=](not_null<UserData*> user) {
|
||||
auto replacement = QString();
|
||||
auto entityTag = QString();
|
||||
if (user->username.isEmpty()) {
|
||||
_field->insertTag(
|
||||
user->firstName.isEmpty() ? user->name : user->firstName,
|
||||
|
||||
@@ -2004,7 +2004,7 @@ void ListWidget::performDrag() {
|
||||
}
|
||||
|
||||
TextWithEntities sel;
|
||||
QList<QUrl> urls;
|
||||
//QList<QUrl> urls;
|
||||
if (uponSelected) {
|
||||
// sel = getSelectedText();
|
||||
} else if (pressedHandler) {
|
||||
|
||||
@@ -317,14 +317,14 @@ void ListController::collapse() {
|
||||
return;
|
||||
}
|
||||
const auto remove = count - (kFirstPage - kLeavePreloaded);
|
||||
ranges::action::reverse(_preloaded);
|
||||
ranges::actions::reverse(_preloaded);
|
||||
_preloaded.reserve(_preloaded.size() + remove);
|
||||
for (auto i = 0; i != remove; ++i) {
|
||||
const auto row = delegate()->peerListRowAt(count - i - 1);
|
||||
_preloaded.push_back(row->peer()->asUser());
|
||||
delegate()->peerListRemoveRow(row);
|
||||
}
|
||||
ranges::action::reverse(_preloaded);
|
||||
ranges::actions::reverse(_preloaded);
|
||||
|
||||
delegate()->peerListRefreshRows();
|
||||
const auto now = count - remove;
|
||||
|
||||
@@ -213,7 +213,7 @@ void Widget::startShowAnimation() {
|
||||
showChildren();
|
||||
auto image = grabForPanelAnimation();
|
||||
_a_opacity = base::take(opacityAnimation);
|
||||
_cache = base::take(_cache);
|
||||
_cache = base::take(cache);
|
||||
|
||||
_showAnimation = std::make_unique<Ui::PanelAnimation>(st::emojiPanAnimation, Ui::PanelAnimation::Origin::BottomLeft);
|
||||
auto inner = rect().marginsRemoved(st::emojiPanMargins);
|
||||
|
||||
@@ -153,7 +153,7 @@ bool FileParser::readKeyValue(const char *&from, const char *end) {
|
||||
}
|
||||
|
||||
QByteArray FileParser::ReadFile(const QString &absolutePath, const QString &relativePath) {
|
||||
QFile file(QFileInfo(relativePath).exists() ? relativePath : absolutePath);
|
||||
QFile file(QFileInfo::exists(relativePath) ? relativePath : absolutePath);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
Ui::Integration::Instance().writeLogEntry(u"Lang Error: Could not open file at '%1' ('%2')"_q.arg(relativePath, absolutePath));
|
||||
return QByteArray();
|
||||
|
||||
@@ -151,7 +151,7 @@ private:
|
||||
for (QStringList::const_iterator i = oldlogs.cbegin(), e = oldlogs.cend(); i != e; ++i) {
|
||||
QString oldlog = cWorkingDir() + *i, oldlogend = i->mid(qstr("log_start").size());
|
||||
if (oldlogend.size() == 1 + qstr(".txt").size() && oldlogend.at(0).isDigit() && oldlogend.midRef(1) == qstr(".txt")) {
|
||||
bool removed = QFile(*i).remove();
|
||||
bool removed = QFile(oldlog).remove();
|
||||
LOG(("Old start log '%1' found, deleted: %2").arg(*i, Logs::b(removed)));
|
||||
}
|
||||
}
|
||||
@@ -265,7 +265,7 @@ bool DebugModeEnabled = false;
|
||||
|
||||
void MoveOldDataFiles(const QString &wasDir) {
|
||||
QFile data(wasDir + "data"), dataConfig(wasDir + "data_config"), tdataConfig(wasDir + "tdata/config");
|
||||
if (data.exists() && dataConfig.exists() && !QFileInfo(cWorkingDir() + "data").exists() && !QFileInfo(cWorkingDir() + "data_config").exists()) { // move to home dir
|
||||
if (data.exists() && dataConfig.exists() && !QFileInfo::exists(cWorkingDir() + "data") && !QFileInfo::exists(cWorkingDir() + "data_config")) { // move to home dir
|
||||
LOG(("Copying data to home dir '%1' from '%2'").arg(cWorkingDir(), wasDir));
|
||||
if (data.copy(cWorkingDir() + "data")) {
|
||||
LOG(("Copied 'data' to home dir"));
|
||||
|
||||
@@ -1795,8 +1795,6 @@ void MainWidget::showNewSection(
|
||||
Ui::hideSettingsAndLayer();
|
||||
}
|
||||
|
||||
QPixmap animCache;
|
||||
|
||||
_controller->dialogsListFocused().set(false, true);
|
||||
_a_dialogsWidth.stop();
|
||||
|
||||
|
||||
@@ -194,7 +194,7 @@ void Float::paintEvent(QPaintEvent *e) {
|
||||
const auto progress = playback ? playback->value() : 1.;
|
||||
if (progress > 0.) {
|
||||
auto pen = st::historyVideoMessageProgressFg->p;
|
||||
auto was = p.pen();
|
||||
//auto was = p.pen();
|
||||
pen.setWidth(st::radialLine);
|
||||
pen.setCapStyle(Qt::RoundCap);
|
||||
p.setPen(pen);
|
||||
|
||||
@@ -476,7 +476,6 @@ void Widget::handleSongUpdate(const TrackState &state) {
|
||||
}
|
||||
|
||||
void Widget::updateTimeText(const TrackState &state) {
|
||||
QString time;
|
||||
qint64 position = 0, length = 0, display = 0;
|
||||
const auto frequency = state.frequency;
|
||||
const auto document = state.id.audio();
|
||||
|
||||
@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/media/history_view_media.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "main/main_session.h"
|
||||
#include "core/crash_reports.h"
|
||||
#include "app.h"
|
||||
#include "styles/style_media_view.h"
|
||||
|
||||
@@ -37,6 +38,42 @@ int Round(float64 value) {
|
||||
using Context = GroupThumbs::Context;
|
||||
using Key = GroupThumbs::Key;
|
||||
|
||||
[[nodiscard]] QString DebugSerializeMsgId(FullMsgId itemId) {
|
||||
return QString("msg%1_%2").arg(itemId.channel).arg(itemId.msg);
|
||||
}
|
||||
|
||||
[[nodiscard]] QString DebugSerializePeer(PeerId peerId) {
|
||||
return peerIsUser(peerId)
|
||||
? QString("user%1").arg(peerToUser(peerId))
|
||||
: peerIsChat(peerId)
|
||||
? QString("chat%1").arg(peerToChat(peerId))
|
||||
: QString("channel%1").arg(peerToChannel(peerId));
|
||||
}
|
||||
|
||||
[[nodiscard]] QString DebugSerializeKey(const Key &key) {
|
||||
return v::match(key, [&](PhotoId photoId) {
|
||||
return QString("photo%1").arg(photoId);
|
||||
}, [](FullMsgId itemId) {
|
||||
return DebugSerializeMsgId(itemId);
|
||||
}, [&](GroupThumbs::CollageKey key) {
|
||||
return QString("collage%1").arg(key.index);
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] QString DebugSerializeContext(const Context &context) {
|
||||
return v::match(context, [](PeerId peerId) {
|
||||
return DebugSerializePeer(peerId);
|
||||
}, [](MessageGroupId groupId) {
|
||||
return QString("group_%1_%2"
|
||||
).arg(DebugSerializePeer(groupId.peer)
|
||||
).arg(groupId.value);
|
||||
}, [](FullMsgId item) {
|
||||
return DebugSerializeMsgId(item);
|
||||
}, [](v::null_t) -> QString {
|
||||
return "null";
|
||||
});
|
||||
}
|
||||
|
||||
Data::FileOrigin ComputeFileOrigin(const Key &key, const Context &context) {
|
||||
return v::match(key, [&](PhotoId photoId) {
|
||||
return v::match(context, [&](PeerId peerId) {
|
||||
@@ -474,6 +511,37 @@ void GroupThumbs::RefreshFromSlice(
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Slice>
|
||||
void ValidateSlice(
|
||||
const Slice &slice,
|
||||
const Context &context,
|
||||
int from,
|
||||
int index,
|
||||
int till) {
|
||||
auto keys = base::flat_set<Key>();
|
||||
for (auto i = from; i != till; ++i) {
|
||||
const auto key = ComputeKey(slice, i);
|
||||
if (keys.contains(key)) {
|
||||
// All items should be unique!
|
||||
auto strings = QStringList();
|
||||
strings.reserve(till - from);
|
||||
for (auto i = from; i != till; ++i) {
|
||||
strings.push_back(DebugSerializeKey(ComputeKey(slice, i)));
|
||||
}
|
||||
CrashReports::SetAnnotation(
|
||||
"keys",
|
||||
QString("%1:%2-(%3)-%4:"
|
||||
).arg(DebugSerializeContext(context)
|
||||
).arg(from
|
||||
).arg(index
|
||||
).arg(till) + strings.join(","));
|
||||
Unexpected("Bad slice in GroupThumbs.");
|
||||
} else {
|
||||
keys.emplace(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Slice>
|
||||
void GroupThumbs::fillItems(
|
||||
const Slice &slice,
|
||||
@@ -487,6 +555,10 @@ void GroupThumbs::fillItems(
|
||||
const auto current = (index - from);
|
||||
const auto old = base::take(_items);
|
||||
|
||||
if (Logs::DebugEnabled()) {
|
||||
ValidateSlice(slice, _context, from, index, till);
|
||||
}
|
||||
|
||||
markCacheStale();
|
||||
_items.reserve(till - from);
|
||||
for (auto i = from; i != till; ++i) {
|
||||
@@ -514,12 +586,16 @@ void GroupThumbs::animateAliveItems(int current) {
|
||||
}
|
||||
|
||||
void GroupThumbs::fillDyingItems(const std::vector<not_null<Thumb*>> &old) {
|
||||
Expects(_cache.size() >= _items.size());
|
||||
|
||||
_dying.reserve(_cache.size() - _items.size());
|
||||
animatePreviouslyAlive(old);
|
||||
markRestAsDying();
|
||||
}
|
||||
|
||||
void GroupThumbs::markRestAsDying() {
|
||||
Expects(_cache.size() >= _items.size());
|
||||
|
||||
_dying.reserve(_cache.size() - _items.size());
|
||||
for (const auto &cacheItem : _cache) {
|
||||
const auto &thumb = cacheItem.second;
|
||||
|
||||
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/qthelp_url.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "zlib.h"
|
||||
|
||||
namespace MTP {
|
||||
@@ -81,6 +82,25 @@ using namespace details;
|
||||
return idsStr + "]";
|
||||
}
|
||||
|
||||
[[nodiscard]] QString ComputeAppVersion() {
|
||||
return QString::fromLatin1(AppVersionStr) + ([] {
|
||||
#if defined OS_MAC_STORE
|
||||
return u" Mac App Store"_q;
|
||||
#elif defined OS_WIN_STORE // OS_MAC_STORE
|
||||
return (Platform::IsWindows64Bit() ? u" x64"_q : QString())
|
||||
+ u" Microsoft Store"_q;
|
||||
#elif defined Q_OS_UNIX && !defined Q_OS_MAC // OS_MAC_STORE || OS_WIN_STORE
|
||||
return Platform::InFlatpak()
|
||||
? u" Flatpak"_q
|
||||
: Platform::InSnap()
|
||||
? u" Snap"_q
|
||||
: QString();
|
||||
#else // OS_MAC_STORE || OS_WIN_STORE || (defined Q_OS_UNIX && !defined Q_OS_MAC)
|
||||
return Platform::IsWindows64Bit() ? u" x64"_q : QString();
|
||||
#endif // OS_MAC_STORE || OS_WIN_STORE || (defined Q_OS_UNIX && !defined Q_OS_MAC)
|
||||
})();
|
||||
}
|
||||
|
||||
void WrapInvokeAfter(
|
||||
SerializedRequest &to,
|
||||
const SerializedRequest &from,
|
||||
@@ -632,26 +652,7 @@ void SessionPrivate::tryToSend() {
|
||||
const auto systemVersion = (_currentDcType == DcType::Cdn)
|
||||
? "n/a"
|
||||
: _instance->systemVersion();
|
||||
#if defined OS_MAC_STORE
|
||||
const auto appVersion = QString::fromLatin1(AppVersionStr)
|
||||
+ " Mac App Store";
|
||||
#elif defined OS_WIN_STORE // OS_MAC_STORE
|
||||
const auto appVersion = QString::fromLatin1(AppVersionStr)
|
||||
+ " Microsoft Store";
|
||||
#elif defined Q_OS_UNIX && !defined Q_OS_MAC // OS_MAC_STORE || OS_WIN_STORE
|
||||
const auto appVersion = [] {
|
||||
if (Platform::InFlatpak()) {
|
||||
return QString::fromLatin1(AppVersionStr)
|
||||
+ " Flatpak";
|
||||
} else if (Platform::InSnap()) {
|
||||
return QString::fromLatin1(AppVersionStr)
|
||||
+ " Snap";
|
||||
}
|
||||
return QString::fromLatin1(AppVersionStr);
|
||||
}();
|
||||
#else // OS_MAC_STORE || OS_WIN_STORE || (defined Q_OS_UNIX && !defined Q_OS_MAC)
|
||||
const auto appVersion = QString::fromLatin1(AppVersionStr);
|
||||
#endif // OS_MAC_STORE || OS_WIN_STORE || (defined Q_OS_UNIX && !defined Q_OS_MAC)
|
||||
const auto appVersion = ComputeAppVersion();
|
||||
const auto proxyType = _options->proxy.type;
|
||||
const auto mtprotoProxy = (proxyType == ProxyData::Type::Mtproto);
|
||||
const auto clientProxyFields = mtprotoProxy
|
||||
@@ -1669,7 +1670,6 @@ SessionPrivate::HandleResult SessionPrivate::handleOneReceived(
|
||||
|
||||
auto rFrom = originalRequest->constData() + 8;
|
||||
const auto rEnd = originalRequest->constData() + originalRequest->size();
|
||||
auto toAck = QVector<MTPlong>();
|
||||
if (mtpTypeId(*rFrom) == mtpc_msgs_state_req) {
|
||||
MTPMsgsStateReq request;
|
||||
if (!request.read(rFrom, rEnd)) {
|
||||
|
||||
@@ -39,7 +39,7 @@ void UnsafeOpenUrl(const QString &url) {
|
||||
return;
|
||||
}
|
||||
|
||||
QProcess::execute(qsl("xdg-open"), { url });
|
||||
QProcess::startDetached(qsl("xdg-open"), { url });
|
||||
}
|
||||
|
||||
void UnsafeOpenEmailLink(const QString &email) {
|
||||
@@ -83,7 +83,7 @@ void UnsafeLaunch(const QString &filepath) {
|
||||
return;
|
||||
}
|
||||
|
||||
QProcess::execute(qsl("xdg-open"), { qUrlPath.toEncoded() });
|
||||
QProcess::startDetached(qsl("xdg-open"), { qUrlPath.toEncoded() });
|
||||
}
|
||||
|
||||
} // namespace File
|
||||
|
||||
@@ -61,9 +61,9 @@ void Launcher::initHook() {
|
||||
appimagePath.size(),
|
||||
md5Hash);
|
||||
|
||||
return qsl("appimagekit_%1-%2.desktop")
|
||||
.arg(md5Hash)
|
||||
.arg(AppName.utf16().replace(' ', '_'));
|
||||
return qsl("appimagekit_%1-%2.desktop").arg(
|
||||
md5Hash,
|
||||
AppName.utf16().replace(' ', '_'));
|
||||
}
|
||||
|
||||
return qsl(MACRO_TO_STRING(TDESKTOP_LAUNCHER_BASENAME) ".desktop");
|
||||
|
||||
@@ -147,6 +147,8 @@ private:
|
||||
rpl::event_stream<> _accept;
|
||||
rpl::event_stream<> _reject;
|
||||
|
||||
bool _destroyedConnected = false;
|
||||
|
||||
};
|
||||
|
||||
class GtkFileDialog : public QDialog {
|
||||
@@ -261,8 +263,9 @@ void QGtkDialog::exec() {
|
||||
}
|
||||
|
||||
void QGtkDialog::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) {
|
||||
connect(parent, &QWindow::destroyed, this, [=] { onParentWindowDestroyed(); },
|
||||
Qt::UniqueConnection);
|
||||
if (!std::exchange(_destroyedConnected, true)) {
|
||||
connect(parent, &QWindow::destroyed, this, [=] { onParentWindowDestroyed(); });
|
||||
}
|
||||
setParent(parent);
|
||||
setFlags(flags);
|
||||
setModality(modality);
|
||||
|
||||
@@ -465,7 +465,7 @@ std::optional<bool> IsDarkMode() {
|
||||
|
||||
if (!themeName.has_value()) {
|
||||
return std::nullopt;
|
||||
} else if (themeName->toLower().contains(qsl("-dark"))) {
|
||||
} else if (themeName->contains(qsl("-dark"), Qt::CaseInsensitive)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ void GroupMembersWidget::refreshUserOnline(UserData *user) {
|
||||
|
||||
_now = base::unixtime::now();
|
||||
|
||||
auto member = getMember(it.value());
|
||||
auto member = getMember(it->second);
|
||||
member->statusHasOnlineColor = !user->isBot()
|
||||
&& Data::OnlineTextActive(user->onlineTill, _now);
|
||||
member->onlineTill = user->onlineTill;
|
||||
@@ -420,16 +420,16 @@ void GroupMembersWidget::setItemFlags(
|
||||
|
||||
auto GroupMembersWidget::computeMember(not_null<UserData*> user)
|
||||
-> not_null<Member*> {
|
||||
auto it = _membersByUser.constFind(user);
|
||||
auto it = _membersByUser.find(user);
|
||||
if (it == _membersByUser.cend()) {
|
||||
auto member = new Member(user);
|
||||
it = _membersByUser.insert(user, member);
|
||||
it = _membersByUser.emplace(user, member).first;
|
||||
member->statusHasOnlineColor = !user->isBot()
|
||||
&& Data::OnlineTextActive(user->onlineTill, _now);
|
||||
member->onlineTill = user->onlineTill;
|
||||
member->onlineForSort = Data::SortByOnlineValue(user, _now);
|
||||
}
|
||||
return it.value();
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void GroupMembersWidget::onUpdateOnlineDisplay() {
|
||||
@@ -461,7 +461,7 @@ void GroupMembersWidget::onUpdateOnlineDisplay() {
|
||||
|
||||
GroupMembersWidget::~GroupMembersWidget() {
|
||||
auto members = base::take(_membersByUser);
|
||||
for_const (auto member, members) {
|
||||
for (const auto &[_, member] : members) {
|
||||
delete member;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ private:
|
||||
not_null<ChannelData*> megagroup);
|
||||
bool addUsersToEnd(not_null<ChannelData*> megagroup);
|
||||
|
||||
QMap<UserData*, Member*> _membersByUser;
|
||||
base::flat_map<UserData*, Member*> _membersByUser;
|
||||
bool _sortByOnline = false;
|
||||
TimeId _now = 0;
|
||||
|
||||
|
||||
@@ -38,15 +38,15 @@ QString ToFilePart(FileKey val) {
|
||||
|
||||
bool KeyAlreadyUsed(QString &name) {
|
||||
name += '0';
|
||||
if (QFileInfo(name).exists()) {
|
||||
if (QFileInfo::exists(name)) {
|
||||
return true;
|
||||
}
|
||||
name[name.size() - 1] = '1';
|
||||
if (QFileInfo(name).exists()) {
|
||||
if (QFileInfo::exists(name)) {
|
||||
return true;
|
||||
}
|
||||
name[name.size() - 1] = 's';
|
||||
if (QFileInfo(name).exists()) {
|
||||
if (QFileInfo::exists(name)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -319,7 +319,7 @@ bool ReadFile(
|
||||
// detect order of read attempts
|
||||
QString toTry[2];
|
||||
const auto modern = base + 's';
|
||||
if (QFileInfo(modern).exists()) {
|
||||
if (QFileInfo::exists(modern)) {
|
||||
toTry[0] = modern;
|
||||
} else {
|
||||
// Legacy way.
|
||||
|
||||
@@ -387,9 +387,9 @@ QString FormatUpdateNotification(const QString &path, const Delta &delta) {
|
||||
if (!delta.changed.empty()) {
|
||||
result += qstr("-------- Modified --------\n\n");
|
||||
for (const auto question : delta.changed) {
|
||||
result += qsl("Q: %1\nA: %2\n\n"
|
||||
).arg(question->question
|
||||
).arg(question->value.trimmed());
|
||||
result += qsl("Q: %1\nA: %2\n\n").arg(
|
||||
question->question,
|
||||
question->value.trimmed());
|
||||
}
|
||||
}
|
||||
if (!delta.removed.empty()) {
|
||||
@@ -734,7 +734,7 @@ auto Templates::query(const QString &text) const -> std::vector<Question> {
|
||||
pairById
|
||||
) | ranges::views::filter([](const Pair &pair) {
|
||||
return pair.second > 0;
|
||||
}) | ranges::to_vector | ranges::action::stable_sort(sorter);
|
||||
}) | ranges::to_vector | ranges::actions::stable_sort(sorter);
|
||||
return good | ranges::views::transform([&](const Pair &pair) {
|
||||
return questionById(pair.first);
|
||||
}) | ranges::views::take(kQueryLimit) | ranges::to_vector;
|
||||
|
||||
@@ -31,7 +31,7 @@ CountryInput::CountryInput(QWidget *parent, const style::InputField &st) : TWidg
|
||||
auto availableWidth = width() - _st.textMargins.left() - _st.textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right() - 1;
|
||||
auto placeholderFont = _st.placeholderFont->f;
|
||||
placeholderFont.setStyleStrategy(QFont::PreferMatch);
|
||||
auto metrics = QFontMetrics(placeholderFont);
|
||||
//auto metrics = QFontMetrics(placeholderFont);
|
||||
auto placeholder = QString();// metrics.elidedText(tr::lng_country_fake_ph(tr::now), Qt::ElideRight, availableWidth);
|
||||
if (!placeholder.isNull()) {
|
||||
_placeholderPath.addText(0, QFontMetrics(placeholderFont).ascent(), placeholderFont, placeholder);
|
||||
|
||||
@@ -389,7 +389,7 @@ QImage FilterIconPanel::grabForAnimation() {
|
||||
_a_show = base::take(showAnimation);
|
||||
_showAnimation = base::take(showAnimationData);
|
||||
_a_opacity = base::take(opacityAnimation);
|
||||
_cache = base::take(_cache);
|
||||
_cache = base::take(cache);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -223,10 +223,12 @@ UserpicButton::UserpicButton(
|
||||
, _peer(peer)
|
||||
, _cropTitle(CropTitle(_peer))
|
||||
, _role(role) {
|
||||
Expects(_role != Role::OpenProfile);
|
||||
Expects(_role != Role::OpenProfile && _role != Role::OpenPhoto);
|
||||
|
||||
_waiting = false;
|
||||
processPeerPhoto();
|
||||
prepare();
|
||||
setupPeerViewers();
|
||||
}
|
||||
|
||||
void UserpicButton::prepare() {
|
||||
@@ -432,8 +434,10 @@ void UserpicButton::paintUserpicFrame(Painter &p, QPoint photoPosition) {
|
||||
if (_streamed
|
||||
&& _streamed->player().ready()
|
||||
&& !_streamed->player().videoSize().isEmpty()) {
|
||||
const auto paused = _controller->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::RoundPlaying);
|
||||
const auto paused = _controller
|
||||
? _controller->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::RoundPlaying)
|
||||
: false;
|
||||
auto request = Media::Streaming::FrameRequest();
|
||||
auto size = QSize{ _st.photoSize, _st.photoSize };
|
||||
request.outer = size * cIntRetinaFactor();
|
||||
|
||||
@@ -13,14 +13,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Ui {
|
||||
|
||||
void ShowMultilineToast(MultilineToastArgs &&args) {
|
||||
Ui::Toast::Show(Ui::Toast::Config{
|
||||
auto config = Ui::Toast::Config{
|
||||
.text = std::move(args.text),
|
||||
.st = &st::defaultMultilineToast,
|
||||
.durationMs = (args.duration
|
||||
? args.duration
|
||||
: Ui::Toast::kDefaultDuration),
|
||||
.multiline = true,
|
||||
});
|
||||
};
|
||||
if (args.parentOverride) {
|
||||
Ui::Toast::Show(args.parentOverride, std::move(config));
|
||||
} else {
|
||||
Ui::Toast::Show(std::move(config));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Ui {
|
||||
struct MultilineToastArgs {
|
||||
TextWithEntities text;
|
||||
crl::time duration = 0;
|
||||
QWidget *parentOverride = nullptr;
|
||||
};
|
||||
|
||||
void ShowMultilineToast(MultilineToastArgs &&args);
|
||||
|
||||
@@ -658,8 +658,8 @@ void MainWindow::savePosition(Qt::WindowState state) {
|
||||
auto centerY = realPosition.y + realPosition.h / 2;
|
||||
int minDelta = 0;
|
||||
QScreen *chosen = nullptr;
|
||||
auto screens = QGuiApplication::screens();
|
||||
for (auto screen : QGuiApplication::screens()) {
|
||||
const auto screens = QGuiApplication::screens();
|
||||
for (auto screen : screens) {
|
||||
auto delta = (screen->geometry().center() - QPoint(centerX, centerY)).manhattanLength();
|
||||
if (!chosen || delta < minDelta) {
|
||||
minDelta = delta;
|
||||
|
||||
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_group_call.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "passport/passport_form_controller.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
@@ -151,7 +152,9 @@ void SessionNavigation::resolveChannelById(
|
||||
return;
|
||||
}
|
||||
const auto fail = [=] {
|
||||
Ui::Toast::Show(tr::lng_error_post_link_invalid(tr::now));
|
||||
Ui::ShowMultilineToast({
|
||||
.text = { tr::lng_error_post_link_invalid(tr::now) }
|
||||
});
|
||||
};
|
||||
_session->api().request(base::take(_resolveRequestId)).cancel();
|
||||
_resolveRequestId = _session->api().request(MTPchannels_GetChannels(
|
||||
@@ -176,6 +179,11 @@ void SessionNavigation::showPeerByLinkResolved(
|
||||
not_null<PeerData*> peer,
|
||||
const PeerByLinkInfo &info) {
|
||||
if (info.voicechatHash && peer->isChannel()) {
|
||||
const auto bad = [=] {
|
||||
Ui::ShowMultilineToast({
|
||||
.text = { tr::lng_group_invite_bad_link(tr::now) }
|
||||
});
|
||||
};
|
||||
const auto hash = *info.voicechatHash;
|
||||
_session->api().request(base::take(_resolveRequestId)).cancel();
|
||||
_resolveRequestId = _session->api().request(
|
||||
@@ -183,9 +191,25 @@ void SessionNavigation::showPeerByLinkResolved(
|
||||
).done([=](const MTPmessages_ChatFull &result) {
|
||||
_session->api().processFullPeer(peer, result);
|
||||
if (const auto call = peer->groupCall()) {
|
||||
parentController()->startOrJoinGroupCall(peer, hash);
|
||||
const auto id = call->id();
|
||||
_resolveRequestId = _session->api().request(
|
||||
MTPphone_GetGroupCall(call->input())
|
||||
).done([=](const MTPphone_GroupCall &result) {
|
||||
if (const auto now = peer->groupCall()
|
||||
; now && now->id() == id) {
|
||||
now->processFullCall(result);
|
||||
parentController()->startOrJoinGroupCall(
|
||||
peer,
|
||||
hash,
|
||||
SessionController::GroupCallJoinConfirm::Always);
|
||||
} else {
|
||||
bad();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
bad();
|
||||
}).send();
|
||||
} else {
|
||||
Ui::Toast::Show(tr::lng_error_post_link_invalid(tr::now));
|
||||
bad();
|
||||
}
|
||||
}).send();
|
||||
return;
|
||||
@@ -949,30 +973,32 @@ void SessionController::closeThirdSection() {
|
||||
void SessionController::startOrJoinGroupCall(
|
||||
not_null<PeerData*> peer,
|
||||
QString joinHash,
|
||||
bool confirmedLeaveOther) {
|
||||
GroupCallJoinConfirm confirm) {
|
||||
auto &calls = Core::App().calls();
|
||||
const auto confirm = [&](QString text, QString button) {
|
||||
const auto askConfirmation = [&](QString text, QString button) {
|
||||
Ui::show(Box<ConfirmBox>(text, button, crl::guard(this, [=] {
|
||||
Ui::hideLayer();
|
||||
startOrJoinGroupCall(peer, joinHash, true);
|
||||
startOrJoinGroupCall(peer, joinHash, GroupCallJoinConfirm::None);
|
||||
})));
|
||||
};
|
||||
if (!confirmedLeaveOther && calls.inCall()) {
|
||||
if (confirm != GroupCallJoinConfirm::None && calls.inCall()) {
|
||||
// Do you want to leave your active voice chat
|
||||
// to join a voice chat in this group?
|
||||
confirm(
|
||||
askConfirmation(
|
||||
tr::lng_call_leave_to_other_sure(tr::now),
|
||||
tr::lng_call_bar_hangup(tr::now));
|
||||
} else if (!confirmedLeaveOther && calls.inGroupCall()) {
|
||||
} else if (confirm != GroupCallJoinConfirm::None
|
||||
&& calls.inGroupCall()) {
|
||||
if (calls.currentGroupCall()->peer() == peer) {
|
||||
calls.activateCurrentCall(joinHash);
|
||||
} else {
|
||||
confirm(
|
||||
askConfirmation(
|
||||
tr::lng_group_call_leave_to_other_sure(tr::now),
|
||||
tr::lng_group_call_leave(tr::now));
|
||||
}
|
||||
} else {
|
||||
calls.startOrJoinGroupCall(peer, joinHash);
|
||||
const auto confirmNeeded = (confirm == GroupCallJoinConfirm::Always);
|
||||
calls.startOrJoinGroupCall(peer, joinHash, confirmNeeded);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -298,10 +298,15 @@ public:
|
||||
void resizeForThirdSection();
|
||||
void closeThirdSection();
|
||||
|
||||
enum class GroupCallJoinConfirm {
|
||||
None,
|
||||
IfNowInAnother,
|
||||
Always,
|
||||
};
|
||||
void startOrJoinGroupCall(
|
||||
not_null<PeerData*> peer,
|
||||
QString joinHash = QString(),
|
||||
bool confirmedLeaveOther = false);
|
||||
GroupCallJoinConfirm confirm = GroupCallJoinConfirm::IfNowInAnother);
|
||||
|
||||
void showSection(
|
||||
std::shared_ptr<SectionMemento> memento,
|
||||
|
||||
2
Telegram/ThirdParty/tgcalls
vendored
2
Telegram/ThirdParty/tgcalls
vendored
Submodule Telegram/ThirdParty/tgcalls updated: 7fccdee1bb...384a5e4d78
@@ -1,7 +1,7 @@
|
||||
AppVersion 2006002
|
||||
AppVersion 2006004
|
||||
AppVersionStrMajor 2.6
|
||||
AppVersionStrSmall 2.6.2
|
||||
AppVersionStr 2.6.2
|
||||
AppVersionStrSmall 2.6.4
|
||||
AppVersionStr 2.6.4
|
||||
BetaChannel 1
|
||||
AlphaVersion 0
|
||||
AppVersionOriginal 2.6.2.beta
|
||||
AppVersionOriginal 2.6.4.beta
|
||||
|
||||
Submodule Telegram/codegen updated: 04645b6d0f...a4904e076b
Submodule Telegram/lib_base updated: 8f26ce5351...5a4f00cbf9
Submodule Telegram/lib_spellcheck updated: 8c7f1154fa...5ae6877747
Submodule Telegram/lib_ui updated: c74cf04cc6...0adf0383d8
@@ -1,3 +1,13 @@
|
||||
2.6.4 beta (16.03.21)
|
||||
|
||||
- Fix freeze in voice chats.
|
||||
|
||||
2.6.3 beta (16.03.21)
|
||||
|
||||
- Fix audio device selection in voice chats.
|
||||
- Fix blinking self profile photo in case the profile photo privacy is used.
|
||||
- Fix voice chat admin menu on macOS.
|
||||
|
||||
2.6.2 beta (13.03.21)
|
||||
|
||||
- Fix text disappearing because of cloud drafts sync.
|
||||
|
||||
2
cmake
2
cmake
Submodule cmake updated: 25f0733a60...bd9c097fea
Reference in New Issue
Block a user