Merge official 8.7.0

This commit is contained in:
luvletter2333 2022-04-26 14:38:28 +08:00
commit fa99e892bc
No known key found for this signature in database
GPG Key ID: A26A8880836E1978
317 changed files with 24657 additions and 4101 deletions

View File

@ -3,15 +3,15 @@ import cn.hutool.core.util.RuntimeUtil
apply plugin: "com.android.application"
apply plugin: "kotlin-android"
def verName = "8.6.2"
def verCode = 620
def verName = "8.7.2-preview01"
def verCode = 625
if (System.getenv("DEBUG_BUILD") == "true") {
verName += "-" + RuntimeUtil.execForStr("git log --pretty=format:'%h' -n 1").trim()
}
def officialVer = "8.6.2"
def officialCode = 2600
def officialVer = "8.7.2"
def officialCode = 2634
def serviceAccountCredentialsFile = rootProject.file("service_account_credentials.json")

View File

@ -458,6 +458,10 @@ add_library(tgcalls STATIC
voip/tgcalls/v2/NativeNetworkingImpl.cpp
voip/tgcalls/v2/Signaling.cpp
voip/tgcalls/v2/SignalingEncryption.cpp
voip/tgcalls/v2/ContentNegotiation.cpp
voip/tgcalls/v2/InstanceV2ReferenceImpl.cpp
voip/tgcalls/v2_4_0_0/InstanceV2_4_0_0Impl.cpp
voip/tgcalls/v2_4_0_0/Signaling_4_0_0.cpp
voip/webrtc/rtc_base/bitstream_reader.cc
voip/webrtc/rtc_base/async_invoker.cc
voip/webrtc/rtc_base/system_time.cc

View File

@ -24,12 +24,16 @@
#include "libtgvoip/os/android/JNIUtilities.h"
#include "tgcalls/VideoCaptureInterface.h"
#include "tgcalls/v2/InstanceV2Impl.h"
#include "tgcalls/v2_4_0_0/InstanceV2_4_0_0Impl.h"
#include "tgcalls/v2/InstanceV2ReferenceImpl.h"
using namespace tgcalls;
const auto RegisterTag = Register<InstanceImpl>();
const auto RegisterTagLegacy = Register<InstanceImplLegacy>();
const auto RegisterTagV2 = Register<InstanceV2Impl>();
const auto RegisterTagV2_4_0_0 = Register<InstanceV2_4_0_0Impl>();
const auto RegisterTagV2_4_0_1 = Register<InstanceV2Impl>();
const auto RegisterTagV2_4_1_2 = Register<InstanceV2ReferenceImpl>();
jclass TrafficStatsClass;
jclass FingerprintClass;

View File

@ -33,6 +33,7 @@ constexpr auto kServiceCauseResend = 2;
static constexpr uint8_t kAckId = uint8_t(-1);
static constexpr uint8_t kEmptyId = uint8_t(-2);
static constexpr uint8_t kCustomId = uint8_t(127);
void AppendSeq(rtc::CopyOnWriteBuffer &buffer, uint32_t seq) {
const auto bytes = rtc::HostToNetwork32(seq);
@ -68,6 +69,22 @@ bool ConstTimeIsDifferent(const void *a, const void *b, size_t size) {
return different;
}
rtc::CopyOnWriteBuffer SerializeRawMessageWithSeq(
const rtc::CopyOnWriteBuffer &message,
uint32_t seq,
bool singleMessagePacket) {
rtc::ByteBufferWriter writer;
writer.WriteUInt32(seq);
writer.WriteUInt8(kCustomId);
writer.WriteUInt32((uint32_t)message.size());
writer.WriteBytes((const char *)message.data(), message.size());
auto result = rtc::CopyOnWriteBuffer();
result.AppendData(writer.Data(), writer.Length());
return result;
}
} // namespace
EncryptedConnection::EncryptedConnection(
@ -142,7 +159,7 @@ auto EncryptedConnection::prepareForSending(const Message &message)
const auto messageRequiresAck = absl::visit([](const auto &data) {
return std::decay_t<decltype(data)>::kRequiresAck;
}, message.data);
// If message requires ack, then we can't serialize it as a single
// message packet, because later it may be sent as a part of big packet.
const auto singleMessagePacket = !haveAdditionalMessages() && !messageRequiresAck;
@ -152,6 +169,25 @@ auto EncryptedConnection::prepareForSending(const Message &message)
}
const auto seq = *maybeSeq;
auto serialized = SerializeMessageWithSeq(message, seq, singleMessagePacket);
return prepareForSendingMessageInternal(serialized, seq, messageRequiresAck);
}
absl::optional<EncryptedConnection::EncryptedPacket> EncryptedConnection::prepareForSendingRawMessage(rtc::CopyOnWriteBuffer &message, bool messageRequiresAck) {
// If message requires ack, then we can't serialize it as a single
// message packet, because later it may be sent as a part of big packet.
const auto singleMessagePacket = !haveAdditionalMessages() && !messageRequiresAck;
const auto maybeSeq = computeNextSeq(messageRequiresAck, singleMessagePacket);
if (!maybeSeq) {
return absl::nullopt;
}
const auto seq = *maybeSeq;
auto serialized = SerializeRawMessageWithSeq(message, seq, singleMessagePacket);
return prepareForSendingMessageInternal(serialized, seq, messageRequiresAck);
}
absl::optional<EncryptedConnection::EncryptedPacket> EncryptedConnection::prepareForSendingMessageInternal(rtc::CopyOnWriteBuffer &serialized, uint32_t seq, bool messageRequiresAck) {
if (!enoughSpaceInPacket(serialized, 0)) {
return LogError("Too large packet: ", std::to_string(serialized.size()));
}
@ -404,6 +440,41 @@ auto EncryptedConnection::handleIncomingPacket(const char *bytes, size_t size)
return processPacket(decryptionBuffer, incomingSeq);
}
absl::optional<EncryptedConnection::DecryptedRawPacket> EncryptedConnection::handleIncomingRawPacket(const char *bytes, size_t size) {
if (size < 21 || size > kMaxIncomingPacketSize) {
return LogError("Bad incoming packet size: ", std::to_string(size));
}
const auto x = (_key.isOutgoing ? 8 : 0) + (_type == Type::Signaling ? 128 : 0);
const auto key = _key.value->data();
const auto msgKey = reinterpret_cast<const uint8_t*>(bytes);
const auto encryptedData = msgKey + 16;
const auto dataSize = size - 16;
auto aesKeyIv = PrepareAesKeyIv(key, msgKey, x);
auto decryptionBuffer = rtc::Buffer(dataSize);
AesProcessCtr(
MemorySpan{ encryptedData, dataSize },
decryptionBuffer.data(),
std::move(aesKeyIv));
const auto msgKeyLarge = ConcatSHA256(
MemorySpan{ key + 88 + x, 32 },
MemorySpan{ decryptionBuffer.data(), decryptionBuffer.size() });
if (ConstTimeIsDifferent(msgKeyLarge.data() + 8, msgKey, 16)) {
return LogError("Bad incoming data hash.");
}
const auto incomingSeq = ReadSeq(decryptionBuffer.data());
const auto incomingCounter = CounterFromSeq(incomingSeq);
if (!registerIncomingCounter(incomingCounter)) {
// We've received that packet already.
return LogError("Already handled packet received.", std::to_string(incomingCounter));
}
return processRawPacket(decryptionBuffer, incomingSeq);
}
auto EncryptedConnection::processPacket(
const rtc::Buffer &fullBuffer,
uint32_t packetSeq)
@ -490,6 +561,98 @@ auto EncryptedConnection::processPacket(
return result;
}
auto EncryptedConnection::processRawPacket(
const rtc::Buffer &fullBuffer,
uint32_t packetSeq)
-> absl::optional<DecryptedRawPacket> {
assert(fullBuffer.size() >= 5);
auto additionalMessage = false;
auto firstMessageRequiringAck = true;
auto newRequiringAckReceived = false;
auto currentSeq = packetSeq;
auto currentCounter = CounterFromSeq(currentSeq);
rtc::ByteBufferReader reader(
reinterpret_cast<const char*>(fullBuffer.data() + 4), // Skip seq.
fullBuffer.size() - 4);
auto result = absl::optional<DecryptedRawPacket>();
while (true) {
const auto type = uint8_t(*reader.Data());
const auto singleMessagePacket = ((currentSeq & kSingleMessagePacketSeqBit) != 0);
if (singleMessagePacket && additionalMessage) {
return LogError("Single message packet bit in not first message.");
}
if (type == kEmptyId) {
if (additionalMessage) {
return LogError("Empty message should be only the first one in the packet.");
}
RTC_LOG(LS_INFO) << logHeader()
<< "Got RECV:empty" << "#" << currentCounter;
reader.Consume(1);
} else if (type == kAckId) {
if (!additionalMessage) {
return LogError("Ack message must not be the first one in the packet.");
}
ackMyMessage(currentSeq);
reader.Consume(1);
} else if (type == kCustomId) {
reader.Consume(1);
if (auto message = DeserializeRawMessage(reader, singleMessagePacket)) {
const auto messageRequiresAck = ((currentSeq & kMessageRequiresAckSeqBit) != 0);
const auto skipMessage = messageRequiresAck
? !registerSentAck(currentCounter, firstMessageRequiringAck)
: (additionalMessage && !registerIncomingCounter(currentCounter));
if (messageRequiresAck) {
firstMessageRequiringAck = false;
if (!skipMessage) {
newRequiringAckReceived = true;
}
sendAckPostponed(currentSeq);
RTC_LOG(LS_INFO) << logHeader()
<< (skipMessage ? "Repeated RECV:type" : "Got RECV:type") << type << "#" << currentCounter;
}
if (!skipMessage) {
appendReceivedRawMessage(result, std::move(*message), currentSeq);
}
} else {
return LogError("Could not parse message from packet, type: ", std::to_string(type));
}
} else {
return LogError("Could not parse message from packet, type: ", std::to_string(type));
}
if (!reader.Length()) {
break;
} else if (singleMessagePacket) {
return LogError("Single message didn't fill the entire packet.");
} else if (reader.Length() < 5) {
return LogError("Bad remaining data size: ", std::to_string(reader.Length()));
}
const auto success = reader.ReadUInt32(&currentSeq);
assert(success);
(void)success;
currentCounter = CounterFromSeq(currentSeq);
additionalMessage = true;
}
if (!_acksToSendSeqs.empty()) {
if (newRequiringAckReceived) {
_requestSendService(0, 0);
} else if (!_sendAcksTimerActive) {
_sendAcksTimerActive = true;
_requestSendService(
_delayIntervals.maxDelayBeforeAckResend,
kServiceCauseAcks);
}
}
return result;
}
void EncryptedConnection::appendReceivedMessage(
absl::optional<DecryptedPacket> &to,
Message &&message,
@ -505,6 +668,21 @@ void EncryptedConnection::appendReceivedMessage(
}
}
void EncryptedConnection::appendReceivedRawMessage(
absl::optional<DecryptedRawPacket> &to,
rtc::CopyOnWriteBuffer &&message,
uint32_t incomingSeq) {
auto decrypted = DecryptedRawMessage{
std::move(message),
CounterFromSeq(incomingSeq)
};
if (to) {
to->additional.push_back(std::move(decrypted));
} else {
to = DecryptedRawPacket{ std::move(decrypted) };
}
}
const char *EncryptedConnection::logHeader() const {
return (_type == Type::Signaling) ? "(signaling) " : "(transport) ";
}

View File

@ -26,13 +26,19 @@ public:
uint32_t counter = 0;
};
absl::optional<EncryptedPacket> prepareForSending(const Message &message);
absl::optional<EncryptedPacket> prepareForSendingRawMessage(rtc::CopyOnWriteBuffer &serialized, bool messageRequiresAck);
absl::optional<EncryptedPacket> prepareForSendingService(int cause);
struct DecryptedPacket {
DecryptedMessage main;
std::vector<DecryptedMessage> additional;
};
struct DecryptedRawPacket {
DecryptedRawMessage main;
std::vector<DecryptedRawMessage> additional;
};
absl::optional<DecryptedPacket> handleIncomingPacket(const char *bytes, size_t size);
absl::optional<DecryptedRawPacket> handleIncomingRawPacket(const char *bytes, size_t size);
absl::optional<rtc::CopyOnWriteBuffer> encryptRawPacket(rtc::CopyOnWriteBuffer const &buffer);
absl::optional<rtc::CopyOnWriteBuffer> decryptRawPacket(rtc::CopyOnWriteBuffer const &buffer);
@ -57,6 +63,7 @@ private:
EncryptedPacket encryptPrepared(const rtc::CopyOnWriteBuffer &buffer);
bool registerIncomingCounter(uint32_t incomingCounter);
absl::optional<DecryptedPacket> processPacket(const rtc::Buffer &fullBuffer, uint32_t packetSeq);
absl::optional<DecryptedRawPacket> processRawPacket(const rtc::Buffer &fullBuffer, uint32_t packetSeq);
bool registerSentAck(uint32_t counter, bool firstInPacket);
void ackMyMessage(uint32_t counter);
void sendAckPostponed(uint32_t incomingSeq);
@ -66,6 +73,11 @@ private:
absl::optional<DecryptedPacket> &to,
Message &&message,
uint32_t incomingSeq);
void appendReceivedRawMessage(
absl::optional<DecryptedRawPacket> &to,
rtc::CopyOnWriteBuffer &&message,
uint32_t incomingSeq);
absl::optional<EncryptedPacket> prepareForSendingMessageInternal(rtc::CopyOnWriteBuffer &serialized, uint32_t seq, bool messageRequiresAck);
const char *logHeader() const;

View File

@ -215,6 +215,7 @@ template <typename Implementation>
bool Register();
struct Descriptor {
std::string version;
Config config;
PersistentState persistentState;
std::vector<Endpoint> endpoints;

View File

@ -408,7 +408,7 @@ _platformContext(platformContext) {
rtc::scoped_refptr<webrtc::AudioDeviceModule> MediaManager::createAudioDeviceModule() {
const auto create = [&](webrtc::AudioDeviceModule::AudioLayer layer) {
#ifdef WEBRTC_IOS
return rtc::make_ref_counted<webrtc::tgcalls_ios_adm::AudioDeviceModuleIOS>(false, false);
return rtc::make_ref_counted<webrtc::tgcalls_ios_adm::AudioDeviceModuleIOS>(false, false, 1);
#else
return webrtc::AudioDeviceModule::Create(
layer,

View File

@ -378,4 +378,29 @@ absl::optional<Message> DeserializeMessage(
: absl::nullopt;
}
absl::optional<rtc::CopyOnWriteBuffer> DeserializeRawMessage(
rtc::ByteBufferReader &reader,
bool singleMessagePacket) {
if (!reader.Length()) {
return absl::nullopt;
}
uint32_t length = 0;
if (!reader.ReadUInt32(&length)) {
return absl::nullopt;
}
if (length < 0 || length > 1024 * 1024) {
return absl::nullopt;
}
rtc::CopyOnWriteBuffer result;
result.SetSize(length);
if (!reader.ReadBytes((char *)result.MutableData(), result.size())) {
return absl::nullopt;
}
return result;
}
} // namespace tgcalls

View File

@ -18,13 +18,14 @@ enum class AudioState;
struct PeerIceParameters {
std::string ufrag;
std::string pwd;
bool supportsRenomination = false;
PeerIceParameters() = default;
PeerIceParameters(const PeerIceParameters &other) = default;
PeerIceParameters(std::string ufrag_, std::string pwd_) :
PeerIceParameters(std::string ufrag_, std::string pwd_, bool supportsRenomination_) :
ufrag(ufrag_),
pwd(pwd_) {
pwd(pwd_),
supportsRenomination(supportsRenomination_) {
}
};
@ -127,12 +128,21 @@ rtc::CopyOnWriteBuffer SerializeMessageWithSeq(
absl::optional<Message> DeserializeMessage(
rtc::ByteBufferReader &reader,
bool singleMessagePacket);
absl::optional<rtc::CopyOnWriteBuffer> DeserializeRawMessage(
rtc::ByteBufferReader &reader,
bool singleMessagePacket);
struct DecryptedMessage {
Message message;
uint32_t counter = 0;
};
struct DecryptedRawMessage {
rtc::CopyOnWriteBuffer message;
uint32_t counter = 0;
};
} // namespace tgcalls
#endif

View File

@ -88,7 +88,7 @@ _isOutgoing(encryptionKey.isOutgoing),
_stateUpdated(std::move(stateUpdated)),
_transportMessageReceived(std::move(transportMessageReceived)),
_sendSignalingMessage(std::move(sendSignalingMessage)),
_localIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH)) {
_localIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH), false) {
assert(_thread->IsCurrent());
_networkMonitorFactory = PlatformInterface::SharedInstance()->createNetworkMonitorFactory();
@ -104,6 +104,7 @@ NetworkManager::~NetworkManager() {
_portAllocator.reset();
_networkManager.reset();
_socketFactory.reset();
_networkMonitorFactory.reset();
}
void NetworkManager::start() {
@ -206,7 +207,7 @@ void NetworkManager::receiveSignalingMessage(DecryptedMessage &&message) {
assert(list != nullptr);
if (!_remoteIceParameters.has_value()) {
PeerIceParameters parameters(list->iceParameters.ufrag, list->iceParameters.pwd);
PeerIceParameters parameters(list->iceParameters.ufrag, list->iceParameters.pwd, false);
_remoteIceParameters = parameters;
cricket::IceParameters remoteIceParameters(

View File

@ -61,16 +61,20 @@ public:
explicit ThreadsImpl(size_t i) {
auto suffix = i == 0 ? "" : "#" + std::to_string(i);
media_ = create("tgc-media" + suffix);
//worker_ = create("tgc-work" + suffix);
worker_ = create_network("tgc-work" + suffix);
//network_ = create_network("tgc-net" + suffix);
worker_ = create("tgc-work" + suffix);
network_ = create_network("tgc-net" + suffix);
media_->AllowInvokesToThread(worker_.get());
media_->AllowInvokesToThread(network_.get());
worker_->AllowInvokesToThread(network_.get());
//network_->DisallowAllInvokes();
//worker_->DisallowAllInvokes();
//worker_->AllowInvokesToThread(network_.get());
}
rtc::Thread *getNetworkThread() override {
return worker_.get();
return network_.get();
}
rtc::Thread *getMediaThread() override {
return media_.get();
@ -90,7 +94,7 @@ public:
}
private:
//Thread network_;
Thread network_;
Thread media_;
Thread worker_;
rtc::scoped_refptr<webrtc::SharedModuleThread> shared_module_thread_;

View File

@ -104,6 +104,9 @@ _avIoContext(std::move(fileData)) {
_frame = av_frame_alloc();
#if LIBAVFORMAT_VERSION_MAJOR >= 59
const
#endif
AVInputFormat *inputFormat = av_find_input_format(container.c_str());
if (!inputFormat) {
_didReadToEnd = true;
@ -144,7 +147,7 @@ _avIoContext(std::move(fileData)) {
_streamId = i;
_durationInMilliseconds = (int)((inStream->duration + inStream->first_dts) * 1000 / 48000);
_durationInMilliseconds = (int)(inStream->duration * av_q2d(inStream->time_base) * 1000);
if (inStream->metadata) {
AVDictionaryEntry *entry = av_dict_get(inStream->metadata, "TG_META", nullptr, 0);

View File

@ -32,7 +32,7 @@ public:
AudioStreamingPartPersistentDecoderState(AVCodecParameters const *codecParameters, AVRational timeBase) :
_codecParameters(codecParameters),
_timeBase(timeBase) {
AVCodec *codec = avcodec_find_decoder(codecParameters->codec_id);
const AVCodec *codec = avcodec_find_decoder(codecParameters->codec_id);
if (codec) {
_codecContext = avcodec_alloc_context3(codec);
int ret = avcodec_parameters_to_context(_codecContext, codecParameters);

View File

@ -1526,7 +1526,13 @@ public:
std::unique_ptr<AudioCapturePostProcessor> audioProcessor = nullptr;
#endif
if (_videoContentType != VideoContentType::Screencast) {
PlatformInterface::SharedInstance()->configurePlatformAudio();
int numChannels = 1;
#ifdef WEBRTC_IOS
if (_disableAudioInput) {
numChannels = 2;
}
#endif
PlatformInterface::SharedInstance()->configurePlatformAudio(numChannels);
#if USE_RNNOISE
audioProcessor = std::make_unique<AudioCapturePostProcessor>([weak, threads = _threads](GroupLevelValue const &level) {
@ -3310,7 +3316,7 @@ private:
#endif
const auto create = [&](webrtc::AudioDeviceModule::AudioLayer layer) {
#ifdef WEBRTC_IOS
return rtc::make_ref_counted<webrtc::tgcalls_ios_adm::AudioDeviceModuleIOS>(false, disableRecording);
return rtc::make_ref_counted<webrtc::tgcalls_ios_adm::AudioDeviceModuleIOS>(false, disableRecording, disableRecording ? 2 : 1);
#else
return webrtc::AudioDeviceModule::Create(
layer,

View File

@ -318,7 +318,7 @@ _dataChannelMessageReceived(dataChannelMessageReceived),
_audioActivityUpdated(audioActivityUpdated) {
assert(_threads->getNetworkThread()->IsCurrent());
_localIceParameters = PeerIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH));
_localIceParameters = PeerIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH), false);
_localCertificate = rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(rtc::KT_ECDSA), absl::nullopt);
@ -452,7 +452,7 @@ void GroupNetworkManager::stop() {
_transportChannel.reset();
_portAllocator.reset();
_localIceParameters = PeerIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH));
_localIceParameters = PeerIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH), false);
_localCertificate = rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(rtc::KT_ECDSA), absl::nullopt);

View File

@ -423,6 +423,27 @@ public:
if (numChannels == 1) {
frameOut.UpdateFrame(0, audioChannels[0].pcmData.data(), audioChannels[0].pcmData.size(), 48000, webrtc::AudioFrame::SpeechType::kNormalSpeech, webrtc::AudioFrame::VADActivity::kVadActive, numChannels);
} else if (numChannels == _audioRingBufferNumChannels) {
bool skipFrame = false;
int numSamples = (int)audioChannels[0].pcmData.size();
for (int i = 1; i < numChannels; i++) {
if (audioChannels[i].pcmData.size() != numSamples) {
skipFrame = true;
break;
}
}
if (skipFrame) {
break;
}
if (_stereoShuffleBuffer.size() < numChannels * numSamples) {
_stereoShuffleBuffer.resize(numChannels * numSamples);
}
for (int i = 0; i < numSamples; i++) {
for (int j = 0; j < numChannels; j++) {
_stereoShuffleBuffer[i * numChannels + j] = audioChannels[j].pcmData[i];
}
}
frameOut.UpdateFrame(0, _stereoShuffleBuffer.data(), numSamples, 48000, webrtc::AudioFrame::SpeechType::kNormalSpeech, webrtc::AudioFrame::VADActivity::kVadActive, numChannels);
} else {
bool skipFrame = false;
int numSamples = (int)audioChannels[0].pcmData.size();
@ -483,6 +504,9 @@ public:
RTC_LOG(LS_INFO) << "render: discarding video frames at the end of a segment (displayed " << segment->video[0]->_displayedFrames << " frames)";
}
}
if (!segment->unified.empty() && segment->unified[0]->videoPart->hasRemainingFrames()) {
RTC_LOG(LS_INFO) << "render: discarding video frames at the end of a segment (displayed " << segment->unified[0]->_displayedFrames << " frames)";
}
_availableSegments.erase(_availableSegments.begin());
}

View File

@ -87,20 +87,16 @@ public:
return _frame;
}
double pts(AVStream *stream) {
double pts(AVStream *stream, double &firstFramePts) {
int64_t framePts = _frame->pts;
double spf = av_q2d(stream->time_base);
return ((double)framePts) * spf;
}
double duration(AVStream *stream) {
int64_t frameDuration = _frame->pkt_duration;
double spf = av_q2d(stream->time_base);
if (frameDuration != 0) {
return ((double)frameDuration) * spf;
} else {
return spf;
double value = ((double)framePts) * spf;
if (firstFramePts < 0.0) {
firstFramePts = value;
}
return value - firstFramePts;
}
private:
@ -280,6 +276,9 @@ public:
int ret = 0;
#if LIBAVFORMAT_VERSION_MAJOR >= 59
const
#endif
AVInputFormat *inputFormat = av_find_input_format(container.c_str());
if (!inputFormat) {
_didReadToEnd = true;
@ -323,7 +322,7 @@ public:
}
if (videoCodecParameters && videoStream) {
AVCodec *codec = avcodec_find_decoder(videoCodecParameters->codec_id);
const AVCodec *codec = avcodec_find_decoder(videoCodecParameters->codec_id);
if (codec) {
_codecContext = avcodec_alloc_context3(codec);
ret = avcodec_parameters_to_context(_codecContext, videoCodecParameters);
@ -410,7 +409,7 @@ public:
.set_rotation(_rotation)
.build();
return VideoStreamingPartFrame(_endpointId, videoFrame, _frame.pts(_videoStream), _frame.duration(_videoStream), _frameIndex);
return VideoStreamingPartFrame(_endpointId, videoFrame, _frame.pts(_videoStream, _firstFramePts), _frameIndex);
} else {
return absl::nullopt;
}
@ -490,6 +489,7 @@ private:
std::vector<VideoStreamingPartFrame> _finalFrames;
int _frameIndex = 0;
double _firstFramePts = -1.0;
bool _didReadToEnd = false;
};
@ -566,25 +566,33 @@ public:
absl::optional<VideoStreamingPartFrame> getFrameAtRelativeTimestamp(double timestamp) {
while (true) {
if (!_currentFrame) {
while (_availableFrames.size() >= 2) {
if (timestamp >= _availableFrames[1].pts) {
_availableFrames.erase(_availableFrames.begin());
} else {
break;
}
}
if (_availableFrames.size() < 2) {
if (!_parsedVideoParts.empty()) {
auto result = _parsedVideoParts[0]->getNextFrame();
if (result) {
_currentFrame = result;
_relativeTimestamp += result->duration;
_availableFrames.push_back(result.value());
} else {
_parsedVideoParts.erase(_parsedVideoParts.begin());
continue;
}
continue;
}
}
if (_currentFrame) {
if (timestamp <= _relativeTimestamp) {
return _currentFrame;
} else {
_currentFrame = absl::nullopt;
if (!_availableFrames.empty()) {
for (size_t i = 1; i < _availableFrames.size(); i++) {
if (timestamp < _availableFrames[i].pts) {
return _availableFrames[i - 1];
}
}
return _availableFrames[_availableFrames.size() - 1];
} else {
return absl::nullopt;
}
@ -598,6 +606,10 @@ public:
return absl::nullopt;
}
}
bool hasRemainingFrames() const {
return !_parsedVideoParts.empty();
}
int getAudioRemainingMilliseconds() {
while (!_parsedAudioParts.empty()) {
@ -626,8 +638,7 @@ public:
private:
absl::optional<VideoStreamInfo> _videoStreamInfo;
std::vector<std::unique_ptr<VideoStreamingPartInternal>> _parsedVideoParts;
absl::optional<VideoStreamingPartFrame> _currentFrame;
double _relativeTimestamp = 0.0;
std::vector<VideoStreamingPartFrame> _availableFrames;
std::vector<std::unique_ptr<AudioStreamingPart>> _parsedAudioParts;
};
@ -656,6 +667,12 @@ absl::optional<std::string> VideoStreamingPart::getActiveEndpointId() const {
: absl::nullopt;
}
bool VideoStreamingPart::hasRemainingFrames() const {
return _state
? _state->hasRemainingFrames()
: false;
}
int VideoStreamingPart::getAudioRemainingMilliseconds() {
return _state
? _state->getAudioRemainingMilliseconds()

View File

@ -19,14 +19,12 @@ struct VideoStreamingPartFrame {
std::string endpointId;
webrtc::VideoFrame frame;
double pts = 0;
double duration = 0.0;
int index = 0;
VideoStreamingPartFrame(std::string endpointId_, webrtc::VideoFrame const &frame_, double pts_, double duration_, int index_) :
VideoStreamingPartFrame(std::string endpointId_, webrtc::VideoFrame const &frame_, double pts_, int index_) :
endpointId(endpointId_),
frame(frame_),
pts(pts_),
duration(duration_),
index(index_) {
}
};
@ -52,6 +50,7 @@ public:
absl::optional<VideoStreamingPartFrame> getFrameAtRelativeTimestamp(double timestamp);
absl::optional<std::string> getActiveEndpointId() const;
bool hasRemainingFrames() const;
int getAudioRemainingMilliseconds();
std::vector<AudioStreamingPart::StreamingPartChannel> getAudio10msPerChannel(AudioStreamingPartPersistentDecoder &persistentDecoder);

View File

@ -297,7 +297,7 @@ public:
static PlatformInterface *SharedInstance();
virtual ~PlatformInterface() = default;
virtual void configurePlatformAudio() {
virtual void configurePlatformAudio(int numChannels = 1) {
}
virtual std::unique_ptr<rtc::NetworkMonitorFactory> createNetworkMonitorFactory() {

View File

@ -23,7 +23,7 @@
namespace tgcalls {
void AndroidInterface::configurePlatformAudio() {
void AndroidInterface::configurePlatformAudio(int numChannels) {
}

View File

@ -9,7 +9,7 @@ namespace tgcalls {
class AndroidInterface : public PlatformInterface {
public:
void configurePlatformAudio() override;
void configurePlatformAudio(int numChannels = 1) override;
std::unique_ptr<webrtc::VideoEncoderFactory> makeVideoEncoderFactory(std::shared_ptr<PlatformContext> platformContext, bool preferHardwareEncoding = false, bool isScreencast = false) override;
std::unique_ptr<webrtc::VideoDecoderFactory> makeVideoDecoderFactory(std::shared_ptr<PlatformContext> platformContext) override;
bool supportsEncoding(const std::string &codecName, std::shared_ptr<PlatformContext> platformContext) override;

View File

@ -0,0 +1,696 @@
#include "v2/ContentNegotiation.h"
#include "rtc_base/rtc_certificate_generator.h"
#include <sstream>
namespace tgcalls {
namespace {
signaling::MediaContent convertContentInfoToSingalingContent(cricket::ContentInfo const &content) {
signaling::MediaContent mappedContent;
switch (content.media_description()->type()) {
case cricket::MediaType::MEDIA_TYPE_AUDIO: {
mappedContent.type = signaling::MediaContent::Type::Audio;
for (const auto &codec : content.media_description()->as_audio()->codecs()) {
signaling::PayloadType mappedPayloadType;
mappedPayloadType.id = codec.id;
mappedPayloadType.name = codec.name;
mappedPayloadType.clockrate = codec.clockrate;
mappedPayloadType.channels = (uint32_t)codec.channels;
for (const auto &feedbackType : codec.feedback_params.params()) {
signaling::FeedbackType mappedFeedbackType;
mappedFeedbackType.type = feedbackType.id();
mappedFeedbackType.subtype = feedbackType.param();
mappedPayloadType.feedbackTypes.push_back(std::move(mappedFeedbackType));
}
for (const auto &parameter : codec.params) {
mappedPayloadType.parameters.push_back(std::make_pair(parameter.first, parameter.second));
}
std::sort(mappedPayloadType.parameters.begin(), mappedPayloadType.parameters.end(), [](std::pair<std::string, std::string> const &lhs, std::pair<std::string, std::string> const &rhs) -> bool {
return lhs.first < rhs.first;
});
mappedContent.payloadTypes.push_back(std::move(mappedPayloadType));
}
break;
}
case cricket::MediaType::MEDIA_TYPE_VIDEO: {
mappedContent.type = signaling::MediaContent::Type::Video;
for (const auto &codec : content.media_description()->as_video()->codecs()) {
signaling::PayloadType mappedPayloadType;
mappedPayloadType.id = codec.id;
mappedPayloadType.name = codec.name;
mappedPayloadType.clockrate = codec.clockrate;
mappedPayloadType.channels = 0;
for (const auto &feedbackType : codec.feedback_params.params()) {
signaling::FeedbackType mappedFeedbackType;
mappedFeedbackType.type = feedbackType.id();
mappedFeedbackType.subtype = feedbackType.param();
mappedPayloadType.feedbackTypes.push_back(std::move(mappedFeedbackType));
}
for (const auto &parameter : codec.params) {
mappedPayloadType.parameters.push_back(std::make_pair(parameter.first, parameter.second));
}
std::sort(mappedPayloadType.parameters.begin(), mappedPayloadType.parameters.end(), [](std::pair<std::string, std::string> const &lhs, std::pair<std::string, std::string> const &rhs) -> bool {
return lhs.first < rhs.first;
});
mappedContent.payloadTypes.push_back(std::move(mappedPayloadType));
}
break;
}
default: {
RTC_FATAL() << "Unknown media type";
break;
}
}
if (!content.media_description()->streams().empty()) {
mappedContent.ssrc = content.media_description()->streams()[0].first_ssrc();
for (const auto &ssrcGroup : content.media_description()->streams()[0].ssrc_groups) {
signaling::SsrcGroup mappedSsrcGroup;
mappedSsrcGroup.semantics = ssrcGroup.semantics;
mappedSsrcGroup.ssrcs = ssrcGroup.ssrcs;
mappedContent.ssrcGroups.push_back(std::move(mappedSsrcGroup));
}
}
for (const auto &extension : content.media_description()->rtp_header_extensions()) {
mappedContent.rtpExtensions.push_back(extension);
}
return mappedContent;
}
cricket::ContentInfo convertSingalingContentToContentInfo(std::string const &contentId, signaling::MediaContent const &content, webrtc::RtpTransceiverDirection direction) {
std::unique_ptr<cricket::MediaContentDescription> contentDescription;
switch (content.type) {
case signaling::MediaContent::Type::Audio: {
auto audioDescription = std::make_unique<cricket::AudioContentDescription>();
for (const auto &payloadType : content.payloadTypes) {
cricket::AudioCodec mappedCodec((int)payloadType.id, payloadType.name, (int)payloadType.clockrate, 0, payloadType.channels);
for (const auto &parameter : payloadType.parameters) {
mappedCodec.params.insert(parameter);
}
for (const auto &feedbackParam : payloadType.feedbackTypes) {
mappedCodec.AddFeedbackParam(cricket::FeedbackParam(feedbackParam.type, feedbackParam.subtype));
}
audioDescription->AddCodec(mappedCodec);
}
contentDescription = std::move(audioDescription);
break;
}
case signaling::MediaContent::Type::Video: {
auto videoDescription = std::make_unique<cricket::VideoContentDescription>();
for (const auto &payloadType : content.payloadTypes) {
cricket::VideoCodec mappedCodec((int)payloadType.id, payloadType.name);
for (const auto &parameter : payloadType.parameters) {
mappedCodec.params.insert(parameter);
}
for (const auto &feedbackParam : payloadType.feedbackTypes) {
mappedCodec.AddFeedbackParam(cricket::FeedbackParam(feedbackParam.type, feedbackParam.subtype));
}
videoDescription->AddCodec(mappedCodec);
}
contentDescription = std::move(videoDescription);
break;
}
default: {
RTC_FATAL() << "Unknown media type";
break;
}
}
cricket::StreamParams streamParams;
streamParams.id = contentId;
streamParams.set_stream_ids({ contentId });
streamParams.add_ssrc(content.ssrc);
for (const auto &ssrcGroup : content.ssrcGroups) {
streamParams.ssrc_groups.push_back(cricket::SsrcGroup(ssrcGroup.semantics, ssrcGroup.ssrcs));
for (const auto &ssrc : ssrcGroup.ssrcs) {
if (!streamParams.has_ssrc(ssrc)) {
streamParams.add_ssrc(ssrc);
}
}
}
contentDescription->AddStream(streamParams);
for (const auto &extension : content.rtpExtensions) {
contentDescription->AddRtpHeaderExtension(extension);
}
contentDescription->set_direction(direction);
contentDescription->set_rtcp_mux(true);
cricket::ContentInfo mappedContentInfo(cricket::MediaProtocolType::kRtp);
mappedContentInfo.name = contentId;
mappedContentInfo.rejected = false;
mappedContentInfo.bundle_only = false;
mappedContentInfo.set_media_description(std::move(contentDescription));
return mappedContentInfo;
}
cricket::ContentInfo createInactiveContentInfo(std::string const &contentId) {
std::unique_ptr<cricket::MediaContentDescription> contentDescription;
auto audioDescription = std::make_unique<cricket::AudioContentDescription>();
contentDescription = std::move(audioDescription);
contentDescription->set_direction(webrtc::RtpTransceiverDirection::kInactive);
contentDescription->set_rtcp_mux(true);
cricket::ContentInfo mappedContentInfo(cricket::MediaProtocolType::kRtp);
mappedContentInfo.name = contentId;
mappedContentInfo.rejected = false;
mappedContentInfo.bundle_only = false;
mappedContentInfo.set_media_description(std::move(contentDescription));
return mappedContentInfo;
}
std::string contentIdBySsrc(uint32_t ssrc) {
std::ostringstream contentIdString;
contentIdString << ssrc;
return contentIdString.str();
}
}
ContentNegotiationContext::ContentNegotiationContext(bool isOutgoing, rtc::UniqueRandomIdGenerator *uniqueRandomIdGenerator) :
_isOutgoing(isOutgoing),
_uniqueRandomIdGenerator(uniqueRandomIdGenerator) {
_transportDescriptionFactory = std::make_unique<cricket::TransportDescriptionFactory>();
// tempCertificate is only used to fill in the local SDP
auto tempCertificate = rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(rtc::KT_ECDSA), absl::nullopt);
_transportDescriptionFactory->set_secure(cricket::SecurePolicy::SEC_REQUIRED);
_transportDescriptionFactory->set_certificate(tempCertificate);
_sessionDescriptionFactory = std::make_unique<cricket::MediaSessionDescriptionFactory>(_transportDescriptionFactory.get(), uniqueRandomIdGenerator);
_needNegotiation = true;
}
ContentNegotiationContext::~ContentNegotiationContext() {
}
void ContentNegotiationContext::copyCodecsFromChannelManager(cricket::ChannelManager *channelManager, bool randomize) {
cricket::AudioCodecs audioSendCodecs;
cricket::AudioCodecs audioRecvCodecs;
cricket::VideoCodecs videoSendCodecs;
cricket::VideoCodecs videoRecvCodecs;
channelManager->GetSupportedAudioSendCodecs(&audioSendCodecs);
channelManager->GetSupportedAudioReceiveCodecs(&audioRecvCodecs);
channelManager->GetSupportedVideoSendCodecs(&videoSendCodecs);
channelManager->GetSupportedVideoReceiveCodecs(&videoRecvCodecs);
for (const auto &codec : audioSendCodecs) {
if (codec.name == "opus") {
audioSendCodecs = { codec };
audioRecvCodecs = { codec };
break;
}
}
if (randomize) {
for (auto &codec : audioSendCodecs) {
codec.id += 3;
}
for (auto &codec : videoSendCodecs) {
codec.id += 3;
}
for (auto &codec : audioRecvCodecs) {
codec.id += 3;
}
for (auto &codec : videoRecvCodecs) {
codec.id += 3;
}
}
_sessionDescriptionFactory->set_audio_codecs(audioSendCodecs, audioRecvCodecs);
_sessionDescriptionFactory->set_video_codecs(videoSendCodecs, videoRecvCodecs);
int absSendTimeUriId = 2;
int transportSequenceNumberUriId = 3;
int videoRotationUri = 13;
if (randomize) {
absSendTimeUriId = 3;
transportSequenceNumberUriId = 2;
videoRotationUri = 4;
}
_rtpAudioExtensions.emplace_back(webrtc::RtpExtension::kAbsSendTimeUri, absSendTimeUriId);
_rtpAudioExtensions.emplace_back(webrtc::RtpExtension::kTransportSequenceNumberUri, transportSequenceNumberUriId);
_rtpVideoExtensions.emplace_back(webrtc::RtpExtension::kAbsSendTimeUri, absSendTimeUriId);
_rtpVideoExtensions.emplace_back(webrtc::RtpExtension::kTransportSequenceNumberUri, transportSequenceNumberUriId);
_rtpVideoExtensions.emplace_back(webrtc::RtpExtension::kVideoRotationUri, videoRotationUri);
}
std::string ContentNegotiationContext::addOutgoingChannel(signaling::MediaContent::Type mediaType) {
std::string channelId = takeNextOutgoingChannelId();
cricket::MediaType mappedMediaType;
std::vector<webrtc::RtpHeaderExtensionCapability> rtpExtensions;
switch (mediaType) {
case signaling::MediaContent::Type::Audio: {
mappedMediaType = cricket::MediaType::MEDIA_TYPE_AUDIO;
rtpExtensions = _rtpAudioExtensions;
break;
}
case signaling::MediaContent::Type::Video: {
mappedMediaType = cricket::MediaType::MEDIA_TYPE_VIDEO;
rtpExtensions = _rtpVideoExtensions;
break;
}
default: {
RTC_FATAL() << "Unknown media type";
break;
}
}
cricket::MediaDescriptionOptions offerDescription(mappedMediaType, channelId, webrtc::RtpTransceiverDirection::kSendOnly, false);
offerDescription.header_extensions = rtpExtensions;
switch (mediaType) {
case signaling::MediaContent::Type::Audio: {
offerDescription.AddAudioSender(channelId, { channelId });
break;
}
case signaling::MediaContent::Type::Video: {
cricket::SimulcastLayerList simulcastLayers;
offerDescription.AddVideoSender(channelId, { channelId }, {}, simulcastLayers, 1);
break;
}
default: {
RTC_FATAL() << "Unknown media type";
break;
}
}
_outgoingChannelDescriptions.emplace_back(std::move(offerDescription));
_needNegotiation = true;
return channelId;
}
void ContentNegotiationContext::removeOutgoingChannel(std::string const &id) {
for (size_t i = 0; i < _outgoingChannels.size(); i++) {
if (_outgoingChannelDescriptions[i].description.mid == id) {
_outgoingChannelDescriptions.erase(_outgoingChannelDescriptions.begin() + i);
_needNegotiation = true;
break;
}
}
}
std::unique_ptr<cricket::SessionDescription> ContentNegotiationContext::currentSessionDescriptionFromCoordinatedState() {
if (_channelIdOrder.empty()) {
return nullptr;
}
auto sessionDescription = std::make_unique<cricket::SessionDescription>();
for (const auto &id : _channelIdOrder) {
bool found = false;
for (const auto &channel : _incomingChannels) {
if (contentIdBySsrc(channel.ssrc) == id) {
found = true;
auto mappedContent = convertSingalingContentToContentInfo(contentIdBySsrc(channel.ssrc), channel, webrtc::RtpTransceiverDirection::kRecvOnly);
cricket::TransportDescription transportDescription;
cricket::TransportInfo transportInfo(contentIdBySsrc(channel.ssrc), transportDescription);
sessionDescription->AddTransportInfo(transportInfo);
sessionDescription->AddContent(std::move(mappedContent));
break;
}
}
for (const auto &channel : _outgoingChannels) {
if (channel.id == id) {
found = true;
auto mappedContent = convertSingalingContentToContentInfo(channel.id, channel.content, webrtc::RtpTransceiverDirection::kSendOnly);
cricket::TransportDescription transportDescription;
cricket::TransportInfo transportInfo(mappedContent.name, transportDescription);
sessionDescription->AddTransportInfo(transportInfo);
sessionDescription->AddContent(std::move(mappedContent));
break;
}
}
if (!found) {
auto mappedContent = createInactiveContentInfo("_" + id);
cricket::TransportDescription transportDescription;
cricket::TransportInfo transportInfo(mappedContent.name, transportDescription);
sessionDescription->AddTransportInfo(transportInfo);
sessionDescription->AddContent(std::move(mappedContent));
}
}
return sessionDescription;
}
static cricket::MediaDescriptionOptions getIncomingContentDescription(signaling::MediaContent const &content) {
auto mappedContent = convertSingalingContentToContentInfo(contentIdBySsrc(content.ssrc), content, webrtc::RtpTransceiverDirection::kSendOnly);
cricket::MediaDescriptionOptions contentDescription(mappedContent.media_description()->type(), mappedContent.name, webrtc::RtpTransceiverDirection::kRecvOnly, false);
for (const auto &extension : mappedContent.media_description()->rtp_header_extensions()) {
contentDescription.header_extensions.emplace_back(extension.uri, extension.id);
}
return contentDescription;
}
std::unique_ptr<ContentNegotiationContext::NegotiationContents> ContentNegotiationContext::getPendingOffer() {
if (!_needNegotiation) {
return nullptr;
}
if (_pendingOutgoingOffer) {
return nullptr;
}
_pendingOutgoingOffer = std::make_unique<PendingOutgoingOffer>();
_pendingOutgoingOffer->exchangeId = _uniqueRandomIdGenerator->GenerateId();
auto currentSessionDescription = currentSessionDescriptionFromCoordinatedState();
cricket::MediaSessionOptions offerOptions;
offerOptions.offer_extmap_allow_mixed = true;
offerOptions.bundle_enabled = true;
for (const auto &id : _channelIdOrder) {
bool found = false;
for (const auto &channel : _outgoingChannelDescriptions) {
if (channel.description.mid == id) {
found = true;
offerOptions.media_description_options.push_back(channel.description);
break;
}
}
for (const auto &content : _incomingChannels) {
if (contentIdBySsrc(content.ssrc) == id) {
found = true;
offerOptions.media_description_options.push_back(getIncomingContentDescription(content));
break;
}
}
if (!found) {
cricket::MediaDescriptionOptions contentDescription(cricket::MediaType::MEDIA_TYPE_AUDIO, "_" + id, webrtc::RtpTransceiverDirection::kInactive, false);
offerOptions.media_description_options.push_back(contentDescription);
}
}
for (const auto &channel : _outgoingChannelDescriptions) {
if (std::find(_channelIdOrder.begin(), _channelIdOrder.end(), channel.description.mid) == _channelIdOrder.end()) {
_channelIdOrder.push_back(channel.description.mid);
offerOptions.media_description_options.push_back(channel.description);
}
for (const auto &content : _incomingChannels) {
if (std::find(_channelIdOrder.begin(), _channelIdOrder.end(), contentIdBySsrc(content.ssrc)) == _channelIdOrder.end()) {
_channelIdOrder.push_back(contentIdBySsrc(content.ssrc));
offerOptions.media_description_options.push_back(getIncomingContentDescription(content));
}
}
}
std::unique_ptr<cricket::SessionDescription> offer = _sessionDescriptionFactory->CreateOffer(offerOptions, currentSessionDescription.get());
auto mappedOffer = std::make_unique<ContentNegotiationContext::NegotiationContents>();
mappedOffer->exchangeId = _pendingOutgoingOffer->exchangeId;
for (const auto &content : offer->contents()) {
auto mappedContent = convertContentInfoToSingalingContent(content);
if (content.media_description()->direction() == webrtc::RtpTransceiverDirection::kSendOnly) {
mappedOffer->contents.push_back(std::move(mappedContent));
for (auto &channel : _outgoingChannelDescriptions) {
if (channel.description.mid == content.mid()) {
channel.ssrc = mappedContent.ssrc;
channel.ssrcGroups = mappedContent.ssrcGroups;
}
}
}
}
return mappedOffer;
}
std::unique_ptr<ContentNegotiationContext::NegotiationContents> ContentNegotiationContext::setRemoteNegotiationContent(std::unique_ptr<NegotiationContents> &&remoteNegotiationContent) {
if (!remoteNegotiationContent) {
return nullptr;
}
if (_pendingOutgoingOffer) {
if (remoteNegotiationContent->exchangeId == _pendingOutgoingOffer->exchangeId) {
setAnswer(std::move(remoteNegotiationContent));
return nullptr;
} else {
// race condition detected — call initiator wins
if (!_isOutgoing) {
_pendingOutgoingOffer.reset();
return getAnswer(std::move(remoteNegotiationContent));
} else {
return nullptr;
}
}
} else {
return getAnswer(std::move(remoteNegotiationContent));
}
}
std::unique_ptr<ContentNegotiationContext::NegotiationContents> ContentNegotiationContext::getAnswer(std::unique_ptr<ContentNegotiationContext::NegotiationContents> &&offer) {
auto currentSessionDescription = currentSessionDescriptionFromCoordinatedState();
auto mappedOffer = std::make_unique<cricket::SessionDescription>();
cricket::MediaSessionOptions answerOptions;
answerOptions.offer_extmap_allow_mixed = true;
answerOptions.bundle_enabled = true;
for (const auto &id : _channelIdOrder) {
bool found = false;
for (const auto &channel : _outgoingChannels) {
if (channel.id == id) {
found = true;
auto mappedContent = convertSingalingContentToContentInfo(channel.id, channel.content, webrtc::RtpTransceiverDirection::kRecvOnly);
cricket::MediaDescriptionOptions contentDescription(mappedContent.media_description()->type(), mappedContent.name, webrtc::RtpTransceiverDirection::kSendOnly, false);
for (const auto &extension : mappedContent.media_description()->rtp_header_extensions()) {
contentDescription.header_extensions.emplace_back(extension.uri, extension.id);
}
answerOptions.media_description_options.push_back(contentDescription);
cricket::TransportDescription transportDescription;
cricket::TransportInfo transportInfo(channel.id, transportDescription);
mappedOffer->AddTransportInfo(transportInfo);
mappedOffer->AddContent(std::move(mappedContent));
break;
}
}
for (const auto &content : offer->contents) {
if (contentIdBySsrc(content.ssrc) == id) {
found = true;
auto mappedContent = convertSingalingContentToContentInfo(contentIdBySsrc(content.ssrc), content, webrtc::RtpTransceiverDirection::kSendOnly);
cricket::MediaDescriptionOptions contentDescription(mappedContent.media_description()->type(), mappedContent.name, webrtc::RtpTransceiverDirection::kRecvOnly, false);
for (const auto &extension : mappedContent.media_description()->rtp_header_extensions()) {
contentDescription.header_extensions.emplace_back(extension.uri, extension.id);
}
answerOptions.media_description_options.push_back(contentDescription);
cricket::TransportDescription transportDescription;
cricket::TransportInfo transportInfo(mappedContent.mid(), transportDescription);
mappedOffer->AddTransportInfo(transportInfo);
mappedOffer->AddContent(std::move(mappedContent));
break;
}
}
if (!found) {
auto mappedContent = createInactiveContentInfo("_" + id);
cricket::MediaDescriptionOptions contentDescription(cricket::MediaType::MEDIA_TYPE_AUDIO, "_" + id, webrtc::RtpTransceiverDirection::kInactive, false);
answerOptions.media_description_options.push_back(contentDescription);
cricket::TransportDescription transportDescription;
cricket::TransportInfo transportInfo(mappedContent.mid(), transportDescription);
mappedOffer->AddTransportInfo(transportInfo);
mappedOffer->AddContent(std::move(mappedContent));
}
}
for (const auto &content : offer->contents) {
if (std::find(_channelIdOrder.begin(), _channelIdOrder.end(), contentIdBySsrc(content.ssrc)) == _channelIdOrder.end()) {
_channelIdOrder.push_back(contentIdBySsrc(content.ssrc));
answerOptions.media_description_options.push_back(getIncomingContentDescription(content));
auto mappedContent = convertSingalingContentToContentInfo(contentIdBySsrc(content.ssrc), content, webrtc::RtpTransceiverDirection::kSendOnly);
cricket::TransportDescription transportDescription;
cricket::TransportInfo transportInfo(mappedContent.mid(), transportDescription);
mappedOffer->AddTransportInfo(transportInfo);
mappedOffer->AddContent(std::move(mappedContent));
}
}
std::unique_ptr<cricket::SessionDescription> answer = _sessionDescriptionFactory->CreateAnswer(mappedOffer.get(), answerOptions, currentSessionDescription.get());
auto mappedAnswer = std::make_unique<NegotiationContents>();
mappedAnswer->exchangeId = offer->exchangeId;
std::vector<signaling::MediaContent> incomingChannels;
for (const auto &content : answer->contents()) {
auto mappedContent = convertContentInfoToSingalingContent(content);
if (content.media_description()->direction() == webrtc::RtpTransceiverDirection::kRecvOnly) {
for (const auto &offerContent : offer->contents) {
if (contentIdBySsrc(offerContent.ssrc) == content.mid()) {
mappedContent.ssrc = offerContent.ssrc;
mappedContent.ssrcGroups = offerContent.ssrcGroups;
break;
}
}
incomingChannels.push_back(mappedContent);
mappedAnswer->contents.push_back(std::move(mappedContent));
}
}
_incomingChannels = incomingChannels;
return mappedAnswer;
}
void ContentNegotiationContext::setAnswer(std::unique_ptr<ContentNegotiationContext::NegotiationContents> &&answer) {
if (!_pendingOutgoingOffer) {
return;
}
if (_pendingOutgoingOffer->exchangeId != answer->exchangeId) {
return;
}
_pendingOutgoingOffer.reset();
_needNegotiation = false;
_outgoingChannels.clear();
for (const auto &content : answer->contents) {
for (const auto &pendingChannel : _outgoingChannelDescriptions) {
if (pendingChannel.ssrc != 0 && content.ssrc == pendingChannel.ssrc) {
_outgoingChannels.emplace_back(pendingChannel.description.mid, content);
break;
}
}
}
}
std::string ContentNegotiationContext::takeNextOutgoingChannelId() {
std::ostringstream result;
result << "m" << _nextOutgoingChannelId;
_nextOutgoingChannelId++;
return result.str();
}
std::unique_ptr<ContentNegotiationContext::CoordinatedState> ContentNegotiationContext::coordinatedState() const {
auto result = std::make_unique<ContentNegotiationContext::CoordinatedState>();
result->incomingContents = _incomingChannels;
for (const auto &channel : _outgoingChannels) {
bool found = false;
for (const auto &channelDescription : _outgoingChannelDescriptions) {
if (channelDescription.description.mid == channel.id) {
found = true;
break;
}
}
if (found) {
result->outgoingContents.push_back(channel.content);
}
}
return result;
}
absl::optional<uint32_t> ContentNegotiationContext::outgoingChannelSsrc(std::string const &id) const {
for (const auto &channel : _outgoingChannels) {
bool found = false;
for (const auto &channelDescription : _outgoingChannelDescriptions) {
if (channelDescription.description.mid == channel.id) {
found = true;
break;
}
}
if (found && channel.id == id) {
if (channel.content.ssrc != 0) {
return channel.content.ssrc;
}
}
}
return absl::nullopt;
}
} // namespace tgcalls

View File

@ -0,0 +1,99 @@
#ifndef TGCALLS_CONTENT_NEGOTIATION_H
#define TGCALLS_CONTENT_NEGOTIATION_H
#include <memory>
#include "pc/channel_manager.h"
#include "pc/media_session.h"
#include "pc/session_description.h"
#include "p2p/base/transport_description_factory.h"
#include "v2/Signaling.h"
namespace tgcalls {
class ContentNegotiationContext {
public:
struct NegotiationContents {
uint32_t exchangeId = 0;
std::vector<signaling::MediaContent> contents;
};
struct PendingOutgoingOffer {
uint32_t exchangeId = 0;
};
struct PendingOutgoingChannel {
cricket::MediaDescriptionOptions description;
uint32_t ssrc = 0;
std::vector<signaling::SsrcGroup> ssrcGroups;
PendingOutgoingChannel(cricket::MediaDescriptionOptions &&description_) :
description(std::move(description_)) {
}
};
struct OutgoingChannel {
std::string id;
signaling::MediaContent content;
OutgoingChannel(std::string id_, signaling::MediaContent content_) :
id(id_), content(content_) {
}
};
struct CoordinatedState {
std::vector<signaling::MediaContent> outgoingContents;
std::vector<signaling::MediaContent> incomingContents;
};
public:
ContentNegotiationContext(bool isOutgoing, rtc::UniqueRandomIdGenerator *uniqueRandomIdGenerator);
~ContentNegotiationContext();
void copyCodecsFromChannelManager(cricket::ChannelManager *channelManager, bool randomize);
std::string addOutgoingChannel(signaling::MediaContent::Type mediaType);
void removeOutgoingChannel(std::string const &id);
std::unique_ptr<NegotiationContents> getPendingOffer();
std::unique_ptr<NegotiationContents> setRemoteNegotiationContent(std::unique_ptr<NegotiationContents> &&remoteNegotiationContent);
std::unique_ptr<CoordinatedState> coordinatedState() const;
absl::optional<uint32_t> outgoingChannelSsrc(std::string const &id) const;
private:
std::string takeNextOutgoingChannelId();
std::unique_ptr<cricket::SessionDescription> currentSessionDescriptionFromCoordinatedState();
std::unique_ptr<NegotiationContents> getAnswer(std::unique_ptr<NegotiationContents> &&offer);
void setAnswer(std::unique_ptr<NegotiationContents> &&answer);
private:
bool _isOutgoing = false;
rtc::UniqueRandomIdGenerator *_uniqueRandomIdGenerator = nullptr;
std::unique_ptr<cricket::TransportDescriptionFactory> _transportDescriptionFactory;
std::unique_ptr<cricket::MediaSessionDescriptionFactory> _sessionDescriptionFactory;
std::vector<std::string> _channelIdOrder;
std::vector<webrtc::RtpHeaderExtensionCapability> _rtpAudioExtensions;
std::vector<webrtc::RtpHeaderExtensionCapability> _rtpVideoExtensions;
std::vector<PendingOutgoingChannel> _outgoingChannelDescriptions;
bool _needNegotiation = false;
std::vector<OutgoingChannel> _outgoingChannels;
std::vector<signaling::MediaContent> _incomingChannels;
std::unique_ptr<PendingOutgoingOffer> _pendingOutgoingOffer;
int _nextOutgoingChannelId = 0;
};
} // namespace tgcalls
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,59 @@
#ifndef TGCALLS_INSTANCEV2_REFERENCE_IMPL_H
#define TGCALLS_INSTANCEV2_REFERENCE_IMPL_H
#include "Instance.h"
#include "StaticThreads.h"
namespace tgcalls {
class LogSinkImpl;
class Manager;
template <typename T>
class ThreadLocalObject;
class InstanceV2ReferenceImplInternal;
class InstanceV2ReferenceImpl final : public Instance {
public:
explicit InstanceV2ReferenceImpl(Descriptor &&descriptor);
~InstanceV2ReferenceImpl() override;
void receiveSignalingData(const std::vector<uint8_t> &data) override;
void setVideoCapture(std::shared_ptr<VideoCaptureInterface> videoCapture) override;
void setRequestedVideoAspect(float aspect) override;
void setNetworkType(NetworkType networkType) override;
void setMuteMicrophone(bool muteMicrophone) override;
bool supportsVideo() override {
return true;
}
void setIncomingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) override;
void setAudioOutputGainControlEnabled(bool enabled) override;
void setEchoCancellationStrength(int strength) override;
void setAudioInputDevice(std::string id) override;
void setAudioOutputDevice(std::string id) override;
void setInputVolume(float level) override;
void setOutputVolume(float level) override;
void setAudioOutputDuckingEnabled(bool enabled) override;
void setIsLowBatteryLevel(bool isLowBatteryLevel) override;
static std::vector<std::string> GetVersions();
static int GetConnectionMaxLayer();
std::string getLastError() override;
std::string getDebugInfo() override;
int64_t getPreferredRelayId() override;
TrafficStats getTrafficStats() override;
PersistentState getPersistentState() override;
void stop(std::function<void(FinalState)> completion) override;
void sendVideoDeviceUpdated() override {
}
private:
std::shared_ptr<Threads> _threads;
std::unique_ptr<ThreadLocalObject<InstanceV2ReferenceImplInternal>> _internal;
std::unique_ptr<LogSinkImpl> _logSink;
};
} // namespace tgcalls
#endif

View File

@ -13,12 +13,186 @@
#include "p2p/base/dtls_transport_factory.h"
#include "pc/dtls_srtp_transport.h"
#include "pc/dtls_transport.h"
#include "pc/jsep_transport_controller.h"
#include "api/async_dns_resolver.h"
#include "TurnCustomizerImpl.h"
#include "SctpDataChannelProviderInterfaceImpl.h"
#include "StaticThreads.h"
#include "platform/PlatformInterface.h"
#include "p2p/base/turn_port.h"
namespace tgcalls {
namespace {
class CryptStringImpl : public rtc::CryptStringImpl {
public:
CryptStringImpl(std::string const &value) :
_value(value) {
}
virtual ~CryptStringImpl() override {
}
virtual size_t GetLength() const override {
return _value.size();
}
virtual void CopyTo(char* dest, bool nullterminate) const override {
memcpy(dest, _value.data(), _value.size());
if (nullterminate) {
dest[_value.size()] = 0;
}
}
virtual std::string UrlEncode() const override {
return _value;
}
virtual CryptStringImpl* Copy() const override {
return new CryptStringImpl(_value);
}
virtual void CopyRawTo(std::vector<unsigned char>* dest) const override {
dest->resize(_value.size());
memcpy(dest->data(), _value.data(), _value.size());
}
private:
std::string _value;
};
class WrappedAsyncPacketSocket : public rtc::AsyncPacketSocket {
public:
WrappedAsyncPacketSocket(std::unique_ptr<rtc::AsyncPacketSocket> &&wrappedSocket) :
_wrappedSocket(std::move(wrappedSocket)) {
_wrappedSocket->SignalReadPacket.connect(this, &WrappedAsyncPacketSocket::onReadPacket);
_wrappedSocket->SignalSentPacket.connect(this, &WrappedAsyncPacketSocket::onSentPacket);
_wrappedSocket->SignalReadyToSend.connect(this, &WrappedAsyncPacketSocket::onReadyToSend);
_wrappedSocket->SignalAddressReady.connect(this, &WrappedAsyncPacketSocket::onAddressReady);
_wrappedSocket->SignalConnect.connect(this, &WrappedAsyncPacketSocket::onConnect);
_wrappedSocket->SignalClose.connect(this, &WrappedAsyncPacketSocket::onClose);
}
virtual ~WrappedAsyncPacketSocket() override {
_wrappedSocket->SignalReadPacket.disconnect(this);
_wrappedSocket->SignalSentPacket.disconnect(this);
_wrappedSocket->SignalReadyToSend.disconnect(this);
_wrappedSocket->SignalAddressReady.disconnect(this);
_wrappedSocket->SignalConnect.disconnect(this);
_wrappedSocket->SignalClose.disconnect(this);
_wrappedSocket.reset();
}
virtual rtc::SocketAddress GetLocalAddress() const override {
return _wrappedSocket->GetLocalAddress();
}
virtual rtc::SocketAddress GetRemoteAddress() const override {
return _wrappedSocket->GetRemoteAddress();
}
virtual int Send(const void* pv, size_t cb, const rtc::PacketOptions& options) override {
return _wrappedSocket->Send(pv, cb, options);
}
virtual int SendTo(const void* pv,
size_t cb,
const rtc::SocketAddress& addr,
const rtc::PacketOptions& options) override {
return _wrappedSocket->SendTo(pv, cb, addr, options);
}
virtual int Close() override {
return _wrappedSocket->Close();
}
virtual State GetState() const override {
return _wrappedSocket->GetState();
}
virtual int GetOption(rtc::Socket::Option opt, int* value) override {
return _wrappedSocket->GetOption(opt, value);
}
virtual int SetOption(rtc::Socket::Option opt, int value) override {
return _wrappedSocket->SetOption(opt, value);
}
virtual int GetError() const override {
return _wrappedSocket->GetError();
}
virtual void SetError(int error) override {
_wrappedSocket->SetError(error);
}
private:
void onReadPacket(AsyncPacketSocket *socket, const char *data, size_t size, const rtc::SocketAddress &address, const int64_t &timestamp) {
SignalReadPacket.emit(this, data, size, address, timestamp);
}
void onSentPacket(AsyncPacketSocket *socket, const rtc::SentPacket &packet) {
SignalSentPacket.emit(this, packet);
}
void onReadyToSend(AsyncPacketSocket *socket) {
SignalReadyToSend.emit(this);
}
void onAddressReady(AsyncPacketSocket *socket, const rtc::SocketAddress &address) {
SignalAddressReady.emit(this, address);
}
void onConnect(AsyncPacketSocket *socket) {
SignalConnect.emit(this);
}
void onClose(AsyncPacketSocket *socket, int value) {
SignalClose(this, value);
}
private:
std::unique_ptr<rtc::AsyncPacketSocket> _wrappedSocket;
};
class WrappedBasicPacketSocketFactory : public rtc::BasicPacketSocketFactory {
public:
explicit WrappedBasicPacketSocketFactory(rtc::SocketFactory* socket_factory) :
rtc::BasicPacketSocketFactory(socket_factory) {
}
virtual ~WrappedBasicPacketSocketFactory() override {
}
rtc::AsyncPacketSocket* CreateUdpSocket(const rtc::SocketAddress& local_address, uint16_t min_port, uint16_t max_port) override {
rtc::AsyncPacketSocket *socket = rtc::BasicPacketSocketFactory::CreateUdpSocket(local_address, min_port, max_port);
if (socket) {
std::unique_ptr<rtc::AsyncPacketSocket> socketPtr;
socketPtr.reset(socket);
return new WrappedAsyncPacketSocket(std::move(socketPtr));
} else {
return nullptr;
}
}
private:
};
}
NativeNetworkingImpl::ConnectionDescription::CandidateDescription NativeNetworkingImpl::connectionDescriptionFromCandidate(cricket::Candidate const &candidate) {
NativeNetworkingImpl::ConnectionDescription::CandidateDescription result;
result.type = candidate.type();
result.protocol = candidate.protocol();
result.address = candidate.address().ToString();
return result;
}
webrtc::CryptoOptions NativeNetworkingImpl::getDefaulCryptoOptions() {
auto options = webrtc::CryptoOptions();
options.srtp.enable_aes128_sha1_80_crypto_cipher = true;
@ -33,6 +207,7 @@ _enableStunMarking(configuration.enableStunMarking),
_enableTCP(configuration.enableTCP),
_enableP2P(configuration.enableP2P),
_rtcServers(configuration.rtcServers),
_proxy(configuration.proxy),
_stateUpdated(std::move(configuration.stateUpdated)),
_candidateGathered(std::move(configuration.candidateGathered)),
_transportMessageReceived(std::move(configuration.transportMessageReceived)),
@ -41,13 +216,15 @@ _dataChannelStateUpdated(configuration.dataChannelStateUpdated),
_dataChannelMessageReceived(configuration.dataChannelMessageReceived) {
assert(_threads->getNetworkThread()->IsCurrent());
_localIceParameters = PeerIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH));
_localIceParameters = PeerIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH), true);
_localCertificate = rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(rtc::KT_ECDSA), absl::nullopt);
_networkMonitorFactory = PlatformInterface::SharedInstance()->createNetworkMonitorFactory();
_socketFactory.reset(new rtc::BasicPacketSocketFactory(_threads->getNetworkThread()->socketserver()));
_networkManager = std::make_unique<rtc::BasicNetworkManager>();
_asyncResolverFactory = std::make_unique<webrtc::BasicAsyncResolverFactory>();
_networkManager = std::make_unique<rtc::BasicNetworkManager>(_networkMonitorFactory.get(), nullptr);
_asyncResolverFactory = std::make_unique<webrtc::WrappingAsyncDnsResolverFactory>(std::make_unique<webrtc::BasicAsyncResolverFactory>());
_dtlsSrtpTransport = std::make_unique<webrtc::DtlsSrtpTransport>(true);
_dtlsSrtpTransport->SetDtlsTransports(nullptr, nullptr);
@ -72,6 +249,7 @@ NativeNetworkingImpl::~NativeNetworkingImpl() {
_portAllocator.reset();
_networkManager.reset();
_socketFactory.reset();
_networkMonitorFactory.reset();
}
void NativeNetworkingImpl::resetDtlsSrtpTransport() {
@ -88,29 +266,31 @@ void NativeNetworkingImpl::resetDtlsSrtpTransport() {
cricket::PORTALLOCATOR_ENABLE_IPV6 |
cricket::PORTALLOCATOR_ENABLE_IPV6_ON_WIFI;
if (!_enableTCP) {
flags |= cricket::PORTALLOCATOR_DISABLE_TCP;
/*if (_proxy) {
rtc::ProxyInfo proxyInfo;
proxyInfo.type = rtc::ProxyType::PROXY_SOCKS5;
proxyInfo.address = rtc::SocketAddress(_proxy->host, _proxy->port);
proxyInfo.username = _proxy->login;
proxyInfo.password = rtc::CryptString(CryptStringImpl(_proxy->password));
_portAllocator->set_proxy("t/1.0", proxyInfo);
flags &= ~cricket::PORTALLOCATOR_DISABLE_TCP;
} else */{
if (!_enableTCP) {
flags |= cricket::PORTALLOCATOR_DISABLE_TCP;
}
}
if (!_enableP2P) {
if (_proxy || !_enableP2P) {
flags |= cricket::PORTALLOCATOR_DISABLE_UDP;
flags |= cricket::PORTALLOCATOR_DISABLE_STUN;
uint32_t candidateFilter = _portAllocator->candidate_filter();
candidateFilter &= ~(cricket::CF_REFLEXIVE);
_portAllocator->SetCandidateFilter(candidateFilter);
}
_portAllocator->set_step_delay(cricket::kMinimumStepDelay);
//TODO: figure out the proxy setup
/*if (_proxy) {
rtc::ProxyInfo proxyInfo;
proxyInfo.type = rtc::ProxyType::PROXY_SOCKS5;
proxyInfo.address = rtc::SocketAddress(_proxy->host, _proxy->port);
proxyInfo.username = _proxy->login;
proxyInfo.password = rtc::CryptString(TgCallsCryptStringImpl(_proxy->password));
_portAllocator->set_proxy("t/1.0", proxyInfo);
}*/
_portAllocator->set_flags(flags);
_portAllocator->Initialize();
@ -131,9 +311,10 @@ void NativeNetworkingImpl::resetDtlsSrtpTransport() {
}
}
_portAllocator->SetConfiguration(stunServers, turnServers, 2, webrtc::NO_PRUNE, _turnCustomizer.get());
_portAllocator->SetConfiguration(stunServers, turnServers, 0, webrtc::NO_PRUNE, _turnCustomizer.get());
_transportChannel.reset(new cricket::P2PTransportChannel("transport", 0, _portAllocator.get(), _asyncResolverFactory.get(), nullptr));
_transportChannel = cricket::P2PTransportChannel::Create("transport", 0, _portAllocator.get(), _asyncResolverFactory.get());
cricket::IceConfig iceConfig;
iceConfig.continual_gathering_policy = cricket::GATHER_CONTINUALLY;
@ -144,7 +325,7 @@ void NativeNetworkingImpl::resetDtlsSrtpTransport() {
cricket::IceParameters localIceParameters(
_localIceParameters.ufrag,
_localIceParameters.pwd,
false
_localIceParameters.supportsRenomination
);
_transportChannel->SetIceParameters(localIceParameters);
@ -153,7 +334,8 @@ void NativeNetworkingImpl::resetDtlsSrtpTransport() {
_transportChannel->SignalCandidateGathered.connect(this, &NativeNetworkingImpl::candidateGathered);
_transportChannel->SignalIceTransportStateChanged.connect(this, &NativeNetworkingImpl::transportStateChanged);
_transportChannel->SignalReadPacket.connect(this, &NativeNetworkingImpl::transportPacketReceived);
_transportChannel->SignalCandidatePairChanged.connect(this, &NativeNetworkingImpl::candidatePairChanged);
_transportChannel->SignalNetworkRouteChanged.connect(this, &NativeNetworkingImpl::transportRouteChanged);
webrtc::CryptoOptions cryptoOptions = NativeNetworkingImpl::getDefaulCryptoOptions();
_dtlsTransport.reset(new cricket::DtlsTransport(_transportChannel.get(), cryptoOptions, nullptr));
@ -201,12 +383,16 @@ void NativeNetworkingImpl::start() {
},
_threads
));
_lastDisconnectedTimestamp = rtc::TimeMillis();
checkConnectionTimeout();
}
void NativeNetworkingImpl::stop() {
_transportChannel->SignalCandidateGathered.disconnect(this);
_transportChannel->SignalIceTransportStateChanged.disconnect(this);
_transportChannel->SignalReadPacket.disconnect(this);
_transportChannel->SignalNetworkRouteChanged.disconnect(this);
_dtlsTransport->SignalWritableState.disconnect(this);
_dtlsTransport->SignalReceivingState.disconnect(this);
@ -218,7 +404,7 @@ void NativeNetworkingImpl::stop() {
_transportChannel.reset();
_portAllocator.reset();
_localIceParameters = PeerIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH));
_localIceParameters = PeerIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH), true);
_localCertificate = rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(rtc::KT_ECDSA), absl::nullopt);
@ -243,7 +429,7 @@ void NativeNetworkingImpl::setRemoteParams(PeerIceParameters const &remoteIcePar
cricket::IceParameters parameters(
remoteIceParameters.ufrag,
remoteIceParameters.pwd,
false
remoteIceParameters.supportsRenomination
);
_transportChannel->SetRemoteIceParameters(parameters);
@ -288,11 +474,11 @@ void NativeNetworkingImpl::checkConnectionTimeout() {
int64_t currentTimestamp = rtc::TimeMillis();
const int64_t maxTimeout = 20000;
if (strong->_lastNetworkActivityMs + maxTimeout < currentTimestamp) {
NativeNetworkingImpl::State emitState;
emitState.isReadyToSendData = false;
emitState.isFailed = true;
strong->_stateUpdated(emitState);
if (!strong->_isConnected && strong->_lastDisconnectedTimestamp + maxTimeout < currentTimestamp) {
RTC_LOG(LS_INFO) << "NativeNetworkingImpl timeout " << (currentTimestamp - strong->_lastDisconnectedTimestamp) << " ms";
strong->_isFailed = true;
strong->notifyStateUpdated();
}
strong->checkConnectionTimeout();
@ -343,10 +529,43 @@ void NativeNetworkingImpl::transportReadyToSend(cricket::IceTransportInternal *t
assert(_threads->getNetworkThread()->IsCurrent());
}
void NativeNetworkingImpl::transportPacketReceived(rtc::PacketTransportInternal *transport, const char *bytes, size_t size, const int64_t &timestamp, int unused) {
void NativeNetworkingImpl::transportRouteChanged(absl::optional<rtc::NetworkRoute> route) {
assert(_threads->getNetworkThread()->IsCurrent());
if (route.has_value()) {
/*cricket::IceTransportStats iceTransportStats;
if (_transportChannel->GetStats(&iceTransportStats)) {
}*/
RTC_LOG(LS_INFO) << "NativeNetworkingImpl route changed: " << route->DebugString();
bool localIsWifi = route->local.adapter_type() == rtc::AdapterType::ADAPTER_TYPE_WIFI;
bool remoteIsWifi = route->remote.adapter_type() == rtc::AdapterType::ADAPTER_TYPE_WIFI;
RTC_LOG(LS_INFO) << "NativeNetworkingImpl is wifi: local=" << localIsWifi << ", remote=" << remoteIsWifi;
std::string localDescription = route->local.uses_turn() ? "turn" : "p2p";
std::string remoteDescription = route->remote.uses_turn() ? "turn" : "p2p";
RouteDescription routeDescription(localDescription, remoteDescription);
if (!_currentRouteDescription || routeDescription != _currentRouteDescription.value()) {
_currentRouteDescription = std::move(routeDescription);
notifyStateUpdated();
}
}
}
_lastNetworkActivityMs = rtc::TimeMillis();
void NativeNetworkingImpl::candidatePairChanged(cricket::CandidatePairChangeEvent const &event) {
ConnectionDescription connectionDescription;
connectionDescription.local = connectionDescriptionFromCandidate(event.selected_candidate_pair.local);
connectionDescription.remote = connectionDescriptionFromCandidate(event.selected_candidate_pair.remote);
if (!_currentConnectionDescription || _currentConnectionDescription.value() != connectionDescription) {
_currentConnectionDescription = std::move(connectionDescription);
notifyStateUpdated();
}
}
void NativeNetworkingImpl::RtpPacketReceived_n(rtc::CopyOnWriteBuffer *packet, int64_t packet_time_us, bool isUnresolved) {
@ -381,10 +600,12 @@ void NativeNetworkingImpl::UpdateAggregateStates_n() {
if (_isConnected != isConnected) {
_isConnected = isConnected;
if (!isConnected) {
_lastDisconnectedTimestamp = rtc::TimeMillis();
}
NativeNetworkingImpl::State emitState;
emitState.isReadyToSendData = isConnected;
_stateUpdated(emitState);
notifyStateUpdated();
if (_dataChannelInterface) {
_dataChannelInterface->updateIsConnected(isConnected);
@ -392,6 +613,15 @@ void NativeNetworkingImpl::UpdateAggregateStates_n() {
}
}
void NativeNetworkingImpl::notifyStateUpdated() {
NativeNetworkingImpl::State emitState;
emitState.isReadyToSendData = _isConnected;
emitState.route = _currentRouteDescription;
emitState.connection = _currentConnectionDescription;
emitState.isFailed = _isFailed;
_stateUpdated(emitState);
}
void NativeNetworkingImpl::sctpReadyToSendData() {
}

View File

@ -10,9 +10,9 @@
#include "rtc_base/third_party/sigslot/sigslot.h"
#include "api/candidate.h"
#include "media/base/media_channel.h"
//#include "media/sctp/sctp_transport.h"
#include "rtc_base/ssl_fingerprint.h"
#include "pc/sctp_data_channel.h"
#include "p2p/base/port.h"
#include <functional>
#include <memory>
@ -36,10 +36,10 @@ class DtlsTransport;
} // namespace cricket
namespace webrtc {
class BasicAsyncResolverFactory;
class TurnCustomizer;
class DtlsSrtpTransport;
class RtpTransport;
class AsyncDnsResolverFactoryInterface;
} // namespace webrtc
namespace tgcalls {
@ -50,9 +50,80 @@ class Threads;
class NativeNetworkingImpl : public sigslot::has_slots<>, public std::enable_shared_from_this<NativeNetworkingImpl> {
public:
struct RouteDescription {
explicit RouteDescription(std::string const &localDescription_, std::string const &remoteDescription_) :
localDescription(localDescription_),
remoteDescription(remoteDescription_) {
}
std::string localDescription;
std::string remoteDescription;
bool operator==(RouteDescription const &rhs) const {
if (localDescription != rhs.localDescription) {
return false;
}
if (remoteDescription != rhs.remoteDescription) {
return false;
}
return true;
}
bool operator!=(const RouteDescription& rhs) const {
return !(*this == rhs);
}
};
struct ConnectionDescription {
struct CandidateDescription {
std::string protocol;
std::string type;
std::string address;
bool operator==(CandidateDescription const &rhs) const {
if (protocol != rhs.protocol) {
return false;
}
if (type != rhs.type) {
return false;
}
if (address != rhs.address) {
return false;
}
return true;
}
bool operator!=(const CandidateDescription& rhs) const {
return !(*this == rhs);
}
};
CandidateDescription local;
CandidateDescription remote;
bool operator==(ConnectionDescription const &rhs) const {
if (local != rhs.local) {
return false;
}
if (remote != rhs.remote) {
return false;
}
return true;
}
bool operator!=(const ConnectionDescription& rhs) const {
return !(*this == rhs);
}
};
struct State {
bool isReadyToSendData = false;
bool isFailed = false;
absl::optional<RouteDescription> route;
absl::optional<ConnectionDescription> connection;
};
struct Configuration {
@ -61,6 +132,7 @@ public:
bool enableTCP = false;
bool enableP2P = false;
std::vector<RtcServer> rtcServers;
absl::optional<Proxy> proxy;
std::function<void(const NativeNetworkingImpl::State &)> stateUpdated;
std::function<void(const cricket::Candidate &)> candidateGathered;
std::function<void(rtc::CopyOnWriteBuffer const &, bool)> transportMessageReceived;
@ -71,6 +143,7 @@ public:
};
static webrtc::CryptoOptions getDefaulCryptoOptions();
static ConnectionDescription::CandidateDescription connectionDescriptionFromCandidate(cricket::Candidate const &candidate);
NativeNetworkingImpl(Configuration &&configuration);
~NativeNetworkingImpl();
@ -96,7 +169,8 @@ private:
void OnTransportReceivingState_n(rtc::PacketTransportInternal *transport);
void transportStateChanged(cricket::IceTransportInternal *transport);
void transportReadyToSend(cricket::IceTransportInternal *transport);
void transportPacketReceived(rtc::PacketTransportInternal *transport, const char *bytes, size_t size, const int64_t &timestamp, int unused);
void transportRouteChanged(absl::optional<rtc::NetworkRoute> route);
void candidatePairChanged(cricket::CandidatePairChangeEvent const &event);
void DtlsReadyToSend(bool DtlsReadyToSend);
void UpdateAggregateStates_n();
void RtpPacketReceived_n(rtc::CopyOnWriteBuffer *packet, int64_t packet_time_us, bool isUnresolved);
@ -104,6 +178,8 @@ private:
void sctpReadyToSendData();
void sctpDataReceived(const cricket::ReceiveDataParams& params, const rtc::CopyOnWriteBuffer& buffer);
void notifyStateUpdated();
std::shared_ptr<Threads> _threads;
bool _isOutgoing = false;
@ -111,6 +187,7 @@ private:
bool _enableTCP = false;
bool _enableP2P = false;
std::vector<RtcServer> _rtcServers;
absl::optional<Proxy> _proxy;
std::function<void(const NativeNetworkingImpl::State &)> _stateUpdated;
std::function<void(const cricket::Candidate &)> _candidateGathered;
@ -119,11 +196,12 @@ private:
std::function<void(bool)> _dataChannelStateUpdated;
std::function<void(std::string const &)> _dataChannelMessageReceived;
std::unique_ptr<rtc::NetworkMonitorFactory> _networkMonitorFactory;
std::unique_ptr<rtc::BasicPacketSocketFactory> _socketFactory;
std::unique_ptr<rtc::BasicNetworkManager> _networkManager;
std::unique_ptr<webrtc::TurnCustomizer> _turnCustomizer;
std::unique_ptr<cricket::BasicPortAllocator> _portAllocator;
std::unique_ptr<webrtc::BasicAsyncResolverFactory> _asyncResolverFactory;
std::unique_ptr<webrtc::AsyncDnsResolverFactoryInterface> _asyncResolverFactory;
std::unique_ptr<cricket::P2PTransportChannel> _transportChannel;
std::unique_ptr<cricket::DtlsTransport> _dtlsTransport;
std::unique_ptr<webrtc::DtlsSrtpTransport> _dtlsSrtpTransport;
@ -135,7 +213,10 @@ private:
absl::optional<PeerIceParameters> _remoteIceParameters;
bool _isConnected = false;
int64_t _lastNetworkActivityMs = 0;
bool _isFailed = false;
int64_t _lastDisconnectedTimestamp = 0;
absl::optional<RouteDescription> _currentRouteDescription;
absl::optional<ConnectionDescription> _currentConnectionDescription;
};
} // namespace tgcalls

View File

@ -3,6 +3,7 @@
#include "third-party/json11.hpp"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include <sstream>
@ -40,18 +41,21 @@ absl::optional<SsrcGroup> SsrcGroup_parse(json11::Json::object const &object) {
const auto semantics = object.find("semantics");
if (semantics == object.end() || !semantics->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: semantics must be a string";
return absl::nullopt;
}
result.semantics = semantics->second.string_value();
const auto ssrcs = object.find("ssrcs");
if (ssrcs == object.end() || !ssrcs->second.is_array()) {
RTC_LOG(LS_ERROR) << "Signaling: ssrcs must be an array";
return absl::nullopt;
}
for (const auto &ssrc : ssrcs->second.array_items()) {
if (ssrc.is_string()) {
uint32_t parsedSsrc = stringToUInt32(ssrc.string_value());
if (parsedSsrc == 0) {
RTC_LOG(LS_ERROR) << "Signaling: parsedSsrc must not be 0";
return absl::nullopt;
}
result.ssrcs.push_back(parsedSsrc);
@ -59,6 +63,7 @@ absl::optional<SsrcGroup> SsrcGroup_parse(json11::Json::object const &object) {
uint32_t parsedSsrc = (uint32_t)ssrc.number_value();
result.ssrcs.push_back(parsedSsrc);
} else {
RTC_LOG(LS_ERROR) << "Signaling: ssrcs item must be a string or a number";
return absl::nullopt;
}
}
@ -80,12 +85,14 @@ absl::optional<FeedbackType> FeedbackType_parse(json11::Json::object const &obje
const auto type = object.find("type");
if (type == object.end() || !type->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: type must be a string";
return absl::nullopt;
}
result.type = type->second.string_value();
const auto subtype = object.find("subtype");
if (subtype == object.end() || !subtype->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: subtype must be a string";
return absl::nullopt;
}
result.subtype = subtype->second.string_value();
@ -105,11 +112,13 @@ json11::Json::object RtpExtension_serialize(webrtc::RtpExtension const &rtpExten
absl::optional<webrtc::RtpExtension> RtpExtension_parse(json11::Json::object const &object) {
const auto id = object.find("id");
if (id == object.end() || !id->second.is_number()) {
RTC_LOG(LS_ERROR) << "Signaling: id must be a number";
return absl::nullopt;
}
const auto uri = object.find("uri");
if (uri == object.end() || !uri->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: uri must be a string";
return absl::nullopt;
}
@ -144,18 +153,21 @@ absl::optional<PayloadType> PayloadType_parse(json11::Json::object const &object
const auto id = object.find("id");
if (id == object.end() || !id->second.is_number()) {
RTC_LOG(LS_ERROR) << "Signaling: id must be a number";
return absl::nullopt;
}
result.id = id->second.int_value();
const auto name = object.find("name");
if (name == object.end() || !name->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: name must be a string";
return absl::nullopt;
}
result.name = name->second.string_value();
const auto clockrate = object.find("clockrate");
if (clockrate == object.end() || !clockrate->second.is_number()) {
RTC_LOG(LS_ERROR) << "Signaling: clockrate must be a number";
return absl::nullopt;
}
result.clockrate = clockrate->second.int_value();
@ -163,6 +175,7 @@ absl::optional<PayloadType> PayloadType_parse(json11::Json::object const &object
const auto channels = object.find("channels");
if (channels != object.end()) {
if (!channels->second.is_number()) {
RTC_LOG(LS_ERROR) << "Signaling: channels must be a number";
return absl::nullopt;
}
result.channels = channels->second.int_value();
@ -171,15 +184,18 @@ absl::optional<PayloadType> PayloadType_parse(json11::Json::object const &object
const auto feedbackTypes = object.find("feedbackTypes");
if (feedbackTypes != object.end()) {
if (!feedbackTypes->second.is_array()) {
RTC_LOG(LS_ERROR) << "Signaling: feedbackTypes must be an array";
return absl::nullopt;
}
for (const auto &feedbackType : feedbackTypes->second.array_items()) {
if (!feedbackType.is_object()) {
RTC_LOG(LS_ERROR) << "Signaling: feedbackTypes items must be objects";
return absl::nullopt;
}
if (const auto parsedFeedbackType = FeedbackType_parse(feedbackType.object_items())) {
result.feedbackTypes.push_back(parsedFeedbackType.value());
} else {
RTC_LOG(LS_ERROR) << "Signaling: could not parse FeedbackType";
return absl::nullopt;
}
}
@ -188,10 +204,12 @@ absl::optional<PayloadType> PayloadType_parse(json11::Json::object const &object
const auto parameters = object.find("parameters");
if (parameters != object.end()) {
if (!parameters->second.is_object()) {
RTC_LOG(LS_ERROR) << "Signaling: parameters must be an object";
return absl::nullopt;
}
for (const auto &item : parameters->second.object_items()) {
if (!item.second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: parameters items must be strings";
return absl::nullopt;
}
result.parameters.push_back(std::make_pair(item.first, item.second.string_value()));
@ -203,6 +221,23 @@ absl::optional<PayloadType> PayloadType_parse(json11::Json::object const &object
json11::Json::object MediaContent_serialize(MediaContent const &mediaContent) {
json11::Json::object object;
std::string mappedType;
switch (mediaContent.type) {
case MediaContent::Type::Audio: {
mappedType = "audio";
break;
}
case MediaContent::Type::Video: {
mappedType = "video";
break;
}
default: {
RTC_FATAL() << "Unknown media type";
break;
}
}
object.insert(std::make_pair("type", mappedType));
object.insert(std::make_pair("ssrc", json11::Json(uint32ToString(mediaContent.ssrc))));
@ -233,9 +268,24 @@ json11::Json::object MediaContent_serialize(MediaContent const &mediaContent) {
absl::optional<MediaContent> MediaContent_parse(json11::Json::object const &object) {
MediaContent result;
const auto type = object.find("type");
if (type == object.end() || !type->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: type must be a string";
return absl::nullopt;
}
if (type->second.string_value() == "audio") {
result.type = MediaContent::Type::Audio;
} else if (type->second.string_value() == "video") {
result.type = MediaContent::Type::Video;
} else {
RTC_LOG(LS_ERROR) << "Signaling: type must be one of [\"audio\", \"video\"]";
return absl::nullopt;
}
const auto ssrc = object.find("ssrc");
if (ssrc == object.end()) {
RTC_LOG(LS_ERROR) << "Signaling: ssrc must be present";
return absl::nullopt;
}
if (ssrc->second.is_string()) {
@ -243,21 +293,25 @@ absl::optional<MediaContent> MediaContent_parse(json11::Json::object const &obje
} else if (ssrc->second.is_number()) {
result.ssrc = (uint32_t)ssrc->second.number_value();
} else {
RTC_LOG(LS_ERROR) << "Signaling: ssrc must be a string or a number";
return absl::nullopt;
}
const auto ssrcGroups = object.find("ssrcGroups");
if (ssrcGroups != object.end()) {
if (!ssrcGroups->second.is_array()) {
RTC_LOG(LS_ERROR) << "Signaling: ssrcGroups must be an array";
return absl::nullopt;
}
for (const auto &ssrcGroup : ssrcGroups->second.array_items()) {
if (!ssrcGroup.is_object()) {
RTC_LOG(LS_ERROR) << "Signaling: ssrcsGroups items must be objects";
return absl::nullopt;
}
if (const auto parsedSsrcGroup = SsrcGroup_parse(ssrcGroup.object_items())) {
result.ssrcGroups.push_back(parsedSsrcGroup.value());
} else {
RTC_LOG(LS_ERROR) << "Signaling: could not parse SsrcGroup";
return absl::nullopt;
}
}
@ -266,15 +320,18 @@ absl::optional<MediaContent> MediaContent_parse(json11::Json::object const &obje
const auto payloadTypes = object.find("payloadTypes");
if (payloadTypes != object.end()) {
if (!payloadTypes->second.is_array()) {
RTC_LOG(LS_ERROR) << "Signaling: payloadTypes must be an array";
return absl::nullopt;
}
for (const auto &payloadType : payloadTypes->second.array_items()) {
if (!payloadType.is_object()) {
RTC_LOG(LS_ERROR) << "Signaling: payloadTypes items must be objects";
return absl::nullopt;
}
if (const auto parsedPayloadType = PayloadType_parse(payloadType.object_items())) {
result.payloadTypes.push_back(parsedPayloadType.value());
} else {
RTC_LOG(LS_ERROR) << "Signaling: could not parse PayloadType";
return absl::nullopt;
}
}
@ -283,15 +340,18 @@ absl::optional<MediaContent> MediaContent_parse(json11::Json::object const &obje
const auto rtpExtensions = object.find("rtpExtensions");
if (rtpExtensions != object.end()) {
if (!rtpExtensions->second.is_array()) {
RTC_LOG(LS_ERROR) << "Signaling: rtpExtensions must be an array";
return absl::nullopt;
}
for (const auto &rtpExtension : rtpExtensions->second.array_items()) {
if (!rtpExtension.is_object()) {
RTC_LOG(LS_ERROR) << "Signaling: rtpExtensions items must be objects";
return absl::nullopt;
}
if (const auto parsedRtpExtension = RtpExtension_parse(rtpExtension.object_items())) {
result.rtpExtensions.push_back(parsedRtpExtension.value());
} else {
RTC_LOG(LS_ERROR) << "Signaling: could not parse RtpExtension";
return absl::nullopt;
}
}
@ -306,6 +366,7 @@ std::vector<uint8_t> InitialSetupMessage_serialize(const InitialSetupMessage * c
object.insert(std::make_pair("@type", json11::Json("InitialSetup")));
object.insert(std::make_pair("ufrag", json11::Json(message->ufrag)));
object.insert(std::make_pair("pwd", json11::Json(message->pwd)));
object.insert(std::make_pair("renomination", json11::Json(message->supportsRenomination)));
json11::Json::array jsonFingerprints;
for (const auto &fingerprint : message->fingerprints) {
@ -317,18 +378,6 @@ std::vector<uint8_t> InitialSetupMessage_serialize(const InitialSetupMessage * c
}
object.insert(std::make_pair("fingerprints", json11::Json(std::move(jsonFingerprints))));
if (const auto audio = message->audio) {
object.insert(std::make_pair("audio", json11::Json(MediaContent_serialize(audio.value()))));
}
if (const auto video = message->video) {
object.insert(std::make_pair("video", json11::Json(MediaContent_serialize(video.value()))));
}
if (const auto screencast = message->screencast) {
object.insert(std::make_pair("screencast", json11::Json(MediaContent_serialize(screencast.value()))));
}
auto json = json11::Json(std::move(object));
std::string result = json.dump();
return std::vector<uint8_t>(result.begin(), result.end());
@ -337,31 +386,43 @@ std::vector<uint8_t> InitialSetupMessage_serialize(const InitialSetupMessage * c
absl::optional<InitialSetupMessage> InitialSetupMessage_parse(json11::Json::object const &object) {
const auto ufrag = object.find("ufrag");
if (ufrag == object.end() || !ufrag->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: ufrag must be a string";
return absl::nullopt;
}
const auto pwd = object.find("pwd");
if (pwd == object.end() || !pwd->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: pwd must be a string";
return absl::nullopt;
}
const auto renomination = object.find("renomination");
bool renominationValue = false;
if (renomination != object.end() && renomination->second.is_bool()) {
renominationValue = renomination->second.bool_value();
}
const auto fingerprints = object.find("fingerprints");
if (fingerprints == object.end() || !fingerprints->second.is_array()) {
RTC_LOG(LS_ERROR) << "Signaling: fingerprints must be an array";
return absl::nullopt;
}
std::vector<DtlsFingerprint> parsedFingerprints;
for (const auto &fingerprintObject : fingerprints->second.array_items()) {
if (!fingerprintObject.is_object()) {
RTC_LOG(LS_ERROR) << "Signaling: fingerprints items must be objects";
return absl::nullopt;
}
const auto hash = fingerprintObject.object_items().find("hash");
if (hash == fingerprintObject.object_items().end() || !hash->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: hash must be a string";
return absl::nullopt;
}
const auto setup = fingerprintObject.object_items().find("setup");
if (setup == fingerprintObject.object_items().end() || !setup->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: setup must be a string";
return absl::nullopt;
}
const auto fingerprint = fingerprintObject.object_items().find("fingerprint");
if (fingerprint == fingerprintObject.object_items().end() || !fingerprint->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: fingerprint must be a string";
return absl::nullopt;
}
@ -376,41 +437,64 @@ absl::optional<InitialSetupMessage> InitialSetupMessage_parse(json11::Json::obje
InitialSetupMessage message;
message.ufrag = ufrag->second.string_value();
message.pwd = pwd->second.string_value();
message.supportsRenomination = renominationValue;
message.fingerprints = std::move(parsedFingerprints);
const auto audio = object.find("audio");
if (audio != object.end()) {
if (!audio->second.is_object()) {
return absl::nullopt;
}
if (const auto parsedAudio = MediaContent_parse(audio->second.object_items())) {
message.audio = parsedAudio.value();
} else {
return absl::nullopt;
}
return message;
}
std::vector<uint8_t> NegotiateChannelsMessage_serialize(const NegotiateChannelsMessage * const message) {
json11::Json::object object;
object.insert(std::make_pair("@type", json11::Json("NegotiateChannels")));
object.insert(std::make_pair("exchangeId", json11::Json(uint32ToString(message->exchangeId))));
json11::Json::array contents;
for (const auto &content : message->contents) {
contents.push_back(json11::Json(MediaContent_serialize(content)));
}
object.insert(std::make_pair("contents", std::move(contents)));
auto json = json11::Json(std::move(object));
std::string result = json.dump();
return std::vector<uint8_t>(result.begin(), result.end());
}
absl::optional<NegotiateChannelsMessage> NegotiateChannelsMessage_parse(json11::Json::object const &object) {
NegotiateChannelsMessage message;
const auto exchangeId = object.find("exchangeId");
if (exchangeId == object.end()) {
RTC_LOG(LS_ERROR) << "Signaling: exchangeId must be present";
return absl::nullopt;
} else if (exchangeId->second.is_string()) {
message.exchangeId = stringToUInt32(exchangeId->second.string_value());
} else if (exchangeId->second.is_number()) {
message.exchangeId = (uint32_t)exchangeId->second.number_value();
} else {
RTC_LOG(LS_ERROR) << "Signaling: exchangeId must be a string or a number";
return absl::nullopt;
}
const auto video = object.find("video");
if (video != object.end()) {
if (!video->second.is_object()) {
const auto contents = object.find("contents");
if (contents != object.end()) {
if (!contents->second.is_array()) {
RTC_LOG(LS_ERROR) << "Signaling: contents must be an array";
return absl::nullopt;
}
if (const auto parsedVideo = MediaContent_parse(video->second.object_items())) {
message.video = parsedVideo.value();
} else {
return absl::nullopt;
}
}
const auto screencast = object.find("screencast");
if (screencast != object.end()) {
if (!screencast->second.is_object()) {
return absl::nullopt;
}
if (const auto parsedScreencast = MediaContent_parse(screencast->second.object_items())) {
message.screencast = parsedScreencast.value();
} else {
return absl::nullopt;
for (const auto &content : contents->second.array_items()) {
if (!content.is_object()) {
RTC_LOG(LS_ERROR) << "Signaling: contents items must be objects";
return absl::nullopt;
}
if (auto parsedContent = MediaContent_parse(content.object_items())) {
message.contents.push_back(std::move(parsedContent.value()));
} else {
RTC_LOG(LS_ERROR) << "Signaling: could not parse MediaContent";
return absl::nullopt;
}
}
}
@ -429,11 +513,13 @@ json11::Json::object ConnectionAddress_serialize(ConnectionAddress const &connec
absl::optional<ConnectionAddress> ConnectionAddress_parse(json11::Json::object const &object) {
const auto ip = object.find("ip");
if (ip == object.end() || !ip->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: ip must be a string";
return absl::nullopt;
}
const auto port = object.find("port");
if (port == object.end() || !port->second.is_number()) {
RTC_LOG(LS_ERROR) << "Signaling: port must be a number";
return absl::nullopt;
}
@ -466,12 +552,14 @@ std::vector<uint8_t> CandidatesMessage_serialize(const CandidatesMessage * const
absl::optional<CandidatesMessage> CandidatesMessage_parse(json11::Json::object const &object) {
const auto candidates = object.find("candidates");
if (candidates == object.end() || !candidates->second.is_array()) {
RTC_LOG(LS_ERROR) << "Signaling: candidates must be an array";
return absl::nullopt;
}
std::vector<IceCandidate> parsedCandidates;
for (const auto &candidateObject : candidates->second.array_items()) {
if (!candidateObject.is_object()) {
RTC_LOG(LS_ERROR) << "Signaling: candidates items must be objects";
return absl::nullopt;
}
@ -479,6 +567,7 @@ absl::optional<CandidatesMessage> CandidatesMessage_parse(json11::Json::object c
const auto sdpString = candidateObject.object_items().find("sdpString");
if (sdpString == candidateObject.object_items().end() || !sdpString->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: sdpString must be a string";
return absl::nullopt;
}
candidate.sdpString = sdpString->second.string_value();
@ -577,6 +666,7 @@ absl::optional<MediaStateMessage> MediaStateMessage_parse(json11::Json::object c
const auto muted = object.find("muted");
if (muted != object.end()) {
if (!muted->second.is_bool()) {
RTC_LOG(LS_ERROR) << "Signaling: muted must be a bool";
return absl::nullopt;
}
message.isMuted = muted->second.bool_value();
@ -585,6 +675,7 @@ absl::optional<MediaStateMessage> MediaStateMessage_parse(json11::Json::object c
const auto lowBattery = object.find("lowBattery");
if (lowBattery != object.end()) {
if (!lowBattery->second.is_bool()) {
RTC_LOG(LS_ERROR) << "Signaling: lowBattery must be a bool";
return absl::nullopt;
}
message.isBatteryLow = lowBattery->second.bool_value();
@ -593,6 +684,7 @@ absl::optional<MediaStateMessage> MediaStateMessage_parse(json11::Json::object c
const auto videoState = object.find("videoState");
if (videoState != object.end()) {
if (!videoState->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: videoState must be a string";
return absl::nullopt;
}
if (videoState->second.string_value() == "inactive") {
@ -601,6 +693,8 @@ absl::optional<MediaStateMessage> MediaStateMessage_parse(json11::Json::object c
message.videoState = MediaStateMessage::VideoState::Suspended;
} else if (videoState->second.string_value() == "active") {
message.videoState = MediaStateMessage::VideoState::Active;
} else {
RTC_LOG(LS_ERROR) << "videoState must be one of [\"inactive\", \"suspended\", \"active\"]";
}
} else {
message.videoState = MediaStateMessage::VideoState::Inactive;
@ -609,6 +703,7 @@ absl::optional<MediaStateMessage> MediaStateMessage_parse(json11::Json::object c
const auto screencastState = object.find("screencastState");
if (screencastState != object.end()) {
if (!screencastState->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: screencastState must be a string";
return absl::nullopt;
}
if (screencastState->second.string_value() == "inactive") {
@ -617,6 +712,8 @@ absl::optional<MediaStateMessage> MediaStateMessage_parse(json11::Json::object c
message.screencastState = MediaStateMessage::VideoState::Suspended;
} else if (screencastState->second.string_value() == "active") {
message.screencastState = MediaStateMessage::VideoState::Active;
} else {
RTC_LOG(LS_ERROR) << "Signaling: screencastState must be one of [\"inactive\", \"suspended\", \"active\"]";
}
} else {
message.screencastState = MediaStateMessage::VideoState::Inactive;
@ -625,6 +722,7 @@ absl::optional<MediaStateMessage> MediaStateMessage_parse(json11::Json::object c
const auto videoRotation = object.find("videoRotation");
if (videoRotation != object.end()) {
if (!videoRotation->second.is_number()) {
RTC_LOG(LS_ERROR) << "Signaling: videoRotation must be a number";
return absl::nullopt;
}
if (videoState->second.int_value() == 0) {
@ -636,6 +734,7 @@ absl::optional<MediaStateMessage> MediaStateMessage_parse(json11::Json::object c
} else if (videoState->second.int_value() == 270) {
message.videoRotation = MediaStateMessage::VideoRotation::Rotation270;
} else {
RTC_LOG(LS_ERROR) << "Signaling: videoRotation must be one of [0, 90, 180, 270]";
message.videoRotation = MediaStateMessage::VideoRotation::Rotation0;
}
} else {
@ -652,6 +751,8 @@ std::vector<uint8_t> Message::serialize() const {
return CandidatesMessage_serialize(candidates);
} else if (const auto mediaState = absl::get_if<MediaStateMessage>(&data)) {
return MediaStateMessage_serialize(mediaState);
} else if (const auto negotiateChannels = absl::get_if<NegotiateChannelsMessage>(&data)) {
return NegotiateChannelsMessage_serialize(negotiateChannels);
} else {
return {};
}
@ -661,19 +762,32 @@ absl::optional<Message> Message::parse(const std::vector<uint8_t> &data) {
std::string parsingError;
auto json = json11::Json::parse(std::string(data.begin(), data.end()), parsingError);
if (json.type() != json11::Json::OBJECT) {
RTC_LOG(LS_ERROR) << "Signaling: message must be an object";
return absl::nullopt;
}
auto type = json.object_items().find("@type");
if (type == json.object_items().end()) {
RTC_LOG(LS_ERROR) << "Signaling: message does not contain @type attribute";
return absl::nullopt;
}
if (!type->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: @type attribute must be a string";
return absl::nullopt;
}
if (type->second.string_value() == "InitialSetup") {
auto parsed = InitialSetupMessage_parse(json.object_items());
if (!parsed) {
RTC_LOG(LS_ERROR) << "Signaling: could not parse " << type->second.string_value() << " message";
return absl::nullopt;
}
Message message;
message.data = std::move(parsed.value());
return message;
} else if (type->second.string_value() == "NegotiateChannels") {
auto parsed = NegotiateChannelsMessage_parse(json.object_items());
if (!parsed) {
RTC_LOG(LS_ERROR) << "Signaling: could not parse " << type->second.string_value() << " message";
return absl::nullopt;
}
Message message;
@ -682,6 +796,7 @@ absl::optional<Message> Message::parse(const std::vector<uint8_t> &data) {
} else if (type->second.string_value() == "Candidates") {
auto parsed = CandidatesMessage_parse(json.object_items());
if (!parsed) {
RTC_LOG(LS_ERROR) << "Signaling: could not parse " << type->second.string_value() << " message";
return absl::nullopt;
}
Message message;
@ -690,12 +805,14 @@ absl::optional<Message> Message::parse(const std::vector<uint8_t> &data) {
} else if (type->second.string_value() == "MediaState") {
auto parsed = MediaStateMessage_parse(json.object_items());
if (!parsed) {
RTC_LOG(LS_ERROR) << "Signaling: could not parse " << type->second.string_value() << " message";
return absl::nullopt;
}
Message message;
message.data = std::move(parsed.value());
return message;
} else {
RTC_LOG(LS_ERROR) << "Signaling: unknown message type " << type->second.string_value();
return absl::nullopt;
}
}

View File

@ -30,11 +30,34 @@ struct IceCandidate {
struct SsrcGroup {
std::vector<uint32_t> ssrcs;
std::string semantics;
bool operator==(SsrcGroup const &rhs) const {
if (ssrcs != rhs.ssrcs) {
return false;
}
if (semantics != rhs.semantics) {
return false;
}
return true;
}
};
struct FeedbackType {
std::string type;
std::string subtype;
bool operator==(FeedbackType const &rhs) const {
if (type != rhs.type) {
return false;
}
if (subtype != rhs.subtype) {
return false;
}
return true;
}
};
struct PayloadType {
@ -44,22 +67,84 @@ struct PayloadType {
uint32_t channels = 0;
std::vector<FeedbackType> feedbackTypes;
std::vector<std::pair<std::string, std::string>> parameters;
bool operator==(PayloadType const &rhs) const {
if (id != rhs.id) {
return false;
}
if (name != rhs.name) {
return false;
}
if (clockrate != rhs.clockrate) {
return false;
}
if (channels != rhs.channels) {
return false;
}
if (feedbackTypes != rhs.feedbackTypes) {
return false;
}
if (parameters != rhs.parameters) {
return false;
}
return true;
}
};
struct MediaContent {
enum class Type {
Audio,
Video
};
Type type = Type::Audio;
uint32_t ssrc = 0;
std::vector<SsrcGroup> ssrcGroups;
std::vector<PayloadType> payloadTypes;
std::vector<webrtc::RtpExtension> rtpExtensions;
bool operator==(const MediaContent& rhs) const {
if (type != rhs.type) {
return false;
}
if (ssrc != rhs.ssrc) {
return false;
}
if (ssrcGroups != rhs.ssrcGroups) {
return false;
}
std::vector<PayloadType> sortedPayloadTypes = payloadTypes;
std::sort(sortedPayloadTypes.begin(), sortedPayloadTypes.end(), [](PayloadType const &lhs, PayloadType const &rhs) {
return lhs.id < rhs.id;
});
std::vector<PayloadType> sortedRhsPayloadTypes = rhs.payloadTypes;
std::sort(sortedRhsPayloadTypes.begin(), sortedRhsPayloadTypes.end(), [](PayloadType const &lhs, PayloadType const &rhs) {
return lhs.id < rhs.id;
});
if (sortedPayloadTypes != sortedRhsPayloadTypes) {
return false;
}
if (rtpExtensions != rhs.rtpExtensions) {
return false;
}
return true;
}
};
struct InitialSetupMessage {
std::string ufrag;
std::string pwd;
bool supportsRenomination = false;
std::vector<DtlsFingerprint> fingerprints;
absl::optional<MediaContent> audio;
absl::optional<MediaContent> video;
absl::optional<MediaContent> screencast;
};
struct NegotiateChannelsMessage {
uint32_t exchangeId = 0;
std::vector<MediaContent> contents;
};
struct CandidatesMessage {
@ -91,6 +176,7 @@ struct MediaStateMessage {
struct Message {
absl::variant<
InitialSetupMessage,
NegotiateChannelsMessage,
CandidatesMessage,
MediaStateMessage> data;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,59 @@
#ifndef TGCALLS_INSTANCEV2_4_0_0_IMPL_H
#define TGCALLS_INSTANCEV2_4_0_0_IMPL_H
#include "Instance.h"
#include "StaticThreads.h"
namespace tgcalls {
class LogSinkImpl;
class Manager;
template <typename T>
class ThreadLocalObject;
class InstanceV2_4_0_0ImplInternal;
class InstanceV2_4_0_0Impl final : public Instance {
public:
explicit InstanceV2_4_0_0Impl(Descriptor &&descriptor);
~InstanceV2_4_0_0Impl() override;
void receiveSignalingData(const std::vector<uint8_t> &data) override;
void setVideoCapture(std::shared_ptr<VideoCaptureInterface> videoCapture) override;
void setRequestedVideoAspect(float aspect) override;
void setNetworkType(NetworkType networkType) override;
void setMuteMicrophone(bool muteMicrophone) override;
bool supportsVideo() override {
return true;
}
void setIncomingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) override;
void setAudioOutputGainControlEnabled(bool enabled) override;
void setEchoCancellationStrength(int strength) override;
void setAudioInputDevice(std::string id) override;
void setAudioOutputDevice(std::string id) override;
void setInputVolume(float level) override;
void setOutputVolume(float level) override;
void setAudioOutputDuckingEnabled(bool enabled) override;
void setIsLowBatteryLevel(bool isLowBatteryLevel) override;
static std::vector<std::string> GetVersions();
static int GetConnectionMaxLayer();
std::string getLastError() override;
std::string getDebugInfo() override;
int64_t getPreferredRelayId() override;
TrafficStats getTrafficStats() override;
PersistentState getPersistentState() override;
void stop(std::function<void(FinalState)> completion) override;
void sendVideoDeviceUpdated() override {
}
private:
std::shared_ptr<Threads> _threads;
std::unique_ptr<ThreadLocalObject<InstanceV2_4_0_0ImplInternal>> _internal;
std::unique_ptr<LogSinkImpl> _logSink;
};
} // namespace tgcalls
#endif

View File

@ -0,0 +1,705 @@
#include "v2_4_0_0/Signaling_4_0_0.h"
#include "third-party/json11.hpp"
#include "rtc_base/checks.h"
#include <sstream>
namespace tgcalls {
namespace signaling_4_0_0 {
static std::string uint32ToString(uint32_t value) {
std::ostringstream stringStream;
stringStream << value;
return stringStream.str();
}
static uint32_t stringToUInt32(std::string const &string) {
std::stringstream stringStream(string);
uint32_t value = 0;
stringStream >> value;
return value;
}
json11::Json::object SsrcGroup_serialize(SsrcGroup const &ssrcGroup) {
json11::Json::object object;
json11::Json::array ssrcs;
for (auto ssrc : ssrcGroup.ssrcs) {
ssrcs.push_back(json11::Json(uint32ToString(ssrc)));
}
object.insert(std::make_pair("semantics", json11::Json(ssrcGroup.semantics)));
object.insert(std::make_pair("ssrcs", json11::Json(std::move(ssrcs))));
return object;
}
absl::optional<SsrcGroup> SsrcGroup_parse(json11::Json::object const &object) {
SsrcGroup result;
const auto semantics = object.find("semantics");
if (semantics == object.end() || !semantics->second.is_string()) {
return absl::nullopt;
}
result.semantics = semantics->second.string_value();
const auto ssrcs = object.find("ssrcs");
if (ssrcs == object.end() || !ssrcs->second.is_array()) {
return absl::nullopt;
}
for (const auto &ssrc : ssrcs->second.array_items()) {
if (ssrc.is_string()) {
uint32_t parsedSsrc = stringToUInt32(ssrc.string_value());
if (parsedSsrc == 0) {
return absl::nullopt;
}
result.ssrcs.push_back(parsedSsrc);
} else if (ssrc.is_number()) {
uint32_t parsedSsrc = (uint32_t)ssrc.number_value();
result.ssrcs.push_back(parsedSsrc);
} else {
return absl::nullopt;
}
}
return result;
}
json11::Json::object FeedbackType_serialize(FeedbackType const &feedbackType) {
json11::Json::object object;
object.insert(std::make_pair("type", json11::Json(feedbackType.type)));
object.insert(std::make_pair("subtype", json11::Json(feedbackType.subtype)));
return object;
}
absl::optional<FeedbackType> FeedbackType_parse(json11::Json::object const &object) {
FeedbackType result;
const auto type = object.find("type");
if (type == object.end() || !type->second.is_string()) {
return absl::nullopt;
}
result.type = type->second.string_value();
const auto subtype = object.find("subtype");
if (subtype == object.end() || !subtype->second.is_string()) {
return absl::nullopt;
}
result.subtype = subtype->second.string_value();
return result;
}
json11::Json::object RtpExtension_serialize(webrtc::RtpExtension const &rtpExtension) {
json11::Json::object object;
object.insert(std::make_pair("id", json11::Json(rtpExtension.id)));
object.insert(std::make_pair("uri", json11::Json(rtpExtension.uri)));
return object;
}
absl::optional<webrtc::RtpExtension> RtpExtension_parse(json11::Json::object const &object) {
const auto id = object.find("id");
if (id == object.end() || !id->second.is_number()) {
return absl::nullopt;
}
const auto uri = object.find("uri");
if (uri == object.end() || !uri->second.is_string()) {
return absl::nullopt;
}
return webrtc::RtpExtension(uri->second.string_value(), id->second.int_value());
}
json11::Json::object PayloadType_serialize(PayloadType const &payloadType) {
json11::Json::object object;
object.insert(std::make_pair("id", json11::Json((int)payloadType.id)));
object.insert(std::make_pair("name", json11::Json(payloadType.name)));
object.insert(std::make_pair("clockrate", json11::Json((int)payloadType.clockrate)));
object.insert(std::make_pair("channels", json11::Json((int)payloadType.channels)));
json11::Json::array feedbackTypes;
for (const auto &feedbackType : payloadType.feedbackTypes) {
feedbackTypes.push_back(FeedbackType_serialize(feedbackType));
}
object.insert(std::make_pair("feedbackTypes", json11::Json(std::move(feedbackTypes))));
json11::Json::object parameters;
for (auto it : payloadType.parameters) {
parameters.insert(std::make_pair(it.first, json11::Json(it.second)));
}
object.insert(std::make_pair("parameters", json11::Json(std::move(parameters))));
return object;
}
absl::optional<PayloadType> PayloadType_parse(json11::Json::object const &object) {
PayloadType result;
const auto id = object.find("id");
if (id == object.end() || !id->second.is_number()) {
return absl::nullopt;
}
result.id = id->second.int_value();
const auto name = object.find("name");
if (name == object.end() || !name->second.is_string()) {
return absl::nullopt;
}
result.name = name->second.string_value();
const auto clockrate = object.find("clockrate");
if (clockrate == object.end() || !clockrate->second.is_number()) {
return absl::nullopt;
}
result.clockrate = clockrate->second.int_value();
const auto channels = object.find("channels");
if (channels != object.end()) {
if (!channels->second.is_number()) {
return absl::nullopt;
}
result.channels = channels->second.int_value();
}
const auto feedbackTypes = object.find("feedbackTypes");
if (feedbackTypes != object.end()) {
if (!feedbackTypes->second.is_array()) {
return absl::nullopt;
}
for (const auto &feedbackType : feedbackTypes->second.array_items()) {
if (!feedbackType.is_object()) {
return absl::nullopt;
}
if (const auto parsedFeedbackType = FeedbackType_parse(feedbackType.object_items())) {
result.feedbackTypes.push_back(parsedFeedbackType.value());
} else {
return absl::nullopt;
}
}
}
const auto parameters = object.find("parameters");
if (parameters != object.end()) {
if (!parameters->second.is_object()) {
return absl::nullopt;
}
for (const auto &item : parameters->second.object_items()) {
if (!item.second.is_string()) {
return absl::nullopt;
}
result.parameters.push_back(std::make_pair(item.first, item.second.string_value()));
}
}
return result;
}
json11::Json::object MediaContent_serialize(MediaContent const &mediaContent) {
json11::Json::object object;
object.insert(std::make_pair("ssrc", json11::Json(uint32ToString(mediaContent.ssrc))));
if (mediaContent.ssrcGroups.size() != 0) {
json11::Json::array ssrcGroups;
for (const auto &group : mediaContent.ssrcGroups) {
ssrcGroups.push_back(SsrcGroup_serialize(group));
}
object.insert(std::make_pair("ssrcGroups", json11::Json(std::move(ssrcGroups))));
}
if (mediaContent.payloadTypes.size() != 0) {
json11::Json::array payloadTypes;
for (const auto &payloadType : mediaContent.payloadTypes) {
payloadTypes.push_back(PayloadType_serialize(payloadType));
}
object.insert(std::make_pair("payloadTypes", json11::Json(std::move(payloadTypes))));
}
json11::Json::array rtpExtensions;
for (const auto &rtpExtension : mediaContent.rtpExtensions) {
rtpExtensions.push_back(RtpExtension_serialize(rtpExtension));
}
object.insert(std::make_pair("rtpExtensions", json11::Json(std::move(rtpExtensions))));
return object;
}
absl::optional<MediaContent> MediaContent_parse(json11::Json::object const &object) {
MediaContent result;
const auto ssrc = object.find("ssrc");
if (ssrc == object.end()) {
return absl::nullopt;
}
if (ssrc->second.is_string()) {
result.ssrc = stringToUInt32(ssrc->second.string_value());
} else if (ssrc->second.is_number()) {
result.ssrc = (uint32_t)ssrc->second.number_value();
} else {
return absl::nullopt;
}
const auto ssrcGroups = object.find("ssrcGroups");
if (ssrcGroups != object.end()) {
if (!ssrcGroups->second.is_array()) {
return absl::nullopt;
}
for (const auto &ssrcGroup : ssrcGroups->second.array_items()) {
if (!ssrcGroup.is_object()) {
return absl::nullopt;
}
if (const auto parsedSsrcGroup = SsrcGroup_parse(ssrcGroup.object_items())) {
result.ssrcGroups.push_back(parsedSsrcGroup.value());
} else {
return absl::nullopt;
}
}
}
const auto payloadTypes = object.find("payloadTypes");
if (payloadTypes != object.end()) {
if (!payloadTypes->second.is_array()) {
return absl::nullopt;
}
for (const auto &payloadType : payloadTypes->second.array_items()) {
if (!payloadType.is_object()) {
return absl::nullopt;
}
if (const auto parsedPayloadType = PayloadType_parse(payloadType.object_items())) {
result.payloadTypes.push_back(parsedPayloadType.value());
} else {
return absl::nullopt;
}
}
}
const auto rtpExtensions = object.find("rtpExtensions");
if (rtpExtensions != object.end()) {
if (!rtpExtensions->second.is_array()) {
return absl::nullopt;
}
for (const auto &rtpExtension : rtpExtensions->second.array_items()) {
if (!rtpExtension.is_object()) {
return absl::nullopt;
}
if (const auto parsedRtpExtension = RtpExtension_parse(rtpExtension.object_items())) {
result.rtpExtensions.push_back(parsedRtpExtension.value());
} else {
return absl::nullopt;
}
}
}
return result;
}
std::vector<uint8_t> InitialSetupMessage_serialize(const InitialSetupMessage * const message) {
json11::Json::object object;
object.insert(std::make_pair("@type", json11::Json("InitialSetup")));
object.insert(std::make_pair("ufrag", json11::Json(message->ufrag)));
object.insert(std::make_pair("pwd", json11::Json(message->pwd)));
json11::Json::array jsonFingerprints;
for (const auto &fingerprint : message->fingerprints) {
json11::Json::object jsonFingerprint;
jsonFingerprint.insert(std::make_pair("hash", json11::Json(fingerprint.hash)));
jsonFingerprint.insert(std::make_pair("setup", json11::Json(fingerprint.setup)));
jsonFingerprint.insert(std::make_pair("fingerprint", json11::Json(fingerprint.fingerprint)));
jsonFingerprints.emplace_back(std::move(jsonFingerprint));
}
object.insert(std::make_pair("fingerprints", json11::Json(std::move(jsonFingerprints))));
if (const auto audio = message->audio) {
object.insert(std::make_pair("audio", json11::Json(MediaContent_serialize(audio.value()))));
}
if (const auto video = message->video) {
object.insert(std::make_pair("video", json11::Json(MediaContent_serialize(video.value()))));
}
if (const auto screencast = message->screencast) {
object.insert(std::make_pair("screencast", json11::Json(MediaContent_serialize(screencast.value()))));
}
auto json = json11::Json(std::move(object));
std::string result = json.dump();
return std::vector<uint8_t>(result.begin(), result.end());
}
absl::optional<InitialSetupMessage> InitialSetupMessage_parse(json11::Json::object const &object) {
const auto ufrag = object.find("ufrag");
if (ufrag == object.end() || !ufrag->second.is_string()) {
return absl::nullopt;
}
const auto pwd = object.find("pwd");
if (pwd == object.end() || !pwd->second.is_string()) {
return absl::nullopt;
}
const auto fingerprints = object.find("fingerprints");
if (fingerprints == object.end() || !fingerprints->second.is_array()) {
return absl::nullopt;
}
std::vector<DtlsFingerprint> parsedFingerprints;
for (const auto &fingerprintObject : fingerprints->second.array_items()) {
if (!fingerprintObject.is_object()) {
return absl::nullopt;
}
const auto hash = fingerprintObject.object_items().find("hash");
if (hash == fingerprintObject.object_items().end() || !hash->second.is_string()) {
return absl::nullopt;
}
const auto setup = fingerprintObject.object_items().find("setup");
if (setup == fingerprintObject.object_items().end() || !setup->second.is_string()) {
return absl::nullopt;
}
const auto fingerprint = fingerprintObject.object_items().find("fingerprint");
if (fingerprint == fingerprintObject.object_items().end() || !fingerprint->second.is_string()) {
return absl::nullopt;
}
DtlsFingerprint parsedFingerprint;
parsedFingerprint.hash = hash->second.string_value();
parsedFingerprint.setup = setup->second.string_value();
parsedFingerprint.fingerprint = fingerprint->second.string_value();
parsedFingerprints.push_back(std::move(parsedFingerprint));
}
InitialSetupMessage message;
message.ufrag = ufrag->second.string_value();
message.pwd = pwd->second.string_value();
message.fingerprints = std::move(parsedFingerprints);
const auto audio = object.find("audio");
if (audio != object.end()) {
if (!audio->second.is_object()) {
return absl::nullopt;
}
if (const auto parsedAudio = MediaContent_parse(audio->second.object_items())) {
message.audio = parsedAudio.value();
} else {
return absl::nullopt;
}
}
const auto video = object.find("video");
if (video != object.end()) {
if (!video->second.is_object()) {
return absl::nullopt;
}
if (const auto parsedVideo = MediaContent_parse(video->second.object_items())) {
message.video = parsedVideo.value();
} else {
return absl::nullopt;
}
}
const auto screencast = object.find("screencast");
if (screencast != object.end()) {
if (!screencast->second.is_object()) {
return absl::nullopt;
}
if (const auto parsedScreencast = MediaContent_parse(screencast->second.object_items())) {
message.screencast = parsedScreencast.value();
} else {
return absl::nullopt;
}
}
return message;
}
json11::Json::object ConnectionAddress_serialize(ConnectionAddress const &connectionAddress) {
json11::Json::object object;
object.insert(std::make_pair("ip", json11::Json(connectionAddress.ip)));
object.insert(std::make_pair("port", json11::Json(connectionAddress.port)));
return object;
}
absl::optional<ConnectionAddress> ConnectionAddress_parse(json11::Json::object const &object) {
const auto ip = object.find("ip");
if (ip == object.end() || !ip->second.is_string()) {
return absl::nullopt;
}
const auto port = object.find("port");
if (port == object.end() || !port->second.is_number()) {
return absl::nullopt;
}
ConnectionAddress address;
address.ip = ip->second.string_value();
address.port = port->second.int_value();
return address;
}
std::vector<uint8_t> CandidatesMessage_serialize(const CandidatesMessage * const message) {
json11::Json::array candidates;
for (const auto &candidate : message->iceCandidates) {
json11::Json::object candidateObject;
candidateObject.insert(std::make_pair("sdpString", json11::Json(candidate.sdpString)));
candidates.emplace_back(std::move(candidateObject));
}
json11::Json::object object;
object.insert(std::make_pair("@type", json11::Json("Candidates")));
object.insert(std::make_pair("candidates", json11::Json(std::move(candidates))));
auto json = json11::Json(std::move(object));
std::string result = json.dump();
return std::vector<uint8_t>(result.begin(), result.end());
}
absl::optional<CandidatesMessage> CandidatesMessage_parse(json11::Json::object const &object) {
const auto candidates = object.find("candidates");
if (candidates == object.end() || !candidates->second.is_array()) {
return absl::nullopt;
}
std::vector<IceCandidate> parsedCandidates;
for (const auto &candidateObject : candidates->second.array_items()) {
if (!candidateObject.is_object()) {
return absl::nullopt;
}
IceCandidate candidate;
const auto sdpString = candidateObject.object_items().find("sdpString");
if (sdpString == candidateObject.object_items().end() || !sdpString->second.is_string()) {
return absl::nullopt;
}
candidate.sdpString = sdpString->second.string_value();
parsedCandidates.push_back(std::move(candidate));
}
CandidatesMessage message;
message.iceCandidates = std::move(parsedCandidates);
return message;
}
std::vector<uint8_t> MediaStateMessage_serialize(const MediaStateMessage * const message) {
json11::Json::object object;
object.insert(std::make_pair("@type", json11::Json("MediaState")));
object.insert(std::make_pair("muted", json11::Json(message->isMuted)));
object.insert(std::make_pair("lowBattery", json11::Json(message->isBatteryLow)));
std::string videoStateValue;
switch (message->videoState) {
case MediaStateMessage::VideoState::Inactive: {
videoStateValue = "inactive";
break;
}
case MediaStateMessage::VideoState::Suspended: {
videoStateValue = "suspended";
break;
}
case MediaStateMessage::VideoState::Active: {
videoStateValue = "active";
break;
}
default: {
RTC_FATAL() << "Unknown videoState";
break;
}
}
object.insert(std::make_pair("videoState", json11::Json(videoStateValue)));
int videoRotationValue = 0;
switch (message->videoRotation) {
case MediaStateMessage::VideoRotation::Rotation0: {
videoRotationValue = 0;
break;
}
case MediaStateMessage::VideoRotation::Rotation90: {
videoRotationValue = 90;
break;
}
case MediaStateMessage::VideoRotation::Rotation180: {
videoRotationValue = 180;
break;
}
case MediaStateMessage::VideoRotation::Rotation270: {
videoRotationValue = 270;
break;
}
default: {
RTC_FATAL() << "Unknown videoRotation";
break;
}
}
object.insert(std::make_pair("videoRotation", json11::Json(videoRotationValue)));
std::string screencastStateValue;
switch (message->screencastState) {
case MediaStateMessage::VideoState::Inactive: {
screencastStateValue = "inactive";
break;
}
case MediaStateMessage::VideoState::Suspended: {
screencastStateValue = "suspended";
break;
}
case MediaStateMessage::VideoState::Active: {
screencastStateValue = "active";
break;
}
default: {
RTC_FATAL() << "Unknown videoState";
break;
}
}
object.insert(std::make_pair("screencastState", json11::Json(screencastStateValue)));
auto json = json11::Json(std::move(object));
std::string result = json.dump();
return std::vector<uint8_t>(result.begin(), result.end());
}
absl::optional<MediaStateMessage> MediaStateMessage_parse(json11::Json::object const &object) {
MediaStateMessage message;
const auto muted = object.find("muted");
if (muted != object.end()) {
if (!muted->second.is_bool()) {
return absl::nullopt;
}
message.isMuted = muted->second.bool_value();
}
const auto lowBattery = object.find("lowBattery");
if (lowBattery != object.end()) {
if (!lowBattery->second.is_bool()) {
return absl::nullopt;
}
message.isBatteryLow = lowBattery->second.bool_value();
}
const auto videoState = object.find("videoState");
if (videoState != object.end()) {
if (!videoState->second.is_string()) {
return absl::nullopt;
}
if (videoState->second.string_value() == "inactive") {
message.videoState = MediaStateMessage::VideoState::Inactive;
} else if (videoState->second.string_value() == "suspended") {
message.videoState = MediaStateMessage::VideoState::Suspended;
} else if (videoState->second.string_value() == "active") {
message.videoState = MediaStateMessage::VideoState::Active;
}
} else {
message.videoState = MediaStateMessage::VideoState::Inactive;
}
const auto screencastState = object.find("screencastState");
if (screencastState != object.end()) {
if (!screencastState->second.is_string()) {
return absl::nullopt;
}
if (screencastState->second.string_value() == "inactive") {
message.screencastState = MediaStateMessage::VideoState::Inactive;
} else if (screencastState->second.string_value() == "suspended") {
message.screencastState = MediaStateMessage::VideoState::Suspended;
} else if (screencastState->second.string_value() == "active") {
message.screencastState = MediaStateMessage::VideoState::Active;
}
} else {
message.screencastState = MediaStateMessage::VideoState::Inactive;
}
const auto videoRotation = object.find("videoRotation");
if (videoRotation != object.end()) {
if (!videoRotation->second.is_number()) {
return absl::nullopt;
}
if (videoState->second.int_value() == 0) {
message.videoRotation = MediaStateMessage::VideoRotation::Rotation0;
} else if (videoState->second.int_value() == 90) {
message.videoRotation = MediaStateMessage::VideoRotation::Rotation90;
} else if (videoState->second.int_value() == 180) {
message.videoRotation = MediaStateMessage::VideoRotation::Rotation180;
} else if (videoState->second.int_value() == 270) {
message.videoRotation = MediaStateMessage::VideoRotation::Rotation270;
} else {
message.videoRotation = MediaStateMessage::VideoRotation::Rotation0;
}
} else {
message.videoRotation = MediaStateMessage::VideoRotation::Rotation0;
}
return message;
}
std::vector<uint8_t> Message::serialize() const {
if (const auto initialSetup = absl::get_if<InitialSetupMessage>(&data)) {
return InitialSetupMessage_serialize(initialSetup);
} else if (const auto candidates = absl::get_if<CandidatesMessage>(&data)) {
return CandidatesMessage_serialize(candidates);
} else if (const auto mediaState = absl::get_if<MediaStateMessage>(&data)) {
return MediaStateMessage_serialize(mediaState);
} else {
return {};
}
}
absl::optional<Message> Message::parse(const std::vector<uint8_t> &data) {
std::string parsingError;
auto json = json11::Json::parse(std::string(data.begin(), data.end()), parsingError);
if (json.type() != json11::Json::OBJECT) {
return absl::nullopt;
}
auto type = json.object_items().find("@type");
if (type == json.object_items().end()) {
return absl::nullopt;
}
if (!type->second.is_string()) {
return absl::nullopt;
}
if (type->second.string_value() == "InitialSetup") {
auto parsed = InitialSetupMessage_parse(json.object_items());
if (!parsed) {
return absl::nullopt;
}
Message message;
message.data = std::move(parsed.value());
return message;
} else if (type->second.string_value() == "Candidates") {
auto parsed = CandidatesMessage_parse(json.object_items());
if (!parsed) {
return absl::nullopt;
}
Message message;
message.data = std::move(parsed.value());
return message;
} else if (type->second.string_value() == "MediaState") {
auto parsed = MediaStateMessage_parse(json.object_items());
if (!parsed) {
return absl::nullopt;
}
Message message;
message.data = std::move(parsed.value());
return message;
} else {
return absl::nullopt;
}
}
} // namespace signaling
} // namespace tgcalls

View File

@ -0,0 +1,105 @@
#ifndef TGCALLS_SIGNALING_4_0_0_H
#define TGCALLS_SIGNALING_4_0_0_H
#include <string>
#include <vector>
#include "absl/types/variant.h"
#include "absl/types/optional.h"
#include "api/rtp_parameters.h"
namespace tgcalls {
namespace signaling_4_0_0 {
struct DtlsFingerprint {
std::string hash;
std::string setup;
std::string fingerprint;
};
struct ConnectionAddress {
std::string ip;
int port = 0;
};
struct IceCandidate {
std::string sdpString;
};
struct SsrcGroup {
std::vector<uint32_t> ssrcs;
std::string semantics;
};
struct FeedbackType {
std::string type;
std::string subtype;
};
struct PayloadType {
uint32_t id = 0;
std::string name;
uint32_t clockrate = 0;
uint32_t channels = 0;
std::vector<FeedbackType> feedbackTypes;
std::vector<std::pair<std::string, std::string>> parameters;
};
struct MediaContent {
uint32_t ssrc = 0;
std::vector<SsrcGroup> ssrcGroups;
std::vector<PayloadType> payloadTypes;
std::vector<webrtc::RtpExtension> rtpExtensions;
};
struct InitialSetupMessage {
std::string ufrag;
std::string pwd;
std::vector<DtlsFingerprint> fingerprints;
absl::optional<MediaContent> audio;
absl::optional<MediaContent> video;
absl::optional<MediaContent> screencast;
};
struct CandidatesMessage {
std::vector<IceCandidate> iceCandidates;
};
struct MediaStateMessage {
enum class VideoState {
Inactive,
Suspended,
Active
};
enum class VideoRotation {
Rotation0,
Rotation90,
Rotation180,
Rotation270
};
bool isMuted = false;
VideoState videoState = VideoState::Inactive;
VideoRotation videoRotation = VideoRotation::Rotation0;
VideoState screencastState = VideoState::Inactive;
bool isBatteryLow = false;
};
struct Message {
absl::variant<
InitialSetupMessage,
CandidatesMessage,
MediaStateMessage> data;
std::vector<uint8_t> serialize() const;
static absl::optional<Message> parse(const std::vector<uint8_t> &data);
};
};
} // namespace tgcalls
#endif

View File

@ -61,6 +61,9 @@ class RTC_LOCKABLE MutexImpl final {
owner_.SetOwner();
}
ABSL_MUST_USE_RESULT bool TryLock() RTC_EXCLUSIVE_TRYLOCK_FUNCTION(true) {
if (!mutexEnabled()) {
return false;
}
if (pthread_mutex_trylock(&mutex_) != 0) {
return false;
}

View File

@ -121,6 +121,7 @@
<activity
android:name="org.telegram.ui.LaunchActivity"
android:theme="@style/Theme.TMessages.Start"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:hardwareAccelerated="@bool/useHardwareAcceleration"
android:launchMode="singleTask"

View File

@ -691,7 +691,7 @@ public class ChatListItemAnimator extends DefaultItemAnimator {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float v = (float) valueAnimator.getAnimatedValue();
float top = recyclerListView.getMeasuredHeight() / 2f - botCell.getMeasuredHeight() / 2f + activity.getChatListViewPadding();
float top = (recyclerListView.getMeasuredHeight() - activity.getChatListViewPadding() - activity.blurredViewBottomOffset) / 2f - botCell.getMeasuredHeight() / 2f + activity.getChatListViewPadding();
float animateTo = 0;
if (botCell.getTop() > top) {
animateTo = top - botCell.getTop();

View File

@ -330,7 +330,6 @@ public class SimpleExoPlayer extends BasePlayer
private final WakeLockManager wakeLockManager;
private final WifiLockManager wifiLockManager;
private boolean needSetSurface = true;
@Nullable private Format videoFormat;
@Nullable private Format audioFormat;
@ -603,7 +602,6 @@ public class SimpleExoPlayer extends BasePlayer
clearVideoDecoderOutputBufferRenderer();
}
this.textureView = textureView;
needSetSurface = true;
if (textureView == null) {
setVideoSurfaceInternal(/* surface= */ null, /* ownsSurface= */ true);
maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0);
@ -1795,10 +1793,6 @@ public class SimpleExoPlayer extends BasePlayer
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
if (needSetSurface) {
setVideoSurfaceInternal(new Surface(surfaceTexture), true);
needSetSurface = false;
}
setVideoSurfaceInternal(new Surface(surfaceTexture), /* ownsSurface= */ true);
maybeNotifySurfaceSizeChanged(width, height);
}
@ -1817,7 +1811,6 @@ public class SimpleExoPlayer extends BasePlayer
}
setVideoSurfaceInternal(/* surface= */ null, /* ownsSurface= */ true);
maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0);
needSetSurface = true;
return true;
}

View File

@ -64,6 +64,7 @@ import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.text.util.Linkify;
import android.util.DisplayMetrics;
@ -94,6 +95,7 @@ import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.FileProvider;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
@ -228,6 +230,7 @@ public class AndroidUtilities {
public static Pattern BAD_CHARS_PATTERN = null;
public static Pattern BAD_CHARS_MESSAGE_PATTERN = null;
public static Pattern BAD_CHARS_MESSAGE_LONG_PATTERN = null;
private static Pattern singleTagPatter = null;
static {
try {
@ -421,6 +424,35 @@ public class AndroidUtilities {
return null;
}
public static CharSequence replaceSingleTag(String str, Runnable runnable) {
int startIndex = str.indexOf("**");
int endIndex = str.indexOf("**", startIndex + 1);
str = str.replace("**", "");
int index = -1;
int len = 0;
if (startIndex >= 0 && endIndex >= 0 && endIndex - startIndex > 2) {
len = endIndex - startIndex - 2;
index = startIndex;
}
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(str);
if (index >= 0) {
spannableStringBuilder.setSpan(new ClickableSpan() {
@Override
public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(false);
}
@Override
public void onClick(@NonNull View view) {
runnable.run();
}
}, index, index + len, 0);
}
return spannableStringBuilder;
}
private static class LinkSpec {
String url;
int start;
@ -2456,7 +2488,7 @@ public class AndroidUtilities {
}
public static boolean shouldShowClipboardToast() {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.S || !OneUIUtilities.isOneUI();
return Build.VERSION.SDK_INT < Build.VERSION_CODES.S || !OneUIUtilities.hasBuiltInClipboardToasts();
}
public static void addToClipboard(CharSequence str) {
@ -4559,26 +4591,38 @@ public class AndroidUtilities {
return false;
}
public static void setLightNavigationBar(Window window, boolean enable) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
final View decorView = window.getDecorView();
int flags = decorView.getSystemUiVisibility();
public static void setLightNavigationBar(View view, boolean enable) {
if (view != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
int flags = view.getSystemUiVisibility();
if (enable) {
flags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
} else {
flags &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
}
decorView.setSystemUiVisibility(flags);
view.setSystemUiVisibility(flags);
}
}
public static void setLightNavigationBar(Window window, boolean enable) {
if (window != null) {
setLightNavigationBar(window.getDecorView(), enable);
}
}
private static HashMap<Window, ValueAnimator> navigationBarColorAnimators;
public interface IntColorCallback {
public void run(int color);
}
public static void setNavigationBarColor(Window window, int color) {
setNavigationBarColor(window, color, true);
}
public static void setNavigationBarColor(Window window, int color, boolean animated) {
setNavigationBarColor(window, color, animated, null);
}
public static void setNavigationBarColor(Window window, int color, boolean animated, IntColorCallback onUpdate) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (navigationBarColorAnimators != null) {
ValueAnimator animator = navigationBarColorAnimators.get(window);
@ -4589,10 +4633,23 @@ public class AndroidUtilities {
}
if (!animated) {
window.setNavigationBarColor(color);
if (onUpdate != null) {
onUpdate.run(color);
}
try {
window.setNavigationBarColor(color);
} catch (Exception ignore) {}
} else {
ValueAnimator animator = ValueAnimator.ofArgb(window.getNavigationBarColor(), color);
animator.addUpdateListener(a -> window.setNavigationBarColor((int) a.getAnimatedValue()));
animator.addUpdateListener(a -> {
int tcolor = (int) a.getAnimatedValue();
if (onUpdate != null) {
onUpdate.run(tcolor);
}
try {
window.setNavigationBarColor(tcolor);
} catch (Exception ignore) {}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@ -4812,7 +4869,7 @@ public class AndroidUtilities {
bitmap.compress(format, 100, out);
out.close();
return FileProvider.getUriForFile(ApplicationLoader.applicationContext, BuildConfig.APPLICATION_ID + ".provider", file);
} catch (IOException e) {
} catch (Exception e) {
FileLog.e(e);
}
return null;

View File

@ -230,6 +230,7 @@ public class ApplicationLoader extends Application {
applicationInited = true;
SharedConfig.loadConfig();
SharedPrefsHelper.init(applicationContext);
UserConfig.getInstance(0).loadConfig();
LinkedList<Runnable> postRun = new LinkedList<>();

View File

@ -1121,16 +1121,15 @@ public class DownloadController extends BaseController implements NotificationCe
public void startDownloadFile(TLRPC.Document document, MessageObject parentObject) {
if (parentObject.getDocument() == null) {
return;
}
AndroidUtilities.runOnUIThread(() -> {
boolean contains = false;
for (int i = 0; i < recentDownloadingFiles.size(); i++) {
if (recentDownloadingFiles.get(i).getDocument().id == parentObject.getDocument().id) {
if (parentObject.mediaExists) {
contains = true;
} else {
recentDownloadingFiles.remove(i);
}
contains = true;
break;
}
}
@ -1196,51 +1195,52 @@ public class DownloadController extends BaseController implements NotificationCe
putToUnviewedDownloads(parentObject);
}
getNotificationCenter().postNotificationName(NotificationCenter.onDownloadingFilesChanged);
getMessagesStorage().getStorageQueue().postRunnable(() -> {
try {
String req = String.format(Locale.ENGLISH, "UPDATE downloading_documents SET state = 1, date = %d WHERE hash = %d AND id = %d", System.currentTimeMillis(), parentObject.getDocument().dc_id, parentObject.getDocument().id);
getMessagesStorage().getDatabase().executeFast(req).stepThis().dispose();
SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT COUNT(*) FROM downloading_documents WHERE state = 1");
int count = 0;
if (cursor.next()) {
count = cursor.intValue(0);
}
cursor.dispose();
cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT state FROM downloading_documents WHERE state = 1");
if (cursor.next()) {
int state = cursor.intValue(0);
}
cursor.dispose();
int limitDownloadsDocuments = 100;
if (count > limitDownloadsDocuments) {
cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT hash, id FROM downloading_documents WHERE state = 1 ORDER BY date ASC LIMIT " + (limitDownloadsDocuments - count));
ArrayList<DownloadingDocumentEntry> entriesToRemove = new ArrayList<>();
while (cursor.next()) {
DownloadingDocumentEntry entry = new DownloadingDocumentEntry();
entry.hash = cursor.intValue(0);
entry.id = cursor.longValue(1);
entriesToRemove.add(entry);
}
cursor.dispose();
SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("DELETE FROM downloading_documents WHERE hash = ? AND id = ?");
for (int i = 0; i < entriesToRemove.size(); i++) {
state.requery();
state.bindInteger(1, entriesToRemove.get(i).hash);
state.bindLong(2, entriesToRemove.get(i).id);
state.step();
}
state.dispose();
}
} catch (Exception e) {
FileLog.e(e);
}
});
}
});
getMessagesStorage().getStorageQueue().postRunnable(() -> {
try {
String req = String.format(Locale.ENGLISH, "UPDATE downloading_documents SET state = 1, date = %d WHERE hash = %d AND id = %d", System.currentTimeMillis(), parentObject.getDocument().dc_id, parentObject.getDocument().id);
getMessagesStorage().getDatabase().executeFast(req).stepThis().dispose();
SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT COUNT(*) FROM downloading_documents WHERE state = 1");
int count = 0;
if (cursor.next()) {
count = cursor.intValue(0);
}
cursor.dispose();
cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT state FROM downloading_documents WHERE state = 1");
if (cursor.next()) {
int state = cursor.intValue(0);
}
cursor.dispose();
int limitDownloadsDocuments = 100;
if (count > limitDownloadsDocuments) {
cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT hash, id FROM downloading_documents WHERE state = 1 ORDER BY date ASC LIMIT " + (limitDownloadsDocuments - count));
ArrayList<DownloadingDocumentEntry> entriesToRemove = new ArrayList<>();
while (cursor.next()) {
DownloadingDocumentEntry entry = new DownloadingDocumentEntry();
entry.hash = cursor.intValue(0);
entry.id = cursor.longValue(1);
entriesToRemove.add(entry);
}
cursor.dispose();
SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("DELETE FROM downloading_documents WHERE hash = ? AND id = ?");
for (int i = 0; i < entriesToRemove.size(); i++) {
state.requery();
state.bindInteger(1, entriesToRemove.get(i).hash);
state.bindLong(2, entriesToRemove.get(i).id);
state.step();
}
state.dispose();
}
} catch (Exception e) {
FileLog.e(e);
}
});
}
public void onDownloadFail(MessageObject parentObject, int reason) {
@ -1278,7 +1278,7 @@ public class DownloadController extends BaseController implements NotificationCe
});
}
Runnable clearUnviewedDownloadsRunnbale = new Runnable() {
Runnable clearUnviewedDownloadsRunnale = new Runnable() {
@Override
public void run() {
clearUnviewedDownloads();
@ -1287,8 +1287,8 @@ public class DownloadController extends BaseController implements NotificationCe
};
private void putToUnviewedDownloads(MessageObject parentObject) {
unviewedDownloads.put(parentObject.getId(), parentObject);
AndroidUtilities.cancelRunOnUIThread(clearUnviewedDownloadsRunnbale);
AndroidUtilities.runOnUIThread(clearUnviewedDownloadsRunnbale, 60000);
AndroidUtilities.cancelRunOnUIThread(clearUnviewedDownloadsRunnale);
AndroidUtilities.runOnUIThread(clearUnviewedDownloadsRunnale, 60000);
}
public void clearUnviewedDownloads() {
@ -1370,7 +1370,7 @@ public class DownloadController extends BaseController implements NotificationCe
for (int i = 0; i < messageObjects.size(); i++) {
boolean found = false;
for (int j = 0; j < recentDownloadingFiles.size(); j++) {
if (messageObjects.get(i).getId() == recentDownloadingFiles.get(j).getId()) {
if (messageObjects.get(i).getId() == recentDownloadingFiles.get(j).getId() && recentDownloadingFiles.get(j).getDialogId() == messageObjects.get(i).getDialogId()) {
recentDownloadingFiles.remove(j);
found = true;
break;
@ -1378,7 +1378,7 @@ public class DownloadController extends BaseController implements NotificationCe
}
if (!found) {
for (int j = 0; j < downloadingFiles.size(); j++) {
if (messageObjects.get(i).getId() == downloadingFiles.get(j).getId()) {
if (messageObjects.get(i).getId() == downloadingFiles.get(j).getId() && downloadingFiles.get(j).getDialogId() == messageObjects.get(i).getDialogId()) {
downloadingFiles.remove(j);
found = true;
break;

View File

@ -30,15 +30,10 @@ public class FileLoader extends BaseController {
public interface FileLoaderDelegate {
void fileUploadProgressChanged(FileUploadOperation operation, String location, long uploadedSize, long totalSize, boolean isEncrypted);
void fileDidUploaded(String location, TLRPC.InputFile inputFile, TLRPC.InputEncryptedFile inputEncryptedFile, byte[] key, byte[] iv, long totalFileSize);
void fileDidFailedUpload(String location, boolean isEncrypted);
void fileDidLoaded(String location, File finalFile, int type);
void fileDidLoaded(String location, File finalFile, Object parentObject, int type);
void fileDidFailedLoad(String location, int state);
void fileLoadProgressChanged(FileLoadOperation operation, String location, long uploadedSize, long totalSize);
}
@ -48,6 +43,9 @@ public class FileLoader extends BaseController {
public static final int MEDIA_DIR_DOCUMENT = 3;
public static final int MEDIA_DIR_CACHE = 4;
public static final int MEDIA_DIR_IMAGE_PUBLIC = 100;
public static final int MEDIA_DIR_VIDEO_PUBLIC = 101;
public static final int IMAGE_TYPE_LOTTIE = 1;
public static final int IMAGE_TYPE_ANIMATION = 2;
public static final int IMAGE_TYPE_SVG = 3;
@ -726,14 +724,14 @@ public class FileLoader extends BaseController {
if (!operation.isPreloadVideoOperation() && operation.isPreloadFinished()) {
return;
}
if (document != null && parentObject instanceof MessageObject) {
if (document != null && parentObject instanceof MessageObject && ((MessageObject) parentObject).putInDownloadsStore) {
getDownloadController().onDownloadComplete((MessageObject) parentObject);
}
if (!operation.isPreloadVideoOperation()) {
loadOperationPathsUI.remove(fileName);
if (getDelegate() != null) {
delegate.fileDidLoaded(fileName, finalFile, finalType);
delegate.fileDidLoaded(fileName, finalFile, parentObject, finalType);
}
}

View File

@ -67,6 +67,7 @@ public class ForwardingMessagesParams {
message.post = messageObject.messageOwner.post;
message.legacy = messageObject.messageOwner.legacy;
message.restriction_reason = messageObject.messageOwner.restriction_reason;
message.replyMessage = messageObject.messageOwner.replyMessage;
TLRPC.MessageFwdHeader header = null;

View File

@ -1908,12 +1908,16 @@ public class ImageLoader {
}
@Override
public void fileDidLoaded(final String location, final File finalFile, final int type) {
public void fileDidLoaded(final String location, final File finalFile, Object parentObject, final int type) {
fileProgresses.remove(location);
AndroidUtilities.runOnUIThread(() -> {
if (SharedConfig.saveToGallery && telegramPath != null && finalFile != null && (location.endsWith(".mp4") || location.endsWith(".jpg"))) {
if (finalFile.toString().startsWith(telegramPath.toString())) {
AndroidUtilities.addMediaToGallery(finalFile.toString());
if (SharedConfig.saveToGallery && finalFile != null && (location.endsWith(".mp4") || location.endsWith(".jpg"))) {
if (parentObject instanceof MessageObject) {
MessageObject messageObject = (MessageObject) parentObject;
// test add only for peer dialogs
if (messageObject.getDialogId() >= 0) {
AndroidUtilities.addMediaToGallery(finalFile.toString());
}
}
}
NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.fileLoaded, location, finalFile);
@ -3782,6 +3786,10 @@ public class ImageLoader {
return null;
}
public DispatchQueue getCacheOutQueue() {
return cacheOutQueue;
}
public static class MessageThumb {
BitmapDrawable drawable;
String key;

View File

@ -1556,11 +1556,12 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
}
public Bitmap getBitmap() {
AnimatedFileDrawable animation = getAnimation();
RLottieDrawable lottieDrawable = getLottieAnimation();
if (lottieDrawable != null && lottieDrawable.hasBitmap()) {
return lottieDrawable.getAnimatedBitmap();
} else if (animation != null && animation.hasBitmap()) {
}
AnimatedFileDrawable animation = getAnimation();
if (animation != null && animation.hasBitmap()) {
return animation.getAnimatedBitmap();
} else if (currentMediaDrawable instanceof BitmapDrawable && !(currentMediaDrawable instanceof AnimatedFileDrawable) && !(currentMediaDrawable instanceof RLottieDrawable)) {
return ((BitmapDrawable) currentMediaDrawable).getBitmap();
@ -2047,7 +2048,6 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
}
public AnimatedFileDrawable getAnimation() {
AnimatedFileDrawable animatedFileDrawable;
if (currentMediaDrawable instanceof AnimatedFileDrawable) {
return (AnimatedFileDrawable) currentMediaDrawable;
} else if (currentImageDrawable instanceof AnimatedFileDrawable) {
@ -2061,7 +2061,6 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
}
public RLottieDrawable getLottieAnimation() {
RLottieDrawable animatedFileDrawable;
if (currentMediaDrawable instanceof RLottieDrawable) {
return (RLottieDrawable) currentMediaDrawable;
} else if (currentImageDrawable instanceof RLottieDrawable) {

View File

@ -1043,13 +1043,20 @@ public class LocaleController {
reloadLastFile = false;
}
if (!isLoadingRemote) {
AndroidUtilities.runOnUIThread(() -> NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.reloadInterface));
if (init) {
AndroidUtilities.runOnUIThread(() -> NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.reloadInterface));
} else {
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.reloadInterface);
}
}
} catch (Exception e) {
FileLog.e(e);
changingConfiguration = false;
}
recreateFormatters();
if (force) {
MediaDataController.getInstance(currentAccount).loadAttachMenuBots(false, true);
}
}
public LocaleInfo getCurrentLocaleInfo() {
@ -1197,6 +1204,14 @@ public class LocaleController {
}
}
public static String formatString(@StringRes int res, Object... args) {
String key = resourcesCacheMap.get(res);
if (key == null) {
resourcesCacheMap.put(res, key = ApplicationLoader.applicationContext.getResources().getResourceEntryName(res));
}
return formatString(key, res, args);
}
public static String formatString(String key, int res, Object... args) {
return formatString(key, null, res, args);
}
@ -1233,15 +1248,15 @@ public class LocaleController {
return LocaleController.formatPluralString("Hours", ttl / 60 / 60);
} else if (ttl < 60 * 60 * 24 * 7) {
return LocaleController.formatPluralString("Days", ttl / 60 / 60 / 24);
} else if (ttl >= 60 * 60 * 24 * 30 && ttl <= 60 * 60 * 24 * 31) {
return LocaleController.formatPluralString("Months", ttl / 60 / 60 / 24 / 30);
} else {
} else if (ttl < 60 * 60 * 24 * 31) {
int days = ttl / 60 / 60 / 24;
if (ttl % 7 == 0) {
return LocaleController.formatPluralString("Weeks", days / 7);
} else {
return String.format("%s %s", LocaleController.formatPluralString("Weeks", days / 7), LocaleController.formatPluralString("Days", days % 7));
}
} else {
return LocaleController.formatPluralString("Months", ttl / 60 / 60 / 24 / 30);
}
}

View File

@ -236,6 +236,33 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
public float lockedAspectRatio;
public boolean initied;
@Override
public CropState clone() {
CropState cloned = new CropState();
cloned.cropPx = this.cropPx;
cloned.cropPy = this.cropPy;
cloned.cropScale = this.cropScale;
cloned.cropRotate = this.cropRotate;
cloned.cropPw = this.cropPw;
cloned.cropPh = this.cropPh;
cloned.transformWidth = this.transformWidth;
cloned.transformHeight = this.transformHeight;
cloned.transformRotation = this.transformRotation;
cloned.mirrored = this.mirrored;
cloned.stateScale = this.stateScale;
cloned.scale = this.scale;
cloned.matrix = this.matrix;
cloned.width = this.width;
cloned.height = this.height;
cloned.freeform = this.freeform;
cloned.lockedAspectRatio = this.lockedAspectRatio;
cloned.initied = this.initied;
return cloned;
}
}
public static class MediaEditState {
@ -4055,7 +4082,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
try {
int selectedType = type;
ContentValues contentValues = new ContentValues();
String extension = MimeTypeMap.getFileExtensionFromUrl(sourceFile.getAbsolutePath());
String extension = FileLoader.getFileExtension(sourceFile);
String mimeType = null;
if (extension != null) {
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
@ -4806,7 +4833,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
resultWidth = temp;
}
// if (framerate > 30 && (Math.min(resultHeight, resultWidth) <= 480)) {
// if (framerate > 40 && (Math.min(resultHeight, resultWidth) <= 480)) {
// framerate = 30;
// }
//
@ -4860,6 +4887,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
info.mediaEntities,
info.isPhoto,
info.cropState,
info.roundVideo,
callback);
@ -4908,8 +4936,8 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
minCompressFactor = 1.0f;
} else if (Math.min(height, width) >= 480) {
maxBitrate = 1000_000;
compressFactor = 0.7f;
minCompressFactor = 0.8f;
compressFactor = 0.75f;
minCompressFactor = 0.9f;
} else {
maxBitrate = 750_000;
compressFactor = 0.6f;

View File

@ -35,6 +35,9 @@ import android.text.style.CharacterStyle;
import android.util.SparseArray;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.collection.LongSparseArray;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.IconCompat;
@ -45,6 +48,8 @@ import org.telegram.SQLite.SQLiteCursor;
import org.telegram.SQLite.SQLiteDatabase;
import org.telegram.SQLite.SQLiteException;
import org.telegram.SQLite.SQLitePreparedStatement;
import org.telegram.messenger.ringtone.RingtoneDataStore;
import org.telegram.messenger.ringtone.RingtoneUploader;
import org.telegram.messenger.support.SparseLongArray;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.NativeByteBuffer;
@ -81,6 +86,14 @@ import tw.nekomimi.nekogram.ui.PinnedStickerHelper;
@SuppressWarnings("unchecked")
public class MediaDataController extends BaseController {
public final static String ATTACH_MENU_BOT_ANIMATED_ICON_KEY = "android_animated",
ATTACH_MENU_BOT_STATIC_ICON_KEY = "default_static",
ATTACH_MENU_BOT_PLACEHOLDER_STATIC_KEY = "placeholder_static",
ATTACH_MENU_BOT_COLOR_LIGHT_ICON = "light_icon",
ATTACH_MENU_BOT_COLOR_LIGHT_TEXT = "light_text",
ATTACH_MENU_BOT_COLOR_DARK_ICON = "dark_icon",
ATTACH_MENU_BOT_COLOR_DARK_TEXT = "dark_text";
private static Pattern BOLD_PATTERN = Pattern.compile("\\*\\*(.+?)\\*\\*"),
ITALIC_PATTERN = Pattern.compile("__(.+?)__"),
SPOILER_PATTERN = Pattern.compile("\\|\\|(.+?)\\|\\|"),
@ -151,6 +164,7 @@ public class MediaDataController extends BaseController {
}
loadStickersByEmojiOrName(AndroidUtilities.STICKERS_PLACEHOLDER_PACK_NAME, false, true);
ringtoneDataStore = new RingtoneDataStore(currentAccount);
}
public static final int TYPE_IMAGE = 0;
@ -161,6 +175,11 @@ public class MediaDataController extends BaseController {
public static final int TYPE_GREETINGS = 3;
private long menuBotsUpdateHash;
private TLRPC.TL_attachMenuBots attachMenuBots = new TLRPC.TL_attachMenuBots();
private boolean isLoadingMenuBots;
private int menuBotsUpdateDate;
private int reactionsUpdateHash;
private List<TLRPC.TL_availableReaction> reactionsList = new ArrayList<>();
private List<TLRPC.TL_availableReaction> enabledReactionsList = new ArrayList<>();
@ -184,6 +203,7 @@ public class MediaDataController extends BaseController {
private boolean[] stickersLoaded = new boolean[5];
private long[] loadHash = new long[5];
private int[] loadDate = new int[5];
public HashMap<String, RingtoneUploader> ringtoneUploaderHashMap = new HashMap<>();
private HashMap<String, ArrayList<TLRPC.Message>> verifyingMessages = new HashMap<>();
@ -211,6 +231,7 @@ public class MediaDataController extends BaseController {
private boolean featuredStickersLoaded;
private TLRPC.Document greetingsSticker;
public final RingtoneDataStore ringtoneDataStore;
public void cleanup() {
for (int a = 0; a < recentStickers.length; a++) {
@ -288,6 +309,108 @@ public class MediaDataController extends BaseController {
}
}
public void checkMenuBots() {
if (!isLoadingMenuBots && Math.abs(System.currentTimeMillis() / 1000 - menuBotsUpdateDate) >= 60 * 60) {
loadAttachMenuBots(true, false);
}
}
public TLRPC.TL_attachMenuBots getAttachMenuBots() {
return attachMenuBots;
}
public void loadAttachMenuBots(boolean cache, boolean force) {
isLoadingMenuBots = true;
if (cache) {
getMessagesStorage().getStorageQueue().postRunnable(() -> {
SQLiteCursor c = null;
long hash = 0;
int date = 0;
TLRPC.TL_attachMenuBots bots = null;
try {
c = getMessagesStorage().getDatabase().queryFinalized("SELECT data, hash, date FROM attach_menu_bots");
if (c.next()) {
NativeByteBuffer data = c.byteBufferValue(0);
if (data != null) {
TLRPC.AttachMenuBots attachMenuBots = TLRPC.TL_attachMenuBots.TLdeserialize(data, data.readInt32(false), true);
if (attachMenuBots instanceof TLRPC.TL_attachMenuBots) {
bots = (TLRPC.TL_attachMenuBots) attachMenuBots;
}
data.reuse();
}
hash = c.longValue(1);
date = c.intValue(2);
}
} catch (Exception e) {
FileLog.e(e, false);
} finally {
if (c != null) {
c.dispose();
}
}
processLoadedMenuBots(bots, hash, date, true);
});
} else {
TLRPC.TL_messages_getAttachMenuBots req = new TLRPC.TL_messages_getAttachMenuBots();
req.hash = force ? 0 : menuBotsUpdateHash;
getConnectionsManager().sendRequest(req, (response, error) -> {
int date = (int) (System.currentTimeMillis() / 1000);
if (response instanceof TLRPC.TL_attachMenuBotsNotModified) {
processLoadedMenuBots(null, 0, date, false);
} else if (response instanceof TLRPC.TL_attachMenuBots) {
TLRPC.TL_attachMenuBots r = (TLRPC.TL_attachMenuBots) response;
processLoadedMenuBots(r, r.hash, date, false);
}
});
}
}
private void processLoadedMenuBots(TLRPC.TL_attachMenuBots bots, long hash, int date, boolean cache) {
if (bots != null && date != 0) {
attachMenuBots = bots;
menuBotsUpdateHash = hash;
}
menuBotsUpdateDate = date;
if (bots != null) {
getMessagesController().putUsers(bots.users, cache);
AndroidUtilities.runOnUIThread(() -> NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.attachMenuBotsDidLoad));
}
if (!cache) {
putMenuBotsToCache(bots, hash, date);
} else if (Math.abs(System.currentTimeMillis() / 1000 - date) >= 60 * 60) {
loadAttachMenuBots(false, true);
}
}
private void putMenuBotsToCache(TLRPC.TL_attachMenuBots bots, long hash, int date) {
getMessagesStorage().getStorageQueue().postRunnable(() -> {
try {
if (bots != null) {
getMessagesStorage().getDatabase().executeFast("DELETE FROM attach_menu_bots").stepThis().dispose();
SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO attach_menu_bots VALUES(?, ?, ?)");
state.requery();
NativeByteBuffer data = new NativeByteBuffer(bots.getObjectSize());
bots.serializeToStream(data);
state.bindByteBuffer(1, data);
state.bindLong(2, hash);
state.bindInteger(3, date);
state.step();
data.reuse();
state.dispose();
} else {
SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("UPDATE attach_menu_bots SET date = ?");
state.requery();
state.bindLong(1, date);
state.step();
state.dispose();
}
} catch (Exception e) {
FileLog.e(e);
}
});
}
public List<TLRPC.TL_availableReaction> getReactionsList() {
return reactionsList;
}
@ -876,6 +999,36 @@ public class MediaDataController extends BaseController {
return value != null ? value : "";
}
@Nullable
public static TLRPC.TL_attachMenuBotIcon getAnimatedAttachMenuBotIcon(@NonNull TLRPC.TL_attachMenuBot bot) {
for (TLRPC.TL_attachMenuBotIcon icon : bot.icons) {
if (icon.name.equals(ATTACH_MENU_BOT_ANIMATED_ICON_KEY)) {
return icon;
}
}
return null;
}
@Nullable
public static TLRPC.TL_attachMenuBotIcon getStaticAttachMenuBotIcon(@NonNull TLRPC.TL_attachMenuBot bot) {
for (TLRPC.TL_attachMenuBotIcon icon : bot.icons) {
if (icon.name.equals(ATTACH_MENU_BOT_STATIC_ICON_KEY)) {
return icon;
}
}
return null;
}
@Nullable
public static TLRPC.TL_attachMenuBotIcon getPlaceholderStaticAttachMenuBotIcon(@NonNull TLRPC.TL_attachMenuBot bot) {
for (TLRPC.TL_attachMenuBotIcon icon : bot.icons) {
if (icon.name.equals(ATTACH_MENU_BOT_PLACEHOLDER_STATIC_KEY)) {
return icon;
}
}
return null;
}
public static long calcDocumentsHash(ArrayList<TLRPC.Document> arrayList) {
return calcDocumentsHash(arrayList, 200);
}
@ -2681,9 +2834,9 @@ public class MediaDataController extends BaseController {
type = MEDIA_MUSIC;
} else if (searchCounter.filter instanceof TLRPC.TL_inputMessagesFilterGif) {
type = MEDIA_GIF;
} else if (searchCounter.filter instanceof TLRPC.TL_inputMessagesFilterPhotos) {
} else if (searchCounter.filter instanceof TLRPC.TL_inputMessagesFilterPhotos) {
type = MEDIA_PHOTOS_ONLY;
} else if (searchCounter.filter instanceof TLRPC.TL_inputMessagesFilterVideo) {
} else if (searchCounter.filter instanceof TLRPC.TL_inputMessagesFilterVideo) {
type = MEDIA_VIDEOS_ONLY;
} else {
continue;
@ -4345,7 +4498,12 @@ public class MediaDataController extends BaseController {
if (ids == null) {
continue;
}
SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid, date, uid FROM messages_v2 WHERE mid IN(%s) AND uid = %d", TextUtils.join(",", ids), dialogId));
SQLiteCursor cursor;
if (scheduled) {
cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid, date, uid FROM scheduled_messages_v2 WHERE mid IN(%s) AND uid = %d", TextUtils.join(",", ids), dialogId));
} else {
cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid, date, uid FROM messages_v2 WHERE mid IN(%s) AND uid = %d", TextUtils.join(",", ids), dialogId));
}
while (cursor.next()) {
NativeByteBuffer data = cursor.byteBufferValue(0);
if (data != null) {
@ -4382,7 +4540,30 @@ public class MediaDataController extends BaseController {
if (!dialogReplyMessagesIds.isEmpty()) {
for (int a = 0, N = dialogReplyMessagesIds.size(); a < N; a++) {
long channelId = dialogReplyMessagesIds.keyAt(a);
if (channelId != 0) {
if (scheduled) {
TLRPC.TL_messages_getScheduledMessages req = new TLRPC.TL_messages_getScheduledMessages();
req.peer = getMessagesController().getInputPeer(dialogId);
req.id = dialogReplyMessagesIds.valueAt(a);
getConnectionsManager().sendRequest(req, (response, error) -> {
if (error == null) {
TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response;
for (int i = 0; i < messagesRes.messages.size(); i++) {
TLRPC.Message message = messagesRes.messages.get(i);
if (message.dialog_id == 0) {
message.dialog_id = dialogId;
}
}
MessageObject.fixMessagePeer(messagesRes.messages, channelId);
ImageLoader.saveMessagesThumbs(messagesRes.messages);
broadcastReplyMessages(messagesRes.messages, replyMessageOwners, messagesRes.users, messagesRes.chats, dialogId, false);
getMessagesStorage().putUsersAndChats(messagesRes.users, messagesRes.chats, true, true);
saveReplyMessages(replyMessageOwners, messagesRes.messages, scheduled);
}
if (callback != null) {
AndroidUtilities.runOnUIThread(callback);
}
});
} else if (channelId != 0) {
TLRPC.TL_channels_getMessages req = new TLRPC.TL_channels_getMessages();
req.channel = getMessagesController().getInputChannel(channelId);
req.id = dialogReplyMessagesIds.valueAt(a);
@ -4694,7 +4875,7 @@ public class MediaDataController extends BaseController {
});
for (int a = 0, N = entitiesCopy.size(); a < N; a++) {
TLRPC.MessageEntity entity = entitiesCopy.get(a);
if (entity.length <= 0 || entity.offset < 0 || entity.offset >= text.length()) {
if (entity == null || entity.length <= 0 || entity.offset < 0 || entity.offset >= text.length()) {
continue;
} else if (entity.offset + entity.length > text.length()) {
entity.length = text.length() - entity.offset;
@ -5533,6 +5714,61 @@ public class MediaDataController extends BaseController {
public List<TLRPC.TL_availableReaction> getEnabledReactionsList() {
return enabledReactionsList;
}
public void uploadRingtone(String filePath) {
if (ringtoneUploaderHashMap.containsKey(filePath)) {
return;
}
ringtoneUploaderHashMap.put(filePath, new RingtoneUploader(filePath, currentAccount));
ringtoneDataStore.addUploadingTone(filePath);
}
public void onRingtoneUploaded(String filePath, TLRPC.Document document, boolean error) {
ringtoneUploaderHashMap.remove(filePath);
ringtoneDataStore.onRingtoneUploaded(filePath, document, error);
}
public void checkRingtones() {
ringtoneDataStore.loadUserRingtones();
}
public boolean saveToRingtones(TLRPC.Document document) {
if (document == null) {
return false;
}
if (ringtoneDataStore.contains(document.id)) {
return true;
}
if (document.size > MessagesController.getInstance(currentAccount).ringtoneSizeMax) {
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.showBulletin, Bulletin.TYPE_ERROR_SUBTITLE, LocaleController.formatString("TooLargeError", R.string.TooLargeError), LocaleController.formatString("ErrorRingtoneSizeTooBig", R.string.ErrorRingtoneSizeTooBig, (MessagesController.getInstance(UserConfig.selectedAccount).ringtoneSizeMax / 1024)));
return false;
}
for (int a = 0; a < document.attributes.size(); a++) {
TLRPC.DocumentAttribute attribute = document.attributes.get(a);
if (attribute instanceof TLRPC.TL_documentAttributeAudio) {
if (attribute.duration > MessagesController.getInstance(currentAccount).ringtoneDurationMax) {
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.showBulletin, Bulletin.TYPE_ERROR_SUBTITLE, LocaleController.formatString("TooLongError", R.string.TooLongError), LocaleController.formatString("ErrorRingtoneDurationTooLong", R.string.ErrorRingtoneDurationTooLong, MessagesController.getInstance(UserConfig.selectedAccount).ringtoneDurationMax));
return false;
}
}
}
TLRPC.TL_account_saveRingtone saveRingtone = new TLRPC.TL_account_saveRingtone();
saveRingtone.id = new TLRPC.TL_inputDocument();
saveRingtone.id.id = document.id;
saveRingtone.id.file_reference = document.file_reference;
saveRingtone.id.access_hash = document.access_hash;
ConnectionsManager.getInstance(currentAccount).sendRequest(saveRingtone, (response, error) -> AndroidUtilities.runOnUIThread(() -> {
if (response != null) {
if (response instanceof TLRPC.TL_account_savedRingtoneConverted) {
ringtoneDataStore.addTone(((TLRPC.TL_account_savedRingtoneConverted) response).document);
} else {
ringtoneDataStore.addTone(document);
}
}
}));
return true;
}
//---------------- BOT END ----------------
//---------------- EMOJI START ----------------
@ -5542,6 +5778,7 @@ public class MediaDataController extends BaseController {
public String keyword;
}
public interface KeywordResultCallback {
void run(ArrayList<KeywordResult> param, String alias);
}

View File

@ -30,13 +30,13 @@ import androidx.collection.LongSparseArray;
import org.telegram.PhoneFormat.PhoneFormat;
import org.telegram.messenger.browser.Browser;
import org.telegram.messenger.ringtone.RingtoneDataStore;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.SerializedData;
import org.telegram.tgnet.TLObject;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Cells.ChatMessageCell;
import org.telegram.ui.Components.spoilers.SpoilerEffect;
import org.telegram.ui.Components.TextStyleSpan;
import org.telegram.ui.Components.TypefaceSpan;
import org.telegram.ui.Components.URLSpanBotCommand;
@ -46,6 +46,7 @@ import org.telegram.ui.Components.URLSpanNoUnderline;
import org.telegram.ui.Components.URLSpanNoUnderlineBold;
import org.telegram.ui.Components.URLSpanReplacement;
import org.telegram.ui.Components.URLSpanUserMention;
import org.telegram.ui.Components.spoilers.SpoilerEffect;
import java.io.BufferedReader;
import java.io.File;
@ -209,6 +210,10 @@ public class MessageObject {
public ImageLocation mediaThumb;
public ImageLocation mediaSmallThumb;
// forwarding preview params
public boolean hideSendersName;
public TLRPC.Peer sendAsPeer;
static final String[] excludeWords = new String[] {
" vs. ",
" vs ",
@ -2543,7 +2548,11 @@ public class MessageObject {
if (button instanceof TLRPC.TL_keyboardButtonBuy && (messageOwner.media.flags & 4) != 0) {
text = LocaleController.getString("PaymentReceipt", R.string.PaymentReceipt);
} else {
text = Emoji.replaceEmoji(button.text, Theme.chat_msgBotButtonPaint.getFontMetricsInt(), AndroidUtilities.dp(15), false);
String str = button.text;
if (str == null) {
str = "";
}
text = Emoji.replaceEmoji(str, Theme.chat_msgBotButtonPaint.getFontMetricsInt(), AndroidUtilities.dp(15), false);
}
StaticLayout staticLayout = new StaticLayout(text, Theme.chat_msgBotButtonPaint, AndroidUtilities.dp(2000), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
if (staticLayout.getLineCount() > 0) {
@ -3069,6 +3078,9 @@ public class MessageObject {
user = getUser(users, sUsers, messageOwner.peer_id.user_id);
}
messageText = LocaleController.formatString("ActionBotDocuments", R.string.ActionBotDocuments, UserObject.getFirstName(user), str.toString());
} else if (messageOwner.action instanceof TLRPC.TL_messageActionWebViewDataSent) {
TLRPC.TL_messageActionWebViewDataSent dataSent = (TLRPC.TL_messageActionWebViewDataSent) messageOwner.action;
messageText = LocaleController.formatString("ActionBotWebViewData", R.string.ActionBotWebViewData, dataSent.text);
} else if (messageOwner.action instanceof TLRPC.TL_messageActionSetChatTheme) {
String emoticon = ((TLRPC.TL_messageActionSetChatTheme) messageOwner.action).emoticon;
String userName = UserObject.getFirstName(fromUser);
@ -5560,7 +5572,7 @@ public class MessageObject {
}
public boolean isMusic() {
return isMusicMessage(messageOwner);
return isMusicMessage(messageOwner) && !isVideo();
}
public boolean isDocument() {
@ -6456,4 +6468,18 @@ public class MessageObject {
reactionsChanged = true;
return true;
}
public boolean probablyRingtone() {
if (getDocument() != null && RingtoneDataStore.ringtoneSupportedMimeType.contains(getDocument().mime_type) && getDocument().size < MessagesController.getInstance(currentAccount).ringtoneSizeMax * 2) {
for (int a = 0; a < getDocument().attributes.size(); a++) {
TLRPC.DocumentAttribute attribute = getDocument().attributes.get(a);
if (attribute instanceof TLRPC.TL_documentAttributeAudio) {
if (attribute.duration < 60) {
return true;
}
}
}
}
return false;
}
}

View File

@ -8,6 +8,9 @@
package org.telegram.messenger;
import static org.telegram.messenger.NotificationsController.TYPE_CHANNEL;
import static org.telegram.messenger.NotificationsController.TYPE_PRIVATE;
import android.app.Activity;
import android.appwidget.AppWidgetManager;
import android.content.Context;
@ -45,6 +48,7 @@ import org.telegram.ui.ActionBar.AlertDialog;
import org.telegram.ui.ActionBar.BaseFragment;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.ChatActivity;
import org.telegram.ui.ChatRightsEditActivity;
import org.telegram.ui.Components.AlertsCreator;
import org.telegram.ui.Components.BulletinFactory;
import org.telegram.ui.Components.JoinCallAlert;
@ -97,6 +101,8 @@ public class MessagesController extends BaseController implements NotificationCe
public ArrayList<TLRPC.Dialog> dialogsForward = new ArrayList<>();
public ArrayList<TLRPC.Dialog> dialogsServerOnly = new ArrayList<>();
public ArrayList<TLRPC.Dialog> dialogsCanAddUsers = new ArrayList<>();
public ArrayList<TLRPC.Dialog> dialogsMyChannels = new ArrayList<>();
public ArrayList<TLRPC.Dialog> dialogsMyGroups = new ArrayList<>();
public ArrayList<TLRPC.Dialog> dialogsChannelsOnly = new ArrayList<>();
public ArrayList<TLRPC.Dialog> dialogsUsersOnly = new ArrayList<>();
public ArrayList<TLRPC.Dialog> dialogsForBlock = new ArrayList<>();
@ -106,13 +112,7 @@ public class MessagesController extends BaseController implements NotificationCe
public int unreadUnmutedDialogs;
public ConcurrentHashMap<Long, Integer> dialogs_read_inbox_max = new ConcurrentHashMap<>(100, 1.0f, 2);
public ConcurrentHashMap<Long, Integer> dialogs_read_outbox_max = new ConcurrentHashMap<>(100, 1.0f, 2);
public LongSparseArray<TLRPC.Dialog> dialogs_dict = new LongSparseArray<TLRPC.Dialog>() {
@Override
public void put(long key, TLRPC.Dialog value) {
super.put(key, value);
}
};
public LongSparseArray<TLRPC.Dialog> dialogs_dict = new LongSparseArray<>();
public LongSparseArray<MessageObject> dialogMessage = new LongSparseArray<>();
// NekoX: ignoreBlocked, Messages cache for Dialog Cell
public LongSparseArray<MessageObject> dialogMessageFromUnblocked = new LongSparseArray<>();
@ -350,6 +350,8 @@ public class MessagesController extends BaseController implements NotificationCe
public HashMap<String, EmojiSound> emojiSounds = new HashMap<>();
public HashMap<Long, ArrayList<TLRPC.TL_sendMessageEmojiInteraction>> emojiInteractions = new HashMap<>();
public boolean remoteConfigLoaded;
public int ringtoneDurationMax;
public int ringtoneSizeMax;
private SharedPreferences notificationsPreferences;
private SharedPreferences mainPreferences;
@ -931,6 +933,9 @@ public class MessagesController extends BaseController implements NotificationCe
groupCallVideoMaxParticipants = mainPreferences.getInt("groipCallVideoMaxParticipants", 30);
chatReadMarkSizeThreshold = mainPreferences.getInt("chatReadMarkSizeThreshold", 100);
chatReadMarkExpirePeriod = mainPreferences.getInt("chatReadMarkExpirePeriod", 7 * 86400);
ringtoneDurationMax = mainPreferences.getInt("ringtoneDurationMax", 5);
ringtoneSizeMax = mainPreferences.getInt("ringtoneSizeMax", 1024_00);
chatReadMarkExpirePeriod = mainPreferences.getInt("chatReadMarkExpirePeriod", 7 * 86400);
suggestStickersApiOnly = mainPreferences.getBoolean("suggestStickersApiOnly", false);
roundVideoSize = mainPreferences.getInt("roundVideoSize", 384);
roundVideoBitrate = mainPreferences.getInt("roundVideoBitrate", 1000);
@ -2020,6 +2025,28 @@ public class MessagesController extends BaseController implements NotificationCe
}
break;
}
case "ringtone_size_max": {
if (value.value instanceof TLRPC.TL_jsonNumber) {
TLRPC.TL_jsonNumber number = (TLRPC.TL_jsonNumber) value.value;
if (number.value != ringtoneSizeMax) {
ringtoneSizeMax = (int) number.value;
editor.putInt("ringtoneSizeMax", ringtoneSizeMax);
changed = true;
}
}
break;
}
case "ringtone_duration_max": {
if (value.value instanceof TLRPC.TL_jsonNumber) {
TLRPC.TL_jsonNumber number = (TLRPC.TL_jsonNumber) value.value;
if (number.value != ringtoneDurationMax) {
ringtoneDurationMax = (int) number.value;
editor.putInt("ringtoneDurationMax", ringtoneDurationMax);
changed = true;
}
}
break;
}
}
}
if (changed) {
@ -2785,6 +2812,8 @@ public class MessagesController extends BaseController implements NotificationCe
allDialogs.clear();
dialogsLoadedTillDate = Integer.MAX_VALUE;
dialogsCanAddUsers.clear();
dialogsMyChannels.clear();
dialogsMyGroups.clear();
dialogsChannelsOnly.clear();
dialogsGroupsOnly.clear();
dialogsUsersOnly.clear();
@ -4311,7 +4340,11 @@ public class MessagesController extends BaseController implements NotificationCe
});
}
public void setUserAdminRole(long chatId, TLRPC.User user, TLRPC.TL_chatAdminRights rights, String rank, boolean isChannel, BaseFragment parentFragment, boolean addingNew) {
public void setUserAdminRole(long chatId, TLRPC.User user, TLRPC.TL_chatAdminRights rights, String rank, boolean isChannel, BaseFragment parentFragment, boolean addingNew, boolean forceAdmin, String botHash, Runnable onSuccess) {
setUserAdminRole(chatId, user, rights, rank, isChannel, parentFragment, addingNew, forceAdmin, botHash, onSuccess, null);
}
public void setUserAdminRole(long chatId, TLRPC.User user, TLRPC.TL_chatAdminRights rights, String rank, boolean isChannel, BaseFragment parentFragment, boolean addingNew, boolean forceAdmin, String botHash, Runnable onSuccess, Runnable onError) {
if (user == null || rights == null) {
return;
}
@ -4322,28 +4355,49 @@ public class MessagesController extends BaseController implements NotificationCe
req.user_id = getInputUser(user);
req.admin_rights = rights;
req.rank = rank;
getConnectionsManager().sendRequest(req, (response, error) -> {
RequestDelegate requestDelegate = (response, error) -> {
if (error == null) {
processUpdates((TLRPC.Updates) response, false);
AndroidUtilities.runOnUIThread(() -> loadFullChat(chatId, 0, true), 1000);
AndroidUtilities.runOnUIThread(() -> {
loadFullChat(chatId, 0, true);
if (onSuccess != null) {
onSuccess.run();
}
}, 1000);
} else {
AndroidUtilities.runOnUIThread(() -> AlertsCreator.processError(currentAccount, error, parentFragment, req, isChannel));
if (onError != null) {
AndroidUtilities.runOnUIThread(onError);
}
}
});
};
if (chat.megagroup && addingNew) {
addUserToChat(chatId, user, 0, botHash, parentFragment, true, () -> getConnectionsManager().sendRequest(req, requestDelegate), onError);
} else {
getConnectionsManager().sendRequest(req, requestDelegate);
}
} else {
TLRPC.TL_messages_editChatAdmin req = new TLRPC.TL_messages_editChatAdmin();
req.chat_id = chatId;
req.user_id = getInputUser(user);
req.is_admin = rights.change_info || rights.delete_messages || rights.ban_users || rights.invite_users || rights.pin_messages || rights.add_admins || rights.manage_call;
req.is_admin = forceAdmin || rights.change_info || rights.delete_messages || rights.ban_users || rights.invite_users || rights.pin_messages || rights.add_admins || rights.manage_call;
RequestDelegate requestDelegate = (response, error) -> {
if (error == null) {
AndroidUtilities.runOnUIThread(() -> loadFullChat(chatId, 0, true), 1000);
AndroidUtilities.runOnUIThread(() -> {
loadFullChat(chatId, 0, true);
if (onSuccess != null) {
onSuccess.run();
}
}, 1000);
} else {
AndroidUtilities.runOnUIThread(() -> AlertsCreator.processError(currentAccount, error, parentFragment, req, false));
if (onError != null) {
AndroidUtilities.runOnUIThread(onError);
}
}
};
if (req.is_admin && addingNew) {
addUserToChat(chatId, user, 0, null, parentFragment, () -> getConnectionsManager().sendRequest(req, requestDelegate));
if (req.is_admin || addingNew) {
addUserToChat(chatId, user, 0, botHash, parentFragment, true, () -> getConnectionsManager().sendRequest(req, requestDelegate), onError);
} else {
getConnectionsManager().sendRequest(req, requestDelegate);
}
@ -5102,6 +5156,8 @@ public class MessagesController extends BaseController implements NotificationCe
});
}
allDialogs.remove(dialog);
dialogsMyChannels.remove(dialog);
dialogsMyGroups.remove(dialog);
dialogsCanAddUsers.remove(dialog);
dialogsChannelsOnly.remove(dialog);
dialogsGroupsOnly.remove(dialog);
@ -5386,6 +5442,7 @@ public class MessagesController extends BaseController implements NotificationCe
}
TLRPC.TL_channels_deleteHistory req = new TLRPC.TL_channels_deleteHistory();
req.channel = new TLRPC.TL_inputChannel();
req.for_everyone = revoke;
req.channel.channel_id = peer.channel_id;
req.channel.access_hash = peer.access_hash;
req.max_id = max_id_delete > 0 ? max_id_delete : Integer.MAX_VALUE;
@ -5393,6 +5450,9 @@ public class MessagesController extends BaseController implements NotificationCe
if (newTaskId != 0) {
getMessagesStorage().removePendingTask(newTaskId);
}
if (response != null) {
processUpdates((TLRPC.Updates) response, false);
}
}, ConnectionsManager.RequestFlagInvokeAfter);
} else {
TLRPC.TL_messages_deleteHistory req = new TLRPC.TL_messages_deleteHistory();
@ -6802,6 +6862,10 @@ public class MessagesController extends BaseController implements NotificationCe
reload = ((SystemClock.elapsedRealtime() - lastScheduledServerQueryTime.get(dialogId, 0L)) > 60 * 1000);
} else {
reload = resCount == 0 && (!isInitialLoading || (SystemClock.elapsedRealtime() - lastServerQueryTime.get(dialogId, 0L)) > 60 * 1000);
if (mode == 0 && isCache && dialogId < 0 && !dialogs_dict.containsKey(dialogId) && (SystemClock.elapsedRealtime() - lastServerQueryTime.get(dialogId, 0L)) > 24 * 60 * 60 * 1000) {
messagesRes.messages.clear();
reload = true;
}
}
if (!DialogObject.isEncryptedDialog(dialogId) && isCache && reload) {
int hash;
@ -7336,6 +7400,7 @@ public class MessagesController extends BaseController implements NotificationCe
editor.putInt("EnableChannel2", notify_settings.mute_until);
}
}
applySoundSettings(notify_settings.android_sound, editor, 0, type, false);
editor.commit();
if (loadingNotificationSettings == 0) {
getUserConfig().notificationsSettingsLoaded = true;
@ -8368,6 +8433,7 @@ public class MessagesController extends BaseController implements NotificationCe
}
getMessagesStorage().setDialogFlags(dialogId, 0);
}
applySoundSettings(notify_settings.android_sound, editor, dialogId, 0, false);
editor.commit();
if (updated) {
getNotificationCenter().postNotificationName(NotificationCenter.notificationsSettingsUpdated);
@ -9649,6 +9715,10 @@ public class MessagesController extends BaseController implements NotificationCe
}
public void addUserToChat(long chatId, TLRPC.User user, int forwardCount, String botHash, BaseFragment fragment, Runnable onFinishRunnable) {
addUserToChat(chatId, user, forwardCount, botHash, fragment, false, onFinishRunnable, null);
}
public void addUserToChat(long chatId, TLRPC.User user, int forwardCount, String botHash, BaseFragment fragment, boolean ignoreIfAlreadyExists, Runnable onFinishRunnable, Runnable onError) {
if (user == null) {
return;
}
@ -9700,6 +9770,15 @@ public class MessagesController extends BaseController implements NotificationCe
AndroidUtilities.runOnUIThread(() -> joiningToChannels.remove(chatId));
}
if (error != null) {
if ("USER_ALREADY_PARTICIPANT".equals(error.text) && ignoreIfAlreadyExists) {
if (onFinishRunnable != null) {
AndroidUtilities.runOnUIThread(onFinishRunnable);
}
return;
}
if (onError != null) {
AndroidUtilities.runOnUIThread(onError);
}
AndroidUtilities.runOnUIThread(() -> {
AlertsCreator.processError(currentAccount, error, fragment, request, isChannel && !isMegagroup);
if (isChannel && inputUser instanceof TLRPC.TL_inputUserSelf) {
@ -9963,6 +10042,7 @@ public class MessagesController extends BaseController implements NotificationCe
getConnectionsManager().cleanup(type == 2);
}
getUserConfig().clearConfig();
SharedPrefsHelper.cleanupAccount(currentAccount);
boolean shouldHandle = true;
ArrayList<NotificationCenter.NotificationCenterDelegate> observers = getNotificationCenter().getObservers(NotificationCenter.appDidLogout);
@ -10873,19 +10953,22 @@ public class MessagesController extends BaseController implements NotificationCe
arr.add(obj);
}
AndroidUtilities.runOnUIThread(() -> {
for (int a = 0; a < messages.size(); a++) {
long key = messages.keyAt(a);
ArrayList<MessageObject> value = messages.valueAt(a);
updateInterfaceWithMessages(key, value, false);
}
getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload);
});
getMessagesStorage().getStorageQueue().postRunnable(() -> {
if (!pushMessages.isEmpty()) {
AndroidUtilities.runOnUIThread(() -> getNotificationsController().processNewMessages(pushMessages, !(res instanceof TLRPC.TL_updates_differenceSlice), false, null));
}
getMessagesStorage().putMessages(res.new_messages, true, false, false, getDownloadController().getAutodownloadMask(), false);
for (int a = 0; a < messages.size(); a++) {
long dialogId = messages.keyAt(a);
ArrayList<MessageObject> arr = messages.valueAt(a);
getMediaDataController().loadReplyMessagesForMessages(arr, dialogId, false, () -> {
AndroidUtilities.runOnUIThread(() -> {
updateInterfaceWithMessages(dialogId, arr, false);
getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload);
});
});
}
});
getSecretChatHelper().processPendingEncMessages();
@ -13256,6 +13339,21 @@ public class MessagesController extends BaseController implements NotificationCe
updatesOnMainThread = new ArrayList<>();
}
updatesOnMainThread.add(baseUpdate);
} else if (baseUpdate instanceof TLRPC.TL_updateWebViewResultSent) {
if (updatesOnMainThread == null) {
updatesOnMainThread = new ArrayList<>();
}
updatesOnMainThread.add(baseUpdate);
} else if (baseUpdate instanceof TLRPC.TL_updateAttachMenuBots) {
if (updatesOnMainThread == null) {
updatesOnMainThread = new ArrayList<>();
}
updatesOnMainThread.add(baseUpdate);
} else if (baseUpdate instanceof TLRPC.TL_updateBotMenuButton) {
if (updatesOnMainThread == null) {
updatesOnMainThread = new ArrayList<>();
}
updatesOnMainThread.add(baseUpdate);
} else if (baseUpdate instanceof TLRPC.TL_updateReadChannelDiscussionInbox) {
if (updatesOnMainThread == null) {
updatesOnMainThread = new ArrayList<>();
@ -13281,6 +13379,11 @@ public class MessagesController extends BaseController implements NotificationCe
updatesOnMainThread = new ArrayList<>();
}
updatesOnMainThread.add(baseUpdate);
} else if (baseUpdate instanceof TLRPC.TL_updateSavedRingtones) {
if (updatesOnMainThread == null) {
updatesOnMainThread = new ArrayList<>();
}
updatesOnMainThread.add(baseUpdate);
}
}
if (messages != null) {
@ -13564,6 +13667,7 @@ public class MessagesController extends BaseController implements NotificationCe
editor.remove("notify2_" + dialogId);
getMessagesStorage().setDialogFlags(dialogId, 0);
}
applySoundSettings(update.notify_settings.android_sound, editor, dialogId, 0, true);
} else if (update.peer instanceof TLRPC.TL_notifyChats) {
if ((update.notify_settings.flags & 1) != 0) {
editor.putBoolean("EnablePreviewGroup", update.notify_settings.show_previews);
@ -13582,6 +13686,7 @@ public class MessagesController extends BaseController implements NotificationCe
AndroidUtilities.runOnUIThread(() -> getNotificationsController().deleteNotificationChannelGlobal(NotificationsController.TYPE_GROUP));
}
}
applySoundSettings(update.notify_settings.android_sound, editor, 0, NotificationsController.TYPE_GROUP, false);
} else if (update.peer instanceof TLRPC.TL_notifyUsers) {
if ((update.notify_settings.flags & 1) != 0) {
editor.putBoolean("EnablePreviewAll", update.notify_settings.show_previews);
@ -13593,11 +13698,12 @@ public class MessagesController extends BaseController implements NotificationCe
editor.remove("GlobalSoundPath");
}*/
}
applySoundSettings(update.notify_settings.android_sound, editor, 0, TYPE_PRIVATE, false);
if ((update.notify_settings.flags & 4) != 0) {
if (notificationsPreferences.getInt("EnableAll2", 0) != update.notify_settings.mute_until) {
editor.putInt("EnableAll2", update.notify_settings.mute_until);
editor.putBoolean("overwrite_private", true);
AndroidUtilities.runOnUIThread(() -> getNotificationsController().deleteNotificationChannelGlobal(NotificationsController.TYPE_PRIVATE));
AndroidUtilities.runOnUIThread(() -> getNotificationsController().deleteNotificationChannelGlobal(TYPE_PRIVATE));
}
}
} else if (update.peer instanceof TLRPC.TL_notifyBroadcasts) {
@ -13618,6 +13724,7 @@ public class MessagesController extends BaseController implements NotificationCe
AndroidUtilities.runOnUIThread(() -> getNotificationsController().deleteNotificationChannelGlobal(NotificationsController.TYPE_CHANNEL));
}
}
applySoundSettings(update.notify_settings.android_sound, editor, 0, TYPE_CHANNEL, false);
}
getMessagesStorage().updateMutedDialogsFiltersCounters();
}
@ -13889,6 +13996,14 @@ public class MessagesController extends BaseController implements NotificationCe
loadRemoteFilters(true);
} else if (baseUpdate instanceof TLRPC.TL_updateDialogFilters) {
loadRemoteFilters(true);
} else if (baseUpdate instanceof TLRPC.TL_updateWebViewResultSent) {
TLRPC.TL_updateWebViewResultSent resultSent = (TLRPC.TL_updateWebViewResultSent) baseUpdate;
getNotificationCenter().postNotificationName(NotificationCenter.webViewResultSent, resultSent.query_id);
} else if (baseUpdate instanceof TLRPC.TL_updateAttachMenuBots) {
getMediaDataController().loadAttachMenuBots(false, true);
} else if (baseUpdate instanceof TLRPC.TL_updateBotMenuButton) {
TLRPC.TL_updateBotMenuButton updateBotMenuButton = (TLRPC.TL_updateBotMenuButton) baseUpdate;
getNotificationCenter().postNotificationName(NotificationCenter.updateBotMenuButton, updateBotMenuButton.bot_id, updateBotMenuButton.button);
} else if (baseUpdate instanceof TLRPC.TL_updateReadChannelDiscussionInbox) {
TLRPC.TL_updateReadChannelDiscussionInbox update = (TLRPC.TL_updateReadChannelDiscussionInbox) baseUpdate;
getNotificationCenter().postNotificationName(NotificationCenter.threadMessagesRead, -update.channel_id, update.top_msg_id, update.read_max_id, 0);
@ -13945,6 +14060,8 @@ public class MessagesController extends BaseController implements NotificationCe
} else if (baseUpdate instanceof TLRPC.TL_updatePendingJoinRequests) {
TLRPC.TL_updatePendingJoinRequests update = (TLRPC.TL_updatePendingJoinRequests) baseUpdate;
getMemberRequestsController().onPendingRequestsUpdated(update);
} else if (baseUpdate instanceof TLRPC.TL_updateSavedRingtones) {
getMediaDataController().ringtoneDataStore.loadUserRingtones();
}
}
if (editor != null) {
@ -14337,7 +14454,6 @@ public class MessagesController extends BaseController implements NotificationCe
if (reactionsMentionsMessageIds.get(messageId) != hasUnreadReaction) {
newUnreadCount += hasUnreadReaction ? 1 : -1;
changed = true;
}
} else {
needReload = true;
@ -14360,13 +14476,14 @@ public class MessagesController extends BaseController implements NotificationCe
}
}
if (needReload) {
TLRPC.TL_messages_getUnreadReactions req = new TLRPC.TL_messages_getUnreadReactions();
req.limit = 1;
req.peer = getInputPeer(dialogId);
TLRPC.TL_messages_getPeerDialogs req = new TLRPC.TL_messages_getPeerDialogs();
TLRPC.TL_inputDialogPeer inputDialogPeer = new TLRPC.TL_inputDialogPeer();
inputDialogPeer.peer = getInputPeer(dialogId);
req.peers.add(inputDialogPeer);
ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> {
if (response != null) {
TLRPC.messages_Messages messages = (TLRPC.messages_Messages) response;
int count = Math.max(messages.count, messages.messages.size());
TLRPC.TL_messages_peerDialogs dialogs = (TLRPC.TL_messages_peerDialogs) response;
int count = dialogs.dialogs.size() == 0 ? 0 : dialogs.dialogs.get(0).unread_reactions_count;
AndroidUtilities.runOnUIThread(() -> {
TLRPC.Dialog dialog = dialogs_dict.get(dialogId);
if (dialog == null) {
@ -14402,6 +14519,10 @@ public class MessagesController extends BaseController implements NotificationCe
return isDialogMuted(dialogId, null);
}
public boolean isDialogNotificationsSoundEnabled(long dialogId) {
return notificationsPreferences.getBoolean("sound_enabled_" + dialogId, true);
}
public boolean isDialogMuted(long dialogId, TLRPC.Chat chat) {
int mute_type = notificationsPreferences.getInt("notify2_" + dialogId, -1);
if (mute_type == -1) {
@ -14707,6 +14828,8 @@ public class MessagesController extends BaseController implements NotificationCe
allDialogs.remove(dialog);
dialogsServerOnly.remove(dialog);
dialogsCanAddUsers.remove(dialog);
dialogsMyGroups.remove(dialog);
dialogsMyChannels.remove(dialog);
dialogsChannelsOnly.remove(dialog);
dialogsGroupsOnly.remove(dialog);
for (int a = 0; a < selectedDialogFilter.length; a++) {
@ -14895,6 +15018,8 @@ public class MessagesController extends BaseController implements NotificationCe
public void sortDialogs(LongSparseArray<TLRPC.Chat> chatsDict) {
dialogsServerOnly.clear();
dialogsCanAddUsers.clear();
dialogsMyGroups.clear();
dialogsMyChannels.clear();
dialogsChannelsOnly.clear();
dialogsGroupsOnly.clear();
for (int a = 0; a < selectedDialogFilter.length; a++) {
@ -14982,8 +15107,16 @@ public class MessagesController extends BaseController implements NotificationCe
dialogsServerOnly.add(d);
if (DialogObject.isChannel(d)) {
TLRPC.Chat chat = getChat(-d.id);
if (chat != null && chat.megagroup && (chat.admin_rights != null && (chat.admin_rights.post_messages || chat.admin_rights.add_admins) || chat.creator)) {
dialogsCanAddUsers.add(d);
if (chat != null && (chat.creator || chat.megagroup && (chat.admin_rights != null && (chat.admin_rights.post_messages || chat.admin_rights.add_admins) || chat.default_banned_rights == null || !chat.default_banned_rights.invite_users) || !chat.megagroup && chat.admin_rights != null && chat.admin_rights.add_admins)) {
if (chat.creator || chat.megagroup && chat.admin_rights != null || !chat.megagroup && chat.admin_rights != null) {
if (chat.megagroup) {
dialogsMyGroups.add(d);
} else {
dialogsMyChannels.add(d);
}
} else {
dialogsCanAddUsers.add(d);
}
}
if (chat != null && chat.megagroup) {
dialogsGroupsOnly.add(d);
@ -15002,7 +15135,14 @@ public class MessagesController extends BaseController implements NotificationCe
continue;
}
}
dialogsCanAddUsers.add(d);
TLRPC.Chat chat = getChat(-d.id);
if (chat != null && (chat.admin_rights != null && (chat.admin_rights.add_admins || chat.admin_rights.invite_users) || chat.creator)) {
if (chat.creator) {
dialogsMyGroups.add(d);
} else {
dialogsCanAddUsers.add(d);
}
}
dialogsGroupsOnly.add(d);
} else if (d.id != selfId) {
dialogsUsersOnly.add(d);
@ -15328,9 +15468,9 @@ public class MessagesController extends BaseController implements NotificationCe
int lastMessageId = (int) args[4];
if ((size < count / 2 && !isEnd) && isCache) {
if (finalMessageId != 0) {
loadMessagesInternal(dialogId, 0, false, count, finalMessageId, 0, false, 0, classGuid, 3, lastMessageId, 0, 0, 0, 0, 0, 0, false, 0, true, false);
loadMessagesInternal(dialogId, 0, false, count, finalMessageId, 0, false, 0, classGuid, 3, lastMessageId, 0, 0, -1, 0, 0, 0, false, 0, true, false);
} else {
loadMessagesInternal(dialogId, 0, false, count, finalMessageId, 0, false, 0, classGuid, 2, lastMessageId, 0, 0, 0, 0, 0, 0, false, 0, true, false);
loadMessagesInternal(dialogId, 0, false, count, finalMessageId, 0, false, 0, classGuid, 2, lastMessageId, 0, 0, -1, 0, 0, 0, false, 0, true, false);
}
} else {
getNotificationCenter().removeObserver(this, NotificationCenter.messagesDidLoadWithoutProcess);
@ -15354,9 +15494,9 @@ public class MessagesController extends BaseController implements NotificationCe
getNotificationCenter().addObserver(delegate, NotificationCenter.loadingMessagesFailed);
if (messageId != 0) {
loadMessagesInternal(dialogId, 0, true, count, finalMessageId, 0, true, 0, classGuid, 3, 0, 0, 0, 0, 0, 0, 0, false, 0, true, false);
loadMessagesInternal(dialogId, 0, true, count, finalMessageId, 0, true, 0, classGuid, 3, 0, 0, 0, -1, 0, 0, 0, false, 0, true, false);
} else {
loadMessagesInternal(dialogId, 0, true, count, finalMessageId, 0, true, 0, classGuid, 2, 0, 0, 0, 0, 0, 0, 0, false, 0, true, false);
loadMessagesInternal(dialogId, 0, true, count, finalMessageId, 0, true, 0, classGuid, 2, 0, 0, 0, -1, 0, 0, 0, false, 0, true, false);
}
}
@ -15426,6 +15566,111 @@ public class MessagesController extends BaseController implements NotificationCe
});
}
public void checkIsInChat(TLRPC.Chat chat, TLRPC.User user, IsInChatCheckedCallback callback) {
if (chat == null || user == null) {
if (callback != null) {
callback.run(false, null, null);
}
return;
}
if (chat.megagroup || ChatObject.isChannel(chat)) {
TLRPC.TL_channels_getParticipant req = new TLRPC.TL_channels_getParticipant();
req.channel = getInputChannel(chat.id);
req.participant = getInputPeer(user);
getConnectionsManager().sendRequest(req, (res, err) -> {
if (callback != null) {
TLRPC.ChannelParticipant participant = res instanceof TLRPC.TL_channels_channelParticipant ? ((TLRPC.TL_channels_channelParticipant) res).participant : null;
callback.run(
err == null && participant != null && !participant.left,
participant != null ? participant.admin_rights : null,
participant != null ? participant.rank : null
);
}
});
} else {
TLRPC.ChatFull chatFull = getChatFull(chat.id);
if (chatFull != null) {
TLRPC.ChatParticipant userParticipant = null;
if (chatFull.participants != null && chatFull.participants.participants != null) {
final int count = chatFull.participants.participants.size();
for (int i = 0; i < count; ++i) {
TLRPC.ChatParticipant participant = chatFull.participants.participants.get(i);
if (participant != null && participant.user_id == user.id) {
userParticipant = participant;
break;
}
}
}
if (callback != null) {
callback.run(
userParticipant != null,
chatFull.participants != null && chatFull.participants.admin_id == user.id ? ChatRightsEditActivity.emptyAdminRights(true) : null,
null
);
}
} else {
if (callback != null) {
callback.run(false, null, null);
}
}
}
}
private void applySoundSettings(TLRPC.NotificationSound settings, SharedPreferences.Editor editor, long dialogId, int globalType, boolean serverUpdate) {
if (settings == null) {
return;
}
String soundPref;
String soundPathPref;
String soundDocPref;
if (dialogId != 0) {
soundPref = "sound_" + dialogId;
soundPathPref = "sound_path_" + dialogId;
soundDocPref = "sound_document_id_" + dialogId;
} else {
if (globalType == NotificationsController.TYPE_GROUP) {
soundPref = "GroupSound";
soundDocPref = "GroupSoundDocId";
soundPathPref = "GroupSoundPath";
} else if (globalType == TYPE_PRIVATE) {
soundPref = "GlobalSound";
soundDocPref = "GlobalSoundDocId";
soundPathPref = "GlobalSoundPath";
} else {
soundPref = "ChannelSound";
soundDocPref = "ChannelSoundDocId";
soundPathPref = "ChannelSoundPath";
}
}
if (settings instanceof TLRPC.TL_notificationSoundDefault) {
editor.putString(soundPref, "Default");
editor.putString(soundPathPref, "Default");
editor.remove(soundDocPref);
} else if (settings instanceof TLRPC.TL_notificationSoundNone) {
editor.putString(soundPref, "NoSound");
editor.putString(soundPathPref, "NoSound");
editor.remove(soundDocPref);
} else if (settings instanceof TLRPC.TL_notificationSoundLocal) {
TLRPC.TL_notificationSoundLocal localSound = (TLRPC.TL_notificationSoundLocal) settings;
editor.putString(soundPref, localSound.title);
editor.putString(soundPathPref, localSound.data);
editor.remove(soundDocPref);
} else if (settings instanceof TLRPC.TL_notificationSoundRingtone) {
TLRPC.TL_notificationSoundRingtone soundRingtone = (TLRPC.TL_notificationSoundRingtone) settings;
editor.putLong(soundDocPref, soundRingtone.id);
getMediaDataController().checkRingtones();
if (serverUpdate && dialogId != 0) {
editor.putBoolean("custom_" + dialogId, true);
}
getMediaDataController().ringtoneDataStore.getDocument(soundRingtone.id);
}
}
public interface IsInChatCheckedCallback {
void run(boolean isInChat, TLRPC.TL_chatAdminRights currentAdminRights, String rank);
}
public interface MessagesLoadedCallback {
void onMessagesLoaded(boolean fromCache);

View File

@ -88,7 +88,7 @@ public class MessagesStorage extends BaseController {
private CountDownLatch openSync = new CountDownLatch(1);
private static SparseArray<MessagesStorage> Instance = new SparseArray();
private final static int LAST_DB_VERSION = 92;
private final static int LAST_DB_VERSION = 93;
private boolean databaseMigrationInProgress;
public boolean showClearDatabaseAlert;
@ -400,6 +400,8 @@ public class MessagesStorage extends BaseController {
database.executeFast("CREATE INDEX IF NOT EXISTS reaction_mentions_did ON reaction_mentions(dialog_id);").stepThis().dispose();
database.executeFast("CREATE TABLE downloading_documents(data BLOB, hash INTEGER, id INTEGER, state INTEGER, date INTEGER, PRIMARY KEY(hash, id));").stepThis().dispose();
database.executeFast("CREATE TABLE attach_menu_bots(data BLOB, hash INTEGER, date INTEGER);").stepThis().dispose();
//version
database.executeFast("PRAGMA user_version = " + LAST_DB_VERSION).stepThis().dispose();
} else {
@ -1571,6 +1573,11 @@ public class MessagesStorage extends BaseController {
version = 92;
}
if (version == 92) {
database.executeFast("CREATE TABLE IF NOT EXISTS attach_menu_bots(data BLOB, hash INTEGER, date INTEGER);").stepThis().dispose();
database.executeFast("PRAGMA user_version = 93").stepThis().dispose();
}
FileLog.d("MessagesStorage db migration finished");
AndroidUtilities.runOnUIThread(() -> {
databaseMigrationInProgress = false;
@ -2097,6 +2104,7 @@ public class MessagesStorage extends BaseController {
database.executeFast("DELETE FROM reaction_mentions").stepThis().dispose();
database.executeFast("DELETE FROM downloading_documents").stepThis().dispose();
database.executeFast("DELETE FROM attach_menu_bots").stepThis().dispose();
SQLiteCursor cursor = database.queryFinalized("SELECT did FROM dialogs WHERE 1");
StringBuilder ids = new StringBuilder();
@ -2172,6 +2180,7 @@ public class MessagesStorage extends BaseController {
} finally {
AndroidUtilities.runOnUIThread(() -> {
NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.didClearDatabase);
getMediaDataController().loadAttachMenuBots(false, true);
});
}
});
@ -3384,7 +3393,7 @@ public class MessagesStorage extends BaseController {
arrayList.add(message);
}
protected void loadReplyMessages(LongSparseArray<SparseArray<ArrayList<TLRPC.Message>>> replyMessageOwners, LongSparseArray<ArrayList<Integer>> dialogReplyMessagesIds, ArrayList<Long> usersToLoad, ArrayList<Long> chatsToLoad) throws SQLiteException {
protected void loadReplyMessages(LongSparseArray<SparseArray<ArrayList<TLRPC.Message>>> replyMessageOwners, LongSparseArray<ArrayList<Integer>> dialogReplyMessagesIds, ArrayList<Long> usersToLoad, ArrayList<Long> chatsToLoad, boolean scheduled) throws SQLiteException {
if (replyMessageOwners.isEmpty()) {
return;
}
@ -3396,7 +3405,12 @@ public class MessagesStorage extends BaseController {
if (ids == null) {
continue;
}
SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid, date, uid FROM messages_v2 WHERE mid IN(%s) AND uid = %d", TextUtils.join(",", ids), dialogId));
SQLiteCursor cursor;
if (scheduled) {
cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid, date, uid FROM scheduled_messages_v2 WHERE mid IN(%s) AND uid = %d", TextUtils.join(",", ids), dialogId));
} else {
cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid, date, uid FROM messages_v2 WHERE mid IN(%s) AND uid = %d", TextUtils.join(",", ids), dialogId));
}
while (cursor.next()) {
NativeByteBuffer data = cursor.byteBufferValue(0);
if (data != null) {
@ -3563,7 +3577,7 @@ public class MessagesStorage extends BaseController {
}
cursor.dispose();
loadReplyMessages(replyMessageOwners, dialogReplyMessagesIds, usersToLoad, chatsToLoad);
loadReplyMessages(replyMessageOwners, dialogReplyMessagesIds, usersToLoad, chatsToLoad, false);
if (!encryptedChatIds.isEmpty()) {
getEncryptedChatsInternal(TextUtils.join(",", encryptedChatIds), encryptedChats, usersToLoad);
@ -7479,7 +7493,7 @@ public class MessagesStorage extends BaseController {
}
}
} else {
loadReplyMessages(replyMessageOwners, dialogReplyMessagesIds, usersToLoad, chatsToLoad);
loadReplyMessages(replyMessageOwners, dialogReplyMessagesIds, usersToLoad, chatsToLoad, scheduled);
}
if (!usersToLoad.isEmpty()) {
getUsersInternal(TextUtils.join(",", usersToLoad), res.users);
@ -11573,7 +11587,7 @@ public class MessagesStorage extends BaseController {
cursor.dispose();
}
loadReplyMessages(replyMessageOwners, dialogReplyMessagesIds, usersToLoad, chatsToLoad);
loadReplyMessages(replyMessageOwners, dialogReplyMessagesIds, usersToLoad, chatsToLoad, false);
if (draftsDialogIds != null) {
ArrayList<Long> unloadedDialogs = new ArrayList<>();
@ -12628,7 +12642,7 @@ public class MessagesStorage extends BaseController {
state.dispose();
if (count == 0) {
state = database.executeFast("UPDATE reaction_mentions SET state = 0 WHERE dialog_id ?");
state = database.executeFast("UPDATE reaction_mentions SET state = 0 WHERE dialog_id = ?");
state.bindLong(1, dialogId);
state.step();
state.dispose();

View File

@ -123,6 +123,7 @@ public class NotificationCenter {
public static final int stickersImportProgressChanged = totalEvents++;
public static final int stickersImportComplete = totalEvents++;
public static final int dialogDeleted = totalEvents++;
public static final int webViewResultSent = totalEvents++;
public static final int didGenerateFingerprintKeyPair = totalEvents++;
@ -190,6 +191,8 @@ public class NotificationCenter {
public static final int filterSettingsUpdated = totalEvents++;
public static final int suggestedFiltersLoaded = totalEvents++;
public static final int updateBotMenuButton = totalEvents++;
//global
public static final int pushMessagesUpdated = totalEvents++;
public static final int stopEncodingService = totalEvents++;
@ -235,10 +238,14 @@ public class NotificationCenter {
public static final int onEmojiInteractionsReceived = totalEvents++;
public static final int emojiPreviewThemesChanged = totalEvents++;
public static final int reactionsDidLoad = totalEvents++;
public static final int attachMenuBotsDidLoad = totalEvents++;
public static final int chatAvailableReactionsUpdated = totalEvents++;
public static final int dialogsUnreadReactionsCounterChanged = totalEvents++;
public static final int onDatabaseOpened = totalEvents++;
public static final int onDownloadingFilesChanged = totalEvents++;
public static final int onActivityResultReceived = totalEvents++;
public static final int onRequestPermissionResultReceived = totalEvents++;
public static final int onUserRingtonesUpdated = totalEvents++;
// custom

View File

@ -43,6 +43,10 @@ import android.os.PowerManager;
import android.os.SystemClock;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.text.TextUtils;
import android.util.SparseArray;
@ -56,9 +60,6 @@ import androidx.core.content.LocusIdCompat;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.IconCompat;
import android.text.TextUtils;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import org.telegram.messenger.support.LongSparseIntArray;
import org.telegram.tgnet.ConnectionsManager;
@ -137,6 +138,9 @@ public class NotificationsController extends BaseController {
private int notificationId;
private String notificationGroup;
public static final int SETTING_SOUND_ON = 0;
public static final int SETTING_SOUND_OFF = 1;
static {
if (Build.VERSION.SDK_INT >= 26 && ApplicationLoader.applicationContext != null) {
notificationManager = NotificationManagerCompat.from(ApplicationLoader.applicationContext);
@ -247,6 +251,40 @@ public class NotificationsController extends BaseController {
}
}
public void muteUntil(long did, int selectedTimeInSeconds) {
if (did != 0) {
SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount);
SharedPreferences.Editor editor = preferences.edit();
long flags;
boolean defaultEnabled = NotificationsController.getInstance(currentAccount).isGlobalNotificationsEnabled(did);
if (selectedTimeInSeconds == Integer.MAX_VALUE) {
if (!defaultEnabled) {
editor.remove("notify2_" + did);
flags = 0;
} else {
editor.putInt("notify2_" + did, 2);
flags = 1;
}
} else {
editor.putInt("notify2_" + did, 3);
editor.putInt("notifyuntil_" + did, getConnectionsManager().getCurrentTime() + selectedTimeInSeconds);
flags = ((long) selectedTimeInSeconds << 32) | 1;
}
NotificationsController.getInstance(currentAccount).removeNotificationsForDialog(did);
MessagesStorage.getInstance(currentAccount).setDialogFlags(did, flags);
editor.commit();
TLRPC.Dialog dialog = MessagesController.getInstance(currentAccount).dialogs_dict.get(did);
if (dialog != null) {
dialog.notify_settings = new TLRPC.TL_peerNotifySettings();
if (selectedTimeInSeconds != Integer.MAX_VALUE || defaultEnabled) {
dialog.notify_settings.mute_until = selectedTimeInSeconds;
}
}
NotificationsController.getInstance(currentAccount).updateServerNotificationsSettings(did);
}
}
public void cleanup() {
popupMessages.clear();
popupReplyMessages.clear();
@ -3130,6 +3168,10 @@ public class NotificationsController extends BaseController {
boolean secretChat = !isDefault && DialogObject.isEncryptedDialog(dialogId);
boolean shouldOverwrite = !isInApp && overwriteKey != null && preferences.getBoolean(overwriteKey, false);
String soundHash = Utilities.MD5(sound == null ? "NoSound" : sound.toString());
if (soundHash != null && soundHash.length() > 5) {
soundHash = soundHash.substring(0, 5);
}
if (isSilent) {
name = LocaleController.getString("NotificationsSilent", R.string.NotificationsSilent);
key = "silent";
@ -3148,6 +3190,7 @@ public class NotificationsController extends BaseController {
}
key = (isInApp ? "org.telegram.keyia" : "org.telegram.key") + dialogId;
}
key += "_" + soundHash;
String channelId = preferences.getString(key, null);
String settings = preferences.getString(key + "_s", null);
boolean edited = false;
@ -3231,66 +3274,6 @@ public class NotificationsController extends BaseController {
}
edited = true;
}
if (channelSound == null && sound != null || channelSound != null && (sound == null || !TextUtils.equals(channelSound.toString(), sound.toString()))) {
if (!isInApp) {
if (editor == null) {
editor = preferences.edit();
}
String newSound;
if (channelSound == null) {
newSound = "NoSound";
if (isDefault) {
if (type == TYPE_CHANNEL) {
editor.putString("ChannelSound", "NoSound");
} else if (type == TYPE_GROUP) {
editor.putString("GroupSound", "NoSound");
} else {
editor.putString("GlobalSound", "NoSound");
}
} else {
editor.putString("sound_" + dialogId, "NoSound");
}
} else {
newSound = channelSound.toString();
Ringtone rng = RingtoneManager.getRingtone(ApplicationLoader.applicationContext, channelSound);
String ringtoneName = null;
if (rng != null) {
if (channelSound.equals(Settings.System.DEFAULT_RINGTONE_URI)) {
ringtoneName = LocaleController.getString("DefaultRingtone", R.string.DefaultRingtone);
} else {
ringtoneName = rng.getTitle(ApplicationLoader.applicationContext);
}
rng.stop();
}
if (ringtoneName != null) {
if (isDefault) {
if (type == TYPE_CHANNEL) {
editor.putString("ChannelSound", ringtoneName);
} else if (type == TYPE_GROUP) {
editor.putString("GroupSound", ringtoneName);
} else {
editor.putString("GlobalSound", ringtoneName);
}
} else {
editor.putString("sound_" + dialogId, ringtoneName);
}
}
}
if (isDefault) {
if (type == TYPE_CHANNEL) {
editor.putString("ChannelSoundPath", newSound);
} else if (type == TYPE_GROUP) {
editor.putString("GroupSoundPath", newSound);
} else {
editor.putString("GlobalSoundPath", newSound);
}
} else {
editor.putString("sound_path_" + dialogId, newSound);
}
}
sound = channelSound;
edited = true;
}
boolean hasVibration = !isEmptyVibration(vibrationPattern);
if (hasVibration != vibrate) {
if (!isInApp) {
@ -3402,7 +3385,7 @@ public class NotificationsController extends BaseController {
if (sound != null) {
notificationChannel.setSound(sound, builder.build());
} else {
notificationChannel.setSound(null, builder.build());
notificationChannel.setSound(null, null);
}
systemNotificationManager.createNotificationChannel(notificationChannel);
if (BuildVars.LOGS_ENABLED) {
@ -3459,6 +3442,7 @@ public class NotificationsController extends BaseController {
boolean notifyDisabled = false;
int vibrate = 0;
String soundPath = null;
boolean isInternalSoundFile = false;
int ledColor = 0xff0000ff;
int importance = 0;
@ -3523,7 +3507,7 @@ public class NotificationsController extends BaseController {
MessageObject messageObject = pushMessages.get(0);
boolean[] text = new boolean[1];
String message = lastMessage = getStringForMessage(messageObject, false, text, null);
silent = messageObject.messageOwner.silent ? 1 : 0;
silent = isSilentMessage(messageObject) ? 1 : 0;
if (message == null) {
return;
}
@ -3554,7 +3538,7 @@ public class NotificationsController extends BaseController {
}
if (silent == 2) {
lastMessage = message;
silent = messageObject.messageOwner.silent ? 1 : 0;
silent = isSilentMessage(messageObject) ? 1 : 0;
}
if (pushDialogs.size() == 1) {
if (replace) {
@ -3610,6 +3594,10 @@ public class NotificationsController extends BaseController {
}
}
if (!notifyDisabled && !preferences.getBoolean("sound_enabled_" + dialog_id, true)) {
notifyDisabled = true;
}
String defaultPath = Settings.System.DEFAULT_NOTIFICATION_URI.getPath();
boolean isDefault = true;
@ -3617,13 +3605,20 @@ public class NotificationsController extends BaseController {
int chatType = TYPE_PRIVATE;
String customSoundPath;
boolean customIsInternalSound = false;
int customVibrate;
int customImportance;
Integer customLedColor;
if (preferences.getBoolean("custom_" + dialog_id, false)) {
customVibrate = preferences.getInt("vibrate_" + dialog_id, 0);
customImportance = preferences.getInt("priority_" + dialog_id, 3);
customSoundPath = preferences.getString("sound_path_" + dialog_id, null);
long soundDocumentId = preferences.getLong("sound_document_id_" + dialog_id, 0);
if (soundDocumentId != 0) {
customIsInternalSound = true;
customSoundPath = getMediaDataController().ringtoneDataStore.getSoundPath(soundDocumentId);
} else {
customSoundPath = preferences.getString("sound_path_" + dialog_id, null);
}
if (preferences.contains("color_" + dialog_id)) {
customLedColor = preferences.getInt("color_" + dialog_id, 0);
} else {
@ -3639,20 +3634,38 @@ public class NotificationsController extends BaseController {
if (chatId != 0) {
if (isChannel) {
soundPath = preferences.getString("ChannelSoundPath", defaultPath);
long soundDocumentId = preferences.getLong("ChannelSoundDocId", 0);
if (soundDocumentId != 0) {
isInternalSoundFile = true;
soundPath = getMediaDataController().ringtoneDataStore.getSoundPath(soundDocumentId);
} else {
soundPath = preferences.getString("ChannelSoundPath", defaultPath);
}
vibrate = preferences.getInt("vibrate_channel", 0);
importance = preferences.getInt("priority_channel", 1);
ledColor = preferences.getInt("ChannelLed", 0xff0000ff);
chatType = TYPE_CHANNEL;
} else {
soundPath = preferences.getString("GroupSoundPath", defaultPath);
long soundDocumentId = preferences.getLong("GroupSoundDocId", 0);
if (soundDocumentId != 0) {
isInternalSoundFile = true;
soundPath = getMediaDataController().ringtoneDataStore.getSoundPath(soundDocumentId);
} else {
soundPath = preferences.getString("GroupSoundPath", defaultPath);
}
vibrate = preferences.getInt("vibrate_group", 0);
importance = preferences.getInt("priority_group", 1);
ledColor = preferences.getInt("GroupLed", 0xff0000ff);
chatType = TYPE_GROUP;
}
} else if (userId != 0) {
soundPath = preferences.getString("GlobalSoundPath", defaultPath);
long soundDocumentId = preferences.getLong("GlobalSoundDocId", 0);
if (soundDocumentId != 0) {
isInternalSoundFile = true;
soundPath = getMediaDataController().ringtoneDataStore.getSoundPath(soundDocumentId);
} else {
soundPath = preferences.getString("GlobalSoundPath", defaultPath);
}
vibrate = preferences.getInt("vibrate_messages", 0);
importance = preferences.getInt("priority_messages", 1);
ledColor = preferences.getInt("MessagesLed", 0xff0000ff);
@ -3662,7 +3675,8 @@ public class NotificationsController extends BaseController {
vibrateOnlyIfSilent = true;
vibrate = 0;
}
if (customSoundPath != null && !TextUtils.equals(soundPath, customSoundPath)) {
if (!TextUtils.isEmpty(customSoundPath) && !TextUtils.equals(soundPath, customSoundPath)) {
isInternalSoundFile = customIsInternalSound;
soundPath = customSoundPath;
isDefault = false;
}
@ -3829,10 +3843,15 @@ public class NotificationsController extends BaseController {
}
if (soundPath != null && !soundPath.equals("NoSound")) {
if (Build.VERSION.SDK_INT >= 26) {
if (soundPath.equals(defaultPath)) {
if (soundPath.equals("Default") || soundPath.equals(defaultPath)) {
sound = Settings.System.DEFAULT_NOTIFICATION_URI;
} else {
sound = Uri.parse(soundPath);
if (isInternalSoundFile) {
sound = FileProvider.getUriForFile(ApplicationLoader.applicationContext, BuildConfig.APPLICATION_ID + ".provider", new File(soundPath));
ApplicationLoader.applicationContext.grantUriPermission("com.android.systemui", sound, Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
sound = Uri.parse(soundPath);
}
}
} else {
if (soundPath.equals(defaultPath)) {
@ -3909,6 +3928,10 @@ public class NotificationsController extends BaseController {
}
}
private boolean isSilentMessage(MessageObject messageObject) {
return messageObject.messageOwner.silent || messageObject.isReactionPush;
}
@SuppressLint("NewApi")
private void setNotificationChannel(Notification mainNotification, NotificationCompat.Builder builder, boolean useSummaryNotification) {
if (useSummaryNotification) {
@ -3989,7 +4012,6 @@ public class NotificationsController extends BaseController {
arrayList.add(messageObject);
}
LongSparseArray<Integer> oldIdsWear = new LongSparseArray<>();
for (int i = 0; i < wearNotificationsIds.size(); i++) {
oldIdsWear.put(wearNotificationsIds.keyAt(i), wearNotificationsIds.valueAt(i));
@ -4658,6 +4680,7 @@ public class NotificationsController extends BaseController {
public static final int SETTING_MUTE_2_DAYS = 2;
public static final int SETTING_MUTE_FOREVER = 3;
public static final int SETTING_MUTE_UNMUTE = 4;
public static final int SETTING_MUTE_CUSTOM = 5;
public void clearDialogNotificationsSettings(long did) {
SharedPreferences preferences = getAccountInstance().getNotificationsSettings();
@ -4749,7 +4772,28 @@ public class NotificationsController extends BaseController {
}
}
long soundDocumentId = preferences.getLong("sound_document_id_" + dialogId, 0);
String soundPath = preferences.getString("sound_path_" + dialogId, null);
req.settings.flags |= 8;
if (soundDocumentId != 0) {
TLRPC.TL_notificationSoundRingtone ringtoneSound = new TLRPC.TL_notificationSoundRingtone();
ringtoneSound.id = soundDocumentId;
req.settings.sound = ringtoneSound;
} else if (soundPath != null) {
if (soundPath.equals("NoSound")){
req.settings.sound = new TLRPC.TL_notificationSoundNone();
} else {
TLRPC.TL_notificationSoundLocal localSound = new TLRPC.TL_notificationSoundLocal();
localSound.title = preferences.getString("sound_" + dialogId, null);
localSound.data = soundPath;
req.settings.sound = localSound;
}
} else {
req.settings.sound = new TLRPC.TL_notificationSoundDefault();
}
req.peer = new TLRPC.TL_inputNotifyPeer();
((TLRPC.TL_inputNotifyPeer) req.peer).peer = getMessagesController().getInputPeer(dialogId);
getConnectionsManager().sendRequest(req, (response, error) -> {
@ -4765,18 +4809,51 @@ public class NotificationsController extends BaseController {
TLRPC.TL_account_updateNotifySettings req = new TLRPC.TL_account_updateNotifySettings();
req.settings = new TLRPC.TL_inputPeerNotifySettings();
req.settings.flags = 5;
String soundDocumentIdPref;
String soundPathPref;
String soundNamePref;
if (type == TYPE_GROUP) {
req.peer = new TLRPC.TL_inputNotifyChats();
req.settings.mute_until = preferences.getInt("EnableGroup2", 0);
req.settings.show_previews = preferences.getBoolean("EnablePreviewGroup", true);
soundNamePref = "GroupSound";
soundDocumentIdPref = "GroupSoundDocId";
soundPathPref = "GroupSoundPath";
} else if (type == TYPE_PRIVATE) {
req.peer = new TLRPC.TL_inputNotifyUsers();
req.settings.mute_until = preferences.getInt("EnableAll2", 0);
req.settings.show_previews = preferences.getBoolean("EnablePreviewAll", true);
soundNamePref = "GlobalSound";
soundDocumentIdPref = "GlobalSoundDocId";
soundPathPref = "GlobalSoundPath";
} else {
req.peer = new TLRPC.TL_inputNotifyBroadcasts();
req.settings.mute_until = preferences.getInt("EnableChannel2", 0);
req.settings.show_previews = preferences.getBoolean("EnablePreviewChannel", true);
soundNamePref = "ChannelSound";
soundDocumentIdPref = "ChannelSoundDocId";
soundPathPref = "ChannelSoundPath";
}
req.settings.flags |= 8;
long soundDocumentId = preferences.getLong(soundDocumentIdPref, 0);
String soundPath = preferences.getString(soundPathPref, "NoSound");
if (soundDocumentId != 0) {
TLRPC.TL_notificationSoundRingtone ringtoneSound = new TLRPC.TL_notificationSoundRingtone();
ringtoneSound.id = soundDocumentId;
req.settings.sound = ringtoneSound;
} else if (soundPath != null) {
if (soundPath.equals("NoSound")){
req.settings.sound = new TLRPC.TL_notificationSoundNone();
} else {
TLRPC.TL_notificationSoundLocal localSound = new TLRPC.TL_notificationSoundLocal();
localSound.title = preferences.getString(soundNamePref, null);
localSound.data = soundPath;
req.settings.sound = localSound;
}
} else {
req.settings.sound = new TLRPC.TL_notificationSoundDefault();
}
getConnectionsManager().sendRequest(req, (response, error) -> {
@ -4831,4 +4908,25 @@ public class NotificationsController extends BaseController {
}
}
public void muteDialog(long dialog_id, boolean mute) {
if (mute) {
NotificationsController.getInstance(currentAccount).muteUntil(dialog_id, Integer.MAX_VALUE);
} else {
boolean defaultEnabled = NotificationsController.getInstance(currentAccount).isGlobalNotificationsEnabled(dialog_id);
SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount);
SharedPreferences.Editor editor = preferences.edit();
if (defaultEnabled) {
editor.remove("notify2_" + dialog_id);
} else {
editor.putInt("notify2_" + dialog_id, 0);
}
getMessagesStorage().setDialogFlags(dialog_id, 0);
editor.apply();
TLRPC.Dialog dialog = getMessagesController().dialogs_dict.get(dialog_id);
if (dialog != null) {
dialog.notify_settings = new TLRPC.TL_peerNotifySettings();
}
updateServerNotificationsSettings(dialog_id);
}
}
}

View File

@ -2,8 +2,15 @@ package org.telegram.messenger;
import android.os.Build;
import java.lang.reflect.Field;
public class OneUIUtilities {
public final static int ONE_UI_4_0 = 40000;
private static Boolean isOneUI;
private static int oneUIEncodedVersion;
private static int oneUIMajorVersion;
private static float oneUIMinorVersion;
@SuppressWarnings("JavaReflectionMemberAccess")
public static boolean isOneUI() {
@ -12,11 +19,46 @@ public class OneUIUtilities {
}
try {
Build.VERSION.class.getDeclaredField("SEM_PLATFORM_INT");
Field f = Build.VERSION.class.getDeclaredField("SEM_PLATFORM_INT");
f.setAccessible(true);
int semPlatformInt = (int) f.get(null);
if (semPlatformInt < 100000) {
// Samsung Experience then
return false;
}
oneUIEncodedVersion = semPlatformInt - 90000;
oneUIMajorVersion = oneUIEncodedVersion / 10000;
oneUIMinorVersion = (oneUIEncodedVersion % 10000) / 100F;
isOneUI = true;
} catch (NoSuchFieldException e) {
} catch (Exception e) {
isOneUI = false;
}
return isOneUI;
}
public static boolean hasBuiltInClipboardToasts() {
return isOneUI() && getOneUIEncodedVersion() == ONE_UI_4_0;
}
public static int getOneUIMajorVersion() {
if (!isOneUI()) {
return 0;
}
return oneUIMajorVersion;
}
public static int getOneUIEncodedVersion() {
if (!isOneUI()) {
return 0;
}
return oneUIEncodedVersion;
}
public static float getOneUIMinorVersion() {
if (!isOneUI()) {
return 0;
}
return oneUIMinorVersion;
}
}

View File

@ -1932,6 +1932,16 @@ public boolean retriedToSend;
objArr.add(newMsgObj);
arr.add(newMsg);
if (msgObj.replyMessageObject != null) {
for (int i = 0; i < messages.size(); i++) {
if (messages.get(i).getId() == msgObj.replyMessageObject.getId()) {
newMsgObj.messageOwner.replyMessage = msgObj.replyMessageObject.messageOwner;
newMsgObj.replyMessageObject = msgObj.replyMessageObject;
break;
}
}
}
putToSendingMessages(newMsg, scheduleDate != 0);
boolean differentDialog = false;
@ -2744,6 +2754,27 @@ public boolean retriedToSend;
passwordFragment.needHideProgress();
passwordFragment.finishFragment();
}
long uid = messageObject.getFromChatId();
if (messageObject.messageOwner.via_bot_id != 0) {
uid = messageObject.messageOwner.via_bot_id;
}
String name = null;
if (uid > 0) {
TLRPC.User user = getMessagesController().getUser(uid);
if (user != null) {
name = ContactsController.formatName(user.first_name, user.last_name);
}
} else {
TLRPC.Chat chat = getMessagesController().getChat(-uid);
if (chat != null) {
name = chat.title;
}
}
if (name == null) {
name = "bot";
}
if (button instanceof TLRPC.TL_keyboardButtonUrlAuth) {
if (response instanceof TLRPC.TL_urlAuthResultRequest) {
TLRPC.TL_urlAuthResultRequest res = (TLRPC.TL_urlAuthResultRequest) response;
@ -2763,26 +2794,8 @@ public boolean retriedToSend;
if (!cacheFinal && res.cache_time != 0 && !button.requires_password) {
getMessagesStorage().saveBotCache(key, res);
}
if (res.message != null) {
long uid = messageObject.getFromChatId();
if (messageObject.messageOwner.via_bot_id != 0) {
uid = messageObject.messageOwner.via_bot_id;
}
String name = null;
if (uid > 0) {
TLRPC.User user = getMessagesController().getUser(uid);
if (user != null) {
name = ContactsController.formatName(user.first_name, user.last_name);
}
} else {
TLRPC.Chat chat = getMessagesController().getChat(-uid);
if (chat != null) {
name = chat.title;
}
}
if (name == null) {
name = "bot";
}
if (res.alert) {
if (parentFragment.getParentActivity() == null) {
return;
@ -2799,10 +2812,6 @@ public boolean retriedToSend;
if (parentFragment.getParentActivity() == null) {
return;
}
long uid = messageObject.getFromChatId();
if (messageObject.messageOwner.via_bot_id != 0) {
uid = messageObject.messageOwner.via_bot_id;
}
TLRPC.User user = getMessagesController().getUser(uid);
boolean verified = user != null && user.verified;
if (button instanceof TLRPC.TL_keyboardButtonGame) {
@ -7878,7 +7887,7 @@ public boolean retriedToSend;
selectedCompression = compressionsCount;
}
boolean needCompress = false;
if (selectedCompression != compressionsCount - 1 || Math.max(videoEditedInfo.originalWidth, videoEditedInfo.originalHeight) > 1280) {
if (selectedCompression != compressionsCount || Math.max(videoEditedInfo.originalWidth, videoEditedInfo.originalHeight) > 1280) {
needCompress = true;
switch (selectedCompression) {
case 1:

View File

@ -1688,7 +1688,10 @@ public class SharedConfig {
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("save_gallery", saveToGallery);
editor.commit();
checkSaveToGalleryFiles();
ImageLoader.getInstance().checkMediaPaths();
ImageLoader.getInstance().getCacheOutQueue().postRunnable(() -> {
checkSaveToGalleryFiles();
});
}
public static void toggleAutoplayGifs() {

View File

@ -0,0 +1,38 @@
package org.telegram.messenger;
import android.content.Context;
import android.content.SharedPreferences;
public class SharedPrefsHelper {
private static String WEB_VIEW_SHOWN_DIALOG_FORMAT = "confirm_shown_%d_%d";
private static SharedPreferences webViewBotsPrefs;
public static void init(Context ctx) {
webViewBotsPrefs = ctx.getSharedPreferences("webview_bots", Context.MODE_PRIVATE);
}
public static boolean isWebViewConfirmShown(int currentAccount, long botId) {
return webViewBotsPrefs.getBoolean(String.format(WEB_VIEW_SHOWN_DIALOG_FORMAT, currentAccount, botId), false);
}
public static void setWebViewConfirmShown(int currentAccount, long botId, boolean shown) {
webViewBotsPrefs.edit().putBoolean(String.format(WEB_VIEW_SHOWN_DIALOG_FORMAT, currentAccount, botId), shown).apply();
}
public static void cleanupAccount(int account) {
if (webViewBotsPrefs != null) {
SharedPreferences.Editor editor = webViewBotsPrefs.edit();
for (String key : webViewBotsPrefs.getAll().keySet()) {
if (key.startsWith("confirm_shown_" + account + "_")) {
editor.remove(key);
}
}
editor.apply();
}
}
public static SharedPreferences getWebViewBotsPrefs() {
return webViewBotsPrefs;
}
}

View File

@ -27,7 +27,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Utilities {
public static Pattern pattern = Pattern.compile("[\\-0-9]+");
public static SecureRandom random = new SecureRandom();
public static Random fastRandom = new Xoroshiro128PlusRandom(random.nextLong());
@ -39,6 +38,8 @@ public class Utilities {
public static volatile DispatchQueue phoneBookQueue = new DispatchQueue("phoneBookQueue");
public static volatile DispatchQueue themeQueue = new DispatchQueue("themeQueue");
private final static String RANDOM_STRING_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
static {
@ -405,4 +406,16 @@ public class Utilities {
public static float clamp(float value, float top, float bottom) {
return Math.max(Math.min(value, top), bottom);
}
public static String generateRandomString() {
return generateRandomString(16);
}
public static String generateRandomString(int chars) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < chars; i++) {
sb.append(RANDOM_STRING_CHARS.charAt(fastRandom.nextInt(RANDOM_STRING_CHARS.length())));
}
return sb.toString();
}
}

View File

@ -13,6 +13,7 @@ import android.view.View;
import org.telegram.tgnet.SerializedData;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.Components.AnimatedFileDrawable;
import org.telegram.ui.Components.PhotoFilterView;
import org.telegram.ui.Components.Point;
@ -84,6 +85,7 @@ public class VideoEditedInfo {
public Bitmap bitmap;
public View view;
public AnimatedFileDrawable animatedFileDrawable;
public MediaEntity() {

View File

@ -0,0 +1,320 @@
package org.telegram.messenger.ringtone;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.BuildVars;
import org.telegram.messenger.FileLoader;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.NotificationCenter;
import org.telegram.messenger.UserConfig;
import org.telegram.messenger.Utilities;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.SerializedData;
import org.telegram.tgnet.TLRPC;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
public class RingtoneDataStore {
private final long clientUserId;
String prefName = null;
private static volatile long queryHash;
private static volatile long lastReloadTimeMs;
private final int currentAccount;
private int localIds;
private static final long reloadTimeoutMs = 24 * 60 * 60 * 1000;//1 day
public final ArrayList<CachedTone> userRingtones = new ArrayList<>();
private boolean loaded;
public final static HashSet<String> ringtoneSupportedMimeType = new HashSet<>(Arrays.asList("audio/mpeg3", "audio/mpeg", "audio/ogg", "audio/m4a"));
public RingtoneDataStore(int currentAccount) {
this.currentAccount = currentAccount;
this.clientUserId = UserConfig.getInstance(currentAccount).clientUserId;
SharedPreferences preferences = getSharedPreferences();
try {
queryHash = preferences.getLong("hash", 0);
lastReloadTimeMs = preferences.getLong("lastReload", 0);
} catch (Exception e) {
FileLog.e(e);
}
AndroidUtilities.runOnUIThread(() -> {
loadUserRingtones();
});
}
public void loadUserRingtones() {
boolean needReload = System.currentTimeMillis() - lastReloadTimeMs > reloadTimeoutMs;
TLRPC.TL_account_getSavedRingtones req = new TLRPC.TL_account_getSavedRingtones();
req.hash = queryHash;
if (needReload) {
ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> {
if (response != null) {
if (response instanceof TLRPC.TL_account_savedRingtonesNotModified) {
loadFromPrefs(true);
} else if (response instanceof TLRPC.TL_account_savedRingtones) {
TLRPC.TL_account_savedRingtones res = (TLRPC.TL_account_savedRingtones) response;
saveTones(res.ringtones);
getSharedPreferences().edit()
.putLong("hash", queryHash = res.hash)
.putLong("lastReload", lastReloadTimeMs = System.currentTimeMillis())
.apply();
}
checkRingtoneSoundsLoaded();
}
}));
} else {
if (!loaded) {
loadFromPrefs(true);
loaded = true;
}
checkRingtoneSoundsLoaded();
}
}
private void loadFromPrefs(boolean notify) {
SharedPreferences preferences = getSharedPreferences();
int count = preferences.getInt("count", 0);
userRingtones.clear();
for (int i = 0; i < count; ++i) {
String value = preferences.getString("tone_document" + i, "");
String localPath = preferences.getString("tone_local_path" + i, "");
SerializedData serializedData = new SerializedData(Utilities.hexToBytes(value));
try {
TLRPC.Document document = TLRPC.Document.TLdeserialize(serializedData, serializedData.readInt32(true), true);
CachedTone tone = new CachedTone();
tone.document = document;
tone.localUri = localPath;
tone.localId = localIds++;
userRingtones.add(tone);
} catch (Throwable e) {
if (BuildVars.DEBUG_PRIVATE_VERSION) {
throw e;
}
FileLog.e(e);
}
}
if (notify) {
NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.onUserRingtonesUpdated);
}
}
private void saveTones(ArrayList<TLRPC.Document> ringtones) {
if (!loaded) {
loadFromPrefs(false);
loaded = true;
}
HashMap<Long, String> documentIdToLocalFilePath = new HashMap<>();
for (CachedTone cachedTone : userRingtones) {
if (cachedTone.localUri != null && cachedTone.document != null) {
documentIdToLocalFilePath.put(cachedTone.document.id, cachedTone.localUri);
}
}
userRingtones.clear();
SharedPreferences preferences = getSharedPreferences();
preferences.edit().clear().apply();
SharedPreferences.Editor editor = preferences.edit();
editor.putInt("count", ringtones.size());
for (int i = 0; i < ringtones.size(); i++) {
TLRPC.Document document = ringtones.get(i);
String localPath = documentIdToLocalFilePath.get(document.id);
SerializedData data = new SerializedData(document.getObjectSize());
document.serializeToStream(data);
editor.putString("tone_document" + i, Utilities.bytesToHex(data.toByteArray()));
if (localPath != null) {
editor.putString("tone_local_path" + i, localPath);
}
CachedTone tone = new CachedTone();
tone.document = document;
tone.localUri = localPath;
tone.localId = localIds++;
userRingtones.add(tone);
}
editor.apply();
NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.onUserRingtonesUpdated);
}
public void saveTones() {
SharedPreferences preferences = getSharedPreferences();
preferences.edit().clear().apply();
SharedPreferences.Editor editor = preferences.edit();
int count = 0;
for (int i = 0; i < userRingtones.size(); i++) {
if (userRingtones.get(i).uploading) {
continue;
}
count++;
TLRPC.Document document = userRingtones.get(i).document;
String localPath = userRingtones.get(i).localUri;
SerializedData data = new SerializedData(document.getObjectSize());
document.serializeToStream(data);
editor.putString("tone_document" + i, Utilities.bytesToHex(data.toByteArray()));
if (localPath != null) {
editor.putString("tone_local_path" + i, localPath);
}
}
editor.putInt("count", count);
editor.apply();
NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.onUserRingtonesUpdated);
}
private SharedPreferences getSharedPreferences() {
if (prefName == null) {
prefName = "ringtones_pref_" + clientUserId;
}
return ApplicationLoader.applicationContext.getSharedPreferences(prefName, Context.MODE_PRIVATE);
}
public void addUploadingTone(String filePath) {
CachedTone cachedTone = new CachedTone();
cachedTone.localUri = filePath;
cachedTone.localId = localIds++;
cachedTone.uploading = true;
userRingtones.add(cachedTone);
}
public void onRingtoneUploaded(String filePath, TLRPC.Document document, boolean error) {
boolean changed = false;
if (error) {
for (int i = 0; i < userRingtones.size(); i++) {
if (userRingtones.get(i).uploading && filePath.equals(userRingtones.get(i).localUri)) {
userRingtones.remove(i);
changed = true;
break;
}
}
} else {
for (int i = 0; i < userRingtones.size(); i++) {
if (userRingtones.get(i).uploading && filePath.equals(userRingtones.get(i).localUri)) {
userRingtones.get(i).uploading = false;
userRingtones.get(i).document = document;
changed = true;
break;
}
}
if (changed) {
saveTones();
}
}
if (changed) {
NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.onUserRingtonesUpdated);
}
}
public String getSoundPath(long id) {
if (!loaded) {
loadFromPrefs(true);
loaded = true;
}
for (int i = 0; i < userRingtones.size(); i++) {
if (userRingtones.get(i).document != null && userRingtones.get(i).document.id == id) {
if (!TextUtils.isEmpty(userRingtones.get(i).localUri)) {
return userRingtones.get(i).localUri;
}
return FileLoader.getPathToAttach(userRingtones.get(i).document).toString();
}
}
return "NoSound";
}
public void checkRingtoneSoundsLoaded() {
if (!loaded) {
loadFromPrefs(true);
loaded = true;
}
final ArrayList<CachedTone> cachedTones = new ArrayList<>(userRingtones);
Utilities.globalQueue.postRunnable(() -> {
for (int i = 0; i < cachedTones.size(); i++) {
CachedTone tone = cachedTones.get(i);
if (!TextUtils.isEmpty(tone.localUri)) {
File file = new File(tone.localUri);
if (file.exists()) {
continue;
}
}
if (tone.document != null) {
TLRPC.Document document = tone.document;
File file = FileLoader.getPathToAttach(document);
if (file == null || !file.exists()) {
AndroidUtilities.runOnUIThread(() -> {
FileLoader.getInstance(currentAccount).loadFile(document, document, 0, 0);
});
}
}
}
});
}
public boolean isLoaded() {
return loaded;
}
public void remove(TLRPC.Document document) {
if (document == null) {
return;
}
if (!loaded) {
loadFromPrefs(true);
loaded = true;
}
for (int i = 0; i < userRingtones.size(); i++) {
if (userRingtones.get(i).document != null && userRingtones.get(i).document.id == document.id) {
userRingtones.remove(i);
break;
}
}
}
public boolean contains(long id) {
return getDocument(id) != null;
}
public void addTone(TLRPC.Document document) {
if (document == null || contains(document.id)) {
return;
}
CachedTone cachedTone = new CachedTone();
cachedTone.document = document;
cachedTone.localId = localIds++;
cachedTone.uploading = false;
userRingtones.add(cachedTone);
saveTones();
}
public TLRPC.Document getDocument(long id) {
if (!loaded) {
loadFromPrefs(true);
loaded = true;
}
for (int i = 0; i < userRingtones.size(); i++) {
if (userRingtones.get(i).document != null && userRingtones.get(i).document.id == id) {
return userRingtones.get(i).document;
}
}
return null;
}
public class CachedTone {
public TLRPC.Document document;
public String localUri;
public int localId;
public boolean uploading;
}
}

View File

@ -0,0 +1,97 @@
package org.telegram.messenger.ringtone;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.FileLoader;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MediaDataController;
import org.telegram.messenger.MessagesController;
import org.telegram.messenger.NotificationCenter;
import org.telegram.messenger.R;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.Components.Bulletin;
import java.io.File;
public class RingtoneUploader implements NotificationCenter.NotificationCenterDelegate {
private int currentAccount;
public final String filePath;
private boolean canceled;
public RingtoneUploader(String filePath, int currentAccount) {
this.currentAccount = currentAccount;
this.filePath = filePath;
subscribe();
FileLoader.getInstance(currentAccount).uploadFile(filePath, false, true, ConnectionsManager.FileTypeAudio);
}
@Override
public void didReceivedNotification(int id, int account, Object... args) {
if (id == NotificationCenter.fileUploaded) {
final String location = (String) args[0];
if (canceled) {
return;
}
if (location.equals(filePath)) {
final TLRPC.InputFile file = (TLRPC.InputFile) args[1];
TLRPC.TL_account_uploadRingtone req = new TLRPC.TL_account_uploadRingtone();
req.file = file;
req.file_name = file.name;
req.mime_type = FileLoader.getFileExtension(new File(file.name));
if ("ogg".equals(req.mime_type)) {
req.mime_type = "audio/ogg";
} else {
req.mime_type = "audio/mpeg";
}
ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> {
AndroidUtilities.runOnUIThread(() -> {
if (response != null) {
onComplete((TLRPC.Document) response);
} else {
error(error);
}
unsubscribe();
});
});
}
}
}
private void subscribe() {
NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.fileUploaded);
NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.fileUploadFailed);
}
private void unsubscribe() {
NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.fileUploaded);
NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.fileUploadFailed);
}
private void onComplete(TLRPC.Document document) {
MediaDataController.getInstance(currentAccount).onRingtoneUploaded(filePath, document, false);
}
public void cancel() {
canceled = true;
unsubscribe();
FileLoader.getInstance(currentAccount).cancelFileUpload(filePath, false);
MediaDataController.getInstance(currentAccount).onRingtoneUploaded(filePath, null, true);
}
public void error(TLRPC.TL_error error) {
unsubscribe();
MediaDataController.getInstance(currentAccount).onRingtoneUploaded(filePath, null, true);
if (error != null) {
NotificationCenter.getInstance(currentAccount).doOnIdle(() -> {
if (error.text.equals("RINGTONE_DURATION_TOO_LONG")) {
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.showBulletin, Bulletin.TYPE_ERROR_SUBTITLE, LocaleController.formatString("TooLongError", R.string.TooLongError), LocaleController.formatString("ErrorRingtoneDurationTooLong", R.string.ErrorRingtoneDurationTooLong, MessagesController.getInstance(currentAccount).ringtoneDurationMax));
} else if (error.text.equals("RINGTONE_SIZE_TOO_BIG")) {
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.showBulletin, Bulletin.TYPE_ERROR_SUBTITLE, LocaleController.formatString("TooLargeError", R.string.TooLargeError), LocaleController.formatString("ErrorRingtoneSizeTooBig", R.string.ErrorRingtoneSizeTooBig, (MessagesController.getInstance(currentAccount).ringtoneSizeMax / 1024)));
} else {
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.showBulletin, Bulletin.TYPE_ERROR_SUBTITLE, LocaleController.formatString("InvalidFormatError", R.string.InvalidFormatError), LocaleController.formatString("ErrorRingtoneInvalidFormat", R.string.ErrorRingtoneInvalidFormat));
}
});
}
}
}

View File

@ -50,10 +50,11 @@ public class MediaCodecVideoConvertor {
ArrayList<VideoEditedInfo.MediaEntity> mediaEntities,
boolean isPhoto,
MediaController.CropState cropState,
boolean isRound,
MediaController.VideoConvertorListener callback) {
this.callback = callback;
return convertVideoInternal(videoPath, cacheFile, rotationValue, isSecret, originalWidth, originalHeight,
resultWidth, resultHeight, framerate, bitrate, originalBitrate, startTime, endTime, avatarStartTime, duration, needCompress, false, savedFilterState, paintPath, mediaEntities, isPhoto, cropState);
resultWidth, resultHeight, framerate, bitrate, originalBitrate, startTime, endTime, avatarStartTime, duration, needCompress, false, savedFilterState, paintPath, mediaEntities, isPhoto, cropState, isRound);
}
public long getLastFrameTimestamp() {
@ -73,7 +74,8 @@ public class MediaCodecVideoConvertor {
String paintPath,
ArrayList<VideoEditedInfo.MediaEntity> mediaEntities,
boolean isPhoto,
MediaController.CropState cropState) {
MediaController.CropState cropState,
boolean isRound) {
long time = System.currentTimeMillis();
boolean error = false;
@ -330,6 +332,12 @@ public class MediaCodecVideoConvertor {
long additionalPresentationTime = 0;
long minPresentationTime = Integer.MIN_VALUE;
long frameDelta = 1000 / framerate * 1000;
long frameDeltaFroSkipFrames;
if (framerate < 30) {
frameDeltaFroSkipFrames = 1000 / (framerate + 5) * 1000;
} else {
frameDeltaFroSkipFrames = 1000 / (framerate + 1) * 1000;
}
extractor.selectTrack(videoIndex);
MediaFormat videoFormat = extractor.getTrackFormat(videoIndex);
@ -401,7 +409,9 @@ public class MediaCodecVideoConvertor {
decoder = MediaCodec.createDecoderByType(videoFormat.getString(MediaFormat.KEY_MIME));
outputSurface = new OutputSurface(savedFilterState, null, paintPath, mediaEntities, cropState, resultWidth, resultHeight, originalWidth, originalHeight, rotationValue, framerate, false);
outputSurface.changeFragmentShader(createFragmentShader(originalWidth, originalHeight, resultWidth, resultHeight, true), createFragmentShader(originalWidth, originalHeight, resultWidth, resultHeight, false));
if (!isRound && Math.max(resultHeight, resultHeight) / (float) Math.max(originalHeight, originalWidth) < 0.9f) {
outputSurface.changeFragmentShader(createFragmentShader(originalWidth, originalHeight, resultWidth, resultHeight, true), createFragmentShader(originalWidth, originalHeight, resultWidth, resultHeight, false));
}
decoder.configure(videoFormat, outputSurface.getSurface(), null, 0);
decoder.start();
@ -681,7 +691,7 @@ public class MediaCodecVideoConvertor {
decoder.flush();
flushed = true;
}
if (lastFramePts > 0 && info.presentationTimeUs - lastFramePts < frameDelta && (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == 0) {
if (lastFramePts > 0 && info.presentationTimeUs - lastFramePts < frameDeltaFroSkipFrames && (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == 0) {
doRender = false;
}
trueStartTime = avatarStartTime >= 0 ? avatarStartTime : startTime;
@ -794,7 +804,7 @@ public class MediaCodecVideoConvertor {
originalWidth, originalHeight,
resultWidth, resultHeight, framerate, bitrate, originalBitrate, startTime, endTime, avatarStartTime, duration,
needCompress, true, savedFilterState, paintPath, mediaEntities,
isPhoto, cropState);
isPhoto, cropState, isRound);
}
long timeLeft = System.currentTimeMillis() - time;

View File

@ -8,14 +8,6 @@
package org.telegram.messenger.video;
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@ -35,22 +27,32 @@ import android.view.Gravity;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import androidx.annotation.RequiresApi;
import androidx.exifinterface.media.ExifInterface;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.Bitmaps;
import org.telegram.messenger.BuildVars;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.MediaController;
import org.telegram.messenger.UserConfig;
import org.telegram.messenger.Utilities;
import org.telegram.messenger.VideoEditedInfo;
import org.telegram.ui.Components.AnimatedFileDrawable;
import org.telegram.ui.Components.FilterShaders;
import org.telegram.ui.Components.Paint.Views.EditTextOutline;
import org.telegram.ui.Components.RLottieDrawable;
import javax.microedition.khronos.opengles.GL10;
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import androidx.annotation.RequiresApi;
import androidx.exifinterface.media.ExifInterface;
import javax.microedition.khronos.opengles.GL10;
public class TextureRenderer {
@ -121,6 +123,7 @@ public class TextureRenderer {
private int[] paintTexture;
private int[] stickerTexture;
private Bitmap stickerBitmap;
private Canvas stickerCanvas;
private float videoFps;
private int imageOrientation;
@ -398,10 +401,31 @@ public class TextureRenderer {
entity.currentFrame = 0;
}
drawTexture(false, stickerTexture[0], entity.x, entity.y, entity.width, entity.height, entity.rotation, (entity.subType & 2) != 0);
} else if (entity.bitmap != null) {
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, stickerTexture[0]);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, entity.bitmap, 0);
drawTexture(false, stickerTexture[0], entity.x, entity.y, entity.width, entity.height, entity.rotation, (entity.subType & 2) != 0);
} else if (entity.animatedFileDrawable != null) {
int lastFrame = (int) entity.currentFrame;
entity.currentFrame += entity.framesPerDraw;
int currentFrame = (int) entity.currentFrame;
while (lastFrame != currentFrame) {
entity.animatedFileDrawable.getNextFrame();
currentFrame--;
}
Bitmap frameBitmap = entity.animatedFileDrawable.getBackgroundBitmap();
if (stickerCanvas == null && stickerBitmap != null) {
stickerCanvas = new Canvas(stickerBitmap);
}
if (stickerBitmap != null && frameBitmap != null) {
stickerBitmap.eraseColor(Color.TRANSPARENT);
stickerCanvas.drawBitmap(frameBitmap, 0, 0, null);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, stickerTexture[0]);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, stickerBitmap, 0);
drawTexture(false, stickerTexture[0], entity.x, entity.y, entity.width, entity.height, entity.rotation, (entity.subType & 2) != 0);
}
} else {
if (entity.bitmap != null) {
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, stickerTexture[0]);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, entity.bitmap, 0);
drawTexture(false, stickerTexture[0], entity.x, entity.y, entity.width, entity.height, entity.rotation, (entity.subType & 2) != 0);
}
}
}
}
@ -608,6 +632,10 @@ public class TextureRenderer {
entity.metadata = new int[3];
entity.ptr = RLottieDrawable.create(entity.text, null, 512, 512, entity.metadata, false, null, false, 0);
entity.framesPerDraw = entity.metadata[1] / videoFps;
} else if ((entity.subType & 4) != 0) {
entity.animatedFileDrawable = new AnimatedFileDrawable(new File(entity.text), true, 0, null, null, null, 0, UserConfig.selectedAccount, true, 512, 512);
entity.framesPerDraw = videoFps / 30f;
entity.currentFrame = 0;
} else {
if (Build.VERSION.SDK_INT >= 19) {
entity.bitmap = BitmapFactory.decodeFile(entity.text);
@ -713,6 +741,9 @@ public class TextureRenderer {
if (entity.ptr != 0) {
RLottieDrawable.destroy(entity.ptr);
}
if (entity.animatedFileDrawable != null) {
entity.animatedFileDrawable.recycle();
}
}
}
}

View File

@ -15,7 +15,7 @@ import java.util.List;
public final class Instance {
public static final List<String> AVAILABLE_VERSIONS = Build.VERSION.SDK_INT >= 18 ? Arrays.asList("4.0.0", "3.0.0", "2.7.7", "2.4.4") : Arrays.asList("2.4.4");
public static final List<String> AVAILABLE_VERSIONS = Build.VERSION.SDK_INT >= 18 ? Arrays.asList("4.1.2", "4.0.2", "4.0.1", "4.0.0", "3.0.0", "2.7.7", "2.4.4") : Arrays.asList("2.4.4");
public static final int AUDIO_STATE_MUTED = 0;
public static final int AUDIO_STATE_ACTIVE = 1;
@ -283,7 +283,7 @@ public final class Instance {
public static final class FinalState {
public final byte[] persistentState;
public final String debugLog;
public String debugLog;
public final TrafficStats trafficStats;
public final boolean isRatingSuggested;

View File

@ -107,7 +107,6 @@ import org.telegram.messenger.UserObject;
import org.telegram.messenger.Utilities;
import org.telegram.messenger.XiaomiUtilities;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.RequestDelegate;
import org.telegram.tgnet.TLObject;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.BottomSheet;
@ -123,9 +122,13 @@ import org.webrtc.VideoFrame;
import org.webrtc.VideoSink;
import org.webrtc.voiceengine.WebRtcAudioTrack;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigInteger;
@ -2335,11 +2338,11 @@ public class VoIPService extends Service implements SensorEventListener, AudioMa
final boolean enableAec = !(sysAecAvailable && serverConfig.useSystemAec);
final boolean enableNs = !(sysNsAvailable && serverConfig.useSystemNs);
final String logFilePath = BuildVars.DEBUG_VERSION ? VoIPHelper.getLogFilePath("voip" + privateCall.id) : VoIPHelper.getLogFilePath(privateCall.id, false);
final String statisLogFilePath = "";
final Instance.Config config = new Instance.Config(initializationTimeout, receiveTimeout, voipDataSaving, privateCall.p2p_allowed, enableAec, enableNs, true, false, serverConfig.enableStunMarking, logFilePath, statisLogFilePath, privateCall.protocol.max_layer);
final String statsLogFilePath = VoIPHelper.getLogFilePath(privateCall.id, true);
final Instance.Config config = new Instance.Config(initializationTimeout, receiveTimeout, voipDataSaving, privateCall.p2p_allowed, enableAec, enableNs, true, false, serverConfig.enableStunMarking, logFilePath, statsLogFilePath, privateCall.protocol.max_layer);
// persistent state
final String persistentStateFilePath = new File(ApplicationLoader.applicationContext.getFilesDir(), "voip_persistent_state.json").getAbsolutePath();
final String persistentStateFilePath = new File(ApplicationLoader.applicationContext.getCacheDir(), "voip_persistent_state.json").getAbsolutePath();
// endpoints
final boolean forceTcp = preferences.getBoolean("dbg_force_tcp_in_calls", false);
@ -3365,10 +3368,37 @@ public class VoIPService extends Service implements SensorEventListener, AudioMa
}*/
}
public static String convertStreamToString(InputStream is) throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
reader.close();
return sb.toString();
}
public static String getStringFromFile(String filePath) throws Exception {
File fl = new File(filePath);
FileInputStream fin = new FileInputStream(fl);
String ret = convertStreamToString(fin);
fin.close();
return ret;
}
private void onTgVoipStop(Instance.FinalState finalState) {
if (user == null) {
return;
}
if (TextUtils.isEmpty(finalState.debugLog)) {
try {
finalState.debugLog = getStringFromFile(VoIPHelper.getLogFilePath(privateCall.id, true));
} catch (Exception e) {
e.printStackTrace();
}
}
if (needRateCall || forceRating || finalState.isRatingSuggested) {
startRatingActivity();
needRateCall = false;

File diff suppressed because it is too large Load Diff

View File

@ -50,6 +50,7 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
@ -170,6 +171,8 @@ public class ActionBarMenuItem extends FrameLayout {
private View showSubMenuFrom;
private final Theme.ResourcesProvider resourcesProvider;
private OnClickListener onClickListener;
public ActionBarMenuItem(Context context, ActionBarMenu menu, int backgroundColor, int iconColor) {
this(context, menu, backgroundColor, iconColor, false);
}
@ -338,7 +341,7 @@ public class ActionBarMenuItem extends FrameLayout {
}
rect = new Rect();
location = new int[2];
popupLayout = new ActionBarPopupWindow.ActionBarPopupWindowLayout(getContext(), resourcesProvider);
popupLayout = new ActionBarPopupWindow.ActionBarPopupWindowLayout(getContext(), R.drawable.popup_fixed_alert2, resourcesProvider, ActionBarPopupWindow.ActionBarPopupWindowLayout.FLAG_USE_SWIPEBACK);
popupLayout.setOnTouchListener((v, event) -> {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
if (popupWindow != null && popupWindow.isShowing()) {
@ -355,6 +358,14 @@ public class ActionBarMenuItem extends FrameLayout {
popupWindow.dismiss();
}
});
if (popupLayout.getSwipeBack() != null) {
popupLayout.getSwipeBack().setOnClickListener(view -> {
if (popupWindow != null) {
popupWindow.dismiss();
}
});
}
}
public void removeAllSubItems() {
@ -512,6 +523,35 @@ public class ActionBarMenuItem extends FrameLayout {
return cell;
}
public ActionBarMenuSubItem addSwipeBackItem(int icon, Drawable iconDrawable, String text, View viewToSwipeBack) {
createPopupLayout();
ActionBarMenuSubItem cell = new ActionBarMenuSubItem(getContext(), false, false, false, resourcesProvider);
cell.setTextAndIcon(text, icon, iconDrawable);
cell.setMinimumWidth(AndroidUtilities.dp(196));
cell.setRightIcon(R.drawable.msg_arrowright);
popupLayout.addView(cell);
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) cell.getLayoutParams();
if (LocaleController.isRTL) {
layoutParams.gravity = Gravity.RIGHT;
}
layoutParams.width = LayoutHelper.MATCH_PARENT;
layoutParams.height = AndroidUtilities.dp(48);
cell.setLayoutParams(layoutParams);
int swipeBackIndex = popupLayout.addViewToSwipeBack(viewToSwipeBack);
cell.openSwipeBackLayout = () -> {
if (popupLayout.getSwipeBack() != null) {
popupLayout.getSwipeBack().openForeground(swipeBackIndex);
}
};
cell.setOnClickListener(view -> {
cell.openSwipeBack();
});
popupLayout.swipeBackGravityRight = true;
return cell;
}
public View addDivider(int color) {
createPopupLayout();
@ -580,6 +620,9 @@ public class ActionBarMenuItem extends FrameLayout {
}
public ActionBarPopupWindow.ActionBarPopupWindowLayout getPopupLayout() {
if (popupLayout == null) {
createPopupLayout();
}
return popupLayout;
}
@ -634,7 +677,11 @@ public class ActionBarMenuItem extends FrameLayout {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
popupLayout.measure(widthMeasureSpec, heightMeasureSpec);
topView.getLayoutParams().width = popupLayout.getMeasuredWidth() - AndroidUtilities.dp(16);
if (popupLayout.getSwipeBack() != null) {
topView.getLayoutParams().width = popupLayout.getSwipeBack().getChildAt(0).getMeasuredWidth();
} else {
topView.getLayoutParams().width = popupLayout.getMeasuredWidth() - AndroidUtilities.dp(16);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
};
@ -650,6 +697,7 @@ public class ActionBarMenuItem extends FrameLayout {
linearLayout.addView(frameLayout, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT));
linearLayout.addView(popupLayout, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, 0, 0, -AndroidUtilities.dp(4), 0, 0));
container = linearLayout;
popupLayout.setTopView(frameLayout);
}
popupWindow = new ActionBarPopupWindow(container, LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT);
if (animationEnabled && Build.VERSION.SDK_INT >= 19) {
@ -694,6 +742,9 @@ public class ActionBarMenuItem extends FrameLayout {
updateOrShowPopup(true, false);
}
popupLayout.updateRadialSelectors();
if (popupLayout.getSwipeBack() != null) {
popupLayout.getSwipeBack().closeForeground(false);
}
popupWindow.startAnimation();
}
public void toggleSubMenu() {
@ -1358,6 +1409,15 @@ public class ActionBarMenuItem extends FrameLayout {
return this;
}
public OnClickListener getOnClickListener() {
return onClickListener;
}
@Override
public void setOnClickListener(@Nullable OnClickListener l) {
super.setOnClickListener(onClickListener = l);
}
private void checkClearButton() {
if (clearButton != null) {
if (!hasRemovableFilters() && TextUtils.isEmpty(searchField.getText()) &&
@ -1879,4 +1939,27 @@ public class ActionBarMenuItem extends FrameLayout {
return color != null ? color : Theme.getColor(key);
}
}
public ActionBarPopupWindow.GapView addColoredGap() {
createPopupLayout();
ActionBarPopupWindow.GapView gap = new ActionBarPopupWindow.GapView(getContext(), Theme.key_graySection);
gap.setTag(R.id.fit_width_tag, 1);
popupLayout.addView(gap, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 8));
return gap;
}
public static ActionBarMenuSubItem addItem(ActionBarPopupWindow.ActionBarPopupWindowLayout windowLayout, int icon, String text, boolean needCheck, Theme.ResourcesProvider resourcesProvider) {
ActionBarMenuSubItem cell = new ActionBarMenuSubItem(windowLayout.getContext(), needCheck, false, false, resourcesProvider);
cell.setTextAndIcon(text, icon);
cell.setMinimumWidth(AndroidUtilities.dp(196));
windowLayout.addView(cell);
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) cell.getLayoutParams();
if (LocaleController.isRTL) {
layoutParams.gravity = Gravity.RIGHT;
}
layoutParams.width = LayoutHelper.MATCH_PARENT;
layoutParams.height = AndroidUtilities.dp(48);
cell.setLayoutParams(layoutParams);
return cell;
}
}

View File

@ -34,6 +34,7 @@ public class ActionBarMenuSubItem extends FrameLayout {
private int itemHeight = 48;
private final Theme.ResourcesProvider resourcesProvider;
Runnable openSwipeBackLayout;
public ActionBarMenuSubItem(Context context, boolean top, boolean bottom) {
this(context, false, top, bottom);
@ -108,7 +109,7 @@ public class ActionBarMenuSubItem extends FrameLayout {
if (rightIcon == null) {
rightIcon = new ImageView(getContext());
rightIcon.setScaleType(ImageView.ScaleType.CENTER);
rightIcon.setColorFilter(textColor, PorterDuff.Mode.MULTIPLY);
rightIcon.setColorFilter(iconColor, PorterDuff.Mode.MULTIPLY);
if (LocaleController.isRTL) {
rightIcon.setScaleX(-1);
}
@ -237,4 +238,14 @@ public class ActionBarMenuSubItem extends FrameLayout {
public CheckBox2 getCheckView() {
return checkView;
}
public void openSwipeBack() {
if (openSwipeBackLayout != null) {
openSwipeBackLayout.run();
}
}
public ImageView getRightIcon() {
return rightIcon;
}
}

View File

@ -16,6 +16,7 @@ import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
@ -27,7 +28,6 @@ import androidx.core.view.ViewCompat;
import androidx.core.widget.ScrollerCompat;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@ -41,8 +41,6 @@ import android.widget.ScrollView;
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.util.Log;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.NotificationCenter;
@ -95,6 +93,8 @@ public class ActionBarPopupWindow extends PopupWindow {
public static class ActionBarPopupWindowLayout extends FrameLayout {
public final static int FLAG_USE_SWIPEBACK = 1;
public boolean updateAnimation;
public boolean swipeBackGravityRight;
private OnDispatchKeyEventListener mOnDispatchKeyEventListener;
private float backScaleX = 1;
@ -119,6 +119,7 @@ public class ActionBarPopupWindow extends PopupWindow {
private boolean fitItems;
private final Theme.ResourcesProvider resourcesProvider;
private View topView;
public ActionBarPopupWindowLayout(Context context) {
this(context, null);
@ -135,14 +136,16 @@ public class ActionBarPopupWindow extends PopupWindow {
public ActionBarPopupWindowLayout(Context context, int resId, Theme.ResourcesProvider resourcesProvider, int flags) {
super(context);
this.resourcesProvider = resourcesProvider;
backgroundDrawable = getResources().getDrawable(resId).mutate();
if (resId != 0) {
backgroundDrawable = getResources().getDrawable(resId).mutate();
setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8));
}
if (backgroundDrawable != null) {
backgroundDrawable.getPadding(bgPaddings);
setBackgroundColor(getThemedColor(Theme.key_actionBarDefaultSubmenuBackground));
}
setBackgroundColor(getThemedColor(Theme.key_actionBarDefaultSubmenuBackground));
setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8));
setWillNotDraw(false);
if ((flags & FLAG_USE_SWIPEBACK) > 0) {
@ -176,11 +179,14 @@ public class ActionBarPopupWindow extends PopupWindow {
}
Object tag = view.getTag(R.id.width_tag);
Object tag2 = view.getTag(R.id.object_tag);
Object fitToWidth = view.getTag(R.id.fit_width_tag);
if (tag != null) {
view.getLayoutParams().width = LayoutHelper.WRAP_CONTENT;
}
measureChildWithMargins(view, widthMeasureSpec, 0, heightMeasureSpec, 0);
if (!(tag instanceof Integer) && tag2 == null) {
if (fitToWidth != null) {
} else if (!(tag instanceof Integer) && tag2 == null) {
maxWidth = Math.max(maxWidth, view.getMeasuredWidth());
continue;
} else if (tag instanceof Integer) {
@ -218,7 +224,7 @@ public class ActionBarPopupWindow extends PopupWindow {
}
public int addViewToSwipeBack(View v) {
swipeBackLayout.addView(v);
swipeBackLayout.addView(v, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT));
return swipeBackLayout.getChildCount() - 1;
}
@ -239,7 +245,7 @@ public class ActionBarPopupWindow extends PopupWindow {
}
public void setBackgroundColor(int color) {
if (backgroundColor != color) {
if (backgroundColor != color && backgroundDrawable != null) {
backgroundDrawable.setColorFilter(new PorterDuffColorFilter(backgroundColor = color, PorterDuff.Mode.SRC_IN));
}
}
@ -270,12 +276,12 @@ public class ActionBarPopupWindow extends PopupWindow {
public void setBackScaleY(float value) {
if (backScaleY != value) {
backScaleY = value;
if (animationEnabled) {
if (animationEnabled && updateAnimation) {
int height = getMeasuredHeight() - AndroidUtilities.dp(16);
if (shownFromBotton) {
for (int a = lastStartedChild; a >= 0; a--) {
View child = getItemAt(a);
if (child.getVisibility() != VISIBLE) {
if (child.getVisibility() != VISIBLE || child instanceof GapView) {
continue;
}
Integer position = positions.get(child);
@ -376,20 +382,50 @@ public class ActionBarPopupWindow extends PopupWindow {
return super.dispatchKeyEvent(event);
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (swipeBackGravityRight) {
setTranslationX(getMeasuredWidth() * (1f - backScaleX));
if (topView != null) {
topView.setTranslationX(getMeasuredWidth() * (1f - backScaleX));
topView.setAlpha(1f - swipeBackLayout.transitionProgress);
float h = topView.getMeasuredHeight() - AndroidUtilities.dp(16);
float yOffset = -h * swipeBackLayout.transitionProgress;
topView.setTranslationY(yOffset);
setTranslationY(yOffset);
}
}
super.dispatchDraw(canvas);
}
@Override
protected void onDraw(Canvas canvas) {
if (backgroundDrawable != null) {
int start = gapStartY - scrollView.getScrollY();
int end = gapEndY - scrollView.getScrollY();
boolean hasGap = false;
for (int i = 0; i < linearLayout.getChildCount(); i++) {
if (linearLayout.getChildAt(i) instanceof GapView) {
hasGap = true;
break;
}
}
for (int a = 0; a < 2; a++) {
if (a == 1 && start < -AndroidUtilities.dp(16)) {
break;
}
if (gapStartY != -1000000) {
boolean needRestore = false;
boolean applyAlpha = true;
if (hasGap && backAlpha != 255) {
canvas.saveLayerAlpha(0, bgPaddings.top, getMeasuredWidth(), getMeasuredHeight(), backAlpha, Canvas.ALL_SAVE_FLAG);
needRestore = true;
applyAlpha = false;
} else if (gapStartY != -1000000) {
needRestore = true;
canvas.save();
canvas.clipRect(0, bgPaddings.top, getMeasuredWidth(), getMeasuredHeight());
}
backgroundDrawable.setAlpha(backAlpha);
backgroundDrawable.setAlpha(applyAlpha ? backAlpha : 255);
if (shownFromBotton) {
final int height = getMeasuredHeight();
backgroundDrawable.setBounds(0, (int) (height * (1.0f - backScaleY)), (int) (getMeasuredWidth() * backScaleX), height);
@ -412,7 +448,33 @@ public class ActionBarPopupWindow extends PopupWindow {
}
}
backgroundDrawable.draw(canvas);
if (gapStartY != -1000000) {
if (hasGap) {
canvas.save();
AndroidUtilities.rectTmp2.set(backgroundDrawable.getBounds());
AndroidUtilities.rectTmp2.inset(AndroidUtilities.dp(8), AndroidUtilities.dp(8));
canvas.clipRect(AndroidUtilities.rectTmp2);
for (int i = 0; i < linearLayout.getChildCount(); i++) {
if (linearLayout.getChildAt(i) instanceof GapView) {
canvas.save();
float x = 0, y = 0;
View view = linearLayout.getChildAt(i) ;
while (view != this) {
x += view.getX();
y += view.getY();
view = (View) view.getParent();
if (view == null) {
return;
}
}
canvas.translate(x, y);
linearLayout.getChildAt(i).draw(canvas);
canvas.restore();
}
}
canvas.restore();
}
if (needRestore) {
canvas.restore();
}
}
@ -490,6 +552,14 @@ public class ActionBarPopupWindow extends PopupWindow {
public int getVisibleHeight() {
return (int) (getMeasuredHeight() * backScaleY);
}
public void setTopView(View topView) {
this.topView = topView;
}
public void setSwipeBackForegroundColor(int color) {
getSwipeBack().setForegroundColor(color);
}
}
public ActionBarPopupWindow() {
@ -587,6 +657,26 @@ public class ActionBarPopupWindow extends PopupWindow {
wm.updateViewLayout(container, p);
}
private void dismissDim() {
View container = getContentView().getRootView();
Context context = getContentView().getContext();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
if (container.getLayoutParams() == null || !(container.getLayoutParams() instanceof WindowManager.LayoutParams)) {
return;
}
WindowManager.LayoutParams p = (WindowManager.LayoutParams) container.getLayoutParams();
try {
if ((p.flags & WindowManager.LayoutParams.FLAG_DIM_BEHIND) != 0) {
p.flags &= ~WindowManager.LayoutParams.FLAG_DIM_BEHIND;
p.dimAmount = 0.0f;
wm.updateViewLayout(container, p);
}
} catch (Exception e) {
}
}
@Override
public void showAsDropDown(View anchor, int xoff, int yoff) {
try {
@ -641,9 +731,14 @@ public class ActionBarPopupWindow extends PopupWindow {
} else {
content.lastStartedChild = 0;
}
float finalsScaleY = 1f;
if (content.getSwipeBack() != null) {
content.getSwipeBack().invalidateTransforms();
finalsScaleY = content.backScaleY;
}
windowAnimatorSet = new AnimatorSet();
windowAnimatorSet.playTogether(
ObjectAnimator.ofFloat(content, "backScaleY", 0.0f, 1.0f),
ObjectAnimator.ofFloat(content, "backScaleY", 0.0f, finalsScaleY),
ObjectAnimator.ofInt(content, "backAlpha", 0, 255));
windowAnimatorSet.setDuration(150 + 16 * visibleCount);
int finalCount = count;
@ -665,6 +760,9 @@ public class ActionBarPopupWindow extends PopupWindow {
int count = content.getItemsCount();
for (int a = 0; a < count; a++) {
View child = content.getItemAt(a);
if (child instanceof GapView) {
continue;
}
child.setAlpha(child.isEnabled() ? 1f : 0.5f);
}
}
@ -702,6 +800,7 @@ public class ActionBarPopupWindow extends PopupWindow {
public void dismiss(boolean animated) {
setFocusable(false);
dismissDim();
if (windowAnimatorSet != null) {
if (animated && isClosingAnimated) {
return;
@ -778,4 +877,29 @@ public class ActionBarPopupWindow extends PopupWindow {
public interface onSizeChangedListener {
void onSizeChanged();
}
public static class GapView extends FrameLayout {
Paint paint = new Paint();
String colorKey;
int color = 0;
public GapView(Context context, String colorKey) {
super(context);
this.colorKey = colorKey;
}
@Override
protected void onDraw(Canvas canvas) {
if (color == 0) {
paint.setColor(Theme.getColor(colorKey));
} else {
paint.setColor(color);
}
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint);
}
public void setColor(int color) {
this.color = color;
}
}
}

View File

@ -139,6 +139,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback {
private ArrayList<AlertDialogCell> itemViews = new ArrayList<>();
private float aspectRatio;
private boolean dimEnabled = true;
private float dimAlpha = 0.6f;
private final Theme.ResourcesProvider resourcesProvider;
private boolean topAnimationAutoRepeat = true;
@ -871,7 +872,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback {
params.width = WindowManager.LayoutParams.MATCH_PARENT;
} else {
if (dimEnabled) {
params.dimAmount = 0.6f;
params.dimAmount = dimAlpha;
params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
} else {
params.dimAmount = 0f;
@ -1384,6 +1385,11 @@ public class AlertDialog extends Dialog implements Drawable.Callback {
return this;
}
public Builder setDimAlpha(float dimAlpha) {
alertDialog.dimAlpha = dimAlpha;
return this;
}
public void notDrawBackgroundOnTopView(boolean b) {
alertDialog.notDrawBackgroundOnTopView = b;
}

View File

@ -783,10 +783,14 @@ public abstract class BaseFragment {
}
Theme.ResourcesProvider resourcesProvider = getResourceProvider();
int color;
String key = Theme.key_actionBarDefault;
if (actionBar != null && actionBar.isActionModeShowed()) {
key = Theme.key_actionBarActionModeDefault;
}
if (resourcesProvider != null) {
color = resourcesProvider.getColorOrDefault(Theme.key_actionBarDefault);
color = resourcesProvider.getColorOrDefault(key);
} else {
color = Theme.getColor(Theme.key_actionBarDefault, null, true);
color = Theme.getColor(key, null, true);
}
return ColorUtils.calculateLuminance(color) > 0.7f;
}

View File

@ -17,12 +17,12 @@ import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
@ -57,6 +57,7 @@ import org.telegram.messenger.LocaleController;
import org.telegram.messenger.NotificationCenter;
import org.telegram.messenger.R;
import org.telegram.messenger.UserConfig;
import org.telegram.messenger.camera.CameraView;
import org.telegram.ui.Components.AnimationProperties;
import org.telegram.ui.Components.Bulletin;
import org.telegram.ui.Components.CubicBezierInterpolator;
@ -72,8 +73,8 @@ public class BottomSheet extends Dialog {
protected ContainerView container;
protected boolean keyboardVisible;
private WindowInsets lastInsets;
protected boolean drawNavigationBar;
protected boolean scrollNavBar;
public boolean drawNavigationBar;
public boolean scrollNavBar;
protected boolean useSmoothKeyboard;
@ -578,7 +579,83 @@ public class BottomSheet extends Dialog {
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (navBarColorKey != null) {
backgroundPaint.setColor(getThemedColor(navBarColorKey));
} else {
backgroundPaint.setColor(navBarColor);
}
} else {
backgroundPaint.setColor(0xff000000);
}
if (backgroundPaint.getAlpha() < 255 && drawNavigationBar) {
float translation = 0;
if (scrollNavBar || Build.VERSION.SDK_INT >= 29 && getAdditionalMandatoryOffsets() > 0) {
float dist = containerView.getMeasuredHeight() - containerView.getTranslationY();
translation = Math.max(0, getBottomInset() - dist);
}
int navBarHeight = drawNavigationBar ? getBottomInset() : 0;
canvas.save();
canvas.clipRect(containerView.getLeft() + backgroundPaddingLeft, getMeasuredHeight() - navBarHeight + translation - currentPanTranslationY, containerView.getRight() - backgroundPaddingLeft, getMeasuredHeight() + translation, Region.Op.DIFFERENCE);
super.dispatchDraw(canvas);
canvas.restore();
} else {
super.dispatchDraw(canvas);
}
if (!shouldOverlayCameraViewOverNavBar()) {
drawNavigationBar(canvas);
}
if (drawNavigationBar && rightInset != 0 && rightInset > leftInset && fullWidth && AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y) {
canvas.drawRect(containerView.getRight() - backgroundPaddingLeft, containerView.getTranslationY(), containerView.getRight() + rightInset, getMeasuredHeight(), backgroundPaint);
}
if (drawNavigationBar && leftInset != 0 && leftInset > rightInset && fullWidth && AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y) {
canvas.drawRect(0, containerView.getTranslationY(), containerView.getLeft() + backgroundPaddingLeft, getMeasuredHeight(), backgroundPaint);
}
if (containerView.getTranslationY() < 0) {
backgroundPaint.setColor(behindKeyboardColorKey != null ? getThemedColor(behindKeyboardColorKey) : behindKeyboardColor);
canvas.drawRect(containerView.getLeft() + backgroundPaddingLeft, containerView.getY() + containerView.getMeasuredHeight(), containerView.getRight() - backgroundPaddingLeft, getMeasuredHeight(), backgroundPaint);
}
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
if (child instanceof CameraView) {
if (shouldOverlayCameraViewOverNavBar()) {
drawNavigationBar(canvas);
}
return super.drawChild(canvas, child, drawingTime);
}
return super.drawChild(canvas, child, drawingTime);
}
@Override
protected void onDraw(Canvas canvas) {
boolean restore = false;
if (backgroundPaint.getAlpha() < 255 && drawNavigationBar) {
float translation = 0;
if (scrollNavBar || Build.VERSION.SDK_INT >= 29 && getAdditionalMandatoryOffsets() > 0) {
float dist = containerView.getMeasuredHeight() - containerView.getTranslationY();
translation = Math.max(0, getBottomInset() - dist);
}
int navBarHeight = drawNavigationBar ? getBottomInset() : 0;
canvas.save();
canvas.clipRect(containerView.getLeft() + backgroundPaddingLeft, getMeasuredHeight() - navBarHeight + translation - currentPanTranslationY, containerView.getRight() - backgroundPaddingLeft, getMeasuredHeight() + translation, Region.Op.DIFFERENCE);
restore = true;
}
super.onDraw(canvas);
if (lastInsets != null && keyboardHeight != 0) {
backgroundPaint.setColor(behindKeyboardColorKey != null ? getThemedColor(behindKeyboardColorKey) : behindKeyboardColor);
canvas.drawRect(containerView.getLeft() + backgroundPaddingLeft, getMeasuredHeight() - keyboardHeight - (drawNavigationBar ? getBottomInset() : 0), containerView.getRight() - backgroundPaddingLeft, getMeasuredHeight() - (drawNavigationBar ? getBottomInset() : 0), backgroundPaint);
}
onContainerDraw(canvas);
if (restore) {
canvas.restore();
}
}
public void drawNavigationBar(Canvas canvas) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (navBarColorKey != null) {
backgroundPaint.setColor(getThemedColor(navBarColorKey));
@ -602,29 +679,11 @@ public class BottomSheet extends Dialog {
canvas.drawRect(containerView.getLeft() + backgroundPaddingLeft, getMeasuredHeight() - navBarHeight + translation - currentPanTranslationY, containerView.getRight() - backgroundPaddingLeft, getMeasuredHeight() + translation, backgroundPaint);
}
}
if (drawNavigationBar && rightInset != 0 && rightInset > leftInset && fullWidth && AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y) {
canvas.drawRect(containerView.getRight() - backgroundPaddingLeft, containerView.getTranslationY(), containerView.getRight() + rightInset, getMeasuredHeight(), backgroundPaint);
}
if (drawNavigationBar && leftInset != 0 && leftInset > rightInset && fullWidth && AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y) {
canvas.drawRect(0, containerView.getTranslationY(), containerView.getLeft() + backgroundPaddingLeft, getMeasuredHeight(), backgroundPaint);
}
if (containerView.getTranslationY() < 0) {
backgroundPaint.setColor(behindKeyboardColorKey != null ? getThemedColor(behindKeyboardColorKey) : behindKeyboardColor);
canvas.drawRect(containerView.getLeft() + backgroundPaddingLeft, containerView.getY() + containerView.getMeasuredHeight(), containerView.getRight() - backgroundPaddingLeft, getMeasuredHeight(), backgroundPaint);
}
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (lastInsets != null && keyboardHeight != 0) {
backgroundPaint.setColor(behindKeyboardColorKey != null ? getThemedColor(behindKeyboardColorKey) : behindKeyboardColor);
canvas.drawRect(containerView.getLeft() + backgroundPaddingLeft, getMeasuredHeight() - keyboardHeight - (drawNavigationBar ? getBottomInset() : 0), containerView.getRight() - backgroundPaddingLeft, getMeasuredHeight() - (drawNavigationBar ? getBottomInset() : 0), backgroundPaint);
}
onContainerDraw(canvas);
}
protected boolean shouldOverlayCameraViewOverNavBar() {
return false;
}
public void setHideSystemVerticalInsets(boolean hideSystemVerticalInsets) {
@ -866,9 +925,6 @@ public class BottomSheet extends Dialog {
super.onCreate(savedInstanceState);
Window window = getWindow();
/*if (Build.VERSION.SDK_INT >= 30) {
window.setDecorFitsSystemWindows(true);
}*/
window.setWindowAnimations(R.style.DialogNoAnimation);
setContentView(container, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
@ -1045,7 +1101,7 @@ public class BottomSheet extends Dialog {
backDrawable.setAlpha(0);
if (Build.VERSION.SDK_INT >= 18) {
layoutCount = 2;
containerView.setTranslationY((Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight * (1f - hideSystemVerticalInsetsProgress) : 0) + containerView.getMeasuredHeight());
containerView.setTranslationY((Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight * (1f - hideSystemVerticalInsetsProgress) : 0) + containerView.getMeasuredHeight() + (scrollNavBar ? getBottomInset() : 0));
AndroidUtilities.runOnUIThread(startAnimationRunnable = new Runnable() {
@Override
public void run() {
@ -1163,7 +1219,7 @@ public class BottomSheet extends Dialog {
if (Build.VERSION.SDK_INT >= 20 && useHardwareLayer) {
container.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
containerView.setTranslationY(containerView.getMeasuredHeight());
containerView.setTranslationY(containerView.getMeasuredHeight() + (scrollNavBar ? getBottomInset() : 0));
currentSheetAnimationType = 1;
currentSheetAnimation = new AnimatorSet();
currentSheetAnimation.playTogether(
@ -1277,7 +1333,7 @@ public class BottomSheet extends Dialog {
currentSheetAnimationType = 2;
currentSheetAnimation = new AnimatorSet();
currentSheetAnimation.playTogether(
ObjectAnimator.ofFloat(containerView, View.TRANSLATION_Y, containerView.getMeasuredHeight() + AndroidUtilities.dp(10)),
ObjectAnimator.ofFloat(containerView, View.TRANSLATION_Y, containerView.getMeasuredHeight() + AndroidUtilities.dp(10) + (scrollNavBar ? getBottomInset() : 0)),
ObjectAnimator.ofInt(backDrawable, AnimationProperties.COLOR_DRAWABLE_ALPHA, 0)
);
currentSheetAnimation.setDuration(180);
@ -1343,7 +1399,7 @@ public class BottomSheet extends Dialog {
currentSheetAnimationType = 2;
currentSheetAnimation = new AnimatorSet();
currentSheetAnimation.playTogether(
ObjectAnimator.ofFloat(containerView, View.TRANSLATION_Y, containerView.getMeasuredHeight() + container.keyboardHeight + AndroidUtilities.dp(10)),
ObjectAnimator.ofFloat(containerView, View.TRANSLATION_Y, containerView.getMeasuredHeight() + container.keyboardHeight + AndroidUtilities.dp(10) + (scrollNavBar ? getBottomInset() : 0)),
ObjectAnimator.ofInt(backDrawable, AnimationProperties.COLOR_DRAWABLE_ALPHA, 0)
);
if (useFastDismiss) {
@ -1519,6 +1575,13 @@ public class BottomSheet extends Dialog {
bottomSheet.setOnHideListener(onDismissListener);
return this;
}
public Builder fixNavigationBar() {
bottomSheet.drawNavigationBar = true;
bottomSheet.scrollNavBar = true;
bottomSheet.setOverlayNavBarColor(bottomSheet.getThemedColor(Theme.key_dialogBackground));
return this;
}
}
public int getLeftInset() {
@ -1580,13 +1643,15 @@ public class BottomSheet extends Dialog {
container.invalidate();
}
if (Color.alpha(color) > 120) {
AndroidUtilities.setLightStatusBar(getWindow(), false);
AndroidUtilities.setLightNavigationBar(getWindow(), false);
} else {
AndroidUtilities.setLightNavigationBar(getWindow(), !useLightNavBar);
AndroidUtilities.setLightStatusBar(getWindow(), !useLightStatusBar);
}
// if (Color.alpha(color) > 120) {
// AndroidUtilities.setLightStatusBar(getWindow(), false);
// AndroidUtilities.setLightNavigationBar(getWindow(), false);
// } else {
// AndroidUtilities.setLightStatusBar(getWindow(), !useLightStatusBar);
// AndroidUtilities.setLightNavigationBar(getWindow(), !useLightNavBar);
// }
AndroidUtilities.setNavigationBarColor(getWindow(), overlayDrawNavBarColor);
AndroidUtilities.setLightNavigationBar(getWindow(), AndroidUtilities.computePerceivedBrightness(overlayDrawNavBarColor) > .721);
}
public ViewGroup getContainerView() {

View File

@ -311,6 +311,13 @@ public class DrawerLayoutContainer extends FrameLayout {
parentActionBarLayout = layout;
}
public void presentFragment(BaseFragment fragment) {
if (parentActionBarLayout != null) {
parentActionBarLayout.presentFragment(fragment);
}
closeDrawer(false);
}
public void closeDrawer() {
if (drawerPosition != 0) {
setDrawerPosition(0);

View File

@ -1538,6 +1538,14 @@ public class Theme {
currentColors.put(key_chat_wallpaper_gradient_rotation, backgroundRotation);
}
Integer outBubble = currentColors.get(key_chat_outBubble);
if (outBubble == null) {
outBubble = getColor(key_chat_outBubble);
}
Integer inBubble = currentColors.get(key_chat_inBubble);
if (inBubble == null) {
inBubble = getColor(key_chat_inBubble);
}
if (info != null && info.emoticon != null && !isDarkTheme) {
currentColors.remove(key_chat_selectedBackground);
int gradientAverageColor = averageColor(currentColors, key_chat_wallpaper_gradient_to1, key_chat_wallpaper_gradient_to2, key_chat_wallpaper_gradient_to3);
@ -1548,24 +1556,27 @@ public class Theme {
gradientAverageColor = accentColor;
}
Integer outBubble = currentColors.get(key_chat_outBubble);
if (outBubble == null) {
outBubble = getColor(key_chat_outBubble);
}
int outOverlay = bubbleSelectedOverlay(outBubble, gradientAverageColor);
currentColors.put(key_chat_outBubbleSelectedOverlay, outOverlay);
currentColors.put(key_chat_outBubbleGradientSelectedOverlay, outOverlay);
currentColors.put(key_chat_outBubbleSelected, Theme.blendOver(outBubble, outOverlay));
Integer inBubble = currentColors.get(key_chat_inBubble);
if (inBubble == null) {
inBubble = getColor(key_chat_inBubble);
}
int inOverlay = bubbleSelectedOverlay(inBubble, accentColor);
currentColors.put(key_chat_inBubbleSelectedOverlay, inOverlay);
currentColors.put(key_chat_inBubbleSelected, Theme.blendOver(inBubble, inOverlay));
}
Integer inMsgLink = currentColors.get(key_chat_messageLinkIn);
if (inMsgLink == null) {
inMsgLink = getColor(key_chat_messageLinkIn);
}
Integer outMsgLink = currentColors.get(key_chat_messageLinkOut);
if (outMsgLink == null) {
outMsgLink = getColor(key_chat_messageLinkOut);
}
currentColors.put(key_chat_linkSelectBackground, linkSelectionBackground(inMsgLink, inBubble, isDarkTheme));
currentColors.put(key_chat_outLinkSelectBackground, linkSelectionBackground(outMsgLink, outBubble, isDarkTheme));
return !isMyMessagesGradientColorsNear;
}
@ -1581,6 +1592,12 @@ public class Theme {
tempHSV[2] = Math.max(0, Math.min(1, tempHSV[2] - .05f));
return Color.HSVToColor(30, tempHSV);
}
private int linkSelectionBackground(int linkColor, int bgColor, boolean isDarkTheme) {
Color.colorToHSV(ColorUtils.blendARGB(linkColor, bgColor, .25f), tempHSV);
tempHSV[1] = Math.max(0, Math.min(1, tempHSV[1] - .1f));
tempHSV[2] = Math.max(0, Math.min(1, tempHSV[2] + (isDarkTheme ? .1f : 0)));
return Color.HSVToColor(0x33, tempHSV);
}
private int averageColor(HashMap<String, Integer> colors, String ...keys) {
int r = 0, g = 0, b = 0, c = 0;
for (int i = 0; i < keys.length; ++i) {
@ -2827,6 +2844,7 @@ public class Theme {
public static Paint chat_deleteProgressPaint;
public static Paint chat_botProgressPaint;
public static Paint chat_urlPaint;
public static Paint chat_outUrlPaint;
public static Paint chat_textSearchSelectionPaint;
public static Paint chat_instantViewRectPaint;
public static Paint chat_pollTimerPaint;
@ -2946,9 +2964,11 @@ public class Theme {
public static Drawable chat_shareIconDrawable;
public static Drawable chat_replyIconDrawable;
public static Drawable chat_goIconDrawable;
public static Drawable chat_botLinkDrawalbe;
public static Drawable chat_botCardDrawalbe;
public static Drawable chat_botLinkDrawable;
public static Drawable chat_botCardDrawable;
public static Drawable chat_botInlineDrawable;
public static Drawable chat_botWebViewDrawable;
public static Drawable chat_botInviteDrawable;
public static Drawable chat_commentDrawable;
public static Drawable chat_commentStickerDrawable;
public static Drawable chat_commentArrowDrawable;
@ -3442,6 +3462,7 @@ public class Theme {
public static final String key_chat_outVenueInfoSelectedText = "chat_outVenueInfoSelectedText";
public static final String key_chat_mediaInfoText = "chat_mediaInfoText";
public static final String key_chat_linkSelectBackground = "chat_linkSelectBackground";
public static final String key_chat_outLinkSelectBackground = "chat_outLinkSelectBackground";
public static final String key_chat_textSelectBackground = "chat_textSelectBackground";
public static final String key_chat_wallpaper = "chat_wallpaper";
public static final String key_chat_wallpaper_gradient_to1 = "chat_wallpaper_gradient_to";
@ -3792,6 +3813,8 @@ public class Theme {
public static final String key_drawable_botInline = "drawableBotInline";
public static final String key_drawable_botLink = "drawableBotLink";
public static final String key_drawable_botWebView = "drawableBotWebView";
public static final String key_drawable_botInvite = "drawable_botInvite";
public static final String key_drawable_commentSticker = "drawableCommentSticker";
public static final String key_drawable_goIcon = "drawableGoIcon";
public static final String key_drawable_msgError = "drawableMsgError";
@ -4299,6 +4322,7 @@ public class Theme {
defaultColors.put(key_chat_outVenueInfoSelectedText, 0xff65b05b);
defaultColors.put(key_chat_mediaInfoText, 0xffffffff);
defaultColors.put(key_chat_linkSelectBackground, 0x3362a9e3);
defaultColors.put(key_chat_outLinkSelectBackground, 0x3362a9e3);
defaultColors.put(key_chat_textSelectBackground, 0x6662a9e3);
defaultColors.put(key_chat_emojiPanelBackground, 0xfff0f2f5);
defaultColors.put(key_chat_emojiPanelBadgeBackground, 0xff4da6ea);
@ -4739,6 +4763,7 @@ public class Theme {
fallbackKeys.put(key_actionBarTabSelector, key_actionBarDefaultSelector);
fallbackKeys.put(key_profile_status, key_avatar_subtitleInProfileBlue);
fallbackKeys.put(key_chats_menuTopBackgroundCats, key_avatar_backgroundActionBarBlue);
fallbackKeys.put(key_chat_outLinkSelectBackground, key_chat_linkSelectBackground);
//fallbackKeys.put(key_chat_attachActiveTab, 0xff33a7f5);
//fallbackKeys.put(key_chat_attachUnactiveTab, 0xff92999e);
fallbackKeys.put(key_chat_attachPermissionImage, key_dialogTextBlack);
@ -5828,6 +5853,12 @@ public class Theme {
return defaultDrawable;
}
public static Drawable createRoundRectDrawable(int topRad, int bottomRad, int defaultColor) {
ShapeDrawable defaultDrawable = new ShapeDrawable(new RoundRectShape(new float[]{topRad, topRad, topRad, topRad, bottomRad, bottomRad, bottomRad, bottomRad}, null, null));
defaultDrawable.getPaint().setColor(defaultColor);
return defaultDrawable;
}
public static Drawable createServiceDrawable(int rad, View view, View containerView) {
return createServiceDrawable(rad, view, containerView, chat_actionBackgroundPaint);
}
@ -5918,6 +5949,23 @@ public class Theme {
}
}
public static Drawable getRoundRectSelectorWithBackgroundDrawable(int corners, int bgColor, int color) {
if (Build.VERSION.SDK_INT >= 21) {
Drawable maskDrawable = createRoundRectDrawable(corners, 0xffffffff);
ColorStateList colorStateList = new ColorStateList(
new int[][]{StateSet.WILD_CARD},
new int[]{color}
);
return new RippleDrawable(colorStateList, createRoundRectDrawable(corners, bgColor), maskDrawable);
} else {
StateListDrawable stateListDrawable = new StateListDrawable();
stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, createRoundRectDrawable(corners, color));
stateListDrawable.addState(new int[]{android.R.attr.state_selected}, createRoundRectDrawable(corners, color));
stateListDrawable.addState(StateSet.WILD_CARD, new ColorDrawable(bgColor));
return stateListDrawable;
}
}
public static Drawable createSelectorWithBackgroundDrawable(int backgroundColor, int color) {
if (Build.VERSION.SDK_INT >= 21) {
Drawable maskDrawable = new ColorDrawable(backgroundColor);
@ -6091,8 +6139,8 @@ public class Theme {
public static class RippleRadMaskDrawable extends Drawable {
private Path path = new Path();
private RectF rect = new RectF();
private float[] radii = new float[8];
boolean invalidatePath = true;
public RippleRadMaskDrawable(float top, float bottom) {
radii[0] = radii[1] = radii[2] = radii[3] = AndroidUtilities.dp(top);
@ -6108,6 +6156,7 @@ public class Theme {
public void setRadius(float top, float bottom) {
radii[0] = radii[1] = radii[2] = radii[3] = AndroidUtilities.dp(top);
radii[4] = radii[5] = radii[6] = radii[7] = AndroidUtilities.dp(bottom);
invalidatePath = true;
invalidateSelf();
}
public void setRadius(float topLeft, float topRight, float bottomRight, float bottomLeft) {
@ -6115,13 +6164,23 @@ public class Theme {
radii[2] = radii[3] = AndroidUtilities.dp(topRight);
radii[4] = radii[5] = AndroidUtilities.dp(bottomRight);
radii[6] = radii[7] = AndroidUtilities.dp(bottomLeft);
invalidatePath = true;
invalidateSelf();
}
@Override
protected void onBoundsChange(Rect bounds) {
invalidatePath = true;
}
@Override
public void draw(Canvas canvas) {
rect.set(getBounds());
path.addRoundRect(rect, radii, Path.Direction.CW);
if (invalidatePath) {
invalidatePath = false;
path.reset();
AndroidUtilities.rectTmp.set(getBounds());
path.addRoundRect(AndroidUtilities.rectTmp, radii, Path.Direction.CW);
}
canvas.drawPath(path, maskPaint);
}
@ -8110,7 +8169,7 @@ public class Theme {
checkboxSquare_backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linkSelectionPaint = new Paint();
linkSelectionPaint.setPathEffect(LinkPath.roundedEffect);
linkSelectionPaint.setPathEffect(LinkPath.getRoundedEffect());
Resources resources = context.getResources();
@ -8432,9 +8491,11 @@ public class Theme {
chat_locationTitlePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
chat_locationAddressPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
chat_urlPaint = new Paint();
chat_urlPaint.setPathEffect(LinkPath.roundedEffect);
chat_urlPaint.setPathEffect(LinkPath.getRoundedEffect());
chat_outUrlPaint = new Paint();
chat_outUrlPaint.setPathEffect(LinkPath.getRoundedEffect());
chat_textSearchSelectionPaint = new Paint();
chat_textSearchSelectionPaint.setPathEffect(LinkPath.roundedEffect);
chat_textSearchSelectionPaint.setPathEffect(LinkPath.getRoundedEffect());
chat_radialProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
chat_radialProgressPaint.setStrokeCap(Paint.Cap.ROUND);
chat_radialProgressPaint.setStyle(Paint.Style.STROKE);
@ -8604,9 +8665,11 @@ public class Theme {
chat_inlineResultLocation = resources.getDrawable(R.drawable.bot_location);
chat_redLocationIcon = resources.getDrawable(R.drawable.map_pin).mutate();
chat_botLinkDrawalbe = resources.getDrawable(R.drawable.bot_link);
chat_botLinkDrawable = resources.getDrawable(R.drawable.bot_link);
chat_botInlineDrawable = resources.getDrawable(R.drawable.bot_lines);
chat_botCardDrawalbe = resources.getDrawable(R.drawable.bot_card);
chat_botCardDrawable = resources.getDrawable(R.drawable.bot_card);
chat_botWebViewDrawable = resources.getDrawable(R.drawable.bot_webview);
chat_botInviteDrawable = resources.getDrawable(R.drawable.bot_invite);
chat_commentDrawable = resources.getDrawable(R.drawable.msg_msgbubble);
chat_commentStickerDrawable = resources.getDrawable(R.drawable.msg_msgbubble2);
@ -8749,7 +8812,9 @@ public class Theme {
defaultChatDrawableColorKeys.clear();
addChatDrawable(key_drawable_botInline, chat_botInlineDrawable, key_chat_serviceIcon);
addChatDrawable(key_drawable_botLink, chat_botLinkDrawalbe, key_chat_serviceIcon);
addChatDrawable(key_drawable_botWebView, chat_botWebViewDrawable, key_chat_serviceIcon);
addChatDrawable(key_drawable_botLink, chat_botLinkDrawable, key_chat_serviceIcon);
addChatDrawable(key_drawable_botInvite, chat_botInviteDrawable, key_chat_serviceIcon);
addChatDrawable(key_drawable_goIcon, chat_goIconDrawable, key_chat_serviceIcon);
addChatDrawable(key_drawable_commentSticker, chat_commentStickerDrawable, key_chat_serviceIcon);
addChatDrawable(key_drawable_msgError, chat_msgErrorDrawable, key_chat_sentErrorIcon);
@ -8872,6 +8937,7 @@ public class Theme {
chat_durationPaint.setColor(getColor(key_chat_previewDurationText));
chat_botButtonPaint.setColor(getColor(key_chat_botButtonText));
chat_urlPaint.setColor(getColor(key_chat_linkSelectBackground));
chat_outUrlPaint.setColor(getColor(key_chat_outLinkSelectBackground));
chat_botProgressPaint.setColor(getColor(key_chat_botProgress));
chat_deleteProgressPaint.setColor(getColor(key_chat_secretTimeText));
chat_textSearchSelectionPaint.setColor(getColor(key_chat_textSelectBackground));
@ -8905,7 +8971,9 @@ public class Theme {
setDrawableColorByKey(chat_replyIconDrawable, key_chat_serviceIcon);
setDrawableColorByKey(chat_goIconDrawable, key_chat_serviceIcon);
setDrawableColorByKey(chat_botInlineDrawable, key_chat_serviceIcon);
setDrawableColorByKey(chat_botLinkDrawalbe, key_chat_serviceIcon);
setDrawableColorByKey(chat_botWebViewDrawable, key_chat_serviceIcon);
setDrawableColorByKey(chat_botInviteDrawable, key_chat_serviceIcon);
setDrawableColorByKey(chat_botLinkDrawable, key_chat_serviceIcon);
setDrawableColorByKey(chat_msgInViewsDrawable, key_chat_inViews);
setDrawableColorByKey(chat_msgInViewsSelectedDrawable, key_chat_inViewsSelected);
setDrawableColorByKey(chat_msgOutViewsDrawable, key_chat_outViews);
@ -9148,7 +9216,9 @@ public class Theme {
setDrawableColor(chat_replyIconDrawable, 0xffffffff);
setDrawableColor(chat_goIconDrawable, 0xffffffff);
setDrawableColor(chat_botInlineDrawable, 0xffffffff);
setDrawableColor(chat_botLinkDrawalbe, 0xffffffff);
setDrawableColor(chat_botWebViewDrawable, 0xffffffff);
setDrawableColor(chat_botInviteDrawable, 0xffffffff);
setDrawableColor(chat_botLinkDrawable, 0xffffffff);
} else {
serviceBitmap = null;
serviceBitmapShader = null;
@ -9165,7 +9235,9 @@ public class Theme {
setDrawableColorByKey(chat_replyIconDrawable, key_chat_serviceIcon);
setDrawableColorByKey(chat_goIconDrawable, key_chat_serviceIcon);
setDrawableColorByKey(chat_botInlineDrawable, key_chat_serviceIcon);
setDrawableColorByKey(chat_botLinkDrawalbe, key_chat_serviceIcon);
setDrawableColorByKey(chat_botWebViewDrawable, key_chat_serviceIcon);
setDrawableColorByKey(chat_botInviteDrawable, key_chat_serviceIcon);
setDrawableColorByKey(chat_botLinkDrawable, key_chat_serviceIcon);
chat_botButtonPaint.setColor(getColor(key_chat_botButtonText));
}

View File

@ -14,8 +14,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.location.LocationManager;
@ -35,6 +33,7 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.IntDef;
import androidx.core.graphics.ColorUtils;
import org.telegram.PhoneFormat.PhoneFormat;
import org.telegram.messenger.AndroidUtilities;
@ -52,7 +51,6 @@ import org.telegram.ui.ActionBar.BaseFragment;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.ActionBar.ThemeDescription;
import org.telegram.ui.Components.AlertsCreator;
import org.telegram.ui.Components.CombinedDrawable;
import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.RLottieImageView;
import org.telegram.ui.Components.ShareLocationDrawable;
@ -134,9 +132,6 @@ public class ActionIntroActivity extends BaseFragment implements LocationControl
actionBar.setItemsBackgroundColor(Theme.getColor(Theme.key_actionBarWhiteSelector), false);
actionBar.setCastShadows(false);
actionBar.setAddToContainer(false);
if (!AndroidUtilities.isTablet()) {
actionBar.showActionModeTop();
}
actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() {
@Override
public void onItemClick(int id) {
@ -234,18 +229,18 @@ public class ActionIntroActivity extends BaseFragment implements LocationControl
break;
}
case ACTION_TYPE_CHANGE_PHONE_NUMBER: {
imageView.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(150), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(150), MeasureSpec.EXACTLY));
if (width > height) {
imageView.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.45f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((int) (height * 0.78f), MeasureSpec.AT_MOST));
subtitleTextView.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.45f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED));
titleTextView.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.6f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED));
descriptionText.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.6f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED));
buttonTextView.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.6f), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(42), MeasureSpec.EXACTLY));
} else {
imageView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((int) (height * 0.44f), MeasureSpec.AT_MOST));
titleTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED));
subtitleTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED));
descriptionText.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED));
buttonTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(42), MeasureSpec.EXACTLY));
subtitleTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED));
buttonTextView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(24 * 2), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(50), MeasureSpec.EXACTLY));
}
break;
}
@ -435,10 +430,9 @@ public class ActionIntroActivity extends BaseFragment implements LocationControl
case ACTION_TYPE_CHANGE_PHONE_NUMBER: {
if (r > b) {
int y = (int) (height * 0.95f - imageView.getMeasuredHeight()) / 2;
imageView.layout(0, y, imageView.getMeasuredWidth(), y + imageView.getMeasuredHeight());
y += imageView.getMeasuredHeight() + AndroidUtilities.dp(10);
subtitleTextView.layout(0, y, subtitleTextView.getMeasuredWidth(), y + subtitleTextView.getMeasuredHeight());
int x = (int) (width * 0.4f);
int x = (int) (getWidth() * 0.35f - imageView.getMeasuredWidth());
imageView.layout(x, y, x + imageView.getMeasuredWidth(), y + imageView.getMeasuredHeight());
x = (int) (width * 0.4f);
y = (int) (height * 0.12f);
titleTextView.layout(x, y, x + titleTextView.getMeasuredWidth(), y + titleTextView.getMeasuredHeight());
x = (int) (width * 0.4f);
@ -447,18 +441,25 @@ public class ActionIntroActivity extends BaseFragment implements LocationControl
x = (int) (width * 0.4f + (width * 0.6f - buttonTextView.getMeasuredWidth()) / 2);
y = (int) (height * 0.8f);
buttonTextView.layout(x, y, x + buttonTextView.getMeasuredWidth(), y + buttonTextView.getMeasuredHeight());
x = (int) (width * 0.4f + (width * 0.6f - subtitleTextView.getMeasuredWidth()) / 2);
y -= subtitleTextView.getMeasuredHeight() + AndroidUtilities.dp(16);
subtitleTextView.layout(x, y, x + subtitleTextView.getMeasuredWidth(), y + subtitleTextView.getMeasuredHeight());
} else {
int y = (int) (height * 0.2229f);
imageView.layout(0, y, imageView.getMeasuredWidth(), y + imageView.getMeasuredHeight());
y = (int) (height * 0.352f);
int y = (int) (height * 0.3f);
int x = (width - imageView.getMeasuredWidth()) / 2;
imageView.layout(x, y, x + imageView.getMeasuredWidth(), y + imageView.getMeasuredHeight());
y += imageView.getMeasuredHeight() + AndroidUtilities.dp(24);
titleTextView.layout(0, y, titleTextView.getMeasuredWidth(), y + titleTextView.getMeasuredHeight());
y = (int) (height * 0.409f);
subtitleTextView.layout(0, y, subtitleTextView.getMeasuredWidth(), y + subtitleTextView.getMeasuredHeight());
y = (int) (height * 0.468f);
y += titleTextView.getTextSize() + AndroidUtilities.dp(16);
descriptionText.layout(0, y, descriptionText.getMeasuredWidth(), y + descriptionText.getMeasuredHeight());
int x = (width - buttonTextView.getMeasuredWidth()) / 2;
y = (int) (height * 0.805f);
x = (width - buttonTextView.getMeasuredWidth()) / 2;
y = height - buttonTextView.getMeasuredHeight() - AndroidUtilities.dp(48);
buttonTextView.layout(x, y, x + buttonTextView.getMeasuredWidth(), y + buttonTextView.getMeasuredHeight());
x = (width - subtitleTextView.getMeasuredWidth()) / 2;
y -= subtitleTextView.getMeasuredHeight() + AndroidUtilities.dp(32);
subtitleTextView.layout(x, y, x + subtitleTextView.getMeasuredWidth(), y + subtitleTextView.getMeasuredHeight());
}
break;
}
@ -484,7 +485,7 @@ public class ActionIntroActivity extends BaseFragment implements LocationControl
viewGroup.addView(titleTextView);
subtitleTextView = new TextView(context);
subtitleTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
subtitleTextView.setTextColor(Theme.getColor(currentType == ACTION_TYPE_CHANGE_PHONE_NUMBER ? Theme.key_featuredStickers_addButton : Theme.key_windowBackgroundWhiteBlackText));
subtitleTextView.setGravity(Gravity.CENTER_HORIZONTAL);
subtitleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
subtitleTextView.setSingleLine(true);
@ -502,7 +503,7 @@ public class ActionIntroActivity extends BaseFragment implements LocationControl
descriptionText.setGravity(Gravity.CENTER_HORIZONTAL);
descriptionText.setLineSpacing(AndroidUtilities.dp(2), 1);
descriptionText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
if (currentType == ACTION_TYPE_SET_PASSCODE) {
if (currentType == ACTION_TYPE_SET_PASSCODE || currentType == ACTION_TYPE_CHANGE_PHONE_NUMBER) {
descriptionText.setPadding(AndroidUtilities.dp(48), 0, AndroidUtilities.dp(48), 0);
} else if (currentType == ACTION_TYPE_NEARBY_GROUP_CREATE) {
descriptionText.setPadding(AndroidUtilities.dp(24), 0, AndroidUtilities.dp(24), 0);
@ -604,7 +605,7 @@ public class ActionIntroActivity extends BaseFragment implements LocationControl
buttonTextView.setTextColor(Theme.getColor(Theme.key_featuredStickers_buttonText));
buttonTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
buttonTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
int buttonRadiusDp = currentType == ACTION_TYPE_SET_PASSCODE ? 6 : 4;
int buttonRadiusDp = currentType == ACTION_TYPE_SET_PASSCODE || currentType == ACTION_TYPE_CHANGE_PHONE_NUMBER ? 6 : 4;
buttonTextView.setBackground(Theme.createSimpleSelectorRoundRectDrawable(AndroidUtilities.dp(buttonRadiusDp), Theme.getColor(Theme.key_featuredStickers_addButton), Theme.getColor(Theme.key_featuredStickers_addButtonPressed)));
viewGroup.addView(buttonTextView);
buttonTextView.setOnClickListener(v -> {
@ -741,23 +742,29 @@ public class ActionIntroActivity extends BaseFragment implements LocationControl
}
case ACTION_TYPE_CHANGE_PHONE_NUMBER: {
subtitleTextView.setVisibility(View.VISIBLE);
drawable1 = context.getResources().getDrawable(R.drawable.sim_old);
drawable2 = context.getResources().getDrawable(R.drawable.sim_new);
drawable1.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_changephoneinfo_image), PorterDuff.Mode.SRC_IN));
drawable2.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_changephoneinfo_image2), PorterDuff.Mode.SRC_IN));
imageView.setImageDrawable(new CombinedDrawable(drawable1, drawable2));
imageView.setScaleType(ImageView.ScaleType.CENTER);
imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
imageView.setAnimation(R.raw.utyan_change_number, 200, 200);
imageView.setOnClickListener(v -> {
if (!imageView.getAnimatedDrawable().isRunning()) {
imageView.getAnimatedDrawable().setCurrentFrame(0, false);
imageView.playAnimation();
}
});
UserConfig userConfig = getUserConfig();
TLRPC.User user = getMessagesController().getUser(userConfig.clientUserId);
if (user == null) {
user = userConfig.getCurrentUser();
}
if (user != null) {
subtitleTextView.setText(PhoneFormat.getInstance().format("+" + user.phone));
subtitleTextView.setText(LocaleController.formatString("PhoneNumberKeepButton", R.string.PhoneNumberKeepButton, PhoneFormat.getInstance().format("+" + user.phone)));
}
subtitleTextView.setOnClickListener(v -> getParentLayout().closeLastFragment(true));
titleTextView.setText(LocaleController.getString("PhoneNumberChange2", R.string.PhoneNumberChange2));
descriptionText.setText(AndroidUtilities.replaceTags(LocaleController.getString("PhoneNumberHelp", R.string.PhoneNumberHelp)));
buttonTextView.setText(LocaleController.getString("PhoneNumberChange2", R.string.PhoneNumberChange2));
imageView.playAnimation();
flickerButton = true;
break;
}
}
@ -770,11 +777,6 @@ public class ActionIntroActivity extends BaseFragment implements LocationControl
return fragmentView;
}
@Override
public boolean hasForceLightStatusBar() {
return true;
}
@Override
public void onLocationAddressAvailable(String address, String displayAddress, Location location) {
if (subtitleTextView == null) {
@ -927,4 +929,10 @@ public class ActionIntroActivity extends BaseFragment implements LocationControl
return themeDescriptions;
}
@Override
public boolean isLightStatusBar() {
int color = Theme.getColor(Theme.key_windowBackgroundWhite, null, true);
return ColorUtils.calculateLuminance(color) > 0.7f;
}
}

View File

@ -17,6 +17,7 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
@ -27,8 +28,10 @@ import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.ViewPager;
import org.telegram.messenger.AccountInstance;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.BuildVars;
import org.telegram.messenger.ChatObject;
import org.telegram.messenger.ContactsController;
import org.telegram.messenger.DialogObject;
import org.telegram.messenger.FileLog;
@ -47,6 +50,7 @@ import org.telegram.ui.Cells.DialogCell;
import org.telegram.ui.Cells.DialogMeUrlCell;
import org.telegram.ui.Cells.DialogsEmptyCell;
import org.telegram.ui.Cells.HeaderCell;
import org.telegram.ui.Cells.ProfileSearchCell;
import org.telegram.ui.Cells.ShadowSectionCell;
import org.telegram.ui.Cells.TextCell;
import org.telegram.ui.Cells.TextInfoPrivacyCell;
@ -77,7 +81,8 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter {
VIEW_TYPE_LAST_EMPTY = 10,
VIEW_TYPE_NEW_CHAT_HINT = 11,
VIEW_TYPE_TEXT = 12,
VIEW_TYPE_CONTACTS_FLICKER = 13;
VIEW_TYPE_CONTACTS_FLICKER = 13,
VIEW_TYPE_HEADER_2 = 14;
private Context mContext;
private ArchiveHintCell archiveHintCell;
@ -400,10 +405,14 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter {
View view;
switch (viewType) {
case VIEW_TYPE_DIALOG:
DialogCell dialogCell = new DialogCell(parentFragment, mContext, true, false, currentAccount, null);
dialogCell.setArchivedPullAnimation(pullForegroundDrawable);
dialogCell.setPreloader(preloader);
view = dialogCell;
if (dialogsType == 2) {
view = new ProfileSearchCell(mContext);
} else {
DialogCell dialogCell = new DialogCell(parentFragment, mContext, true, false, currentAccount, null);
dialogCell.setArchivedPullAnimation(pullForegroundDrawable);
dialogCell.setPreloader(preloader);
view = dialogCell;
}
break;
case VIEW_TYPE_FLICKER:
case VIEW_TYPE_CONTACTS_FLICKER:
@ -462,6 +471,12 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter {
view = new HeaderCell(mContext);
view.setPadding(0, 0, 0, AndroidUtilities.dp(12));
break;
case VIEW_TYPE_HEADER_2:
HeaderCell cell = new HeaderCell(mContext, Theme.key_graySectionText, 16, 0, false);
cell.setHeight(32);
view = cell;
view.setClickable(false);
break;
case VIEW_TYPE_SHADOW: {
view = new ShadowSectionCell(mContext);
Drawable drawable = Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow);
@ -559,20 +574,72 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter {
public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) {
switch (holder.getItemViewType()) {
case VIEW_TYPE_DIALOG: {
DialogCell cell = (DialogCell) holder.itemView;
TLRPC.Dialog dialog = (TLRPC.Dialog) getItem(i);
TLRPC.Dialog nextDialog = (TLRPC.Dialog) getItem(i + 1);
cell.useSeparator = nextDialog != null;
cell.fullSeparator = dialog.pinned && nextDialog != null && !nextDialog.pinned;
if (dialogsType == 0) {
if (AndroidUtilities.isTablet()) {
cell.setDialogSelected(dialog.id == openedDialogId);
if (dialogsType == 2) {
ProfileSearchCell cell = (ProfileSearchCell) holder.itemView;
long oldDialogId = cell.getDialogId();
TLRPC.Chat chat = null;
CharSequence title = null;
CharSequence subtitle;
boolean isRecent = false;
if (dialog.id != 0) {
chat = MessagesController.getInstance(currentAccount).getChat(-dialog.id);
if (chat != null && chat.migrated_to != null) {
TLRPC.Chat chat2 = MessagesController.getInstance(currentAccount).getChat(chat.migrated_to.channel_id);
if (chat2 != null) {
chat = chat2;
}
}
}
if (chat != null) {
title = chat.title;
if (ChatObject.isChannel(chat) && !chat.megagroup) {
if (chat.participants_count != 0) {
subtitle = LocaleController.formatPluralStringComma("Subscribers", chat.participants_count);
} else {
if (TextUtils.isEmpty(chat.username)) {
subtitle = LocaleController.getString("ChannelPrivate", R.string.ChannelPrivate).toLowerCase();
} else {
subtitle = LocaleController.getString("ChannelPublic", R.string.ChannelPublic).toLowerCase();
}
}
} else {
if (chat.participants_count != 0) {
subtitle = LocaleController.formatPluralStringComma("Members", chat.participants_count);
} else {
if (chat.has_geo) {
subtitle = LocaleController.getString("MegaLocation", R.string.MegaLocation);
} else if (TextUtils.isEmpty(chat.username)) {
subtitle = LocaleController.getString("MegaPrivate", R.string.MegaPrivate).toLowerCase();
} else {
subtitle = LocaleController.getString("MegaPublic", R.string.MegaPublic).toLowerCase();
}
}
}
} else {
subtitle = "";
}
cell.useSeparator = nextDialog != null;
cell.setData(chat, null, title, subtitle, isRecent, false);
cell.setChecked(selectedDialogs.contains(cell.getDialogId()), oldDialogId == cell.getDialogId());
} else {
DialogCell cell = (DialogCell) holder.itemView;
cell.useSeparator = nextDialog != null;
cell.fullSeparator = dialog.pinned && nextDialog != null && !nextDialog.pinned;
if (dialogsType == 0) {
if (AndroidUtilities.isTablet()) {
cell.setDialogSelected(dialog.id == openedDialogId);
}
}
cell.setChecked(selectedDialogs.contains(dialog.id), false);
cell.setDialog(dialog, dialogsType, folderId);
if (preloader != null && i < 10) {
preloader.add(dialog.id);
}
}
cell.setChecked(selectedDialogs.contains(dialog.id), false);
cell.setDialog(dialog, dialogsType, folderId);
if (preloader != null && i < 10) {
preloader.add(dialog.id);
}
break;
}
@ -631,6 +698,32 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter {
}
break;
}
case VIEW_TYPE_HEADER_2: {
HeaderCell cell = (HeaderCell) holder.itemView;
cell.setTextSize(14);
cell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText));
cell.setBackgroundColor(Theme.getColor(Theme.key_graySection));
try {
MessagesController messagesController = AccountInstance.getInstance(currentAccount).getMessagesController();
int j = 0;
if (messagesController.dialogsMyChannels.size() > 0) {
if (i == j) {
cell.setText(LocaleController.getString("MyChannels", R.string.MyChannels));
}
j += 1 + messagesController.dialogsMyChannels.size();
}
if (messagesController.dialogsMyGroups.size() > 0) {
if (i == j) {
cell.setText(LocaleController.getString("MyGroups", R.string.MyGroups));
}
j += 1 + messagesController.dialogsMyGroups.size();
}
if (messagesController.dialogsCanAddUsers.size() > 0 && i == j) {
cell.setText(LocaleController.getString("FilterGroups", R.string.FilterGroups));
}
} catch (Exception ignore) {}
break;
}
case VIEW_TYPE_NEW_CHAT_HINT: {
TextInfoPrivacyCell cell = (TextInfoPrivacyCell) holder.itemView;
cell.setText(LocaleController.getString("TapOnThePencil", R.string.TapOnThePencil));
@ -746,6 +839,9 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter {
} else if (i > size) {
return VIEW_TYPE_LAST_EMPTY;
}
if (dialogsType == 2 && getItem(i) == null) {
return VIEW_TYPE_HEADER_2;
}
return VIEW_TYPE_DIALOG;
}
@ -929,6 +1025,7 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter {
blurOffset = ((BlurredRecyclerView) parent).blurTopPadding;
}
int paddingTop = parent.getPaddingTop();
paddingTop -= blurOffset;
if (size == 0 || paddingTop == 0 && !hasArchive) {
height = 0;
} else {

View File

@ -65,6 +65,14 @@ import androidx.recyclerview.widget.RecyclerView;
public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter {
private final int VIEW_TYPE_PROFILE_CELL = 0;
private final int VIEW_TYPE_GRAY_SECTION = 1;
private final int VIEW_TYPE_DIALOG_CELL = 2;
private final int VIEW_TYPE_LOADING = 3;
private final int VIEW_TYPE_HASHTAG_CELL = 4;
private final int VIEW_TYPE_CATEGORY_LIST = 5;
private final int VIEW_TYPE_ADD_BY_PHONE = 6;
private Context mContext;
private Runnable searchRunnable;
private Runnable searchRunnable2;
@ -938,25 +946,25 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter {
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
switch (viewType) {
case 0:
case VIEW_TYPE_PROFILE_CELL:
view = new ProfileSearchCell(mContext);
break;
case 1:
case VIEW_TYPE_GRAY_SECTION:
view = new GraySectionCell(mContext);
break;
case 2:
case VIEW_TYPE_DIALOG_CELL:
view = new DialogCell(null, mContext, false, true);
break;
case 3:
case VIEW_TYPE_LOADING:
FlickerLoadingView flickerLoadingView = new FlickerLoadingView(mContext);
flickerLoadingView.setViewType(FlickerLoadingView.DIALOG_TYPE);
flickerLoadingView.setIsSingleCell(true);
view = flickerLoadingView;
break;
case 4:
case VIEW_TYPE_HASHTAG_CELL:
view = new HashtagSearchCell(mContext);
break;
case 5:
case VIEW_TYPE_CATEGORY_LIST:
RecyclerListView horizontalListView = new RecyclerListView(mContext) {
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
@ -966,7 +974,6 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter {
return super.onInterceptTouchEvent(e);
}
};
horizontalListView.setSelectorRadius(AndroidUtilities.dp(4));
horizontalListView.setSelectorDrawableColor(Theme.getColor(Theme.key_listSelector));
horizontalListView.setTag(9);
horizontalListView.setItemAnimator(null);
@ -995,7 +1002,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter {
view = horizontalListView;
innerListView = horizontalListView;
break;
case 6:
case VIEW_TYPE_ADD_BY_PHONE:
default:
view = new TextCell(mContext, 16, false);
break;
@ -1011,7 +1018,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter {
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
switch (holder.getItemViewType()) {
case 0: {
case VIEW_TYPE_PROFILE_CELL: {
ProfileSearchCell cell = (ProfileSearchCell) holder.itemView;
long oldDialogId = cell.getDialogId();
@ -1139,7 +1146,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter {
cell.setChecked(delegate.isSelected(cell.getDialogId()), oldDialogId == cell.getDialogId());
break;
}
case 1: {
case VIEW_TYPE_GRAY_SECTION: {
GraySectionCell cell = (GraySectionCell) holder.itemView;
if (isRecentSearchDisplayed()) {
int offset = (!MediaDataController.getInstance(currentAccount).hints.isEmpty() ? 1 : 0);
@ -1209,25 +1216,25 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter {
}
break;
}
case 2: {
case VIEW_TYPE_DIALOG_CELL: {
DialogCell cell = (DialogCell) holder.itemView;
cell.useSeparator = (position != getItemCount() - 1);
MessageObject messageObject = (MessageObject) getItem(position);
cell.setDialog(messageObject.getDialogId(), messageObject, messageObject.messageOwner.date, false);
break;
}
case 4: {
case VIEW_TYPE_HASHTAG_CELL: {
HashtagSearchCell cell = (HashtagSearchCell) holder.itemView;
cell.setText(searchResultHashtags.get(position - 1));
cell.setNeedDivider(position != searchResultHashtags.size());
break;
}
case 5: {
case VIEW_TYPE_CATEGORY_LIST: {
RecyclerListView recyclerListView = (RecyclerListView) holder.itemView;
((CategoryAdapterRecycler) recyclerListView.getAdapter()).setIndex(position / 2);
break;
}
case 6: {
case VIEW_TYPE_ADD_BY_PHONE: {
String str = (String) getItem(position);
TextCell cell = (TextCell) holder.itemView;
cell.setColors(null, Theme.key_windowBackgroundWhiteBlueText2);
@ -1245,15 +1252,15 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter {
if (isRecentSearchDisplayed()) {
int offset = (!MediaDataController.getInstance(currentAccount).hints.isEmpty() ? 1 : 0);
if (i < offset) {
return 5;
return VIEW_TYPE_CATEGORY_LIST;
}
if (i == offset) {
return 1;
return VIEW_TYPE_GRAY_SECTION;
}
return 0;
return VIEW_TYPE_PROFILE_CELL;
}
if (!searchResultHashtags.isEmpty()) {
return i == 0 ? 1 : 4;
return i == 0 ? VIEW_TYPE_GRAY_SECTION : VIEW_TYPE_HASHTAG_CELL;
}
ArrayList<TLObject> globalSearch = searchAdapterHelper.getGlobalSearch();
int localCount = searchResult.size();
@ -1269,11 +1276,11 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter {
int messagesCount = searchResultMessages.isEmpty() ? 0 : searchResultMessages.size() + 1;
if (i >= 0 && i < localCount) {
return 0;
return VIEW_TYPE_PROFILE_CELL;
} else {
i -= localCount;
if (i >= 0 && i < localServerCount) {
return 0;
return VIEW_TYPE_PROFILE_CELL;
} else {
i -= localServerCount;
if (i >= 0 && i < phoneCount) {
@ -1281,34 +1288,34 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter {
if (object instanceof String) {
String str = (String) object;
if ("section".equals(str)) {
return 1;
return VIEW_TYPE_GRAY_SECTION;
} else {
return 6;
return VIEW_TYPE_ADD_BY_PHONE;
}
}
return 0;
return VIEW_TYPE_PROFILE_CELL;
} else {
i -= phoneCount;
if (i >= 0 && i < globalCount) {
if (i == 0) {
return 1;
return VIEW_TYPE_GRAY_SECTION;
} else {
return 0;
return VIEW_TYPE_PROFILE_CELL;
}
} else {
i -= globalCount;
if (i >= 0 && i < messagesCount) {
if (i == 0) {
return 1;
return VIEW_TYPE_GRAY_SECTION;
} else {
return 2;
return VIEW_TYPE_DIALOG_CELL;
}
}
}
}
}
}
return 3;
return VIEW_TYPE_LOADING;
}
public void setFiltersDelegate(FilteredSearchView.Delegate filtersDelegate, boolean update) {

View File

@ -24,6 +24,8 @@ import org.telegram.messenger.NotificationCenter;
import org.telegram.messenger.R;
import org.telegram.messenger.SharedConfig;
import org.telegram.messenger.UserConfig;
import org.telegram.ui.ActionBar.ActionBarLayout;
import org.telegram.ui.ActionBar.DrawerLayoutContainer;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Cells.DividerCell;
@ -47,15 +49,17 @@ import tw.nekomimi.nekogram.NekoXConfig;
public class DrawerLayoutAdapter extends RecyclerListView.SelectionAdapter implements NotificationCenter.NotificationCenterDelegate {
private Context mContext;
public ArrayList<Item> items = new ArrayList<>(11);
private DrawerLayoutContainer mDrawerLayoutContainer;
private ArrayList<Item> items = new ArrayList<>(11);
private ArrayList<Integer> accountNumbers = new ArrayList<>();
private boolean accountsShown;
private DrawerProfileCell profileCell;
private SideMenultItemAnimator itemAnimator;
private boolean hasGps;
public DrawerLayoutAdapter(Context context, SideMenultItemAnimator animator) {
public DrawerLayoutAdapter(Context context, SideMenultItemAnimator animator, DrawerLayoutContainer drawerLayoutContainer) {
mContext = context;
mDrawerLayoutContainer = drawerLayoutContainer;
itemAnimator = animator;
accountsShown = MessagesController.getGlobalMainSettings().getBoolean("accountsShown", true);
Theme.createCommonDialogResources(context);
@ -137,7 +141,7 @@ public class DrawerLayoutAdapter extends RecyclerListView.SelectionAdapter imple
View view;
switch (viewType) {
case 0:
view = profileCell = new DrawerProfileCell(mContext);
view = profileCell = new DrawerProfileCell(mContext, mDrawerLayoutContainer);
break;
case 2:
view = new DividerCell(mContext);

View File

@ -150,6 +150,7 @@ import org.telegram.ui.Components.EditTextBoldCursor;
import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.LineProgressView;
import org.telegram.ui.Components.LinkPath;
import org.telegram.ui.Components.LinkSpanDrawable;
import org.telegram.ui.Components.MediaActionDrawable;
import org.telegram.ui.Components.RadialProgress2;
import org.telegram.ui.Components.RadioButton;
@ -283,7 +284,8 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
private int pressCount = 0;
private CheckForTap pendingCheckForTap = null;
private TextPaintUrlSpan pressedLink;
private LinkSpanDrawable<TextPaintUrlSpan> pressedLink;
private LinkSpanDrawable.LinkCollector links = new LinkSpanDrawable.LinkCollector();
private BottomSheet linkSheet;
private int pressedLayoutY;
private DrawingText pressedLinkOwnerLayout;
@ -392,6 +394,8 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
}
public class DrawingText implements TextSelectionHelper.TextLayoutBlock {
public View latestParentView;
public StaticLayout textLayout;
public LinkPath textPath;
public LinkPath markPath;
@ -404,7 +408,9 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
public int row;
public CharSequence prefix;
public void draw(Canvas canvas) {
public void draw(Canvas canvas, View view) {
latestParentView = view;
if (!searchResults.isEmpty()) {
SearchResult result = searchResults.get(currentSearchIndex);
if (result.block == parentBlock && (result.text == parentText || result.text instanceof String && parentText == null)) {
@ -433,7 +439,21 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
if (markPath != null) {
canvas.drawPath(markPath, webpageMarkPaint);
}
drawLayoutLink(canvas, this);
if (links.draw(canvas, this)) {
view.invalidate();
}
if (pressedLinkOwnerLayout == this && pressedLink == null && drawBlockSelection) {
float width;
float x;
if (getLineCount() == 1) {
width = getLineWidth(0);
x = getLineLeft(0);
} else {
width = getWidth();
x = 0;
}
canvas.drawRect(-AndroidUtilities.dp(2) + x, 0, x + width + AndroidUtilities.dp(2), getHeight(), urlPaint);
}
textLayout.draw(canvas);
}
@ -1097,9 +1117,9 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
checkingForLongPress = false;
if (pressedLink != null) {
if (!NekoConfig.disableVibration.Bool()) {
windowView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
windowView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
}
showCopyPopup(pressedLink.getUrl());
showCopyPopup(pressedLink.getSpan().getUrl());
pressedLink = null;
pressedLinkOwnerLayout = null;
if (pressedLinkOwnerView != null) {
@ -1112,11 +1132,11 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
textSelectionHelper.trySelect(pressedLinkOwnerView);
}
if (textSelectionHelper.isSelectionMode() && !NekoConfig.disableVibration.Bool()) {
windowView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
windowView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
}
} else if (pressedLinkOwnerLayout != null && pressedLinkOwnerView != null) {
if (!NekoConfig.disableVibration.Bool()) {
windowView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
windowView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);;
}
int[] location = new int[2];
pressedLinkOwnerView.getLocationInWindow(location);
@ -1165,16 +1185,16 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
float lightness = (0.2126f * Color.red(color2) + 0.7152f * Color.green(color2) + 0.0722f * Color.blue(color2)) / 255.0f;
webpageSearchPaint.setColor(lightness <= 0.705f ? 0xffd1982e : 0xffffe669);
webpageUrlPaint.setColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkSelection) & 0x33ffffff);
webpageUrlPaint.setPathEffect(LinkPath.roundedEffect);
webpageUrlPaint.setPathEffect(LinkPath.getRoundedEffect());
urlPaint.setColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkSelection) & 0x33ffffff);
urlPaint.setPathEffect(LinkPath.roundedEffect);
urlPaint.setPathEffect(LinkPath.getRoundedEffect());
tableHalfLinePaint.setColor(Theme.getColor(Theme.key_windowBackgroundWhiteInputField));
tableLinePaint.setColor(Theme.getColor(Theme.key_windowBackgroundWhiteInputField));
photoBackgroundPaint.setColor(0x0f000000);
dividerPaint.setColor(Theme.getColor(Theme.key_divider));
webpageMarkPaint.setColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkSelection) & 0x33ffffff);
webpageMarkPaint.setPathEffect(LinkPath.roundedEffect);
webpageMarkPaint.setPathEffect(LinkPath.getRoundedEffect());
int color = Theme.getColor(Theme.key_switchTrack);
int r = Color.red(color);
@ -1249,6 +1269,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
}
return Unit.INSTANCE;
});
builder.setOnPreDismissListener(di -> links.clear());
BottomSheet sheet = builder.create();
showDialog(sheet);
}
@ -2617,26 +2638,6 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
return drawingText;
}
private void drawLayoutLink(Canvas canvas, DrawingText layout) {
if (canvas == null || layout == null || pressedLinkOwnerLayout != layout) {
return;
}
if (pressedLink != null) {
canvas.drawPath(urlPath, urlPaint);
} else if (drawBlockSelection && layout != null) {
float width;
float x;
if (layout.getLineCount() == 1) {
width = layout.getLineWidth(0);
x = layout.getLineLeft(0);
} else {
width = layout.getWidth();
x = 0;
}
canvas.drawRect(-AndroidUtilities.dp(2) + x, 0, x + width + AndroidUtilities.dp(2), layout.getHeight(), urlPaint);
}
}
private boolean checkLayoutForLinks(WebpageAdapter adapter, MotionEvent event, View parentView, DrawingText drawingText, int layoutX, int layoutY) {
if (pageSwitchAnimation != null || parentView == null || !textSelectionHelper.isSelectable(parentView)) {
return false;
@ -2656,7 +2657,6 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
}
if (x >= layoutX + left && x <= left + layoutX + width && y >= layoutY && y <= layoutY + layout.getHeight()) {
pressedLinkOwnerLayout = drawingText;
pressedLinkOwnerView = parentView;
pressedLayoutY = layoutY;
CharSequence text = layout.getText();
if (text instanceof Spannable) {
@ -2670,25 +2670,32 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
Spannable buffer = (Spannable) layout.getText();
TextPaintUrlSpan[] link = buffer.getSpans(off, off, TextPaintUrlSpan.class);
if (link != null && link.length > 0) {
pressedLink = link[0];
int pressedStart = buffer.getSpanStart(pressedLink);
int pressedEnd = buffer.getSpanEnd(pressedLink);
TextPaintUrlSpan selectedLink = link[0];
int pressedStart = buffer.getSpanStart(selectedLink);
int pressedEnd = buffer.getSpanEnd(selectedLink);
for (int a = 1; a < link.length; a++) {
TextPaintUrlSpan span = link[a];
int start = buffer.getSpanStart(span);
int end = buffer.getSpanEnd(span);
if (pressedStart > start || end > pressedEnd) {
pressedLink = span;
selectedLink = span;
pressedStart = start;
pressedEnd = end;
}
}
if (pressedLink != null) {
links.removeLink(pressedLink);
}
pressedLink = new LinkSpanDrawable<TextPaintUrlSpan>(selectedLink, null, x, y);
pressedLink.setColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkSelection) & 0x33ffffff);
links.addLink(pressedLink, pressedLinkOwnerLayout);
try {
urlPath.setUseRoundRect(true);
urlPath.setCurrentLayout(layout, pressedStart, 0);
int shift = pressedLink.getTextPaint() != null ? pressedLink.getTextPaint().baselineShift : 0;
urlPath.setBaselineShift(shift != 0 ? shift + AndroidUtilities.dp(shift > 0 ? 5 : -2) : 0);
layout.getSelectionPath(pressedStart, pressedEnd, urlPath);
LinkPath path = pressedLink.obtainNewPath();
path.setCurrentLayout(layout, pressedStart, 0);
TextPaint textPaint = selectedLink.getTextPaint();
int shift = textPaint != null ? textPaint.baselineShift : 0;
path.setBaselineShift(shift != 0 ? shift + AndroidUtilities.dp(shift > 0 ? 5 : -2) : 0);
layout.getSelectionPath(pressedStart, pressedEnd, path);
parentView.invalidate();
} catch (Exception e) {
FileLog.e(e);
@ -2703,7 +2710,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
} else if (event.getAction() == MotionEvent.ACTION_UP) {
if (pressedLink != null) {
removeLink = true;
String url = pressedLink.getUrl();
String url = pressedLink.getSpan().getUrl();
if (url != null) {
if (linkSheet != null) {
linkSheet.dismiss();
@ -2737,7 +2744,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
anchor = null;
}
if (!isAnchor) {
openWebpageUrl(pressedLink.getUrl(), anchor);
openWebpageUrl(pressedLink.getSpan().getUrl(), anchor);
}
}
}
@ -2766,6 +2773,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
return;
}
View parentView = pressedLinkOwnerView;
links.clear();
pressedLink = null;
pressedLinkOwnerLayout = null;
pressedLinkOwnerView = null;
@ -6243,14 +6251,14 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this, count++);
captionLayout.draw(canvas);
captionLayout.draw(canvas, this);
canvas.restore();
}
if (creditLayout != null) {
canvas.save();
canvas.translate(textX, textY + creditOffset);
drawTextSelection(canvas, this, count);
creditLayout.draw(canvas);
creditLayout.draw(canvas, this);
canvas.restore();
}
if (currentBlock.level > 0) {
@ -6609,7 +6617,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
titleLayout.y = seekBarY - AndroidUtilities.dp(16);
canvas.translate(titleLayout.x, titleLayout.y);
drawTextSelection(canvas, this, count++);
titleLayout.draw(canvas);
titleLayout.draw(canvas, this);
canvas.restore();
}
if (captionLayout != null) {
@ -6618,7 +6626,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
captionLayout.y = textY;
canvas.translate(textX, textY);
drawTextSelection(canvas, this, count++);
captionLayout.draw(canvas);
captionLayout.draw(canvas, this);
canvas.restore();
}
if (creditLayout != null) {
@ -6627,7 +6635,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
creditLayout.y = textY + creditOffset;
canvas.translate(textX, textY + creditOffset);
drawTextSelection(canvas, this, count);
creditLayout.draw(canvas);
creditLayout.draw(canvas, this);
canvas.restore();
}
if (currentBlock.level > 0) {
@ -6940,14 +6948,14 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(AndroidUtilities.dp(18 + 14 + (avatarVisible ? 40 + 14 : 0)), AndroidUtilities.dp(dateLayout != null ? 10 : 19));
drawTextSelection(canvas, this, count++);
nameLayout.draw(canvas);
nameLayout.draw(canvas, this);
canvas.restore();
}
if (dateLayout != null) {
canvas.save();
canvas.translate(AndroidUtilities.dp(18 + 14 + (avatarVisible ? 40 + 14 : 0)), AndroidUtilities.dp(29));
drawTextSelection(canvas, this, count++);
dateLayout.draw(canvas);
dateLayout.draw(canvas, this);
canvas.restore();
}
canvas.drawRect(AndroidUtilities.dp(18), AndroidUtilities.dp(6), AndroidUtilities.dp(20), lineHeight - (currentBlock.level != 0 ? 0 : AndroidUtilities.dp(6)), quoteLinePaint);
@ -6956,14 +6964,14 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this, count++);
captionLayout.draw(canvas);
captionLayout.draw(canvas, this);
canvas.restore();
}
if (creditLayout != null) {
canvas.save();
canvas.translate(textX, textY + creditOffset);
drawTextSelection(canvas, this, count);
creditLayout.draw(canvas);
creditLayout.draw(canvas, this);
canvas.restore();
}
}
@ -7052,7 +7060,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this);
textLayout.draw(canvas);
textLayout.draw(canvas, this);
canvas.restore();
}
if (currentBlock.level > 0) {
@ -7183,7 +7191,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
}
@Override
public TextureView onSwitchInlineMode(View controlsView, boolean inline, float aspectRatio, int rotation, boolean animated) {
public TextureView onSwitchInlineMode(View controlsView, boolean inline, int videoWidth, int videoHeight, int rotation, boolean animated) {
return null;
}
@ -7484,14 +7492,14 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this, count++);
captionLayout.draw(canvas);
captionLayout.draw(canvas, this);
canvas.restore();
}
if (creditLayout != null) {
canvas.save();
canvas.translate(textX, textY + creditOffset);
drawTextSelection(canvas, this, count);
creditLayout.draw(canvas);
creditLayout.draw(canvas, this);
canvas.restore();
}
if (currentBlock.level > 0) {
@ -7769,7 +7777,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this, 0);
titleLayout.draw(canvas);
titleLayout.draw(canvas, this);
canvas.restore();
}
if (currentBlock.level > 0) {
@ -8373,14 +8381,14 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this, count++);
captionLayout.draw(canvas);
captionLayout.draw(canvas, this);
canvas.restore();
}
if (creditLayout != null) {
canvas.save();
canvas.translate(textX, textY + creditOffset);
drawTextSelection(canvas, this, count);
creditLayout.draw(canvas);
creditLayout.draw(canvas, this);
canvas.restore();
}
if (currentBlock.level > 0) {
@ -8633,14 +8641,14 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this, count++);
captionLayout.draw(canvas);
captionLayout.draw(canvas, this);
canvas.restore();
}
if (creditLayout != null) {
canvas.save();
canvas.translate(textX, textY + creditOffset);
drawTextSelection(canvas, this, count);
creditLayout.draw(canvas);
creditLayout.draw(canvas, this);
canvas.restore();
}
}
@ -8857,14 +8865,14 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
} else {
canvas.translate(AndroidUtilities.dp(15) + currentBlock.parent.maxNumWidth - (int) Math.ceil(currentBlock.numLayout.getLineWidth(0)) + currentBlock.parent.level * AndroidUtilities.dp(12), textY + numOffsetY - (drawDot ? AndroidUtilities.dp(1) : 0));
}
currentBlock.numLayout.draw(canvas);
currentBlock.numLayout.draw(canvas, this);
canvas.restore();
}
if (textLayout != null) {
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this);
textLayout.draw(canvas);
textLayout.draw(canvas, this);
canvas.restore();
}
}
@ -9093,14 +9101,14 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
} else {
canvas.translate(AndroidUtilities.dp(18) + currentBlock.parent.maxNumWidth - (int) Math.ceil(currentBlock.numLayout.getLineWidth(0)) + currentBlock.parent.level * AndroidUtilities.dp(20), textY + numOffsetY);
}
currentBlock.numLayout.draw(canvas);
currentBlock.numLayout.draw(canvas, this);
canvas.restore();
}
if (textLayout != null) {
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this);
textLayout.draw(canvas);
textLayout.draw(canvas, this);
canvas.restore();
}
}
@ -9209,7 +9217,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this);
textLayout.draw(canvas);
textLayout.draw(canvas, this);
canvas.restore();
}
@ -9316,7 +9324,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this);
textLayout.draw(canvas);
textLayout.draw(canvas, this);
canvas.restore();
}
}
@ -9452,12 +9460,12 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.translate(textX, AndroidUtilities.dp(10));
if (textLayout != null) {
drawTextSelection(canvas, this, count++);
textLayout.draw(canvas);
textLayout.draw(canvas, this);
}
if (textLayout2 != null) {
canvas.translate(0, textOffset);
drawTextSelection(canvas, this, count);
textLayout2.draw(canvas);
textLayout2.draw(canvas, this);
}
canvas.restore();
if (divider) {
@ -9530,7 +9538,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this);
textLayout.draw(canvas);
textLayout.draw(canvas, this);
canvas.restore();
}
}
@ -9627,7 +9635,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this);
textLayout.draw(canvas);
textLayout.draw(canvas, this);
canvas.restore();
}
}
@ -9716,14 +9724,14 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this, count++);
textLayout.draw(canvas);
textLayout.draw(canvas, this);
canvas.restore();
}
if (textLayout2 != null) {
canvas.save();
canvas.translate(textX, textY2);
drawTextSelection(canvas, this, count);
textLayout2.draw(canvas);
textLayout2.draw(canvas, this);
canvas.restore();
}
}
@ -9827,14 +9835,14 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this, counter++);
textLayout.draw(canvas);
textLayout.draw(canvas, this);
canvas.restore();
}
if (textLayout2 != null) {
canvas.save();
canvas.translate(textX, textY2);
drawTextSelection(canvas, this, counter);
textLayout2.draw(canvas);
textLayout2.draw(canvas, this);
canvas.restore();
}
if (parentAdapter.isRtl) {
@ -10128,14 +10136,14 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this, count++);
captionLayout.draw(canvas);
captionLayout.draw(canvas, this);
canvas.restore();
}
if (creditLayout != null) {
canvas.save();
canvas.translate(textX, textY + creditOffset);
drawTextSelection(canvas, this, count);
creditLayout.draw(canvas);
creditLayout.draw(canvas, this);
canvas.restore();
}
if (currentBlock.level > 0) {
@ -10429,14 +10437,14 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this, count++);
captionLayout.draw(canvas);
captionLayout.draw(canvas, this);
canvas.restore();
}
if (creditLayout != null) {
canvas.save();
canvas.translate(textX, textY + creditOffset);
drawTextSelection(canvas, this, count);
creditLayout.draw(canvas);
creditLayout.draw(canvas, this);
canvas.restore();
}
if (currentBlock.level > 0) {
@ -10640,7 +10648,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
if (currentType == 0) {
drawTextSelection(canvas, this);
}
textLayout.draw(canvas);
textLayout.draw(canvas, this);
canvas.restore();
}
}
@ -10748,7 +10756,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this);
textLayout.draw(canvas);
textLayout.draw(canvas, this);
canvas.restore();
}
}
@ -10830,7 +10838,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this);
textLayout.draw(canvas);
textLayout.draw(canvas, this);
canvas.restore();
}
}
@ -10912,7 +10920,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this);
textLayout.draw(canvas);
textLayout.draw(canvas, this);
canvas.restore();
}
}
@ -10991,7 +10999,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this);
textLayout.draw(canvas);
textLayout.draw(canvas, this);
canvas.restore();
}
if (currentBlock.level > 0) {
@ -11072,7 +11080,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
if (textLayout != null) {
canvas.save();
drawTextSelection(canvas, BlockPreformattedCell.this);
textLayout.draw(canvas);
textLayout.draw(canvas, this);
canvas.restore();
textLayout.x = (int) getX();
textLayout.y = (int) getY();
@ -11184,7 +11192,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
canvas.save();
canvas.translate(textX, textY);
drawTextSelection(canvas, this);
textLayout.draw(canvas);
textLayout.draw(canvas, this);
canvas.restore();
}
}

View File

@ -144,10 +144,12 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe
return;
}
photoSize = getDirectorySize(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_IMAGE), 0);
photoSize += getDirectorySize(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_IMAGE_PUBLIC), 0);
if (canceled) {
return;
}
videoSize = getDirectorySize(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_VIDEO), 0);
videoSize += getDirectorySize(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_VIDEO_PUBLIC), 0);
if (canceled) {
return;
}
@ -333,6 +335,19 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe
if (file != null) {
Utilities.clearDir(file.getAbsolutePath(), documentsMusicType, Long.MAX_VALUE, true);
}
if (type == FileLoader.MEDIA_DIR_IMAGE || type == FileLoader.MEDIA_DIR_VIDEO) {
int publicDirectoryType;
if (type == FileLoader.MEDIA_DIR_IMAGE) {
publicDirectoryType = FileLoader.MEDIA_DIR_IMAGE_PUBLIC;
} else {
publicDirectoryType = FileLoader.MEDIA_DIR_VIDEO_PUBLIC;
}
file = FileLoader.checkDirectory(publicDirectoryType);
if (file != null) {
Utilities.clearDir(file.getAbsolutePath(), documentsMusicType, Long.MAX_VALUE, false);
}
}
if (type == FileLoader.MEDIA_DIR_CACHE) {
cacheSize = getDirectorySize(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_CACHE), documentsMusicType);
imagesCleared = true;
@ -357,8 +372,10 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe
} else if (type == FileLoader.MEDIA_DIR_IMAGE) {
imagesCleared = true;
photoSize = getDirectorySize(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_IMAGE), documentsMusicType);
photoSize += getDirectorySize(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_IMAGE_PUBLIC), documentsMusicType);
} else if (type == FileLoader.MEDIA_DIR_VIDEO) {
videoSize = getDirectorySize(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_VIDEO), documentsMusicType);
videoSize += getDirectorySize(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_VIDEO_PUBLIC), documentsMusicType);
} else if (type == 100) {
imagesCleared = true;
stickersSize = getDirectorySize(new File(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_CACHE), "acache"), documentsMusicType);
@ -407,8 +424,11 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe
FileLog.e(e);
}
getMediaDataController().ringtoneDataStore.checkRingtoneSoundsLoaded();
cacheRemovedTooltip.setInfoText(LocaleController.formatString("CacheWasCleared", R.string.CacheWasCleared, AndroidUtilities.formatFileSize(finalClearedSize)));
cacheRemovedTooltip.showWithAction(0, UndoView.ACTION_CACHE_WAS_CLEARED, null, null);
getMediaDataController().loadAttachMenuBots(false, true);
});
});
}

View File

@ -308,7 +308,7 @@ public class CalendarActivity extends BaseFragment {
selectDaysHint.showForView(bottomBar, true);
return;
}
AlertsCreator.createClearDaysDialogAlert(this, lastDaysSelected, getMessagesController().getUser(dialogId), new MessagesStorage.BooleanCallback() {
AlertsCreator.createClearDaysDialogAlert(this, lastDaysSelected, getMessagesController().getUser(dialogId), null, false, new MessagesStorage.BooleanCallback() {
@Override
public void run(boolean forAll) {
finishFragment();
@ -743,7 +743,7 @@ public class CalendarActivity extends BaseFragment {
if (parentLayout.fragmentsStack.size() >= 3) {
BaseFragment fragment = parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 3);
if (fragment instanceof ChatActivity) {
AlertsCreator.createClearDaysDialogAlert(CalendarActivity.this, 1, getMessagesController().getUser(dialogId), new MessagesStorage.BooleanCallback() {
AlertsCreator.createClearDaysDialogAlert(CalendarActivity.this, 1, getMessagesController().getUser(dialogId), null, false, new MessagesStorage.BooleanCallback() {
@Override
public void run(boolean forAll) {
finishFragment();

View File

@ -27,14 +27,18 @@ import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.ClickableSpan;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
@ -77,6 +81,7 @@ import org.telegram.ui.Components.AnimationProperties;
import org.telegram.ui.Components.CubicBezierInterpolator;
import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.LinkPath;
import org.telegram.ui.Components.LinkSpanDrawable;
import org.telegram.ui.Components.TypefaceSpan;
import org.telegram.ui.Components.URLSpanNoUnderline;
@ -203,6 +208,7 @@ public class CameraScanActivity extends BaseFragment {
actionBarLayout[0] = null;
}
};
bottomSheet.setUseLightStatusBar(false);
AndroidUtilities.setLightNavigationBar(bottomSheet.getWindow(), false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
bottomSheet.getWindow().setNavigationBarColor(0xff000000);
@ -425,7 +431,7 @@ public class CameraScanActivity extends BaseFragment {
fragmentView = viewGroup;
if (currentType == TYPE_QR || currentType == TYPE_QR_LOGIN) {
fragmentView.postDelayed(this::initCameraView, 200);
fragmentView.postDelayed(this::initCameraView, 350);
} else {
initCameraView();
}
@ -436,6 +442,7 @@ public class CameraScanActivity extends BaseFragment {
} else {
actionBar.setBackgroundDrawable(null);
actionBar.setAddToContainer(false);
actionBar.setTitleColor(0xffffffff);
actionBar.setItemsColor(0xffffffff, false);
actionBar.setItemsBackgroundColor(0x22ffffff, false);
viewGroup.setBackgroundColor(Theme.getColor(Theme.key_wallet_blackBackground));
@ -447,10 +454,12 @@ public class CameraScanActivity extends BaseFragment {
}
Paint selectionPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
selectionPaint.setPathEffect(LinkPath.roundedEffect);
selectionPaint.setColor(ColorUtils.setAlphaComponent(Color.WHITE, 50));
selectionPaint.setPathEffect(LinkPath.getRoundedEffect());
selectionPaint.setColor(ColorUtils.setAlphaComponent(Color.WHITE, 40));
titleTextView = new TextView(context) {
LinkPath textPath;
private LinkSpanDrawable<URLSpanNoUnderline> pressedLink;
LinkSpanDrawable.LinkCollector links = new LinkSpanDrawable.LinkCollector(this);
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
@ -474,11 +483,56 @@ public class CameraScanActivity extends BaseFragment {
}
}
@Override
public boolean onTouchEvent(MotionEvent e) {
final Layout textLayout = getLayout();
int textX = 0, textY = 0;
int x = (int) (e.getX() - textX);
int y = (int) (e.getY() - textY);
if (e.getAction() == MotionEvent.ACTION_DOWN || e.getAction() == MotionEvent.ACTION_UP) {
final int line = textLayout.getLineForVertical(y);
final int off = textLayout.getOffsetForHorizontal(line, x);
final float left = textLayout.getLineLeft(line);
if (left <= x && left + textLayout.getLineWidth(line) >= x && y >= 0 && y <= textLayout.getHeight()) {
Spannable buffer = (Spannable) textLayout.getText();
ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
if (link.length != 0) {
links.clear();
if (e.getAction() == MotionEvent.ACTION_DOWN) {
pressedLink = new LinkSpanDrawable(link[0], null, e.getX(), e.getY());
pressedLink.setColor(0x2dffffff);
links.addLink(pressedLink);
int start = buffer.getSpanStart(pressedLink.getSpan());
int end = buffer.getSpanEnd(pressedLink.getSpan());
LinkPath path = pressedLink.obtainNewPath();
path.setCurrentLayout(textLayout, start, textY);
textLayout.getSelectionPath(start, end, path);
} else if (e.getAction() == MotionEvent.ACTION_UP) {
if (pressedLink != null && pressedLink.getSpan() == link[0]) {
link[0].onClick(this);
}
pressedLink = null;
}
return true;
}
}
}
if (e.getAction() == MotionEvent.ACTION_UP || e.getAction() == MotionEvent.ACTION_CANCEL) {
links.clear();
pressedLink = null;
}
return super.onTouchEvent(e);
}
@Override
protected void onDraw(Canvas canvas) {
if (textPath != null) {
canvas.drawPath(textPath, selectionPaint);
}
if (links.draw(canvas)) {
invalidate();
}
super.onDraw(canvas);
}
};
@ -514,8 +568,8 @@ public class CameraScanActivity extends BaseFragment {
SpannableStringBuilder spanned = new SpannableStringBuilder(text);
String[] links = new String[] {
LocaleController.getString("AuthAnotherWebClientUrl", R.string.AuthAnotherWebClientUrl),
LocaleController.getString("AuthAnotherClientDownloadClientUrl", R.string.AuthAnotherClientDownloadClientUrl)
LocaleController.getString("AuthAnotherClientDownloadClientUrl", R.string.AuthAnotherClientDownloadClientUrl),
LocaleController.getString("AuthAnotherWebClientUrl", R.string.AuthAnotherWebClientUrl)
};
for (int i = 0; i < links.length; ++i) {
text = spanned.toString();
@ -528,15 +582,14 @@ public class CameraScanActivity extends BaseFragment {
spanned.replace(index1, index1 + 1, " ");
index1 += 1;
index2 += 1;
spanned.setSpan(new URLSpanNoUnderline(links[i]), index1, index2 - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spanned.setSpan(new URLSpanNoUnderline(links[i], true), index1, index2 - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spanned.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf")), index1, index2 - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} else {
break;
}
}
titleTextView.setLinkTextColor(Color.WHITE);
titleTextView.setHighlightColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkSelection));
titleTextView.setLinkTextColor(0xffffffff);
titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
titleTextView.setLineSpacing(AndroidUtilities.dp(2), 1.0f);
@ -778,9 +831,9 @@ public class CameraScanActivity extends BaseFragment {
if (normalBounds == null) {
normalBounds = new RectF();
}
int width = AndroidUtilities.displaySize.x,
height = AndroidUtilities.displaySize.y,
side = (int) (Math.min(width, height) / 1.5f);
int width = Math.max(AndroidUtilities.displaySize.x, fragmentView.getWidth()),
height = Math.max(AndroidUtilities.displaySize.y, fragmentView.getHeight()),
side = (int) (Math.min(width, height) / 1.5f);
normalBounds.set(
(width - side) / 2f / (float) width,
(height - side) / 2f / (float) height,

View File

@ -29,6 +29,7 @@ import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
@ -55,6 +56,7 @@ import org.telegram.ui.Components.EmojiTextView;
import org.telegram.ui.Components.BulletinFactory;
import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.LinkPath;
import org.telegram.ui.Components.LinkSpanDrawable;
import org.telegram.ui.Components.StaticLayoutEx;
import org.telegram.ui.Components.URLSpanNoUnderline;
@ -73,7 +75,8 @@ public class AboutLinkCell extends FrameLayout {
private FrameLayout bottomShadow;
private Drawable showMoreBackgroundDrawable;
private ClickableSpan pressedLink;
private LinkSpanDrawable pressedLink;
private LinkSpanDrawable.LinkCollector links;
private Point urlPathOffset = new Point();
private LinkPath urlPath = new LinkPath(true);
@ -123,7 +126,7 @@ public class AboutLinkCell extends FrameLayout {
}
} else if (pressedLink != null) {
try {
onLinkClick(pressedLink);
onLinkClick((ClickableSpan) pressedLink.getSpan());
} catch (Exception e) {
FileLog.e(e);
}
@ -137,6 +140,7 @@ public class AboutLinkCell extends FrameLayout {
return result || super.onTouchEvent(event);
}
};
links = new LinkSpanDrawable.LinkCollector(container);
container.setClickable(true);
rippleBackground = Theme.createRadSelectorDrawable(Theme.getColor(Theme.key_listSelector), 0, 0);
@ -258,15 +262,12 @@ public class AboutLinkCell extends FrameLayout {
final float SPACE = AndroidUtilities.dp(3f);
private void drawText(Canvas canvas) {
canvas.save();
AndroidUtilities.rectTmp.set(AndroidUtilities.dp(23 - 8), AndroidUtilities.dp(8), getWidth() - AndroidUtilities.dp(23), getHeight());
canvas.clipRect(AndroidUtilities.rectTmp);
if (pressedLink != null) {
canvas.save();
canvas.translate(urlPathOffset.x, urlPathOffset.y);
canvas.drawPath(urlPath, Theme.linkSelectionPaint);
canvas.restore();
canvas.clipRect(AndroidUtilities.dp(23 - 8), AndroidUtilities.dp(8), getWidth() - AndroidUtilities.dp(23), getHeight());
canvas.translate(textX = AndroidUtilities.dp(23), 0);
if (links != null && links.draw(canvas)) {
invalidate();
}
canvas.translate(textX = AndroidUtilities.dp(23), textY = AndroidUtilities.dp(8));
canvas.translate(0, textY = AndroidUtilities.dp(8));
try {
if (firstThreeLinesLayout == null || !shouldExpand) {
@ -325,10 +326,8 @@ public class AboutLinkCell extends FrameLayout {
protected void didExtend() {}
private void resetPressedLink() {
if (pressedLink != null) {
pressedLink = null;
container.invalidate();
}
links.clear();
pressedLink = null;
AndroidUtilities.cancelRunOnUIThread(longPressedRunnable);
invalidate();
}
@ -372,15 +371,19 @@ public class AboutLinkCell extends FrameLayout {
public void run() {
if (pressedLink != null) {
String url;
if (pressedLink instanceof URLSpanNoUnderline) {
url = ((URLSpanNoUnderline) pressedLink).getURL();
} else if (pressedLink instanceof URLSpan) {
url = ((URLSpan) pressedLink).getURL();
if (pressedLink.getSpan() instanceof URLSpanNoUnderline) {
url = ((URLSpanNoUnderline) pressedLink.getSpan()).getURL();
} else if (pressedLink.getSpan() instanceof URLSpan) {
url = ((URLSpan) pressedLink.getSpan()).getURL();
} else {
url = pressedLink.toString();
url = pressedLink.getSpan().toString();
}
ClickableSpan pressedLinkFinal = pressedLink;
try {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
} catch (Exception ignore) {}
ClickableSpan pressedLinkFinal = (ClickableSpan) pressedLink.getSpan();
BottomSheet.Builder builder = new BottomSheet.Builder(parentFragment.getParentActivity());
builder.setTitle(url);
builder.setItems(new CharSequence[]{LocaleController.getString("Open", R.string.Open), LocaleController.getString("Copy", R.string.Copy)}, (dialog, which) -> {
@ -399,8 +402,10 @@ public class AboutLinkCell extends FrameLayout {
}
}
});
builder.setOnPreDismissListener(di -> resetPressedLink());
builder.show();
resetPressedLink();
pressedLink = null;
}
}
};
@ -418,16 +423,13 @@ public class AboutLinkCell extends FrameLayout {
ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
if (link.length != 0) {
resetPressedLink();
pressedLink = link[0];
try {
int start = buffer.getSpanStart(pressedLink);
urlPathOffset.set(textX, textY);
urlPath.setCurrentLayout(textLayout, start, 0);
textLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink), urlPath);
} catch (Exception e) {
FileLog.e(e);
}
container.invalidate();
pressedLink = new LinkSpanDrawable(link[0], parentFragment.getResourceProvider(), ex, ey);
links.addLink(pressedLink);
int start = buffer.getSpanStart(pressedLink.getSpan());
int end = buffer.getSpanEnd(pressedLink.getSpan());
LinkPath path = pressedLink.obtainNewPath();
path.setCurrentLayout(textLayout, start, textY);
textLayout.getSelectionPath(start, end, path);
AndroidUtilities.runOnUIThread(longPressedRunnable, ViewConfiguration.getLongPressTimeout());
return true;
} else {
@ -600,7 +602,6 @@ public class AboutLinkCell extends FrameLayout {
int height = updateHeight();
super.onMeasure(
widthMeasureSpec,
// heightMeasureSpec
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
);
}

View File

@ -73,7 +73,8 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD
default void didClickImage(ChatActionCell cell) {
}
default void didLongPress(ChatActionCell cell, float x, float y) {
default boolean didLongPress(ChatActionCell cell, float x, float y) {
return false;
}
default void needOpenUserProfile(long uid) {
@ -283,9 +284,9 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD
@Override
protected boolean onLongPress() {
if (delegate != null) {
delegate.didLongPress(this, lastTouchX, lastTouchY);
return delegate.didLongPress(this, lastTouchX, lastTouchY);
}
return true;
return false;
}
@Override

View File

@ -71,6 +71,7 @@ import android.widget.Toast;
import androidx.core.graphics.ColorUtils;
import org.telegram.PhoneFormat.PhoneFormat;
import org.telegram.messenger.AccountInstance;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ChatObject;
import org.telegram.messenger.ContactsController;
@ -115,6 +116,7 @@ import org.telegram.ui.Components.EmptyStubSpan;
import org.telegram.ui.Components.FloatSeekBarAccessibilityDelegate;
import org.telegram.ui.Components.InfiniteProgress;
import org.telegram.ui.Components.LinkPath;
import org.telegram.ui.Components.LinkSpanDrawable;
import org.telegram.ui.Components.MediaActionDrawable;
import org.telegram.ui.Components.MessageBackgroundDrawable;
import org.telegram.ui.Components.MotionBackgroundDrawable;
@ -288,6 +290,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
default void didPressHiddenForward(ChatMessageCell cell) {
}
default void didPressViaBotNotInline(ChatMessageCell cell, long botId) {
}
default void didPressViaBot(ChatMessageCell cell, String username) {
}
@ -429,6 +434,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
private int angle;
private float progressAlpha;
private long lastUpdateTime;
private boolean isInviteButton;
}
public static class PollButton {
@ -639,12 +645,12 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
private boolean mediaWasInvisible;
private boolean timeWasInvisible;
private CharacterStyle pressedLink;
private LinkSpanDrawable pressedLink;
private LinkSpanDrawable.LinkCollector links = new LinkSpanDrawable.LinkCollector(this);
private int pressedLinkType;
private boolean linkPreviewPressed;
private boolean gamePreviewPressed;
private ArrayList<LinkPath> urlPathCache = new ArrayList<>();
private ArrayList<LinkPath> urlPath = new ArrayList<>();
private ArrayList<LinkPath> urlPathSelection = new ArrayList<>();
private Path rectPath = new Path();
@ -1095,47 +1101,38 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
}
private void resetPressedLink(int type) {
public void resetPressedLink(int type) {
if (type != -1) {
links.removeLinks(type);
} else {
links.clear();
}
if (pressedLink == null || pressedLinkType != type && type != -1) {
return;
}
resetUrlPaths(false);
pressedLink = null;
pressedLinkType = -1;
invalidate();
}
private void resetUrlPaths(boolean text) {
if (text) {
if (urlPathSelection.isEmpty()) {
return;
}
urlPathCache.addAll(urlPathSelection);
urlPathSelection.clear();
} else {
if (urlPath.isEmpty()) {
return;
}
urlPathCache.addAll(urlPath);
urlPath.clear();
private void resetUrlPaths() {
if (urlPathSelection.isEmpty()) {
return;
}
urlPathCache.addAll(urlPathSelection);
urlPathSelection.clear();
}
private LinkPath obtainNewUrlPath(boolean text) {
private LinkPath obtainNewUrlPath() {
LinkPath linkPath;
if (!urlPathCache.isEmpty()) {
linkPath = urlPathCache.get(0);
urlPathCache.remove(0);
} else {
linkPath = new LinkPath();
linkPath.setUseRoundRect(true);
linkPath = new LinkPath(true);
}
linkPath.reset();
if (text) {
urlPathSelection.add(linkPath);
} else {
urlPath.add(linkPath);
}
urlPathSelection.add(linkPath);
return linkPath;
}
@ -1197,70 +1194,74 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
if (!ignore) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
pressedLink = link[0];
linkBlockNum = blockNum;
pressedLinkType = 1;
resetUrlPaths(false);
try {
LinkPath path = obtainNewUrlPath(false);
int[] pos = getRealSpanStartAndEnd(buffer, pressedLink);
pos[0] -= block.charactersOffset;
pos[1] -= block.charactersOffset;
path.setCurrentLayout(block.textLayout, pos[0], 0);
block.textLayout.getSelectionPath(pos[0], pos[1], path);
if (pos[1] >= block.charactersEnd) {
for (int a = blockNum + 1; a < currentMessageObject.textLayoutBlocks.size(); a++) {
MessageObject.TextLayoutBlock nextBlock = currentMessageObject.textLayoutBlocks.get(a);
CharacterStyle[] nextLink;
if (isMono) {
nextLink = buffer.getSpans(nextBlock.charactersOffset, nextBlock.charactersOffset, URLSpanMono.class);
} else {
nextLink = buffer.getSpans(nextBlock.charactersOffset, nextBlock.charactersOffset, ClickableSpan.class);
}
if (nextLink == null || nextLink.length == 0 || nextLink[0] != pressedLink) {
break;
}
path = obtainNewUrlPath(false);
path.setCurrentLayout(nextBlock.textLayout, 0, nextBlock.textYOffset - block.textYOffset);
int p1 = pos[1] + block.charactersOffset - nextBlock.charactersOffset;
nextBlock.textLayout.getSelectionPath(0, p1, path);
if (p1 < nextBlock.charactersEnd - 1) {
break;
if (pressedLink == null || pressedLink.getSpan() != link[0]) {
links.removeLink(pressedLink);
pressedLink = new LinkSpanDrawable(link[0], resourcesProvider, x, y, spanSupportsLongPress(link[0]));
pressedLink.setColor(getThemedColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outLinkSelectBackground : Theme.key_chat_linkSelectBackground));
linkBlockNum = blockNum;
pressedLinkType = 1;
try {
LinkPath path = pressedLink.obtainNewPath();
int[] pos = getRealSpanStartAndEnd(buffer, pressedLink.getSpan());
pos[0] -= block.charactersOffset;
pos[1] -= block.charactersOffset;
path.setCurrentLayout(block.textLayout, pos[0], 0);
block.textLayout.getSelectionPath(pos[0], pos[1], path);
if (pos[1] >= block.charactersEnd) {
for (int a = blockNum + 1; a < currentMessageObject.textLayoutBlocks.size(); a++) {
MessageObject.TextLayoutBlock nextBlock = currentMessageObject.textLayoutBlocks.get(a);
CharacterStyle[] nextLink;
if (isMono) {
nextLink = buffer.getSpans(nextBlock.charactersOffset, nextBlock.charactersOffset, URLSpanMono.class);
} else {
nextLink = buffer.getSpans(nextBlock.charactersOffset, nextBlock.charactersOffset, ClickableSpan.class);
}
if (nextLink == null || nextLink.length == 0 || nextLink[0] != pressedLink.getSpan()) {
break;
}
path = pressedLink.obtainNewPath();
path.setCurrentLayout(nextBlock.textLayout, 0, nextBlock.textYOffset - block.textYOffset);
int p1 = pos[1] + block.charactersOffset - nextBlock.charactersOffset;
nextBlock.textLayout.getSelectionPath(0, p1, path);
if (p1 < nextBlock.charactersEnd - 1) {
break;
}
}
}
}
if (pos[0] <= block.charactersOffset) {
int offsetY = 0;
for (int a = blockNum - 1; a >= 0; a--) {
MessageObject.TextLayoutBlock nextBlock = currentMessageObject.textLayoutBlocks.get(a);
CharacterStyle[] nextLink;
if (isMono) {
nextLink = buffer.getSpans(nextBlock.charactersEnd - 1, nextBlock.charactersEnd - 1, URLSpanMono.class);
} else {
nextLink = buffer.getSpans(nextBlock.charactersEnd - 1, nextBlock.charactersEnd - 1, ClickableSpan.class);
}
if (nextLink == null || nextLink.length == 0 || nextLink[0] != pressedLink) {
break;
}
path = obtainNewUrlPath(false);
offsetY -= nextBlock.height;
int p0 = pos[0] + block.charactersOffset - nextBlock.charactersOffset;
int p1 = pos[1] + block.charactersOffset - nextBlock.charactersOffset;
path.setCurrentLayout(nextBlock.textLayout, p0, offsetY);
nextBlock.textLayout.getSelectionPath(p0, p1, path);
if (p0 > nextBlock.charactersOffset) {
break;
if (pos[0] <= block.charactersOffset) {
int offsetY = 0;
for (int a = blockNum - 1; a >= 0; a--) {
MessageObject.TextLayoutBlock nextBlock = currentMessageObject.textLayoutBlocks.get(a);
CharacterStyle[] nextLink;
if (isMono) {
nextLink = buffer.getSpans(nextBlock.charactersEnd - 1, nextBlock.charactersEnd - 1, URLSpanMono.class);
} else {
nextLink = buffer.getSpans(nextBlock.charactersEnd - 1, nextBlock.charactersEnd - 1, ClickableSpan.class);
}
if (nextLink == null || nextLink.length == 0 || nextLink[0] != pressedLink.getSpan()) {
break;
}
path = pressedLink.obtainNewPath();
offsetY -= nextBlock.height;
int p0 = pos[0] + block.charactersOffset - nextBlock.charactersOffset;
int p1 = pos[1] + block.charactersOffset - nextBlock.charactersOffset;
path.setCurrentLayout(nextBlock.textLayout, p0, offsetY);
nextBlock.textLayout.getSelectionPath(p0, p1, path);
if (p0 > nextBlock.charactersOffset) {
break;
}
}
}
} catch (Exception e) {
FileLog.e(e);
}
} catch (Exception e) {
FileLog.e(e);
links.addLink(pressedLink, 1);
}
invalidate();
return true;
} else {
if (link[0] == pressedLink) {
delegate.didPressUrl(this, pressedLink, false);
if (link[0] == pressedLink.getSpan()) {
delegate.didPressUrl(this, pressedLink.getSpan(), false);
resetPressedLink(1);
return true;
}
@ -1304,16 +1305,20 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
ignore = true;
}
if (!ignore) {
pressedLink = link[0];
pressedLinkType = 3;
resetUrlPaths(false);
try {
LinkPath path = obtainNewUrlPath(false);
int[] pos = getRealSpanStartAndEnd(buffer, pressedLink);
path.setCurrentLayout(captionLayout, pos[0], 0);
captionLayout.getSelectionPath(pos[0], pos[1], path);
} catch (Exception e) {
FileLog.e(e);
if (pressedLink == null || pressedLink.getSpan() != link[0]) {
links.removeLink(pressedLink);
pressedLink = new LinkSpanDrawable(link[0], resourcesProvider, x, y, spanSupportsLongPress(link[0]));
pressedLink.setColor(getThemedColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outLinkSelectBackground : Theme.key_chat_linkSelectBackground));
pressedLinkType = 3;
try {
LinkPath path = pressedLink.obtainNewPath();
int[] pos = getRealSpanStartAndEnd(buffer, pressedLink.getSpan());
path.setCurrentLayout(captionLayout, pos[0], 0);
captionLayout.getSelectionPath(pos[0], pos[1], path);
} catch (Exception e) {
FileLog.e(e);
}
links.addLink(pressedLink, 3);
}
invalidateWithParent();
return true;
@ -1323,7 +1328,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
FileLog.e(e);
}
} else if (pressedLinkType == 3) {
delegate.didPressUrl(this, pressedLink, false);
delegate.didPressUrl(this, pressedLink.getSpan(), false);
resetPressedLink(3);
return true;
}
@ -1365,17 +1370,21 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
ignore = true;
}
if (!ignore) {
pressedLink = link[0];
linkBlockNum = -10;
pressedLinkType = 2;
resetUrlPaths(false);
try {
LinkPath path = obtainNewUrlPath(false);
int[] pos = getRealSpanStartAndEnd(buffer, pressedLink);
path.setCurrentLayout(descriptionLayout, pos[0], 0);
descriptionLayout.getSelectionPath(pos[0], pos[1], path);
} catch (Exception e) {
FileLog.e(e);
if (pressedLink == null || pressedLink.getSpan() != link[0]) {
links.removeLink(pressedLink);
pressedLink = new LinkSpanDrawable(link[0], resourcesProvider, x, y, spanSupportsLongPress(link[0]));
pressedLink.setColor(getThemedColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outLinkSelectBackground : Theme.key_chat_linkSelectBackground));
linkBlockNum = -10;
pressedLinkType = 2;
try {
LinkPath path = pressedLink.obtainNewPath();
int[] pos = getRealSpanStartAndEnd(buffer, pressedLink.getSpan());
path.setCurrentLayout(descriptionLayout, pos[0], 0);
descriptionLayout.getSelectionPath(pos[0], pos[1], path);
} catch (Exception e) {
FileLog.e(e);
}
links.addLink(pressedLink, 2);
}
invalidate();
return true;
@ -1393,10 +1402,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
didPressButton(true, false);
invalidate();
} else if (pressedLink != null) {
if (pressedLink instanceof URLSpan) {
Browser.openUrl(getContext(), ((URLSpan) pressedLink).getURL());
} else if (pressedLink instanceof ClickableSpan) {
((ClickableSpan) pressedLink).onClick(this);
if (pressedLink.getSpan() instanceof URLSpan) {
Browser.openUrl(getContext(), ((URLSpan) pressedLink.getSpan()).getURL());
} else if (pressedLink.getSpan() instanceof ClickableSpan) {
((ClickableSpan) pressedLink.getSpan()).onClick(this);
}
resetPressedLink(2);
} else {
@ -1446,18 +1455,22 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
ignore = true;
}
if (!ignore) {
pressedLink = link[0];
linkBlockNum = -10;
pressedLinkType = 2;
resetUrlPaths(false);
startCheckLongPress();
try {
LinkPath path = obtainNewUrlPath(false);
int[] pos = getRealSpanStartAndEnd(buffer, pressedLink);
path.setCurrentLayout(descriptionLayout, pos[0], 0);
descriptionLayout.getSelectionPath(pos[0], pos[1], path);
} catch (Exception e) {
FileLog.e(e);
if (pressedLink == null || pressedLink.getSpan() != link[0]) {
links.removeLink(pressedLink);
pressedLink = new LinkSpanDrawable(link[0], resourcesProvider, x, y, spanSupportsLongPress(link[0]));
pressedLink.setColor(getThemedColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outLinkSelectBackground : Theme.key_chat_linkSelectBackground));
linkBlockNum = -10;
pressedLinkType = 2;
startCheckLongPress();
try {
LinkPath path = pressedLink.obtainNewPath();
int[] pos = getRealSpanStartAndEnd(buffer, pressedLink.getSpan());
path.setCurrentLayout(descriptionLayout, pos[0], 0);
descriptionLayout.getSelectionPath(pos[0], pos[1], path);
} catch (Exception e) {
FileLog.e(e);
}
links.addLink(pressedLink, 2);
}
invalidate();
return true;
@ -1541,10 +1554,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
didPressMiniButton(true);
invalidate();
} else if (pressedLink != null) {
if (pressedLink instanceof URLSpan) {
delegate.didPressUrl(this, pressedLink, false);
} else if (pressedLink instanceof ClickableSpan) {
((ClickableSpan) pressedLink).onClick(this);
if (pressedLink.getSpan() instanceof URLSpan) {
delegate.didPressUrl(this, pressedLink.getSpan(), false);
} else if (pressedLink.getSpan() instanceof ClickableSpan) {
((ClickableSpan) pressedLink.getSpan()).onClick(this);
}
resetPressedLink(2);
} else {
@ -1585,7 +1598,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
resetPressedLink(2);
return true;
}
} else {
} else if (!hadLongPress) {
hadLongPress = false;
resetPressedLink(2);
}
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
@ -2430,7 +2444,15 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
}
result = false;
resetPressedLink(-1);
if (hadLongPress) {
if (pressedLinkType != 2) {
hadLongPress = false;
}
pressedLink = null;
pressedLinkType = -1;
} else {
resetPressedLink(-1);
}
}
updateRadialProgressBackground();
if (!disallowLongPress && result && event.getAction() == MotionEvent.ACTION_DOWN) {
@ -2567,7 +2589,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
forwardBotPressed = false;
playSoundEffect(SoundEffectConstants.CLICK);
if (delegate != null) {
delegate.didPressViaBot(this, currentViaBotUser != null ? currentViaBotUser.username : currentMessageObject.messageOwner.via_bot_name);
if (currentViaBotUser.bot_inline_placeholder == null) {
delegate.didPressViaBotNotInline(this, currentViaBotUser != null ? currentViaBotUser.id : 0);
} else {
delegate.didPressViaBot(this, currentViaBotUser != null ? currentViaBotUser.username : currentMessageObject.messageOwner.via_bot_name);
}
}
} else if (event.getAction() == MotionEvent.ACTION_CANCEL) {
forwardBotPressed = false;
@ -3552,8 +3578,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
AndroidUtilities.cancelRunOnUIThread(invalidateRunnable);
scheduledInvalidate = false;
}
resetPressedLink(-1);
links.clear();
pressedLink = null;
pressedLinkType = -1;
messageObject.forceUpdate = false;
drawPhotoImage = false;
drawMediaCheckBox = false;
@ -6584,7 +6611,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (botButton.button instanceof TLRPC.TL_keyboardButtonBuy && (messageObject.messageOwner.media.flags & 4) != 0) {
buttonText = LocaleController.getString("PaymentReceipt", R.string.PaymentReceipt);
} else {
buttonText = Emoji.replaceEmoji(botButton.button.text, botButtonPaint.getFontMetricsInt(), AndroidUtilities.dp(15), false);
buttonText = botButton.button.text == null ? "" : botButton.button.text;
buttonText = Emoji.replaceEmoji(buttonText, botButtonPaint.getFontMetricsInt(), AndroidUtilities.dp(15), false);
buttonText = TextUtils.ellipsize(buttonText, botButtonPaint, buttonWidth - AndroidUtilities.dp(10), TextUtils.TruncateAt.END);
}
botButton.title = new StaticLayout(buttonText, botButtonPaint, buttonWidth - AndroidUtilities.dp(10), Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
@ -6592,6 +6620,16 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (b == row.buttons.size() - 1) {
maxButtonsWidth = Math.max(maxButtonsWidth, botButton.x + botButton.width);
}
if (messageObject.isFromUser() && botButton.button instanceof TLRPC.TL_keyboardButtonUrl) { // TODO(dkaraush): or instanceof TLRPC.TL_keyboardButtonInvite in the future
try {
final Uri uri = Uri.parse(botButton.button.url);
final String host = uri.getHost().toLowerCase();
botButton.isInviteButton = (uri.getQueryParameter("startgroup") != null && (
("http".equals(uri.getScheme()) || "https".equals(uri.getScheme())) && ("t.me".equals(host) || "telegram.me".equals(host) || "telegram.dog".equals(host)) ||
"tg".equals(uri.getScheme()) && (botButton.button.url.startsWith("tg:resolve") || botButton.button.url.startsWith("tg://resolve"))
));
} catch (Exception ignore) {}
}
}
}
}
@ -6802,6 +6840,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
private boolean hadLongPress = false;
private static boolean spanSupportsLongPress(CharacterStyle span) {
return span instanceof URLSpanMono || span instanceof URLSpan;
}
@Override
protected boolean onLongPress() {
if (isRoundVideo && isPlayingRound && MediaController.getInstance().isPlayingMessage(currentMessageObject)) {
@ -6852,18 +6894,23 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
return false;
}
}
if (pressedLink instanceof URLSpanMono) {
delegate.didPressUrl(this, pressedLink, true);
return true;
} else if (pressedLink instanceof URLSpanNoUnderline) {
URLSpanNoUnderline url = (URLSpanNoUnderline) pressedLink;
if (ChatActivity.isClickableLink(url.getURL()) || url.getURL().startsWith("/")) {
delegate.didPressUrl(this, pressedLink, true);
if (pressedLink != null) {
if (pressedLink.getSpan() instanceof URLSpanMono) {
hadLongPress = true;
delegate.didPressUrl(this, pressedLink.getSpan(), true);
return true;
} else if (pressedLink.getSpan() instanceof URLSpanNoUnderline) {
URLSpanNoUnderline url = (URLSpanNoUnderline) pressedLink.getSpan();
if (ChatActivity.isClickableLink(url.getURL()) || url.getURL().startsWith("/")) {
hadLongPress = true;
delegate.didPressUrl(this, pressedLink.getSpan(), true);
return true;
}
} else if (pressedLink.getSpan() instanceof URLSpan) {
hadLongPress = true;
delegate.didPressUrl(this, pressedLink.getSpan(), true);
return true;
}
} else if (pressedLink instanceof URLSpan) {
delegate.didPressUrl(this, pressedLink, true);
return true;
}
resetPressedLink(-1);
if (buttonPressed != 0 || miniButtonPressed != 0 || videoButtonPressed != 0 || pressedBotButton != -1) {
@ -6977,7 +7024,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
return;
}
super.invalidate();
if (invalidatesParent && getParent() != null) {
if ((invalidatesParent || currentMessagesGroup != null && !links.isEmpty()) && getParent() != null) {
View parent = (View) getParent();
if (parent.getParent() != null) {
parent.invalidate();
@ -7143,6 +7190,16 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
seekBarWaveform.setMessageObject(messageObject);
return 0;
} else if (MessageObject.isVideoDocument(documentAttach)) {
documentAttachType = DOCUMENT_ATTACH_TYPE_VIDEO;
if (!messageObject.needDrawBluredPreview()) {
updatePlayingMessageProgress();
String str;
str = String.format("%s", AndroidUtilities.formatFileSize(documentAttach.size));
docTitleWidth = (int) Math.ceil(Theme.chat_infoPaint.measureText(str));
docTitleLayout = new StaticLayout(str, Theme.chat_infoPaint, docTitleWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
}
return 0;
} else if (MessageObject.isMusicDocument(documentAttach)) {
documentAttachType = DOCUMENT_ATTACH_TYPE_MUSIC;
@ -7175,16 +7232,6 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
widthBeforeNewTimeLine = backgroundWidth - AndroidUtilities.dp(10 + 76) - durationWidth;
availableTimeWidth = backgroundWidth - AndroidUtilities.dp(28);
return durationWidth;
} else if (MessageObject.isVideoDocument(documentAttach)) {
documentAttachType = DOCUMENT_ATTACH_TYPE_VIDEO;
if (!messageObject.needDrawBluredPreview()) {
updatePlayingMessageProgress();
String str;
str = String.format("%s", AndroidUtilities.formatFileSize(documentAttach.size));
docTitleWidth = (int) Math.ceil(Theme.chat_infoPaint.measureText(str));
docTitleLayout = new StaticLayout(str, Theme.chat_infoPaint, docTitleWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
}
return 0;
} else if (MessageObject.isGifDocument(documentAttach, messageObject.hasValidGroupId())) {
documentAttachType = DOCUMENT_ATTACH_TYPE_GIF;
if (!messageObject.needDrawBluredPreview()) {
@ -7286,7 +7333,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (messageObject == null || messageObject.messageOwner.message == null || TextUtils.isEmpty(text)) {
if (!urlPathSelection.isEmpty()) {
linkSelectionBlockNum = -1;
resetUrlPaths(true);
resetUrlPaths();
invalidate();
}
return;
@ -7319,7 +7366,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (start == -1) {
if (!urlPathSelection.isEmpty()) {
linkSelectionBlockNum = -1;
resetUrlPaths(true);
resetUrlPaths();
invalidate();
}
return;
@ -7333,9 +7380,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
int end = start + length;
if (captionLayout != null && !TextUtils.isEmpty(messageObject.caption)) {
resetUrlPaths(true);
resetUrlPaths();
try {
LinkPath path = obtainNewUrlPath(true);
LinkPath path = obtainNewUrlPath();
path.setCurrentLayout(captionLayout, start, 0);
captionLayout.getSelectionPath(start, end, path);
} catch (Exception e) {
@ -7347,16 +7394,16 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
MessageObject.TextLayoutBlock block = messageObject.textLayoutBlocks.get(c);
if (start >= block.charactersOffset && start < block.charactersEnd) {
linkSelectionBlockNum = c;
resetUrlPaths(true);
resetUrlPaths();
try {
LinkPath path = obtainNewUrlPath(true);
LinkPath path = obtainNewUrlPath();
path.setCurrentLayout(block.textLayout, start, 0);
block.textLayout.getSelectionPath(start, end, path);
if (end >= block.charactersOffset + length) {
for (int a = c + 1; a < messageObject.textLayoutBlocks.size(); a++) {
MessageObject.TextLayoutBlock nextBlock = messageObject.textLayoutBlocks.get(a);
length = nextBlock.charactersEnd - nextBlock.charactersOffset;
path = obtainNewUrlPath(true);
path = obtainNewUrlPath();
path.setCurrentLayout(nextBlock.textLayout, 0, nextBlock.height);
nextBlock.textLayout.getSelectionPath(0, end - nextBlock.charactersOffset, path);
if (end < block.charactersOffset + length - 1) {
@ -8632,7 +8679,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (hasNewLineForTime) {
reactionsLayoutInBubble.y -= AndroidUtilities.dp(16);
}
if (captionLayout != null && ((currentMessageObject.type != 2 && !(currentMessageObject.type == 9 && drawPhotoImage)) || (currentPosition != null && currentMessagesGroup != null))) {
if (captionLayout != null && ((currentMessageObject.type != 2 && !(currentMessageObject.isOut() && drawForwardedName && !drawPhotoImage) && !(currentMessageObject.type == 9 && drawPhotoImage)) || (currentPosition != null && currentMessagesGroup != null))) {
reactionsLayoutInBubble.y -= AndroidUtilities.dp(14);
}
reactionsLayoutInBubble.y = reactionsLayoutInBubble.y + reactionsLayoutInBubble.positionOffsetY;
@ -8835,9 +8882,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
descriptionY = linkPreviewY - AndroidUtilities.dp(3);
canvas.save();
canvas.translate(linkX + (hasInvoicePreview ? 0 : AndroidUtilities.dp(10)) + descriptionX, descriptionY);
if (pressedLink != null && linkBlockNum == -10) {
for (int b = 0; b < urlPath.size(); b++) {
canvas.drawPath(urlPath.get(b), Theme.chat_urlPaint);
if (linkBlockNum == -10) {
if (links.draw(canvas)) {
invalidate();
}
}
if (delegate != null && delegate.getTextSelectionHelper() != null && getDelegate().getTextSelectionHelper().isSelected(currentMessageObject)) {
@ -9014,8 +9061,18 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
canvas.translate(button.x + addX + AndroidUtilities.dp(5), y + (AndroidUtilities.dp(44) - button.title.getLineBottom(button.title.getLineCount() - 1)) / 2);
button.title.draw(canvas);
canvas.restore();
if (button.button instanceof TLRPC.TL_keyboardButtonUrl) {
Drawable drawable = getThemedDrawable(Theme.key_drawable_botLink);
if (button.button instanceof TLRPC.TL_keyboardButtonWebView) {
Drawable drawable = getThemedDrawable(Theme.key_drawable_botWebView);
int x = button.x + button.width - AndroidUtilities.dp(3) - drawable.getIntrinsicWidth() + addX;
setDrawableBounds(drawable, x, y + AndroidUtilities.dp(3));
drawable.draw(canvas);
} else if (button.button instanceof TLRPC.TL_keyboardButtonUrl) {
Drawable drawable;
if (button.isInviteButton) {
drawable = getThemedDrawable(Theme.key_drawable_botInvite);
} else {
drawable = getThemedDrawable(Theme.key_drawable_botLink);
}
int x = button.x + button.width - AndroidUtilities.dp(3) - drawable.getIntrinsicWidth() + addX;
setDrawableBounds(drawable, x, y + AndroidUtilities.dp(3));
drawable.draw(canvas);
@ -9026,9 +9083,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
drawable.draw(canvas);
} else if (button.button instanceof TLRPC.TL_keyboardButtonCallback || button.button instanceof TLRPC.TL_keyboardButtonRequestGeoLocation || button.button instanceof TLRPC.TL_keyboardButtonGame || button.button instanceof TLRPC.TL_keyboardButtonBuy || button.button instanceof TLRPC.TL_keyboardButtonUrlAuth) {
if (button.button instanceof TLRPC.TL_keyboardButtonBuy) {
int x = button.x + button.width - AndroidUtilities.dp(5) - Theme.chat_botCardDrawalbe.getIntrinsicWidth() + addX;
setDrawableBounds(Theme.chat_botCardDrawalbe, x, y + AndroidUtilities.dp(4));
Theme.chat_botCardDrawalbe.draw(canvas);
int x = button.x + button.width - AndroidUtilities.dp(5) - Theme.chat_botCardDrawable.getIntrinsicWidth() + addX;
setDrawableBounds(Theme.chat_botCardDrawable, x, y + AndroidUtilities.dp(4));
Theme.chat_botCardDrawable.draw(canvas);
}
boolean drawProgress = (button.button instanceof TLRPC.TL_keyboardButtonCallback || button.button instanceof TLRPC.TL_keyboardButtonGame || button.button instanceof TLRPC.TL_keyboardButtonBuy || button.button instanceof TLRPC.TL_keyboardButtonUrlAuth) && SendMessagesHelper.getInstance(currentAccount).isSendingCallback(currentMessageObject, button.button) ||
button.button instanceof TLRPC.TL_keyboardButtonRequestGeoLocation && SendMessagesHelper.getInstance(currentAccount).isSendingCurrentLocation(currentMessageObject, button.button);
@ -9134,9 +9191,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
MessageObject.TextLayoutBlock block = textLayoutBlocks.get(a);
canvas.save();
canvas.translate(textX - (block.isRtl() ? (int) Math.ceil(currentMessageObject.textXOffset) : 0), textY + block.textYOffset + transitionYOffsetForDrawables);
if (pressedLink != null && a == linkBlockNum && !drawOnlyText) {
for (int b = 0; b < urlPath.size(); b++) {
canvas.drawPath(urlPath.get(b), Theme.chat_urlPaint);
if (a == linkBlockNum && !drawOnlyText) {
if (links.draw(canvas)) {
invalidate();
}
}
if (a == linkSelectionBlockNum && !urlPathSelection.isEmpty() && !drawOnlyText) {
@ -10769,24 +10826,41 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
maxWidth -= AndroidUtilities.dp(44);
}
if (messageObject.customReplyName != null) {
name = messageObject.customReplyName;
} else {
long fromId = messageObject.replyMessageObject.getFromChatId();
if (fromId > 0) {
TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(fromId);
if (user != null) {
if (messageObject.hideSendersName) {
if (messageObject.sendAsPeer != null) {
if (messageObject.sendAsPeer.channel_id != 0) {
TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(messageObject.sendAsPeer.channel_id);
if (chat != null) {
name = chat.title;
}
} else {
TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(messageObject.sendAsPeer.user_id);
name = UserObject.getUserName(user);
}
} else if (fromId < 0) {
TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-fromId);
if (chat != null) {
name = chat.title;
}
} else {
TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(messageObject.replyMessageObject.messageOwner.peer_id.channel_id);
if (chat != null) {
name = chat.title;
name = UserObject.getUserName(AccountInstance.getInstance(currentAccount).getUserConfig().getCurrentUser());
}
} else if (messageObject.customReplyName != null) {
name = messageObject.customReplyName;
} else {
name = messageObject.replyMessageObject.getForwardedName();
if (name == null) {
long fromId = messageObject.replyMessageObject.getFromChatId();
if (fromId > 0) {
TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(fromId);
if (user != null) {
name = UserObject.getUserName(user);
}
} else if (fromId < 0) {
TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-fromId);
if (chat != null) {
name = chat.title;
}
} else {
TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(messageObject.replyMessageObject.messageOwner.peer_id.channel_id);
if (chat != null) {
name = chat.title;
}
}
}
}
@ -10883,12 +10957,19 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
replyTextWidth = AndroidUtilities.dp(4 + (needReplyImage ? 44 : 0));
if (stringFinalText != null) {
SpannableStringBuilder sb = new SpannableStringBuilder(stringFinalText);
boolean changed = false;
for (TextStyleSpan span : sb.getSpans(0, sb.length(), TextStyleSpan.class)) {
if ((span.getTextStyleRun().flags & TextStyleSpan.FLAG_STYLE_MONO) != 0) {
changed = true;
sb.removeSpan(span);
}
}
replyTextLayout = new StaticLayout(sb, Theme.chat_replyTextPaint, maxWidth + AndroidUtilities.dp(10), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
if (changed) {
stringFinalText = TextUtils.ellipsize(sb, Theme.chat_replyTextPaint, maxWidth, TextUtils.TruncateAt.END);
} else {
stringFinalText = sb;
}
replyTextLayout = new StaticLayout(stringFinalText, Theme.chat_replyTextPaint, maxWidth + AndroidUtilities.dp(10), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
if (replyTextLayout.getLineCount() > 0) {
replyTextWidth += (int) Math.ceil(replyTextLayout.getLineWidth(0)) + AndroidUtilities.dp(8);
replyTextOffset = (int) replyTextLayout.getLineLeft(0);
@ -11204,7 +11285,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
} else {
replyStartX = backgroundDrawableLeft + backgroundDrawableRight + AndroidUtilities.dp(17);
}
replyStartY = AndroidUtilities.dp(12);
if (drawForwardedName) {
replyStartY = forwardNameY + AndroidUtilities.dp(38);
} else {
replyStartY = AndroidUtilities.dp(12);
}
} else {
if (currentMessageObject.isOutOwner()) {
replyStartX = backgroundDrawableLeft + AndroidUtilities.dp(12) + getExtraTextX();
@ -12083,6 +12168,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
boolean drawForwardedNameLocal = drawForwardedName;
boolean hasReply = replyNameLayout != null;
StaticLayout[] forwardedNameLayoutLocal = forwardedNameLayout;
float animatingAlpha = 1f;
int forwardedNameWidthLocal = forwardedNameWidth;
@ -12098,6 +12184,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
float forwardNameXLocal;
boolean needDrawReplyBackground = true;
if (drawForwardedNameLocal && forwardedNameLayoutLocal[0] != null && forwardedNameLayoutLocal[1] != null && (currentPosition == null || currentPosition.minY == 0 && currentPosition.minX == 0)) {
if (currentMessageObject.type == MessageObject.TYPE_ROUND_VIDEO || currentMessageObject.isAnyKindOfSticker()) {
Theme.chat_forwardNamePaint.setColor(getThemedColor(Theme.key_chat_stickerReplyNameText));
@ -12116,8 +12203,13 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
forwardNameY = AndroidUtilities.dp(12);
int backWidth = forwardedNameWidthLocal + AndroidUtilities.dp(14);
rect.set((int) forwardNameXLocal - AndroidUtilities.dp(7), forwardNameY - AndroidUtilities.dp(6), (int) forwardNameXLocal - AndroidUtilities.dp(7) + backWidth, forwardNameY + AndroidUtilities.dp(38));
if (hasReply) {
needDrawReplyBackground = false;
int replyBackWidth = Math.max(replyNameWidth, replyTextWidth) + AndroidUtilities.dp(14);
rect.set((int) forwardNameXLocal - AndroidUtilities.dp(7), forwardNameY - AndroidUtilities.dp(6), (int) forwardNameXLocal - AndroidUtilities.dp(7) + Math.max(backWidth, replyBackWidth), forwardNameY + AndroidUtilities.dp(38) + AndroidUtilities.dp(41));
} else {
rect.set((int) forwardNameXLocal - AndroidUtilities.dp(7), forwardNameY - AndroidUtilities.dp(6), (int) forwardNameXLocal - AndroidUtilities.dp(7) + backWidth, forwardNameY + AndroidUtilities.dp(38));
}
applyServiceShaderMatrix();
int oldAlpha1 = -1, oldAlpha2 = -1;
if (animatingAlpha != 1f || replyForwardAlpha != 1f) {
@ -12242,8 +12334,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
}
if (replyNameLayout != null) {
if (hasReply) {
float replyStartX = this.replyStartX;
float replyStartY = this.replyStartY;
if (currentMessagesGroup != null && currentMessagesGroup.transitionParams.backgroundChangeBounds) {
replyStartX += currentMessagesGroup.transitionParams.offsetLeft;
}
@ -12253,6 +12346,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
} else {
replyStartX += transitionParams.deltaLeft;
}
replyStartY = this.replyStartY * transitionParams.animateChangeProgress + transitionParams.animateFromReplyY * (1f - transitionParams.animateChangeProgress);
}
if (currentMessageObject.shouldDrawWithoutBackground()) {
Theme.chat_replyLinePaint.setColor(getThemedColor(Theme.key_chat_stickerReplyLine));
@ -12264,19 +12358,20 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
Theme.chat_replyTextPaint.setColor(getThemedColor(Theme.key_chat_stickerReplyMessageText));
oldAlpha = Theme.chat_replyTextPaint.getAlpha();
Theme.chat_replyTextPaint.setAlpha((int) (oldAlpha * timeAlpha * replyForwardAlpha));
int backWidth = Math.max(replyNameWidth, replyTextWidth) + AndroidUtilities.dp(14);
rect.set((int) replyStartX - AndroidUtilities.dp(7), replyStartY - AndroidUtilities.dp(6), (int) replyStartX - AndroidUtilities.dp(7) + backWidth, replyStartY + AndroidUtilities.dp(41));
applyServiceShaderMatrix();
oldAlpha = getThemedPaint(Theme.key_paint_chatActionBackground).getAlpha();
getThemedPaint(Theme.key_paint_chatActionBackground).setAlpha((int) (oldAlpha * timeAlpha * replyForwardAlpha));
canvas.drawRoundRect(rect, AndroidUtilities.dp(6), AndroidUtilities.dp(6), getThemedPaint(Theme.key_paint_chatActionBackground));
getThemedPaint(Theme.key_paint_chatActionBackground).setAlpha(oldAlpha);
if (hasGradientService()) {
oldAlpha = Theme.chat_actionBackgroundGradientDarkenPaint.getAlpha();
Theme.chat_actionBackgroundGradientDarkenPaint.setAlpha((int) (oldAlpha * timeAlpha * replyForwardAlpha));
canvas.drawRoundRect(rect, AndroidUtilities.dp(6), AndroidUtilities.dp(6), Theme.chat_actionBackgroundGradientDarkenPaint);
Theme.chat_actionBackgroundGradientDarkenPaint.setAlpha(oldAlpha);
if (needDrawReplyBackground) {
int backWidth = Math.max(replyNameWidth, replyTextWidth) + AndroidUtilities.dp(14);
rect.set((int) replyStartX - AndroidUtilities.dp(7), replyStartY - AndroidUtilities.dp(6), (int) replyStartX - AndroidUtilities.dp(7) + backWidth, replyStartY + AndroidUtilities.dp(41));
applyServiceShaderMatrix();
oldAlpha = getThemedPaint(Theme.key_paint_chatActionBackground).getAlpha();
getThemedPaint(Theme.key_paint_chatActionBackground).setAlpha((int) (oldAlpha * timeAlpha * replyForwardAlpha));
canvas.drawRoundRect(rect, AndroidUtilities.dp(6), AndroidUtilities.dp(6), getThemedPaint(Theme.key_paint_chatActionBackground));
getThemedPaint(Theme.key_paint_chatActionBackground).setAlpha(oldAlpha);
if (hasGradientService()) {
oldAlpha = Theme.chat_actionBackgroundGradientDarkenPaint.getAlpha();
Theme.chat_actionBackgroundGradientDarkenPaint.setAlpha((int) (oldAlpha * timeAlpha * replyForwardAlpha));
canvas.drawRoundRect(rect, AndroidUtilities.dp(6), AndroidUtilities.dp(6), Theme.chat_actionBackgroundGradientDarkenPaint);
Theme.chat_actionBackgroundGradientDarkenPaint.setAlpha(oldAlpha);
}
}
} else {
if (currentMessageObject.isOutOwner()) {
@ -12299,7 +12394,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
forwardNameX = replyStartX - replyTextOffset + AndroidUtilities.dp(10 + (needReplyImage ? 44 : 0));
if ((currentPosition == null || currentPosition.minY == 0 && currentPosition.minX == 0) && !(enterTransitionInProgress && !currentMessageObject.isVoice())) {
canvas.drawRect(replyStartX, replyStartY, replyStartX + AndroidUtilities.dp(2), replyStartY + AndroidUtilities.dp(35), Theme.chat_replyLinePaint);
AndroidUtilities.rectTmp.set(replyStartX, replyStartY, replyStartX + AndroidUtilities.dp(2), replyStartY + AndroidUtilities.dp(35));
canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(1), AndroidUtilities.dp(1), Theme.chat_replyLinePaint);
if (needReplyImage) {
replyImageReceiver.setAlpha(replyForwardAlpha);
replyImageReceiver.setImageCoords(replyStartX + AndroidUtilities.dp(10), replyStartY, AndroidUtilities.dp(35), AndroidUtilities.dp(35));
@ -12689,7 +12785,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
}
if (captionLayout == null || selectionOnly && pressedLink == null || (currentMessageObject.deleted && currentPosition != null) || alpha == 0) {
if (captionLayout == null || selectionOnly && links.isEmpty() || (currentMessageObject.deleted && currentPosition != null) || alpha == 0) {
return;
}
if (currentMessageObject.isOutOwner()) {
@ -12745,10 +12841,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
canvas.translate(captionX, captionY);
if (pressedLink != null) {
for (int b = 0; b < urlPath.size(); b++) {
canvas.drawPath(urlPath.get(b), Theme.chat_urlPaint);
}
if (links.draw(canvas)) {
invalidate();
}
if (!urlPathSelection.isEmpty()) {
for (int b = 0; b < urlPathSelection.size(); b++) {
@ -15558,6 +15652,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
public boolean animateRoundVideoDotY;
public float lastDrawRoundVideoDotY;
public float animateFromRoundVideoDotY;
public boolean animateReplyY;
public float lastDrawReplyY;
public float animateFromReplyY;
private boolean lastIsPinned;
private boolean animatePinned;
@ -15756,6 +15853,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
lastBackgroundRight = currentBackgroundDrawable.getBounds().right;
reactionsLayoutInBubble.recordDrawingState();
if (replyNameLayout != null) {
lastDrawReplyY = replyStartY;
} else {
lastDrawReplyY = 0;
}
}
public void recordDrawingStatePreview() {
@ -15982,6 +16084,12 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
}
if (replyNameLayout != null && replyStartX != lastDrawReplyY && lastDrawReplyY != 0) {
animateReplyY = true;
animateFromReplyY = lastDrawReplyY;
changed = true;
}
return changed;
}
@ -16044,6 +16152,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
animatingForwardedNameLayout[0] = null;
animatingForwardedNameLayout[1] = null;
animateRoundVideoDotY = false;
animateReplyY = false;
reactionsLayoutInBubble.resetAnimation();
}

View File

@ -0,0 +1,79 @@
package org.telegram.ui.Cells;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.view.Gravity;
import android.widget.FrameLayout;
import android.widget.ImageView;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.LocaleController;
import org.telegram.ui.ActionBar.SimpleTextView;
import org.telegram.ui.ActionBar.Theme;
public class CreationTextCell extends FrameLayout {
private SimpleTextView textView;
private ImageView imageView;
boolean divider;
public int startPadding = 70;
public CreationTextCell(Context context) {
super(context);
textView = new SimpleTextView(context);
textView.setTextSize(16);
textView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT);
textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText2));
textView.setTag(Theme.key_windowBackgroundWhiteBlueText2);
addView(textView);
imageView = new ImageView(context);
imageView.setScaleType(ImageView.ScaleType.CENTER);
addView(imageView);
setWillNotDraw(false);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = AndroidUtilities.dp(48);
textView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(71 + 23), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(20), MeasureSpec.EXACTLY));
imageView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(50), MeasureSpec.EXACTLY));
setMeasuredDimension(width, AndroidUtilities.dp(50));
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int height = bottom - top;
int width = right - left;
int viewLeft;
int viewTop = (height - textView.getTextHeight()) / 2;
if (LocaleController.isRTL) {
viewLeft = getMeasuredWidth() - textView.getMeasuredWidth() - AndroidUtilities.dp(imageView.getVisibility() == VISIBLE ? startPadding : 25);
} else {
viewLeft = AndroidUtilities.dp(imageView.getVisibility() == VISIBLE ? startPadding : 25);
}
textView.layout(viewLeft, viewTop, viewLeft + textView.getMeasuredWidth(), viewTop + textView.getMeasuredHeight());
viewLeft = !LocaleController.isRTL ? (AndroidUtilities.dp(startPadding) - imageView.getMeasuredWidth()) / 2 : width - imageView.getMeasuredWidth() - AndroidUtilities.dp(25);
imageView.layout(viewLeft, 0, viewLeft + imageView.getMeasuredWidth(), imageView.getMeasuredHeight());
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (divider) {
canvas.drawLine(AndroidUtilities.dp(startPadding), getMeasuredHeight() - 1, getMeasuredWidth() + AndroidUtilities.dp(23), getMeasuredHeight(), Theme.dividerPaint);
}
}
public void setTextAndIcon(String text, Drawable icon, boolean divider) {
textView.setText(text);
imageView.setImageDrawable(icon);
this.divider = divider;
}
}

View File

@ -267,6 +267,7 @@ public class DialogCell extends BaseCell {
private int pinLeft;
private boolean drawCount;
private boolean drawCount2 = true;
private int countTop;
private int countLeft;
private int countWidth;
@ -528,6 +529,7 @@ public class DialogCell extends BaseCell {
bottomClip = getMeasuredHeight();
}
int lastSize;
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (currentDialogId == 0 && customDialog == null) {
@ -538,7 +540,9 @@ public class DialogCell extends BaseCell {
int y = AndroidUtilities.dp(useForceThreeLines || SharedConfig.useThreeLinesLayout ? 48 : 42);
checkBox.layout(x, y, x + checkBox.getMeasuredWidth(), y + checkBox.getMeasuredHeight());
}
if (changed) {
int size = getMeasuredHeight() + getMeasuredWidth() << 16;
if (size != lastSize) {
lastSize = size;
try {
buildLayout();
} catch (Exception e) {
@ -1015,7 +1019,39 @@ public class DialogCell extends BaseCell {
} else {
fromChat = MessagesController.getInstance(currentAccount).getChat(-fromId);
}
if (dialogsType == 3 && UserObject.isUserSelf(user)) {
drawCount2 = true;
if (dialogsType == 2) {
if (chat != null) {
if (ChatObject.isChannel(chat) && !chat.megagroup) {
if (chat.participants_count != 0) {
messageString = LocaleController.formatPluralStringComma("Subscribers", chat.participants_count);
} else {
if (TextUtils.isEmpty(chat.username)) {
messageString = LocaleController.getString("ChannelPrivate", R.string.ChannelPrivate).toLowerCase();
} else {
messageString = LocaleController.getString("ChannelPublic", R.string.ChannelPublic).toLowerCase();
}
}
} else {
if (chat.participants_count != 0) {
messageString = LocaleController.formatPluralStringComma("Members", chat.participants_count);
} else {
if (chat.has_geo) {
messageString = LocaleController.getString("MegaLocation", R.string.MegaLocation);
} else if (TextUtils.isEmpty(chat.username)) {
messageString = LocaleController.getString("MegaPrivate", R.string.MegaPrivate).toLowerCase();
} else {
messageString = LocaleController.getString("MegaPublic", R.string.MegaPublic).toLowerCase();
}
}
}
} else {
messageString = "";
}
drawCount2 = false;
showChecks = false;
drawTime = false;
} else if (dialogsType == 3 && UserObject.isUserSelf(user)) {
messageString = LocaleController.getString("SavedMessagesInfo", R.string.SavedMessagesInfo);
showChecks = false;
drawTime = false;
@ -1023,8 +1059,7 @@ public class DialogCell extends BaseCell {
checkMessage = false;
messageString = formatArchivedDialogNames();
} else if (message.messageOwner instanceof TLRPC.TL_messageService) {
if (ChatObject.isChannelAndNotMegaGroup(chat) && (message.messageOwner.action instanceof TLRPC.TL_messageActionHistoryClear ||
message.messageOwner.action instanceof TLRPC.TL_messageActionChannelMigrateFrom)) {
if (ChatObject.isChannelAndNotMegaGroup(chat) && (message.messageOwner.action instanceof TLRPC.TL_messageActionChannelMigrateFrom)) {
messageString = "";
showChecks = false;
} else {
@ -2114,6 +2149,9 @@ public class DialogCell extends BaseCell {
} else {
drawPin = false;
}
if (dialogsType == 2) {
drawPin = false;
}
if (mask != 0) {
boolean continueUpdate = false;
@ -2294,7 +2332,7 @@ public class DialogCell extends BaseCell {
countAnimator.setDuration(430);
countAnimator.setInterpolator(CubicBezierInterpolator.DEFAULT);
}
if (drawCount && countLayout != null) {
if (drawCount && drawCount2 && countLayout != null) {
String oldStr = String.valueOf(oldUnreadCount);
String newStr = String.valueOf(unreadCount);
@ -2791,7 +2829,7 @@ public class DialogCell extends BaseCell {
lastStatusDrawableParams = (this.drawClock ? 1 : 0) + (this.drawCheck1 ? 2 : 0) + (this.drawCheck2 ? 4 : 0);
}
if ((dialogMuted || dialogMutedProgress > 0) && !drawVerified && drawScam == 0) {
if (dialogsType != 2 && (dialogMuted || dialogMutedProgress > 0) && !drawVerified && drawScam == 0) {
if (dialogMuted && dialogMutedProgress != 1f) {
dialogMutedProgress += 16 / 150f;
if (dialogMutedProgress > 1f) {
@ -2840,8 +2878,8 @@ public class DialogCell extends BaseCell {
canvas.drawRoundRect(rect, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, Theme.dialogs_errorPaint);
setDrawableBounds(Theme.dialogs_errorDrawable, errorLeft + AndroidUtilities.dp(5.5f), errorTop + AndroidUtilities.dp(5));
Theme.dialogs_errorDrawable.draw(canvas);
} else if (drawCount || drawMention || countChangeProgress != 1f || drawReactionMention || reactionsMentionsChangeProgress != 1f) {
if (drawCount || countChangeProgress != 1f) {
} else if ((drawCount || drawMention) && drawCount2 || countChangeProgress != 1f || drawReactionMention || reactionsMentionsChangeProgress != 1f) {
if (drawCount && drawCount2 || countChangeProgress != 1f) {
final float progressFinal = (unreadCount == 0 && !markUnread) ? 1f - countChangeProgress : countChangeProgress;
if (countOldLayout == null || unreadCount == 0) {
StaticLayout drawLayout = unreadCount == 0 ? countOldLayout : countLayout;

View File

@ -49,6 +49,9 @@ import org.telegram.messenger.Utilities;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Components.AudioPlayerAlert;
import org.telegram.ui.ActionBar.ActionBarLayout;
import org.telegram.ui.ActionBar.BaseFragment;
import org.telegram.ui.ActionBar.DrawerLayoutContainer;
import org.telegram.ui.Components.AvatarDrawable;
import org.telegram.ui.Components.BackupImageView;
import org.telegram.ui.Components.CubicBezierInterpolator;
@ -58,6 +61,7 @@ import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.RLottieDrawable;
import org.telegram.ui.Components.RLottieImageView;
import org.telegram.ui.Components.SnowflakesEffect;
import org.telegram.ui.ThemeActivity;
import tw.nekomimi.nekogram.NekoConfig;
@ -88,7 +92,7 @@ public class DrawerProfileCell extends FrameLayout {
private TLRPC.User user;
private boolean allowInvalidate = true;
public DrawerProfileCell(Context context) {
public DrawerProfileCell(Context context, DrawerLayoutContainer drawerLayoutContainer) {
super(context);
imageReceiver = new ImageReceiver(this);
@ -196,6 +200,7 @@ public class DrawerProfileCell extends FrameLayout {
}
}
};
darkThemeView.setBackground(Theme.createCircleSelectorDrawable(Theme.getColor(Theme.key_dialogButtonSelector), 0, 0));
sunDrawable.beginApplyLayerColors();
int color = Theme.getColor(Theme.key_chats_menuName);
sunDrawable.setLayerColor("Sunny.**", color);
@ -249,6 +254,13 @@ public class DrawerProfileCell extends FrameLayout {
}
switchTheme(themeInfo, toDark);
});
darkThemeView.setOnLongClickListener(e -> {
if (drawerLayoutContainer != null) {
drawerLayoutContainer.presentFragment(new ThemeActivity(ThemeActivity.THEME_TYPE_BASIC));
return true;
}
return false;
});
addView(darkThemeView, LayoutHelper.createFrame(48, 48, Gravity.RIGHT | Gravity.BOTTOM, 0, 10, 6, 90));
if (Theme.getEventType() == 0 || NekoConfig.actionBarDecoration.Int() == 1) {

View File

@ -105,6 +105,19 @@ public class HeaderCell extends LinearLayout {
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
}
public void setTextSize(float dip) {
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, dip);
}
public void setTextColor(int color) {
textView.setTextColor(color);
}
public void setText(CharSequence text) {
textView.setText(text);
}

View File

@ -37,6 +37,7 @@ public class NotificationsCheckCell extends FrameLayout {
private boolean drawLine = true;
private boolean isMultiline;
private int currentHeight;
private boolean animationsEnabled;
public NotificationsCheckCell(Context context) {
this(context, 21, 70, false);
@ -103,7 +104,7 @@ public class NotificationsCheckCell extends FrameLayout {
public void setTextAndValueAndCheck(String text, CharSequence value, boolean checked, int iconType, boolean multiline, boolean divider) {
textView.setText(text);
valueTextView.setText(value);
checkBox.setChecked(checked, iconType, false);
checkBox.setChecked(checked, iconType, animationsEnabled);
valueTextView.setVisibility(VISIBLE);
needDivider = divider;
isMultiline = multiline;
@ -151,4 +152,8 @@ public class NotificationsCheckCell extends FrameLayout {
canvas.drawRect(x, y, x + 2, y + AndroidUtilities.dp(22), Theme.dividerPaint);
}
}
public void setAnimationsEnabled(boolean animationsEnabled) {
this.animationsEnabled = animationsEnabled;
}
}

View File

@ -12,6 +12,7 @@ import android.content.Context;
import android.graphics.Canvas;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.LinearLayout;
import android.widget.TextView;
@ -26,7 +27,7 @@ public class RadioButtonCell extends LinearLayout {
private LinearLayout textLayout;
private TextView textView;
private TextView valueTextView;
public TextView valueTextView;
private RadioButton radioButton;
private boolean needDivider;
@ -83,6 +84,11 @@ public class RadioButtonCell extends LinearLayout {
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
}
public void setTextAndValue(String text, boolean divider, boolean checked) {
textView.setText(text);
valueTextView.setVisibility(GONE);

View File

@ -113,6 +113,9 @@ public class TextCheckCell2 extends FrameLayout {
@Override
public void setEnabled(boolean value) {
super.setEnabled(value);
textView.clearAnimation();
valueTextView.clearAnimation();
checkBox.clearAnimation();
if (value) {
textView.setAlpha(1.0f);
valueTextView.setAlpha(1.0f);
@ -124,6 +127,28 @@ public class TextCheckCell2 extends FrameLayout {
}
}
public void setEnabled(boolean value, boolean animated) {
super.setEnabled(value);
if (animated) {
textView.clearAnimation();
valueTextView.clearAnimation();
checkBox.clearAnimation();
textView.animate().alpha(value ? 1 : .5f).start();
valueTextView.animate().alpha(value ? 1 : .5f).start();
checkBox.animate().alpha(value ? 1 : .5f).start();
} else {
if (value) {
textView.setAlpha(1.0f);
valueTextView.setAlpha(1.0f);
checkBox.setAlpha(1.0f);
} else {
checkBox.setAlpha(0.5f);
textView.setAlpha(0.5f);
valueTextView.setAlpha(0.5f);
}
}
}
public void setChecked(boolean checked) {
checkBox.setChecked(checked, true);
}

View File

@ -57,7 +57,6 @@ import org.telegram.ui.ActionBar.FloatingToolbar;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.ArticleViewer;
import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.LinkPath;
import org.telegram.ui.Components.RecyclerListView;
import org.telegram.ui.RestrictedLanguagesSelectActivity;
@ -1501,7 +1500,7 @@ public abstract class TextSelectionHelper<Cell extends TextSelectionHelper.Selec
}
canvas.drawPath(selectionPath, selectionPaint);
final int R = (int) (cornerRadius * 1.66f);
final float R = cornerRadius * 1.9f;
float startLeft = layout.getPrimaryHorizontal(selectionStart),
endLeft = layout.getPrimaryHorizontal(selectionEnd);
float x, b;

View File

@ -49,7 +49,7 @@ public class UserCell2 extends FrameLayout {
private TLObject currentObject;
private CharSequence currentName;
private CharSequence currrntStatus;
private CharSequence currentStatus;
private int currentId;
private int currentDrawable;
@ -104,7 +104,7 @@ public class UserCell2 extends FrameLayout {
public void setData(TLObject object, CharSequence name, CharSequence status, int resId) {
if (object == null && name == null && status == null) {
currrntStatus = null;
currentStatus = null;
currentName = null;
currentObject = null;
nameTextView.setText("");
@ -112,7 +112,7 @@ public class UserCell2 extends FrameLayout {
avatarImageView.setImageDrawable(null);
return;
}
currrntStatus = status;
currentStatus = status;
currentName = name;
currentObject = object;
currentDrawable = resId;
@ -240,9 +240,13 @@ public class UserCell2 extends FrameLayout {
}
nameTextView.setText(lastName);
}
if (currrntStatus != null) {
if (currentStatus != null) {
statusTextView.setTextColor(statusColor);
statusTextView.setText(currrntStatus);
statusTextView.setText(currentStatus);
if (avatarImageView != null) {
avatarImageView.setForUserOrChat(currentUser, avatarDrawable);
}
} else if (currentUser != null) {
if (currentUser.bot) {
statusTextView.setTextColor(statusColor);

View File

@ -1112,7 +1112,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio
return fragmentView;
}
private void createMenu(View v) {
private boolean createMenu(View v) {
MessageObject message = null;
if (v instanceof ChatMessageCell) {
message = ((ChatMessageCell) v).getMessageObject();
@ -1120,12 +1120,12 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio
message = ((ChatActionCell) v).getMessageObject();
}
if (message == null) {
return;
return false;
}
final int type = getMessageType(message);
selectedObject = message;
if (getParentActivity() == null) {
return;
return false;
}
AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity());
@ -1145,7 +1145,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio
}
if (stickerSet != null) {
showDialog(new StickersAlert(getParentActivity(), ChannelAdminLogActivity.this, stickerSet, null, null));
return;
return true;
}
} else if (selectedObject.currentEvent != null && selectedObject.currentEvent.action instanceof TLRPC.TL_channelAdminLogEventActionChangeHistoryTTL) {
if (ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_DELETE_MESSAGES)) {
@ -1239,7 +1239,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio
}
if (options.isEmpty()) {
return;
return false;
}
final CharSequence[] finalItems = items.toArray(new CharSequence[0]);
builder.setItems(finalItems, (dialogInterface, i) -> {
@ -1251,6 +1251,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio
builder.setTitle(LocaleController.getString("Message", R.string.Message));
showDialog(builder.create());
return true;
}
private String getMessageContent(MessageObject messageObject, int previousUid, boolean name) {
@ -2366,8 +2367,8 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio
}
@Override
public void didLongPress(ChatActionCell cell, float x, float y) {
createMenu(cell);
public boolean didLongPress(ChatActionCell cell, float x, float y) {
return createMenu(cell);
}
@Override
@ -2769,7 +2770,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio
themeDescriptions.add(new ThemeDescription(chatListView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{ChatActionCell.class}, Theme.chat_actionTextPaint, null, null, Theme.key_chat_serviceText));
themeDescriptions.add(new ThemeDescription(chatListView, ThemeDescription.FLAG_LINKCOLOR, new Class[]{ChatActionCell.class}, Theme.chat_actionTextPaint, null, null, Theme.key_chat_serviceLink));
themeDescriptions.add(new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_botCardDrawalbe, Theme.chat_shareIconDrawable, Theme.chat_botInlineDrawable, Theme.chat_botLinkDrawalbe, Theme.chat_goIconDrawable, Theme.chat_commentStickerDrawable}, null, Theme.key_chat_serviceIcon));
themeDescriptions.add(new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_botCardDrawable, Theme.chat_shareIconDrawable, Theme.chat_botInlineDrawable, Theme.chat_botLinkDrawable, Theme.chat_goIconDrawable, Theme.chat_commentStickerDrawable}, null, Theme.key_chat_serviceIcon));
themeDescriptions.add(new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class, ChatActionCell.class}, null, null, null, Theme.key_chat_serviceBackground));
themeDescriptions.add(new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class, ChatActionCell.class}, null, null, null, Theme.key_chat_serviceBackgroundSelected));

File diff suppressed because it is too large Load Diff

View File

@ -932,7 +932,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image
deleteCell.setText(LocaleController.getString("DeleteAndExitButton", R.string.DeleteAndExitButton), false);
}
deleteContainer.addView(deleteCell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
deleteCell.setOnClickListener(v -> AlertsCreator.createClearOrDeleteDialogAlert(ChatEditActivity.this, false, true, false, currentChat, null, false, true, (param) -> {
deleteCell.setOnClickListener(v -> AlertsCreator.createClearOrDeleteDialogAlert(ChatEditActivity.this, false, true, false, currentChat, null, false, true, false, (param) -> {
if (AndroidUtilities.isTablet()) {
getNotificationCenter().postNotificationName(NotificationCenter.closeChats, -chatId);
} else {

View File

@ -104,7 +104,6 @@ public class ChatReactionsEditActivity extends BaseFragment implements Notificat
enableReactionsCell.setTextAndCheck(LocaleController.getString("EnableReactions", R.string.EnableReactions), !chatReactions.isEmpty(), false);
enableReactionsCell.setBackgroundColor(Theme.getColor(enableReactionsCell.isChecked() ? Theme.key_windowBackgroundChecked : Theme.key_windowBackgroundUnchecked));
enableReactionsCell.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
enableReactionsCell.setAnimatingToThumbInsteadOfTouch(true);
enableReactionsCell.setOnClickListener(v -> {
setCheckedEnableReactionCell(!enableReactionsCell.isChecked());
});

View File

@ -23,7 +23,6 @@ import android.text.Spanned;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.util.SparseIntArray;
import android.util.TypedValue;
import android.view.Gravity;
@ -86,7 +85,6 @@ import org.telegram.ui.Components.UndoView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class ChatUsersActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate {
@ -1055,9 +1053,11 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente
}
});
} else if (position == addNew2Row) {
ManageLinksActivity fragment = new ManageLinksActivity(chatId, 0, 0);
fragment.setInfo(info, info.exported_invite);
presentFragment(fragment);
if (info != null) {
ManageLinksActivity fragment = new ManageLinksActivity(chatId, 0, 0);
fragment.setInfo(info, info.exported_invite);
presentFragment(fragment);
}
return;
} else if (position > permissionsSectionRow && position <= changeInfoRow) {
TextCheckCell2 checkCell = (TextCheckCell2) view;
@ -1294,7 +1294,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente
bannedRights.invite_users = true;
bannedRights.change_info = true;
}
ChatRightsEditActivity fragment = new ChatRightsEditActivity(peerId, chatId, adminRights, defaultBannedRights, bannedRights, rank, type == TYPE_ADMIN ? ChatRightsEditActivity.TYPE_ADMIN : ChatRightsEditActivity.TYPE_BANNED, canEdit, participant == null, participant);
ChatRightsEditActivity fragment = new ChatRightsEditActivity(peerId, chatId, adminRights, defaultBannedRights, bannedRights, rank, type == TYPE_ADMIN ? ChatRightsEditActivity.TYPE_ADMIN : ChatRightsEditActivity.TYPE_BANNED, canEdit, participant == null, null, participant);
fragment.setDelegate(new ChatRightsEditActivity.ChatRightsEditActivityDelegate() {
@Override
public void didSetRights(int rights, TLRPC.TL_chatAdminRights rightsAdmin, TLRPC.TL_chatBannedRights rightsBanned, String rank) {
@ -1519,7 +1519,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente
private void openRightsEdit2(long peerId, int date, TLObject participant, TLRPC.TL_chatAdminRights adminRights, TLRPC.TL_chatBannedRights bannedRights, String rank, boolean canEditAdmin, int type, boolean removeFragment) {
boolean[] needShowBulletin = new boolean[1];
final boolean isAdmin = participant instanceof TLRPC.TL_channelParticipantAdmin || participant instanceof TLRPC.TL_chatParticipantAdmin;
ChatRightsEditActivity fragment = new ChatRightsEditActivity(peerId, chatId, adminRights, defaultBannedRights, bannedRights, rank, type, true, false, participant) {
ChatRightsEditActivity fragment = new ChatRightsEditActivity(peerId, chatId, adminRights, defaultBannedRights, bannedRights, rank, type, true, false, null, participant) {
@Override
protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) {
if (!isOpen && backward && needShowBulletin[0] && BulletinFactory.canShowBulletin(ChatUsersActivity.this)) {
@ -1610,7 +1610,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente
}
private void openRightsEdit(long user_id, TLObject participant, TLRPC.TL_chatAdminRights adminRights, TLRPC.TL_chatBannedRights bannedRights, String rank, boolean canEditAdmin, int type, boolean removeFragment) {
ChatRightsEditActivity fragment = new ChatRightsEditActivity(user_id, chatId, adminRights, defaultBannedRights, bannedRights, rank, type, canEditAdmin, participant == null, participant);
ChatRightsEditActivity fragment = new ChatRightsEditActivity(user_id, chatId, adminRights, defaultBannedRights, bannedRights, rank, type, canEditAdmin, participant == null, null, participant);
fragment.setDelegate(new ChatRightsEditActivity.ChatRightsEditActivityDelegate() {
@Override
public void didSetRights(int rights, TLRPC.TL_chatAdminRights rightsAdmin, TLRPC.TL_chatBannedRights rightsBanned, String rank) {
@ -1699,7 +1699,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente
map.remove(peerId);
arrayList.remove(p);
updated = true;
if (type == TYPE_BANNED) {
if (type == TYPE_BANNED && info != null) {
info.kicked_count--;
}
}
@ -1907,7 +1907,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente
builder.setItems(items, icons, (dialogInterface, i) -> {
if (type == TYPE_ADMIN) {
if (i == 0 && items.length == 2) {
ChatRightsEditActivity fragment = new ChatRightsEditActivity(peerId, chatId, adminRights, null, null, rank, ChatRightsEditActivity.TYPE_ADMIN, true, false, participant);
ChatRightsEditActivity fragment = new ChatRightsEditActivity(peerId, chatId, adminRights, null, null, rank, ChatRightsEditActivity.TYPE_ADMIN, true, false, null, participant);
fragment.setDelegate(new ChatRightsEditActivity.ChatRightsEditActivityDelegate() {
@Override
public void didSetRights(int rights, TLRPC.TL_chatAdminRights rightsAdmin, TLRPC.TL_chatBannedRights rightsBanned, String rank) {
@ -1927,13 +1927,13 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente
});
presentFragment(fragment);
} else {
getMessagesController().setUserAdminRole(chatId, getMessagesController().getUser(peerId), new TLRPC.TL_chatAdminRights(), "", !isChannel, ChatUsersActivity.this, false);
getMessagesController().setUserAdminRole(chatId, getMessagesController().getUser(peerId), new TLRPC.TL_chatAdminRights(), "", !isChannel, ChatUsersActivity.this, false, false, null,null);
removeParticipants(peerId);
}
} else if (type == TYPE_BANNED || type == TYPE_KICKED) {
if (i == 0) {
if (type == TYPE_KICKED) {
ChatRightsEditActivity fragment = new ChatRightsEditActivity(peerId, chatId, null, defaultBannedRights, bannedRights, rank, ChatRightsEditActivity.TYPE_BANNED, true, false, participant);
ChatRightsEditActivity fragment = new ChatRightsEditActivity(peerId, chatId, null, defaultBannedRights, bannedRights, rank, ChatRightsEditActivity.TYPE_BANNED, true, false, null, participant);
fragment.setDelegate(new ChatRightsEditActivity.ChatRightsEditActivityDelegate() {
@Override
public void didSetRights(int rights, TLRPC.TL_chatAdminRights rightsAdmin, TLRPC.TL_chatBannedRights rightsBanned, String rank) {

Some files were not shown because too many files have changed in this diff Show More