mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 21:57:10 +02:00
Start group call bar in HistoryWidget.
This commit is contained in:
parent
3aa2619a7f
commit
33941ad1b9
24 changed files with 1211 additions and 22 deletions
|
@ -264,6 +264,8 @@ PRIVATE
|
|||
calls/calls_box_controller.h
|
||||
calls/calls_call.cpp
|
||||
calls/calls_call.h
|
||||
calls/calls_group_call.cpp
|
||||
calls/calls_group_call.h
|
||||
calls/calls_emoji_fingerprint.cpp
|
||||
calls/calls_emoji_fingerprint.h
|
||||
calls/calls_instance.cpp
|
||||
|
@ -385,6 +387,8 @@ PRIVATE
|
|||
data/data_file_origin.h
|
||||
data/data_flags.h
|
||||
data/data_game.h
|
||||
data/data_group_call.cpp
|
||||
data/data_group_call.h
|
||||
data/data_groups.cpp
|
||||
data/data_groups.h
|
||||
data/data_histories.cpp
|
||||
|
@ -536,6 +540,8 @@ PRIVATE
|
|||
history/view/history_view_cursor_state.h
|
||||
history/view/history_view_element.cpp
|
||||
history/view/history_view_element.h
|
||||
history/view/history_view_group_call_tracker.cpp
|
||||
history/view/history_view_group_call_tracker.h
|
||||
history/view/history_view_list_widget.cpp
|
||||
history/view/history_view_list_widget.h
|
||||
history/view/history_view_message.cpp
|
||||
|
|
|
@ -1342,7 +1342,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_broadcast_silent_ph" = "Silent broadcast...";
|
||||
"lng_send_anonymous_ph" = "Send anonymously...";
|
||||
"lng_record_cancel" = "Release outside this field to cancel";
|
||||
"lng_record_lock_cancel" = "Click outside of circle to cancel";
|
||||
"lng_record_lock_cancel" = "Click outside of the circle to cancel";
|
||||
"lng_record_lock_cancel_sure" = "Are you sure you want to stop recording and discard your voice message?";
|
||||
"lng_record_lock_discard" = "Discard";
|
||||
"lng_will_be_notified" = "Members will be notified when you post";
|
||||
|
|
|
@ -1796,7 +1796,9 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
|||
} break;
|
||||
|
||||
case mtpc_updatePhoneCall:
|
||||
case mtpc_updatePhoneCallSignalingData: {
|
||||
case mtpc_updatePhoneCallSignalingData:
|
||||
case mtpc_updateGroupCallParticipants:
|
||||
case mtpc_updateGroupCall: {
|
||||
Core::App().calls().handleUpdate(&session(), update);
|
||||
} break;
|
||||
|
||||
|
|
|
@ -289,7 +289,7 @@ void Call::startIncoming() {
|
|||
}
|
||||
|
||||
void Call::answer() {
|
||||
_delegate->requestPermissionsOrFail(crl::guard(this, [=] {
|
||||
_delegate->callRequestPermissionsOrFail(crl::guard(this, [=] {
|
||||
actuallyAnswer();
|
||||
}));
|
||||
}
|
||||
|
@ -776,11 +776,11 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
|
|||
auto callLogPath = callLogFolder + qsl("/last_call_log.txt");
|
||||
auto callLogNative = QDir::toNativeSeparators(callLogPath);
|
||||
#ifdef Q_OS_WIN
|
||||
descriptor.config.logPath = callLogNative.toStdWString();
|
||||
descriptor.config.logPath.data = callLogNative.toStdWString();
|
||||
#else // Q_OS_WIN
|
||||
const auto callLogUtf = QFile::encodeName(callLogNative);
|
||||
descriptor.config.logPath.resize(callLogUtf.size());
|
||||
ranges::copy(callLogUtf, descriptor.config.logPath.begin());
|
||||
descriptor.config.logPath.data.resize(callLogUtf.size());
|
||||
ranges::copy(callLogUtf, descriptor.config.logPath.data.begin());
|
||||
#endif // Q_OS_WIN
|
||||
QFile(callLogPath).remove();
|
||||
QDir().mkpath(callLogFolder);
|
||||
|
|
|
@ -68,7 +68,7 @@ public:
|
|||
Ended,
|
||||
};
|
||||
virtual void playSound(Sound sound) = 0;
|
||||
virtual void requestPermissionsOrFail(Fn<void()> onSuccess) = 0;
|
||||
virtual void callRequestPermissionsOrFail(Fn<void()> onSuccess) = 0;
|
||||
virtual auto getVideoCapture()
|
||||
-> std::shared_ptr<tgcalls::VideoCaptureInterface> = 0;
|
||||
|
||||
|
|
329
Telegram/SourceFiles/calls/calls_group_call.cpp
Normal file
329
Telegram/SourceFiles/calls/calls_group_call.cpp
Normal file
|
@ -0,0 +1,329 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "calls/calls_group_call.h"
|
||||
|
||||
#include "main/main_session.h"
|
||||
//#include "main/main_account.h"
|
||||
//#include "main/main_app_config.h"
|
||||
#include "apiwrap.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
//#include "boxes/rate_call_box.h"
|
||||
//#include "calls/calls_instance.h"
|
||||
//#include "base/openssl_help.h"
|
||||
//#include "mtproto/mtproto_dh_utils.h"
|
||||
//#include "mtproto/mtproto_config.h"
|
||||
//#include "core/application.h"
|
||||
//#include "core/core_settings.h"
|
||||
//#include "media/audio/media_audio_track.h"
|
||||
//#include "base/platform/base_platform_info.h"
|
||||
//#include "calls/calls_panel.h"
|
||||
//#include "webrtc/webrtc_video_track.h"
|
||||
//#include "webrtc/webrtc_media_devices.h"
|
||||
#include "data/data_channel.h"
|
||||
//#include "data/data_session.h"
|
||||
//#include "facades.h"
|
||||
|
||||
#include <tgcalls/group/GroupInstanceImpl.h>
|
||||
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonArray>
|
||||
|
||||
namespace tgcalls {
|
||||
class GroupInstanceImpl;
|
||||
} // namespace tgcalls
|
||||
|
||||
namespace Calls {
|
||||
|
||||
GroupCall::GroupCall(
|
||||
not_null<Delegate*> delegate,
|
||||
not_null<ChannelData*> channel,
|
||||
const MTPInputGroupCall &inputCall)
|
||||
: _delegate(delegate)
|
||||
, _channel(channel)
|
||||
, _api(&_channel->session().mtp()) {
|
||||
if (inputCall.c_inputGroupCall().vid().v) {
|
||||
join(inputCall);
|
||||
} else {
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
GroupCall::~GroupCall() {
|
||||
destroyController();
|
||||
}
|
||||
|
||||
void GroupCall::start() {
|
||||
const auto randomId = rand_value<int32>();
|
||||
_api.request(MTPphone_CreateGroupCall(
|
||||
_channel->inputChannel,
|
||||
MTP_int(randomId)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_channel->session().api().applyUpdates(result);
|
||||
}).fail([=](const RPCError &error) {
|
||||
int a = error.code();
|
||||
}).send();
|
||||
}
|
||||
|
||||
void GroupCall::join(const MTPInputGroupCall &inputCall) {
|
||||
inputCall.match([&](const MTPDinputGroupCall &data) {
|
||||
_id = data.vid().v;
|
||||
_accessHash = data.vaccess_hash().v;
|
||||
createAndStartController();
|
||||
const auto weak = base::make_weak(this);
|
||||
_instance->emitJoinPayload([=](tgcalls::GroupJoinPayload payload) {
|
||||
crl::on_main(weak, [=, payload = std::move(payload)]{
|
||||
auto fingerprints = QJsonArray();
|
||||
for (const auto print : payload.fingerprints) {
|
||||
auto object = QJsonObject();
|
||||
object.insert("hash", QString::fromStdString(print.hash));
|
||||
object.insert("setup", QString::fromStdString(print.setup));
|
||||
object.insert(
|
||||
"fingerprint",
|
||||
QString::fromStdString(print.fingerprint));
|
||||
fingerprints.push_back(object);
|
||||
}
|
||||
|
||||
auto root = QJsonObject();
|
||||
root.insert("ufrag", QString::fromStdString(payload.ufrag));
|
||||
root.insert("pwd", QString::fromStdString(payload.pwd));
|
||||
root.insert("fingerprints", fingerprints);
|
||||
root.insert("ssrc", int(payload.ssrc));
|
||||
|
||||
const auto json = QJsonDocument(root).toJson(
|
||||
QJsonDocument::Compact);
|
||||
_api.request(MTPphone_JoinGroupCall(
|
||||
MTP_flags(_muted.current()
|
||||
? MTPphone_JoinGroupCall::Flag::f_muted
|
||||
: MTPphone_JoinGroupCall::Flag(0)),
|
||||
inputCall,
|
||||
MTP_dataJSON(MTP_bytes(json))
|
||||
)).done([=](const MTPUpdates &updates) {
|
||||
_channel->session().api().applyUpdates(updates);
|
||||
}).fail([=](const RPCError &error) {
|
||||
int a = error.code();
|
||||
}).send();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void GroupCall::setMuted(bool mute) {
|
||||
_muted = mute;
|
||||
if (_instance) {
|
||||
_instance->setIsMuted(mute);
|
||||
}
|
||||
}
|
||||
|
||||
bool GroupCall::handleUpdate(const MTPGroupCall &call) {
|
||||
return call.match([&](const MTPDgroupCall &data) {
|
||||
if (_id != data.vid().v
|
||||
|| _accessHash != data.vaccess_hash().v
|
||||
|| !_instance) {
|
||||
return false;
|
||||
}
|
||||
if (const auto params = data.vparams()) {
|
||||
params->match([&](const MTPDdataJSON &data) {
|
||||
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
|
||||
const auto document = QJsonDocument::fromJson(
|
||||
data.vdata().v,
|
||||
&error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
LOG(("API Error: "
|
||||
"Failed to parse group call params, error: %1."
|
||||
).arg(error.errorString()));
|
||||
return;
|
||||
} else if (!document.isObject()) {
|
||||
LOG(("API Error: "
|
||||
"Not an object received in group call params."));
|
||||
return;
|
||||
}
|
||||
const auto readString = [](
|
||||
const QJsonObject &object,
|
||||
const char *key) {
|
||||
return object.value(key).toString().toStdString();
|
||||
};
|
||||
const auto root = document.object().value("transport").toObject();
|
||||
auto payload = tgcalls::GroupJoinResponsePayload();
|
||||
payload.ufrag = readString(root, "ufrag");
|
||||
payload.pwd = readString(root, "pwd");
|
||||
const auto prints = root.value("fingerprints").toArray();
|
||||
const auto candidates = root.value("candidates").toArray();
|
||||
for (const auto &print : prints) {
|
||||
const auto object = print.toObject();
|
||||
payload.fingerprints.push_back(tgcalls::GroupJoinPayloadFingerprint{
|
||||
.hash = readString(object, "hash"),
|
||||
.setup = readString(object, "setup"),
|
||||
.fingerprint = readString(object, "fingerprint"),
|
||||
});
|
||||
}
|
||||
for (const auto &candidate : candidates) {
|
||||
const auto object = candidate.toObject();
|
||||
payload.candidates.push_back(tgcalls::GroupJoinResponseCandidate{
|
||||
.port = readString(object, "port"),
|
||||
.protocol = readString(object, "protocol"),
|
||||
.network = readString(object, "network"),
|
||||
.generation = readString(object, "generation"),
|
||||
.id = readString(object, "id"),
|
||||
.component = readString(object, "component"),
|
||||
.foundation = readString(object, "foundation"),
|
||||
.priority = readString(object, "priority"),
|
||||
.ip = readString(object, "ip"),
|
||||
.type = readString(object, "type"),
|
||||
.tcpType = readString(object, "tcpType"),
|
||||
.relAddr = readString(object, "relAddr"),
|
||||
.relPort = readString(object, "relPort"),
|
||||
});
|
||||
}
|
||||
_instance->setJoinResponsePayload(payload);
|
||||
_api.request(MTPphone_GetGroupParticipants(
|
||||
inputCall(),
|
||||
MTP_int(0),
|
||||
MTP_int(10)
|
||||
)).done([=](const MTPphone_GroupParticipants &result) {
|
||||
auto sources = std::vector<uint32_t>();
|
||||
result.match([&](const MTPDphone_groupParticipants &data) {
|
||||
for (const auto &p : data.vparticipants().v) {
|
||||
p.match([&](const MTPDgroupCallParticipant &data) {
|
||||
if (data.vuser_id().v != _channel->session().userId()) {
|
||||
sources.push_back(data.vsource().v);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
_instance->setSsrcs(std::move(sources));
|
||||
_instance->setIsMuted(false);
|
||||
}).fail([=](const RPCError &error) {
|
||||
int a = error.code();
|
||||
}).send();
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}, [&](const MTPDgroupCallPrivate &data) {
|
||||
if (_instance || _id) {
|
||||
return false;
|
||||
}
|
||||
join(MTP_inputGroupCall(data.vid(), data.vaccess_hash()));
|
||||
return true;
|
||||
}, [&](const MTPDgroupCallDiscarded &data) {
|
||||
if (data.vid().v != _id) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void GroupCall::createAndStartController() {
|
||||
using AudioLevels = std::vector<std::pair<uint32_t, float>>;
|
||||
|
||||
const auto weak = base::make_weak(this);
|
||||
tgcalls::GroupInstanceDescriptor descriptor = {
|
||||
.config = tgcalls::GroupConfig{
|
||||
},
|
||||
.networkStateUpdated = [=](bool) {
|
||||
},
|
||||
.audioLevelsUpdated = [=](const AudioLevels &data) {
|
||||
},
|
||||
};
|
||||
if (Logs::DebugEnabled()) {
|
||||
auto callLogFolder = cWorkingDir() + qsl("DebugLogs");
|
||||
auto callLogPath = callLogFolder + qsl("/last_group_call_log.txt");
|
||||
auto callLogNative = QDir::toNativeSeparators(callLogPath);
|
||||
#ifdef Q_OS_WIN
|
||||
descriptor.config.logPath.data = callLogNative.toStdWString();
|
||||
#else // Q_OS_WIN
|
||||
const auto callLogUtf = QFile::encodeName(callLogNative);
|
||||
descriptor.config.logPath.data.resize(callLogUtf.size());
|
||||
ranges::copy(callLogUtf, descriptor.config.logPath.data.begin());
|
||||
#endif // Q_OS_WIN
|
||||
QFile(callLogPath).remove();
|
||||
QDir().mkpath(callLogFolder);
|
||||
}
|
||||
|
||||
LOG(("Call Info: Creating group instance"));
|
||||
_instance = std::make_unique<tgcalls::GroupInstanceImpl>(
|
||||
std::move(descriptor));
|
||||
|
||||
const auto raw = _instance.get();
|
||||
if (_muted.current()) {
|
||||
raw->setIsMuted(_muted.current());
|
||||
}
|
||||
//raw->setAudioOutputDuckingEnabled(settings.callAudioDuckingEnabled());
|
||||
}
|
||||
|
||||
void GroupCall::setCurrentAudioDevice(bool input, const QString &deviceId) {
|
||||
if (_instance) {
|
||||
const auto id = deviceId.toStdString();
|
||||
//if (input) {
|
||||
// _instance->setAudioInputDevice(id);
|
||||
//} else {
|
||||
// _instance->setAudioOutputDevice(id);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::setAudioVolume(bool input, float level) {
|
||||
if (_instance) {
|
||||
//if (input) {
|
||||
// _instance->setInputVolume(level);
|
||||
//} else {
|
||||
// _instance->setOutputVolume(level);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::setAudioDuckingEnabled(bool enabled) {
|
||||
if (_instance) {
|
||||
//_instance->setAudioOutputDuckingEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::handleRequestError(const RPCError &error) {
|
||||
//if (error.type() == qstr("USER_PRIVACY_RESTRICTED")) {
|
||||
// Ui::show(Box<InformBox>(tr::lng_call_error_not_available(tr::now, lt_user, _user->name)));
|
||||
//} else if (error.type() == qstr("PARTICIPANT_VERSION_OUTDATED")) {
|
||||
// Ui::show(Box<InformBox>(tr::lng_call_error_outdated(tr::now, lt_user, _user->name)));
|
||||
//} else if (error.type() == qstr("CALL_PROTOCOL_LAYER_INVALID")) {
|
||||
// Ui::show(Box<InformBox>(Lang::Hard::CallErrorIncompatible().replace("{user}", _user->name)));
|
||||
//}
|
||||
//finish(FinishType::Failed);
|
||||
}
|
||||
|
||||
void GroupCall::handleControllerError(const QString &error) {
|
||||
if (error == u"ERROR_INCOMPATIBLE"_q) {
|
||||
//Ui::show(Box<InformBox>(
|
||||
// Lang::Hard::CallErrorIncompatible().replace(
|
||||
// "{user}",
|
||||
// _user->name)));
|
||||
} else if (error == u"ERROR_AUDIO_IO"_q) {
|
||||
Ui::show(Box<InformBox>(tr::lng_call_error_audio_io(tr::now)));
|
||||
}
|
||||
//finish(FinishType::Failed);
|
||||
}
|
||||
|
||||
MTPInputGroupCall GroupCall::inputCall() const {
|
||||
Expects(_id != 0);
|
||||
|
||||
return MTP_inputGroupCall(
|
||||
MTP_long(_id),
|
||||
MTP_long(_accessHash));
|
||||
}
|
||||
|
||||
void GroupCall::destroyController() {
|
||||
if (_instance) {
|
||||
//_instance->stop([](tgcalls::FinalState) {
|
||||
//});
|
||||
|
||||
DEBUG_LOG(("Call Info: Destroying call controller.."));
|
||||
_instance.reset();
|
||||
DEBUG_LOG(("Call Info: Call controller destroyed."));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Calls
|
84
Telegram/SourceFiles/calls/calls_group_call.h
Normal file
84
Telegram/SourceFiles/calls/calls_group_call.h
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/weak_ptr.h"
|
||||
#include "base/timer.h"
|
||||
#include "base/bytes.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "mtproto/mtproto_auth_key.h"
|
||||
|
||||
namespace tgcalls {
|
||||
class GroupInstanceImpl;
|
||||
} // namespace tgcalls
|
||||
|
||||
namespace Calls {
|
||||
|
||||
class GroupCall final : public base::has_weak_ptr {
|
||||
public:
|
||||
class Delegate {
|
||||
public:
|
||||
virtual ~Delegate() = default;
|
||||
|
||||
};
|
||||
|
||||
GroupCall(
|
||||
not_null<Delegate*> delegate,
|
||||
not_null<ChannelData*> channel,
|
||||
const MTPInputGroupCall &inputCall);
|
||||
~GroupCall();
|
||||
|
||||
[[nodiscard]] not_null<ChannelData*> channel() const {
|
||||
return _channel;
|
||||
}
|
||||
|
||||
void start();
|
||||
void join(const MTPInputGroupCall &inputCall);
|
||||
bool handleUpdate(const MTPGroupCall &call);
|
||||
|
||||
void setMuted(bool mute);
|
||||
[[nodiscard]] bool muted() const {
|
||||
return _muted.current();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<bool> mutedValue() const {
|
||||
return _muted.value();
|
||||
}
|
||||
|
||||
void setCurrentAudioDevice(bool input, const QString &deviceId);
|
||||
void setAudioVolume(bool input, float level);
|
||||
void setAudioDuckingEnabled(bool enabled);
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
||||
private:
|
||||
void handleRequestError(const RPCError &error);
|
||||
void handleControllerError(const QString &error);
|
||||
void createAndStartController();
|
||||
void destroyController();
|
||||
|
||||
[[nodiscard]] MTPInputGroupCall inputCall() const;
|
||||
|
||||
const not_null<Delegate*> _delegate;
|
||||
const not_null<ChannelData*> _channel;
|
||||
MTP::Sender _api;
|
||||
crl::time _startTime = 0;
|
||||
|
||||
rpl::variable<bool> _muted = false;
|
||||
|
||||
uint64 _id = 0;
|
||||
uint64 _accessHash = 0;
|
||||
|
||||
std::unique_ptr<tgcalls::GroupInstanceImpl> _instance;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Calls
|
|
@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "calls/calls_call.h"
|
||||
#include "calls/calls_panel.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_session.h"
|
||||
#include "media/audio/media_audio_track.h"
|
||||
#include "platform/platform_specific.h"
|
||||
|
@ -39,8 +40,7 @@ Instance::Instance() = default;
|
|||
Instance::~Instance() = default;
|
||||
|
||||
void Instance::startOutgoingCall(not_null<UserData*> user, bool video) {
|
||||
if (alreadyInCall()) { // Already in a call.
|
||||
_currentCallPanel->showAndActivate();
|
||||
if (activateCurrentCall()) {
|
||||
return;
|
||||
}
|
||||
if (user->callsStatus() == UserData::CallsStatus::Private) {
|
||||
|
@ -55,6 +55,26 @@ void Instance::startOutgoingCall(not_null<UserData*> user, bool video) {
|
|||
}));
|
||||
}
|
||||
|
||||
void Instance::startGroupCall(not_null<ChannelData*> channel) {
|
||||
if (activateCurrentCall()) {
|
||||
return;
|
||||
}
|
||||
requestPermissionsOrFail(crl::guard(this, [=] {
|
||||
createGroupCall(channel, MTP_inputGroupCall(MTPlong(), MTPlong()));
|
||||
}));
|
||||
}
|
||||
|
||||
void Instance::joinGroupCall(
|
||||
not_null<ChannelData*> channel,
|
||||
const MTPInputGroupCall &call) {
|
||||
if (activateCurrentCall()) {
|
||||
return;
|
||||
}
|
||||
requestPermissionsOrFail(crl::guard(this, [=] {
|
||||
createGroupCall(channel, call);
|
||||
}));
|
||||
}
|
||||
|
||||
void Instance::callFinished(not_null<Call*> call) {
|
||||
crl::on_main(call, [=] {
|
||||
destroyCall(call);
|
||||
|
@ -142,6 +162,40 @@ void Instance::createCall(not_null<UserData*> user, Call::Type type, bool video)
|
|||
refreshDhConfig();
|
||||
}
|
||||
|
||||
void Instance::destroyGroupCall(not_null<GroupCall*> call) {
|
||||
if (_currentGroupCall.get() == call) {
|
||||
//_currentCallPanel->closeBeforeDestroy();
|
||||
//_currentCallPanel = nullptr;
|
||||
|
||||
auto taken = base::take(_currentGroupCall);
|
||||
_currentGroupCallChanges.fire(nullptr);
|
||||
taken.reset();
|
||||
|
||||
if (App::quitting()) {
|
||||
LOG(("Calls::Instance doesn't prevent quit any more."));
|
||||
}
|
||||
Core::App().quitPreventFinished();
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::createGroupCall(
|
||||
not_null<ChannelData*> channel,
|
||||
const MTPInputGroupCall &inputCall) {
|
||||
auto call = std::make_unique<GroupCall>(
|
||||
getGroupCallDelegate(),
|
||||
channel,
|
||||
inputCall);
|
||||
const auto raw = call.get();
|
||||
|
||||
channel->session().account().sessionChanges(
|
||||
) | rpl::start_with_next([=] {
|
||||
destroyGroupCall(raw);
|
||||
}, raw->lifetime());
|
||||
|
||||
_currentGroupCall = std::move(call);
|
||||
_currentGroupCallChanges.fire_copy(raw);
|
||||
}
|
||||
|
||||
void Instance::refreshDhConfig() {
|
||||
Expects(_currentCall != nullptr);
|
||||
|
||||
|
@ -232,6 +286,10 @@ void Instance::handleUpdate(
|
|||
handleCallUpdate(session, data.vphone_call());
|
||||
}, [&](const MTPDupdatePhoneCallSignalingData &data) {
|
||||
handleSignalingData(data);
|
||||
}, [&](const MTPDupdateGroupCall &data) {
|
||||
handleGroupCallUpdate(session, data.vcall());
|
||||
}, [&](const MTPDupdateGroupCallParticipants &data) {
|
||||
handleGroupCallUpdate(session, data);
|
||||
}, [](const auto &) {
|
||||
Unexpected("Update type in Calls::Instance::handleUpdate.");
|
||||
});
|
||||
|
@ -267,7 +325,7 @@ void Instance::handleCallUpdate(
|
|||
LOG(("API Error: Self found in phoneCallRequested."));
|
||||
}
|
||||
const auto &config = session->serverConfig();
|
||||
if (alreadyInCall() || !user || user->isSelf()) {
|
||||
if (inCall() || inGroupCall() || !user || user->isSelf()) {
|
||||
const auto flags = phoneCall.is_video()
|
||||
? MTPphone_DiscardCall::Flag::f_video
|
||||
: MTPphone_DiscardCall::Flag(0);
|
||||
|
@ -290,6 +348,19 @@ void Instance::handleCallUpdate(
|
|||
}
|
||||
}
|
||||
|
||||
void Instance::handleGroupCallUpdate(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPGroupCall &call) {
|
||||
if (_currentGroupCall) {
|
||||
_currentGroupCall->handleUpdate(call);
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::handleGroupCallUpdate(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDupdateGroupCallParticipants &update) {
|
||||
}
|
||||
|
||||
void Instance::handleSignalingData(
|
||||
const MTPDupdatePhoneCallSignalingData &data) {
|
||||
if (!_currentCall || !_currentCall->handleSignalingData(data)) {
|
||||
|
@ -298,10 +369,24 @@ void Instance::handleSignalingData(
|
|||
}
|
||||
}
|
||||
|
||||
bool Instance::alreadyInCall() {
|
||||
bool Instance::inCall() const {
|
||||
return (_currentCall && _currentCall->state() != Call::State::Busy);
|
||||
}
|
||||
|
||||
bool Instance::inGroupCall() const {
|
||||
return (_currentGroupCall != nullptr);
|
||||
}
|
||||
|
||||
bool Instance::activateCurrentCall() {
|
||||
if (inCall()) {
|
||||
_currentCallPanel->showAndActivate();
|
||||
return true;
|
||||
} else if (inGroupCall()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Call *Instance::currentCall() const {
|
||||
return _currentCall.get();
|
||||
}
|
||||
|
@ -335,9 +420,12 @@ void Instance::requestPermissionOrFail(Platform::PermissionType type, Fn<void()>
|
|||
}
|
||||
}));
|
||||
} else {
|
||||
if (alreadyInCall()) {
|
||||
if (inCall()) {
|
||||
_currentCall->hangup();
|
||||
}
|
||||
if (inGroupCall()) {
|
||||
//_currentGroupCall->hangup(); // #TODO calls
|
||||
}
|
||||
Ui::show(Box<ConfirmBox>(tr::lng_no_mic_permission(tr::now), tr::lng_menu_settings(tr::now), crl::guard(this, [=] {
|
||||
Platform::OpenSystemSettingsForPermission(type);
|
||||
Ui::hideLayer();
|
||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "mtproto/sender.h"
|
||||
#include "calls/calls_call.h"
|
||||
#include "calls/calls_group_call.h"
|
||||
|
||||
namespace Platform {
|
||||
enum class PermissionType;
|
||||
|
@ -30,6 +31,7 @@ class Panel;
|
|||
|
||||
class Instance
|
||||
: private Call::Delegate
|
||||
, private GroupCall::Delegate
|
||||
, private base::Subscriber
|
||||
, public base::has_weak_ptr {
|
||||
public:
|
||||
|
@ -37,6 +39,10 @@ public:
|
|||
~Instance();
|
||||
|
||||
void startOutgoingCall(not_null<UserData*> user, bool video);
|
||||
void startGroupCall(not_null<ChannelData*> channel);
|
||||
void joinGroupCall(
|
||||
not_null<ChannelData*> channel,
|
||||
const MTPInputGroupCall &call);
|
||||
void handleUpdate(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPUpdate &update);
|
||||
|
@ -48,20 +54,33 @@ public:
|
|||
[[nodiscard]] bool isQuitPrevent();
|
||||
|
||||
private:
|
||||
not_null<Call::Delegate*> getCallDelegate() {
|
||||
[[nodiscard]] not_null<Call::Delegate*> getCallDelegate() {
|
||||
return static_cast<Call::Delegate*>(this);
|
||||
}
|
||||
DhConfig getDhConfig() const override {
|
||||
[[nodiscard]] not_null<GroupCall::Delegate*> getGroupCallDelegate() {
|
||||
return static_cast<GroupCall::Delegate*>(this);
|
||||
}
|
||||
[[nodiscard]] DhConfig getDhConfig() const override {
|
||||
return _dhConfig;
|
||||
}
|
||||
void callFinished(not_null<Call*> call) override;
|
||||
void callFailed(not_null<Call*> call) override;
|
||||
void callRedial(not_null<Call*> call) override;
|
||||
void callRequestPermissionsOrFail(Fn<void()> onSuccess) override {
|
||||
requestPermissionsOrFail(std::move(onSuccess));
|
||||
}
|
||||
|
||||
using Sound = Call::Delegate::Sound;
|
||||
void playSound(Sound sound) override;
|
||||
void createCall(not_null<UserData*> user, Call::Type type, bool video);
|
||||
void destroyCall(not_null<Call*> call);
|
||||
void requestPermissionsOrFail(Fn<void()> onSuccess) override;
|
||||
|
||||
void createGroupCall(
|
||||
not_null<ChannelData*> channel,
|
||||
const MTPInputGroupCall &inputCall);
|
||||
void destroyGroupCall(not_null<GroupCall*> call);
|
||||
|
||||
void requestPermissionsOrFail(Fn<void()> onSuccess);
|
||||
void requestPermissionOrFail(Platform::PermissionType type, Fn<void()> onSuccess);
|
||||
|
||||
void handleSignalingData(const MTPDupdatePhoneCallSignalingData &data);
|
||||
|
@ -70,10 +89,18 @@ private:
|
|||
void refreshServerConfig(not_null<Main::Session*> session);
|
||||
bytes::const_span updateDhConfig(const MTPmessages_DhConfig &data);
|
||||
|
||||
bool alreadyInCall();
|
||||
bool activateCurrentCall();
|
||||
[[nodiscard]] bool inCall() const;
|
||||
[[nodiscard]] bool inGroupCall() const;
|
||||
void handleCallUpdate(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPPhoneCall &call);
|
||||
void handleGroupCallUpdate(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPGroupCall &call);
|
||||
void handleGroupCallUpdate(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDupdateGroupCallParticipants &update);
|
||||
|
||||
DhConfig _dhConfig;
|
||||
|
||||
|
@ -84,8 +111,9 @@ private:
|
|||
std::unique_ptr<Call> _currentCall;
|
||||
rpl::event_stream<Call*> _currentCallChanges;
|
||||
std::unique_ptr<Panel> _currentCallPanel;
|
||||
base::Observable<Call*> _currentCallChanged;
|
||||
base::Observable<FullMsgId> _newServiceMessage;
|
||||
|
||||
std::unique_ptr<GroupCall> _currentGroupCall;
|
||||
rpl::event_stream<GroupCall*> _currentGroupCallChanges;
|
||||
|
||||
std::unique_ptr<Media::Audio::Track> _callConnectingTrack;
|
||||
std::unique_ptr<Media::Audio::Track> _callEndedTrack;
|
||||
|
|
|
@ -86,9 +86,10 @@ struct PeerUpdate {
|
|||
ChannelLinkedChat = (1 << 27),
|
||||
ChannelLocation = (1 << 28),
|
||||
Slowmode = (1 << 29),
|
||||
GroupCall = (1 << 30),
|
||||
|
||||
// For iteration
|
||||
LastUsedBit = (1 << 29),
|
||||
LastUsedBit = (1 << 30),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
|
|
@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_folder.h"
|
||||
#include "data/data_location.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_group_call.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
|
@ -671,6 +672,32 @@ void ChannelData::privateErrorReceived() {
|
|||
}
|
||||
}
|
||||
|
||||
void ChannelData::setCall(const MTPInputGroupCall &call) {
|
||||
call.match([&](const MTPDinputGroupCall &data) {
|
||||
if (_call && _call->id() == data.vid().v) {
|
||||
return;
|
||||
} else if (!_call && !data.vid().v) {
|
||||
return;
|
||||
} else if (!data.vid().v) {
|
||||
clearCall();
|
||||
return;
|
||||
}
|
||||
_call = std::make_unique<Data::GroupCall>(
|
||||
this,
|
||||
data.vid().v,
|
||||
data.vaccess_hash().v);
|
||||
session().changes().peerUpdated(this, UpdateFlag::GroupCall);
|
||||
});
|
||||
}
|
||||
|
||||
void ChannelData::clearCall() {
|
||||
if (!_call) {
|
||||
return;
|
||||
}
|
||||
_call = nullptr;
|
||||
session().changes().peerUpdated(this, UpdateFlag::GroupCall);
|
||||
}
|
||||
|
||||
namespace Data {
|
||||
|
||||
void ApplyMigration(
|
||||
|
@ -702,6 +729,12 @@ void ApplyChannelUpdate(
|
|||
auto canViewMembers = channel->canViewMembers();
|
||||
auto canEditStickers = channel->canEditStickers();
|
||||
|
||||
if (const auto call = update.vcall()) {
|
||||
channel->setCall(*call);
|
||||
} else {
|
||||
channel->clearCall();
|
||||
}
|
||||
|
||||
channel->setFullFlags(update.vflags().v);
|
||||
channel->setUserpicPhoto(update.vchat_photo());
|
||||
if (const auto migratedFrom = update.vmigrated_from_chat_id()) {
|
||||
|
|
|
@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_pts_waiter.h"
|
||||
#include "data/data_location.h"
|
||||
|
||||
namespace Data {
|
||||
class GroupCall;
|
||||
} // namespace Data
|
||||
|
||||
struct ChannelLocation {
|
||||
QString address;
|
||||
Data::LocationPoint point;
|
||||
|
@ -393,6 +397,12 @@ public:
|
|||
[[nodiscard]] QString invitePeekHash() const;
|
||||
void privateErrorReceived();
|
||||
|
||||
[[nodiscard]] Data::GroupCall *call() const {
|
||||
return _call.get();
|
||||
}
|
||||
void setCall(const MTPInputGroupCall &call);
|
||||
void clearCall();
|
||||
|
||||
// Still public data members.
|
||||
uint64 access = 0;
|
||||
|
||||
|
@ -439,6 +449,8 @@ private:
|
|||
QString _inviteLink;
|
||||
std::optional<ChannelData*> _linkedChat;
|
||||
|
||||
std::unique_ptr<Data::GroupCall> _call;
|
||||
|
||||
int _slowmodeSeconds = 0;
|
||||
TimeId _slowmodeLastMessage = 0;
|
||||
|
||||
|
|
115
Telegram/SourceFiles/data/data_group_call.cpp
Normal file
115
Telegram/SourceFiles/data/data_group_call.cpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_group_call.h"
|
||||
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kRequestPerPage = 30;
|
||||
|
||||
} // namespace
|
||||
|
||||
GroupCall::GroupCall(
|
||||
not_null<ChannelData*> channel,
|
||||
uint64 id,
|
||||
uint64 accessHash)
|
||||
: _channel(channel)
|
||||
, _id(id)
|
||||
, _accessHash(accessHash) {
|
||||
}
|
||||
|
||||
uint64 GroupCall::id() const {
|
||||
return _id;
|
||||
}
|
||||
|
||||
MTPInputGroupCall GroupCall::input() const {
|
||||
return MTP_inputGroupCall(MTP_long(_id), MTP_long(_accessHash));
|
||||
}
|
||||
|
||||
auto GroupCall::participants() const
|
||||
-> const std::vector<Participant> & {
|
||||
return _participants;
|
||||
}
|
||||
|
||||
void GroupCall::requestParticipants() {
|
||||
if (_participantsRequestId) {
|
||||
return;
|
||||
} else if (_participants.size() >= _fullCount && _allReceived) {
|
||||
return;
|
||||
}
|
||||
const auto requestFromDate = (_allReceived || _participants.empty())
|
||||
? TimeId(0)
|
||||
: _participants.back().date;
|
||||
auto &api = _channel->session().api();
|
||||
_participantsRequestId = api.request(MTPphone_GetGroupParticipants(
|
||||
input(),
|
||||
MTP_int(requestFromDate),
|
||||
MTP_int(kRequestPerPage)
|
||||
)).done([=](const MTPphone_GroupParticipants &result) {
|
||||
result.match([&](const MTPDphone_groupParticipants &data) {
|
||||
_fullCount = data.vcount().v;
|
||||
_channel->owner().processUsers(data.vusers());
|
||||
for (const auto &p : data.vparticipants().v) {
|
||||
p.match([&](const MTPDgroupCallParticipant &data) {
|
||||
const auto userId = data.vuser_id().v;
|
||||
const auto user = _channel->owner().user(userId);
|
||||
const auto value = Participant{
|
||||
.user = user,
|
||||
.date = data.vdate().v,
|
||||
.source = data.vsource().v,
|
||||
.muted = data.is_muted(),
|
||||
.canSelfUnmute = data.is_can_self_unmute(),
|
||||
.left = data.is_left()
|
||||
};
|
||||
const auto i = ranges::find(
|
||||
_participants,
|
||||
user,
|
||||
&Participant::user);
|
||||
if (i == end(_participants)) {
|
||||
_participants.push_back(value);
|
||||
} else {
|
||||
*i = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!_allReceived
|
||||
&& (data.vparticipants().v.size() < kRequestPerPage)) {
|
||||
_allReceived = true;
|
||||
}
|
||||
if (_allReceived) {
|
||||
_fullCount = _participants.size();
|
||||
}
|
||||
});
|
||||
ranges::sort(_participants, std::greater<>(), &Participant::date);
|
||||
_channel->session().changes().peerUpdated(
|
||||
_channel,
|
||||
PeerUpdate::Flag::GroupCall);
|
||||
}).fail([=](const RPCError &error) {
|
||||
_allReceived = true;
|
||||
_fullCount = _participants.size();
|
||||
_channel->session().changes().peerUpdated(
|
||||
_channel,
|
||||
PeerUpdate::Flag::GroupCall);
|
||||
}).send();
|
||||
}
|
||||
|
||||
int GroupCall::fullCount() const {
|
||||
return _fullCount;
|
||||
}
|
||||
|
||||
bool GroupCall::participantsLoaded() const {
|
||||
return _allReceived;
|
||||
}
|
||||
|
||||
} // namespace Data
|
54
Telegram/SourceFiles/data/data_group_call.h
Normal file
54
Telegram/SourceFiles/data/data_group_call.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class UserData;
|
||||
class ChannelData;
|
||||
|
||||
namespace Data {
|
||||
|
||||
class GroupCall final {
|
||||
public:
|
||||
GroupCall(not_null<ChannelData*> channel, uint64 id, uint64 accessHash);
|
||||
|
||||
[[nodiscard]] uint64 id() const;
|
||||
[[nodiscard]] MTPInputGroupCall input() const;
|
||||
|
||||
struct Participant {
|
||||
not_null<UserData*> user;
|
||||
TimeId date = 0;
|
||||
int source = 0;
|
||||
bool muted = false;
|
||||
bool canSelfUnmute = false;
|
||||
bool left = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] auto participants() const
|
||||
-> const std::vector<Participant> &;
|
||||
void requestParticipants();
|
||||
[[nodiscard]] bool participantsLoaded() const;
|
||||
|
||||
[[nodiscard]] int fullCount() const;
|
||||
|
||||
private:
|
||||
const not_null<ChannelData*> _channel;
|
||||
const uint64 _id = 0;
|
||||
const uint64 _accessHash = 0;
|
||||
|
||||
int _version = 0;
|
||||
UserId _adminId = 0;
|
||||
uint64 _reflectorId = 0;
|
||||
mtpRequestId _participantsRequestId = 0;
|
||||
|
||||
std::vector<Participant> _participants;
|
||||
int _fullCount = 0;
|
||||
bool _allReceived = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
|
@ -55,6 +55,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_scheduled_messages.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_group_call.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
|
@ -74,6 +75,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/view/history_view_pinned_tracker.h"
|
||||
#include "history/view/history_view_pinned_section.h"
|
||||
#include "history/view/history_view_pinned_bar.h"
|
||||
#include "history/view/history_view_group_call_tracker.h"
|
||||
#include "history/view/media/history_view_media.h"
|
||||
#include "profile/profile_block_group_members.h"
|
||||
#include "info/info_memento.h"
|
||||
|
@ -100,6 +102,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "apiwrap.h"
|
||||
#include "base/qthelp_regex.h"
|
||||
#include "ui/chat/pinned_bar.h"
|
||||
#include "ui/chat/group_call_bar.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/item_text_options.h"
|
||||
#include "ui/unread_badge.h"
|
||||
|
@ -118,6 +121,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "support/support_common.h"
|
||||
#include "support/support_autocomplete.h"
|
||||
#include "dialogs/dialogs_key.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "facades.h"
|
||||
#include "app.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
@ -1775,6 +1779,8 @@ void HistoryWidget::showHistory(
|
|||
destroyUnreadBarOnClose();
|
||||
_pinnedBar = nullptr;
|
||||
_pinnedTracker = nullptr;
|
||||
_groupCallBar = nullptr;
|
||||
_groupCallTracker = nullptr;
|
||||
_membersDropdown.destroy();
|
||||
_scrollToAnimation.stop();
|
||||
|
||||
|
@ -1886,6 +1892,7 @@ void HistoryWidget::showHistory(
|
|||
_updateHistoryItems.cancel();
|
||||
|
||||
setupPinnedTracker();
|
||||
setupGroupCallTracker();
|
||||
if (_history->scrollTopItem
|
||||
|| (_migrated && _migrated->scrollTopItem)
|
||||
|| _history->isReadyFor(_showAtMsgId)) {
|
||||
|
@ -2093,6 +2100,9 @@ void HistoryWidget::updateControlsVisibility() {
|
|||
if (_pinnedBar) {
|
||||
_pinnedBar->show();
|
||||
}
|
||||
if (_groupCallBar) {
|
||||
_groupCallBar->show();
|
||||
}
|
||||
if (_firstLoadRequest && !_scroll->isHidden()) {
|
||||
_scroll->hide();
|
||||
} else if (!_firstLoadRequest && _scroll->isHidden()) {
|
||||
|
@ -3037,6 +3047,9 @@ void HistoryWidget::hideChildWidgets() {
|
|||
if (_pinnedBar) {
|
||||
_pinnedBar->hide();
|
||||
}
|
||||
if (_groupCallBar) {
|
||||
_groupCallBar->hide();
|
||||
}
|
||||
if (_voiceRecordBar) {
|
||||
_voiceRecordBar->hideFast();
|
||||
}
|
||||
|
@ -3250,6 +3263,9 @@ void HistoryWidget::showAnimated(
|
|||
if (_pinnedBar) {
|
||||
_pinnedBar->finishAnimating();
|
||||
}
|
||||
if (_groupCallBar) {
|
||||
_groupCallBar->finishAnimating();
|
||||
}
|
||||
_topShadow->setVisible(params.withTopBarShadow ? false : true);
|
||||
_preserveScrollTop = false;
|
||||
|
||||
|
@ -3278,6 +3294,9 @@ void HistoryWidget::animationCallback() {
|
|||
if (_pinnedBar) {
|
||||
_pinnedBar->finishAnimating();
|
||||
}
|
||||
if (_groupCallBar) {
|
||||
_groupCallBar->finishAnimating();
|
||||
}
|
||||
_cacheUnder = _cacheOver = QPixmap();
|
||||
doneShow();
|
||||
synteticScrollToY(_scroll->scrollTop());
|
||||
|
@ -3301,6 +3320,9 @@ void HistoryWidget::doneShow() {
|
|||
if (_pinnedBar) {
|
||||
_pinnedBar->finishAnimating();
|
||||
}
|
||||
if (_groupCallBar) {
|
||||
_groupCallBar->finishAnimating();
|
||||
}
|
||||
checkHistoryActivation();
|
||||
App::wnd()->setInnerFocus();
|
||||
_preserveScrollTop = false;
|
||||
|
@ -4409,7 +4431,12 @@ void HistoryWidget::updateControlsGeometry() {
|
|||
_pinnedBar->move(0, pinnedBarTop);
|
||||
_pinnedBar->resizeToWidth(width());
|
||||
}
|
||||
const auto contactStatusTop = pinnedBarTop + (_pinnedBar ? _pinnedBar->height() : 0);
|
||||
const auto groupCallTop = pinnedBarTop + (_pinnedBar ? _pinnedBar->height() : 0);
|
||||
if (_groupCallBar) {
|
||||
_groupCallBar->move(0, groupCallTop);
|
||||
_groupCallBar->resizeToWidth(width());
|
||||
}
|
||||
const auto contactStatusTop = groupCallTop + (_groupCallBar ? _groupCallBar->height() : 0);
|
||||
if (_contactStatus) {
|
||||
_contactStatus->move(0, contactStatusTop);
|
||||
}
|
||||
|
@ -4572,6 +4599,9 @@ void HistoryWidget::updateHistoryGeometry(
|
|||
if (_pinnedBar) {
|
||||
newScrollHeight -= _pinnedBar->height();
|
||||
}
|
||||
if (_groupCallBar) {
|
||||
newScrollHeight -= _groupCallBar->height();
|
||||
}
|
||||
if (_contactStatus) {
|
||||
newScrollHeight -= _contactStatus->height();
|
||||
}
|
||||
|
@ -4804,6 +4834,7 @@ int HistoryWidget::computeMaxFieldHeight() const {
|
|||
- _topBar->height()
|
||||
- (_contactStatus ? _contactStatus->height() : 0)
|
||||
- (_pinnedBar ? _pinnedBar->height() : 0)
|
||||
- (_groupCallBar ? _groupCallBar->height() : 0)
|
||||
- ((_editMsgId
|
||||
|| replyToId()
|
||||
|| readyToForward()
|
||||
|
@ -5009,6 +5040,7 @@ void HistoryWidget::handlePeerMigration() {
|
|||
_migrated = _history->migrateFrom();
|
||||
_list->notifyMigrateUpdated();
|
||||
setupPinnedTracker();
|
||||
setupGroupCallTracker();
|
||||
updateHistoryGeometry();
|
||||
}
|
||||
const auto from = chat->owner().historyLoaded(chat);
|
||||
|
@ -5341,6 +5373,68 @@ void HistoryWidget::refreshPinnedBarButton(bool many) {
|
|||
_pinnedBar->setRightButton(std::move(button));
|
||||
}
|
||||
|
||||
void HistoryWidget::setupGroupCallTracker() {
|
||||
Expects(_history != nullptr);
|
||||
|
||||
const auto channel = _history->peer->asChannel();
|
||||
if (!channel) {
|
||||
_groupCallTracker = nullptr;
|
||||
_groupCallBar = nullptr;
|
||||
return;
|
||||
}
|
||||
_groupCallTracker = std::make_unique<HistoryView::GroupCallTracker>(
|
||||
channel);
|
||||
_groupCallBar = std::make_unique<Ui::GroupCallBar>(
|
||||
this,
|
||||
_groupCallTracker->content());
|
||||
|
||||
rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
base::ObservableViewer(Adaptive::Changed())
|
||||
) | rpl::map([] {
|
||||
return Adaptive::OneColumn();
|
||||
}) | rpl::start_with_next([=](bool one) {
|
||||
_groupCallBar->setShadowGeometryPostprocess([=](QRect geometry) {
|
||||
if (!one) {
|
||||
geometry.setLeft(geometry.left() + st::lineWidth);
|
||||
}
|
||||
return geometry;
|
||||
});
|
||||
}, _groupCallBar->lifetime());
|
||||
|
||||
rpl::merge(
|
||||
_groupCallBar->barClicks(),
|
||||
_groupCallBar->joinClicks()
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto channel = _history->peer->asChannel();
|
||||
if (!channel) {
|
||||
return;
|
||||
}
|
||||
const auto call = channel->call();
|
||||
if (!call) {
|
||||
return;
|
||||
}
|
||||
Core::App().calls().joinGroupCall(channel, call->input());
|
||||
}, _groupCallBar->lifetime());
|
||||
|
||||
_groupCallBarHeight = 0;
|
||||
_groupCallBar->heightValue(
|
||||
) | rpl::start_with_next([=](int height) {
|
||||
_topDelta = _preserveScrollTop ? 0 : (height - _groupCallBarHeight);
|
||||
_groupCallBarHeight = height;
|
||||
updateHistoryGeometry();
|
||||
updateControlsGeometry();
|
||||
_topDelta = 0;
|
||||
}, _groupCallBar->lifetime());
|
||||
|
||||
orderWidgets();
|
||||
|
||||
if (_a_show.animating()) {
|
||||
_groupCallBar->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryWidget::requestMessageData(MsgId msgId) {
|
||||
const auto callback = [=](ChannelData *channel, MsgId msgId) {
|
||||
messageDataReceived(channel, msgId);
|
||||
|
|
|
@ -61,6 +61,7 @@ class FlatButton;
|
|||
class LinkButton;
|
||||
class RoundButton;
|
||||
class PinnedBar;
|
||||
class GroupCallBar;
|
||||
struct PreparedList;
|
||||
class SendFilesWay;
|
||||
namespace Toast {
|
||||
|
@ -90,6 +91,7 @@ class TopBarWidget;
|
|||
class ContactStatus;
|
||||
class Element;
|
||||
class PinnedTracker;
|
||||
class GroupCallTracker;
|
||||
namespace Controls {
|
||||
class RecordLock;
|
||||
class VoiceRecordBar;
|
||||
|
@ -475,6 +477,8 @@ private:
|
|||
int wasScrollTop,
|
||||
int nowScrollTop);
|
||||
|
||||
void setupGroupCallTracker();
|
||||
|
||||
void sendInlineResult(InlineBots::ResultSelected result);
|
||||
|
||||
void drawField(Painter &p, const QRect &rect);
|
||||
|
@ -591,10 +595,15 @@ private:
|
|||
std::unique_ptr<HistoryView::PinnedTracker> _pinnedTracker;
|
||||
std::unique_ptr<Ui::PinnedBar> _pinnedBar;
|
||||
int _pinnedBarHeight = 0;
|
||||
bool _preserveScrollTop = false;
|
||||
FullMsgId _pinnedClickedId;
|
||||
std::optional<FullMsgId> _minPinnedId;
|
||||
|
||||
std::unique_ptr<HistoryView::GroupCallTracker> _groupCallTracker;
|
||||
std::unique_ptr<Ui::GroupCallBar> _groupCallBar;
|
||||
int _groupCallBarHeight = 0;
|
||||
|
||||
bool _preserveScrollTop = false;
|
||||
|
||||
mtpRequestId _saveEditMsgRequestId = 0;
|
||||
|
||||
QStringList _parsedLinks;
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "history/view/history_view_group_call_tracker.h"
|
||||
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_group_call.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/chat/group_call_bar.h"
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
GroupCallTracker::GroupCallTracker(not_null<ChannelData*> channel)
|
||||
: _channel(channel) {
|
||||
}
|
||||
|
||||
rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::content() const {
|
||||
const auto channel = _channel;
|
||||
return channel->session().changes().peerFlagsValue(
|
||||
channel,
|
||||
Data::PeerUpdate::Flag::GroupCall
|
||||
) | rpl::map([=]() -> Ui::GroupCallBarContent {
|
||||
const auto call = channel->call();
|
||||
if (!call) {
|
||||
return { .shown = false };
|
||||
} else if (!call->fullCount() && !call->participantsLoaded()) {
|
||||
call->requestParticipants();
|
||||
}
|
||||
return { .count = call->fullCount(), .shown = true };
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<> GroupCallTracker::joinClicks() const {
|
||||
return _joinClicks.events();
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
namespace Ui {
|
||||
struct GroupCallBarContent;
|
||||
} // namespace Ui
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
class GroupCallTracker final {
|
||||
public:
|
||||
GroupCallTracker(not_null<ChannelData*> channel);
|
||||
|
||||
[[nodiscard]] rpl::producer<Ui::GroupCallBarContent> content() const;
|
||||
[[nodiscard]] rpl::producer<> joinClicks() const;
|
||||
|
||||
private:
|
||||
not_null<ChannelData*> _channel;
|
||||
|
||||
rpl::event_stream<> _joinClicks;
|
||||
|
||||
};
|
||||
|
||||
} // namespace HistoryView
|
|
@ -200,6 +200,8 @@ void TopBarWidget::onCall() {
|
|||
if (const auto peer = _activeChat.key.peer()) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
Core::App().calls().startOutgoingCall(user, false);
|
||||
} else if (const auto megagroup = peer->asMegagroup()) {
|
||||
Core::App().calls().startGroupCall(megagroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -691,6 +693,8 @@ void TopBarWidget::updateControlsVisibility() {
|
|||
if (const auto user = peer->asUser()) {
|
||||
return session().serverConfig().phoneCallsEnabled.current()
|
||||
&& user->hasCalls();
|
||||
} else if (const auto megagroup = peer->asMegagroup()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
|
182
Telegram/SourceFiles/ui/chat/group_call_bar.cpp
Normal file
182
Telegram/SourceFiles/ui/chat/group_call_bar.cpp
Normal file
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "ui/chat/group_call_bar.h"
|
||||
|
||||
#include "ui/chat/message_bar.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/palette.h"
|
||||
|
||||
#include <QtGui/QtEvents>
|
||||
|
||||
namespace Ui {
|
||||
|
||||
GroupCallBar::GroupCallBar(
|
||||
not_null<QWidget*> parent,
|
||||
rpl::producer<GroupCallBarContent> content)
|
||||
: _wrap(parent, object_ptr<RpWidget>(parent))
|
||||
, _inner(_wrap.entity())
|
||||
, _shadow(std::make_unique<PlainShadow>(_wrap.parentWidget())) {
|
||||
_wrap.hide(anim::type::instant);
|
||||
_shadow->hide();
|
||||
|
||||
_wrap.entity()->paintRequest(
|
||||
) | rpl::start_with_next([=](QRect clip) {
|
||||
QPainter(_wrap.entity()).fillRect(clip, st::historyPinnedBg);
|
||||
}, lifetime());
|
||||
_wrap.setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
|
||||
auto copy = std::move(
|
||||
content
|
||||
) | rpl::start_spawning(_wrap.lifetime());
|
||||
|
||||
rpl::duplicate(
|
||||
copy
|
||||
) | rpl::start_with_next([=](GroupCallBarContent &&content) {
|
||||
_content = content;
|
||||
_inner->update();
|
||||
}, lifetime());
|
||||
|
||||
std::move(
|
||||
copy
|
||||
) | rpl::map([=](const GroupCallBarContent &content) {
|
||||
return !content.shown;
|
||||
}) | rpl::start_with_next_done([=](bool hidden) {
|
||||
_shouldBeShown = !hidden;
|
||||
if (!_forceHidden) {
|
||||
_wrap.toggle(_shouldBeShown, anim::type::normal);
|
||||
}
|
||||
}, [=] {
|
||||
_forceHidden = true;
|
||||
_wrap.toggle(false, anim::type::normal);
|
||||
}, lifetime());
|
||||
|
||||
setupInner();
|
||||
}
|
||||
|
||||
GroupCallBar::~GroupCallBar() {
|
||||
}
|
||||
|
||||
void GroupCallBar::setupInner() {
|
||||
_inner->resize(0, st::historyReplyHeight);
|
||||
_inner->paintRequest(
|
||||
) | rpl::start_with_next([=](QRect rect) {
|
||||
auto p = Painter(_inner);
|
||||
paint(p);
|
||||
}, _inner->lifetime());
|
||||
|
||||
|
||||
// Clicks.
|
||||
_inner->setCursor(style::cur_pointer);
|
||||
_inner->events(
|
||||
) | rpl::filter([=](not_null<QEvent*> event) {
|
||||
return (event->type() == QEvent::MouseButtonPress);
|
||||
}) | rpl::map([=] {
|
||||
return _inner->events(
|
||||
) | rpl::filter([=](not_null<QEvent*> event) {
|
||||
return (event->type() == QEvent::MouseButtonRelease);
|
||||
}) | rpl::take(1) | rpl::filter([=](not_null<QEvent*> event) {
|
||||
return _inner->rect().contains(
|
||||
static_cast<QMouseEvent*>(event.get())->pos());
|
||||
});
|
||||
}) | rpl::flatten_latest(
|
||||
) | rpl::map([] {
|
||||
return rpl::empty_value();
|
||||
}) | rpl::start_to_stream(_barClicks, _inner->lifetime());
|
||||
}
|
||||
|
||||
void GroupCallBar::paint(Painter &p) {
|
||||
p.fillRect(_inner->rect(), st::historyComposeAreaBg);
|
||||
p.setPen(st::defaultMessageBar.textFg);
|
||||
p.setFont(st::defaultMessageBar.text.font);
|
||||
p.drawText(_inner->rect(), "Voice Chat", style::al_center);
|
||||
}
|
||||
|
||||
void GroupCallBar::updateControlsGeometry(QRect wrapGeometry) {
|
||||
_inner->resizeToWidth(wrapGeometry.width());
|
||||
const auto hidden = _wrap.isHidden() || !wrapGeometry.height();
|
||||
if (_shadow->isHidden() != hidden) {
|
||||
_shadow->setVisible(!hidden);
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCallBar::setShadowGeometryPostprocess(Fn<QRect(QRect)> postprocess) {
|
||||
_shadowGeometryPostprocess = std::move(postprocess);
|
||||
updateShadowGeometry(_wrap.geometry());
|
||||
}
|
||||
|
||||
void GroupCallBar::updateShadowGeometry(QRect wrapGeometry) {
|
||||
const auto regular = QRect(
|
||||
wrapGeometry.x(),
|
||||
wrapGeometry.y() + wrapGeometry.height(),
|
||||
wrapGeometry.width(),
|
||||
st::lineWidth);
|
||||
_shadow->setGeometry(_shadowGeometryPostprocess
|
||||
? _shadowGeometryPostprocess(regular)
|
||||
: regular);
|
||||
}
|
||||
|
||||
void GroupCallBar::show() {
|
||||
if (!_forceHidden) {
|
||||
return;
|
||||
}
|
||||
_forceHidden = false;
|
||||
if (_shouldBeShown) {
|
||||
_wrap.show(anim::type::instant);
|
||||
_shadow->show();
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCallBar::hide() {
|
||||
if (_forceHidden) {
|
||||
return;
|
||||
}
|
||||
_forceHidden = true;
|
||||
_wrap.hide(anim::type::instant);
|
||||
_shadow->hide();
|
||||
}
|
||||
|
||||
void GroupCallBar::raise() {
|
||||
_wrap.raise();
|
||||
_shadow->raise();
|
||||
}
|
||||
|
||||
void GroupCallBar::finishAnimating() {
|
||||
_wrap.finishAnimating();
|
||||
}
|
||||
|
||||
void GroupCallBar::move(int x, int y) {
|
||||
_wrap.move(x, y);
|
||||
}
|
||||
|
||||
void GroupCallBar::resizeToWidth(int width) {
|
||||
_wrap.entity()->resizeToWidth(width);
|
||||
}
|
||||
|
||||
int GroupCallBar::height() const {
|
||||
return !_forceHidden
|
||||
? _wrap.height()
|
||||
: _shouldBeShown
|
||||
? st::historyReplyHeight
|
||||
: 0;
|
||||
}
|
||||
|
||||
rpl::producer<int> GroupCallBar::heightValue() const {
|
||||
return _wrap.heightValue();
|
||||
}
|
||||
|
||||
rpl::producer<> GroupCallBar::barClicks() const {
|
||||
return _barClicks.events();
|
||||
}
|
||||
|
||||
rpl::producer<> GroupCallBar::joinClicks() const {
|
||||
return rpl::never<>();
|
||||
}
|
||||
|
||||
} // namespace Ui
|
69
Telegram/SourceFiles/ui/chat/group_call_bar.h
Normal file
69
Telegram/SourceFiles/ui/chat/group_call_bar.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "base/object_ptr.h"
|
||||
|
||||
class Painter;
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class PlainShadow;
|
||||
|
||||
struct GroupCallBarContent {
|
||||
int count = 0;
|
||||
bool shown = false;
|
||||
bool joined = false;
|
||||
// #TODO calls userpics
|
||||
};
|
||||
|
||||
class GroupCallBar final {
|
||||
public:
|
||||
GroupCallBar(
|
||||
not_null<QWidget*> parent,
|
||||
rpl::producer<GroupCallBarContent> content);
|
||||
~GroupCallBar();
|
||||
|
||||
void show();
|
||||
void hide();
|
||||
void raise();
|
||||
void finishAnimating();
|
||||
|
||||
void setShadowGeometryPostprocess(Fn<QRect(QRect)> postprocess);
|
||||
|
||||
void move(int x, int y);
|
||||
void resizeToWidth(int width);
|
||||
[[nodiscard]] int height() const;
|
||||
[[nodiscard]] rpl::producer<int> heightValue() const;
|
||||
[[nodiscard]] rpl::producer<> barClicks() const;
|
||||
[[nodiscard]] rpl::producer<> joinClicks() const;
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _wrap.lifetime();
|
||||
}
|
||||
|
||||
private:
|
||||
void updateShadowGeometry(QRect wrapGeometry);
|
||||
void updateControlsGeometry(QRect wrapGeometry);
|
||||
void setupInner();
|
||||
void paint(Painter &p);
|
||||
|
||||
Ui::SlideWrap<> _wrap;
|
||||
not_null<Ui::RpWidget*> _inner;
|
||||
std::unique_ptr<Ui::PlainShadow> _shadow;
|
||||
rpl::event_stream<> _barClicks;
|
||||
Fn<QRect(QRect)> _shadowGeometryPostprocess;
|
||||
bool _shouldBeShown = false;
|
||||
bool _forceHidden = false;
|
||||
|
||||
GroupCallBarContent _content;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
2
Telegram/ThirdParty/tgcalls
vendored
2
Telegram/ThirdParty/tgcalls
vendored
|
@ -1 +1 @@
|
|||
Subproject commit e8e9dd8934fdea47b2b2b0dadf2100fc3c17c914
|
||||
Subproject commit 176e5fff55478f23deec25c3bbcdaf9d0f3e19b2
|
|
@ -53,6 +53,9 @@ if (NOT DESKTOP_APP_DISABLE_WEBRTC_INTEGRATION)
|
|||
VideoCaptureInterfaceImpl.h
|
||||
VideoCapturerInterface.h
|
||||
|
||||
group/GroupInstanceImpl.cpp
|
||||
group/GroupInstanceImpl.h
|
||||
|
||||
platform/PlatformInterface.h
|
||||
|
||||
# Android
|
||||
|
|
|
@ -78,6 +78,8 @@ PRIVATE
|
|||
ui/chat/attach/attach_single_file_preview.h
|
||||
ui/chat/attach/attach_single_media_preview.cpp
|
||||
ui/chat/attach/attach_single_media_preview.h
|
||||
ui/chat/group_call_bar.cpp
|
||||
ui/chat/group_call_bar.h
|
||||
ui/chat/message_bar.cpp
|
||||
ui/chat/message_bar.h
|
||||
ui/chat/pinned_bar.cpp
|
||||
|
|
Loading…
Add table
Reference in a new issue