Update to 8.7.0

This commit is contained in:
xaxtix 2022-04-16 17:43:17 +03:00
parent 0abe4541dd
commit 1e50785b90
306 changed files with 22967 additions and 3965 deletions

View File

@ -300,7 +300,7 @@ android {
}
}
defaultConfig.versionCode = 2600
defaultConfig.versionCode = 2622
applicationVariants.all { variant ->
variant.outputs.all { output ->
@ -319,7 +319,7 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 30
versionName "8.6.2"
versionName "8.7.0"
vectorDrawables.generatedDensities = ['mdpi', 'hdpi', 'xhdpi', 'xxhdpi']

View File

@ -457,6 +457,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,14 @@
#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"
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>();
jclass TrafficStatsClass;
jclass FingerprintClass;

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

@ -104,6 +104,7 @@ NetworkManager::~NetworkManager() {
_portAllocator.reset();
_networkManager.reset();
_socketFactory.reset();
_networkMonitorFactory.reset();
}
void NetworkManager::start() {

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

@ -1514,7 +1514,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) {
@ -3297,7 +3303,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

@ -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,185 @@
#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"
namespace tgcalls {
namespace {
NativeNetworkingImpl::ConnectionDescription::CandidateDescription connectionDescriptionFromCandidate(cricket::Candidate const &candidate) {
NativeNetworkingImpl::ConnectionDescription::CandidateDescription result;
result.type = candidate.type();
result.protocol = candidate.protocol();
result.address = candidate.address().ToString();
return result;
}
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:
};
}
webrtc::CryptoOptions NativeNetworkingImpl::getDefaulCryptoOptions() {
auto options = webrtc::CryptoOptions();
options.srtp.enable_aes128_sha1_80_crypto_cipher = true;
@ -33,6 +206,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)),
@ -45,9 +219,11 @@ _dataChannelMessageReceived(configuration.dataChannelMessageReceived) {
_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 +248,7 @@ NativeNetworkingImpl::~NativeNetworkingImpl() {
_portAllocator.reset();
_networkManager.reset();
_socketFactory.reset();
_networkMonitorFactory.reset();
}
void NativeNetworkingImpl::resetDtlsSrtpTransport() {
@ -88,29 +265,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();
@ -133,7 +312,8 @@ void NativeNetworkingImpl::resetDtlsSrtpTransport() {
_portAllocator->SetConfiguration(stunServers, turnServers, 2, 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;
@ -153,7 +333,9 @@ void NativeNetworkingImpl::resetDtlsSrtpTransport() {
_transportChannel->SignalCandidateGathered.connect(this, &NativeNetworkingImpl::candidateGathered);
_transportChannel->SignalIceTransportStateChanged.connect(this, &NativeNetworkingImpl::transportStateChanged);
_transportChannel->SignalCandidatePairChanged.connect(this, &NativeNetworkingImpl::candidatePairChanged);
_transportChannel->SignalReadPacket.connect(this, &NativeNetworkingImpl::transportPacketReceived);
_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
));
_lastNetworkActivityMs = 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);
@ -289,10 +475,8 @@ void NativeNetworkingImpl::checkConnectionTimeout() {
const int64_t maxTimeout = 20000;
if (strong->_lastNetworkActivityMs + maxTimeout < currentTimestamp) {
NativeNetworkingImpl::State emitState;
emitState.isReadyToSendData = false;
emitState.isFailed = true;
strong->_stateUpdated(emitState);
strong->_isFailed = true;
strong->notifyStateUpdated();
}
strong->checkConnectionTimeout();
@ -347,6 +531,46 @@ void NativeNetworkingImpl::transportPacketReceived(rtc::PacketTransportInternal
assert(_threads->getNetworkThread()->IsCurrent());
_lastNetworkActivityMs = rtc::TimeMillis();
_isFailed = false;
}
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();
}
}
}
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) {
@ -382,9 +606,7 @@ void NativeNetworkingImpl::UpdateAggregateStates_n() {
if (_isConnected != isConnected) {
_isConnected = isConnected;
NativeNetworkingImpl::State emitState;
emitState.isReadyToSendData = isConnected;
_stateUpdated(emitState);
notifyStateUpdated();
if (_dataChannelInterface) {
_dataChannelInterface->updateIsConnected(isConnected);
@ -392,6 +614,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;
@ -97,6 +169,8 @@ private:
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;
bool _isFailed = false;
int64_t _lastNetworkActivityMs = 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;
}
}
@ -317,18 +377,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 +385,38 @@ 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 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;
}
@ -378,39 +433,61 @@ absl::optional<InitialSetupMessage> InitialSetupMessage_parse(json11::Json::obje
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;
}
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 +506,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 +545,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 +560,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 +659,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 +668,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 +677,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 +686,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 +696,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 +705,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 +715,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 +727,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 +744,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 +755,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 +789,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 +798,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,83 @@ 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;
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 +175,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

@ -86,6 +86,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;
@ -210,6 +212,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 {
@ -403,6 +406,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;
@ -2423,11 +2455,11 @@ public class AndroidUtilities {
}
public static void appCenterLog(Throwable e) {
}
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) {
@ -3954,26 +3986,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);
@ -3984,10 +4028,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) {
@ -4207,7 +4264,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

@ -150,6 +150,7 @@ public class ApplicationLoader extends Application {
}
SharedConfig.loadConfig();
SharedPrefsHelper.init(applicationContext);
for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) { //TODO improve account
UserConfig.getInstance(a).loadConfig();
MessagesController.getInstance(a);

View File

@ -20,8 +20,8 @@ public class BuildVars {
public static boolean USE_CLOUD_STRINGS = true;
public static boolean CHECK_UPDATES = true;
public static boolean NO_SCOPED_STORAGE = Build.VERSION.SDK_INT <= 29;
public static int BUILD_VERSION = 2600;
public static String BUILD_VERSION_STRING = "8.6.2";
public static int BUILD_VERSION = 2622;
public static String BUILD_VERSION_STRING = "8.7.0";
public static int APP_ID = 4;
public static String APP_HASH = "014b35b6184100b085b0d0572f9b5103";

View File

@ -127,6 +127,7 @@ public class ChatThemeController extends BaseController {
private static SharedPreferences getSharedPreferences() {
return ApplicationLoader.applicationContext.getSharedPreferences("chatthemeconfig", Context.MODE_PRIVATE);
}
private static SharedPreferences getEmojiSharedPreferences() {
return ApplicationLoader.applicationContext.getSharedPreferences("chatthemeconfig_emoji", Context.MODE_PRIVATE);
}

View File

@ -1107,16 +1107,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;
}
}
@ -1182,51 +1181,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) {
@ -1264,7 +1264,7 @@ public class DownloadController extends BaseController implements NotificationCe
});
}
Runnable clearUnviewedDownloadsRunnbale = new Runnable() {
Runnable clearUnviewedDownloadsRunnale = new Runnable() {
@Override
public void run() {
clearUnviewedDownloads();
@ -1273,8 +1273,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() {

View File

@ -31,7 +31,7 @@ public class FileLoader extends BaseController {
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);
}
@ -42,6 +42,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;
@ -707,14 +710,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 (delegate != 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

@ -1905,12 +1905,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);
@ -2052,14 +2056,20 @@ public class ImageLoader {
}
}
File publicMediaDir = null;
if (Build.VERSION.SDK_INT >= 30) {
File newPath = ApplicationLoader.applicationContext.getExternalFilesDir(null);
File newPath;
try {
if (ApplicationLoader.applicationContext.getExternalMediaDirs().length > 0) {
publicMediaDir = ApplicationLoader.applicationContext.getExternalMediaDirs()[0];
publicMediaDir = new File(publicMediaDir, "Telegram");
publicMediaDir.mkdirs();
}
} catch (Exception e) {
FileLog.e(e);
}
newPath = ApplicationLoader.applicationContext.getExternalFilesDir(null);
telegramPath = new File(newPath, "Telegram");
// File oldPath = new File(path, "Telegram");
// long moveStart = System.currentTimeMillis();
// moveDirectory(oldPath, telegramPath);
// long dt = System.currentTimeMillis() - moveStart;
// FileLog.d("move time = " + dt);
} else {
telegramPath = new File(path, "Telegram");
}
@ -2133,6 +2143,33 @@ public class ImageLoader {
FileLog.e(e);
}
}
if (publicMediaDir != null && publicMediaDir.isDirectory()) {
try {
File imagePath = new File(publicMediaDir, "Telegram Images");
imagePath.mkdir();
if (imagePath.isDirectory() && canMoveFiles(cachePath, imagePath, FileLoader.MEDIA_DIR_IMAGE)) {
mediaDirs.put(FileLoader.MEDIA_DIR_IMAGE_PUBLIC, imagePath);
if (BuildVars.LOGS_ENABLED) {
FileLog.d("image path = " + imagePath);
}
}
} catch (Exception e) {
FileLog.e(e);
}
try {
File videoPath = new File(publicMediaDir, "Telegram Video");
videoPath.mkdir();
if (videoPath.isDirectory() && canMoveFiles(cachePath, videoPath, FileLoader.MEDIA_DIR_VIDEO)) {
mediaDirs.put(FileLoader.MEDIA_DIR_VIDEO_PUBLIC, videoPath);
if (BuildVars.LOGS_ENABLED) {
FileLog.d("video path = " + videoPath);
}
}
} catch (Exception e) {
FileLog.e(e);
}
}
} else {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("this Android can't rename files");
@ -3844,6 +3881,10 @@ public class ImageLoader {
return null;
}
public DispatchQueue getCacheOutQueue() {
return cacheOutQueue;
}
public static class MessageThumb {
BitmapDrawable drawable;
String key;

View File

@ -1552,11 +1552,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();
@ -2043,7 +2044,6 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
}
public AnimatedFileDrawable getAnimation() {
AnimatedFileDrawable animatedFileDrawable;
if (currentMediaDrawable instanceof AnimatedFileDrawable) {
return (AnimatedFileDrawable) currentMediaDrawable;
} else if (currentImageDrawable instanceof AnimatedFileDrawable) {
@ -2057,7 +2057,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

@ -13,18 +13,40 @@ public class LanguageDetector {
}
public static void detectLanguage(String text, StringCallback onSuccess, ExceptionCallback onFail) {
detectLanguage(text, onSuccess, onFail, false);
}
public static void detectLanguage(String text, StringCallback onSuccess, ExceptionCallback onFail, boolean initializeFirst) {
try {
if (initializeFirst) {
com.google.mlkit.common.sdkinternal.MlKitContext.zza(ApplicationLoader.applicationContext);
}
com.google.mlkit.nl.languageid.LanguageIdentification.getClient()
.identifyLanguage(text)
.addOnSuccessListener(str -> {
.identifyLanguage(text)
.addOnSuccessListener(str -> {
if (onSuccess != null) {
onSuccess.run(str);
})
.addOnFailureListener(e -> {
}
})
.addOnFailureListener(e -> {
if (onFail != null) {
onFail.run(e);
});
}
});
} catch (IllegalStateException e) {
if (!initializeFirst) {
detectLanguage(text, onSuccess, onFail, true);
} else if (onFail != null) {
onFail.run(e);
}
} catch (Exception e) {
FileLog.e(e);
onFail.run(e);
if (onFail != null) {
onFail.run(e);
}
} catch (Throwable t) {
if (onFail != null) {
onFail.run(null);
}
}
}
}

View File

@ -948,13 +948,20 @@ public class LocaleController {
reloadLastFile = false;
}
if (!isLoadingRemote) {
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() {
@ -1085,6 +1092,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);
}
@ -1121,15 +1136,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

@ -231,6 +231,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 {
@ -4082,7 +4109,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);
@ -4831,7 +4858,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;
}
@ -4929,12 +4956,12 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
minCompressFactor = 1f;
} else if (Math.min(height, width) >= 720) {
maxBitrate = 2600_000;
compressFactor = 0.8f;
minCompressFactor = 0.8f;
compressFactor = 1f;
minCompressFactor = 1f;
} 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

@ -34,6 +34,8 @@ import android.text.TextUtils;
import android.text.style.CharacterStyle;
import android.util.SparseArray;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.collection.LongSparseArray;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
@ -43,6 +45,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;
@ -76,6 +80,14 @@ import java.util.regex.Pattern;
@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("\\|\\|(.+?)\\|\\|"),
@ -145,6 +157,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;
@ -155,6 +168,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<>();
@ -178,6 +196,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<>();
@ -205,6 +224,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++) {
@ -282,6 +302,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;
}
@ -866,6 +988,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);
}
@ -2640,9 +2792,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;
@ -4294,7 +4446,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) {
@ -4331,7 +4488,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);
@ -4643,7 +4823,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;
@ -5482,6 +5662,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 ----------------
@ -5491,6 +5726,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;
@ -205,6 +206,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 ",
@ -2532,7 +2537,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) {
@ -3058,6 +3067,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);
@ -5513,7 +5525,7 @@ public class MessageObject {
}
public boolean isMusic() {
return isMusicMessage(messageOwner);
return isMusicMessage(messageOwner) && !isVideo();
}
public boolean isDocument() {
@ -6399,4 +6411,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 < MessagesController.getInstance(currentAccount).ringtoneDurationMax * 2) {
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;
@ -88,6 +92,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<>();
@ -97,13 +103,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<>();
public LongSparseArray<MessageObject> dialogMessagesByRandomIds = new LongSparseArray<>();
public LongSparseIntArray deletedHistory = new LongSparseIntArray();
@ -332,6 +332,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;
@ -835,6 +837,9 @@ public class MessagesController extends BaseController implements NotificationCe
groupCallVideoMaxParticipants = mainPreferences.getInt("groipCallVideoMaxParticipants", 30);
chatReadMarkSizeThreshold = mainPreferences.getInt("chatReadMarkSizeThreshold", 50);
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);
@ -1900,6 +1905,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) {
@ -2665,6 +2692,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();
@ -4183,7 +4212,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;
}
@ -4194,28 +4227,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);
}
@ -4939,6 +4993,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);
@ -5221,6 +5277,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;
@ -5228,6 +5285,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();
@ -6585,6 +6645,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;
@ -7119,6 +7183,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;
@ -8148,6 +8213,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);
@ -9398,6 +9464,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;
}
@ -9449,6 +9519,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) {
@ -9711,6 +9790,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);
@ -12996,6 +13076,16 @@ 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_updateReadChannelDiscussionInbox) {
if (updatesOnMainThread == null) {
updatesOnMainThread = new ArrayList<>();
@ -13016,6 +13106,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) {
@ -13292,6 +13387,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);
@ -13310,6 +13406,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);
@ -13321,11 +13418,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) {
@ -13346,6 +13444,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();
}
@ -13617,6 +13716,11 @@ 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_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);
@ -13671,6 +13775,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) {
@ -14063,7 +14169,6 @@ public class MessagesController extends BaseController implements NotificationCe
if (reactionsMentionsMessageIds.get(messageId) != hasUnreadReaction) {
newUnreadCount += hasUnreadReaction ? 1 : -1;
changed = true;
}
} else {
needReload = true;
@ -14086,13 +14191,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) {
@ -14128,6 +14234,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) {
@ -14431,6 +14541,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++) {
@ -14591,6 +14703,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++) {
@ -14657,8 +14771,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);
@ -14677,7 +14799,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);
@ -15008,9 +15137,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);
@ -15034,9 +15163,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);
}
}
@ -15106,6 +15235,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

@ -82,7 +82,7 @@ public class MessagesStorage extends BaseController {
private CountDownLatch openSync = new CountDownLatch(1);
private static volatile MessagesStorage[] Instance = new MessagesStorage[UserConfig.MAX_ACCOUNT_COUNT];
private final static int LAST_DB_VERSION = 92;
private final static int LAST_DB_VERSION = 93;
private boolean databaseMigrationInProgress;
public boolean showClearDatabaseAlert;
@ -394,6 +394,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 {
@ -1560,6 +1562,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;
@ -2081,6 +2088,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();
@ -2156,6 +2164,7 @@ public class MessagesStorage extends BaseController {
} finally {
AndroidUtilities.runOnUIThread(() -> {
NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.didClearDatabase);
getMediaDataController().loadAttachMenuBots(false, true);
});
}
});
@ -3354,7 +3363,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;
}
@ -3366,7 +3375,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) {
@ -3533,7 +3547,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);
@ -7446,7 +7460,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);
@ -11540,7 +11554,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<>();

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++;
@ -233,10 +234,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++;
private SparseArray<ArrayList<NotificationCenterDelegate>> observers = new SparseArray<>();
private SparseArray<ArrayList<NotificationCenterDelegate>> removeAfterBroadcast = new SparseArray<>();

View File

@ -42,6 +42,10 @@ import android.os.Build;
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 androidx.collection.LongSparseArray;
import androidx.core.app.NotificationCompat;
@ -53,9 +57,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;
@ -131,6 +132,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);
@ -240,6 +244,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();
@ -3062,6 +3100,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";
@ -3080,6 +3122,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;
@ -3163,66 +3206,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) {
@ -3334,7 +3317,7 @@ public class NotificationsController extends BaseController {
if (sound != null) {
notificationChannel.setSound(sound, builder.build());
} else {
notificationChannel.setSound(null, builder.build());
notificationChannel.setSound(null, null);
}
if (BuildVars.LOGS_ENABLED) {
FileLog.d("create new channel " + channelId);
@ -3390,6 +3373,7 @@ public class NotificationsController extends BaseController {
boolean notifyDisabled = false;
int vibrate = 0;
String soundPath = null;
boolean isInternalSoundFile = false;
int ledColor = 0xff0000ff;
int importance = 0;
@ -3541,6 +3525,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;
@ -3548,13 +3536,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 {
@ -3570,20 +3565,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);
@ -3593,7 +3606,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;
}
@ -3760,10 +3774,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)) {
@ -3920,7 +3939,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));
@ -4586,6 +4604,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();
@ -4677,7 +4696,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) -> {
@ -4693,18 +4733,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) -> {
@ -4758,4 +4831,26 @@ public class NotificationsController extends BaseController {
return "EnableChannel2";
}
}
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

@ -1934,6 +1934,16 @@ public class SendMessagesHelper extends BaseController implements NotificationCe
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;
@ -2746,6 +2756,27 @@ public class SendMessagesHelper extends BaseController implements NotificationCe
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;
@ -2770,26 +2801,8 @@ public class SendMessagesHelper extends BaseController implements NotificationCe
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;
@ -2806,10 +2819,6 @@ public class SendMessagesHelper extends BaseController implements NotificationCe
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) {
@ -7868,7 +7877,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe
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

@ -806,7 +806,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,36 @@
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) {
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,318 @@
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/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);
}
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, null, 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

@ -330,6 +330,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);
@ -681,7 +687,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;

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.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

@ -108,7 +108,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;
@ -124,9 +123,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;
@ -2332,11 +2335,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);
@ -3362,10 +3365,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

@ -14,7 +14,10 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.google.android.exoplayer2.util.Log;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.R;
import org.telegram.ui.Adapters.FiltersView;
import org.telegram.ui.Components.RLottieDrawable;
@ -91,7 +94,15 @@ public class ActionBarMenu extends LinearLayout {
}
public ActionBarMenuItem addItem(int id, int icon, CharSequence text, int backgroundColor, Drawable drawable, int width, CharSequence title, Theme.ResourcesProvider resourcesProvider) {
ActionBarMenuItem menuItem = new ActionBarMenuItem(getContext(), this, backgroundColor, isActionMode ? parentActionBar.itemsActionModeColor : parentActionBar.itemsColor, text != null, resourcesProvider);
ActionBarMenuItem menuItem = new ActionBarMenuItem(getContext(), this, backgroundColor, isActionMode ? parentActionBar.itemsActionModeColor : parentActionBar.itemsColor, text != null, resourcesProvider) {
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
if (icon == R.drawable.ic_ab_other && (id == 0 || id == 14)) {
Log.d("kek", id + " " + (View.VISIBLE == visibility));
}
}
};
menuItem.setTag(id);
if (text != null) {
menuItem.textView.setText(text);

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;
@ -169,6 +170,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);
}
@ -337,7 +340,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()) {
@ -354,6 +357,14 @@ public class ActionBarMenuItem extends FrameLayout {
popupWindow.dismiss();
}
});
if (popupLayout.getSwipeBack() != null) {
popupLayout.getSwipeBack().setOnClickListener(view -> {
if (popupWindow != null) {
popupWindow.dismiss();
}
});
}
}
public void removeAllSubItems() {
@ -511,6 +522,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();
@ -579,6 +619,9 @@ public class ActionBarMenuItem extends FrameLayout {
}
public ActionBarPopupWindow.ActionBarPopupWindowLayout getPopupLayout() {
if (popupLayout == null) {
createPopupLayout();
}
return popupLayout;
}
@ -613,7 +656,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);
}
};
@ -629,6 +676,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) {
@ -673,6 +721,9 @@ public class ActionBarMenuItem extends FrameLayout {
updateOrShowPopup(true, false);
}
popupLayout.updateRadialSelectors();
if (popupLayout.getSwipeBack() != null) {
popupLayout.getSwipeBack().closeForeground(false);
}
popupWindow.startAnimation();
}
public void toggleSubMenu() {
@ -1389,6 +1440,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()) &&
@ -1853,4 +1913,27 @@ public class ActionBarMenuItem extends FrameLayout {
return color != null ? color : Theme.getColor(key);
}
}
public View addColoredGap() {
createPopupLayout();
View 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,13 +16,13 @@ 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;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@ -36,8 +36,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;
@ -90,6 +88,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;
@ -114,6 +114,7 @@ public class ActionBarPopupWindow extends PopupWindow {
private boolean fitItems;
private final Theme.ResourcesProvider resourcesProvider;
private View topView;
public ActionBarPopupWindowLayout(Context context) {
this(context, null);
@ -130,14 +131,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) {
@ -171,11 +174,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) {
@ -213,7 +219,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;
}
@ -234,7 +240,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.MULTIPLY));
}
}
@ -265,12 +271,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);
@ -371,20 +377,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);
@ -407,7 +443,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();
}
}
@ -485,6 +547,10 @@ public class ActionBarPopupWindow extends PopupWindow {
public int getVisibleHeight() {
return (int) (getMeasuredHeight() * backScaleY);
}
public void setTopView(View topView) {
this.topView = topView;
}
}
public ActionBarPopupWindow() {
@ -582,6 +648,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 {
@ -630,9 +716,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);
windowAnimatorSet.addListener(new AnimatorListenerAdapter() {
@ -653,6 +744,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);
}
}
@ -690,6 +784,7 @@ public class ActionBarPopupWindow extends PopupWindow {
public void dismiss(boolean animated) {
setFocusable(false);
dismissDim();
if (windowAnimatorSet != null) {
if (animated && isClosingAnimated) {
return;
@ -766,4 +861,20 @@ public class ActionBarPopupWindow extends PopupWindow {
public interface onSizeChangedListener {
void onSizeChanged();
}
public static class GapView extends FrameLayout {
Paint paint = new Paint();
String colorKey;
public GapView(Context context, String colorKey) {
super(context);
this.colorKey = colorKey;
}
@Override
protected void onDraw(Canvas canvas) {
paint.setColor(Theme.getColor(colorKey));
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint);
}
}
}

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;
@ -867,7 +868,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;
@ -1375,6 +1376,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

@ -772,10 +772,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

@ -23,6 +23,7 @@ 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;
@ -55,6 +56,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;
@ -70,8 +72,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;
@ -576,7 +578,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));
@ -600,29 +678,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) {
@ -1035,7 +1095,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() {
@ -1152,7 +1212,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(
@ -1267,7 +1327,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);
@ -1333,7 +1393,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) {
@ -1509,6 +1569,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() {
@ -1570,13 +1637,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

@ -1529,6 +1529,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);
@ -1539,24 +1547,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;
}
@ -1572,6 +1583,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) {
@ -2817,6 +2834,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;
@ -2936,9 +2954,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;
@ -3432,6 +3452,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";
@ -3782,6 +3803,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";
@ -4289,6 +4312,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);
@ -4729,6 +4753,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);
@ -5771,6 +5796,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);
}
@ -5861,6 +5892,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);
@ -6034,8 +6082,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);
@ -6051,6 +6099,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) {
@ -6058,13 +6107,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);
}
@ -8046,7 +8105,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();
@ -8368,9 +8427,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);
@ -8540,9 +8601,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);
@ -8685,7 +8748,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);
@ -8808,6 +8873,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));
@ -8841,7 +8907,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);
@ -9084,7 +9152,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;
@ -9101,7 +9171,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;
@ -131,9 +129,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) {
@ -231,18 +226,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;
}
@ -432,10 +427,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);
@ -444,18 +438,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;
}
@ -481,7 +482,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);
@ -499,7 +500,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);
@ -601,7 +602,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 -> {
@ -736,23 +737,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.MULTIPLY));
drawable2.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_changephoneinfo_image2), PorterDuff.Mode.MULTIPLY));
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;
}
}
@ -765,11 +772,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) {
@ -921,4 +923,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

@ -59,6 +59,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;
@ -932,25 +940,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) {
@ -960,7 +968,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);
@ -989,7 +996,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;
@ -1005,7 +1012,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();
@ -1127,7 +1134,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);
@ -1197,25 +1204,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);
@ -1233,15 +1240,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();
@ -1257,11 +1264,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) {
@ -1269,34 +1276,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

@ -19,6 +19,8 @@ import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MessagesController;
import org.telegram.messenger.R;
import org.telegram.messenger.UserConfig;
import org.telegram.ui.ActionBar.ActionBarLayout;
import org.telegram.ui.ActionBar.DrawerLayoutContainer;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Cells.DrawerActionCell;
import org.telegram.ui.Cells.DividerCell;
@ -37,6 +39,7 @@ import androidx.recyclerview.widget.RecyclerView;
public class DrawerLayoutAdapter extends RecyclerListView.SelectionAdapter {
private Context mContext;
private DrawerLayoutContainer mDrawerLayoutContainer;
private ArrayList<Item> items = new ArrayList<>(11);
private ArrayList<Integer> accountNumbers = new ArrayList<>();
private boolean accountsShown;
@ -44,8 +47,9 @@ public class DrawerLayoutAdapter extends RecyclerListView.SelectionAdapter {
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 = UserConfig.getActivatedAccountsCount() > 1 && MessagesController.getGlobalMainSettings().getBoolean("accountsShown", true);
Theme.createCommonDialogResources(context);
@ -116,7 +120,7 @@ public class DrawerLayoutAdapter extends RecyclerListView.SelectionAdapter {
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

@ -149,6 +149,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;
@ -274,7 +275,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;
@ -380,6 +382,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;
@ -392,7 +396,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)) {
@ -421,7 +427,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);
}
@ -1084,8 +1104,8 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
if (checkingForLongPress && windowView != null) {
checkingForLongPress = false;
if (pressedLink != null) {
windowView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
showCopyPopup(pressedLink.getUrl());
windowView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
showCopyPopup(pressedLink.getSpan().getUrl());
pressedLink = null;
pressedLinkOwnerLayout = null;
if (pressedLinkOwnerView != null) {
@ -1098,10 +1118,10 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
textSelectionHelper.trySelect(pressedLinkOwnerView);
}
if (textSelectionHelper.isSelectionMode()) {
windowView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
windowView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
}
} else if (pressedLinkOwnerLayout != null && pressedLinkOwnerView != null) {
windowView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
windowView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);;
int[] location = new int[2];
pressedLinkOwnerView.getLocationInWindow(location);
@ -1150,16 +1170,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);
@ -1228,6 +1248,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
AndroidUtilities.addToClipboard(url);
}
});
builder.setOnPreDismissListener(di -> links.clear());
BottomSheet sheet = builder.create();
showDialog(sheet);
}
@ -2577,26 +2598,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;
@ -2616,7 +2617,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) {
@ -2630,25 +2630,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);
@ -2663,7 +2670,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();
@ -2697,7 +2704,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
anchor = null;
}
if (!isAnchor) {
openWebpageUrl(pressedLink.getUrl(), anchor);
openWebpageUrl(pressedLink.getSpan().getUrl(), anchor);
}
}
}
@ -2726,6 +2733,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
return;
}
View parentView = pressedLinkOwnerView;
links.clear();
pressedLink = null;
pressedLinkOwnerLayout = null;
pressedLinkOwnerView = null;
@ -6189,14 +6197,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) {
@ -6555,7 +6563,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) {
@ -6564,7 +6572,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) {
@ -6573,7 +6581,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) {
@ -6886,14 +6894,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);
@ -6902,14 +6910,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();
}
}
@ -6998,7 +7006,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) {
@ -7129,7 +7137,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;
}
@ -7430,14 +7438,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) {
@ -7715,7 +7723,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) {
@ -8319,14 +8327,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) {
@ -8579,14 +8587,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();
}
}
@ -8803,14 +8811,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();
}
}
@ -9039,14 +9047,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();
}
}
@ -9155,7 +9163,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();
}
@ -9262,7 +9270,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();
}
}
@ -9398,12 +9406,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) {
@ -9476,7 +9484,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();
}
}
@ -9573,7 +9581,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();
}
}
@ -9662,14 +9670,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();
}
}
@ -9773,14 +9781,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) {
@ -10074,14 +10082,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) {
@ -10369,14 +10377,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) {
@ -10580,7 +10588,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
if (currentType == 0) {
drawTextSelection(canvas, this);
}
textLayout.draw(canvas);
textLayout.draw(canvas, this);
canvas.restore();
}
}
@ -10683,7 +10691,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();
}
}
@ -10765,7 +10773,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();
}
}
@ -10847,7 +10855,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();
}
}
@ -10926,7 +10934,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) {
@ -11007,7 +11015,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();
@ -11119,7 +11127,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

@ -121,10 +121,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;
}
@ -320,6 +322,19 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe
if (file != null) {
Utilities.clearDir(file.getAbsolutePath(), documentsMusicType, Long.MAX_VALUE, false);
}
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;
@ -334,8 +349,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);
@ -384,8 +401,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,13 +27,19 @@ 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.StaticLayout;
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.ViewConfiguration;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
@ -81,6 +87,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;
@ -418,7 +425,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();
}
@ -440,10 +447,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) {
@ -467,11 +476,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);
}
};
@ -507,8 +561,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();
@ -521,15 +575,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);
@ -771,9 +824,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;
@ -54,6 +55,7 @@ import org.telegram.ui.Components.AlertsCreator;
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;
@ -72,7 +74,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);
@ -122,7 +125,7 @@ public class AboutLinkCell extends FrameLayout {
}
} else if (pressedLink != null) {
try {
onLinkClick(pressedLink);
onLinkClick((ClickableSpan) pressedLink.getSpan());
} catch (Exception e) {
FileLog.e(e);
}
@ -136,6 +139,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);
@ -257,15 +261,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) {
@ -324,10 +325,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();
}
@ -371,15 +370,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) -> {
@ -398,8 +401,10 @@ public class AboutLinkCell extends FrameLayout {
}
}
});
builder.setOnPreDismissListener(di -> resetPressedLink());
builder.show();
resetPressedLink();
pressedLink = null;
}
}
};
@ -417,16 +422,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 {
@ -599,7 +601,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;
@ -284,6 +286,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) {
}
@ -425,6 +430,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 {
@ -635,12 +641,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();
@ -1084,47 +1090,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;
}
@ -1186,70 +1183,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;
}
@ -1293,16 +1294,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;
@ -1312,7 +1317,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;
}
@ -1354,17 +1359,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;
@ -1382,10 +1391,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 {
@ -1435,18 +1444,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;
@ -1530,10 +1543,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 {
@ -1574,7 +1587,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) {
@ -2419,7 +2433,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) {
@ -2556,7 +2578,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;
@ -3547,8 +3573,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;
@ -6545,7 +6572,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);
@ -6553,6 +6581,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) {}
}
}
}
}
@ -6740,6 +6778,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)) {
@ -6790,18 +6832,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) {
@ -6915,7 +6962,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();
@ -7081,6 +7128,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;
@ -7113,16 +7170,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()) {
@ -7224,7 +7271,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;
@ -7257,7 +7304,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (start == -1) {
if (!urlPathSelection.isEmpty()) {
linkSelectionBlockNum = -1;
resetUrlPaths(true);
resetUrlPaths();
invalidate();
}
return;
@ -7271,9 +7318,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) {
@ -7285,16 +7332,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) {
@ -8569,7 +8616,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;
@ -8772,9 +8819,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)) {
@ -8951,8 +8998,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);
@ -8963,9 +9020,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);
@ -9071,9 +9128,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) {
@ -10685,24 +10742,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;
}
}
}
}
@ -11119,7 +11193,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();
@ -11998,6 +12076,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;
@ -12013,6 +12092,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));
@ -12031,8 +12111,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) {
@ -12157,8 +12242,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;
}
@ -12168,6 +12254,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));
@ -12179,19 +12266,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()) {
@ -12214,7 +12302,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));
@ -12604,7 +12693,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()) {
@ -12660,10 +12749,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++) {
@ -15473,6 +15560,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;
@ -15671,6 +15761,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() {
@ -15897,6 +15992,12 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
}
if (replyNameLayout != null && replyStartX != lastDrawReplyY && lastDrawReplyY != 0) {
animateReplyY = true;
animateFromReplyY = lastDrawReplyY;
changed = true;
}
return changed;
}
@ -15959,6 +16060,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

@ -264,6 +264,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;
@ -525,6 +526,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) {
@ -535,7 +537,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) {
@ -1012,7 +1016,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;
@ -2092,6 +2128,9 @@ public class DialogCell extends BaseCell {
} else {
drawPin = false;
}
if (dialogsType == 2) {
drawPin = false;
}
if (mask != 0) {
boolean continueUpdate = false;
@ -2272,7 +2311,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);
@ -2769,7 +2808,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) {
@ -2818,8 +2857,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

@ -41,6 +41,9 @@ import org.telegram.messenger.UserObject;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.R;
import org.telegram.tgnet.TLRPC;
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;
@ -49,6 +52,7 @@ import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Components.RLottieDrawable;
import org.telegram.ui.Components.RLottieImageView;
import org.telegram.ui.Components.SnowflakesEffect;
import org.telegram.ui.ThemeActivity;
public class DrawerProfileCell extends FrameLayout {
@ -71,7 +75,7 @@ public class DrawerProfileCell extends FrameLayout {
private int darkThemeBackgroundColor;
public static boolean switchingTheme;
public DrawerProfileCell(Context context) {
public DrawerProfileCell(Context context, DrawerLayoutContainer drawerLayoutContainer) {
super(context);
shadowView = new ImageView(context);
@ -127,6 +131,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);
@ -180,6 +185,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, 0, 6, 90));
if (Theme.getEventType() == 0) {

View File

@ -93,6 +93,14 @@ public class HeaderCell extends FrameLayout {
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

@ -26,7 +26,7 @@ import org.telegram.ui.Components.RadioButton;
public class RadioButtonCell extends FrameLayout {
private TextView textView;
private TextView valueTextView;
public TextView valueTextView;
private RadioButton radioButton;
private boolean needDivider;

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

@ -55,7 +55,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;
@ -1473,7 +1472,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

@ -19,7 +19,6 @@ import android.widget.ImageView;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ChatObject;
import org.telegram.messenger.ImageLocation;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MessagesController;
import org.telegram.messenger.R;
@ -49,7 +48,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 +103,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 +111,7 @@ public class UserCell2 extends FrameLayout {
avatarImageView.setImageDrawable(null);
return;
}
currrntStatus = status;
currentStatus = status;
currentName = name;
currentObject = object;
currentDrawable = resId;
@ -240,9 +239,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

@ -1107,7 +1107,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();
@ -1115,12 +1115,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());
@ -1140,7 +1140,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)) {
@ -1234,7 +1234,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) -> {
@ -1246,6 +1246,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) {
@ -2361,8 +2362,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
@ -2764,7 +2765,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

@ -929,7 +929,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 {
@ -1045,9 +1043,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;
@ -1257,7 +1257,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);
ChatRightsEditActivity fragment = new ChatRightsEditActivity(peerId, chatId, adminRights, defaultBannedRights, bannedRights, rank, type == TYPE_ADMIN ? ChatRightsEditActivity.TYPE_ADMIN : ChatRightsEditActivity.TYPE_BANNED, canEdit, participant == null, null);
fragment.setDelegate(new ChatRightsEditActivity.ChatRightsEditActivityDelegate() {
@Override
public void didSetRights(int rights, TLRPC.TL_chatAdminRights rightsAdmin, TLRPC.TL_chatBannedRights rightsBanned, String rank) {
@ -1482,7 +1482,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) {
ChatRightsEditActivity fragment = new ChatRightsEditActivity(peerId, chatId, adminRights, defaultBannedRights, bannedRights, rank, type, true, false, null) {
@Override
protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) {
if (!isOpen && backward && needShowBulletin[0] && BulletinFactory.canShowBulletin(ChatUsersActivity.this)) {
@ -1573,7 +1573,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);
ChatRightsEditActivity fragment = new ChatRightsEditActivity(user_id, chatId, adminRights, defaultBannedRights, bannedRights, rank, type, canEditAdmin, participant == null, null);
fragment.setDelegate(new ChatRightsEditActivity.ChatRightsEditActivityDelegate() {
@Override
public void didSetRights(int rights, TLRPC.TL_chatAdminRights rightsAdmin, TLRPC.TL_chatBannedRights rightsBanned, String rank) {
@ -1662,7 +1662,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--;
}
}
@ -1870,7 +1870,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);
ChatRightsEditActivity fragment = new ChatRightsEditActivity(peerId, chatId, adminRights, null, null, rank, ChatRightsEditActivity.TYPE_ADMIN, true, false, null);
fragment.setDelegate(new ChatRightsEditActivity.ChatRightsEditActivityDelegate() {
@Override
public void didSetRights(int rights, TLRPC.TL_chatAdminRights rightsAdmin, TLRPC.TL_chatBannedRights rightsBanned, String rank) {
@ -1890,13 +1890,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);
ChatRightsEditActivity fragment = new ChatRightsEditActivity(peerId, chatId, null, defaultBannedRights, bannedRights, rank, ChatRightsEditActivity.TYPE_BANNED, true, false, null);
fragment.setDelegate(new ChatRightsEditActivity.ChatRightsEditActivityDelegate() {
@Override
public void didSetRights(int rights, TLRPC.TL_chatAdminRights rightsAdmin, TLRPC.TL_chatBannedRights rightsBanned, String rank) {

View File

@ -42,7 +42,6 @@ import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.style.URLSpan;
import android.util.Base64;
import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.Gravity;
@ -60,7 +59,9 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.RawRes;
import androidx.annotation.RequiresApi;
import androidx.core.util.Consumer;
import org.telegram.messenger.AccountInstance;
import org.telegram.messenger.AndroidUtilities;
@ -119,6 +120,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
public class AlertsCreator {
public final static int PERMISSIONS_REQUEST_TOP_ICON_SIZE = 72;
@ -168,6 +170,48 @@ public class AlertsCreator {
.create();
}
public static Dialog createWebViewPermissionsRequestDialog(Context ctx, Theme.ResourcesProvider resourcesProvider, String[] systemPermissions, @RawRes int animationId, String title, String titleWithHint, Consumer<Boolean> callback) {
boolean showSettings = false;
if (systemPermissions != null && ctx instanceof Activity && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Activity activity = (Activity) ctx;
for (String perm : systemPermissions) {
if (activity.checkSelfPermission(perm) != PackageManager.PERMISSION_GRANTED && activity.shouldShowRequestPermissionRationale(perm)) {
showSettings = true;
break;
}
}
}
AtomicBoolean gotCallback = new AtomicBoolean();
boolean finalShowSettings = showSettings;
return new AlertDialog.Builder(ctx, resourcesProvider)
.setTopAnimation(animationId, AlertsCreator.PERMISSIONS_REQUEST_TOP_ICON_SIZE, false, Theme.getColor(Theme.key_dialogTopBackground))
.setMessage(AndroidUtilities.replaceTags(showSettings ? titleWithHint : title))
.setPositiveButton(LocaleController.getString(showSettings ? R.string.PermissionOpenSettings : R.string.BotWebViewRequestAllow), (dialogInterface, i) -> {
if (finalShowSettings) {
try {
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + ApplicationLoader.applicationContext.getPackageName()));
ctx.startActivity(intent);
} catch (Exception e) {
FileLog.e(e);
}
} else {
gotCallback.set(true);
callback.accept(true);
}
})
.setNegativeButton(LocaleController.getString(R.string.BotWebViewRequestDontAllow), (dialog, which) -> {
gotCallback.set(true);
callback.accept(false);
})
.setOnDismissListener(dialog -> {
if (!gotCallback.get()) {
callback.accept(false);
}
})
.create();
}
@RequiresApi(api = Build.VERSION_CODES.O)
public static Dialog createApkRestrictedDialog(Context ctx, Theme.ResourcesProvider resourcesProvider) {
return new AlertDialog.Builder(ctx, resourcesProvider)
@ -884,49 +928,17 @@ public class AlertsCreator {
} else if (i == 4) {
untilTime = Integer.MAX_VALUE;
}
if (did != 0) {
SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount);
SharedPreferences.Editor editor = preferences.edit();
long flags;
if (i == 4) {
if (!defaultEnabled) {
editor.remove("notify2_" + did);
flags = 0;
} else {
editor.putInt("notify2_" + did, 2);
flags = 1;
}
NotificationsController.getInstance(currentAccount).muteUntil(did, untilTime);
if (did != 0 && resultCallback != null) {
if (i == 4 && !defaultEnabled) {
resultCallback.run(0);
} else {
editor.putInt("notify2_" + did, 3);
editor.putInt("notifyuntil_" + did, untilTime);
flags = ((long) untilTime << 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 (i != 4 || defaultEnabled) {
dialog.notify_settings.mute_until = untilTime;
}
}
NotificationsController.getInstance(currentAccount).updateServerNotificationsSettings(did);
if (resultCallback != null) {
if (i == 4 && !defaultEnabled) {
resultCallback.run(0);
} else {
resultCallback.run(1);
}
}
} else {
if (i == 4) {
NotificationsController.getInstance(currentAccount).setGlobalNotificationsEnabled(globalType, Integer.MAX_VALUE);
} else {
NotificationsController.getInstance(currentAccount).setGlobalNotificationsEnabled(globalType, untilTime);
resultCallback.run(1);
}
}
if (did == 0) {
NotificationsController.getInstance(currentAccount).setGlobalNotificationsEnabled(globalType, Integer.MAX_VALUE);
}
}
if (callback != null) {
@ -1279,20 +1291,20 @@ public class AlertsCreator {
fragment.showDialog(alertDialog);
}
public static void createClearOrDeleteDialogAlert(BaseFragment fragment, boolean clear, TLRPC.Chat chat, TLRPC.User user, boolean secret, MessagesStorage.BooleanCallback onProcessRunnable) {
createClearOrDeleteDialogAlert(fragment, clear, false, false, chat, user, secret, false, onProcessRunnable, null);
public static void createClearOrDeleteDialogAlert(BaseFragment fragment, boolean clear, TLRPC.Chat chat, TLRPC.User user, boolean secret, boolean canDeleteHistory, MessagesStorage.BooleanCallback onProcessRunnable) {
createClearOrDeleteDialogAlert(fragment, clear, false, false, chat, user, secret, false, canDeleteHistory, onProcessRunnable, null);
}
public static void createClearOrDeleteDialogAlert(BaseFragment fragment, boolean clear, TLRPC.Chat chat, TLRPC.User user, boolean secret, boolean checkDeleteForAll, MessagesStorage.BooleanCallback onProcessRunnable) {
createClearOrDeleteDialogAlert(fragment, clear, chat != null && chat.creator, false, chat, user, secret, checkDeleteForAll, onProcessRunnable, null);
public static void createClearOrDeleteDialogAlert(BaseFragment fragment, boolean clear, TLRPC.Chat chat, TLRPC.User user, boolean secret, boolean checkDeleteForAll, boolean canDeleteHistory, MessagesStorage.BooleanCallback onProcessRunnable) {
createClearOrDeleteDialogAlert(fragment, clear, chat != null && chat.creator, false, chat, user, secret, checkDeleteForAll, canDeleteHistory, onProcessRunnable, null);
}
public static void createClearOrDeleteDialogAlert(BaseFragment fragment, boolean clear, TLRPC.Chat chat, TLRPC.User user, boolean secret, boolean checkDeleteForAll, MessagesStorage.BooleanCallback onProcessRunnable, Theme.ResourcesProvider resourcesProvider) {
createClearOrDeleteDialogAlert(fragment, clear, chat != null && chat.creator, false, chat, user, secret, checkDeleteForAll, onProcessRunnable, resourcesProvider);
public static void createClearOrDeleteDialogAlert(BaseFragment fragment, boolean clear, TLRPC.Chat chat, TLRPC.User user, boolean secret, boolean checkDeleteForAll, boolean canDeleteHistory, MessagesStorage.BooleanCallback onProcessRunnable, Theme.ResourcesProvider resourcesProvider) {
createClearOrDeleteDialogAlert(fragment, clear, chat != null && chat.creator, false, chat, user, secret, checkDeleteForAll, canDeleteHistory, onProcessRunnable, resourcesProvider);
}
public static void createClearOrDeleteDialogAlert(BaseFragment fragment, boolean clear, boolean admin, boolean second, TLRPC.Chat chat, TLRPC.User user, boolean secret, boolean checkDeleteForAll, MessagesStorage.BooleanCallback onProcessRunnable, Theme.ResourcesProvider resourcesProvider) {
if (fragment == null || fragment.getParentActivity() == null || chat == null && user == null) {
public static void createClearOrDeleteDialogAlert(BaseFragment fragment, boolean clear, boolean admin, boolean second, TLRPC.Chat chat, TLRPC.User user, boolean secret, boolean checkDeleteForAll, boolean canDeleteHistory, MessagesStorage.BooleanCallback onProcessRunnable, Theme.ResourcesProvider resourcesProvider) {
if (fragment == null || fragment.getParentActivity() == null || (chat == null && user == null)) {
return;
}
int account = fragment.getCurrentAccount();
@ -1308,7 +1320,7 @@ public class AlertsCreator {
messageTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
messageTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP);
boolean clearingCache = ChatObject.isChannel(chat) && !TextUtils.isEmpty(chat.username);
boolean clearingCache = !canDeleteHistory && ChatObject.isChannel(chat) && !TextUtils.isEmpty(chat.username);
FrameLayout frameLayout = new FrameLayout(context) {
@Override
@ -1529,12 +1541,12 @@ public class AlertsCreator {
builder.setPositiveButton(actionText, (dialogInterface, i) -> {
if (!clearingCache && !second && !secret) {
if (UserObject.isUserSelf(user)) {
createClearOrDeleteDialogAlert(fragment, clear, admin, true, chat, user, false, checkDeleteForAll, onProcessRunnable, resourcesProvider);
createClearOrDeleteDialogAlert(fragment, clear, admin, true, chat, user, false, checkDeleteForAll, canDeleteHistory, onProcessRunnable, resourcesProvider);
return;
} else if (user != null && deleteForAll[0]) {
MessagesStorage.getInstance(fragment.getCurrentAccount()).getMessagesCount(user.id, (count) -> {
if (count >= 50) {
createClearOrDeleteDialogAlert(fragment, clear, admin, true, chat, user, false, checkDeleteForAll, onProcessRunnable, resourcesProvider);
createClearOrDeleteDialogAlert(fragment, clear, admin, true, chat, user, false, checkDeleteForAll, canDeleteHistory, onProcessRunnable, resourcesProvider);
} else {
if (onProcessRunnable != null) {
onProcessRunnable.run(deleteForAll[0]);
@ -1557,8 +1569,8 @@ public class AlertsCreator {
}
}
public static void createClearDaysDialogAlert(BaseFragment fragment, int days, TLRPC.User user, MessagesStorage.BooleanCallback onProcessRunnable, Theme.ResourcesProvider resourcesProvider) {
if (fragment == null || fragment.getParentActivity() == null || user == null) {
public static void createClearDaysDialogAlert(BaseFragment fragment, int days, TLRPC.User user, TLRPC.Chat chat, boolean canDeleteHistory, MessagesStorage.BooleanCallback onProcessRunnable, Theme.ResourcesProvider resourcesProvider) {
if (fragment == null || fragment.getParentActivity() == null || (user == null && chat == null)) {
return;
}
int account = fragment.getCurrentAccount();
@ -1595,18 +1607,40 @@ public class AlertsCreator {
textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL);
textView.setEllipsize(TextUtils.TruncateAt.END);
textView.setText(LocaleController.formatPluralString("DeleteDays", days));
frameLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 24, 11, 24, 0));
frameLayout.addView(messageTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 24, 48, 24, 18));
messageTextView.setText(LocaleController.getString("DeleteHistoryByDaysMessage", R.string.DeleteHistoryByDaysMessage));
if (days == -1) {
textView.setText(LocaleController.formatString("ClearHistory", R.string.ClearHistory));
if (user != null) {
messageTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("AreYouSureClearHistoryWithUser", R.string.AreYouSureClearHistoryWithUser, UserObject.getUserName(user))));
} else {
if (chat != null && canDeleteHistory) {
messageTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("AreYouSureClearHistoryWithChat", R.string.AreYouSureClearHistoryWithChat, chat.title)));
} else if (chat.megagroup) {
messageTextView.setText(LocaleController.getString("AreYouSureClearHistoryGroup", R.string.AreYouSureClearHistoryGroup));
} else {
messageTextView.setText(LocaleController.getString("AreYouSureClearHistoryChannel", R.string.AreYouSureClearHistoryChannel));
}
}
} else {
textView.setText(LocaleController.formatPluralString("DeleteDays", days));
messageTextView.setText(LocaleController.getString("DeleteHistoryByDaysMessage", R.string.DeleteHistoryByDaysMessage));
}
final boolean[] deleteForAll = new boolean[]{false};
if (user.id != selfUserId) {
if (chat != null && canDeleteHistory && !TextUtils.isEmpty(chat.username)) {
deleteForAll[0] = true;
}
if ((user != null && user.id != selfUserId) || (chat != null && canDeleteHistory && TextUtils.isEmpty(chat.username))) {
cell[0] = new CheckBoxCell(context, 1, resourcesProvider);
cell[0].setBackgroundDrawable(Theme.getSelectorDrawable(false));
cell[0].setText(LocaleController.formatString("DeleteMessagesOptionAlso", R.string.DeleteMessagesOptionAlso, UserObject.getFirstName(user)), "", false, false);
if (chat != null) {
cell[0].setText(LocaleController.getString("DeleteMessagesOptionAlsoChat", R.string.DeleteMessagesOptionAlsoChat), "", false, false);
} else {
cell[0].setText(LocaleController.formatString("DeleteMessagesOptionAlso", R.string.DeleteMessagesOptionAlso, UserObject.getFirstName(user)), "", false, false);
}
cell[0].setPadding(LocaleController.isRTL ? AndroidUtilities.dp(16) : AndroidUtilities.dp(8), 0, LocaleController.isRTL ? AndroidUtilities.dp(8) : AndroidUtilities.dp(16), 0);
frameLayout.addView(cell[0], LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 0, 0));
@ -2369,9 +2403,9 @@ public class AlertsCreator {
dayPicker.setItemCount(count);
hourPicker.setItemCount(count);
minutePicker.setItemCount(count);
dayPicker.getLayoutParams().height = AndroidUtilities.dp(54) * count;
hourPicker.getLayoutParams().height = AndroidUtilities.dp(54) * count;
minutePicker.getLayoutParams().height = AndroidUtilities.dp(54) * count;
dayPicker.getLayoutParams().height = AndroidUtilities.dp(NumberPicker.DEFAULT_SIZE_PER_COUNT) * count;
hourPicker.getLayoutParams().height = AndroidUtilities.dp(NumberPicker.DEFAULT_SIZE_PER_COUNT) * count;
minutePicker.getLayoutParams().height = AndroidUtilities.dp(NumberPicker.DEFAULT_SIZE_PER_COUNT) * count;
ignoreLayout = false;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@ -2435,7 +2469,7 @@ public class AlertsCreator {
LinearLayout linearLayout = new LinearLayout(context);
linearLayout.setOrientation(LinearLayout.HORIZONTAL);
linearLayout.setWeightSum(1.0f);
container.addView(linearLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
container.addView(linearLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 1f, 0, 0, 12, 0, 12));
long currentTime = System.currentTimeMillis();
Calendar calendar = Calendar.getInstance();
@ -2589,9 +2623,9 @@ public class AlertsCreator {
dayPicker.setItemCount(count);
hourPicker.setItemCount(count);
minutePicker.setItemCount(count);
dayPicker.getLayoutParams().height = AndroidUtilities.dp(54) * count;
hourPicker.getLayoutParams().height = AndroidUtilities.dp(54) * count;
minutePicker.getLayoutParams().height = AndroidUtilities.dp(54) * count;
dayPicker.getLayoutParams().height = AndroidUtilities.dp(NumberPicker.DEFAULT_SIZE_PER_COUNT) * count;
hourPicker.getLayoutParams().height = AndroidUtilities.dp(NumberPicker.DEFAULT_SIZE_PER_COUNT) * count;
minutePicker.getLayoutParams().height = AndroidUtilities.dp(NumberPicker.DEFAULT_SIZE_PER_COUNT) * count;
ignoreLayout = false;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@ -2621,7 +2655,7 @@ public class AlertsCreator {
LinearLayout linearLayout = new LinearLayout(context);
linearLayout.setOrientation(LinearLayout.HORIZONTAL);
linearLayout.setWeightSum(1.0f);
container.addView(linearLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
container.addView(linearLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 1f, 0, 0, 12, 0, 12));
long currentTime = System.currentTimeMillis();
Calendar calendar = Calendar.getInstance();
@ -2720,6 +2754,483 @@ public class AlertsCreator {
return builder;
}
public static BottomSheet.Builder createAutoDeleteDatePickerDialog(Context context, final ScheduleDatePickerDelegate datePickerDelegate) {
if (context == null) {
return null;
}
ScheduleDatePickerColors datePickerColors = new ScheduleDatePickerColors();
BottomSheet.Builder builder = new BottomSheet.Builder(context, false);
builder.setApplyBottomPadding(false);
int[] values = new int[]{
0,
60 * 24,
2 * 60 * 24,
3 * 60 * 24,
4 * 60 * 24,
5 * 60 * 24,
6 * 60 * 24,
7 * 60 * 24,
2 * 7 * 60 * 24,
3 * 7 * 60 * 24,
31 * 60 * 24,
2 * 31 * 60 * 24,
3 * 31 * 60 * 24,
4 * 31 * 60 * 24,
5 * 31 * 60 * 24,
6 * 31 * 60 * 24,
365 * 60 * 24
};
final NumberPicker numberPicker = new NumberPicker(context) {
@Override
protected CharSequence getContentDescription(int index) {
if (values[index] == 0) {
return LocaleController.getString("AutoDeleteNever", R.string.AutoDeleteNever);
} else if (values[index] < 7 * 60 * 24) {
return LocaleController.formatPluralString("Days", values[index] / (60 * 24));
} else if (values[index] < 31 * 60 * 24) {
return LocaleController.formatPluralString("Weeks", values[index] / (60 * 24));
} else if (values[index] < 365 * 60 * 24) {
return LocaleController.formatPluralString("Months", values[index] / (7 * 60 * 24));
} else {
return LocaleController.formatPluralString("Years", values[index] * 5 / 31 * 60 * 24);
}
}
};
numberPicker.setMinValue(0);
numberPicker.setMaxValue(values.length - 1);
numberPicker.setTextColor(datePickerColors.textColor);
numberPicker.setValue(0);
numberPicker.setWrapSelectorWheel(false);
numberPicker.setFormatter(index -> {
if (values[index] == 0) {
return LocaleController.getString("AutoDeleteNever", R.string.AutoDeleteNever);
} else if (values[index] < 7 * 60 * 24) {
return LocaleController.formatPluralString("Days", values[index] / (60 * 24));
} else if (values[index] < 31 * 60 * 24) {
return LocaleController.formatPluralString("Weeks", values[index] / (7 * 60 * 24));
} else if (values[index] < 365 * 60 * 24) {
return LocaleController.formatPluralString("Months", values[index] / (31 * 60 * 24));
} else {
return LocaleController.formatPluralString("Years", values[index] / (365 * 60 * 24));
}
});
LinearLayout container = new LinearLayout(context) {
boolean ignoreLayout = false;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
ignoreLayout = true;
int count;
if (AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y) {
count = 3;
} else {
count = 5;
}
numberPicker.setItemCount(count);
numberPicker.getLayoutParams().height = AndroidUtilities.dp(NumberPicker.DEFAULT_SIZE_PER_COUNT) * count;
ignoreLayout = false;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public void requestLayout() {
if (ignoreLayout) {
return;
}
super.requestLayout();
}
};
container.setOrientation(LinearLayout.VERTICAL);
FrameLayout titleLayout = new FrameLayout(context);
container.addView(titleLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 22, 0, 0, 4));
TextView titleView = new TextView(context);
titleView.setText(LocaleController.getString("AutoDeleteAfteTitle", R.string.AutoDeleteAfteTitle));
titleView.setTextColor(datePickerColors.textColor);
titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20);
titleView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
titleLayout.addView(titleView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 0, 12, 0, 0));
titleView.setOnTouchListener((v, event) -> true);
LinearLayout linearLayout = new LinearLayout(context);
linearLayout.setOrientation(LinearLayout.HORIZONTAL);
linearLayout.setWeightSum(1.0f);
container.addView(linearLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 1f, 0, 0, 12, 0, 12));
TextView buttonTextView = new TextView(context) {
@Override
public CharSequence getAccessibilityClassName() {
return Button.class.getName();
}
};
linearLayout.addView(numberPicker, LayoutHelper.createLinear(0, 54 * 5, 1f));
buttonTextView.setPadding(AndroidUtilities.dp(34), 0, AndroidUtilities.dp(34), 0);
buttonTextView.setGravity(Gravity.CENTER);
buttonTextView.setTextColor(datePickerColors.buttonTextColor);
buttonTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
buttonTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
buttonTextView.setBackgroundDrawable(Theme.createSimpleSelectorRoundRectDrawable(AndroidUtilities.dp(4), datePickerColors.buttonBackgroundColor, datePickerColors.buttonBackgroundPressedColor));
buttonTextView.setText(LocaleController.getString("AutoDeleteConfirm", R.string.AutoDeleteConfirm));
container.addView(buttonTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.BOTTOM, 16, 15, 16, 16));
final NumberPicker.OnValueChangeListener onValueChangeListener = (picker, oldVal, newVal) -> {
try {
container.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
} catch (Exception ignore) {
}
};
numberPicker.setOnValueChangedListener(onValueChangeListener);
buttonTextView.setOnClickListener(v -> {
int time = values[numberPicker.getValue()];
datePickerDelegate.didSelectDate(true, time);
builder.getDismissRunnable().run();
});
builder.setCustomView(container);
BottomSheet bottomSheet = builder.show();
bottomSheet.setBackgroundColor(datePickerColors.backgroundColor);
return builder;
}
public static BottomSheet.Builder createSoundFrequencyPickerDialog(Context context, int notifyMaxCount, int notifyDelay, final SoundFrequencyDelegate delegate) {
if (context == null) {
return null;
}
ScheduleDatePickerColors datePickerColors = new ScheduleDatePickerColors();
BottomSheet.Builder builder = new BottomSheet.Builder(context, false);
builder.setApplyBottomPadding(false);
final NumberPicker times = new NumberPicker(context) {
@Override
protected CharSequence getContentDescription(int index) {
return LocaleController.formatPluralString("Times", index + 1);
}
};
times.setMinValue(0);
times.setMaxValue(10);
times.setTextColor(datePickerColors.textColor);
times.setValue(notifyMaxCount - 1);
times.setWrapSelectorWheel(false);
times.setFormatter(index -> LocaleController.formatPluralString("Times", index + 1));
final NumberPicker minutes = new NumberPicker(context) {
@Override
protected CharSequence getContentDescription(int index) {
return LocaleController.formatPluralString("Times", index + 1);
}
};
minutes.setMinValue(0);
minutes.setMaxValue(10);
minutes.setTextColor(datePickerColors.textColor);
minutes.setValue(notifyDelay / 60 - 1);
minutes.setWrapSelectorWheel(false);
minutes.setFormatter(index -> LocaleController.formatPluralString("Minutes", index + 1));
NumberPicker divider = new NumberPicker(context);
divider.setMinValue(0);
divider.setMaxValue(0);
divider.setTextColor(datePickerColors.textColor);
divider.setValue(0);
divider.setWrapSelectorWheel(false);
divider.setFormatter(index -> LocaleController.getString("NotificationsFrequencyDivider", R.string.NotificationsFrequencyDivider));
LinearLayout container = new LinearLayout(context) {
boolean ignoreLayout = false;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
ignoreLayout = true;
int count;
if (AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y) {
count = 3;
} else {
count = 5;
}
times.setItemCount(count);
times.getLayoutParams().height = AndroidUtilities.dp(NumberPicker.DEFAULT_SIZE_PER_COUNT) * count;
minutes.setItemCount(count);
minutes.getLayoutParams().height = AndroidUtilities.dp(NumberPicker.DEFAULT_SIZE_PER_COUNT) * count;
divider.setItemCount(count);
divider.getLayoutParams().height = AndroidUtilities.dp(NumberPicker.DEFAULT_SIZE_PER_COUNT) * count;
ignoreLayout = false;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public void requestLayout() {
if (ignoreLayout) {
return;
}
super.requestLayout();
}
};
container.setOrientation(LinearLayout.VERTICAL);
FrameLayout titleLayout = new FrameLayout(context);
container.addView(titleLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 22, 0, 0, 4));
TextView titleView = new TextView(context);
titleView.setText(LocaleController.getString("NotfificationsFrequencyTitle", R.string.NotfificationsFrequencyTitle));
titleView.setTextColor(datePickerColors.textColor);
titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20);
titleView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
titleLayout.addView(titleView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 0, 12, 0, 0));
titleView.setOnTouchListener((v, event) -> true);
LinearLayout linearLayout = new LinearLayout(context);
linearLayout.setOrientation(LinearLayout.HORIZONTAL);
linearLayout.setWeightSum(1.0f);
container.addView(linearLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 1f, 0, 0, 12, 0, 12));
TextView buttonTextView = new TextView(context) {
@Override
public CharSequence getAccessibilityClassName() {
return Button.class.getName();
}
};
linearLayout.addView(times, LayoutHelper.createLinear(0, 54 * 5, 0.4f));
linearLayout.addView(divider, LayoutHelper.createLinear(0, LayoutHelper.WRAP_CONTENT, 0.2f, Gravity.CENTER_VERTICAL));
linearLayout.addView(minutes, LayoutHelper.createLinear(0, 54 * 5, 0.4f));
buttonTextView.setPadding(AndroidUtilities.dp(34), 0, AndroidUtilities.dp(34), 0);
buttonTextView.setGravity(Gravity.CENTER);
buttonTextView.setTextColor(datePickerColors.buttonTextColor);
buttonTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
buttonTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
buttonTextView.setBackgroundDrawable(Theme.createSimpleSelectorRoundRectDrawable(AndroidUtilities.dp(4), datePickerColors.buttonBackgroundColor, datePickerColors.buttonBackgroundPressedColor));
buttonTextView.setText(LocaleController.getString("AutoDeleteConfirm", R.string.AutoDeleteConfirm));
container.addView(buttonTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.BOTTOM, 16, 15, 16, 16));
final NumberPicker.OnValueChangeListener onValueChangeListener = (picker, oldVal, newVal) -> {
try {
container.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
} catch (Exception ignore) {
}
};
times.setOnValueChangedListener(onValueChangeListener);
minutes.setOnValueChangedListener(onValueChangeListener);
buttonTextView.setOnClickListener(v -> {
int time = times.getValue() + 1;
int minute = (minutes.getValue() + 1) * 60;
delegate.didSelectValues(time, minute);
builder.getDismissRunnable().run();
});
builder.setCustomView(container);
BottomSheet bottomSheet = builder.show();
bottomSheet.setBackgroundColor(datePickerColors.backgroundColor);
return builder;
}
public static BottomSheet.Builder createMuteForPickerDialog(Context context, final ScheduleDatePickerDelegate datePickerDelegate) {
if (context == null) {
return null;
}
ScheduleDatePickerColors datePickerColors = new ScheduleDatePickerColors();
BottomSheet.Builder builder = new BottomSheet.Builder(context, false);
builder.setApplyBottomPadding(false);
final NumberPicker dayPicker = new NumberPicker(context);
dayPicker.setTextColor(datePickerColors.textColor);
dayPicker.setTextOffset(AndroidUtilities.dp(10));
dayPicker.setItemCount(5);
final NumberPicker hourPicker = new NumberPicker(context) {
@Override
protected CharSequence getContentDescription(int value) {
return LocaleController.formatPluralString("Hours", value);
}
};
hourPicker.setItemCount(5);
hourPicker.setTextColor(datePickerColors.textColor);
LinearLayout container = new LinearLayout(context) {
boolean ignoreLayout = false;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
ignoreLayout = true;
int count;
if (AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y) {
count = 3;
} else {
count = 5;
}
dayPicker.setItemCount(count);
hourPicker.setItemCount(count);
dayPicker.getLayoutParams().height = AndroidUtilities.dp(NumberPicker.DEFAULT_SIZE_PER_COUNT) * count;
hourPicker.getLayoutParams().height = AndroidUtilities.dp(NumberPicker.DEFAULT_SIZE_PER_COUNT) * count;
ignoreLayout = false;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public void requestLayout() {
if (ignoreLayout) {
return;
}
super.requestLayout();
}
};
container.setOrientation(LinearLayout.VERTICAL);
FrameLayout titleLayout = new FrameLayout(context);
container.addView(titleLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 22, 0, 0, 4));
TextView titleView = new TextView(context);
titleView.setText(LocaleController.getString("MuteForAlert", R.string.MuteForAlert));
titleView.setTextColor(datePickerColors.textColor);
titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20);
titleView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
titleLayout.addView(titleView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 0, 12, 0, 0));
titleView.setOnTouchListener((v, event) -> true);
LinearLayout linearLayout = new LinearLayout(context);
linearLayout.setOrientation(LinearLayout.HORIZONTAL);
linearLayout.setWeightSum(1.0f);
container.addView(linearLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 1f, 0, 0, 12, 0, 12));
TextView buttonTextView = new TextView(context) {
@Override
public CharSequence getAccessibilityClassName() {
return Button.class.getName();
}
};
linearLayout.addView(dayPicker, LayoutHelper.createLinear(0, 54 * 5, 0.5f));
dayPicker.setMinValue(0);
dayPicker.setMaxValue(365);
dayPicker.setWrapSelectorWheel(false);
dayPicker.setFormatter(value -> LocaleController.formatPluralString("Days", value));
final NumberPicker.OnValueChangeListener onValueChangeListener = (picker, oldVal, newVal) -> {
try {
container.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
} catch (Exception ignore) {
}
checkMuteForButton(dayPicker, hourPicker, buttonTextView, true);
};
dayPicker.setOnValueChangedListener(onValueChangeListener);
hourPicker.setMinValue(0);
hourPicker.setMaxValue(23);
linearLayout.addView(hourPicker, LayoutHelper.createLinear(0, 54 * 5, 0.5f));
hourPicker.setFormatter(value -> LocaleController.formatPluralString("Hours", value));
hourPicker.setOnValueChangedListener(onValueChangeListener);
buttonTextView.setPadding(AndroidUtilities.dp(34), 0, AndroidUtilities.dp(34), 0);
buttonTextView.setGravity(Gravity.CENTER);
buttonTextView.setTextColor(datePickerColors.buttonTextColor);
buttonTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
buttonTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
buttonTextView.setBackgroundDrawable(Theme.createSimpleSelectorRoundRectDrawable(AndroidUtilities.dp(4), datePickerColors.buttonBackgroundColor, datePickerColors.buttonBackgroundPressedColor));
buttonTextView.setText(LocaleController.getString("SetTimeLimit", R.string.SetTimeLimit));
container.addView(buttonTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.BOTTOM, 16, 15, 16, 16));
buttonTextView.setOnClickListener(v -> {
int time = hourPicker.getValue() * 60 + dayPicker.getValue() * 60 * 24;
datePickerDelegate.didSelectDate(true, time);
builder.getDismissRunnable().run();
});
builder.setCustomView(container);
BottomSheet bottomSheet = builder.show();
bottomSheet.setBackgroundColor(datePickerColors.backgroundColor);
checkMuteForButton(dayPicker, hourPicker, buttonTextView, false);
return builder;
}
private static void checkMuteForButton(NumberPicker dayPicker, NumberPicker hourPicker, TextView buttonTextView, boolean animated) {
StringBuilder stringBuilder = new StringBuilder();
if (dayPicker.getValue() != 0) {
stringBuilder.append(dayPicker.getValue()).append(LocaleController.getString("SecretChatTimerDays", R.string.SecretChatTimerDays));
}
if (hourPicker.getValue() != 0) {
if (stringBuilder.length() > 0) {
stringBuilder.append(" ");
}
stringBuilder.append(hourPicker.getValue()).append(LocaleController.getString("SecretChatTimerHours", R.string.SecretChatTimerHours));
}
if (stringBuilder.length() == 0) {
buttonTextView.setText(LocaleController.getString("ChooseTimeForMute", R.string.ChooseTimeForMute));
if (buttonTextView.isEnabled()) {
buttonTextView.setEnabled(false);
if (animated) {
buttonTextView.animate().alpha(0.5f);
} else {
buttonTextView.setAlpha(0.5f);
}
}
} else {
buttonTextView.setText(LocaleController.formatString("MuteForButton", R.string.MuteForButton, stringBuilder.toString()));
if (!buttonTextView.isEnabled()) {
buttonTextView.setEnabled(true);
if (animated) {
buttonTextView.animate().alpha(1f);
} else {
buttonTextView.setAlpha(1f);
}
}
}
}
private static void checkAutoDeleteButton(NumberPicker dayPicker, NumberPicker hourPicker, NumberPicker minutePicker, TextView buttonTextView, boolean animated) {
StringBuilder stringBuilder = new StringBuilder();
if (dayPicker.getValue() != 0) {
stringBuilder.append(dayPicker.getValue()).append(LocaleController.getString("SecretChatTimerDays", R.string.SecretChatTimerDays));
}
if (hourPicker.getValue() != 0) {
if (stringBuilder.length() > 0) {
stringBuilder.append(" ");
}
stringBuilder.append(hourPicker.getValue()).append(LocaleController.getString("SecretChatTimerHours", R.string.SecretChatTimerHours));
}
if (minutePicker.getValue() != 0) {
if (stringBuilder.length() > 0) {
stringBuilder.append(" ");
}
stringBuilder.append(minutePicker.getValue() * 5).append(LocaleController.getString("SecretChatTimerMinutes", R.string.SecretChatTimerMinutes));
}
if (stringBuilder.length() == 0) {
buttonTextView.setText(LocaleController.formatString("ChooseTimeForAutoDelete", R.string.ChooseTimeForAutoDelete));
if (buttonTextView.isEnabled()) {
buttonTextView.setEnabled(false);
if (animated) {
buttonTextView.animate().alpha(0.5f);
} else {
buttonTextView.setAlpha(0.5f);
}
}
} else {
buttonTextView.setText(LocaleController.formatString("AutoDeleteAfter", R.string.AutoDeleteAfter, stringBuilder.toString()));
if (!buttonTextView.isEnabled()) {
buttonTextView.setEnabled(true);
if (animated) {
buttonTextView.animate().alpha(1f);
} else {
buttonTextView.setAlpha(1f);
}
}
}
}
private static void checkCalendarDate(long minDate, NumberPicker dayPicker, NumberPicker monthPicker, NumberPicker yearPicker) {
int day = dayPicker.getValue();
int month = monthPicker.getValue();
@ -2808,9 +3319,9 @@ public class AlertsCreator {
dayPicker.setItemCount(count);
monthPicker.setItemCount(count);
yearPicker.setItemCount(count);
dayPicker.getLayoutParams().height = AndroidUtilities.dp(54) * count;
monthPicker.getLayoutParams().height = AndroidUtilities.dp(54) * count;
yearPicker.getLayoutParams().height = AndroidUtilities.dp(54) * count;
dayPicker.getLayoutParams().height = AndroidUtilities.dp(NumberPicker.DEFAULT_SIZE_PER_COUNT) * count;
monthPicker.getLayoutParams().height = AndroidUtilities.dp(NumberPicker.DEFAULT_SIZE_PER_COUNT) * count;
yearPicker.getLayoutParams().height = AndroidUtilities.dp(NumberPicker.DEFAULT_SIZE_PER_COUNT) * count;
ignoreLayout = false;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@ -2839,7 +3350,7 @@ public class AlertsCreator {
LinearLayout linearLayout = new LinearLayout(context);
linearLayout.setOrientation(LinearLayout.HORIZONTAL);
linearLayout.setWeightSum(1.0f);
container.addView(linearLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
container.addView(linearLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 1f, 0, 0, 12, 0, 12));
long currentTime = System.currentTimeMillis();
@ -2981,7 +3492,7 @@ public class AlertsCreator {
}
NotificationsController.getInstance(UserConfig.selectedAccount).setDialogNotificationsSettings(dialog_id, setting);
if (BulletinFactory.canShowBulletin(fragment)) {
BulletinFactory.createMuteBulletin(fragment, setting, resourcesProvider).show();
BulletinFactory.createMuteBulletin(fragment, setting, 0, resourcesProvider).show();
}
}
);
@ -4169,42 +4680,6 @@ public class AlertsCreator {
return alertDialog[0] = builder.create();
}
// public static AlertDialog createExpireDateAlert(final Context context, final boolean month, final int[] result, final Runnable callback) {
// AlertDialog.Builder builder = new AlertDialog.Builder(context);
// builder.setTitle(month ? LocaleController.getString("PaymentCardExpireDateMonth", R.string.PaymentCardExpireDateMonth) : LocaleController.getString("PaymentCardExpireDateYear", R.string.PaymentCardExpireDateYear));
// final NumberPicker numberPicker = new NumberPicker(context);
// final int currentYear;
// if (month) {
// numberPicker.setMinValue(1);
// numberPicker.setMaxValue(12);
// currentYear = 0;
// } else {
// Calendar rightNow = Calendar.getInstance();
// currentYear = rightNow.get(Calendar.YEAR);
// numberPicker.setMinValue(0);
// numberPicker.setMaxValue(30);
// }
// numberPicker.setFormatter(new NumberPicker.Formatter() {
// @Override
// public String format(int value) {
// if (month) {
// return String.format(Locale.US, "%02d", value);
// } else {
// return String.format(Locale.US, "%02d", value + currentYear);
// }
// }
// });
// builder.setView(numberPicker);
// builder.setNegativeButton(LocaleController.getString("Done", R.string.Done), new DialogInterface.OnClickListener() {
// @Override
// public void onClick(DialogInterface dialog, int which) {
// result[0] = month ? numberPicker.getValue() : ((numberPicker.getValue() + currentYear) % 100);
// callback.run();
// }
// });
// return builder.create();
// }
public interface PaymentAlertDelegate {
void didPressedNewCard();
}
@ -4220,7 +4695,7 @@ public class AlertsCreator {
int currentAccount = fragment.getCurrentAccount();
AlertDialog.Builder builder = new AlertDialog.Builder(activity, resourcesProvider);
builder.setDimEnabled(hideDim == null);
builder.setDimAlpha(hideDim != null ? .5f : .6f);
int count;
if (selectedGroup != null) {
count = selectedGroup.messages.size();
@ -5092,4 +5567,8 @@ public class AlertsCreator {
});
return popupWindow;
}
public interface SoundFrequencyDelegate {
void didSelectValues(int time, int minute);
}
}

View File

@ -136,18 +136,6 @@ public class AnimatedFileDrawable extends BitmapDrawable implements Animatable {
private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(8, new ThreadPoolExecutor.DiscardPolicy());
protected final Runnable mInvalidateTask = () -> {
invalidateTaskIsRunning = false;
if (!secondParentViews.isEmpty()) {
for (int a = 0, N = secondParentViews.size(); a < N; a++) {
secondParentViews.get(a).invalidate();
}
}
if ((secondParentViews.isEmpty() || invalidateParentViewWithSecond) && parentView != null) {
parentView.invalidate();
}
};
private Runnable uiRunnableNoFrame = new Runnable() {
@Override
public void run() {
@ -832,4 +820,12 @@ public class AnimatedFileDrawable extends BitmapDrawable implements Animatable {
public boolean isRecycled() {
return isRecycled;
}
public Bitmap getNextFrame() {
if (backgroundBitmap == null) {
backgroundBitmap = Bitmap.createBitmap((int) (metaData[0] * scaleFactor), (int) (metaData[1] * scaleFactor), Bitmap.Config.ARGB_8888);
}
getVideoFrame(nativePtr, backgroundBitmap, metaData, backgroundBitmap.getRowBytes(), false, startTime, endTime) ;
return backgroundBitmap;
}
}

View File

@ -53,6 +53,18 @@ public class AnimationProperties {
}
};
public static final Property<Paint, Integer> PAINT_COLOR = new IntProperty<Paint>("color") {
@Override
public void setValue(Paint object, int value) {
object.setColor(value);
}
@Override
public Integer get(Paint object) {
return object.getColor();
}
};
public static final Property<ColorDrawable, Integer> COLOR_DRAWABLE_ALPHA = new IntProperty<ColorDrawable>("alpha") {
@Override
public void setValue(ColorDrawable object, int value) {

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