Update to 8.0.0 (2406)

This commit is contained in:
DrKLO 2021-08-31 22:06:39 +03:00
parent ab221dafad
commit 368822d20f
191 changed files with 12004 additions and 2919 deletions

View file

@ -1,4 +1,4 @@
FROM gradle:6.7.1-jdk11
FROM gradle:7.0.2-jdk11
ENV ANDROID_SDK_URL https://dl.google.com/android/repository/commandlinetools-linux-7302050_latest.zip
ENV ANDROID_API_LEVEL android-31

View file

@ -25,7 +25,7 @@ dependencies {
compileOnly 'org.checkerframework:checker-qual:2.5.2'
compileOnly 'org.checkerframework:checker-compat-qual:2.5.0'
implementation 'com.google.firebase:firebase-messaging:22.0.0'
implementation 'com.google.firebase:firebase-config:21.0.0'
implementation 'com.google.firebase:firebase-config:21.0.1'
implementation 'com.google.firebase:firebase-datatransport:18.0.1'
implementation 'com.google.firebase:firebase-appindexing:20.0.0'
implementation 'com.google.android.gms:play-services-maps:17.0.1'
@ -299,7 +299,7 @@ android {
}
}
defaultConfig.versionCode = 2390
defaultConfig.versionCode = 2406
applicationVariants.all { variant ->
variant.outputs.all { output ->
@ -318,7 +318,7 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 29
versionName "7.9.3"
versionName "8.0.0"
vectorDrawables.generatedDensities = ['mdpi', 'hdpi', 'xhdpi', 'xxhdpi']

View file

@ -1224,7 +1224,7 @@ JNIEXPORT void Java_org_telegram_messenger_Utilities_generateGradient(JNIEnv *en
float directPixelY;
float centerDistanceY;
float centerDistanceY2;
int32_t colorsCount = colorsArray[12] == 0 ? 3 : 4;
int32_t colorsCount = colorsArray[12] == 0 && colorsArray[13] == 0 && colorsArray[14] == 0 && colorsArray[15] == 0 ? 3 : 4;
for (int y = 0; y < height; y++) {
if (pixelCache == nullptr) {

View file

@ -2499,7 +2499,7 @@ void ConnectionsManager::processRequestQueue(uint32_t connectionTypes, uint32_t
} else {
currentCount = 0;
}
if (!networkAvailable || currentCount >= 12) {
if (!networkAvailable || currentCount >= 16) {
iter++;
continue;
}

View file

@ -918,7 +918,9 @@ add_library(tgcalls STATIC
voip/tgcalls/group/GroupNetworkManager.cpp
voip/tgcalls/group/GroupInstanceCustomImpl.cpp
voip/tgcalls/group/GroupJoinPayloadInternal.cpp
voip/tgcalls/group/StreamingPart.cpp
voip/tgcalls/group/AudioStreamingPart.cpp
voip/tgcalls/group/VideoStreamingPart.cpp
voip/tgcalls/group/StreamingMediaContext.cpp
voip/tgcalls/third-party/json11.cpp
voip/webrtc/rtc_base/async_invoker.cc

View file

@ -76,10 +76,12 @@ class BroadcastPartTaskJava : public BroadcastPartTask {
public:
BroadcastPartTaskJava(std::shared_ptr<PlatformContext> platformContext,
std::function<void(BroadcastPart &&)> callback,
int64_t timestamp) :
int64_t timestamp, int32_t videoChannel, VideoChannelDescription::Quality quality) :
_platformContext(std::move(platformContext)),
_callback(std::move(callback)),
_timestamp(timestamp) {
_timestamp(timestamp),
_videoChannel(videoChannel),
_quality(quality) {
}
void call(int64_t ts, int64_t responseTs, BroadcastPart::Status status, uint8_t *data, int32_t len) {
@ -91,22 +93,48 @@ public:
part.responseTimestamp = responseTs / 1000.0;
part.status = status;
if (data != nullptr) {
part.oggData = std::vector<uint8_t>(data, data + len);
part.data = std::vector<uint8_t>(data, data + len);
}
_callback(std::move<>(part));
}
bool isValidTaskFor(int64_t timestamp, int32_t videoChannel, VideoChannelDescription::Quality quality) {
if (_videoChannel == 0) {
return _timestamp == timestamp;
} else {
return _timestamp == timestamp && _videoChannel == videoChannel && _quality == quality;
}
}
private:
void cancel() override {
tgvoip::jni::DoWithJNI([&](JNIEnv *env) {
jobject globalRef = ((AndroidContext *) _platformContext.get())->getJavaInstance();
env->CallVoidMethod(globalRef, env->GetMethodID(NativeInstanceClass, "onCancelRequestBroadcastPart", "(J)V"), _timestamp);
auto context = (AndroidContext *) _platformContext.get();
jobject globalRef = context->getJavaInstance();
env->CallVoidMethod(globalRef, env->GetMethodID(NativeInstanceClass, "onCancelRequestBroadcastPart", "(JII)V"), _timestamp, _videoChannel, (jint) _quality);
if (_videoChannel != 0) {
for (auto videoTaskIter = context->videoStreamTasks.begin(); videoTaskIter != context->videoStreamTasks.end(); videoTaskIter++) {
if (((BroadcastPartTaskJava *) videoTaskIter->get())->isValidTaskFor(_timestamp, _videoChannel, _quality)) {
context->videoStreamTasks.erase(videoTaskIter);
break;
}
}
} else {
for (auto audioTaskIter = context->audioStreamTasks.begin(); audioTaskIter != context->audioStreamTasks.end(); audioTaskIter++) {
if (((BroadcastPartTaskJava *) audioTaskIter->get())->isValidTaskFor(_timestamp, _videoChannel, _quality)) {
context->audioStreamTasks.erase(audioTaskIter);
break;
}
}
}
});
}
std::shared_ptr<PlatformContext> _platformContext;
std::function<void(BroadcastPart &&)> _callback;
int64_t _timestamp;
int32_t _videoChannel;
VideoChannelDescription::Quality _quality;
};
class JavaObject {
@ -399,12 +427,21 @@ JNIEXPORT jlong JNICALL Java_org_telegram_messenger_voip_NativeInstance_makeGrou
.platformContext = platformContext
};
if (!screencast) {
descriptor.requestBroadcastPart = [](std::shared_ptr<PlatformContext> platformContext, int64_t timestamp, int64_t duration, std::function<void(BroadcastPart &&)> callback) -> std::shared_ptr<BroadcastPartTask> {
std::shared_ptr<BroadcastPartTask> task = std::make_shared<BroadcastPartTaskJava>(platformContext, callback, timestamp);
((AndroidContext *) platformContext.get())->streamTask = task;
descriptor.requestAudioBroadcastPart = [](std::shared_ptr<PlatformContext> platformContext, int64_t timestamp, int64_t duration, std::function<void(BroadcastPart &&)> callback) -> std::shared_ptr<BroadcastPartTask> {
std::shared_ptr<BroadcastPartTask> task = std::make_shared<BroadcastPartTaskJava>(platformContext, callback, timestamp, 0, VideoChannelDescription::Quality::Full);
((AndroidContext *) platformContext.get())->audioStreamTasks.push_back(task);
tgvoip::jni::DoWithJNI([platformContext, timestamp, duration, task](JNIEnv *env) {
jobject globalRef = ((AndroidContext *) platformContext.get())->getJavaInstance();
env->CallVoidMethod(globalRef, env->GetMethodID(NativeInstanceClass, "onRequestBroadcastPart", "(JJ)V"), timestamp, duration);
env->CallVoidMethod(globalRef, env->GetMethodID(NativeInstanceClass, "onRequestBroadcastPart", "(JJII)V"), timestamp, duration, 0, 0);
});
return task;
};
descriptor.requestVideoBroadcastPart = [](std::shared_ptr<PlatformContext> platformContext, int64_t timestamp, int64_t duration, int32_t video_channel, VideoChannelDescription::Quality quality, std::function<void(BroadcastPart &&)> callback) -> std::shared_ptr<BroadcastPartTask> {
std::shared_ptr<BroadcastPartTask> task = std::make_shared<BroadcastPartTaskJava>(platformContext, callback, timestamp, video_channel, quality);
((AndroidContext *) platformContext.get())->videoStreamTasks.push_back(task);
tgvoip::jni::DoWithJNI([platformContext, timestamp, duration, task, video_channel, quality](JNIEnv *env) {
jobject globalRef = ((AndroidContext *) platformContext.get())->getJavaInstance();
env->CallVoidMethod(globalRef, env->GetMethodID(NativeInstanceClass, "onRequestBroadcastPart", "(JJII)V"), timestamp, duration, video_channel, (jint) quality);
});
return task;
};
@ -812,20 +849,37 @@ JNIEXPORT void JNICALL Java_org_telegram_messenger_voip_NativeInstance_stopGroup
delete instance;
}
JNIEXPORT void JNICALL Java_org_telegram_messenger_voip_NativeInstance_onStreamPartAvailable(JNIEnv *env, jobject obj, jlong ts, jobject byteBuffer, jint size, jlong responseTs) {
JNIEXPORT void JNICALL Java_org_telegram_messenger_voip_NativeInstance_onStreamPartAvailable(JNIEnv *env, jobject obj, jlong ts, jobject byteBuffer, jint size, jlong responseTs, jint videoChannel, jint quality) {
InstanceHolder *instance = getInstanceHolder(env, obj);
if (instance->groupNativeInstance == nullptr) {
return;
}
auto context = (AndroidContext *) instance->_platformContext.get();
std::shared_ptr<BroadcastPartTask> streamTask = context->streamTask;
auto task = (BroadcastPartTaskJava *) streamTask.get();
std::shared_ptr<BroadcastPartTask> task;
auto q = (VideoChannelDescription::Quality) quality;
if (videoChannel != 0) {
for (auto videoTaskIter = context->videoStreamTasks.begin(); videoTaskIter != context->videoStreamTasks.end(); videoTaskIter++) {
if (((BroadcastPartTaskJava *) videoTaskIter->get())->isValidTaskFor(ts, videoChannel, q)) {
task = *videoTaskIter;
context->videoStreamTasks.erase(videoTaskIter);
break;
}
}
} else {
for (auto audioTaskIter = context->audioStreamTasks.begin(); audioTaskIter != context->audioStreamTasks.end(); audioTaskIter++) {
if (((BroadcastPartTaskJava *) audioTaskIter->get())->isValidTaskFor(ts, 0, q)) {
task = *audioTaskIter;
context->audioStreamTasks.erase(audioTaskIter);
break;
}
}
}
if (task != nullptr) {
if (byteBuffer != nullptr) {
auto buf = (uint8_t *) env->GetDirectBufferAddress(byteBuffer);
task->call(ts, responseTs, BroadcastPart::Status::Success, buf, size);
((BroadcastPartTaskJava *) task.get())->call(ts, responseTs, BroadcastPart::Status::Success, buf, size);
} else {
task->call(ts, responseTs, size == 0 ? BroadcastPart::Status::NotReady : BroadcastPart::Status::ResyncNeeded, nullptr, 0);
((BroadcastPartTaskJava *) task.get())->call(ts, responseTs, size == 0 ? BroadcastPart::Status::NotReady : BroadcastPart::Status::ResyncNeeded, nullptr, 0);
}
}
}
@ -886,7 +940,7 @@ JNIEXPORT void JNICALL Java_org_telegram_messenger_voip_NativeInstance_destroyVi
JNIEXPORT void JNICALL Java_org_telegram_messenger_voip_NativeInstance_switchCameraCapturer(JNIEnv *env, jclass clazz, jlong videoCapturer, jboolean front) {
auto capturer = reinterpret_cast<VideoCaptureInterface *>(videoCapturer);
capturer->switchToDevice(front ? "front" : "back");
capturer->switchToDevice(front ? "front" : "back", false);
}
JNIEXPORT void JNICALL Java_org_telegram_messenger_voip_NativeInstance_setVideoStateCapturer(JNIEnv *env, jclass clazz, jlong videoCapturer, jint videoState) {
@ -899,7 +953,7 @@ JNIEXPORT void JNICALL Java_org_telegram_messenger_voip_NativeInstance_switchCam
if (instance->_videoCapture == nullptr) {
return;
}
instance->_videoCapture->switchToDevice(front ? "front" : "back");
instance->_videoCapture->switchToDevice(front ? "front" : "back", false);
}
JNIEXPORT jboolean JNICALL Java_org_telegram_messenger_voip_NativeInstance_hasVideoCapturer(JNIEnv *env, jobject obj) {

View file

@ -204,6 +204,7 @@ public:
virtual void receiveSignalingData(const std::vector<uint8_t> &data) = 0;
virtual void setVideoCapture(std::shared_ptr<VideoCaptureInterface> videoCapture) = 0;
virtual void sendVideoDeviceUpdated() = 0;
virtual void setRequestedVideoAspect(float aspect) = 0;
virtual void stop(std::function<void(FinalState)> completion) = 0;

View file

@ -58,6 +58,12 @@ void InstanceImpl::setVideoCapture(std::shared_ptr<VideoCaptureInterface> videoC
});
}
void InstanceImpl::sendVideoDeviceUpdated() {
_manager->perform(RTC_FROM_HERE, [](Manager *manager) {
manager->sendVideoDeviceUpdated();
});
}
void InstanceImpl::setRequestedVideoAspect(float aspect) {
_manager->perform(RTC_FROM_HERE, [aspect](Manager *manager) {
manager->setRequestedVideoAspect(aspect);

View file

@ -21,6 +21,7 @@ public:
void receiveSignalingData(const std::vector<uint8_t> &data) override;
void setVideoCapture(std::shared_ptr<VideoCaptureInterface> videoCapture) override;
void sendVideoDeviceUpdated() override;
void setRequestedVideoAspect(float aspect) override;
void setNetworkType(NetworkType networkType) override;
void setMuteMicrophone(bool muteMicrophone) override;

View file

@ -316,6 +316,12 @@ void Manager::setVideoCapture(std::shared_ptr<VideoCaptureInterface> videoCaptur
});
}
void Manager::sendVideoDeviceUpdated() {
_mediaManager->perform(RTC_FROM_HERE, [](MediaManager *mediaManager) {
mediaManager->sendVideoDeviceUpdated();
});
}
void Manager::setRequestedVideoAspect(float aspect) {
_mediaManager->perform(RTC_FROM_HERE, [aspect](MediaManager *mediaManager) {
mediaManager->setRequestedVideoAspect(aspect);

View file

@ -29,6 +29,7 @@ public:
void start();
void receiveSignalingData(const std::vector<uint8_t> &data);
void setVideoCapture(std::shared_ptr<VideoCaptureInterface> videoCapture);
void sendVideoDeviceUpdated();
void setRequestedVideoAspect(float aspect);
void setMuteOutgoingAudio(bool mute);
void setIncomingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);

View file

@ -641,17 +641,18 @@ void MediaManager::setSendVideo(std::shared_ptr<VideoCaptureInterface> videoCapt
_videoCapture = videoCapture;
if (_videoCapture) {
_videoCapture->setPreferredAspectRatio(_preferredAspectRatio);
_isScreenCapture = _videoCapture->isScreenCapture();
const auto thread = _thread;
const auto weak = std::weak_ptr<MediaManager>(shared_from_this());
GetVideoCaptureAssumingSameThread(_videoCapture.get())->setStateUpdated([=](VideoState state) {
thread->PostTask(RTC_FROM_HERE, [=] {
if (const auto strong = weak.lock()) {
strong->setOutgoingVideoState(state);
}
});
});
const auto thread = _thread;
const auto weak = std::weak_ptr<MediaManager>(shared_from_this());
const auto object = GetVideoCaptureAssumingSameThread(_videoCapture.get());
_isScreenCapture = object->isScreenCapture();
object->setStateUpdated([=](VideoState state) {
thread->PostTask(RTC_FROM_HERE, [=] {
if (const auto strong = weak.lock()) {
strong->setOutgoingVideoState(state);
}
});
});
setOutgoingVideoState(VideoState::Active);
} else {
_isScreenCapture = false;
@ -681,6 +682,18 @@ void MediaManager::setSendVideo(std::shared_ptr<VideoCaptureInterface> videoCapt
checkIsReceivingVideoChanged(wasReceiving);
}
void MediaManager::sendVideoDeviceUpdated() {
if (!computeIsSendingVideo()) {
return;
}
const auto wasScreenCapture = _isScreenCapture;
const auto object = GetVideoCaptureAssumingSameThread(_videoCapture.get());
_isScreenCapture = object->isScreenCapture();
if (_isScreenCapture != wasScreenCapture) {
adjustBitratePreferences(true);
}
}
void MediaManager::setRequestedVideoAspect(float aspect) {
if (_localPreferredVideoAspectRatio != aspect) {
_localPreferredVideoAspectRatio = aspect;

View file

@ -58,6 +58,7 @@ public:
void setIsConnected(bool isConnected);
void notifyPacketSent(const rtc::SentPacket &sentPacket);
void setSendVideo(std::shared_ptr<VideoCaptureInterface> videoCapture);
void sendVideoDeviceUpdated();
void setRequestedVideoAspect(float aspect);
void setMuteOutgoingAudio(bool mute);
void setIncomingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);

View file

@ -39,8 +39,7 @@ public:
virtual ~VideoCaptureInterface();
virtual bool isScreenCapture() = 0;
virtual void switchToDevice(std::string deviceId) = 0;
virtual void switchToDevice(std::string deviceId, bool isScreenCapture) = 0;
virtual void setState(VideoState state) = 0;
virtual void setPreferredAspectRatio(float aspectRatio) = 0;
virtual void setOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) = 0;

View file

@ -9,11 +9,11 @@
namespace tgcalls {
VideoCaptureInterfaceObject::VideoCaptureInterfaceObject(std::string deviceId, std::shared_ptr<PlatformContext> platformContext, Threads &threads)
: _videoSource(PlatformInterface::SharedInstance()->makeVideoSource(threads.getMediaThread(), threads.getWorkerThread(), deviceId == "screen")) {
VideoCaptureInterfaceObject::VideoCaptureInterfaceObject(std::string deviceId, bool isScreenCapture, std::shared_ptr<PlatformContext> platformContext, Threads &threads)
: _videoSource(PlatformInterface::SharedInstance()->makeVideoSource(threads.getMediaThread(), threads.getWorkerThread(), isScreenCapture)) {
_platformContext = platformContext;
switchToDevice(deviceId);
switchToDevice(deviceId, isScreenCapture);
}
VideoCaptureInterfaceObject::~VideoCaptureInterfaceObject() {
@ -34,13 +34,18 @@ int VideoCaptureInterfaceObject::getRotation() {
}
}
void VideoCaptureInterfaceObject::switchToDevice(std::string deviceId) {
if (_videoCapturer && _currentUncroppedSink != nullptr) {
bool VideoCaptureInterfaceObject::isScreenCapture() {
return _isScreenCapture;
}
void VideoCaptureInterfaceObject::switchToDevice(std::string deviceId, bool isScreenCapture) {
if (_videoCapturer) {
_videoCapturer->setUncroppedOutput(nullptr);
}
_isScreenCapture = isScreenCapture;
if (_videoSource) {
//this should outlive the capturer
_videoCapturer = NULL;
_videoCapturer = nullptr;
_videoCapturer = PlatformInterface::SharedInstance()->makeVideoCapturer(_videoSource, deviceId, [this](VideoState state) {
if (this->_stateUpdated) {
this->_stateUpdated(state);
@ -164,23 +169,19 @@ void VideoCaptureInterfaceObject::setRotationUpdated(std::function<void(int)> ro
VideoCaptureInterfaceImpl::VideoCaptureInterfaceImpl(std::string deviceId, bool isScreenCapture, std::shared_ptr<PlatformContext> platformContext, std::shared_ptr<Threads> threads) :
_platformContext(platformContext),
_impl(threads->getMediaThread(), [deviceId, platformContext, threads]() {
return new VideoCaptureInterfaceObject(deviceId, platformContext, *threads);
}), _isScreenCapture(isScreenCapture) {
_impl(threads->getMediaThread(), [deviceId, isScreenCapture, platformContext, threads]() {
return new VideoCaptureInterfaceObject(deviceId, isScreenCapture, platformContext, *threads);
}) {
}
VideoCaptureInterfaceImpl::~VideoCaptureInterfaceImpl() = default;
void VideoCaptureInterfaceImpl::switchToDevice(std::string deviceId) {
_impl.perform(RTC_FROM_HERE, [deviceId](VideoCaptureInterfaceObject *impl) {
impl->switchToDevice(deviceId);
void VideoCaptureInterfaceImpl::switchToDevice(std::string deviceId, bool isScreenCapture) {
_impl.perform(RTC_FROM_HERE, [deviceId, isScreenCapture](VideoCaptureInterfaceObject *impl) {
impl->switchToDevice(deviceId, isScreenCapture);
});
}
bool VideoCaptureInterfaceImpl::isScreenCapture() {
return _isScreenCapture;
}
void VideoCaptureInterfaceImpl::withNativeImplementation(std::function<void(void *)> completion) {
_impl.perform(RTC_FROM_HERE, [completion](VideoCaptureInterfaceObject *impl) {
impl->withNativeImplementation(completion);

View file

@ -14,10 +14,10 @@ class Threads;
class VideoCaptureInterfaceObject {
public:
VideoCaptureInterfaceObject(std::string deviceId, std::shared_ptr<PlatformContext> platformContext, Threads &threads);
VideoCaptureInterfaceObject(std::string deviceId, bool isScreenCapture, std::shared_ptr<PlatformContext> platformContext, Threads &threads);
~VideoCaptureInterfaceObject();
void switchToDevice(std::string deviceId);
void switchToDevice(std::string deviceId, bool isScreenCapture);
void withNativeImplementation(std::function<void(void *)> completion);
void setState(VideoState state);
void setPreferredAspectRatio(float aspectRatio);
@ -29,10 +29,11 @@ public:
void setOnIsActiveUpdated(std::function<void(bool)> onIsActiveUpdated);
webrtc::VideoTrackSourceInterface *source();
int getRotation();
bool isScreenCapture();
private:
void updateAspectRateAdaptation();
rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> _videoSource;
std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> _currentUncroppedSink;
std::shared_ptr<PlatformContext> _platformContext;
@ -46,6 +47,7 @@ private:
VideoState _state = VideoState::Active;
float _preferredAspectRatio = 0.0f;
bool _shouldBeAdaptedToReceiverAspectRate = true;
bool _isScreenCapture = false;
};
class VideoCaptureInterfaceImpl : public VideoCaptureInterface {
@ -53,8 +55,7 @@ public:
VideoCaptureInterfaceImpl(std::string deviceId, bool isScreenCapture, std::shared_ptr<PlatformContext> platformContext, std::shared_ptr<Threads> threads);
virtual ~VideoCaptureInterfaceImpl();
bool isScreenCapture() override;
void switchToDevice(std::string deviceId) override;
void switchToDevice(std::string deviceId, bool isScreenCapture) override;
void withNativeImplementation(std::function<void(void *)> completion) override;
void setState(VideoState state) override;
void setPreferredAspectRatio(float aspectRatio) override;
@ -68,8 +69,6 @@ public:
private:
ThreadLocalObject<VideoCaptureInterfaceObject> _impl;
bool _isScreenCapture = false;
std::shared_ptr<PlatformContext> _platformContext;

View file

@ -1,4 +1,4 @@
#include "StreamingPart.h"
#include "AudioStreamingPart.h"
#include "rtc_base/logging.h"
#include "rtc_base/third_party/base64/base64.h"
@ -10,6 +10,7 @@ extern "C" {
}
#include <string>
#include <bitset>
#include <set>
#include <map>
@ -17,6 +18,28 @@ namespace tgcalls {
namespace {
uint32_t stringToUInt32(std::string const &string) {
std::stringstream stringStream(string);
uint32_t value = 0;
stringStream >> value;
return value;
}
template <typename Out>
void splitString(const std::string &s, char delim, Out result) {
std::istringstream iss(s);
std::string item;
while (std::getline(iss, item, delim)) {
*result++ = item;
}
}
std::vector<std::string> splitString(const std::string &s, char delim) {
std::vector<std::string> elems;
splitString(s, delim, std::back_inserter(elems));
return elems;
}
static absl::optional<uint32_t> readInt32(std::string const &data, int &offset) {
if (offset + 4 > data.length()) {
return absl::nullopt;
@ -139,9 +162,9 @@ struct ReadPcmResult {
int numChannels = 0;
};
class StreamingPartInternal {
class AudioStreamingPartInternal {
public:
StreamingPartInternal(std::vector<uint8_t> &&fileData) :
AudioStreamingPartInternal(std::vector<uint8_t> &&fileData) :
_avIoContext(std::move(fileData)) {
int ret = 0;
@ -201,6 +224,31 @@ public:
_channelUpdates = parseChannelUpdates(result, offset);
}
}
uint32_t videoChannelMask = 0;
entry = av_dict_get(inStream->metadata, "ACTIVE_MASK", nullptr, 0);
if (entry && entry->value) {
std::string sourceString = (const char *)entry->value;
videoChannelMask = stringToUInt32(sourceString);
}
std::vector<std::string> endpointList;
entry = av_dict_get(inStream->metadata, "ENDPOINTS", nullptr, 0);
if (entry && entry->value) {
std::string sourceString = (const char *)entry->value;
endpointList = splitString(sourceString, ' ');
}
std::bitset<32> videoChannels(videoChannelMask);
size_t endpointIndex = 0;
if (videoChannels.count() == endpointList.size()) {
for (size_t i = 0; i < videoChannels.size(); i++) {
if (videoChannels[i]) {
_endpointMapping.insert(std::make_pair(endpointList[endpointIndex], i));
endpointIndex++;
}
}
}
}
break;
@ -233,7 +281,7 @@ public:
}
}
~StreamingPartInternal() {
~AudioStreamingPartInternal() {
if (_frame) {
av_frame_unref(_frame);
}
@ -283,10 +331,14 @@ public:
return _channelCount;
}
std::vector<ChannelUpdate> const &getChannelUpdates() {
std::vector<ChannelUpdate> const &getChannelUpdates() const {
return _channelUpdates;
}
std::map<std::string, int32_t> getEndpointMapping() const {
return _endpointMapping;
}
private:
static int16_t sampleFloatToInt16(float sample) {
return av_clip_int16 (static_cast<int32_t>(lrint(sample*32767)));
@ -399,13 +451,14 @@ private:
int _channelCount = 0;
std::vector<ChannelUpdate> _channelUpdates;
std::map<std::string, int32_t> _endpointMapping;
std::vector<int16_t> _pcmBuffer;
int _pcmBufferSampleOffset = 0;
int _pcmBufferSampleSize = 0;
};
class StreamingPartState {
class AudioStreamingPartState {
struct ChannelMapping {
uint32_t ssrc = 0;
int channelIndex = 0;
@ -416,7 +469,7 @@ class StreamingPartState {
};
public:
StreamingPartState(std::vector<uint8_t> &&data) :
AudioStreamingPartState(std::vector<uint8_t> &&data) :
_parsedPart(std::move(data)) {
if (_parsedPart.getChannelUpdates().size() == 0) {
_didReadToEnd = true;
@ -431,14 +484,18 @@ public:
}
}
~StreamingPartState() {
~AudioStreamingPartState() {
}
std::map<std::string, int32_t> getEndpointMapping() const {
return _parsedPart.getEndpointMapping();
}
int getRemainingMilliseconds() const {
return _remainingMilliseconds;
}
std::vector<StreamingPart::StreamingPartChannel> get10msPerChannel() {
std::vector<AudioStreamingPart::StreamingPartChannel> get10msPerChannel() {
if (_didReadToEnd) {
return {};
}
@ -455,9 +512,9 @@ public:
return {};
}
std::vector<StreamingPart::StreamingPartChannel> resultChannels;
std::vector<AudioStreamingPart::StreamingPartChannel> resultChannels;
for (const auto ssrc : _allSsrcs) {
StreamingPart::StreamingPartChannel emptyPart;
AudioStreamingPart::StreamingPartChannel emptyPart;
emptyPart.ssrc = ssrc;
resultChannels.push_back(emptyPart);
}
@ -509,7 +566,7 @@ private:
}
private:
StreamingPartInternal _parsedPart;
AudioStreamingPartInternal _parsedPart;
std::set<uint32_t> _allSsrcs;
std::vector<int16_t> _pcm10ms;
@ -520,26 +577,30 @@ private:
bool _didReadToEnd = false;
};
StreamingPart::StreamingPart(std::vector<uint8_t> &&data) {
AudioStreamingPart::AudioStreamingPart(std::vector<uint8_t> &&data) {
if (!data.empty()) {
_state = new StreamingPartState(std::move(data));
_state = new AudioStreamingPartState(std::move(data));
}
}
StreamingPart::~StreamingPart() {
AudioStreamingPart::~AudioStreamingPart() {
if (_state) {
delete _state;
}
}
int StreamingPart::getRemainingMilliseconds() const {
std::map<std::string, int32_t> AudioStreamingPart::getEndpointMapping() const {
return _state ? _state->getEndpointMapping() : std::map<std::string, int32_t>();
}
int AudioStreamingPart::getRemainingMilliseconds() const {
return _state ? _state->getRemainingMilliseconds() : 0;
}
std::vector<StreamingPart::StreamingPartChannel> StreamingPart::get10msPerChannel() {
std::vector<AudioStreamingPart::StreamingPartChannel> AudioStreamingPart::get10msPerChannel() {
return _state
? _state->get10msPerChannel()
: std::vector<StreamingPart::StreamingPartChannel>();
: std::vector<AudioStreamingPart::StreamingPartChannel>();
}
}

View file

@ -0,0 +1,41 @@
#ifndef TGCALLS_AUDIO_STREAMING_PART_H
#define TGCALLS_AUDIO_STREAMING_PART_H
#include "absl/types/optional.h"
#include <vector>
#include <map>
#include <stdint.h>
namespace tgcalls {
class AudioStreamingPartState;
class AudioStreamingPart {
public:
struct StreamingPartChannel {
uint32_t ssrc = 0;
std::vector<int16_t> pcmData;
};
explicit AudioStreamingPart(std::vector<uint8_t> &&data);
~AudioStreamingPart();
AudioStreamingPart(const AudioStreamingPart&) = delete;
AudioStreamingPart(AudioStreamingPart&& other) {
_state = other._state;
other._state = nullptr;
}
AudioStreamingPart& operator=(const AudioStreamingPart&) = delete;
AudioStreamingPart& operator=(AudioStreamingPart&&) = delete;
std::map<std::string, int32_t> getEndpointMapping() const;
int getRemainingMilliseconds() const;
std::vector<StreamingPartChannel> get10msPerChannel();
private:
AudioStreamingPartState *_state = nullptr;
};
}
#endif

View file

@ -35,6 +35,7 @@
#include "modules/audio_coding/neteq/default_neteq_factory.h"
#include "modules/audio_coding/include/audio_coding_module.h"
#include "common_audio/include/audio_util.h"
#include "modules/audio_device/include/audio_device_data_observer.h"
#include "AudioFrame.h"
#include "ThreadLocalObject.h"
@ -44,9 +45,11 @@
#include "platform/PlatformInterface.h"
#include "LogSinkImpl.h"
#include "CodecSelectHelper.h"
#include "StreamingPart.h"
#include "AudioStreamingPart.h"
#include "VideoStreamingPart.h"
#include "AudioDeviceHelper.h"
#include "FakeAudioDeviceModule.h"
#include "StreamingMediaContext.h"
#include <mutex>
#include <random>
@ -1238,6 +1241,83 @@ std::function<webrtc::VideoTrackSourceInterface*()> videoCaptureToGetVideoSource
};
}
class AudioDeviceDataObserverShared {
public:
AudioDeviceDataObserverShared() {
}
~AudioDeviceDataObserverShared() {
}
void setStreamingContext(std::shared_ptr<StreamingMediaContext> streamingContext) {
_mutex.Lock();
_streamingContext = streamingContext;
_mutex.Unlock();
}
void mixAudio(int16_t *audio_samples, const size_t num_samples, const size_t num_channels, const uint32_t samples_per_sec) {
const auto numSamplesOut = num_samples * num_channels;
const auto numBytesOut = sizeof(int16_t) * numSamplesOut;
if (samples_per_sec != 48000) {
return;
}
if (_buffer.size() < numSamplesOut) {
_buffer.resize(numSamplesOut);
}
_mutex.Lock();
const auto context = _streamingContext;
_mutex.Unlock();
if (context) {
context->getAudio(_buffer.data(), num_samples, num_channels, samples_per_sec);
memcpy(audio_samples, _buffer.data(), numBytesOut);
}
}
private:
webrtc::Mutex _mutex;
std::vector<int16_t> _buffer;
std::shared_ptr<StreamingMediaContext> _streamingContext;
};
class AudioDeviceDataObserverImpl : public webrtc::AudioDeviceDataObserver {
public:
AudioDeviceDataObserverImpl(std::shared_ptr<AudioDeviceDataObserverShared> shared) :
_shared(shared) {
}
virtual ~AudioDeviceDataObserverImpl() {
}
virtual void OnCaptureData(const void* audio_samples,
const size_t num_samples,
const size_t bytes_per_sample,
const size_t num_channels,
const uint32_t samples_per_sec) override {
}
virtual void OnRenderData(const void* audio_samples,
const size_t num_samples,
const size_t bytes_per_sample,
const size_t num_channels,
const uint32_t samples_per_sec) override {
if (samples_per_sec != 48000) {
return;
}
if (bytes_per_sample != num_channels * 2) {
return;
}
if (_shared) {
_shared->mixAudio((int16_t *)audio_samples, num_samples, num_channels, samples_per_sec);
}
}
private:
std::shared_ptr<AudioDeviceDataObserverShared> _shared;
};
} // namespace
class GroupInstanceCustomInternal : public sigslot::has_slots<>, public std::enable_shared_from_this<GroupInstanceCustomInternal> {
@ -1248,7 +1328,9 @@ public:
_audioLevelsUpdated(descriptor.audioLevelsUpdated),
_onAudioFrame(descriptor.onAudioFrame),
_requestMediaChannelDescriptions(descriptor.requestMediaChannelDescriptions),
_requestBroadcastPart(descriptor.requestBroadcastPart),
_requestCurrentTime(descriptor.requestCurrentTime),
_requestAudioBroadcastPart(descriptor.requestAudioBroadcastPart),
_requestVideoBroadcastPart(descriptor.requestVideoBroadcastPart),
_videoCapture(descriptor.videoCapture),
_videoCaptureSink(new VideoSinkImpl("VideoCapture")),
_getVideoSource(descriptor.getVideoSource),
@ -1407,6 +1489,8 @@ public:
}
#endif
_audioDeviceDataObserverShared = std::make_shared<AudioDeviceDataObserverShared>();
_audioDeviceModule = createAudioDeviceModule();
if (!_audioDeviceModule) {
return;
@ -1763,8 +1847,14 @@ public:
}
void updateSsrcAudioLevel(uint32_t ssrc, uint8_t audioLevel, bool isSpeech) {
float mappedLevel = ((float)audioLevel) / (float)(0x7f);
mappedLevel = (fabs(1.0f - mappedLevel)) * 1.0f;
float mappedLevelDb = ((float)audioLevel) / (float)(0x7f);
//mappedLevelDb = fabs(1.0f - mappedLevelDb);
//float mappedLevel = pow(10.0f, mappedLevelDb * 0.1f);
//printf("mappedLevelDb: %f, mappedLevel: %f\n", mappedLevelDb, mappedLevel);
float mappedLevel = (fabs(1.0f - mappedLevelDb)) * 1.0f;
auto it = _audioLevels.find(ChannelId(ssrc));
if (it != _audioLevels.end()) {
@ -1795,18 +1885,18 @@ public:
return;
}
int64_t timestamp = rtc::TimeMillis();
int64_t maxSampleTimeout = 400;
//int64_t timestamp = rtc::TimeMillis();
//int64_t maxSampleTimeout = 400;
GroupLevelsUpdate levelsUpdate;
levelsUpdate.updates.reserve(strong->_audioLevels.size() + 1);
for (auto &it : strong->_audioLevels) {
if (it.second.value.level < 0.001f) {
/*if (it.second.value.level < 0.001f) {
continue;
}
if (it.second.timestamp <= timestamp - maxSampleTimeout) {
continue;
}
}*/
uint32_t effectiveSsrc = it.first.actualSsrc;
if (std::find_if(levelsUpdate.updates.begin(), levelsUpdate.updates.end(), [&](GroupLevelUpdate const &item) {
@ -1825,10 +1915,12 @@ public:
}
}
it.second.value.level *= 0.5f;
it.second.value.voice = false;
//it.second.value.level *= 0.5f;
//it.second.value.voice = false;
}
strong->_audioLevels.clear();
auto myAudioLevel = strong->_myAudioLevel;
myAudioLevel.isMuted = strong->_isMuted;
levelsUpdate.updates.push_back(GroupLevelUpdate{ 0, myAudioLevel });
@ -1906,26 +1998,7 @@ public:
}
void updateBroadcastNetworkStatus() {
auto timestamp = rtc::TimeMillis();
bool isBroadcastConnected = true;
if (_lastBroadcastPartReceivedTimestamp < timestamp - 3000) {
isBroadcastConnected = false;
}
if (_broadcastEnabledUntilRtcIsConnectedAtTimestamp) {
auto timestamp = rtc::TimeMillis();
if (std::abs(timestamp - _broadcastEnabledUntilRtcIsConnectedAtTimestamp.value()) > 3000) {
_broadcastEnabledUntilRtcIsConnectedAtTimestamp = absl::nullopt;
if (_currentRequestedBroadcastPart) {
if (_currentRequestedBroadcastPart->task) {
_currentRequestedBroadcastPart->task->cancel();
}
_currentRequestedBroadcastPart.reset();
}
isBroadcastConnected = false;
}
}
if (isBroadcastConnected != _isBroadcastConnected) {
_isBroadcastConnected = isBroadcastConnected;
@ -1933,214 +2006,6 @@ public:
}
}
absl::optional<DecodedBroadcastPart> getNextBroadcastPart() {
while (true) {
if (_sourceBroadcastParts.size() != 0) {
auto readChannels = _sourceBroadcastParts[0]->get10msPerChannel();
if (readChannels.size() == 0 || readChannels[0].pcmData.size() == 0) {
_sourceBroadcastParts.erase(_sourceBroadcastParts.begin());
} else {
std::vector<DecodedBroadcastPart::DecodedBroadcastPartChannel> channels;
int numSamples = (int)readChannels[0].pcmData.size();
for (auto &readChannel : readChannels) {
DecodedBroadcastPart::DecodedBroadcastPartChannel channel;
channel.ssrc = readChannel.ssrc;
channel.pcmData = std::move(readChannel.pcmData);
channels.push_back(channel);
}
absl::optional<DecodedBroadcastPart> decodedPart;
decodedPart.emplace(numSamples, std::move(channels));
return decodedPart;
}
} else {
return absl::nullopt;
}
}
return absl::nullopt;
}
void commitBroadcastPackets() {
int numMillisecondsInQueue = 0;
for (const auto &part : _sourceBroadcastParts) {
numMillisecondsInQueue += part->getRemainingMilliseconds();
}
int commitMilliseconds = 20;
if (numMillisecondsInQueue > 1000) {
commitMilliseconds = numMillisecondsInQueue - 1000;
}
std::set<ChannelId> channelsWithActivity;
for (int msIndex = 0; msIndex < commitMilliseconds; msIndex += 10) {
auto packetData = getNextBroadcastPart();
if (!packetData) {
break;
}
for (const auto &decodedChannel : packetData->channels) {
if (decodedChannel.ssrc == _outgoingAudioSsrc) {
continue;
}
ChannelId channelSsrc = ChannelId(decodedChannel.ssrc + 1000, decodedChannel.ssrc);
if (_incomingAudioChannels.find(channelSsrc) == _incomingAudioChannels.end()) {
addIncomingAudioChannel(channelSsrc, true);
}
webrtc::RtpPacket packet(nullptr, 12 + decodedChannel.pcmData.size() * 2);
packet.SetMarker(false);
packet.SetPayloadType(112);
uint16_t packetSeq = 0;
auto it = _broadcastSeqBySsrc.find(channelSsrc.networkSsrc);
if (it == _broadcastSeqBySsrc.end()) {
packetSeq = 1000;
_broadcastSeqBySsrc.insert(std::make_pair(channelSsrc.networkSsrc, packetSeq));
} else {
it->second++;
packetSeq = it->second;
}
packet.SetSequenceNumber(packetSeq);
packet.SetTimestamp(_broadcastTimestamp);
packet.SetSsrc(channelSsrc.networkSsrc);
uint8_t *payload = packet.SetPayloadSize(decodedChannel.pcmData.size() * 2);
memcpy(payload, decodedChannel.pcmData.data(), decodedChannel.pcmData.size() * 2);
for (int i = 0; i < decodedChannel.pcmData.size() * 2; i += 2) {
auto temp = payload[i];
payload[i] = payload[i + 1];
payload[i + 1] = temp;
}
auto buffer = packet.Buffer();
_threads->getWorkerThread()->Invoke<void>(RTC_FROM_HERE, [this, buffer]() {
_call->Receiver()->DeliverPacket(webrtc::MediaType::AUDIO, buffer, -1);
});
channelsWithActivity.insert(ChannelId(channelSsrc));
}
for (auto channelId : channelsWithActivity) {
const auto it = _incomingAudioChannels.find(channelId);
if (it != _incomingAudioChannels.end()) {
it->second->updateActivity();
}
}
_broadcastTimestamp += packetData->numSamples;
}
}
void requestNextBroadcastPart() {
const auto weak = std::weak_ptr<GroupInstanceCustomInternal>(shared_from_this());
auto requestedPartId = _nextBroadcastTimestampMilliseconds;
auto task = _requestBroadcastPart(_platformContext, requestedPartId, _broadcastPartDurationMilliseconds, [weak, threads = _threads, requestedPartId](BroadcastPart &&part) {
threads->getMediaThread()->PostTask(RTC_FROM_HERE, [weak, part = std::move(part), requestedPartId]() mutable {
auto strong = weak.lock();
if (!strong) {
return;
}
if (strong->_currentRequestedBroadcastPart && strong->_currentRequestedBroadcastPart->timestamp == requestedPartId) {
strong->onReceivedNextBroadcastPart(std::move(part));
}
});
});
if (_currentRequestedBroadcastPart) {
if (_currentRequestedBroadcastPart->task) {
_currentRequestedBroadcastPart->task->cancel();
}
_currentRequestedBroadcastPart.reset();
}
_currentRequestedBroadcastPart.emplace(requestedPartId, task);
}
void requestNextBroadcastPartWithDelay(int timeoutMs) {
const auto weak = std::weak_ptr<GroupInstanceCustomInternal>(shared_from_this());
_threads->getMediaThread()->PostDelayedTask(RTC_FROM_HERE, [weak]() {
auto strong = weak.lock();
if (!strong) {
return;
}
strong->requestNextBroadcastPart();
}, timeoutMs);
}
void onReceivedNextBroadcastPart(BroadcastPart &&part) {
_currentRequestedBroadcastPart.reset();
if (_connectionMode != GroupConnectionMode::GroupConnectionModeBroadcast && !_broadcastEnabledUntilRtcIsConnectedAtTimestamp) {
return;
}
int64_t responseTimestampMilliseconds = (int64_t)(part.responseTimestamp * 1000.0);
int64_t responseTimestampBoundary = (responseTimestampMilliseconds / _broadcastPartDurationMilliseconds) * _broadcastPartDurationMilliseconds;
switch (part.status) {
case BroadcastPart::Status::Success: {
_lastBroadcastPartReceivedTimestamp = rtc::TimeMillis();
updateBroadcastNetworkStatus();
if (std::abs((int64_t)(part.responseTimestamp * 1000.0) - part.timestampMilliseconds) > 2000) {
_nextBroadcastTimestampMilliseconds = std::max(part.timestampMilliseconds + _broadcastPartDurationMilliseconds, responseTimestampBoundary);
} else {
_nextBroadcastTimestampMilliseconds = part.timestampMilliseconds + _broadcastPartDurationMilliseconds;
}
_sourceBroadcastParts.emplace_back(new StreamingPart(std::move(part.oggData)));
break;
}
case BroadcastPart::Status::NotReady: {
_nextBroadcastTimestampMilliseconds = part.timestampMilliseconds;
break;
}
case BroadcastPart::Status::ResyncNeeded: {
_nextBroadcastTimestampMilliseconds = responseTimestampBoundary;
break;
}
default: {
//RTC_FATAL() << "Unknown part.status";
break;
}
}
int64_t nextDelay = _nextBroadcastTimestampMilliseconds - responseTimestampMilliseconds;
int clippedDelay = std::max((int)nextDelay, 100);
//RTC_LOG(LS_INFO) << "requestNextBroadcastPartWithDelay(" << clippedDelay << ") (from " << nextDelay << ")";
requestNextBroadcastPartWithDelay(clippedDelay);
}
void beginBroadcastPartsDecodeTimer(int timeoutMs) {
const auto weak = std::weak_ptr<GroupInstanceCustomInternal>(shared_from_this());
_threads->getMediaThread()->PostDelayedTask(RTC_FROM_HERE, [weak]() {
auto strong = weak.lock();
if (!strong) {
return;
}
if (strong->_connectionMode != GroupConnectionMode::GroupConnectionModeBroadcast && !strong->_broadcastEnabledUntilRtcIsConnectedAtTimestamp) {
return;
}
strong->commitBroadcastPackets();
strong->beginBroadcastPartsDecodeTimer(20);
}, timeoutMs);
}
void configureVideoParams() {
if (!_sharedVideoInformation) {
@ -2320,11 +2185,10 @@ public:
if (_broadcastEnabledUntilRtcIsConnectedAtTimestamp) {
_broadcastEnabledUntilRtcIsConnectedAtTimestamp = absl::nullopt;
if (_currentRequestedBroadcastPart) {
if (_currentRequestedBroadcastPart->task) {
_currentRequestedBroadcastPart->task->cancel();
}
_currentRequestedBroadcastPart.reset();
if (_streamingContext) {
_streamingContext.reset();
_audioDeviceDataObserverShared->setStreamingContext(nullptr);
}
}
@ -2710,11 +2574,9 @@ public:
if (keepBroadcastIfWasEnabled) {
_broadcastEnabledUntilRtcIsConnectedAtTimestamp = rtc::TimeMillis();
} else {
if (_currentRequestedBroadcastPart) {
if (_currentRequestedBroadcastPart->task) {
_currentRequestedBroadcastPart->task->cancel();
}
_currentRequestedBroadcastPart.reset();
if (_streamingContext) {
_streamingContext.reset();
_audioDeviceDataObserverShared->setStreamingContext(nullptr);
}
}
}
@ -2743,12 +2605,50 @@ public:
break;
}
case GroupConnectionMode::GroupConnectionModeBroadcast: {
_broadcastTimestamp = 100001;
_isBroadcastConnected = false;
beginBroadcastPartsDecodeTimer(0);
requestNextBroadcastPart();
if (!_streamingContext) {
StreamingMediaContext::StreamingMediaContextArguments arguments;
const auto weak = std::weak_ptr<GroupInstanceCustomInternal>(shared_from_this());
arguments.threads = _threads;
arguments.platformContext = _platformContext;
arguments.requestCurrentTime = _requestCurrentTime;
arguments.requestAudioBroadcastPart = _requestAudioBroadcastPart;
arguments.requestVideoBroadcastPart = _requestVideoBroadcastPart;
arguments.updateAudioLevel = [weak, threads = _threads](uint32_t ssrc, float level, bool isSpeech) {
assert(threads->getMediaThread()->IsCurrent());
auto strong = weak.lock();
if (!strong) {
return;
}
InternalGroupLevelValue updated;
updated.value.level = level;
updated.value.voice = isSpeech;
updated.timestamp = rtc::TimeMillis();
strong->_audioLevels.insert(std::make_pair(ChannelId(ssrc), std::move(updated)));
};
_streamingContext = std::make_shared<StreamingMediaContext>(std::move(arguments));
for (const auto &it : _pendingVideoSinks) {
for (const auto &sink : it.second) {
_streamingContext->addVideoSink(it.first.endpointId, sink);
}
}
for (const auto &it : _volumeBySsrc) {
_streamingContext->setVolume(it.first, it.second);
}
std::vector<StreamingMediaContext::VideoChannel> streamingVideoChannels;
for (const auto &it : _pendingRequestedVideo) {
streamingVideoChannels.emplace_back(it.maxQuality, it.endpointId);
}
_streamingContext->setActiveVideoChannels(streamingVideoChannels);
_audioDeviceDataObserverShared->setStreamingContext(_streamingContext);
}
break;
}
@ -3033,6 +2933,10 @@ public:
} else {
_pendingVideoSinks[VideoChannelId(endpointId)].push_back(sink);
}
if (_streamingContext) {
_streamingContext->addVideoSink(endpointId, sink);
}
}
}
@ -3233,9 +3137,21 @@ public:
if (it != _incomingAudioChannels.end()) {
it->second->setVolume(volume);
}
if (_streamingContext) {
_streamingContext->setVolume(ssrc, volume);
}
}
void setRequestedVideoChannels(std::vector<VideoChannelDescription> &&requestedVideoChannels) {
if (_streamingContext) {
std::vector<StreamingMediaContext::VideoChannel> streamingVideoChannels;
for (const auto &it : requestedVideoChannels) {
streamingVideoChannels.emplace_back(it.maxQuality, it.endpointId);
}
_streamingContext->setActiveVideoChannels(streamingVideoChannels);
}
if (!_sharedVideoInformation) {
_pendingRequestedVideo = std::move(requestedVideoChannels);
return;
@ -3312,14 +3228,22 @@ public:
private:
rtc::scoped_refptr<WrappedAudioDeviceModule> createAudioDeviceModule() {
auto audioDeviceDataObserverShared = _audioDeviceDataObserverShared;
const auto create = [&](webrtc::AudioDeviceModule::AudioLayer layer) {
return webrtc::AudioDeviceModule::Create(
layer,
_taskQueueFactory.get());
};
const auto check = [&](const rtc::scoped_refptr<webrtc::AudioDeviceModule> &result) -> rtc::scoped_refptr<WrappedAudioDeviceModule> {
if (result && result->Init() == 0) {
return PlatformInterface::SharedInstance()->wrapAudioDeviceModule(result);
if (!result) {
return nullptr;
}
auto audioDeviceObserver = std::make_unique<AudioDeviceDataObserverImpl>(audioDeviceDataObserverShared);
auto module = webrtc::CreateAudioDeviceWithDataObserver(result, std::move(audioDeviceObserver));
if (module->Init() == 0) {
return PlatformInterface::SharedInstance()->wrapAudioDeviceModule(module);
} else {
return nullptr;
}
@ -3348,7 +3272,9 @@ private:
std::function<void(GroupLevelsUpdate const &)> _audioLevelsUpdated;
std::function<void(uint32_t, const AudioFrame &)> _onAudioFrame;
std::function<std::shared_ptr<RequestMediaChannelDescriptionTask>(std::vector<uint32_t> const &, std::function<void(std::vector<MediaChannelDescription> &&)>)> _requestMediaChannelDescriptions;
std::function<std::shared_ptr<BroadcastPartTask>(std::shared_ptr<PlatformContext>, int64_t, int64_t, std::function<void(BroadcastPart &&)>)> _requestBroadcastPart;
std::function<std::shared_ptr<BroadcastPartTask>(std::function<void(int64_t)>)> _requestCurrentTime;
std::function<std::shared_ptr<BroadcastPartTask>(std::shared_ptr<PlatformContext>, int64_t, int64_t, std::function<void(BroadcastPart &&)>)> _requestAudioBroadcastPart;
std::function<std::shared_ptr<BroadcastPartTask>(std::shared_ptr<PlatformContext>, int64_t, int64_t, int32_t, VideoChannelDescription::Quality, std::function<void(BroadcastPart &&)>)> _requestVideoBroadcastPart;
std::shared_ptr<VideoCaptureInterface> _videoCapture;
std::shared_ptr<VideoSinkImpl> _videoCaptureSink;
std::function<webrtc::VideoTrackSourceInterface*()> _getVideoSource;
@ -3371,6 +3297,7 @@ private:
std::unique_ptr<webrtc::Call> _call;
webrtc::FieldTrialBasedConfig _fieldTrials;
webrtc::LocalAudioSinkAdapter _audioSource;
std::shared_ptr<AudioDeviceDataObserverShared> _audioDeviceDataObserverShared;
rtc::scoped_refptr<WrappedAudioDeviceModule> _audioDeviceModule;
std::function<rtc::scoped_refptr<webrtc::AudioDeviceModule>(webrtc::TaskQueueFactory*)> _createAudioDeviceModule;
std::string _initialInputDeviceId;
@ -3419,14 +3346,6 @@ private:
absl::optional<GroupJoinVideoInformation> _sharedVideoInformation;
int64_t _broadcastPartDurationMilliseconds = 500;
std::vector<std::unique_ptr<StreamingPart>> _sourceBroadcastParts;
std::map<uint32_t, uint16_t> _broadcastSeqBySsrc;
uint32_t _broadcastTimestamp = 0;
int64_t _nextBroadcastTimestampMilliseconds = 0;
absl::optional<RequestedBroadcastPart> _currentRequestedBroadcastPart;
int64_t _lastBroadcastPartReceivedTimestamp = 0;
std::vector<float> _externalAudioSamples;
webrtc::Mutex _externalAudioSamplesMutex;
std::shared_ptr<ExternalAudioRecorder> _externalAudioRecorder;
@ -3437,6 +3356,8 @@ private:
bool _isDataChannelOpen = false;
GroupNetworkState _effectiveNetworkState;
std::shared_ptr<StreamingMediaContext> _streamingContext;
rtc::scoped_refptr<webrtc::PendingTaskSafetyFlag> _workerThreadSafery;
rtc::scoped_refptr<webrtc::PendingTaskSafetyFlag> _networkThreadSafery;

View file

@ -57,6 +57,9 @@ public:
};
struct BroadcastPart {
struct VideoParams {
};
enum class Status {
Success,
NotReady,
@ -66,7 +69,7 @@ struct BroadcastPart {
int64_t timestampMilliseconds = 0;
double responseTimestamp = 0;
Status status = Status::NotReady;
std::vector<uint8_t> oggData;
std::vector<uint8_t> data;
};
enum class GroupConnectionMode {
@ -150,7 +153,9 @@ struct GroupInstanceDescriptor {
std::function<rtc::scoped_refptr<webrtc::AudioDeviceModule>(webrtc::TaskQueueFactory*)> createAudioDeviceModule;
std::shared_ptr<VideoCaptureInterface> videoCapture; // deprecated
std::function<webrtc::VideoTrackSourceInterface*()> getVideoSource;
std::function<std::shared_ptr<BroadcastPartTask>(std::shared_ptr<PlatformContext>, int64_t, int64_t, std::function<void(BroadcastPart &&)>)> requestBroadcastPart;
std::function<std::shared_ptr<BroadcastPartTask>(std::function<void(int64_t)>)> requestCurrentTime;
std::function<std::shared_ptr<BroadcastPartTask>(std::shared_ptr<PlatformContext>, int64_t, int64_t, std::function<void(BroadcastPart &&)>)> requestAudioBroadcastPart;
std::function<std::shared_ptr<BroadcastPartTask>(std::shared_ptr<PlatformContext>, int64_t, int64_t, int32_t, VideoChannelDescription::Quality, std::function<void(BroadcastPart &&)>)> requestVideoBroadcastPart;
int outgoingAudioBitrateKbit{32};
bool disableOutgoingAudioProcessing{false};
VideoContentType videoContentType{VideoContentType::None};

View file

@ -0,0 +1,869 @@
#include "StreamingMediaContext.h"
#include "AudioStreamingPart.h"
#include "VideoStreamingPart.h"
#include "absl/types/optional.h"
#include "rtc_base/thread.h"
#include "rtc_base/time_utils.h"
#include "absl/types/variant.h"
#include "rtc_base/logging.h"
#include "rtc_base/synchronization/mutex.h"
#include "common_audio/ring_buffer.h"
#include "modules/audio_mixer/frame_combiner.h"
#include "modules/audio_processing/agc2/vad_with_level.h"
#include "modules/audio_processing/audio_buffer.h"
#include "api/video/video_sink_interface.h"
#include "audio/utility/audio_frame_operations.h"
namespace tgcalls {
namespace {
struct PendingAudioSegmentData {
};
struct PendingVideoSegmentData {
int32_t channelId = 0;
VideoChannelDescription::Quality quality = VideoChannelDescription::Quality::Thumbnail;
PendingVideoSegmentData(int32_t channelId_, VideoChannelDescription::Quality quality_) :
channelId(channelId_),
quality(quality_) {
}
};
struct PendingMediaSegmentPartResult {
std::vector<uint8_t> data;
explicit PendingMediaSegmentPartResult(std::vector<uint8_t> &&data_) :
data(std::move(data_)) {
}
};
struct PendingMediaSegmentPart {
absl::variant<PendingAudioSegmentData, PendingVideoSegmentData> typeData;
int64_t minRequestTimestamp = 0;
std::shared_ptr<BroadcastPartTask> task;
std::shared_ptr<PendingMediaSegmentPartResult> result;
};
struct PendingMediaSegment {
int64_t timestamp = 0;
std::vector<std::shared_ptr<PendingMediaSegmentPart>> parts;
};
struct VideoSegment {
VideoChannelDescription::Quality quality;
std::shared_ptr<VideoStreamingPart> part;
double lastFramePts = -1.0;
int _displayedFrames = 0;
bool isPlaying = false;
std::shared_ptr<PendingMediaSegmentPart> pendingVideoQualityUpdatePart;
};
struct MediaSegment {
int64_t timestamp = 0;
int64_t duration = 0;
std::shared_ptr<AudioStreamingPart> audio;
std::vector<std::shared_ptr<VideoSegment>> video;
};
class SampleRingBuffer {
public:
SampleRingBuffer(size_t size) {
_buffer = WebRtc_CreateBuffer(size, sizeof(int16_t));
}
~SampleRingBuffer() {
if (_buffer) {
WebRtc_FreeBuffer(_buffer);
}
}
size_t availableForWriting() {
return WebRtc_available_write(_buffer);
}
size_t write(int16_t const *samples, size_t count) {
return WebRtc_WriteBuffer(_buffer, samples, count);
}
size_t read(int16_t *samples, size_t count) {
return WebRtc_ReadBuffer(_buffer, nullptr, samples, count);
}
private:
RingBuffer *_buffer = nullptr;
};
static const int kVadResultHistoryLength = 8;
class VadHistory {
private:
float _vadResultHistory[kVadResultHistoryLength];
public:
VadHistory() {
for (int i = 0; i < kVadResultHistoryLength; i++) {
_vadResultHistory[i] = 0.0f;
}
}
~VadHistory() {
}
bool update(float vadProbability) {
for (int i = 1; i < kVadResultHistoryLength; i++) {
_vadResultHistory[i - 1] = _vadResultHistory[i];
}
_vadResultHistory[kVadResultHistoryLength - 1] = vadProbability;
float movingAverage = 0.0f;
for (int i = 0; i < kVadResultHistoryLength; i++) {
movingAverage += _vadResultHistory[i];
}
movingAverage /= (float)kVadResultHistoryLength;
bool vadResult = false;
if (movingAverage > 0.8f) {
vadResult = true;
}
return vadResult;
}
};
class CombinedVad {
private:
std::unique_ptr<webrtc::VadLevelAnalyzer> _vadWithLevel;
VadHistory _history;
public:
CombinedVad() {
_vadWithLevel = std::make_unique<webrtc::VadLevelAnalyzer>(500, webrtc::GetAvailableCpuFeatures());
}
~CombinedVad() {
}
bool update(webrtc::AudioBuffer *buffer) {
if (buffer->num_channels() <= 0) {
return _history.update(0.0f);
}
webrtc::AudioFrameView<float> frameView(buffer->channels(), buffer->num_channels(), buffer->num_frames());
float peak = 0.0f;
for (const auto &x : frameView.channel(0)) {
peak = std::max(std::fabs(x), peak);
}
if (peak <= 0.01f) {
return _history.update(false);
}
auto result = _vadWithLevel->AnalyzeFrame(frameView);
return _history.update(result.speech_probability);
}
bool update() {
return _history.update(0.0f);
}
};
class SparseVad {
public:
SparseVad() {
}
std::pair<float, bool> update(webrtc::AudioBuffer *buffer) {
_sampleCount += buffer->num_frames();
if (_sampleCount >= 400) {
_sampleCount = 0;
_currentValue = _vad.update(buffer);
}
float currentPeak = 0.0;
float *samples = buffer->channels()[0];
for (int i = 0; i < buffer->num_frames(); i++) {
float sample = samples[i];
if (sample < 0.0f) {
sample = -sample;
}
if (_peak < sample) {
_peak = sample;
}
if (currentPeak < sample) {
currentPeak = sample;
}
_peakCount += 1;
}
if (_peakCount >= 4400) {
float norm = 8000.0f;
_currentLevel = ((float)(_peak)) / norm;
_peak = 0;
_peakCount = 0;
}
return std::make_pair(_currentLevel, _currentValue);
}
private:
CombinedVad _vad;
bool _currentValue = false;
size_t _sampleCount = 0;
int _peakCount = 0;
float _peak = 0.0;
float _currentLevel = 0.0;
};
}
class StreamingMediaContextPrivate : public std::enable_shared_from_this<StreamingMediaContextPrivate> {
public:
StreamingMediaContextPrivate(StreamingMediaContext::StreamingMediaContextArguments &&arguments) :
_threads(arguments.threads),
_requestCurrentTime(arguments.requestCurrentTime),
_requestAudioBroadcastPart(arguments.requestAudioBroadcastPart),
_requestVideoBroadcastPart(arguments.requestVideoBroadcastPart),
_updateAudioLevel(arguments.updateAudioLevel),
_audioRingBuffer(_audioDataRingBufferMaxSize),
_audioFrameCombiner(false),
_platformContext(arguments.platformContext) {
}
~StreamingMediaContextPrivate() {
}
void start() {
beginRenderTimer(0);
}
void beginRenderTimer(int timeoutMs) {
const auto weak = std::weak_ptr<StreamingMediaContextPrivate>(shared_from_this());
_threads->getMediaThread()->PostDelayedTask(RTC_FROM_HERE, [weak]() {
auto strong = weak.lock();
if (!strong) {
return;
}
strong->render();
strong->beginRenderTimer((int)(1.0 * 1000.0 / 120.0));
}, timeoutMs);
}
void render() {
int64_t absoluteTimestamp = rtc::TimeMillis();
while (true) {
if (_waitForBufferredMillisecondsBeforeRendering) {
if (getAvailableBufferDuration() < _waitForBufferredMillisecondsBeforeRendering.value()) {
break;
} else {
_waitForBufferredMillisecondsBeforeRendering = absl::nullopt;
}
}
if (_availableSegments.empty()) {
_playbackReferenceTimestamp = 0;
_waitForBufferredMillisecondsBeforeRendering = _segmentBufferDuration + _segmentDuration;
break;
}
if (_playbackReferenceTimestamp == 0) {
_playbackReferenceTimestamp = absoluteTimestamp;
}
double relativeTimestamp = ((double)(absoluteTimestamp - _playbackReferenceTimestamp)) / 1000.0;
auto segment = _availableSegments[0];
double segmentDuration = ((double)segment->duration) / 1000.0;
for (auto &videoSegment : segment->video) {
videoSegment->isPlaying = true;
cancelPendingVideoQualityUpdate(videoSegment);
auto frame = videoSegment->part->getFrameAtRelativeTimestamp(relativeTimestamp);
if (frame) {
if (videoSegment->lastFramePts != frame->pts) {
videoSegment->lastFramePts = frame->pts;
videoSegment->_displayedFrames += 1;
auto sinkList = _videoSinks.find(frame->endpointId);
if (sinkList != _videoSinks.end()) {
for (const auto &weakSink : sinkList->second) {
auto sink = weakSink.lock();
if (sink) {
sink->OnFrame(frame->frame);
}
}
}
}
}
}
if (segment->audio) {
const auto available = [&] {
_audioDataMutex.Lock();
const auto result = (_audioRingBuffer.availableForWriting() >= 480);
_audioDataMutex.Unlock();
return result;
};
while (available()) {
auto audioChannels = segment->audio->get10msPerChannel();
if (audioChannels.empty()) {
break;
}
std::vector<webrtc::AudioFrame *> audioFrames;
for (const auto &audioChannel : audioChannels) {
webrtc::AudioFrame *frame = new webrtc::AudioFrame();
frame->UpdateFrame(0, audioChannel.pcmData.data(), audioChannel.pcmData.size(), 48000, webrtc::AudioFrame::SpeechType::kNormalSpeech, webrtc::AudioFrame::VADActivity::kVadActive);
auto volumeIt = _volumeBySsrc.find(audioChannel.ssrc);
if (volumeIt != _volumeBySsrc.end()) {
double outputGain = volumeIt->second;
if (outputGain < 0.99f || outputGain > 1.01f) {
webrtc::AudioFrameOperations::ScaleWithSat(outputGain, frame);
}
}
audioFrames.push_back(frame);
processAudioLevel(audioChannel.ssrc, audioChannel.pcmData);
}
webrtc::AudioFrame frameOut;
_audioFrameCombiner.Combine(audioFrames, 1, 48000, audioFrames.size(), &frameOut);
for (webrtc::AudioFrame *frame : audioFrames) {
delete frame;
}
_audioDataMutex.Lock();
_audioRingBuffer.write(frameOut.data(), frameOut.samples_per_channel());
_audioDataMutex.Unlock();
}
}
if (relativeTimestamp >= segmentDuration) {
_playbackReferenceTimestamp += segment->duration;
if (segment->audio && segment->audio->getRemainingMilliseconds() > 0) {
RTC_LOG(LS_INFO) << "render: discarding " << segment->audio->getRemainingMilliseconds() << " ms of audio at the end of a segment";
}
if (!segment->video.empty()) {
if (segment->video[0]->part->getActiveEndpointId()) {
RTC_LOG(LS_INFO) << "render: discarding video frames at the end of a segment (displayed " << segment->video[0]->_displayedFrames << " frames)";
}
}
_availableSegments.erase(_availableSegments.begin());
}
break;
}
requestSegmentsIfNeeded();
checkPendingSegments();
}
void processAudioLevel(uint32_t ssrc, std::vector<int16_t> const &samples) {
if (!_updateAudioLevel) {
return;
}
webrtc::AudioBuffer buffer(48000, 1, 48000, 1, 48000, 1);
webrtc::StreamConfig config(48000, 1);
buffer.CopyFrom(samples.data(), config);
std::pair<float, bool> vadResult = std::make_pair(0.0f, false);
auto vad = _audioVadMap.find(ssrc);
if (vad == _audioVadMap.end()) {
auto newVad = std::make_unique<SparseVad>();
vadResult = newVad->update(&buffer);
_audioVadMap.insert(std::make_pair(ssrc, std::move(newVad)));
} else {
vadResult = vad->second->update(&buffer);
}
_updateAudioLevel(ssrc, vadResult.first, vadResult.second);
}
void getAudio(int16_t *audio_samples, const size_t num_samples, const size_t num_channels, const uint32_t samples_per_sec) {
int16_t *buffer = nullptr;
if (num_channels == 1) {
buffer = audio_samples;
} else {
if (_tempAudioBuffer.size() < num_samples) {
_tempAudioBuffer.resize(num_samples);
}
buffer = _tempAudioBuffer.data();
}
_audioDataMutex.Lock();
size_t readSamples = _audioRingBuffer.read(buffer, num_samples);
_audioDataMutex.Unlock();
if (num_channels != 1) {
for (size_t sampleIndex = 0; sampleIndex < readSamples; sampleIndex++) {
for (size_t channelIndex = 0; channelIndex < num_channels; channelIndex++) {
audio_samples[sampleIndex * num_channels + channelIndex] = _tempAudioBuffer[sampleIndex];
}
}
}
if (readSamples < num_samples) {
memset(audio_samples + readSamples * num_channels, 0, (num_samples - readSamples) * num_channels * sizeof(int16_t));
}
}
int64_t getAvailableBufferDuration() {
int64_t result = 0;
for (const auto &segment : _availableSegments) {
result += segment->duration;
}
return (int)result;
}
void discardAllPendingSegments() {
for (size_t i = 0; i < _pendingSegments.size(); i++) {
for (const auto &it : _pendingSegments[i]->parts) {
if (it->task) {
it->task->cancel();
}
}
}
_pendingSegments.clear();
}
void requestSegmentsIfNeeded() {
while (true) {
if (_nextSegmentTimestamp == 0) {
if (_pendingSegments.size() >= 1) {
break;
}
} else {
int64_t availableAndRequestedSegmentsDuration = 0;
availableAndRequestedSegmentsDuration += getAvailableBufferDuration();
availableAndRequestedSegmentsDuration += _pendingSegments.size() * _segmentDuration;
if (availableAndRequestedSegmentsDuration > _segmentBufferDuration) {
break;
}
}
auto pendingSegment = std::make_shared<PendingMediaSegment>();
pendingSegment->timestamp = _nextSegmentTimestamp;
if (_nextSegmentTimestamp != 0) {
_nextSegmentTimestamp += _segmentDuration;
}
auto audio = std::make_shared<PendingMediaSegmentPart>();
audio->typeData = PendingAudioSegmentData();
audio->minRequestTimestamp = 0;
pendingSegment->parts.push_back(audio);
for (const auto &videoChannel : _activeVideoChannels) {
auto channelIdIt = _currentEndpointMapping.find(videoChannel.endpoint);
if (channelIdIt == _currentEndpointMapping.end()) {
continue;
}
int32_t channelId = channelIdIt->second + 1;
auto video = std::make_shared<PendingMediaSegmentPart>();
video->typeData = PendingVideoSegmentData(channelId, videoChannel.quality);
video->minRequestTimestamp = 0;
pendingSegment->parts.push_back(video);
}
_pendingSegments.push_back(pendingSegment);
if (_nextSegmentTimestamp == 0) {
break;
}
}
}
void requestPendingVideoQualityUpdate(std::shared_ptr<VideoSegment> segment, int64_t timestamp) {
if (segment->isPlaying) {
return;
}
auto segmentEndpointId = segment->part->getActiveEndpointId();
if (!segmentEndpointId) {
return;
}
absl::optional<int32_t> updatedChannelId;
absl::optional<VideoChannelDescription::Quality> updatedQuality;
for (const auto &videoChannel : _activeVideoChannels) {
auto channelIdIt = _currentEndpointMapping.find(videoChannel.endpoint);
if (channelIdIt == _currentEndpointMapping.end()) {
continue;
}
updatedChannelId = channelIdIt->second + 1;
updatedQuality = videoChannel.quality;
}
if (updatedChannelId && updatedQuality) {
if (segment->pendingVideoQualityUpdatePart) {
const auto typeData = &segment->pendingVideoQualityUpdatePart->typeData;
if (const auto videoData = absl::get_if<PendingVideoSegmentData>(typeData)) {
if (videoData->channelId == updatedChannelId.value() && videoData->quality == updatedQuality.value()) {
return;
}
}
cancelPendingVideoQualityUpdate(segment);
}
auto video = std::make_shared<PendingMediaSegmentPart>();
video->typeData = PendingVideoSegmentData(updatedChannelId.value(), updatedQuality.value());
video->minRequestTimestamp = 0;
segment->pendingVideoQualityUpdatePart = video;
const auto weak = std::weak_ptr<StreamingMediaContextPrivate>(shared_from_this());
const auto weakSegment = std::weak_ptr<VideoSegment>(segment);
beginPartTask(video, timestamp, [weak, weakSegment]() {
auto strong = weak.lock();
if (!strong) {
return;
}
auto strongSegment = weakSegment.lock();
if (!strongSegment) {
return;
}
if (!strongSegment->pendingVideoQualityUpdatePart) {
return;
}
auto result = strongSegment->pendingVideoQualityUpdatePart->result;
if (result) {
strongSegment->part = std::make_shared<VideoStreamingPart>(std::move(result->data));
}
strongSegment->pendingVideoQualityUpdatePart.reset();
});
}
}
void cancelPendingVideoQualityUpdate(std::shared_ptr<VideoSegment> segment) {
if (!segment->pendingVideoQualityUpdatePart) {
return;
}
if (segment->pendingVideoQualityUpdatePart->task) {
segment->pendingVideoQualityUpdatePart->task->cancel();
}
segment->pendingVideoQualityUpdatePart.reset();
}
void checkPendingSegments() {
const auto weak = std::weak_ptr<StreamingMediaContextPrivate>(shared_from_this());
int64_t absoluteTimestamp = rtc::TimeMillis();
int64_t minDelayedRequestTimeout = INT_MAX;
bool shouldRequestMoreSegments = false;
for (int i = 0; i < _pendingSegments.size(); i++) {
auto pendingSegment = _pendingSegments[i];
auto segmentTimestamp = pendingSegment->timestamp;
bool allPartsDone = true;
for (auto &part : pendingSegment->parts) {
if (!part->result) {
allPartsDone = false;
}
if (!part->result && !part->task) {
if (part->minRequestTimestamp != 0) {
if (i != 0) {
continue;
}
if (part->minRequestTimestamp > absoluteTimestamp) {
minDelayedRequestTimeout = std::min(minDelayedRequestTimeout, part->minRequestTimestamp - absoluteTimestamp);
continue;
}
}
const auto weakSegment = std::weak_ptr<PendingMediaSegment>(pendingSegment);
const auto weakPart = std::weak_ptr<PendingMediaSegmentPart>(part);
std::function<void(BroadcastPart &&)> handleResult = [weak, weakSegment, weakPart, threads = _threads, segmentTimestamp](BroadcastPart &&part) {
threads->getMediaThread()->PostTask(RTC_FROM_HERE, [weak, weakSegment, weakPart, part = std::move(part), segmentTimestamp]() mutable {
auto strong = weak.lock();
if (!strong) {
return;
}
auto strongSegment = weakSegment.lock();
if (!strongSegment) {
return;
}
auto pendingPart = weakPart.lock();
if (!pendingPart) {
return;
}
pendingPart->task.reset();
switch (part.status) {
case BroadcastPart::Status::Success: {
pendingPart->result = std::make_shared<PendingMediaSegmentPartResult>(std::move(part.data));
if (strong->_nextSegmentTimestamp == 0) {
strong->_nextSegmentTimestamp = part.timestampMilliseconds + strong->_segmentDuration;
}
strong->checkPendingSegments();
break;
}
case BroadcastPart::Status::NotReady: {
if (segmentTimestamp == 0) {
int64_t responseTimestampMilliseconds = (int64_t)(part.responseTimestamp * 1000.0);
int64_t responseTimestampBoundary = (responseTimestampMilliseconds / strong->_segmentDuration) * strong->_segmentDuration;
strong->_nextSegmentTimestamp = responseTimestampBoundary;
strong->discardAllPendingSegments();
strong->requestSegmentsIfNeeded();
strong->checkPendingSegments();
} else {
pendingPart->minRequestTimestamp = rtc::TimeMillis() + 100;
strong->checkPendingSegments();
}
break;
}
case BroadcastPart::Status::ResyncNeeded: {
int64_t responseTimestampMilliseconds = (int64_t)(part.responseTimestamp * 1000.0);
int64_t responseTimestampBoundary = (responseTimestampMilliseconds / strong->_segmentDuration) * strong->_segmentDuration;
strong->_nextSegmentTimestamp = responseTimestampBoundary;
strong->discardAllPendingSegments();
strong->requestSegmentsIfNeeded();
strong->checkPendingSegments();
break;
}
default: {
RTC_FATAL() << "Unknown part.status";
break;
}
}
});
};
const auto typeData = &part->typeData;
if (const auto audioData = absl::get_if<PendingAudioSegmentData>(typeData)) {
part->task = _requestAudioBroadcastPart(_platformContext, segmentTimestamp, _segmentDuration, handleResult);
} else if (const auto videoData = absl::get_if<PendingVideoSegmentData>(typeData)) {
part->task = _requestVideoBroadcastPart(_platformContext, segmentTimestamp, _segmentDuration, videoData->channelId, videoData->quality, handleResult);
}
}
}
if (allPartsDone && i == 0) {
std::shared_ptr<MediaSegment> segment = std::make_shared<MediaSegment>();
segment->timestamp = pendingSegment->timestamp;
segment->duration = _segmentDuration;
for (auto &part : pendingSegment->parts) {
const auto typeData = &part->typeData;
if (const auto audioData = absl::get_if<PendingAudioSegmentData>(typeData)) {
segment->audio = std::make_shared<AudioStreamingPart>(std::move(part->result->data));
_currentEndpointMapping = segment->audio->getEndpointMapping();
} else if (const auto videoData = absl::get_if<PendingVideoSegmentData>(typeData)) {
auto videoSegment = std::make_shared<VideoSegment>();
videoSegment->quality = videoData->quality;
if (part->result->data.empty()) {
RTC_LOG(LS_INFO) << "Video part " << segment->timestamp << " is empty";
}
videoSegment->part = std::make_shared<VideoStreamingPart>(std::move(part->result->data));
segment->video.push_back(videoSegment);
}
}
_availableSegments.push_back(segment);
shouldRequestMoreSegments = true;
_pendingSegments.erase(_pendingSegments.begin() + i);
i--;
}
}
if (minDelayedRequestTimeout < INT32_MAX) {
const auto weak = std::weak_ptr<StreamingMediaContextPrivate>(shared_from_this());
_threads->getMediaThread()->PostDelayedTask(RTC_FROM_HERE, [weak]() {
auto strong = weak.lock();
if (!strong) {
return;
}
strong->checkPendingSegments();
}, std::max((int32_t)minDelayedRequestTimeout, 10));
}
if (shouldRequestMoreSegments) {
requestSegmentsIfNeeded();
}
}
void beginPartTask(std::shared_ptr<PendingMediaSegmentPart> part, int64_t segmentTimestamp, std::function<void()> completion) {
const auto weak = std::weak_ptr<StreamingMediaContextPrivate>(shared_from_this());
const auto weakPart = std::weak_ptr<PendingMediaSegmentPart>(part);
std::function<void(BroadcastPart &&)> handleResult = [weak, weakPart, threads = _threads, completion](BroadcastPart &&part) {
threads->getMediaThread()->PostTask(RTC_FROM_HERE, [weak, weakPart, part = std::move(part), completion]() mutable {
auto strong = weak.lock();
if (!strong) {
return;
}
auto pendingPart = weakPart.lock();
if (!pendingPart) {
return;
}
pendingPart->task.reset();
switch (part.status) {
case BroadcastPart::Status::Success: {
pendingPart->result = std::make_shared<PendingMediaSegmentPartResult>(std::move(part.data));
break;
}
case BroadcastPart::Status::NotReady: {
break;
}
case BroadcastPart::Status::ResyncNeeded: {
break;
}
default: {
RTC_FATAL() << "Unknown part.status";
break;
}
}
completion();
});
};
const auto typeData = &part->typeData;
if (const auto audioData = absl::get_if<PendingAudioSegmentData>(typeData)) {
part->task = _requestAudioBroadcastPart(_platformContext, segmentTimestamp, _segmentDuration, handleResult);
} else if (const auto videoData = absl::get_if<PendingVideoSegmentData>(typeData)) {
part->task = _requestVideoBroadcastPart(_platformContext, segmentTimestamp, _segmentDuration, videoData->channelId, videoData->quality, handleResult);
}
}
void setVolume(uint32_t ssrc, double volume) {
_volumeBySsrc[ssrc] = volume;
}
void setActiveVideoChannels(std::vector<StreamingMediaContext::VideoChannel> const &videoChannels) {
_activeVideoChannels = videoChannels;
/*#if DEBUG
for (auto &updatedVideoChannel : _activeVideoChannels) {
if (updatedVideoChannel.quality == VideoChannelDescription::Quality::Medium) {
updatedVideoChannel.quality = VideoChannelDescription::Quality::Thumbnail;
}
}
#endif*/
for (const auto &updatedVideoChannel : _activeVideoChannels) {
for (const auto &segment : _availableSegments) {
for (const auto &video : segment->video) {
if (video->part->getActiveEndpointId() == updatedVideoChannel.endpoint) {
if (video->quality != updatedVideoChannel.quality) {
requestPendingVideoQualityUpdate(video, segment->timestamp);
}
}
}
}
}
}
void addVideoSink(std::string const &endpointId, std::weak_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) {
auto it = _videoSinks.find(endpointId);
if (it == _videoSinks.end()) {
_videoSinks.insert(std::make_pair(endpointId, std::vector<std::weak_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>>>()));
}
_videoSinks[endpointId].push_back(sink);
}
private:
std::shared_ptr<Threads> _threads;
std::function<std::shared_ptr<BroadcastPartTask>(std::function<void(int64_t)>)> _requestCurrentTime;
std::function<std::shared_ptr<BroadcastPartTask>(std::shared_ptr<PlatformContext>, int64_t, int64_t, std::function<void(BroadcastPart &&)>)> _requestAudioBroadcastPart;
std::function<std::shared_ptr<BroadcastPartTask>(std::shared_ptr<PlatformContext>, int64_t, int64_t, int32_t, VideoChannelDescription::Quality, std::function<void(BroadcastPart &&)>)> _requestVideoBroadcastPart;
std::function<void(uint32_t, float, bool)> _updateAudioLevel;
const int _segmentDuration = 1000;
const int _segmentBufferDuration = 2000;
int64_t _nextSegmentTimestamp = 0;
absl::optional<int> _waitForBufferredMillisecondsBeforeRendering;
std::vector<std::shared_ptr<MediaSegment>> _availableSegments;
std::vector<std::shared_ptr<PendingMediaSegment>> _pendingSegments;
int64_t _playbackReferenceTimestamp = 0;
const size_t _audioDataRingBufferMaxSize = 4800;
webrtc::Mutex _audioDataMutex;
SampleRingBuffer _audioRingBuffer;
std::vector<int16_t> _tempAudioBuffer;
webrtc::FrameCombiner _audioFrameCombiner;
std::map<uint32_t, std::unique_ptr<SparseVad>> _audioVadMap;
std::map<uint32_t, double> _volumeBySsrc;
std::vector<StreamingMediaContext::VideoChannel> _activeVideoChannels;
std::map<std::string, std::vector<std::weak_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>>>> _videoSinks;
std::map<std::string, int32_t> _currentEndpointMapping;
std::shared_ptr<PlatformContext> _platformContext;
};
StreamingMediaContext::StreamingMediaContext(StreamingMediaContextArguments &&arguments) {
_private = std::make_shared<StreamingMediaContextPrivate>(std::move(arguments));
_private->start();
}
StreamingMediaContext::~StreamingMediaContext() {
}
void StreamingMediaContext::setActiveVideoChannels(std::vector<VideoChannel> const &videoChannels) {
_private->setActiveVideoChannels(videoChannels);
}
void StreamingMediaContext::setVolume(uint32_t ssrc, double volume) {
_private->setVolume(ssrc, volume);
}
void StreamingMediaContext::addVideoSink(std::string const &endpointId, std::weak_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) {
_private->addVideoSink(endpointId, sink);
}
void StreamingMediaContext::getAudio(int16_t *audio_samples, const size_t num_samples, const size_t num_channels, const uint32_t samples_per_sec) {
_private->getAudio(audio_samples, num_samples, num_channels, samples_per_sec);
}
}

View file

@ -0,0 +1,53 @@
#ifndef TGCALLS_STREAMING_MEDIA_CONTEXT_H
#define TGCALLS_STREAMING_MEDIA_CONTEXT_H
#include "GroupInstanceImpl.h"
#include <stdint.h>
#include "../StaticThreads.h"
namespace tgcalls {
class StreamingMediaContextPrivate;
class StreamingMediaContext {
public:
struct VideoChannel {
VideoChannelDescription::Quality quality = VideoChannelDescription::Quality::Thumbnail;
std::string endpoint;
VideoChannel(VideoChannelDescription::Quality quality_, std::string endpoint_) :
quality(quality_),
endpoint(endpoint_) {
}
};
public:
struct StreamingMediaContextArguments {
std::shared_ptr<Threads> threads;
std::function<std::shared_ptr<BroadcastPartTask>(std::function<void(int64_t)>)> requestCurrentTime;
std::function<std::shared_ptr<BroadcastPartTask>(std::shared_ptr<PlatformContext>, int64_t, int64_t, std::function<void(BroadcastPart &&)>)> requestAudioBroadcastPart;
std::function<std::shared_ptr<BroadcastPartTask>(std::shared_ptr<PlatformContext>, int64_t, int64_t, int32_t, VideoChannelDescription::Quality, std::function<void(BroadcastPart &&)>)> requestVideoBroadcastPart;
std::function<void(uint32_t, float, bool)> updateAudioLevel;
std::shared_ptr<PlatformContext> platformContext;
};
public:
StreamingMediaContext(StreamingMediaContextArguments &&arguments);
~StreamingMediaContext();
StreamingMediaContext& operator=(const StreamingMediaContext&) = delete;
StreamingMediaContext& operator=(StreamingMediaContext&&) = delete;
void setActiveVideoChannels(std::vector<VideoChannel> const &videoChannels);
void setVolume(uint32_t ssrc, double volume);
void addVideoSink(std::string const &endpointId, std::weak_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);
void getAudio(int16_t *audio_samples, const size_t num_samples, const size_t num_channels, const uint32_t samples_per_sec);
private:
std::shared_ptr<StreamingMediaContextPrivate> _private;
};
}
#endif

View file

@ -1,39 +0,0 @@
#ifndef TGCALLS_STREAMING_PART_H
#define TGCALLS_STREAMING_PART_H
#include "absl/types/optional.h"
#include <vector>
#include <stdint.h>
namespace tgcalls {
class StreamingPartState;
class StreamingPart {
public:
struct StreamingPartChannel {
uint32_t ssrc = 0;
std::vector<int16_t> pcmData;
};
explicit StreamingPart(std::vector<uint8_t> &&data);
~StreamingPart();
StreamingPart(const StreamingPart&) = delete;
StreamingPart(StreamingPart&& other) {
_state = other._state;
other._state = nullptr;
}
StreamingPart& operator=(const StreamingPart&) = delete;
StreamingPart& operator=(StreamingPart&&) = delete;
int getRemainingMilliseconds() const;
std::vector<StreamingPartChannel> get10msPerChannel();
private:
StreamingPartState *_state = nullptr;
};
}
#endif

View file

@ -0,0 +1,659 @@
#include "VideoStreamingPart.h"
#include "rtc_base/logging.h"
#include "rtc_base/third_party/base64/base64.h"
#include "api/video/i420_buffer.h"
extern "C" {
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
}
#include <string>
#include <set>
#include <map>
namespace tgcalls {
namespace {
class AVIOContextImpl {
public:
AVIOContextImpl(std::vector<uint8_t> &&fileData) :
_fileData(std::move(fileData)) {
_buffer.resize(4 * 1024);
_context = avio_alloc_context(_buffer.data(), (int)_buffer.size(), 0, this, &AVIOContextImpl::read, NULL, &AVIOContextImpl::seek);
}
~AVIOContextImpl() {
av_free(_context);
}
static int read(void *opaque, unsigned char *buffer, int bufferSize) {
AVIOContextImpl *instance = static_cast<AVIOContextImpl *>(opaque);
int bytesToRead = std::min(bufferSize, ((int)instance->_fileData.size()) - instance->_fileReadPosition);
if (bytesToRead < 0) {
bytesToRead = 0;
}
if (bytesToRead > 0) {
memcpy(buffer, instance->_fileData.data() + instance->_fileReadPosition, bytesToRead);
instance->_fileReadPosition += bytesToRead;
return bytesToRead;
} else {
return AVERROR_EOF;
}
}
static int64_t seek(void *opaque, int64_t offset, int whence) {
AVIOContextImpl *instance = static_cast<AVIOContextImpl *>(opaque);
if (whence == 0x10000) {
return (int64_t)instance->_fileData.size();
} else {
int64_t seekOffset = std::min(offset, (int64_t)instance->_fileData.size());
if (seekOffset < 0) {
seekOffset = 0;
}
instance->_fileReadPosition = (int)seekOffset;
return seekOffset;
}
}
AVIOContext *getContext() {
return _context;
}
private:
std::vector<uint8_t> _fileData;
int _fileReadPosition = 0;
std::vector<uint8_t> _buffer;
AVIOContext *_context = nullptr;
};
class MediaDataPacket {
public:
MediaDataPacket() : _packet(av_packet_alloc()) {
}
MediaDataPacket(MediaDataPacket &&other) : _packet(other._packet) {
other._packet = nullptr;
}
~MediaDataPacket() {
if (_packet) {
av_packet_free(&_packet);
}
}
AVPacket *packet() {
return _packet;
}
private:
AVPacket *_packet = nullptr;
};
class DecodableFrame {
public:
DecodableFrame(MediaDataPacket packet, int64_t pts, int64_t dts):
_packet(std::move(packet)),
_pts(pts),
_dts(dts) {
}
~DecodableFrame() {
}
MediaDataPacket &packet() {
return _packet;
}
int64_t pts() {
return _pts;
}
int64_t dts() {
return _dts;
}
private:
MediaDataPacket _packet;
int64_t _pts = 0;
int64_t _dts = 0;
};
class Frame {
public:
Frame() {
_frame = av_frame_alloc();
}
Frame(Frame &&other) {
_frame = other._frame;
other._frame = nullptr;
}
~Frame() {
if (_frame) {
av_frame_unref(_frame);
}
}
AVFrame *frame() {
return _frame;
}
double pts(AVStream *stream) {
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;
}
}
private:
AVFrame *_frame = nullptr;
};
struct VideoStreamEvent {
int32_t offset = 0;
std::string endpointId;
int32_t rotation = 0;
int32_t extra = 0;
};
struct VideoStreamInfo {
std::string container;
int32_t activeMask = 0;
std::vector<VideoStreamEvent> events;
};
absl::optional<int32_t> readInt32(std::vector<uint8_t> const &data, int &offset) {
if (offset + 4 > data.size()) {
return absl::nullopt;
}
int32_t value = 0;
memcpy(&value, data.data() + offset, 4);
offset += 4;
return value;
}
absl::optional<uint8_t> readBytesAsInt32(std::vector<uint8_t> const &data, int &offset, int count) {
if (offset + count > data.size()) {
return absl::nullopt;
}
if (count == 0) {
return absl::nullopt;
}
if (count <= 4) {
int32_t value = 0;
memcpy(&value, data.data() + offset, count);
offset += count;
return value;
} else {
return absl::nullopt;
}
}
int32_t roundUp(int32_t numToRound, int32_t multiple) {
if (multiple == 0) {
return numToRound;
}
int32_t remainder = numToRound % multiple;
if (remainder == 0) {
return numToRound;
}
return numToRound + multiple - remainder;
}
absl::optional<std::string> readSerializedString(std::vector<uint8_t> const &data, int &offset) {
if (const auto tmp = readBytesAsInt32(data, offset, 1)) {
int paddingBytes = 0;
int length = 0;
if (tmp.value() == 254) {
if (const auto len = readBytesAsInt32(data, offset, 3)) {
length = len.value();
paddingBytes = roundUp(length, 4) - length;
} else {
return absl::nullopt;
}
}
else {
length = tmp.value();
paddingBytes = roundUp(length + 1, 4) - (length + 1);
}
if (offset + length > data.size()) {
return absl::nullopt;
}
std::string result(data.data() + offset, data.data() + offset + length);
offset += length;
offset += paddingBytes;
return result;
} else {
return absl::nullopt;
}
}
absl::optional<VideoStreamEvent> readVideoStreamEvent(std::vector<uint8_t> const &data, int &offset) {
VideoStreamEvent event;
if (const auto offsetValue = readInt32(data, offset)) {
event.offset = offsetValue.value();
} else {
return absl::nullopt;
}
if (const auto endpointId = readSerializedString(data, offset)) {
event.endpointId = endpointId.value();
} else {
return absl::nullopt;
}
if (const auto rotation = readInt32(data, offset)) {
event.rotation = rotation.value();
} else {
return absl::nullopt;
}
if (const auto extra = readInt32(data, offset)) {
event.extra = extra.value();
} else {
return absl::nullopt;
}
return event;
}
absl::optional<VideoStreamInfo> consumeVideoStreamInfo(std::vector<uint8_t> &data) {
int offset = 0;
if (const auto signature = readInt32(data, offset)) {
if (signature.value() != 0xa12e810d) {
return absl::nullopt;
}
} else {
return absl::nullopt;
}
VideoStreamInfo info;
if (const auto container = readSerializedString(data, offset)) {
info.container = container.value();
} else {
return absl::nullopt;
}
if (const auto activeMask = readInt32(data, offset)) {
info.activeMask = activeMask.value();
} else {
return absl::nullopt;
}
if (const auto eventCount = readInt32(data, offset)) {
if (const auto event = readVideoStreamEvent(data, offset)) {
info.events.push_back(event.value());
} else {
return absl::nullopt;
}
} else {
return absl::nullopt;
}
data.erase(data.begin(), data.begin() + offset);
return info;
}
}
class VideoStreamingPartInternal {
public:
VideoStreamingPartInternal(std::string endpointId, webrtc::VideoRotation rotation, std::vector<uint8_t> &&fileData, std::string const &container) :
_endpointId(endpointId),
_rotation(rotation) {
_avIoContext = std::make_unique<AVIOContextImpl>(std::move(fileData));
int ret = 0;
AVInputFormat *inputFormat = av_find_input_format(container.c_str());
if (!inputFormat) {
_didReadToEnd = true;
return;
}
_inputFormatContext = avformat_alloc_context();
if (!_inputFormatContext) {
_didReadToEnd = true;
return;
}
_inputFormatContext->pb = _avIoContext->getContext();
if ((ret = avformat_open_input(&_inputFormatContext, "", inputFormat, nullptr)) < 0) {
_didReadToEnd = true;
return;
}
if ((ret = avformat_find_stream_info(_inputFormatContext, nullptr)) < 0) {
_didReadToEnd = true;
avformat_close_input(&_inputFormatContext);
_inputFormatContext = nullptr;
return;
}
AVCodecParameters *videoCodecParameters = nullptr;
AVStream *videoStream = nullptr;
for (int i = 0; i < _inputFormatContext->nb_streams; i++) {
AVStream *inStream = _inputFormatContext->streams[i];
AVCodecParameters *inCodecpar = inStream->codecpar;
if (inCodecpar->codec_type != AVMEDIA_TYPE_VIDEO) {
continue;
}
videoCodecParameters = inCodecpar;
videoStream = inStream;
break;
}
if (videoCodecParameters && videoStream) {
AVCodec *codec = avcodec_find_decoder(videoCodecParameters->codec_id);
if (codec) {
_codecContext = avcodec_alloc_context3(codec);
ret = avcodec_parameters_to_context(_codecContext, videoCodecParameters);
if (ret < 0) {
_didReadToEnd = true;
avcodec_free_context(&_codecContext);
_codecContext = nullptr;
} else {
_codecContext->pkt_timebase = videoStream->time_base;
ret = avcodec_open2(_codecContext, codec, nullptr);
if (ret < 0) {
_didReadToEnd = true;
avcodec_free_context(&_codecContext);
_codecContext = nullptr;
} else {
_videoStream = videoStream;
}
}
}
}
}
~VideoStreamingPartInternal() {
if (_codecContext) {
avcodec_close(_codecContext);
avcodec_free_context(&_codecContext);
}
if (_inputFormatContext) {
avformat_close_input(&_inputFormatContext);
}
}
std::string endpointId() {
return _endpointId;
}
absl::optional<MediaDataPacket> readPacket() {
if (_didReadToEnd) {
return absl::nullopt;
}
if (!_inputFormatContext) {
return absl::nullopt;
}
MediaDataPacket packet;
int result = av_read_frame(_inputFormatContext, packet.packet());
if (result < 0) {
return absl::nullopt;
}
return packet;
}
std::shared_ptr<DecodableFrame> readNextDecodableFrame() {
while (true) {
absl::optional<MediaDataPacket> packet = readPacket();
if (packet) {
if (_videoStream && packet->packet()->stream_index == _videoStream->index) {
return std::make_shared<DecodableFrame>(std::move(packet.value()), packet->packet()->pts, packet->packet()->dts);
}
} else {
return nullptr;
}
}
}
absl::optional<VideoStreamingPartFrame> convertCurrentFrame() {
rtc::scoped_refptr<webrtc::I420Buffer> i420Buffer = webrtc::I420Buffer::Copy(
_frame.frame()->width,
_frame.frame()->height,
_frame.frame()->data[0],
_frame.frame()->linesize[0],
_frame.frame()->data[1],
_frame.frame()->linesize[1],
_frame.frame()->data[2],
_frame.frame()->linesize[2]
);
if (i420Buffer) {
auto videoFrame = webrtc::VideoFrame::Builder()
.set_video_frame_buffer(i420Buffer)
.set_rotation(_rotation)
.build();
return VideoStreamingPartFrame(_endpointId, videoFrame, _frame.pts(_videoStream), _frame.duration(_videoStream), _frameIndex);
} else {
return absl::nullopt;
}
}
absl::optional<VideoStreamingPartFrame> getNextFrame() {
if (!_codecContext) {
return {};
}
while (true) {
if (_didReadToEnd) {
if (!_finalFrames.empty()) {
auto frame = _finalFrames[0];
_finalFrames.erase(_finalFrames.begin());
return frame;
} else {
break;
}
} else {
const auto frame = readNextDecodableFrame();
if (frame) {
auto status = avcodec_send_packet(_codecContext, frame->packet().packet());
if (status == 0) {
auto status = avcodec_receive_frame(_codecContext, _frame.frame());
if (status == 0) {
auto convertedFrame = convertCurrentFrame();
if (convertedFrame) {
_frameIndex++;
return convertedFrame;
}
} else if (status == -35) {
// more data needed
} else {
_didReadToEnd = true;
break;
}
} else {
_didReadToEnd = true;
return {};
}
} else {
_didReadToEnd = true;
int status = avcodec_send_packet(_codecContext, nullptr);
if (status == 0) {
while (true) {
auto status = avcodec_receive_frame(_codecContext, _frame.frame());
if (status == 0) {
auto convertedFrame = convertCurrentFrame();
if (convertedFrame) {
_frameIndex++;
_finalFrames.push_back(convertedFrame.value());
}
} else {
break;
}
}
}
}
}
}
return {};
}
private:
std::string _endpointId;
webrtc::VideoRotation _rotation = webrtc::VideoRotation::kVideoRotation_0;
std::unique_ptr<AVIOContextImpl> _avIoContext;
AVFormatContext *_inputFormatContext = nullptr;
AVCodecContext *_codecContext = nullptr;
AVStream *_videoStream = nullptr;
Frame _frame;
std::vector<VideoStreamingPartFrame> _finalFrames;
int _frameIndex = 0;
bool _didReadToEnd = false;
};
class VideoStreamingPartState {
public:
VideoStreamingPartState(std::vector<uint8_t> &&data) {
_videoStreamInfo = consumeVideoStreamInfo(data);
if (!_videoStreamInfo) {
return;
}
for (size_t i = 0; i < _videoStreamInfo->events.size(); i++) {
std::vector<uint8_t> dataSlice(data.begin() + _videoStreamInfo->events[i].offset, i == (_videoStreamInfo->events.size() - 1) ? data.end() : (data.begin() + _videoStreamInfo->events[i + 1].offset));
webrtc::VideoRotation rotation = webrtc::VideoRotation::kVideoRotation_0;
switch (_videoStreamInfo->events[i].rotation) {
case 0: {
rotation = webrtc::VideoRotation::kVideoRotation_0;
break;
}
case 90: {
rotation = webrtc::VideoRotation::kVideoRotation_90;
break;
}
case 180: {
rotation = webrtc::VideoRotation::kVideoRotation_180;
break;
}
case 270: {
rotation = webrtc::VideoRotation::kVideoRotation_270;
break;
}
default: {
break;
}
}
auto part = std::make_unique<VideoStreamingPartInternal>(_videoStreamInfo->events[i].endpointId, rotation, std::move(dataSlice), _videoStreamInfo->container);
_parsedParts.push_back(std::move(part));
}
}
~VideoStreamingPartState() {
}
absl::optional<VideoStreamingPartFrame> getFrameAtRelativeTimestamp(double timestamp) {
while (true) {
if (!_currentFrame) {
if (!_parsedParts.empty()) {
auto result = _parsedParts[0]->getNextFrame();
if (result) {
_currentFrame = result;
_relativeTimestamp += result->duration;
} else {
_parsedParts.erase(_parsedParts.begin());
continue;
}
}
}
if (_currentFrame) {
if (timestamp <= _relativeTimestamp) {
return _currentFrame;
} else {
_currentFrame = absl::nullopt;
}
} else {
return absl::nullopt;
}
}
}
absl::optional<std::string> getActiveEndpointId() const {
if (!_parsedParts.empty()) {
return _parsedParts[0]->endpointId();
} else {
return absl::nullopt;
}
}
private:
absl::optional<VideoStreamInfo> _videoStreamInfo;
std::vector<std::unique_ptr<VideoStreamingPartInternal>> _parsedParts;
absl::optional<VideoStreamingPartFrame> _currentFrame;
double _relativeTimestamp = 0.0;
};
VideoStreamingPart::VideoStreamingPart(std::vector<uint8_t> &&data) {
if (!data.empty()) {
_state = new VideoStreamingPartState(std::move(data));
}
}
VideoStreamingPart::~VideoStreamingPart() {
if (_state) {
delete _state;
}
}
absl::optional<VideoStreamingPartFrame> VideoStreamingPart::getFrameAtRelativeTimestamp(double timestamp) {
return _state
? _state->getFrameAtRelativeTimestamp(timestamp)
: absl::nullopt;
}
absl::optional<std::string> VideoStreamingPart::getActiveEndpointId() const {
return _state
? _state->getActiveEndpointId()
: absl::nullopt;
}
}

View file

@ -0,0 +1,53 @@
#ifndef TGCALLS_VIDEO_STREAMING_PART_H
#define TGCALLS_VIDEO_STREAMING_PART_H
#include "absl/types/optional.h"
#include <vector>
#include <stdint.h>
#include "api/video/video_frame.h"
#include "absl/types/optional.h"
namespace tgcalls {
class VideoStreamingPartState;
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_) :
endpointId(endpointId_),
frame(frame_),
pts(pts_),
duration(duration_),
index(index_) {
}
};
class VideoStreamingPart {
public:
explicit VideoStreamingPart(std::vector<uint8_t> &&data);
~VideoStreamingPart();
VideoStreamingPart(const VideoStreamingPart&) = delete;
VideoStreamingPart(VideoStreamingPart&& other) {
_state = other._state;
other._state = nullptr;
}
VideoStreamingPart& operator=(const VideoStreamingPart&) = delete;
VideoStreamingPart& operator=(VideoStreamingPart&&) = delete;
absl::optional<VideoStreamingPartFrame> getFrameAtRelativeTimestamp(double timestamp);
absl::optional<std::string> getActiveEndpointId() const;
private:
VideoStreamingPartState *_state = nullptr;
};
}
#endif

View file

@ -209,6 +209,9 @@ void InstanceImplLegacy::receiveSignalingData(const std::vector<uint8_t> &data)
void InstanceImplLegacy::setVideoCapture(std::shared_ptr<VideoCaptureInterface> videoCapture) {
}
void InstanceImplLegacy::sendVideoDeviceUpdated() {
}
void InstanceImplLegacy::setRequestedVideoAspect(float aspect) {
}

View file

@ -20,6 +20,7 @@ public:
void setNetworkType(NetworkType networkType) override;
void setMuteMicrophone(bool muteMicrophone) override;
void setVideoCapture(std::shared_ptr<VideoCaptureInterface> videoCapture) override;
void sendVideoDeviceUpdated() override;
void setRequestedVideoAspect(float aspect) override;
bool supportsVideo() override {
return false;

View file

@ -19,7 +19,8 @@ public:
void setJavaInstance(JNIEnv *env, jobject instance);
std::shared_ptr<BroadcastPartTask> streamTask;
std::vector<std::shared_ptr<BroadcastPartTask>> audioStreamTasks;
std::vector<std::shared_ptr<BroadcastPartTask>> videoStreamTasks;
std::vector<std::shared_ptr<RequestMediaChannelDescriptionTask>> descriptionTasks;
private:

View file

@ -406,7 +406,10 @@ public:
}
beginSendingVideo();
}
void sendVideoDeviceUpdated() {
}
void setRequestedVideoAspect(float aspect) {
}
@ -1015,12 +1018,12 @@ PersistentState InstanceImplReference::getPersistentState() {
void InstanceImplReference::stop(std::function<void(FinalState)> completion) {
auto result = FinalState();
result.persistentState = getPersistentState();
result.debugLog = logSink_->result();
result.trafficStats = getTrafficStats();
result.isRatingSuggested = false;
completion(result);
}

View file

@ -18,6 +18,8 @@ public:
void setNetworkType(NetworkType networkType) override;
void setMuteMicrophone(bool muteMicrophone) override;
void setVideoCapture(std::shared_ptr<VideoCaptureInterface> videoCapture) override;
void sendVideoDeviceUpdated() override {
}
void setRequestedVideoAspect(float aspect) override;
bool supportsVideo() override {
return true;

View file

@ -44,6 +44,8 @@ public:
TrafficStats getTrafficStats() override;
PersistentState getPersistentState() override;
void stop(std::function<void(FinalState)> completion) override;
void sendVideoDeviceUpdated() override {
}
private:
std::shared_ptr<Threads> _threads;

View file

@ -62,9 +62,7 @@ public class ChatListItemAnimator extends DefaultItemAnimator {
this.activity = activity;
this.recyclerListView = listView;
translationInterpolator = DEFAULT_INTERPOLATOR;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
listView.getElevation();
}
setSupportsChangeAnimations(false);
}
@Override

View file

@ -153,6 +153,8 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements
// time.
private int[] mReusableIntPair = new int[2];
private boolean needFixGap = true;
/**
* Creates a vertical LinearLayoutManager
*
@ -945,6 +947,9 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements
*/
private int fixLayoutEndGap(int endOffset, RecyclerView.Recycler recycler,
RecyclerView.State state, boolean canOffsetChildren) {
if (!needFixGap) {
return 0;
}
int gap = mOrientationHelper.getEndAfterPadding() - endOffset;
int fixOffset = 0;
if (gap > 0) {
@ -974,6 +979,9 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements
*/
private int fixLayoutStartGap(int startOffset, RecyclerView.Recycler recycler,
RecyclerView.State state, boolean canOffsetChildren) {
if (!needFixGap) {
return 0;
}
int gap = startOffset - getStarForFixGap();
int fixOffset = 0;
if (gap > 0) {
@ -2578,4 +2586,8 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements
mFocusable = false;
}
}
public void setNeedFixGap(boolean needFixGap) {
this.needFixGap = needFixGap;
}
}

View file

@ -38,6 +38,7 @@ public class LinearSmoothScrollerCustom extends RecyclerView.SmoothScroller {
public static final int POSITION_MIDDLE = 0;
public static final int POSITION_END = 1;
public static final int POSITION_TOP = 2;
public LinearSmoothScrollerCustom(Context context, int position) {
MILLISECONDS_PER_PX = MILLISECONDS_PER_INCH / context.getResources().getDisplayMetrics().densityDpi;
@ -125,7 +126,9 @@ public class LinearSmoothScrollerCustom extends RecyclerView.SmoothScroller {
int boxSize = end - start;
int viewSize = bottom - top;
if (viewSize > boxSize) {
if (scrollPosition == POSITION_TOP) {
start = layoutManager.getPaddingTop();
} else if (viewSize > boxSize) {
start = 0;
} else if (scrollPosition == POSITION_MIDDLE) {
start = (boxSize - viewSize) / 2;

View file

@ -3717,38 +3717,27 @@ public class AndroidUtilities {
animated = false;
}
if (show && view.getTag() == null) {
view.animate().setListener(null).cancel();
if (animated) {
if (view.getVisibility() != View.VISIBLE) {
view.setVisibility(View.VISIBLE);
view.setAlpha(0f);
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
}
view.animate().alpha(1f).scaleY(1f).scaleX(1f).setDuration(150).start();
} else {
view.setVisibility(View.VISIBLE);
view.setAlpha(1f);
view.setScaleX(1f);
view.setScaleY(1f);
}
view.setTag(1);
} else if (!show && view.getTag() != null){
view.animate().setListener(null).cancel();
if (animated) {
view.animate().alpha(0).scaleY(scaleFactor).scaleX(scaleFactor).setListener(new HideViewAfterAnimation(view)).setDuration(150).start();
} else {
view.setVisibility(View.GONE);
}
view.setTag(null);
} else if (!animated) {
if (!animated) {
view.animate().setListener(null).cancel();
view.setVisibility(show ? View.VISIBLE : View.GONE);
view.setTag(show ? 1 : null);
view.setAlpha(1f);
view.setScaleX(1f);
view.setScaleY(1f);
} else if (show && view.getTag() == null) {
view.animate().setListener(null).cancel();
if (view.getVisibility() != View.VISIBLE) {
view.setVisibility(View.VISIBLE);
view.setAlpha(0f);
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
}
view.animate().alpha(1f).scaleY(1f).scaleX(1f).setDuration(150).start();
view.setTag(1);
} else if (!show && view.getTag() != null) {
view.animate().setListener(null).cancel();
view.animate().alpha(0).scaleY(scaleFactor).scaleX(scaleFactor).setListener(new HideViewAfterAnimation(view)).setDuration(150).start();
view.setTag(null);
}
}
}

View file

@ -265,8 +265,10 @@ public class ApplicationLoader extends Application {
}
Utilities.globalQueue.postRunnable(() -> {
try {
SharedConfig.pushStringGetTimeStart = SystemClock.elapsedRealtime();
FirebaseMessaging.getInstance().getToken()
.addOnCompleteListener(task -> {
SharedConfig.pushStringGetTimeEnd = SystemClock.elapsedRealtime();
if (!task.isSuccessful()) {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("Failed to get regid");

View file

@ -19,8 +19,8 @@ public class BuildVars {
public static boolean USE_CLOUD_STRINGS = true;
public static boolean CHECK_UPDATES = true;
public static boolean NO_SCOPED_STORAGE = true/* || Build.VERSION.SDK_INT <= 28*/;
public static int BUILD_VERSION = 2390;
public static String BUILD_VERSION_STRING = "7.9.3";
public static int BUILD_VERSION = 2406;
public static String BUILD_VERSION_STRING = "8.0.0";
public static int APP_ID = 4;
public static String APP_HASH = "014b35b6184100b085b0d0572f9b5103";
public static String APPCENTER_HASH = "a5b5c4f5-51da-dedc-9918-d9766a22ca7c";

View file

@ -1035,7 +1035,7 @@ public class ChatObject {
int selfId = getSelfId();
VoIPService service = VoIPService.getSharedInstance();
TLRPC.TL_groupCallParticipant selfParticipant = participants.get(selfId);
canStreamVideo = selfParticipant != null && selfParticipant.video_joined;
canStreamVideo = true;//selfParticipant != null && selfParticipant.video_joined || BuildVars.DEBUG_PRIVATE_VERSION;
boolean allowedVideoCount;
boolean hasAnyVideo = false;
activeVideos = 0;
@ -1243,7 +1243,7 @@ public class ChatObject {
}
}
public void toggleRecord(String title) {
public void toggleRecord(String title, int type) {
recording = !recording;
TLRPC.TL_phone_toggleGroupCallRecord req = new TLRPC.TL_phone_toggleGroupCallRecord();
req.call = getInputGroupCall();
@ -1252,6 +1252,11 @@ public class ChatObject {
req.title = title;
req.flags |= 2;
}
if (type == 1 || type == 2) {
req.flags |= 4;
req.video = true;
req.video_portrait = type == 1;
}
currentAccount.getConnectionsManager().sendRequest(req, (response, error) -> {
if (response != null) {
final TLRPC.Updates res = (TLRPC.Updates) response;
@ -1434,6 +1439,10 @@ public class ChatObject {
return chat instanceof TLRPC.TL_channel || chat instanceof TLRPC.TL_channelForbidden;
}
public static boolean isChannelOrGiga(TLRPC.Chat chat) {
return (chat instanceof TLRPC.TL_channel || chat instanceof TLRPC.TL_channelForbidden) && (!chat.megagroup || chat.gigagroup);
}
public static boolean isMegagroup(TLRPC.Chat chat) {
return (chat instanceof TLRPC.TL_channel || chat instanceof TLRPC.TL_channelForbidden) && chat.megagroup;
}

View file

@ -20,6 +20,8 @@ public class DispatchQueue extends Thread {
private volatile Handler handler = null;
private CountDownLatch syncLatch = new CountDownLatch(1);
private long lastTaskTime;
private static int indexPointer = 0;
public final int index = indexPointer++;
public DispatchQueue(final String threadName) {
this(threadName, true);

View file

@ -1,16 +1,16 @@
package org.telegram.messenger;
import android.os.SystemClock;
import java.util.HashMap;
import java.util.LinkedList;
import android.util.SparseIntArray;
import androidx.annotation.UiThread;
import java.util.LinkedList;
public class DispatchQueuePool {
private LinkedList<DispatchQueue> queues = new LinkedList<>();
private HashMap<DispatchQueue, Integer> busyQueuesMap = new HashMap<>();
private SparseIntArray busyQueuesMap = new SparseIntArray();
private LinkedList<DispatchQueue> busyQueues = new LinkedList<>();
private int maxCount;
private int createdCount;
@ -66,22 +66,19 @@ public class DispatchQueuePool {
}
totalTasksCount++;
busyQueues.add(queue);
Integer count = busyQueuesMap.get(queue);
if (count == null) {
count = 0;
}
busyQueuesMap.put(queue, count + 1);
int count = busyQueuesMap.get(queue.index, 0);
busyQueuesMap.put(queue.index, count + 1);
queue.postRunnable(() -> {
runnable.run();
AndroidUtilities.runOnUIThread(() -> {
totalTasksCount--;
int remainingTasksCount = busyQueuesMap.get(queue) - 1;
int remainingTasksCount = busyQueuesMap.get(queue.index) - 1;
if (remainingTasksCount == 0) {
busyQueuesMap.remove(queue);
busyQueuesMap.delete(queue.index);
busyQueues.remove(queue);
queues.add(queue);
} else {
busyQueuesMap.put(queue, remainingTasksCount);
busyQueuesMap.put(queue.index, remainingTasksCount);
}
});
});

View file

@ -27,6 +27,7 @@ import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.Spannable;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.DynamicDrawableSpan;
import android.text.style.ImageSpan;
import android.view.View;
@ -189,6 +190,9 @@ public class Emoji {
}
public static boolean isValidEmoji(CharSequence code) {
if (TextUtils.isEmpty(code)) {
return false;
}
DrawableInfo info = rects.get(code);
if (info == null) {
CharSequence newCode = EmojiData.emojiAliasMap.get(code);

View file

@ -0,0 +1,178 @@
package org.telegram.messenger;
import android.text.TextUtils;
import android.util.LongSparseArray;
import android.util.SparseBooleanArray;
import org.telegram.tgnet.TLRPC;
import java.util.ArrayList;
public class ForwardingMessagesParams {
public LongSparseArray<MessageObject.GroupedMessages> groupedMessagesMap = new LongSparseArray<>();
public ArrayList<MessageObject> messages;
public ArrayList<MessageObject> previewMessages = new ArrayList<>();
public SparseBooleanArray selectedIds = new SparseBooleanArray();
public boolean hideForwardSendersName;
public boolean hideCaption;
public boolean hasCaption;
public boolean hasSenders;
public boolean isSecret;
public boolean willSeeSenders;
public boolean multiplyUsers;
public ArrayList<TLRPC.TL_pollAnswerVoters> pollChoosenAnswers = new ArrayList();
public ForwardingMessagesParams(ArrayList<MessageObject> messages, long newDialogId) {
this.messages = messages;
hasCaption = false;
hasSenders = false;
isSecret = DialogObject.isSecretDialogId(newDialogId);
ArrayList<String> hiddenSendersName = new ArrayList<>();
for (int i = 0; i < messages.size(); i++) {
MessageObject messageObject = messages.get(i);
if (!TextUtils.isEmpty(messageObject.caption)) {
hasCaption = true;
}
selectedIds.put(messageObject.getId(), true);
TLRPC.Message message = new TLRPC.TL_message();
message.id = messageObject.messageOwner.id;
message.grouped_id = messageObject.messageOwner.grouped_id;
message.peer_id = messageObject.messageOwner.peer_id;
message.from_id = messageObject.messageOwner.from_id;
message.message = messageObject.messageOwner.message;
message.media = messageObject.messageOwner.media;
message.action = messageObject.messageOwner.action;
message.edit_date = 0;
message.out = true;
message.unread = false;
message.via_bot_id = messageObject.messageOwner.via_bot_id;
message.reply_markup = messageObject.messageOwner.reply_markup;
message.post = messageObject.messageOwner.post;
message.legacy = messageObject.messageOwner.legacy;
TLRPC.MessageFwdHeader header = null;
int clientUserId = UserConfig.getInstance(messageObject.currentAccount).clientUserId;
if (!isSecret) {
if (messageObject.messageOwner.fwd_from != null) {
header = messageObject.messageOwner.fwd_from;
if (!messageObject.isDice()) {
hasSenders = true;
} else {
willSeeSenders = true;
}
if (header.from_id == null && !hiddenSendersName.contains(header.from_name)) {
hiddenSendersName.add(header.from_name);
}
} else if (messageObject.messageOwner.from_id.user_id == 0 || messageObject.messageOwner.dialog_id != clientUserId || messageObject.messageOwner.from_id.user_id != clientUserId) {
header = new TLRPC.TL_messageFwdHeader();
header.from_id = messageObject.messageOwner.from_id;
if (!messageObject.isDice()) {
hasSenders = true;
} else {
willSeeSenders = true;
}
}
}
if (header != null) {
message.fwd_from = header;
message.flags |= TLRPC.MESSAGE_FLAG_FWD;
}
message.dialog_id = newDialogId;
MessageObject previewMessage = new MessageObject(messageObject.currentAccount, message, true, false) {
@Override
public boolean needDrawForwarded() {
if (hideForwardSendersName) {
return false;
}
return super.needDrawForwarded();
}
};
previewMessage.preview = true;
if (previewMessage.getGroupId() != 0) {
MessageObject.GroupedMessages groupedMessages = groupedMessagesMap.get(previewMessage.getGroupId(), null);
if (groupedMessages == null) {
groupedMessages = new MessageObject.GroupedMessages();
groupedMessagesMap.put(previewMessage.getGroupId(), groupedMessages);
}
groupedMessages.messages.add(previewMessage);
}
previewMessages.add(0, previewMessage);
if (messageObject.isPoll()) {
TLRPC.TL_messageMediaPoll mediaPoll = (TLRPC.TL_messageMediaPoll) messageObject.messageOwner.media;
PreviewMediaPoll newMediaPoll = new PreviewMediaPoll();
newMediaPoll.poll = mediaPoll.poll;
newMediaPoll.provider = mediaPoll.provider;
newMediaPoll.results = new TLRPC.TL_pollResults();
newMediaPoll.totalVotersCached = newMediaPoll.results.total_voters = mediaPoll.results.total_voters;
previewMessage.messageOwner.media = newMediaPoll;
if (messageObject.canUnvote()) {
for (int a = 0, N = mediaPoll.results.results.size(); a < N; a++) {
TLRPC.TL_pollAnswerVoters answer = mediaPoll.results.results.get(a);
if (answer.chosen) {
TLRPC.TL_pollAnswerVoters newAnswer = new TLRPC.TL_pollAnswerVoters();
newAnswer.chosen = answer.chosen;
newAnswer.correct = answer.correct;
newAnswer.flags = answer.flags;
newAnswer.option = answer.option;
newAnswer.voters = answer.voters;
pollChoosenAnswers.add(newAnswer);
newMediaPoll.results.results.add(newAnswer);
} else {
newMediaPoll.results.results.add(answer);
}
}
}
}
}
ArrayList<Integer> uids = new ArrayList<>();
for (int a = 1; a < messages.size(); a++) {
MessageObject object = messages.get(a);
int uid;
if (object.isFromUser()) {
uid = object.messageOwner.from_id.user_id;
} else {
TLRPC.Chat chat = MessagesController.getInstance(object.currentAccount).getChat(object.messageOwner.peer_id.channel_id);
if (ChatObject.isChannel(chat) && chat.megagroup && object.isForwardedChannelPost()) {
uid = -object.messageOwner.fwd_from.from_id.channel_id;
} else {
uid = -object.messageOwner.peer_id.channel_id;
}
}
if (!uids.contains(uid)) {
uids.add(uid);
}
}
if (uids.size() + hiddenSendersName.size() > 1) {
multiplyUsers = true;
}
for (int i = 0; i < groupedMessagesMap.size(); i++) {
groupedMessagesMap.valueAt(i).calculate();
}
}
public void getSelectedMessages(ArrayList<MessageObject> messagesToForward) {
messagesToForward.clear();
for (int i = 0; i < messages.size(); i++) {
MessageObject messageObject = messages.get(i);
int id = messageObject.getId();
if (selectedIds.get(id, false)) {
messagesToForward.add(messageObject);
}
}
}
public class PreviewMediaPoll extends TLRPC.TL_messageMediaPoll {
public int totalVotersCached;
}
}

View file

@ -134,12 +134,21 @@ public class GcmPushListenerService extends FirebaseMessagingService {
}
}
int account = UserConfig.selectedAccount;
boolean foundAccount = false;
for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) {
if (UserConfig.getInstance(a).getClientUserId() == accountUserId) {
account = a;
foundAccount = true;
break;
}
}
if (!foundAccount) {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("GCM ACCOUNT NOT FOUND");
}
countDownLatch.countDown();
return;
}
final int accountFinal = currentAccount = account;
if (!UserConfig.getInstance(currentAccount).isClientActivated()) {
if (BuildVars.LOGS_ENABLED) {
@ -1109,6 +1118,11 @@ public class GcmPushListenerService extends FirebaseMessagingService {
if (token == null) {
return;
}
boolean sendStat = false;
if (SharedConfig.pushStringGetTimeStart != 0 && SharedConfig.pushStringGetTimeEnd != 0 && (!SharedConfig.pushStatSent || !TextUtils.equals(SharedConfig.pushString, token))) {
sendStat = true;
SharedConfig.pushStatSent = false;
}
SharedConfig.pushString = token;
for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) {
UserConfig userConfig = UserConfig.getInstance(a);
@ -1116,6 +1130,30 @@ public class GcmPushListenerService extends FirebaseMessagingService {
userConfig.saveConfig(false);
if (userConfig.getClientUserId() != 0) {
final int currentAccount = a;
if (sendStat) {
TLRPC.TL_help_saveAppLog req = new TLRPC.TL_help_saveAppLog();
TLRPC.TL_inputAppEvent event = new TLRPC.TL_inputAppEvent();
event.time = SharedConfig.pushStringGetTimeStart;
event.type = "fcm_token_request";
event.peer = 0;
event.data = new TLRPC.TL_jsonNull();
req.events.add(event);
event = new TLRPC.TL_inputAppEvent();
event.time = SharedConfig.pushStringGetTimeEnd;
event.type = "fcm_token_response";
event.peer = SharedConfig.pushStringGetTimeEnd - SharedConfig.pushStringGetTimeStart;
event.data = new TLRPC.TL_jsonNull();
req.events.add(event);
sendStat = false;
ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> {
if (error != null) {
SharedConfig.pushStatSent = true;
SharedConfig.saveConfig();
}
}));
}
AndroidUtilities.runOnUIThread(() -> MessagesController.getInstance(currentAccount).registerForPush(token));
}
}

View file

@ -342,7 +342,8 @@ public class ImageLocation {
} else if (document != null) {
if (!url && document instanceof DocumentObject.ThemeDocument) {
DocumentObject.ThemeDocument themeDocument = (DocumentObject.ThemeDocument) document;
return document.dc_id + "_" + document.id + "_" + Theme.getBaseThemeKey(themeDocument.themeSettings) + "_" + themeDocument.themeSettings.accent_color + "_" + themeDocument.themeSettings.message_top_color + "_" + themeDocument.themeSettings.message_bottom_color;
return document.dc_id + "_" + document.id + "_" + Theme.getBaseThemeKey(themeDocument.themeSettings) + "_" + themeDocument.themeSettings.accent_color + "_" +
(themeDocument.themeSettings.message_colors.size() > 1 ? themeDocument.themeSettings.message_colors.get(1) : 0) + "_" + (themeDocument.themeSettings.message_colors.size() > 0 ? themeDocument.themeSettings.message_colors.get(0) : 0);
} else if (document.id != 0 && document.dc_id != 0) {
return document.dc_id + "_" + document.id;
}

View file

@ -203,7 +203,6 @@ public class MediaDataController extends BaseController {
loadingStickers[a] = false;
stickersLoaded[a] = false;
}
featuredStickerSets.clear();
loadingPinnedMessages.clear();
loadFeaturedDate = 0;
loadFeaturedHash = 0;

View file

@ -122,6 +122,9 @@ public class MessageObject {
public boolean isRestrictedMessage;
public long loadedFileSize;
public byte[] sponsoredId;
public String botStartParam;
public boolean animateComments;
public boolean loadingCancelled;
@ -135,6 +138,7 @@ public class MessageObject {
public boolean cancelEditing;
public boolean scheduled;
public boolean preview;
public ArrayList<TLRPC.TL_pollAnswer> checkedVotes;
@ -179,6 +183,7 @@ public class MessageObject {
public ArrayList<String> highlightedWords;
public String messageTrimmedToHighlight;
public int parentWidth;
static final String[] excludeWords = new String[] {
" vs. ",
@ -1579,6 +1584,27 @@ public class MessageObject {
} else {
message.media = new TLRPC.TL_messageMediaEmpty();
}
} else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionChangeTheme) {
messageText = replaceWithLink(chat.megagroup ? LocaleController.getString("EventLogEditedGroupTheme", R.string.EventLogEditedGroupTheme) : LocaleController.getString("EventLogEditedChannelTheme", R.string.EventLogEditedChannelTheme), "un1", fromUser);
message = new TLRPC.TL_message();
message.out = false;
message.unread = false;
message.from_id = new TLRPC.TL_peerUser();
message.from_id.user_id = event.user_id;
message.peer_id = peer_id;
message.date = event.date;
message.message = ((TLRPC.TL_channelAdminLogEventActionChangeTheme) event.action).new_value;
if (!TextUtils.isEmpty(((TLRPC.TL_channelAdminLogEventActionChangeTheme) event.action).prev_value)) {
message.media = new TLRPC.TL_messageMediaWebPage();
message.media.webpage = new TLRPC.TL_webPage();
message.media.webpage.flags = 10;
message.media.webpage.display_url = "";
message.media.webpage.url = "";
message.media.webpage.site_name = LocaleController.getString("EventLogPreviousGroupTheme", R.string.EventLogPreviousGroupTheme);
message.media.webpage.description = ((TLRPC.TL_channelAdminLogEventActionChangeTheme) event.action).prev_value;
} else {
message.media = new TLRPC.TL_messageMediaEmpty();
}
} else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionChangeUsername) {
String newLink = ((TLRPC.TL_channelAdminLogEventActionChangeUsername) event.action).new_value;
if (!TextUtils.isEmpty(newLink)) {
@ -1710,9 +1736,17 @@ public class MessageObject {
messageText = replaceWithLink(LocaleController.formatString("EventLogToggledSlowmodeOn", R.string.EventLogToggledSlowmodeOn, string), "un1", fromUser);
}
} else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionStartGroupCall) {
messageText = replaceWithLink(LocaleController.getString("EventLogStartedVoiceChat", R.string.EventLogStartedVoiceChat), "un1", fromUser);
if (ChatObject.isChannel(chat) && (!chat.megagroup || chat.gigagroup)) {
messageText = replaceWithLink(LocaleController.getString("EventLogStartedLiveStream", R.string.EventLogStartedLiveStream), "un1", fromUser);
} else {
messageText = replaceWithLink(LocaleController.getString("EventLogStartedVoiceChat", R.string.EventLogStartedVoiceChat), "un1", fromUser);
}
} else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionDiscardGroupCall) {
messageText = replaceWithLink(LocaleController.getString("EventLogEndedVoiceChat", R.string.EventLogEndedVoiceChat), "un1", fromUser);
if (ChatObject.isChannel(chat) && (!chat.megagroup || chat.gigagroup)) {
messageText = replaceWithLink(LocaleController.getString("EventLogEndedLiveStream", R.string.EventLogEndedLiveStream), "un1", fromUser);
} else {
messageText = replaceWithLink(LocaleController.getString("EventLogEndedVoiceChat", R.string.EventLogEndedVoiceChat), "un1", fromUser);
}
} else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionParticipantMute) {
TLRPC.TL_channelAdminLogEventActionParticipantMute action = (TLRPC.TL_channelAdminLogEventActionParticipantMute) event.action;
int id = getPeerId(action.participant.peer);
@ -2211,6 +2245,10 @@ public class MessageObject {
return false;
}
public boolean isSponsored() {
return sponsoredId != null;
}
public long getPollId() {
if (type != TYPE_POLL) {
return 0;
@ -2466,7 +2504,11 @@ public class MessageObject {
if (messageOwner.action != null) {
if (messageOwner.action instanceof TLRPC.TL_messageActionGroupCallScheduled) {
TLRPC.TL_messageActionGroupCallScheduled action = (TLRPC.TL_messageActionGroupCallScheduled) messageOwner.action;
messageText = LocaleController.formatString("ActionGroupCallScheduled", R.string.ActionGroupCallScheduled, LocaleController.formatStartsTime(action.schedule_date, 3, false));
if (messageOwner.peer_id instanceof TLRPC.TL_peerChat || isSupergroup()) {
messageText = LocaleController.formatString("ActionGroupCallScheduled", R.string.ActionGroupCallScheduled, LocaleController.formatStartsTime(action.schedule_date, 3, false));
} else {
messageText = LocaleController.formatString("ActionChannelCallScheduled", R.string.ActionChannelCallScheduled, LocaleController.formatStartsTime(action.schedule_date, 3, false));
}
} else if (messageOwner.action instanceof TLRPC.TL_messageActionGroupCall) {
if (messageOwner.action.duration != 0) {
String time;
@ -2494,7 +2536,7 @@ public class MessageObject {
messageText = replaceWithLink(LocaleController.formatString("ActionGroupCallEndedBy", R.string.ActionGroupCallEndedBy, time), "un1", fromObject);
}
} else {
messageText = LocaleController.formatString("ActionGroupCallEnded", R.string.ActionGroupCallEnded, time);
messageText = LocaleController.formatString("ActionChannelCallEnded", R.string.ActionChannelCallEnded, time);
}
} else {
if (messageOwner.peer_id instanceof TLRPC.TL_peerChat || isSupergroup()) {
@ -2504,7 +2546,7 @@ public class MessageObject {
messageText = replaceWithLink(LocaleController.getString("ActionGroupCallStarted", R.string.ActionGroupCallStarted), "un1", fromObject);
}
} else {
messageText = LocaleController.getString("ActionGroupCallJustStarted", R.string.ActionGroupCallJustStarted);
messageText = LocaleController.getString("ActionChannelCallJustStarted", R.string.ActionChannelCallJustStarted);
}
}
} else if (messageOwner.action instanceof TLRPC.TL_messageActionInviteToGroupCall) {
@ -4113,7 +4155,9 @@ public class MessageObject {
}
public boolean needDrawShareButton() {
if (scheduled) {
if (preview) {
return false;
} else if (scheduled) {
return false;
} else if (eventId != 0) {
return false;
@ -4160,7 +4204,7 @@ public class MessageObject {
if (AndroidUtilities.isTablet() && eventId != 0) {
generatedWithMinSize = AndroidUtilities.dp(530);
} else {
generatedWithMinSize = AndroidUtilities.isTablet() ? AndroidUtilities.getMinTabletSide() : AndroidUtilities.displaySize.x;
generatedWithMinSize = AndroidUtilities.isTablet() ? AndroidUtilities.getMinTabletSide() : getParentWidth();
}
generatedWithDensity = AndroidUtilities.density;
if (messageOwner.media instanceof TLRPC.TL_messageMediaWebPage && messageOwner.media.webpage != null && "telegram_background".equals(messageOwner.media.webpage.type)) {
@ -4464,6 +4508,9 @@ public class MessageObject {
}
public boolean isOutOwner() {
if (preview) {
return true;
}
TLRPC.Chat chat = messageOwner.peer_id != null && messageOwner.peer_id.channel_id != 0 ? getChat(null, null, messageOwner.peer_id.channel_id) : null;
if (!messageOwner.out || !(messageOwner.from_id instanceof TLRPC.TL_peerUser) && (!(messageOwner.from_id instanceof TLRPC.TL_peerChannel) || ChatObject.isChannel(chat) && !chat.megagroup) || messageOwner.post) {
return false;
@ -4480,11 +4527,11 @@ public class MessageObject {
}
public boolean needDrawAvatar() {
return isFromUser() || isFromGroup() || eventId != 0 || messageOwner.fwd_from != null && messageOwner.fwd_from.saved_from_peer != null;
return !isSponsored() && (isFromUser() || isFromGroup() || eventId != 0 || messageOwner.fwd_from != null && messageOwner.fwd_from.saved_from_peer != null);
}
private boolean needDrawAvatarInternal() {
return isFromChat() && isFromUser() || isFromGroup() || eventId != 0 || messageOwner.fwd_from != null && messageOwner.fwd_from.saved_from_peer != null;
return !isSponsored() && (isFromChat() && isFromUser() || isFromGroup() || eventId != 0 || messageOwner.fwd_from != null && messageOwner.fwd_from.saved_from_peer != null);
}
public boolean isFromChat() {
@ -4541,7 +4588,7 @@ public class MessageObject {
}
public boolean isForwardedChannelPost() {
return messageOwner.from_id instanceof TLRPC.TL_peerChannel && messageOwner.fwd_from != null && messageOwner.fwd_from.channel_post != 0;
return messageOwner.from_id instanceof TLRPC.TL_peerChannel && messageOwner.fwd_from != null && messageOwner.fwd_from.channel_post != 0 && messageOwner.fwd_from.saved_from_peer instanceof TLRPC.TL_peerChannel && messageOwner.from_id.channel_id == messageOwner.fwd_from.saved_from_peer.channel_id;
}
public boolean isUnread() {
@ -5202,6 +5249,10 @@ public class MessageObject {
}
}
private int getParentWidth() {
return (preview && parentWidth > 0) ? parentWidth : AndroidUtilities.displaySize.x;
}
public String getStickerEmoji() {
TLRPC.Document document = getDocument();
if (document == null) {
@ -5604,7 +5655,7 @@ public class MessageObject {
}
public boolean canForwardMessage() {
return !(messageOwner instanceof TLRPC.TL_message_secret) && !needDrawBluredPreview() && !isLiveLocation() && type != 16;
return !(messageOwner instanceof TLRPC.TL_message_secret) && !needDrawBluredPreview() && !isLiveLocation() && type != 16 && !isSponsored();
}
public boolean canEditMedia() {
@ -5712,7 +5763,7 @@ public class MessageObject {
}
public boolean canDeleteMessage(boolean inScheduleMode, TLRPC.Chat chat) {
return eventId == 0 && canDeleteMessage(currentAccount, inScheduleMode, messageOwner, chat);
return eventId == 0 && sponsoredId == null && canDeleteMessage(currentAccount, inScheduleMode, messageOwner, chat);
}
public static boolean canDeleteMessage(int currentAccount, boolean inScheduleMode, TLRPC.Message message, TLRPC.Chat chat) {

View file

@ -78,7 +78,7 @@ public class MessagesController extends BaseController implements NotificationCe
private SparseArray<TLRPC.TL_chatInviteExported> exportedChats = new SparseArray<>();
public ArrayList<TLRPC.RecentMeUrl> hintDialogs = new ArrayList<>();
private SparseArray<ArrayList<TLRPC.Dialog>> dialogsByFolder = new SparseArray<>();
public SparseArray<ArrayList<TLRPC.Dialog>> dialogsByFolder = new SparseArray<>();
protected ArrayList<TLRPC.Dialog> allDialogs = new ArrayList<>();
public ArrayList<TLRPC.Dialog> dialogsForward = new ArrayList<>();
public ArrayList<TLRPC.Dialog> dialogsServerOnly = new ArrayList<>();
@ -100,7 +100,7 @@ public class MessagesController extends BaseController implements NotificationCe
public ConcurrentHashMap<Long, ConcurrentHashMap<Integer, ArrayList<PrintingUser>>> printingUsers = new ConcurrentHashMap<>(20, 1.0f, 2);
public LongSparseArray<SparseArray<CharSequence>> printingStrings = new LongSparseArray<>();
public LongSparseArray<SparseArray<Integer>> printingStringsTypes = new LongSparseArray<>();
public LongSparseArray<SparseArray<Boolean>>[] sendingTypings = new LongSparseArray[10];
public LongSparseArray<SparseArray<Boolean>>[] sendingTypings = new LongSparseArray[11];
public ConcurrentHashMap<Integer, Integer> onlinePrivacy = new ConcurrentHashMap<>(20, 1.0f, 2);
private int lastPrintingStringCount;
@ -171,6 +171,8 @@ public class MessagesController extends BaseController implements NotificationCe
private SparseIntArray migratedChats = new SparseIntArray();
private LongSparseArray<SponsoredMessagesInfo> sponsoredMessages = new LongSparseArray<>();
private HashMap<String, ArrayList<MessageObject>> reloadingWebpages = new HashMap<>();
private LongSparseArray<ArrayList<MessageObject>> reloadingWebpagesPending = new LongSparseArray<>();
private HashMap<String, ArrayList<MessageObject>> reloadingScheduledWebpages = new HashMap<>();
@ -322,6 +324,12 @@ public class MessagesController extends BaseController implements NotificationCe
public volatile boolean ignoreSetOnline;
private class SponsoredMessagesInfo {
private ArrayList<MessageObject> messages;
private long loadTime;
private boolean loading;
}
public static class FaqSearchResult {
public String title;
@ -2287,14 +2295,18 @@ public class MessagesController extends BaseController implements NotificationCe
settings.base_theme = Theme.getBaseThemeByKey(themeInfo.name);
settings.accent_color = accent.accentColor;
if (accent.myMessagesAccentColor != 0) {
settings.message_bottom_color = accent.myMessagesAccentColor;
settings.message_colors.add(accent.myMessagesAccentColor);
settings.flags |= 1;
}
if (accent.myMessagesGradientAccentColor != 0) {
settings.message_top_color = accent.myMessagesGradientAccentColor;
settings.flags |= 1;
} else if (settings.message_bottom_color != 0) {
settings.message_top_color = settings.message_bottom_color;
if (accent.myMessagesGradientAccentColor1 != 0) {
settings.message_colors.add(accent.myMessagesGradientAccentColor1);
if (accent.myMessagesGradientAccentColor2 != 0) {
settings.message_colors.add(accent.myMessagesGradientAccentColor2);
if (accent.myMessagesGradientAccentColor3 != 0) {
settings.message_colors.add(accent.myMessagesGradientAccentColor3);
}
}
}
settings.message_colors_animated = accent.myMessagesAnimated;
}
settings.flags |= 2;
settings.wallpaper_settings = new TLRPC.TL_wallPaperSettings();
@ -2538,6 +2550,7 @@ public class MessagesController extends BaseController implements NotificationCe
reloadingWebpagesPending.clear();
reloadingScheduledWebpages.clear();
reloadingScheduledWebpagesPending.clear();
sponsoredMessages.clear();
dialogs_dict.clear();
dialogs_read_inbox_max.clear();
loadingPinnedDialogs.clear();
@ -3865,7 +3878,7 @@ public class MessagesController extends BaseController implements NotificationCe
if (currentDeletingTaskMedia) {
getMessagesStorage().emptyMessagesMedia(mids);
} else {
deleteMessages(mids, null, null, 0, 0, false, false, !mids.isEmpty() && mids.get(0) > 0);
deleteMessages(mids, null, null, 0, 0, true, false, !mids.isEmpty() && mids.get(0) > 0);
}
Utilities.stageQueue.postRunnable(() -> {
getNewDeleteTask(mids, currentDeletingTaskChannelId, currentDeletingTaskMedia);
@ -5945,6 +5958,13 @@ public class MessagesController extends BaseController implements NotificationCe
newPrintingStrings.put(threadId, LocaleController.getString("SelectingContact", R.string.SelectingContact));
}
newPrintingStringsTypes.put(threadId, 0);
} else if (pu.action instanceof TLRPC.TL_sendMessageChooseStickerAction) {
if (lower_id < 0) {
newPrintingStrings.put(threadId, LocaleController.formatString("IsChoosingSticker", R.string.IsChoosingSticker, getUserNameForTyping(user)));
} else {
newPrintingStrings.put(threadId, LocaleController.getString("ChoosingSticker", R.string.ChoosingSticker));
}
newPrintingStringsTypes.put(threadId, 5);
} else {
if (lower_id < 0) {
newPrintingStrings.put(threadId, LocaleController.formatString("IsTypingGroup", R.string.IsTypingGroup, getUserNameForTyping(user)));
@ -6084,6 +6104,8 @@ public class MessagesController extends BaseController implements NotificationCe
req.action = new TLRPC.TL_sendMessageUploadRoundAction();
} else if (action == 9) {
req.action = new TLRPC.TL_sendMessageUploadAudioAction();
} else if (action == 10) {
req.action = new TLRPC.TL_sendMessageChooseStickerAction();
}
threads.put(threadMsgId, true);
int reqId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> cancelTyping(action, dialogId, threadMsgId)), ConnectionsManager.RequestFlagFailOnServerErrors);
@ -13753,6 +13775,81 @@ public class MessagesController extends BaseController implements NotificationCe
return false;
}
public ArrayList<MessageObject> getSponsoredMessages(long dialogId) {
SponsoredMessagesInfo info = sponsoredMessages.get(dialogId);
if (info != null && (info.loading || Math.abs(SystemClock.elapsedRealtime() - info.loadTime) <= 5 * 60 * 1000)) {
return info.messages;
}
TLRPC.Chat chat = getChat((int) -dialogId); //TODO long
if (!ChatObject.isChannel(chat) || chat.megagroup || chat.gigagroup) {
return null;
}
info = new SponsoredMessagesInfo();
info.loading = true;
sponsoredMessages.put(dialogId, info);
SponsoredMessagesInfo infoFinal = info;
TLRPC.TL_channels_getSponsoredMessages req = new TLRPC.TL_channels_getSponsoredMessages();
req.channel = getInputChannel(chat);
getConnectionsManager().sendRequest(req, (response, error) -> {
ArrayList<MessageObject> result;
if (response != null) {
TLRPC.TL_messages_sponsoredMessages res = (TLRPC.TL_messages_sponsoredMessages) response;
if (res.messages.isEmpty()) {
result = null;
} else {
result = new ArrayList<>();
AndroidUtilities.runOnUIThread(() -> {
putUsers(res.users, false);
putChats(res.chats, false);
});
final SparseArray<TLRPC.User> usersDict = new SparseArray<>();
final SparseArray<TLRPC.Chat> chatsDict = new SparseArray<>();
for (int a = 0; a < res.users.size(); a++) {
TLRPC.User u = res.users.get(a);
usersDict.put(u.id, u);
}
for (int a = 0; a < res.chats.size(); a++) {
TLRPC.Chat c = res.chats.get(a);
chatsDict.put(c.id, c);
}
int messageId = -10000000;
for (int a = 0, N = res.messages.size(); a < N; a++) {
TLRPC.TL_sponsoredMessage sponsoredMessage = res.messages.get(a);
TLRPC.TL_message message = new TLRPC.TL_message();
message.message = sponsoredMessage.message;
if (!sponsoredMessage.entities.isEmpty()) {
message.entities = sponsoredMessage.entities;
message.flags |= 128;
}
message.peer_id = getPeer((int) dialogId); //TODO long
message.from_id = sponsoredMessage.from_id;
message.flags |= 256;
message.date = getConnectionsManager().getCurrentTime();
message.id = messageId--;
MessageObject messageObject = new MessageObject(currentAccount, message, usersDict, chatsDict, true, true);
messageObject.sponsoredId = sponsoredMessage.random_id;
messageObject.botStartParam = sponsoredMessage.start_param;
result.add(messageObject);
}
}
} else {
result = null;
}
AndroidUtilities.runOnUIThread(() -> {
if (result == null) {
sponsoredMessages.remove(dialogId);
} else {
infoFinal.loadTime = SystemClock.elapsedRealtime();
infoFinal.messages = result;
getNotificationCenter().postNotificationName(NotificationCenter.didLoadSponsoredMessages, dialogId, result);
}
});
});
return null;
}
public CharSequence getPrintingString(long dialogId, int threadId, boolean isDialog) {
if (isDialog && (int) dialogId > 0) {
TLRPC.User user = getUser((int) dialogId);

View file

@ -6743,7 +6743,11 @@ public class MessagesStorage extends BaseController {
}
if (MessageObject.isSecretMedia(message)) {
try {
SQLiteCursor cursor2 = database.queryFinalized(String.format(Locale.US, "SELECT date FROM enc_tasks_v3 WHERE mid = %d", message.id));
long messageId = message.id;
if (message.peer_id.channel_id != 0) {
messageId |= ((long) message.peer_id.channel_id) << 32;
}
SQLiteCursor cursor2 = database.queryFinalized(String.format(Locale.US, "SELECT date FROM enc_tasks_v3 WHERE mid = %d AND media = 1", messageId));
if (cursor2.next()) {
message.destroyTime = cursor2.intValue(0);
}
@ -10732,6 +10736,15 @@ public class MessagesStorage extends BaseController {
chatsToLoad.add(-message.ttl);
}
}
if (message.params != null) {
String peerIdStr = message.params.get("fwd_peer");
if (peerIdStr != null) {
int peerId = Utilities.parseInt(peerIdStr);
if (peerId < 0) {
chatsToLoad.add(-peerId);
}
}
}
}
public void getDialogs(final int folderId, final int offset, final int count, final boolean loadDraftsPeersAndFolders) {

View file

@ -32,6 +32,7 @@ public class NotificationCenter {
public static final int commentsRead = totalEvents++;
public static final int changeRepliesCounter = totalEvents++;
public static final int messagesDidLoad = totalEvents++;
public static final int didLoadSponsoredMessages = totalEvents++;
public static final int messagesDidLoadWithoutProcess = totalEvents++;
public static final int loadingMessagesFailed = totalEvents++;
public static final int messageReceivedByAck = totalEvents++;
@ -188,6 +189,7 @@ public class NotificationCenter {
public static final int didReceiveSmsCode = totalEvents++;
public static final int didReceiveCall = totalEvents++;
public static final int emojiLoaded = totalEvents++;
public static final int invalidateMotionBackground = totalEvents++;
public static final int closeOtherAppActivities = totalEvents++;
public static final int cameraInitied = totalEvents++;
public static final int didReplacedPhotoInMemCache = totalEvents++;
@ -411,7 +413,7 @@ public class NotificationCenter {
}
public void postNotificationName(int id, Object... args) {
boolean allowDuringAnimation = id == startAllHeavyOperations || id == stopAllHeavyOperations || id == didReplacedPhotoInMemCache || id == closeChats;
boolean allowDuringAnimation = id == startAllHeavyOperations || id == stopAllHeavyOperations || id == didReplacedPhotoInMemCache || id == closeChats || id == invalidateMotionBackground;
ArrayList<Integer> expiredIndices = null;
if (!allowDuringAnimation && !allowedNotifications.isEmpty()) {
int size = allowedNotifications.size();

View file

@ -1448,7 +1448,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe
} else if ((int) did != 0) {
ArrayList<MessageObject> arrayList = new ArrayList<>();
arrayList.add(messageObject);
sendMessage(arrayList, did, true, 0);
sendMessage(arrayList, did, true, false, true, 0);
}
} else if (messageObject.messageOwner.message != null) {
TLRPC.WebPage webPage = null;
@ -1475,7 +1475,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe
} else if ((int) did != 0) {
ArrayList<MessageObject> arrayList = new ArrayList<>();
arrayList.add(messageObject);
sendMessage(arrayList, did, true, 0);
sendMessage(arrayList, did, true, false, true, 0);
}
}
@ -1633,7 +1633,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe
}
}
public int sendMessage(ArrayList<MessageObject> messages, final long peer, boolean notify, int scheduleDate) {
public int sendMessage(ArrayList<MessageObject> messages, final long peer, boolean forwardFromMyName, boolean hideCaption, boolean notify, int scheduleDate) {
if (messages == null || messages.isEmpty()) {
return 0;
}
@ -1716,68 +1716,74 @@ public class SendMessagesHelper extends BaseController implements NotificationCe
}
final TLRPC.Message newMsg = new TLRPC.TL_message();
boolean forwardFromSaved = msgObj.getDialogId() == myId && msgObj.isFromUser() && msgObj.messageOwner.from_id.user_id == myId;
if (msgObj.isForwarded()) {
newMsg.fwd_from = new TLRPC.TL_messageFwdHeader();
if ((msgObj.messageOwner.fwd_from.flags & 1) != 0) {
newMsg.fwd_from.flags |= 1;
newMsg.fwd_from.from_id = msgObj.messageOwner.fwd_from.from_id;
}
if ((msgObj.messageOwner.fwd_from.flags & 32) != 0) {
newMsg.fwd_from.flags |= 32;
newMsg.fwd_from.from_name = msgObj.messageOwner.fwd_from.from_name;
}
if ((msgObj.messageOwner.fwd_from.flags & 4) != 0) {
newMsg.fwd_from.flags |= 4;
newMsg.fwd_from.channel_post = msgObj.messageOwner.fwd_from.channel_post;
}
if ((msgObj.messageOwner.fwd_from.flags & 8) != 0) {
newMsg.fwd_from.flags |= 8;
newMsg.fwd_from.post_author = msgObj.messageOwner.fwd_from.post_author;
}
if ((peer == myId || isChannel) && (msgObj.messageOwner.fwd_from.flags & 16) != 0 && !UserObject.isReplyUser(msgObj.getDialogId())) {
newMsg.fwd_from.flags |= 16;
newMsg.fwd_from.saved_from_peer = msgObj.messageOwner.fwd_from.saved_from_peer;
newMsg.fwd_from.saved_from_msg_id = msgObj.messageOwner.fwd_from.saved_from_msg_id;
}
newMsg.fwd_from.date = msgObj.messageOwner.fwd_from.date;
newMsg.flags = TLRPC.MESSAGE_FLAG_FWD;
} else if (!forwardFromSaved) { //if (!toMyself || !msgObj.isOutOwner())
int fromId = msgObj.getFromChatId();
newMsg.fwd_from = new TLRPC.TL_messageFwdHeader();
newMsg.fwd_from.channel_post = msgObj.getId();
newMsg.fwd_from.flags |= 4;
if (msgObj.isFromUser()) {
newMsg.fwd_from.from_id = msgObj.messageOwner.from_id;
newMsg.fwd_from.flags |= 1;
} else {
newMsg.fwd_from.from_id = new TLRPC.TL_peerChannel();
newMsg.fwd_from.from_id.channel_id = msgObj.messageOwner.peer_id.channel_id;
newMsg.fwd_from.flags |= 1;
if (msgObj.messageOwner.post && fromId > 0) {
newMsg.fwd_from.from_id = msgObj.messageOwner.from_id != null ? msgObj.messageOwner.from_id : msgObj.messageOwner.peer_id;
if (!forwardFromMyName) {
boolean forwardFromSaved = msgObj.getDialogId() == myId && msgObj.isFromUser() && msgObj.messageOwner.from_id.user_id == myId;
if (msgObj.isForwarded()) {
newMsg.fwd_from = new TLRPC.TL_messageFwdHeader();
if ((msgObj.messageOwner.fwd_from.flags & 1) != 0) {
newMsg.fwd_from.flags |= 1;
newMsg.fwd_from.from_id = msgObj.messageOwner.fwd_from.from_id;
}
}
if (msgObj.messageOwner.post_author != null) {
if ((msgObj.messageOwner.fwd_from.flags & 32) != 0) {
newMsg.fwd_from.flags |= 32;
newMsg.fwd_from.from_name = msgObj.messageOwner.fwd_from.from_name;
}
if ((msgObj.messageOwner.fwd_from.flags & 4) != 0) {
newMsg.fwd_from.flags |= 4;
newMsg.fwd_from.channel_post = msgObj.messageOwner.fwd_from.channel_post;
}
if ((msgObj.messageOwner.fwd_from.flags & 8) != 0) {
newMsg.fwd_from.flags |= 8;
newMsg.fwd_from.post_author = msgObj.messageOwner.fwd_from.post_author;
}
if ((peer == myId || isChannel) && (msgObj.messageOwner.fwd_from.flags & 16) != 0 && !UserObject.isReplyUser(msgObj.getDialogId())) {
newMsg.fwd_from.flags |= 16;
newMsg.fwd_from.saved_from_peer = msgObj.messageOwner.fwd_from.saved_from_peer;
newMsg.fwd_from.saved_from_msg_id = msgObj.messageOwner.fwd_from.saved_from_msg_id;
}
newMsg.fwd_from.date = msgObj.messageOwner.fwd_from.date;
newMsg.flags = TLRPC.MESSAGE_FLAG_FWD;
} else if (!forwardFromSaved) { //if (!toMyself || !msgObj.isOutOwner())
int fromId = msgObj.getFromChatId();
newMsg.fwd_from = new TLRPC.TL_messageFwdHeader();
newMsg.fwd_from.channel_post = msgObj.getId();
newMsg.fwd_from.flags |= 4;
if (msgObj.isFromUser()) {
newMsg.fwd_from.from_id = msgObj.messageOwner.from_id;
newMsg.fwd_from.flags |= 1;
} else {
newMsg.fwd_from.from_id = new TLRPC.TL_peerChannel();
newMsg.fwd_from.from_id.channel_id = msgObj.messageOwner.peer_id.channel_id;
newMsg.fwd_from.flags |= 1;
if (msgObj.messageOwner.post && fromId > 0) {
newMsg.fwd_from.from_id = msgObj.messageOwner.from_id != null ? msgObj.messageOwner.from_id : msgObj.messageOwner.peer_id;
}
}
if (msgObj.messageOwner.post_author != null) {
/*newMsg.fwd_from.post_author = msgObj.messageOwner.post_author;
newMsg.fwd_from.flags |= 8;*/
} else if (!msgObj.isOutOwner() && fromId > 0 && msgObj.messageOwner.post) {
TLRPC.User signUser = getMessagesController().getUser(fromId);
if (signUser != null) {
newMsg.fwd_from.post_author = ContactsController.formatName(signUser.first_name, signUser.last_name);
newMsg.fwd_from.flags |= 8;
} else if (!msgObj.isOutOwner() && fromId > 0 && msgObj.messageOwner.post) {
TLRPC.User signUser = getMessagesController().getUser(fromId);
if (signUser != null) {
newMsg.fwd_from.post_author = ContactsController.formatName(signUser.first_name, signUser.last_name);
newMsg.fwd_from.flags |= 8;
}
}
newMsg.date = msgObj.messageOwner.date;
newMsg.flags = TLRPC.MESSAGE_FLAG_FWD;
}
if (peer == myId && newMsg.fwd_from != null) {
newMsg.fwd_from.flags |= 16;
newMsg.fwd_from.saved_from_msg_id = msgObj.getId();
newMsg.fwd_from.saved_from_peer = msgObj.messageOwner.peer_id;
if (newMsg.fwd_from.saved_from_peer.user_id == myId) {
newMsg.fwd_from.saved_from_peer.user_id = (int) msgObj.getDialogId();
}
}
newMsg.date = msgObj.messageOwner.date;
newMsg.flags = TLRPC.MESSAGE_FLAG_FWD;
}
if (peer == myId && newMsg.fwd_from != null) {
newMsg.fwd_from.flags |= 16;
newMsg.fwd_from.saved_from_msg_id = msgObj.getId();
newMsg.fwd_from.saved_from_peer = msgObj.messageOwner.peer_id;
if (newMsg.fwd_from.saved_from_peer.user_id == myId) {
newMsg.fwd_from.saved_from_peer.user_id = (int) msgObj.getDialogId();
}
} else {
newMsg.params = new HashMap<>();
newMsg.params.put("fwd_id", "" + msgObj.getId());
newMsg.params.put("fwd_peer", "" + msgObj.getDialogId());
}
if (!msgObj.messageOwner.restriction_reason.isEmpty()) {
newMsg.restriction_reason = msgObj.messageOwner.restriction_reason;
@ -1803,7 +1809,9 @@ public class SendMessagesHelper extends BaseController implements NotificationCe
newMsg.flags |= 8388608;
}
newMsg.message = msgObj.messageOwner.message;
if (!hideCaption || newMsg.media == null) {
newMsg.message = msgObj.messageOwner.message;
}
if (newMsg.message == null) {
newMsg.message = "";
}
@ -1960,6 +1968,8 @@ public class SendMessagesHelper extends BaseController implements NotificationCe
}
req.random_id = randomIds;
req.id = ids;
req.drop_author = forwardFromMyName;
req.drop_media_captions = hideCaption;
req.with_my_score = messages.size() == 1 && messages.get(0).messageOwner.with_my_score;
final ArrayList<TLRPC.Message> newMsgObjArr = arr;
@ -3135,7 +3145,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe
if (parentObject == null && params != null && params.containsKey("parentObject")) {
parentObject = params.get("parentObject");
}
if (retryMessageObject.isForwarded()) {
if (retryMessageObject.isForwarded() || params != null && params.containsKey("fwd_id")) {
type = 4;
} else {
if (retryMessageObject.isDice()) {
@ -4331,15 +4341,34 @@ public class SendMessagesHelper extends BaseController implements NotificationCe
TLRPC.TL_messages_forwardMessages reqSend = new TLRPC.TL_messages_forwardMessages();
reqSend.to_peer = sendToPeer;
reqSend.with_my_score = retryMessageObject.messageOwner.with_my_score;
if (retryMessageObject.messageOwner.ttl != 0) {
TLRPC.Chat chat = getMessagesController().getChat(-retryMessageObject.messageOwner.ttl);
reqSend.from_peer = new TLRPC.TL_inputPeerChannel();
reqSend.from_peer.channel_id = -retryMessageObject.messageOwner.ttl;
if (chat != null) {
reqSend.from_peer.access_hash = chat.access_hash;
if (params != null && params.containsKey("fwd_id")) {
int fwdId = Utilities.parseInt(params.get("fwd_id"));
reqSend.drop_author = true;
long peerId = Utilities.parseLong(params.get("fwd_peer"));
if (peerId < 0) {
TLRPC.Chat chat = getMessagesController().getChat((int) -peerId);
if (ChatObject.isChannel(chat)) {
reqSend.from_peer = new TLRPC.TL_inputPeerChannel();
reqSend.from_peer.channel_id = chat.id;
reqSend.from_peer.access_hash = chat.access_hash;
} else {
reqSend.from_peer = new TLRPC.TL_inputPeerEmpty();
}
} else {
reqSend.from_peer = new TLRPC.TL_inputPeerEmpty();
}
reqSend.id.add(fwdId);
} else {
reqSend.from_peer = new TLRPC.TL_inputPeerEmpty();
if (retryMessageObject.messageOwner.ttl != 0) {
TLRPC.Chat chat = getMessagesController().getChat(-retryMessageObject.messageOwner.ttl);
reqSend.from_peer = new TLRPC.TL_inputPeerChannel();
reqSend.from_peer.channel_id = -retryMessageObject.messageOwner.ttl;
if (chat != null) {
reqSend.from_peer.access_hash = chat.access_hash;
}
} else {
reqSend.from_peer = new TLRPC.TL_inputPeerEmpty();
}
}
reqSend.silent = newMsg.silent;
if (scheduleDate != 0) {
@ -6921,7 +6950,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe
return String.format(Locale.US, blur ? "%d_%d@%d_%d_b" : "%d_%d@%d_%d", photoSize.location.volume_id, photoSize.location.local_id, (int) (point.x / AndroidUtilities.density), (int) (point.y / AndroidUtilities.density));
}
private static boolean shouldSendWebPAsSticker(String path, Uri uri) {
public static boolean shouldSendWebPAsSticker(String path, Uri uri) {
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
try {

View file

@ -38,6 +38,9 @@ public class SharedConfig {
public static String pushString = "";
public static String pushStringStatus = "";
public static long pushStringGetTimeStart;
public static long pushStringGetTimeEnd;
public static boolean pushStatSent;
public static byte[] pushAuthKey;
public static byte[] pushAuthKeyId;
@ -66,6 +69,7 @@ public class SharedConfig {
public static int textSelectionHintShows;
public static int scheduledOrNoSoundHintShows;
public static int lockRecordAudioVideoHint;
public static boolean forwardingOptionsHintShown;
public static boolean searchMessagesAsListUsed;
public static boolean stickersReorderingHintUsed;
public static boolean disableVoiceAudioEffects;
@ -189,6 +193,7 @@ public class SharedConfig {
editor.putBoolean("useFingerprint", useFingerprint);
editor.putBoolean("allowScreenCapture", allowScreenCapture);
editor.putString("pushString2", pushString);
editor.putBoolean("pushStatSent", pushStatSent);
editor.putString("pushAuthKey", pushAuthKey != null ? Base64.encodeToString(pushAuthKey, Base64.DEFAULT) : "");
editor.putInt("lastLocalId", lastLocalId);
editor.putString("passportConfigJson", passportConfigJson);
@ -197,6 +202,7 @@ public class SharedConfig {
editor.putBoolean("sortFilesByName", sortFilesByName);
editor.putInt("textSelectionHintShows", textSelectionHintShows);
editor.putInt("scheduledOrNoSoundHintShows", scheduledOrNoSoundHintShows);
editor.putBoolean("forwardingOptionsHintShown", forwardingOptionsHintShown);
editor.putInt("lockRecordAudioVideoHint", lockRecordAudioVideoHint);
editor.putString("storageCacheDir", !TextUtils.isEmpty(storageCacheDir) ? storageCacheDir : "");
@ -252,6 +258,7 @@ public class SharedConfig {
allowScreenCapture = preferences.getBoolean("allowScreenCapture", false);
lastLocalId = preferences.getInt("lastLocalId", -210000);
pushString = preferences.getString("pushString2", "");
pushStatSent = preferences.getBoolean("pushStatSent", false);
passportConfigJson = preferences.getString("passportConfigJson", "");
passportConfigHash = preferences.getInt("passportConfigHash", 0);
storageCacheDir = preferences.getString("storageCacheDir", null);
@ -352,6 +359,7 @@ public class SharedConfig {
stickersReorderingHintUsed = preferences.getBoolean("stickersReorderingHintUsed", false);
textSelectionHintShows = preferences.getInt("textSelectionHintShows", 0);
scheduledOrNoSoundHintShows = preferences.getInt("scheduledOrNoSoundHintShows", 0);
forwardingOptionsHintShown = preferences.getBoolean("forwardingOptionsHintShown", false);
lockRecordAudioVideoHint = preferences.getInt("lockRecordAudioVideoHint", 0);
disableVoiceAudioEffects = preferences.getBoolean("disableVoiceAudioEffects", false);
noiseSupression = preferences.getBoolean("noiseSupression", false);
@ -513,6 +521,7 @@ public class SharedConfig {
textSelectionHintShows = 0;
scheduledOrNoSoundHintShows = 0;
lockRecordAudioVideoHint = 0;
forwardingOptionsHintShown = false;
saveConfig();
}
@ -561,6 +570,14 @@ public class SharedConfig {
editor.commit();
}
public static void forwardingOptionsHintHintShowed() {
SharedPreferences preferences = MessagesController.getGlobalMainSettings();
SharedPreferences.Editor editor = preferences.edit();
forwardingOptionsHintShown = true;
editor.putBoolean("forwardingOptionsHintShown", forwardingOptionsHintShown);
editor.commit();
}
public static void removeScheduledOrNoSuoundHint() {
SharedPreferences preferences = MessagesController.getGlobalMainSettings();
SharedPreferences.Editor editor = preferences.edit();

View file

@ -122,6 +122,8 @@ public class SvgHelper {
private float colorAlpha;
private float crossfadeAlpha = 1.0f;
private boolean aspectFill = true;
@Override
public int getIntrinsicHeight() {
return width;
@ -132,6 +134,16 @@ public class SvgHelper {
return height;
}
public void setAspectFill(boolean value) {
aspectFill = value;
}
public void overrideWidthAndHeight(int w, int h) {
width = w;
height = h;
}
@Override
public void draw(Canvas canvas) {
if (currentColorKey != null) {
@ -140,9 +152,12 @@ public class SvgHelper {
Rect bounds = getBounds();
float scaleX = bounds.width() / (float) width;
float scaleY = bounds.height() / (float) height;
float scale = Math.max(scaleX, scaleY);
float scale = aspectFill ? Math.max(scaleX, scaleY) : Math.min(scaleX, scaleY);
canvas.save();
canvas.translate(bounds.left, bounds.top);
if (!aspectFill) {
canvas.translate((bounds.width() - width * scale) / 2, (bounds.height() - height * scale) / 2);
}
canvas.scale(scale, scale);
for (int a = 0, N = commands.size(); a < N; a++) {
Object object = commands.get(a);
@ -1719,7 +1734,7 @@ public class SvgHelper {
int num = encoded[i] & 0xff;
if (num >= 128 + 64) {
int start = num - 128 - 64;
path.append("AACAAAAHAAALMAAAQASTAVAAAZaacaaaahaaalmaaaqastava.az0123456789-,".substring(start, start + 1));
path.append("AACAAAAHAAALMAAAQASTAVAAAZaacaaaahaaalmaaaqastava.az0123456789-,".charAt(start));
} else {
if (num >= 128) {
path.append(',');

View file

@ -103,10 +103,6 @@ public class UserConfig extends BaseController {
}
public void saveConfig(boolean withFile) {
saveConfig(withFile, null);
}
public void saveConfig(boolean withFile, File oldFile) {
NotificationCenter.getInstance(currentAccount).doOnIdle(() -> {
synchronized (sync) {
try {
@ -184,9 +180,6 @@ public class UserConfig extends BaseController {
}
editor.commit();
if (oldFile != null) {
oldFile.delete();
}
} catch (Exception e) {
FileLog.e(e);
}

View file

@ -47,7 +47,7 @@ public class NativeInstance {
}
public interface RequestBroadcastPartCallback {
void run(long timestamp, long duration);
void run(long timestamp, long duration, int videoChannel, int quality);
}
public static NativeInstance make(String version, Instance.Config config, String path, Instance.Endpoint[] endpoints, Instance.Proxy proxy, int networkType, Instance.EncryptionKey encryptionKey, VideoSink remoteSink, long videoCapturer, AudioLevelsCallback audioLevelsCallback) {
@ -152,12 +152,12 @@ public class NativeInstance {
}
}
private void onRequestBroadcastPart(long timestamp, long duration) {
requestBroadcastPartCallback.run(timestamp, duration);
private void onRequestBroadcastPart(long timestamp, long duration, int videoChannel, int quality) {
requestBroadcastPartCallback.run(timestamp, duration, videoChannel, quality);
}
private void onCancelRequestBroadcastPart(long timestamp) {
cancelRequestBroadcastPartCallback.run(timestamp, 0);
private void onCancelRequestBroadcastPart(long timestamp, int videoChannel, int quality) {
cancelRequestBroadcastPartCallback.run(timestamp, 0, 0, 0);
}
public native void setJoinResponsePayload(String payload);
@ -222,6 +222,6 @@ public class NativeInstance {
public native void switchCamera(boolean front);
public native void setVideoState(int videoState);
public native void onSignalingDataReceive(byte[] data);
public native void onStreamPartAvailable(long ts, ByteBuffer buffer, int size, long timestamp);
public native void onStreamPartAvailable(long ts, ByteBuffer buffer, int size, long timestamp, int videoChannel, int quality);
public native boolean hasVideoCapturer();
}

View file

@ -187,8 +187,6 @@ public class VoIPService extends Service implements SensorEventListener, AudioMa
private boolean reconnectScreenCapture;
private int currentStreamRequestId;
private TLRPC.Chat chat;
private boolean isVideoAvailable;
@ -306,7 +304,7 @@ public class VoIPService extends Service implements SensorEventListener, AudioMa
private int classGuid;
private long currentStreamRequestTimestamp;
private HashMap<String, Integer> currentStreamRequestTimestamp = new HashMap<>();
public boolean micSwitching;
private Runnable afterSoundRunnable = new Runnable() {
@ -1272,32 +1270,15 @@ public class VoIPService extends Service implements SensorEventListener, AudioMa
return result;
}
public void requestFullScreen(TLRPC.TL_groupCallParticipant participant, boolean screencast) {
if (currentBackgroundSink[screencast ? CAPTURE_DEVICE_SCREEN : CAPTURE_DEVICE_CAMERA] != null) {
currentBackgroundSink[screencast ? CAPTURE_DEVICE_SCREEN : CAPTURE_DEVICE_CAMERA].setBackground(null);
}
if (participant == null) {
currentBackgroundSink[screencast ? CAPTURE_DEVICE_SCREEN : CAPTURE_DEVICE_CAMERA] = null;
currentBackgroundEndpointId[screencast ? CAPTURE_DEVICE_SCREEN : CAPTURE_DEVICE_CAMERA] = null;
return;
}
public void requestFullScreen(TLRPC.TL_groupCallParticipant participant, boolean full, boolean screencast) {
String endpointId = screencast ? participant.presentationEndpoint : participant.videoEndpoint;
if (endpointId == null) {
return;
}
ProxyVideoSink sink = remoteSinks.get(endpointId);
if (sink == null) {
sink = addRemoteSink(participant, screencast, null, null);
}
if (sink != null) {
sink.setBackground(remoteSink[screencast ? CAPTURE_DEVICE_SCREEN : CAPTURE_DEVICE_CAMERA]);
//tgVoip[CAPTURE_DEVICE_CAMERA].setVideoEndpointQuality(endpointId, QUALITY_FULL); TODO
currentBackgroundSink[screencast ? CAPTURE_DEVICE_SCREEN : CAPTURE_DEVICE_CAMERA] = sink;
currentBackgroundEndpointId[screencast ? CAPTURE_DEVICE_SCREEN : CAPTURE_DEVICE_CAMERA] = endpointId;
if (full) {
tgVoip[CAPTURE_DEVICE_CAMERA].setVideoEndpointQuality(endpointId, QUALITY_FULL);
} else {
//tgVoip[CAPTURE_DEVICE_CAMERA].setVideoEndpointQuality(endpointId, QUALITY_MEDIUM); TODO
currentBackgroundSink[screencast ? CAPTURE_DEVICE_SCREEN : CAPTURE_DEVICE_CAMERA] = null;
currentBackgroundEndpointId[screencast ? CAPTURE_DEVICE_SCREEN : CAPTURE_DEVICE_CAMERA] = null;
tgVoip[CAPTURE_DEVICE_CAMERA].setVideoEndpointQuality(endpointId, QUALITY_MEDIUM);
}
}
@ -2084,7 +2065,7 @@ public class VoIPService extends Service implements SensorEventListener, AudioMa
}
broadcastUnknownParticipants(taskPtr, unknown);
});
}, (timestamp, duration) -> {
}, (timestamp, duration, videoChannel, quality) -> {
if (type != CAPTURE_DEVICE_CAMERA) {
return;
}
@ -2096,15 +2077,21 @@ public class VoIPService extends Service implements SensorEventListener, AudioMa
if (duration == 500) {
inputGroupCallStream.scale = 1;
}
if (videoChannel != 0) {
inputGroupCallStream.flags |= 1;
inputGroupCallStream.video_channel = videoChannel;
inputGroupCallStream.video_quality = quality;
}
req.location = inputGroupCallStream;
currentStreamRequestTimestamp = timestamp;
currentStreamRequestId = AccountInstance.getInstance(currentAccount).getConnectionsManager().sendRequest(req, (response, error, responseTime) -> {
String key = videoChannel == 0 ? ("" + timestamp) : (videoChannel + "_" + timestamp + "_" + quality);
int reqId = AccountInstance.getInstance(currentAccount).getConnectionsManager().sendRequest(req, (response, error, responseTime) -> {
AndroidUtilities.runOnUIThread(() -> currentStreamRequestTimestamp.remove(key));
if (tgVoip[type] == null) {
return;
}
if (response != null) {
TLRPC.TL_upload_file res = (TLRPC.TL_upload_file) response;
tgVoip[type].onStreamPartAvailable(timestamp, res.bytes.buffer, res.bytes.limit(), responseTime);
tgVoip[type].onStreamPartAvailable(timestamp, res.bytes.buffer, res.bytes.limit(), responseTime, videoChannel, quality);
} else {
if ("GROUPCALL_JOIN_MISSING".equals(error.text)) {
AndroidUtilities.runOnUIThread(() -> createGroupInstance(type, false));
@ -2115,18 +2102,23 @@ public class VoIPService extends Service implements SensorEventListener, AudioMa
} else {
status = -1;
}
tgVoip[type].onStreamPartAvailable(timestamp, null, status, responseTime);
tgVoip[type].onStreamPartAvailable(timestamp, null, status, responseTime, videoChannel, quality);
}
}
}, ConnectionsManager.RequestFlagFailOnServerErrors, ConnectionsManager.ConnectionTypeDownload, groupCall.call.stream_dc_id);
}, (timestamp, duration) -> {
AndroidUtilities.runOnUIThread(() -> currentStreamRequestTimestamp.put(key, reqId));
}, (timestamp, duration, videoChannel, quality) -> {
if (type != CAPTURE_DEVICE_CAMERA) {
return;
}
if (currentStreamRequestTimestamp == timestamp) {
AccountInstance.getInstance(currentAccount).getConnectionsManager().cancelRequest(currentStreamRequestId, true);
currentStreamRequestId = 0;
}
AndroidUtilities.runOnUIThread(() -> {
String key = videoChannel == 0 ? ("" + timestamp) : (videoChannel + "_" + timestamp + "_" + quality);
Integer reqId = currentStreamRequestTimestamp.get(key);
if (reqId != null) {
AccountInstance.getInstance(currentAccount).getConnectionsManager().cancelRequest(reqId, true);
currentStreamRequestTimestamp.remove(key);
}
});
});
tgVoip[type].setOnStateUpdatedListener((state, inTransition) -> updateConnectionState(type, state, inTransition));
}
@ -2819,18 +2811,23 @@ public class VoIPService extends Service implements SensorEventListener, AudioMa
intent.putExtra("currentAccount", currentAccount);
}
Notification.Builder builder = new Notification.Builder(this)
.setContentTitle(groupCall != null ? LocaleController.getString("VoipVoiceChat", R.string.VoipVoiceChat) : LocaleController.getString("VoipOutgoingCall", R.string.VoipOutgoingCall))
.setContentText(name)
.setContentIntent(PendingIntent.getActivity(this, 50, intent, 0));
if (groupCall != null) {
builder.setContentTitle(ChatObject.isChannelOrGiga(chat) ? LocaleController.getString("VoipLiveStream", R.string.VoipLiveStream) : LocaleController.getString("VoipVoiceChat", R.string.VoipVoiceChat));
builder.setSmallIcon(isMicMute() ? R.drawable.voicechat_muted : R.drawable.voicechat_active);
} else {
builder.setContentTitle(LocaleController.getString("VoipOutgoingCall", R.string.VoipOutgoingCall));
builder.setSmallIcon(R.drawable.notification);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
Intent endIntent = new Intent(this, VoIPActionsReceiver.class);
endIntent.setAction(getPackageName() + ".END_CALL");
builder.addAction(R.drawable.ic_call_end_white_24dp, groupCall != null ? LocaleController.getString("VoipGroupLeaveAlertTitle", R.string.VoipGroupLeaveAlertTitle) : LocaleController.getString("VoipEndCall", R.string.VoipEndCall), PendingIntent.getBroadcast(this, 0, endIntent, PendingIntent.FLAG_UPDATE_CURRENT));
if (groupCall != null) {
builder.addAction(R.drawable.ic_call_end_white_24dp, ChatObject.isChannelOrGiga(chat) ? LocaleController.getString("VoipChannelLeaveAlertTitle", R.string.VoipChannelLeaveAlertTitle) : LocaleController.getString("VoipGroupLeaveAlertTitle", R.string.VoipGroupLeaveAlertTitle), PendingIntent.getBroadcast(this, 0, endIntent, PendingIntent.FLAG_UPDATE_CURRENT));
} else {
builder.addAction(R.drawable.ic_call_end_white_24dp, LocaleController.getString("VoipEndCall", R.string.VoipEndCall), PendingIntent.getBroadcast(this, 0, endIntent, PendingIntent.FLAG_UPDATE_CURRENT));
}
builder.setPriority(Notification.PRIORITY_MAX);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
@ -2954,8 +2951,10 @@ public class VoIPService extends Service implements SensorEventListener, AudioMa
if (tgVoip[CAPTURE_DEVICE_CAMERA].isGroup()) {
NativeInstance instance = tgVoip[CAPTURE_DEVICE_CAMERA];
Utilities.globalQueue.postRunnable(instance::stopGroup);
AccountInstance.getInstance(currentAccount).getConnectionsManager().cancelRequest(currentStreamRequestId, true);
currentStreamRequestId = 0;
for (HashMap.Entry<String, Integer> entry : currentStreamRequestTimestamp.entrySet()) {
AccountInstance.getInstance(currentAccount).getConnectionsManager().cancelRequest(entry.getValue(), true);
}
currentStreamRequestTimestamp.clear();
} else {
Instance.FinalState state = tgVoip[CAPTURE_DEVICE_CAMERA].stop();
updateTrafficStats(tgVoip[CAPTURE_DEVICE_CAMERA], state.trafficStats);

View file

@ -201,10 +201,7 @@ public class ConnectionsManager extends BaseController {
systemVersion = "SDK Unknown";
}
getUserConfig().loadConfig();
String pushString = SharedConfig.pushString;
if (TextUtils.isEmpty(pushString) && !TextUtils.isEmpty(SharedConfig.pushStringStatus)) {
pushString = SharedConfig.pushStringStatus;
}
String pushString = getRegId();
String fingerprint = AndroidUtilities.getCertificateSHA256Fingerprint();
int timezoneOffset = (TimeZone.getDefault().getRawOffset() + TimeZone.getDefault().getDSTSavings()) / 1000;
@ -212,6 +209,17 @@ public class ConnectionsManager extends BaseController {
init(BuildVars.BUILD_VERSION, TLRPC.LAYER, BuildVars.APP_ID, deviceModel, systemVersion, appVersion, langCode, systemLangCode, configPath, FileLog.getNetworkLogPath(), pushString, fingerprint, timezoneOffset, getUserConfig().getClientUserId(), enablePushConnection);
}
private String getRegId() {
String pushString = SharedConfig.pushString;
if (TextUtils.isEmpty(pushString) && !TextUtils.isEmpty(SharedConfig.pushStringStatus)) {
pushString = SharedConfig.pushStringStatus;
}
if (TextUtils.isEmpty(pushString)) {
pushString = SharedConfig.pushStringStatus = "__FIREBASE_GENERATING_SINCE_" + getCurrentTime() + "__";
}
return pushString;
}
public boolean isPushConnectionEnabled() {
SharedPreferences preferences = MessagesController.getGlobalNotificationsSettings();
if (preferences.contains("pushConnection")) {
@ -402,6 +410,9 @@ public class ConnectionsManager extends BaseController {
if (TextUtils.isEmpty(pushString) && !TextUtils.isEmpty(status)) {
pushString = status;
}
if (TextUtils.isEmpty(pushString)) {
pushString = SharedConfig.pushStringStatus = "__FIREBASE_GENERATING_SINCE_" + getInstance(0).getCurrentTime() + "__";
}
for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) {
native_setRegId(a, pushString);
}

File diff suppressed because it is too large Load diff

View file

@ -40,6 +40,8 @@ import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import com.google.android.exoplayer2.util.Log;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.FileLog;
@ -207,6 +209,12 @@ public class ActionBarLayout extends FrameLayout {
this.fragmentPanTranslationOffset = fragmentPanTranslationOffset;
invalidate();
}
@Override
public void setTranslationX(float translationX) {
Log.d("kek", "set translationX" + translationX);
super.setTranslationX(translationX);
}
}
private static Drawable headerShadowDrawable;
@ -982,7 +990,7 @@ public class ActionBarLayout extends FrameLayout {
return false;
}
fragment.setInPreviewMode(preview);
if (parentActivity.getCurrentFocus() != null && fragment.hideKeyboardOnShow()) {
if (parentActivity.getCurrentFocus() != null && fragment.hideKeyboardOnShow() && !preview) {
AndroidUtilities.hideKeyboard(parentActivity.getCurrentFocus());
}
boolean needAnimation = preview || !forceWithoutAnimation && MessagesController.getGlobalMainSettings().getBoolean("view_animations", true);
@ -1158,7 +1166,7 @@ public class ActionBarLayout extends FrameLayout {
containerView.setScaleY(1.0f);
}
if (containerView.isKeyboardVisible || containerViewBack.isKeyboardVisible) {
if (currentFragment != null) {
if (currentFragment != null && !preview) {
currentFragment.saveKeyboardPositionBeforeTransition();
}
waitingForKeyboardCloseRunnable = new Runnable() {
@ -1218,7 +1226,7 @@ public class ActionBarLayout extends FrameLayout {
startLayoutAnimation(true, true, preview);
}
} else {
if (containerView.isKeyboardVisible || containerViewBack.isKeyboardVisible && currentFragment != null) {
if (!preview && containerView.isKeyboardVisible || containerViewBack.isKeyboardVisible && currentFragment != null) {
currentFragment.saveKeyboardPositionBeforeTransition();
}
currentAnimation = animation;
@ -1443,7 +1451,7 @@ public class ActionBarLayout extends FrameLayout {
animation = currentFragment.onCustomTransitionAnimation(false, () -> onAnimationEndCheck(false));
}
if (animation == null) {
if (containerView.isKeyboardVisible || containerViewBack.isKeyboardVisible) {
if (!inPreviewMode && (containerView.isKeyboardVisible || containerViewBack.isKeyboardVisible)) {
waitingForKeyboardCloseRunnable = new Runnable() {
@Override
public void run() {

View file

@ -107,6 +107,12 @@ public class ActionBarMenuSubItem extends FrameLayout {
setTextAndIcon(text, icon, null);
}
public void setMultiline() {
textView.setLines(2);
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
textView.setSingleLine(false);
}
public void setTextAndIcon(CharSequence text, int icon, Drawable iconDrawable) {
textView.setText(text);
if (icon != 0 || iconDrawable != null || checkView != null) {

View file

@ -92,8 +92,8 @@ public class ActionBarPopupWindow extends PopupWindow {
private boolean animationEnabled = allowAnimation;
private ArrayList<AnimatorSet> itemAnimators;
private HashMap<View, Integer> positions = new HashMap<>();
private int gapStartY = Integer.MIN_VALUE;
private int gapEndY = Integer.MIN_VALUE;
private int gapStartY = -1000000;
private int gapEndY = -1000000;
private Rect bgPaddings = new Rect();
private ScrollView scrollView;
@ -105,9 +105,13 @@ public class ActionBarPopupWindow extends PopupWindow {
private boolean fitItems;
public ActionBarPopupWindowLayout(Context context) {
this(context, R.drawable.popup_fixed_alert2);
}
public ActionBarPopupWindowLayout(Context context, int resId) {
super(context);
backgroundDrawable = getResources().getDrawable(R.drawable.popup_fixed_alert2).mutate();
backgroundDrawable = getResources().getDrawable(resId).mutate();
if (backgroundDrawable != null) {
backgroundDrawable.getPadding(bgPaddings);
}
@ -130,8 +134,8 @@ public class ActionBarPopupWindow extends PopupWindow {
if (fitItems) {
int maxWidth = 0;
int fixWidth = 0;
gapStartY = Integer.MIN_VALUE;
gapEndY = Integer.MIN_VALUE;
gapStartY = -1000000;
gapEndY = -1000000;
ArrayList<View> viewsToFix = null;
for (int a = 0, N = getChildCount(); a < N; a++) {
View view = getChildAt(a);
@ -326,7 +330,7 @@ public class ActionBarPopupWindow extends PopupWindow {
if (a == 1 && start < -AndroidUtilities.dp(16)) {
break;
}
if (gapStartY != Integer.MIN_VALUE) {
if (gapStartY != -1000000) {
canvas.save();
canvas.clipRect(0, bgPaddings.top, getMeasuredWidth(), getMeasuredHeight());
}
@ -338,10 +342,10 @@ public class ActionBarPopupWindow extends PopupWindow {
if (start > -AndroidUtilities.dp(16)) {
int h = (int) (getMeasuredHeight() * backScaleY);
if (a == 0) {
backgroundDrawable.setBounds(0, -scrollView.getScrollY(), (int) (getMeasuredWidth() * backScaleX), Math.min(h, start + AndroidUtilities.dp(16)));
backgroundDrawable.setBounds(0, -scrollView.getScrollY() + (gapStartY != -1000000 ? AndroidUtilities.dp(1) : 0), (int) (getMeasuredWidth() * backScaleX), (gapStartY != -1000000 ? Math.min(h, start + AndroidUtilities.dp(16)) : h));
} else {
if (h < end) {
if (gapStartY != Integer.MIN_VALUE) {
if (gapStartY != -1000000) {
canvas.restore();
}
continue;
@ -353,7 +357,7 @@ public class ActionBarPopupWindow extends PopupWindow {
}
}
backgroundDrawable.draw(canvas);
if (gapStartY != Integer.MIN_VALUE) {
if (gapStartY != -1000000) {
canvas.restore();
}
}

View file

@ -28,6 +28,7 @@ import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.ui.Cells.DialogCell;
import org.telegram.ui.Components.EmptyStubSpan;
import org.telegram.ui.Components.StaticLayoutEx;
@ -44,6 +45,10 @@ public class SimpleTextView extends View implements Drawable.Callback {
private SpannableStringBuilder spannableStringBuilder;
private Drawable leftDrawable;
private Drawable rightDrawable;
private Drawable replacedDrawable;
private String replacedText;
private int replacingDrawableTextIndex;
private float replacingDrawableTextOffset;
private float rightDrawableScale = 1.0f;
private int drawablePadding = AndroidUtilities.dp(4);
private int leftDrawableTopPadding;
@ -211,9 +216,17 @@ public class SimpleTextView extends View implements Drawable.Callback {
fullLayoutLeftCharactersOffset = fullLayout.getPrimaryHorizontal(0) - firstLineLayout.getPrimaryHorizontal(0);
}
}
if (replacingDrawableTextIndex >= 0) {
replacingDrawableTextOffset = layout.getPrimaryHorizontal(replacingDrawableTextIndex);
} else {
replacingDrawableTextOffset = 0;
}
}
protected boolean createLayout(int width) {
CharSequence text = this.text;
replacingDrawableTextIndex = -1;
if (text != null) {
try {
if (leftDrawable != null) {
@ -225,6 +238,17 @@ public class SimpleTextView extends View implements Drawable.Callback {
width -= dw;
width -= drawablePadding;
}
if (replacedText != null && replacedDrawable != null) {
replacingDrawableTextIndex = text.toString().indexOf(replacedText);
if (replacingDrawableTextIndex >= 0) {
SpannableStringBuilder builder = SpannableStringBuilder.valueOf(text);
builder.setSpan(new DialogCell.FixedWidthSpan(replacedDrawable.getIntrinsicWidth()), replacingDrawableTextIndex, replacingDrawableTextIndex + replacedText.length(), 0);
text = builder;
} else {
width -= replacedDrawable.getIntrinsicWidth();
width -= drawablePadding;
}
}
if (buildFullLayout) {
CharSequence string = TextUtils.ellipsize(text, textPaint, width, TextUtils.TruncateAt.END);
if (!string.equals(text)) {
@ -379,6 +403,23 @@ public class SimpleTextView extends View implements Drawable.Callback {
}
}
public void replaceTextWithDrawable(Drawable drawable, String replacedText) {
if (replacedDrawable == drawable) {
return;
}
if (replacedDrawable != null) {
replacedDrawable.setCallback(null);
}
replacedDrawable = drawable;
if (drawable != null) {
drawable.setCallback(this);
}
if (!recreateLayoutMaybe()) {
invalidate();
}
this.replacedText = replacedText;
}
public void setMinusWidth(int value) {
if (value == minusWidth) {
return;
@ -486,6 +527,11 @@ public class SimpleTextView extends View implements Drawable.Callback {
textOffsetX += drawablePadding + leftDrawable.getIntrinsicWidth();
}
}
if (replacedDrawable != null && replacingDrawableTextIndex < 0) {
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT) {
textOffsetX += drawablePadding + replacedDrawable.getIntrinsicWidth();
}
}
return (int) getX() + offsetX + textOffsetX;
}
@ -530,6 +576,26 @@ public class SimpleTextView extends View implements Drawable.Callback {
}
totalWidth += drawablePadding + leftDrawable.getIntrinsicWidth();
}
if (replacedDrawable != null && replacedText != null) {
int x = (int) (-scrollingOffset + replacingDrawableTextOffset);
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.CENTER_HORIZONTAL) {
x += offsetX;
}
int y;
if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.CENTER_VERTICAL) {
y = (getMeasuredHeight() - replacedDrawable.getIntrinsicHeight()) / 2 + leftDrawableTopPadding;
} else {
y = (textHeight - replacedDrawable.getIntrinsicHeight()) / 2 + leftDrawableTopPadding;
}
replacedDrawable.setBounds(x, y, x + replacedDrawable.getIntrinsicWidth(), y + replacedDrawable.getIntrinsicHeight());
replacedDrawable.draw(canvas);
if (replacingDrawableTextIndex < 0) {
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT || (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.CENTER_HORIZONTAL) {
textOffsetX += drawablePadding + replacedDrawable.getIntrinsicWidth();
}
totalWidth += drawablePadding + replacedDrawable.getIntrinsicWidth();
}
}
if (rightDrawable != null) {
int x = textOffsetX + textWidth + drawablePadding + (int) -scrollingOffset;
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.CENTER_HORIZONTAL) {
@ -681,6 +747,8 @@ public class SimpleTextView extends View implements Drawable.Callback {
invalidate(leftDrawable.getBounds());
} else if (who == rightDrawable) {
invalidate(rightDrawable.getBounds());
} else if (who == replacedDrawable) {
invalidate(replacedDrawable.getBounds());
}
}

View file

@ -85,6 +85,7 @@ import org.telegram.tgnet.TLRPC;
import org.telegram.ui.Cells.ThemesHorizontalListCell;
import org.telegram.ui.Components.AudioVisualizerDrawable;
import org.telegram.ui.Components.BackgroundGradientDrawable;
import org.telegram.ui.Components.ChoosingStickerStatusDrawable;
import org.telegram.ui.Components.CombinedDrawable;
import org.telegram.ui.Components.FragmentContextViewWavesDrawable;
import org.telegram.ui.Components.MotionBackgroundDrawable;
@ -132,12 +133,15 @@ public class Theme {
public static class MessageDrawable extends Drawable {
private LinearGradient gradientShader;
private Shader gradientShader;
private int currentBackgroundHeight;
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint selectedPaint;
private int currentColor;
private int currentGradientColor;
private int currentGradientColor1;
private int currentGradientColor2;
private int currentGradientColor3;
private boolean currentAnimateGradient;
private RectF rect = new RectF();
private Matrix matrix = new Matrix();
@ -153,6 +157,8 @@ public class Theme {
private boolean isTopNear;
private boolean isBottomNear;
public static MotionBackgroundDrawable[] motionBackground = new MotionBackgroundDrawable[2];
private int[] currentShadowDrawableRadius = new int[]{-1, -1, -1, -1};
private Drawable[] shadowDrawable = new Drawable[4];
private int[] shadowDrawableColor = new int[]{0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff};
@ -172,6 +178,7 @@ public class Theme {
Drawable transitionDrawable;
int transitionDrawableColor;
private int alpha;
private boolean drawFullBubble;
public MessageDrawable(int type, boolean out, boolean selected) {
super();
@ -187,7 +194,18 @@ public class Theme {
return gradientShader != null && shouldDrawGradientIcons;
}
public LinearGradient getGradientShader() {
public void applyMatrixScale() {
if (gradientShader instanceof BitmapShader) {
int num = currentType == TYPE_PREVIEW ? 1 : 0;
Bitmap bitmap = motionBackground[num].getBitmap();
float scaleW = (bitmap.getWidth() / (float) motionBackground[num].getBounds().width());
float scaleH = (bitmap.getHeight() / (float) motionBackground[num].getBounds().height());
float scale = 1.0f / Math.min(scaleW, scaleH);
matrix.postScale(scale, scale);
}
}
public Shader getGradientShader() {
return gradientShader;
}
@ -203,38 +221,85 @@ public class Theme {
return Theme.currentColors.get(key);
}
public void setTop(int top, int backgroundHeight, boolean topNear, boolean bottomNear) {
public void setTop(int top, int backgroundWidth, int backgroundHeight, boolean topNear, boolean bottomNear) {
setTop(top, backgroundWidth, backgroundHeight, backgroundHeight, topNear, bottomNear);
}
public void setTop(int top, int backgroundWidth, int backgroundHeight, int heightOffset, boolean topNear, boolean bottomNear) {
int color;
Integer gradientColor;
Integer gradientColor1;
Integer gradientColor2;
Integer gradientColor3;
boolean animatedGradient;
if (isOut) {
color = getColor(isSelected ? key_chat_outBubbleSelected : key_chat_outBubble);
gradientColor = getCurrentColor(key_chat_outBubbleGradient);
gradientColor1 = getCurrentColor(key_chat_outBubbleGradient1);
gradientColor2 = getCurrentColor(key_chat_outBubbleGradient2);
gradientColor3 = getCurrentColor(key_chat_outBubbleGradient3);
Integer val = getCurrentColor(key_chat_outBubbleGradientAnimated);
animatedGradient = val != null && val != 0;
} else {
color = getColor(isSelected ? key_chat_inBubbleSelected : key_chat_inBubble);
gradientColor = null;
gradientColor1 = null;
gradientColor2 = null;
gradientColor3 = null;
animatedGradient = false;
}
if (gradientColor != null) {
if (gradientColor1 != null) {
color = getColor(key_chat_outBubble);
}
if (gradientColor == null) {
gradientColor = 0;
if (gradientColor1 == null) {
gradientColor1 = 0;
}
if (gradientColor != 0 && (gradientShader == null || backgroundHeight != currentBackgroundHeight || currentColor != color || currentGradientColor != gradientColor)) {
gradientShader = new LinearGradient(0, 0, 0, backgroundHeight, new int[]{gradientColor, color}, null, Shader.TileMode.CLAMP);
if (gradientColor2 == null) {
gradientColor2 = 0;
}
if (gradientColor3 == null) {
gradientColor3 = 0;
}
int num = currentType == TYPE_PREVIEW ? 1 : 0;
if (gradientColor1 != 0 && (gradientShader == null || backgroundHeight != currentBackgroundHeight || currentColor != color || currentGradientColor1 != gradientColor1 || currentGradientColor2 != gradientColor2 || currentGradientColor3 != gradientColor3 || currentAnimateGradient != animatedGradient)) {
if (gradientColor2 != 0 && animatedGradient) {
if (motionBackground[num] == null) {
motionBackground[num] = new MotionBackgroundDrawable();
if (currentType != TYPE_PREVIEW) {
motionBackground[num].setPostInvalidateParent(true);
}
motionBackground[num].setRoundRadius(AndroidUtilities.dp(1));
}
motionBackground[num].setColors(color, gradientColor1, gradientColor2, gradientColor3);
gradientShader = motionBackground[num].getBitmapShader();
} else {
if (gradientColor2 != 0) {
if (gradientColor3 != 0) {
gradientShader = new LinearGradient(0, 0, 0, backgroundHeight, new int[]{gradientColor3, gradientColor2, gradientColor1, color}, null, Shader.TileMode.CLAMP);
} else {
gradientShader = new LinearGradient(0, 0, 0, backgroundHeight, new int[]{gradientColor2, gradientColor1, color}, null, Shader.TileMode.CLAMP);
}
} else {
gradientShader = new LinearGradient(0, 0, 0, backgroundHeight, new int[]{gradientColor1, color}, null, Shader.TileMode.CLAMP);
}
}
paint.setShader(gradientShader);
currentColor = color;
currentGradientColor = gradientColor;
currentAnimateGradient = animatedGradient;
currentGradientColor1 = gradientColor1;
currentGradientColor2 = gradientColor2;
currentGradientColor3 = gradientColor3;
paint.setColor(0xffffffff);
} else if (gradientColor == 0) {
} else if (gradientColor1 == 0) {
if (gradientShader != null) {
gradientShader = null;
paint.setShader(null);
}
paint.setColor(color);
}
if (gradientShader instanceof BitmapShader) {
motionBackground[num].setBounds(0, 0, backgroundWidth, backgroundHeight - (gradientShader instanceof BitmapShader ? heightOffset : 0));
}
currentBackgroundHeight = backgroundHeight;
topY = top;
topY = top - (gradientShader instanceof BitmapShader ? heightOffset : 0);
isTopNear = topNear;
isBottomNear = bottomNear;
}
@ -358,6 +423,10 @@ public class Theme {
return transitionDrawable;
}
public MotionBackgroundDrawable getMotionBackgroundDrawable() {
return motionBackground[currentType == TYPE_PREVIEW ? 1 : 0];
}
public Drawable getShadowDrawable() {
if (gradientShader == null && !isSelected) {
return null;
@ -482,6 +551,7 @@ public class Theme {
if (paintToUse == null && gradientShader != null) {
matrix.reset();
applyMatrixScale();
matrix.postTranslate(0, -topY);
gradientShader.setLocalMatrix(matrix);
}
@ -489,7 +559,7 @@ public class Theme {
int top = Math.max(bounds.top, 0);
path.reset();
if (isOut) {
if (currentType == TYPE_PREVIEW || paintToUse != null || topY + bounds.bottom - rad < currentBackgroundHeight) {
if (drawFullBubble || currentType == TYPE_PREVIEW || paintToUse != null || topY + bounds.bottom - rad < currentBackgroundHeight) {
if (currentType == TYPE_MEDIA) {
path.moveTo(bounds.right - dp(8) - rad, bounds.bottom - padding);
} else {
@ -502,7 +572,7 @@ public class Theme {
path.moveTo(bounds.right - dp(8), top - topY + currentBackgroundHeight);
path.lineTo(bounds.left + padding, top - topY + currentBackgroundHeight);
}
if (currentType == TYPE_PREVIEW || paintToUse != null || topY + rad * 2 >= 0) {
if (drawFullBubble || currentType == TYPE_PREVIEW || paintToUse != null || topY + rad * 2 >= 0) {
path.lineTo(bounds.left + padding, bounds.top + padding + rad);
rect.set(bounds.left + padding, bounds.top + padding, bounds.left + padding + rad * 2, bounds.top + padding + rad * 2);
path.arcTo(rect, 180, 90, false);
@ -535,7 +605,7 @@ public class Theme {
path.lineTo(bounds.right - padding, top - topY + currentBackgroundHeight);
}
} else {
if (currentType == TYPE_PREVIEW || paintToUse != null || topY + bounds.bottom - smallRad * 2 < currentBackgroundHeight) {
if (drawFullBubble || currentType == TYPE_PREVIEW || paintToUse != null || topY + bounds.bottom - smallRad * 2 < currentBackgroundHeight) {
path.lineTo(bounds.right - dp(8), bounds.bottom - padding - smallRad - dp(3));
rect.set(bounds.right - dp(8), bounds.bottom - padding - smallRad * 2 - dp(9), bounds.right - dp(7) + smallRad * 2, bounds.bottom - padding - dp(1));
path.arcTo(rect, 180, -83, false);
@ -544,7 +614,7 @@ public class Theme {
}
}
} else {
if (currentType == TYPE_PREVIEW || paintToUse != null || topY + bounds.bottom - rad < currentBackgroundHeight) {
if (drawFullBubble || currentType == TYPE_PREVIEW || paintToUse != null || topY + bounds.bottom - rad < currentBackgroundHeight) {
if (currentType == TYPE_MEDIA) {
path.moveTo(bounds.left + dp(8) + rad, bounds.bottom - padding);
} else {
@ -557,7 +627,7 @@ public class Theme {
path.moveTo(bounds.left + dp(8), top - topY + currentBackgroundHeight);
path.lineTo(bounds.right - padding, top - topY + currentBackgroundHeight);
}
if (currentType == TYPE_PREVIEW || paintToUse != null || topY + rad * 2 >= 0) {
if (drawFullBubble || currentType == TYPE_PREVIEW || paintToUse != null || topY + rad * 2 >= 0) {
path.lineTo(bounds.right - padding, bounds.top + padding + rad);
rect.set(bounds.right - padding - rad * 2, bounds.top + padding, bounds.right - padding, bounds.top + padding + rad * 2);
path.arcTo(rect, 0, -90, false);
@ -590,7 +660,7 @@ public class Theme {
path.lineTo(bounds.left + padding, top - topY + currentBackgroundHeight);
}
} else {
if (currentType == TYPE_PREVIEW || paintToUse != null || topY + bounds.bottom - smallRad * 2 < currentBackgroundHeight) {
if (drawFullBubble || currentType == TYPE_PREVIEW || paintToUse != null || topY + bounds.bottom - smallRad * 2 < currentBackgroundHeight) {
path.lineTo(bounds.left + dp(8), bounds.bottom - padding - smallRad - dp(3));
rect.set(bounds.left + dp(7) - smallRad * 2, bounds.bottom - padding - smallRad * 2 - dp(9), bounds.left + dp(8), bounds.bottom - padding - dp(1));
path.arcTo(rect, 0, 83, false);
@ -609,6 +679,10 @@ public class Theme {
}
}
public void setDrawFullBubble(boolean drawFullBuble) {
this.drawFullBubble = drawFullBuble;
}
@Override
public void setAlpha(int alpha) {
if (this.alpha != alpha) {
@ -949,7 +1023,10 @@ public class Theme {
public int accentColor;
public int myMessagesAccentColor;
public int myMessagesGradientAccentColor;
public int myMessagesGradientAccentColor1;
public int myMessagesGradientAccentColor2;
public int myMessagesGradientAccentColor3;
public boolean myMessagesAnimated;
public long backgroundOverrideColor;
public long backgroundGradientOverrideColor1;
public long backgroundGradientOverrideColor2;
@ -1005,7 +1082,7 @@ public class Theme {
}
}
int myMessagesAccent = myMessagesAccentColor;
if ((myMessagesAccentColor != 0 || accentColor != 0) && myMessagesGradientAccentColor != 0) {
if ((myMessagesAccentColor != 0 || accentColor != 0) && myMessagesGradientAccentColor1 != 0) {
int firstColor = myMessagesAccentColor != 0 ? myMessagesAccentColor : accentColor;
Integer color = currentColorsNoAccent.get(key_chat_outBubble);
if (color == null) {
@ -1013,7 +1090,7 @@ public class Theme {
}
int newColor = changeColorAccent(hsvTemp1, hsvTemp2, color, isDarkTheme);
int distance1 = AndroidUtilities.getColorDistance(firstColor, newColor);
int distance2 = AndroidUtilities.getColorDistance(firstColor, myMessagesGradientAccentColor);
int distance2 = AndroidUtilities.getColorDistance(firstColor, myMessagesGradientAccentColor1);
isMyMessagesGradientColorsNear = distance1 <= 35000 && distance2 <= 35000;
myMessagesAccent = getAccentColor(hsvTemp1, color, firstColor);
}
@ -1041,11 +1118,22 @@ public class Theme {
}
}
if (!isMyMessagesGradientColorsNear) {
if (myMessagesGradientAccentColor != 0) {
if (myMessagesGradientAccentColor1 != 0) {
int textColor;
int subTextColor;
int seekbarColor;
if (useBlackText(myMessagesAccentColor, myMessagesGradientAccentColor)) {
boolean useBlackText;
if (myMessagesGradientAccentColor2 != 0) {
int color = AndroidUtilities.getAverageColor(myMessagesAccentColor, myMessagesGradientAccentColor1);
color = AndroidUtilities.getAverageColor(color, myMessagesGradientAccentColor2);
if (myMessagesGradientAccentColor3 != 0) {
color = AndroidUtilities.getAverageColor(color, myMessagesGradientAccentColor3);
}
useBlackText = AndroidUtilities.computePerceivedBrightness(color) > 0.705f;
} else {
useBlackText = useBlackText(myMessagesAccentColor, myMessagesGradientAccentColor1);
}
if (useBlackText) {
textColor = 0xff212121;
subTextColor = 0xff555555;
seekbarColor = 0x4d000000;
@ -1135,9 +1223,16 @@ public class Theme {
isMyMessagesGradientColorsNear = false;
}
}
if (myMessagesAccentColor != 0 && myMessagesGradientAccentColor != 0) {
if (myMessagesAccentColor != 0 && myMessagesGradientAccentColor1 != 0) {
currentColors.put(key_chat_outBubble, myMessagesAccentColor);
currentColors.put(key_chat_outBubbleGradient, myMessagesGradientAccentColor);
currentColors.put(key_chat_outBubbleGradient1, myMessagesGradientAccentColor1);
if (myMessagesGradientAccentColor2 != 0) {
currentColors.put(key_chat_outBubbleGradient2, myMessagesGradientAccentColor2);
if (myMessagesGradientAccentColor3 != 0) {
currentColors.put(key_chat_outBubbleGradient3, myMessagesGradientAccentColor3);
}
}
currentColors.put(key_chat_outBubbleGradientAnimated, myMessagesAnimated ? 1 : 0);
}
int backgroundOverride = (int) backgroundOverrideColor;
if (backgroundOverride != 0) {
@ -1613,7 +1708,11 @@ public class Theme {
if (defaultAccent == null || accent == null) {
return false;
}
return defaultAccent.myMessagesAccentColor == accent.myMessagesAccentColor && defaultAccent.myMessagesGradientAccentColor == accent.myMessagesGradientAccentColor;
return defaultAccent.myMessagesAccentColor == accent.myMessagesAccentColor &&
defaultAccent.myMessagesGradientAccentColor1 == accent.myMessagesGradientAccentColor1 &&
defaultAccent.myMessagesGradientAccentColor2 == accent.myMessagesGradientAccentColor2 &&
defaultAccent.myMessagesGradientAccentColor3 == accent.myMessagesGradientAccentColor3 &&
defaultAccent.myMessagesAnimated == accent.myMessagesAnimated;
}
private boolean isDefaultMainAccent() {
@ -1708,7 +1807,7 @@ public class Theme {
themeAccent.myMessagesAccentColor = myMessages[a];
}
if (myMessagesGradient != null) {
themeAccent.myMessagesGradientAccentColor = myMessagesGradient[a];
themeAccent.myMessagesGradientAccentColor1 = myMessagesGradient[a];
}
if (background != null) {
themeAccent.backgroundOverrideColor = background[a];
@ -1781,10 +1880,13 @@ public class Theme {
}
public static boolean accentEquals(ThemeAccent accent, TLRPC.TL_themeSettings settings) {
int myMessagesGradientAccentColor = settings.message_top_color;
if (settings.message_bottom_color == myMessagesGradientAccentColor) {
myMessagesGradientAccentColor = 0;
int bottomColor = settings.message_colors.size() > 0 ? settings.message_colors.get(0) | 0xff000000 : 0;
int myMessagesGradientAccentColor1 = settings.message_colors.size() > 1 ? settings.message_colors.get(1) | 0xff000000 : 0;
if (bottomColor == myMessagesGradientAccentColor1) {
myMessagesGradientAccentColor1 = 0;
}
int myMessagesGradientAccentColor2 = settings.message_colors.size() > 2 ? settings.message_colors.get(2) | 0xff000000 : 0;
int myMessagesGradientAccentColor3 = settings.message_colors.size() > 3 ? settings.message_colors.get(3) | 0xff000000 : 0;
int backgroundOverrideColor = 0;
long backgroundGradientOverrideColor1 = 0;
long backgroundGradientOverrideColor2 = 0;
@ -1816,8 +1918,11 @@ public class Theme {
}
}
return settings.accent_color == accent.accentColor &&
settings.message_bottom_color == accent.myMessagesAccentColor &&
myMessagesGradientAccentColor == accent.myMessagesGradientAccentColor &&
bottomColor == accent.myMessagesAccentColor &&
myMessagesGradientAccentColor1 == accent.myMessagesGradientAccentColor1 &&
myMessagesGradientAccentColor2 == accent.myMessagesGradientAccentColor2 &&
myMessagesGradientAccentColor3 == accent.myMessagesGradientAccentColor3 &&
settings.message_colors_animated == accent.myMessagesAnimated &&
backgroundOverrideColor == accent.backgroundOverrideColor &&
backgroundGradientOverrideColor1 == accent.backgroundGradientOverrideColor1 &&
backgroundGradientOverrideColor2 == accent.backgroundGradientOverrideColor2 &&
@ -1829,11 +1934,14 @@ public class Theme {
public static void fillAccentValues(ThemeAccent themeAccent, TLRPC.TL_themeSettings settings) {
themeAccent.accentColor = settings.accent_color;
themeAccent.myMessagesAccentColor = settings.message_bottom_color;
themeAccent.myMessagesGradientAccentColor = settings.message_top_color;
if (themeAccent.myMessagesAccentColor == themeAccent.myMessagesGradientAccentColor) {
themeAccent.myMessagesGradientAccentColor = 0;
themeAccent.myMessagesAccentColor = settings.message_colors.size() > 0 ? settings.message_colors.get(0) | 0xff000000 : 0;
themeAccent.myMessagesGradientAccentColor1 = settings.message_colors.size() > 1 ? settings.message_colors.get(1) | 0xff000000 : 0;
if (themeAccent.myMessagesAccentColor == themeAccent.myMessagesGradientAccentColor1) {
themeAccent.myMessagesGradientAccentColor1 = 0;
}
themeAccent.myMessagesGradientAccentColor2 = settings.message_colors.size() > 2 ? settings.message_colors.get(2) | 0xff000000 : 0;
themeAccent.myMessagesGradientAccentColor3 = settings.message_colors.size() > 3 ? settings.message_colors.get(3) | 0xff000000 : 0;
themeAccent.myMessagesAnimated = settings.message_colors_animated;
if (settings.wallpaper != null && settings.wallpaper.settings != null) {
themeAccent.backgroundOverrideColor = getWallpaperColor(settings.wallpaper.settings.background_color);
if ((settings.wallpaper.settings.flags & 16) != 0 && settings.wallpaper.settings.second_background_color == 0) {
@ -1896,7 +2004,10 @@ public class Theme {
ThemeAccent themeAccent = new ThemeAccent();
themeAccent.accentColor = accent.accentColor;
themeAccent.myMessagesAccentColor = accent.myMessagesAccentColor;
themeAccent.myMessagesGradientAccentColor = accent.myMessagesGradientAccentColor;
themeAccent.myMessagesGradientAccentColor1 = accent.myMessagesGradientAccentColor1;
themeAccent.myMessagesGradientAccentColor2 = accent.myMessagesGradientAccentColor2;
themeAccent.myMessagesGradientAccentColor3 = accent.myMessagesGradientAccentColor3;
themeAccent.myMessagesAnimated = accent.myMessagesAnimated;
themeAccent.backgroundOverrideColor = accent.backgroundOverrideColor;
themeAccent.backgroundGradientOverrideColor1 = accent.backgroundGradientOverrideColor1;
themeAccent.backgroundGradientOverrideColor2 = accent.backgroundGradientOverrideColor2;
@ -2256,6 +2367,7 @@ public class Theme {
public static Drawable chat_msgNoSoundDrawable;
public static Drawable chat_composeShadowDrawable;
public static Drawable chat_composeShadowRoundDrawable;
public static Drawable chat_roundVideoShadow;
public static MessageDrawable chat_msgInDrawable;
public static MessageDrawable chat_msgInSelectedDrawable;
@ -2265,7 +2377,7 @@ public class Theme {
public static MessageDrawable chat_msgInMediaSelectedDrawable;
public static MessageDrawable chat_msgOutMediaDrawable;
public static MessageDrawable chat_msgOutMediaSelectedDrawable;
private static StatusDrawable[] chat_status_drawables = new StatusDrawable[5];
private static StatusDrawable[] chat_status_drawables = new StatusDrawable[6];
public static PathAnimator playPauseAnimator;
public static Drawable chat_msgOutCheckDrawable;
@ -2670,7 +2782,10 @@ public class Theme {
public static final String key_chat_inBubbleSelected = "chat_inBubbleSelected";
public static final String key_chat_inBubbleShadow = "chat_inBubbleShadow";
public static final String key_chat_outBubble = "chat_outBubble";
public static final String key_chat_outBubbleGradient = "chat_outBubbleGradient";
public static final String key_chat_outBubbleGradient1 = "chat_outBubbleGradient";
public static final String key_chat_outBubbleGradient2 = "chat_outBubbleGradient2";
public static final String key_chat_outBubbleGradient3 = "chat_outBubbleGradient3";
public static final String key_chat_outBubbleGradientAnimated = "chat_outBubbleGradientAnimated";
public static final String key_chat_outBubbleGradientSelectedOverlay = "chat_outBubbleGradientSelectedOverlay";
public static final String key_chat_outBubbleSelected = "chat_outBubbleSelected";
public static final String key_chat_outBubbleShadow = "chat_outBubbleShadow";
@ -4162,7 +4277,7 @@ public class Theme {
myMessagesColorKeys.add(key_chat_outBubble);
myMessagesColorKeys.add(key_chat_outBubbleSelected);
myMessagesColorKeys.add(key_chat_outBubbleShadow);
myMessagesColorKeys.add(key_chat_outBubbleGradient);
myMessagesColorKeys.add(key_chat_outBubbleGradient1);
myMessagesColorKeys.add(key_chat_outSentCheck);
myMessagesColorKeys.add(key_chat_outSentCheckSelected);
myMessagesColorKeys.add(key_chat_outSentCheckRead);
@ -4456,7 +4571,14 @@ public class Theme {
accent.accentColor = data.readInt32(true);
accent.parentTheme = info;
accent.myMessagesAccentColor = data.readInt32(true);
accent.myMessagesGradientAccentColor = data.readInt32(true);
accent.myMessagesGradientAccentColor1 = data.readInt32(true);
if (version >= 7) {
accent.myMessagesGradientAccentColor2 = data.readInt32(true);
accent.myMessagesGradientAccentColor3 = data.readInt32(true);
}
if (version >= 8) {
accent.myMessagesAnimated = data.readBool(true);
}
if (version >= 3) {
accent.backgroundOverrideColor = data.readInt64(true);
} else {
@ -4527,13 +4649,16 @@ public class Theme {
info.lastAccentId = 101;
SerializedData data = new SerializedData(4 * (15 + 2));
data.writeInt32(6);
data.writeInt32(8);
data.writeInt32(1);
data.writeInt32(accent.id);
data.writeInt32(accent.accentColor);
data.writeInt32(accent.myMessagesAccentColor);
data.writeInt32(accent.myMessagesGradientAccentColor);
data.writeInt32(accent.myMessagesGradientAccentColor1);
data.writeInt32(accent.myMessagesGradientAccentColor2);
data.writeInt32(accent.myMessagesGradientAccentColor3);
data.writeBool(accent.myMessagesAnimated);
data.writeInt64(accent.backgroundOverrideColor);
data.writeInt64(accent.backgroundGradientOverrideColor1);
data.writeInt64(accent.backgroundGradientOverrideColor2);
@ -5644,10 +5769,10 @@ public class Theme {
}
public static void refreshThemeColors() {
refreshThemeColors(false);
refreshThemeColors(false, false);
}
public static void refreshThemeColors(boolean bg) {
public static void refreshThemeColors(boolean bg, boolean messages) {
currentColors.clear();
currentColors.putAll(currentColorsNoAccent);
shouldDrawGradientIcons = true;
@ -5655,7 +5780,9 @@ public class Theme {
if (accent != null) {
shouldDrawGradientIcons = accent.fillAccentColors(currentColorsNoAccent, currentColors);
}
reloadWallpaper();
if (!messages) {
reloadWallpaper();
}
applyCommonTheme();
applyDialogsTheme();
applyProfileTheme();
@ -5814,7 +5941,7 @@ public class Theme {
int N = theme.themeAccents.size();
int count = Math.max(0, N - theme.defaultAccentCount);
SerializedData data = new SerializedData(4 * (count * 15 + 2));
data.writeInt32(6);
data.writeInt32(8);
data.writeInt32(count);
for (int a = 0; a < N; a++) {
ThemeAccent accent = theme.themeAccents.get(a);
@ -5824,7 +5951,10 @@ public class Theme {
data.writeInt32(accent.id);
data.writeInt32(accent.accentColor);
data.writeInt32(accent.myMessagesAccentColor);
data.writeInt32(accent.myMessagesGradientAccentColor);
data.writeInt32(accent.myMessagesGradientAccentColor1);
data.writeInt32(accent.myMessagesGradientAccentColor2);
data.writeInt32(accent.myMessagesGradientAccentColor3);
data.writeBool(accent.myMessagesAnimated);
data.writeInt64(accent.backgroundOverrideColor);
data.writeInt64(accent.backgroundGradientOverrideColor1);
data.writeInt64(accent.backgroundGradientOverrideColor2);
@ -5969,6 +6099,10 @@ public class Theme {
return currentTheme == currentNightTheme;
}
public static boolean isCurrentThemeDark() {
return currentTheme.isDark();
}
public static ThemeInfo getActiveTheme() {
return currentTheme;
}
@ -6290,10 +6424,19 @@ public class Theme {
StringBuilder result = new StringBuilder();
if (colorsMap != defaultColors) {
int outBubbleColor = accent != null ? accent.myMessagesAccentColor : 0;
int outBubbleGradient = accent != null ? accent.myMessagesGradientAccentColor : 0;
if (outBubbleColor != 0 && outBubbleGradient != 0) {
int outBubbleGradient1 = accent != null ? accent.myMessagesGradientAccentColor1 : 0;
int outBubbleGradient2 = accent != null ? accent.myMessagesGradientAccentColor2 : 0;
int outBubbleGradient3 = accent != null ? accent.myMessagesGradientAccentColor3 : 0;
if (outBubbleColor != 0 && outBubbleGradient1 != 0) {
colorsMap.put(key_chat_outBubble, outBubbleColor);
colorsMap.put(key_chat_outBubbleGradient, outBubbleGradient);
colorsMap.put(key_chat_outBubbleGradient1, outBubbleGradient1);
if (outBubbleGradient2 != 0) {
colorsMap.put(key_chat_outBubbleGradient2, outBubbleGradient2);
if (outBubbleGradient3 != 0) {
colorsMap.put(key_chat_outBubbleGradient3, outBubbleGradient3);
}
}
colorsMap.put(key_chat_outBubbleGradientAnimated, accent != null && accent.myMessagesAnimated ? 1 : 0);
}
}
for (HashMap.Entry<String, Integer> entry : colorsMap.entrySet()) {
@ -6737,7 +6880,7 @@ public class Theme {
int messageFieldIconColor = getPreviewColor(colors, key_chat_messagePanelIcons);
int messageInColor = getPreviewColor(colors, key_chat_inBubble);
int messageOutColor = getPreviewColor(colors, key_chat_outBubble);
Integer messageOutGradientColor = colors.get(key_chat_outBubbleGradient);
Integer messageOutGradientColor = colors.get(key_chat_outBubbleGradient1);
Integer backgroundColor = colors.get(key_chat_wallpaper);
Integer gradientToColor1 = colors.get(key_chat_wallpaper_gradient_to1);
Integer gradientToColor2 = colors.get(key_chat_wallpaper_gradient_to2);
@ -6960,15 +7103,15 @@ public class Theme {
otherDrawable.draw(canvas);
}
msgDrawable[1].setBounds(161, 216, bitmap.getWidth() - 20, 216 + 92);
msgDrawable[1].setTop(0, 522, false, false);
msgDrawable[1].setTop(0, 560, 522, false, false);
msgDrawable[1].draw(canvas);
msgDrawable[1].setBounds(161, 430, bitmap.getWidth() - 20, 430 + 92);
msgDrawable[1].setTop(430, 522, false, false);
msgDrawable[1].setTop(430, 560, 522, false, false);
msgDrawable[1].draw(canvas);
msgDrawable[0].setBounds(20, 323, 399, 323 + 92);
msgDrawable[0].setTop(323, 522, false, false);
msgDrawable[0].setTop(323, 560, 522, false, false);
msgDrawable[0].draw(canvas);
paint.setColor(messageFieldColor);
@ -7695,7 +7838,8 @@ public class Theme {
chat_locationDrawable[0] = resources.getDrawable(R.drawable.msg_location).mutate();
chat_locationDrawable[1] = resources.getDrawable(R.drawable.msg_location).mutate();
chat_composeShadowDrawable = context.getResources().getDrawable(R.drawable.compose_panel_shadow);
chat_composeShadowDrawable = context.getResources().getDrawable(R.drawable.compose_panel_shadow).mutate();
chat_composeShadowRoundDrawable = context.getResources().getDrawable(R.drawable.sheet_shadow_round).mutate();
try {
int bitmapSize = AndroidUtilities.roundMessageSize + AndroidUtilities.dp(6);
@ -7750,7 +7894,6 @@ public class Theme {
chat_instantViewPaint.setTextSize(AndroidUtilities.dp(13));
chat_instantViewRectPaint.setStrokeWidth(AndroidUtilities.dp(1));
chat_pollTimerPaint.setStrokeWidth(AndroidUtilities.dp(1.1f));
chat_statusRecordPaint.setStrokeWidth(AndroidUtilities.dp(2));
chat_actionTextPaint.setTextSize(AndroidUtilities.dp(Math.max(16, SharedConfig.fontSize) - 2));
chat_contextResult_titleTextPaint.setTextSize(AndroidUtilities.dp(15));
chat_contextResult_descriptionTextPaint.setTextSize(AndroidUtilities.dp(13));
@ -7961,6 +8104,7 @@ public class Theme {
setDrawableColor(chat_psaHelpDrawable[1], getColor(key_chat_outViews));
setDrawableColorByKey(chat_composeShadowDrawable, key_chat_messagePanelShadow);
setDrawableColorByKey(chat_composeShadowRoundDrawable, key_chat_messagePanelBackground);
int color = getColor(key_chat_outAudioSeekbarFill);
if (color == 0xffffffff) {
@ -8993,7 +9137,7 @@ public class Theme {
}
public static StatusDrawable getChatStatusDrawable(int type) {
if (type < 0 || type > 4) {
if (type < 0 || type > 5) {
return null;
}
StatusDrawable statusDrawable = chat_status_drawables[type];
@ -9016,6 +9160,9 @@ public class Theme {
case 4:
chat_status_drawables[4] = new RoundStatusDrawable(true);
break;
case 5:
chat_status_drawables[5] = new ChoosingStickerStatusDrawable(true);
break;
}
statusDrawable = chat_status_drawables[type];
statusDrawable.start();

View file

@ -144,6 +144,10 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter {
notifyDataSetChanged();
}
public int getDialogsType() {
return dialogsType;
}
@Override
public int getItemCount() {
MessagesController messagesController = MessagesController.getInstance(currentAccount);

View file

@ -106,10 +106,10 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter {
public CharSequence name;
}
protected static class RecentSearchObject {
TLObject object;
int date;
long did;
public static class RecentSearchObject {
public TLObject object;
public int date;
public long did;
}
public interface DialogsSearchAdapterDelegate {
@ -121,7 +121,17 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter {
boolean isSelected(long dialogId);
}
private class CategoryAdapterRecycler extends RecyclerListView.SelectionAdapter {
public static class CategoryAdapterRecycler extends RecyclerListView.SelectionAdapter {
private final Context mContext;
private final int currentAccount;
private boolean drawChecked;
public CategoryAdapterRecycler(Context context, int account, boolean drawChecked) {
this.drawChecked = drawChecked;
mContext = context;
currentAccount = account;
}
public void setIndex(int value) {
notifyDataSetChanged();
@ -129,7 +139,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = new HintDialogCell(mContext);
View view = new HintDialogCell(mContext, drawChecked);
view.setLayoutParams(new RecyclerView.LayoutParams(AndroidUtilities.dp(80), AndroidUtilities.dp(86)));
return new RecyclerListView.Holder(view);
}
@ -371,6 +381,12 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter {
}
public void loadRecentSearch() {
loadRecentSearch(currentAccount, dialogsType, (arrayList, hashMap) -> {
DialogsSearchAdapter.this.setRecentSearch(arrayList, hashMap);
});
}
public static void loadRecentSearch(int currentAccount, int dialogsType, OnRecentSearchLoaded callback) {
MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> {
try {
SQLiteCursor cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized("SELECT did, date FROM search_recent WHERE 1");
@ -465,7 +481,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter {
return 0;
}
});
AndroidUtilities.runOnUIThread(() -> setRecentSearch(arrayList, hashMap));
AndroidUtilities.runOnUIThread(() -> callback.setRecentSearch(arrayList, hashMap));
} catch (Exception e) {
FileLog.e(e);
}
@ -934,7 +950,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter {
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
horizontalListView.setLayoutManager(layoutManager);
//horizontalListView.setDisallowInterceptTouchEvents(true);
horizontalListView.setAdapter(new CategoryAdapterRecycler());
horizontalListView.setAdapter(new CategoryAdapterRecycler(mContext, currentAccount, false));
horizontalListView.setOnItemClickListener((view1, position) -> {
if (delegate != null) {
delegate.didPressedOnSubDialog((Integer) view1.getTag());
@ -1223,4 +1239,8 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter {
public int getCurrentItemCount() {
return currentItemCount;
}
public interface OnRecentSearchLoaded {
void setRecentSearch(ArrayList<RecentSearchObject> arrayList, LongSparseArray<RecentSearchObject> hashMap);
}
}

View file

@ -24,13 +24,18 @@ import android.widget.TextView;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ChatObject;
import org.telegram.messenger.ContactsController;
import org.telegram.messenger.Emoji;
import org.telegram.messenger.FileLoader;
import org.telegram.messenger.ImageLocation;
import org.telegram.messenger.MediaDataController;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MessageObject;
import org.telegram.messenger.MessagesController;
import org.telegram.messenger.MessagesStorage;
import org.telegram.messenger.NotificationCenter;
import org.telegram.messenger.R;
import org.telegram.messenger.SendMessagesHelper;
import org.telegram.messenger.SharedConfig;
import org.telegram.messenger.UserConfig;
import org.telegram.messenger.UserObject;
import org.telegram.tgnet.ConnectionsManager;
@ -42,9 +47,11 @@ import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Cells.BotSwitchCell;
import org.telegram.ui.Cells.ContextLinkCell;
import org.telegram.ui.Cells.MentionCell;
import org.telegram.ui.Cells.StickerCell;
import org.telegram.ui.ChatActivity;
import org.telegram.ui.Components.RecyclerListView;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -53,7 +60,7 @@ import java.util.HashMap;
import androidx.recyclerview.widget.RecyclerView;
public class MentionsAdapter extends RecyclerListView.SelectionAdapter {
public class MentionsAdapter extends RecyclerListView.SelectionAdapter implements NotificationCenter.NotificationCenterDelegate {
public interface MentionsAdapterDelegate {
void needChangePanelVisibility(boolean show);
@ -95,6 +102,8 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter {
private int channelReqId;
private boolean isSearchingMentions;
private boolean visibleByStickersSearch;
private final static String punctuationsChars = " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~\n";
private Runnable cancelDelayRunnable;
@ -110,8 +119,25 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter {
private Runnable contextQueryRunnable;
private Location lastKnownLocation;
private ArrayList<StickerResult> stickers;
private HashMap<String, TLRPC.Document> stickersMap;
private ArrayList<String> stickersToLoad = new ArrayList<>();
private String lastSticker;
private int lastReqId;
private boolean delayLocalResults;
private ChatActivity parentFragment;
private static class StickerResult {
public TLRPC.Document sticker;
public Object parent;
public StickerResult(TLRPC.Document s, Object p) {
sticker = s;
parent = p;
}
}
private SendMessagesHelper.LocationProvider locationProvider = new SendMessagesHelper.LocationProvider(new SendMessagesHelper.LocationProvider.LocationProviderDelegate() {
@Override
public void onLocationAcquired(Location location) {
@ -152,6 +178,134 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter {
}
}
});
if (!darkTheme) {
NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.fileLoaded);
NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.fileLoadFailed);
}
}
@Override
public void didReceivedNotification(int id, int account, final Object... args) {
if (id == NotificationCenter.fileLoaded || id == NotificationCenter.fileLoadFailed) {
if (stickers != null && !stickers.isEmpty() && !stickersToLoad.isEmpty() && visibleByStickersSearch) {
String fileName = (String) args[0];
stickersToLoad.remove(fileName);
if (stickersToLoad.isEmpty()) {
delegate.needChangePanelVisibility(stickers != null && !stickers.isEmpty());
}
}
}
}
private void addStickerToResult(TLRPC.Document document, Object parent) {
if (document == null) {
return;
}
String key = document.dc_id + "_" + document.id;
if (stickersMap != null && stickersMap.containsKey(key)) {
return;
}
if (stickers == null) {
stickers = new ArrayList<>();
stickersMap = new HashMap<>();
}
stickers.add(new StickerResult(document, parent));
stickersMap.put(key, document);
}
private void addStickersToResult(ArrayList<TLRPC.Document> documents, Object parent) {
if (documents == null || documents.isEmpty()) {
return;
}
for (int a = 0, size = documents.size(); a < size; a++) {
TLRPC.Document document = documents.get(a);
String key = document.dc_id + "_" + document.id;
if (stickersMap != null && stickersMap.containsKey(key)) {
continue;
}
if (stickers == null) {
stickers = new ArrayList<>();
stickersMap = new HashMap<>();
}
for (int b = 0, size2 = document.attributes.size(); b < size2; b++) {
TLRPC.DocumentAttribute attribute = document.attributes.get(b);
if (attribute instanceof TLRPC.TL_documentAttributeSticker) {
parent = attribute.stickerset;
break;
}
}
stickers.add(new StickerResult(document, parent));
stickersMap.put(key, document);
}
}
private boolean checkStickerFilesExistAndDownload() {
if (stickers == null) {
return false;
}
stickersToLoad.clear();
int size = Math.min(6, stickers.size());
for (int a = 0; a < size; a++) {
StickerResult result = stickers.get(a);
TLRPC.PhotoSize thumb = FileLoader.getClosestPhotoSizeWithSize(result.sticker.thumbs, 90);
if (thumb instanceof TLRPC.TL_photoSize || thumb instanceof TLRPC.TL_photoSizeProgressive) {
File f = FileLoader.getPathToAttach(thumb, "webp", true);
if (!f.exists()) {
stickersToLoad.add(FileLoader.getAttachFileName(thumb, "webp"));
FileLoader.getInstance(currentAccount).loadFile(ImageLocation.getForDocument(thumb, result.sticker), result.parent, "webp", 1, 1);
}
}
}
return stickersToLoad.isEmpty();
}
private boolean isValidSticker(TLRPC.Document document, String emoji) {
for (int b = 0, size2 = document.attributes.size(); b < size2; b++) {
TLRPC.DocumentAttribute attribute = document.attributes.get(b);
if (attribute instanceof TLRPC.TL_documentAttributeSticker) {
if (attribute.alt != null && attribute.alt.contains(emoji)) {
return true;
}
break;
}
}
return false;
}
private void searchServerStickers(final String emoji, final String originalEmoji) {
TLRPC.TL_messages_getStickers req = new TLRPC.TL_messages_getStickers();
req.emoticon = originalEmoji;
req.hash = 0;
lastReqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> {
lastReqId = 0;
if (!emoji.equals(lastSticker) || !(response instanceof TLRPC.TL_messages_stickers)) {
return;
}
delayLocalResults = false;
TLRPC.TL_messages_stickers res = (TLRPC.TL_messages_stickers) response;
int oldCount = stickers != null ? stickers.size() : 0;
addStickersToResult(res.stickers, "sticker_search_" + emoji);
int newCount = stickers != null ? stickers.size() : 0;
if (!visibleByStickersSearch && stickers != null && !stickers.isEmpty()) {
checkStickerFilesExistAndDownload();
delegate.needChangePanelVisibility(stickersToLoad.isEmpty());
visibleByStickersSearch = true;
}
if (oldCount != newCount) {
notifyDataSetChanged();
}
}));
}
private void clearStickers() {
lastSticker = null;
stickers = null;
stickersMap = null;
notifyDataSetChanged();
if (lastReqId != 0) {
ConnectionsManager.getInstance(currentAccount).cancelRequest(lastReqId, true);
lastReqId = 0;
}
}
public void onDestroy() {
@ -175,6 +329,10 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter {
searchingContextUsername = null;
searchingContextQuery = null;
noUserName = false;
if (!isDarkTheme) {
NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.fileLoaded);
NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.fileLoadFailed);
}
}
public void setParentFragment(ChatActivity fragment) {
@ -510,6 +668,7 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter {
cancelDelayRunnable = null;
}
searchResultHashtags = null;
stickers = null;
searchResultUsernames = null;
searchResultUsernamesMap = null;
searchResultCommands = null;
@ -568,6 +727,7 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter {
searchForContextBot(null, null);
delegate.needChangePanelVisibility(false);
lastText = null;
clearStickers();
return;
}
int searchPostion = position;
@ -579,7 +739,136 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter {
lastForSearch = forSearch;
StringBuilder result = new StringBuilder();
int foundType = -1;
if (!usernameOnly && needBotContext && text.charAt(0) == '@') {
boolean searchEmoji = !usernameOnly && text != null && text.length() > 0 && text.length() <= 14;
String originalEmoji = "";
if (searchEmoji) {
CharSequence emoji = originalEmoji = text;
int length = emoji.length();
for (int a = 0; a < length; a++) {
char ch = emoji.charAt(a);
char nch = a < length - 1 ? emoji.charAt(a + 1) : 0;
if (a < length - 1 && ch == 0xD83C && nch >= 0xDFFB && nch <= 0xDFFF) {
emoji = TextUtils.concat(emoji.subSequence(0, a), emoji.subSequence(a + 2, emoji.length()));
length -= 2;
a--;
} else if (ch == 0xfe0f) {
emoji = TextUtils.concat(emoji.subSequence(0, a), emoji.subSequence(a + 1, emoji.length()));
length--;
a--;
}
}
lastSticker = emoji.toString().trim();
}
boolean isValidEmoji = searchEmoji && (Emoji.isValidEmoji(originalEmoji) || Emoji.isValidEmoji(lastSticker));
if (isValidEmoji && parentFragment != null && (parentFragment.getCurrentChat() == null || ChatObject.canSendStickers(parentFragment.getCurrentChat()))) {
stickersToLoad.clear();
if (SharedConfig.suggestStickers == 2 || !isValidEmoji) {
if (visibleByStickersSearch && SharedConfig.suggestStickers == 2) {
visibleByStickersSearch = false;
delegate.needChangePanelVisibility(false);
notifyDataSetChanged();
}
return;
}
stickers = null;
stickersMap = null;
foundType = 4;
if (lastReqId != 0) {
ConnectionsManager.getInstance(currentAccount).cancelRequest(lastReqId, true);
lastReqId = 0;
}
boolean serverStickersOnly = MessagesController.getInstance(currentAccount).suggestStickersApiOnly;
delayLocalResults = false;
if (!serverStickersOnly) {
final ArrayList<TLRPC.Document> recentStickers = MediaDataController.getInstance(currentAccount).getRecentStickersNoCopy(MediaDataController.TYPE_IMAGE);
final ArrayList<TLRPC.Document> favsStickers = MediaDataController.getInstance(currentAccount).getRecentStickersNoCopy(MediaDataController.TYPE_FAVE);
int recentsAdded = 0;
for (int a = 0, size = Math.min(20, recentStickers.size()); a < size; a++) {
TLRPC.Document document = recentStickers.get(a);
if (isValidSticker(document, lastSticker)) {
addStickerToResult(document, "recent");
recentsAdded++;
if (recentsAdded >= 5) {
break;
}
}
}
for (int a = 0, size = favsStickers.size(); a < size; a++) {
TLRPC.Document document = favsStickers.get(a);
if (isValidSticker(document, lastSticker)) {
addStickerToResult(document, "fav");
}
}
HashMap<String, ArrayList<TLRPC.Document>> allStickers = MediaDataController.getInstance(currentAccount).getAllStickers();
ArrayList<TLRPC.Document> newStickers = allStickers != null ? allStickers.get(lastSticker) : null;
if (newStickers != null && !newStickers.isEmpty()) {
addStickersToResult(newStickers, null);
}
if (stickers != null) {
Collections.sort(stickers, new Comparator<StickerResult>() {
private int getIndex(StickerResult result) {
for (int a = 0; a < favsStickers.size(); a++) {
if (favsStickers.get(a).id == result.sticker.id) {
return a + 2000000;
}
}
for (int a = 0; a < Math.min(20, recentStickers.size()); a++) {
if (recentStickers.get(a).id == result.sticker.id) {
return recentStickers.size() - a + 1000000;
}
}
return -1;
}
@Override
public int compare(StickerResult lhs, StickerResult rhs) {
boolean isAnimated1 = MessageObject.isAnimatedStickerDocument(lhs.sticker, true);
boolean isAnimated2 = MessageObject.isAnimatedStickerDocument(rhs.sticker, true);
if (isAnimated1 == isAnimated2) {
int idx1 = getIndex(lhs);
int idx2 = getIndex(rhs);
if (idx1 > idx2) {
return -1;
} else if (idx1 < idx2) {
return 1;
}
return 0;
} else {
if (isAnimated1) {
return -1;
} else {
return 1;
}
}
}
});
}
}
if (SharedConfig.suggestStickers == 0 || serverStickersOnly) {
searchServerStickers(lastSticker, originalEmoji);
}
if (stickers != null && !stickers.isEmpty()) {
if (SharedConfig.suggestStickers == 0 && stickers.size() < 5) {
delayLocalResults = true;
delegate.needChangePanelVisibility(false);
visibleByStickersSearch = false;
} else {
checkStickerFilesExistAndDownload();
boolean show = stickersToLoad.isEmpty();
delegate.needChangePanelVisibility(show);
visibleByStickersSearch = true;
}
notifyDataSetChanged();
} else if (visibleByStickersSearch) {
delegate.needChangePanelVisibility(false);
visibleByStickersSearch = false;
}
} else if (!usernameOnly && needBotContext && text.charAt(0) == '@') {
int index = text.indexOf(' ');
int len = text.length();
String username = null;
@ -801,6 +1090,7 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter {
}
});
searchResultHashtags = null;
stickers = null;
searchResultCommands = null;
searchResultCommandsHelp = null;
searchResultCommandsUsers = null;
@ -887,6 +1177,7 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter {
}
}
searchResultHashtags = newResult;
stickers = null;
searchResultUsernames = null;
searchResultUsernamesMap = null;
searchResultCommands = null;
@ -912,6 +1203,7 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter {
}
}
searchResultHashtags = null;
stickers = null;
searchResultUsernames = null;
searchResultUsernamesMap = null;
searchResultSuggestions = null;
@ -929,6 +1221,7 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter {
MediaDataController.getInstance(currentAccount).getEmojiSuggestions(lastSearchKeyboardLanguage, result.toString(), false, (param, alias) -> {
searchResultSuggestions = param;
searchResultHashtags = null;
stickers = null;
searchResultUsernames = null;
searchResultUsernamesMap = null;
searchResultCommands = null;
@ -937,6 +1230,14 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter {
notifyDataSetChanged();
delegate.needChangePanelVisibility(searchResultSuggestions != null && !searchResultSuggestions.isEmpty());
});
} else if (foundType == 4) {
searchResultHashtags = null;
searchResultUsernames = null;
searchResultUsernamesMap = null;
searchResultSuggestions = null;
searchResultCommands = null;
searchResultCommandsHelp = null;
searchResultCommandsUsers = null;
}
}
@ -970,7 +1271,9 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter {
if (foundContextBot != null && !inlineMediaEnabled) {
return 1;
}
if (searchResultBotContext != null) {
if (stickers != null) {
return stickers.size();
}else if (searchResultBotContext != null) {
return searchResultBotContext.size() + (searchResultBotContextSwitch != null ? 1 : 0);
} else if (searchResultUsernames != null) {
return searchResultUsernames.size();
@ -986,7 +1289,9 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter {
@Override
public int getItemViewType(int position) {
if (foundContextBot != null && !inlineMediaEnabled) {
if (stickers != null) {
return 4;
} else if (foundContextBot != null && !inlineMediaEnabled) {
return 3;
} else if (searchResultBotContext != null) {
if (position == 0 && searchResultBotContextSwitch != null) {
@ -1009,8 +1314,14 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter {
return i;
}
public Object getItemParent(int i) {
return stickers != null && i >= 0 && i < stickers.size() ? stickers.get(i).parent : null;
}
public Object getItem(int i) {
if (searchResultBotContext != null) {
if (stickers != null) {
return i >= 0 && i < stickers.size() ? stickers.get(i).sticker : null;
} else if (searchResultBotContext != null) {
if (searchResultBotContextSwitch != null) {
if (i == 0) {
return searchResultBotContextSwitch;
@ -1061,6 +1372,10 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter {
return searchResultCommands != null;
}
public boolean isStickers() {
return stickers != null;
}
public boolean isBotContext() {
return searchResultBotContext != null;
}
@ -1070,12 +1385,12 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter {
}
public boolean isMediaLayout() {
return contextMedia;
return contextMedia || stickers != null;
}
@Override
public boolean isEnabled(RecyclerView.ViewHolder holder) {
return foundContextBot == null || inlineMediaEnabled;
return (foundContextBot == null || inlineMediaEnabled) && stickers == null;
}
@Override
@ -1094,20 +1409,29 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter {
view = new BotSwitchCell(mContext);
break;
case 3:
default:
TextView textView = new TextView(mContext);
textView.setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8));
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2));
view = textView;
break;
case 4:
default:
view = new StickerCell(mContext);
break;
}
return new RecyclerListView.Holder(view);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder.getItemViewType() == 3) {
int type = holder.getItemViewType();
if (type == 4) {
StickerCell stickerCell = (StickerCell) holder.itemView;
StickerResult result = stickers.get(position);
stickerCell.setSticker(result.sticker, result.parent);
stickerCell.setClearsInputField(true);
} else if (type == 3) {
TextView textView = (TextView) holder.itemView;
TLRPC.Chat chat = parentFragment.getCurrentChat();
if (chat != null) {

View file

@ -10,57 +10,36 @@ package org.telegram.ui.Adapters;
import android.content.Context;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.MediaDataController;
import org.telegram.messenger.Emoji;
import org.telegram.messenger.ImageLocation;
import org.telegram.messenger.MessageObject;
import org.telegram.messenger.MessagesController;
import org.telegram.messenger.NotificationCenter;
import org.telegram.messenger.SharedConfig;
import org.telegram.messenger.UserConfig;
import org.telegram.messenger.FileLoader;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.Cells.EmojiReplacementCell;
import org.telegram.ui.Cells.StickerCell;
import org.telegram.ui.Components.RecyclerListView;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import androidx.recyclerview.widget.RecyclerView;
public class StickersAdapter extends RecyclerListView.SelectionAdapter implements NotificationCenter.NotificationCenterDelegate {
private static class StickerResult {
public TLRPC.Document sticker;
public Object parent;
public StickerResult(TLRPC.Document s, Object p) {
sticker = s;
parent = p;
}
}
private int currentAccount = UserConfig.selectedAccount;
private Context mContext;
private ArrayList<MediaDataController.KeywordResult> keywordResults;
private ArrayList<StickerResult> stickers;
private HashMap<String, TLRPC.Document> stickersMap;
private ArrayList<String> stickersToLoad = new ArrayList<>();
private StickersAdapterDelegate delegate;
private String lastSticker;
private boolean visible;
private int lastReqId;
private boolean delayLocalResults;
private String lastSearch;
private String[] lastSearchKeyboardLanguage;
private Runnable searchRunnable;
@ -74,114 +53,23 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement
MediaDataController.getInstance(currentAccount).checkStickers(MediaDataController.TYPE_IMAGE);
MediaDataController.getInstance(currentAccount).checkStickers(MediaDataController.TYPE_MASK);
NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.newEmojiSuggestionsAvailable);
NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.fileLoaded);
NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.fileLoadFailed);
}
public void onDestroy() {
NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.newEmojiSuggestionsAvailable);
NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.fileLoaded);
NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.fileLoadFailed);
}
@Override
public void didReceivedNotification(int id, int account, final Object... args) {
if (id == NotificationCenter.fileLoaded || id == NotificationCenter.fileLoadFailed) {
if (stickers != null && !stickers.isEmpty() && !stickersToLoad.isEmpty() && visible) {
String fileName = (String) args[0];
stickersToLoad.remove(fileName);
if (stickersToLoad.isEmpty()) {
boolean show = stickers != null && !stickers.isEmpty();
if (show) {
keywordResults = null;
}
delegate.needChangePanelVisibility(show);
}
}
} else if (id == NotificationCenter.newEmojiSuggestionsAvailable) {
if ((keywordResults == null || keywordResults.isEmpty()) && !TextUtils.isEmpty(lastSticker) && getItemCount() == 0) {
if (id == NotificationCenter.newEmojiSuggestionsAvailable) {
if ((keywordResults == null || keywordResults.isEmpty()) && !TextUtils.isEmpty(lastSearch) && getItemCount() == 0) {
searchEmojiByKeyword();
}
}
}
private boolean checkStickerFilesExistAndDownload() {
if (stickers == null) {
return false;
}
stickersToLoad.clear();
int size = Math.min(6, stickers.size());
for (int a = 0; a < size; a++) {
StickerResult result = stickers.get(a);
TLRPC.PhotoSize thumb = FileLoader.getClosestPhotoSizeWithSize(result.sticker.thumbs, 90);
if (thumb instanceof TLRPC.TL_photoSize || thumb instanceof TLRPC.TL_photoSizeProgressive) {
File f = FileLoader.getPathToAttach(thumb, "webp", true);
if (!f.exists()) {
stickersToLoad.add(FileLoader.getAttachFileName(thumb, "webp"));
FileLoader.getInstance(currentAccount).loadFile(ImageLocation.getForDocument(thumb, result.sticker), result.parent, "webp", 1, 1);
}
}
}
return stickersToLoad.isEmpty();
}
private boolean isValidSticker(TLRPC.Document document, String emoji) {
for (int b = 0, size2 = document.attributes.size(); b < size2; b++) {
TLRPC.DocumentAttribute attribute = document.attributes.get(b);
if (attribute instanceof TLRPC.TL_documentAttributeSticker) {
if (attribute.alt != null && attribute.alt.contains(emoji)) {
return true;
}
break;
}
}
return false;
}
private void addStickerToResult(TLRPC.Document document, Object parent) {
if (document == null) {
return;
}
String key = document.dc_id + "_" + document.id;
if (stickersMap != null && stickersMap.containsKey(key)) {
return;
}
if (stickers == null) {
stickers = new ArrayList<>();
stickersMap = new HashMap<>();
}
stickers.add(new StickerResult(document, parent));
stickersMap.put(key, document);
}
private void addStickersToResult(ArrayList<TLRPC.Document> documents, Object parent) {
if (documents == null || documents.isEmpty()) {
return;
}
for (int a = 0, size = documents.size(); a < size; a++) {
TLRPC.Document document = documents.get(a);
String key = document.dc_id + "_" + document.id;
if (stickersMap != null && stickersMap.containsKey(key)) {
continue;
}
if (stickers == null) {
stickers = new ArrayList<>();
stickersMap = new HashMap<>();
}
for (int b = 0, size2 = document.attributes.size(); b < size2; b++) {
TLRPC.DocumentAttribute attribute = document.attributes.get(b);
if (attribute instanceof TLRPC.TL_documentAttributeSticker) {
parent = attribute.stickerset;
break;
}
}
stickers.add(new StickerResult(document, parent));
stickersMap.put(key, document);
}
}
public void hide() {
if (visible && (stickers != null || keywordResults != null && !keywordResults.isEmpty())) {
if (visible && keywordResults != null && !keywordResults.isEmpty()) {
visible = false;
delegate.needChangePanelVisibility(false);
}
@ -200,10 +88,10 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement
MediaDataController.getInstance(currentAccount).fetchNewEmojiKeywords(newLanguage);
}
lastSearchKeyboardLanguage = newLanguage;
String query = lastSticker;
String query = lastSearch;
cancelEmojiSearch();
searchRunnable = () -> MediaDataController.getInstance(currentAccount).getEmojiSuggestions(lastSearchKeyboardLanguage, query, true, (param, alias) -> {
if (query.equals(lastSticker)) {
if (query.equals(lastSearch)) {
if (!param.isEmpty()) {
keywordResults = param;
}
@ -218,7 +106,7 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement
}
}
public void loadStikersForEmoji(CharSequence emoji, boolean emojiOnly) {
public void searchEmojiByKeyword(CharSequence emoji) {
boolean searchEmoji = emoji != null && emoji.length() > 0 && emoji.length() <= 14;
String originalEmoji = "";
@ -239,9 +127,8 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement
}
}
}
lastSticker = emoji.toString().trim();
stickersToLoad.clear();
boolean isValidEmoji = searchEmoji && (Emoji.isValidEmoji(originalEmoji) || Emoji.isValidEmoji(lastSticker));
lastSearch = emoji.toString().trim();
boolean isValidEmoji = searchEmoji && (Emoji.isValidEmoji(originalEmoji) || Emoji.isValidEmoji(lastSearch));
if (isValidEmoji) {
TLRPC.Document animatedSticker = MediaDataController.getInstance(currentAccount).getEmojiAnimatedSticker(emoji);
if (animatedSticker != null) {
@ -252,166 +139,28 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement
}
}
}
if (emojiOnly || SharedConfig.suggestStickers == 2 || !isValidEmoji) {
if (visible && (emojiOnly || SharedConfig.suggestStickers == 2 || keywordResults == null || keywordResults.isEmpty())) {
visible = false;
delegate.needChangePanelVisibility(false);
notifyDataSetChanged();
}
if (!isValidEmoji) {
searchEmojiByKeyword();
}
return;
}
cancelEmojiSearch();
stickers = null;
stickersMap = null;
if (lastReqId != 0) {
ConnectionsManager.getInstance(currentAccount).cancelRequest(lastReqId, true);
lastReqId = 0;
}
boolean serverStickersOnly = MessagesController.getInstance(currentAccount).suggestStickersApiOnly;
delayLocalResults = false;
if (!serverStickersOnly) {
final ArrayList<TLRPC.Document> recentStickers = MediaDataController.getInstance(currentAccount).getRecentStickersNoCopy(MediaDataController.TYPE_IMAGE);
final ArrayList<TLRPC.Document> favsStickers = MediaDataController.getInstance(currentAccount).getRecentStickersNoCopy(MediaDataController.TYPE_FAVE);
int recentsAdded = 0;
for (int a = 0, size = Math.min(20, recentStickers.size()); a < size; a++) {
TLRPC.Document document = recentStickers.get(a);
if (isValidSticker(document, lastSticker)) {
addStickerToResult(document, "recent");
recentsAdded++;
if (recentsAdded >= 5) {
break;
}
}
}
for (int a = 0, size = favsStickers.size(); a < size; a++) {
TLRPC.Document document = favsStickers.get(a);
if (isValidSticker(document, lastSticker)) {
addStickerToResult(document, "fav");
}
}
HashMap<String, ArrayList<TLRPC.Document>> allStickers = MediaDataController.getInstance(currentAccount).getAllStickers();
ArrayList<TLRPC.Document> newStickers = allStickers != null ? allStickers.get(lastSticker) : null;
if (newStickers != null && !newStickers.isEmpty()) {
addStickersToResult(newStickers, null);
}
if (stickers != null) {
Collections.sort(stickers, new Comparator<StickerResult>() {
private int getIndex(StickerResult result) {
for (int a = 0; a < favsStickers.size(); a++) {
if (favsStickers.get(a).id == result.sticker.id) {
return a + 2000000;
}
}
for (int a = 0; a < Math.min(20, recentStickers.size()); a++) {
if (recentStickers.get(a).id == result.sticker.id) {
return recentStickers.size() - a + 1000000;
}
}
return -1;
}
@Override
public int compare(StickerResult lhs, StickerResult rhs) {
boolean isAnimated1 = MessageObject.isAnimatedStickerDocument(lhs.sticker, true);
boolean isAnimated2 = MessageObject.isAnimatedStickerDocument(rhs.sticker, true);
if (isAnimated1 == isAnimated2) {
int idx1 = getIndex(lhs);
int idx2 = getIndex(rhs);
if (idx1 > idx2) {
return -1;
} else if (idx1 < idx2) {
return 1;
}
return 0;
} else {
if (isAnimated1) {
return -1;
} else {
return 1;
}
}
}
});
}
}
if (SharedConfig.suggestStickers == 0 || serverStickersOnly) {
searchServerStickers(lastSticker, originalEmoji);
}
if (stickers != null && !stickers.isEmpty()) {
if (SharedConfig.suggestStickers == 0 && stickers.size() < 5) {
delayLocalResults = true;
delegate.needChangePanelVisibility(false);
visible = false;
} else {
checkStickerFilesExistAndDownload();
boolean show = stickersToLoad.isEmpty();
if (show) {
keywordResults = null;
}
delegate.needChangePanelVisibility(show);
visible = true;
}
notifyDataSetChanged();
} else if (visible) {
delegate.needChangePanelVisibility(false);
if (visible && (keywordResults == null || keywordResults.isEmpty())) {
visible = false;
delegate.needChangePanelVisibility(false);
notifyDataSetChanged();
}
if (!isValidEmoji) {
searchEmojiByKeyword();
} else {
clearSearch();
delegate.needChangePanelVisibility(false);
}
}
private void searchServerStickers(final String emoji, final String originalEmoji) {
TLRPC.TL_messages_getStickers req = new TLRPC.TL_messages_getStickers();
req.emoticon = originalEmoji;
req.hash = 0;
lastReqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> {
lastReqId = 0;
if (!emoji.equals(lastSticker) || !(response instanceof TLRPC.TL_messages_stickers)) {
return;
}
delayLocalResults = false;
TLRPC.TL_messages_stickers res = (TLRPC.TL_messages_stickers) response;
int oldCount = stickers != null ? stickers.size() : 0;
addStickersToResult(res.stickers, "sticker_search_" + emoji);
int newCount = stickers != null ? stickers.size() : 0;
if (!visible && stickers != null && !stickers.isEmpty()) {
checkStickerFilesExistAndDownload();
boolean show = stickersToLoad.isEmpty();
if (show) {
keywordResults = null;
}
delegate.needChangePanelVisibility(show);
visible = true;
}
if (oldCount != newCount) {
notifyDataSetChanged();
}
}));
}
public void clearStickers() {
if (delayLocalResults || lastReqId != 0) {
return;
}
if (stickersToLoad.isEmpty()) {
lastSticker = null;
stickers = null;
stickersMap = null;
}
public void clearSearch() {
lastSearch = null;
keywordResults = null;
notifyDataSetChanged();
if (lastReqId != 0) {
ConnectionsManager.getInstance(currentAccount).cancelRequest(lastReqId, true);
lastReqId = 0;
}
}
public String getQuery() {
return lastSticker;
return lastSearch;
}
public boolean isShowingKeywords() {
@ -423,21 +172,14 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement
if (keywordResults != null && !keywordResults.isEmpty()) {
return keywordResults.size();
}
return !delayLocalResults && stickers != null ? stickers.size() : 0;
return 0;
}
public Object getItem(int i) {
if (keywordResults != null && !keywordResults.isEmpty()) {
return i >= 0 && i < keywordResults.size() ? keywordResults.get(i).emoji : null;
}
return stickers != null && i >= 0 && i < stickers.size() ? stickers.get(i).sticker : null;
}
public Object getItemParent(int i) {
if (keywordResults != null && !keywordResults.isEmpty()) {
return null;
}
return stickers != null && i >= 0 && i < stickers.size() ? stickers.get(i).parent : null;
return null;
}
@Override
@ -447,61 +189,27 @@ public class StickersAdapter extends RecyclerListView.SelectionAdapter implement
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View view;
switch (viewType) {
case 0:
view = new StickerCell(mContext);
break;
case 1:
default:
view = new EmojiReplacementCell(mContext);
}
return new RecyclerListView.Holder(view);
return new RecyclerListView.Holder(new EmojiReplacementCell(mContext));
}
@Override
public int getItemViewType(int position) {
if (keywordResults != null && !keywordResults.isEmpty()) {
return 1;
}
return 0;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
switch (holder.getItemViewType()) {
case 0: {
int side = 0;
if (position == 0) {
if (stickers.size() == 1) {
side = 2;
} else {
side = -1;
}
} else if (position == stickers.size() - 1) {
side = 1;
}
StickerCell stickerCell = (StickerCell) holder.itemView;
StickerResult result = stickers.get(position);
stickerCell.setSticker(result.sticker, result.parent, side);
stickerCell.setClearsInputField(true);
break;
}
case 1: {
int side = 0;
if (position == 0) {
if (keywordResults.size() == 1) {
side = 2;
} else {
side = -1;
}
} else if (position == keywordResults.size() - 1) {
side = 1;
}
EmojiReplacementCell cell = (EmojiReplacementCell) holder.itemView;
cell.setEmoji(keywordResults.get(position).emoji, side);
break;
int side = 0;
if (position == 0) {
if (keywordResults.size() == 1) {
side = 2;
} else {
side = -1;
}
} else if (position == keywordResults.size() - 1) {
side = 1;
}
EmojiReplacementCell cell = (EmojiReplacementCell) holder.itemView;
cell.setEmoji(keywordResults.get(position).emoji, side);
}
}

View file

@ -626,6 +626,8 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
}
};
private boolean closeAnimationInProgress;
private class WindowView extends FrameLayout {
private final Paint blackPaint = new Paint();
@ -640,7 +642,6 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
private int startedTrackingX;
private int startedTrackingY;
private VelocityTracker tracker;
private boolean closeAnimationInProgress;
private float innerTranslationX;
private float alpha;
@ -4491,7 +4492,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg
}
public void close(boolean byBackPress, boolean force) {
if (parentActivity == null || !isVisible || checkAnimation()) {
if (parentActivity == null || closeAnimationInProgress || !isVisible || checkAnimation()) {
return;
}
if (fullscreenVideoContainer.getVisibility() == View.VISIBLE) {

View file

@ -556,7 +556,7 @@ public class CancelAccountDeletionActivity extends BaseFragment {
Intent mailer = new Intent(Intent.ACTION_SENDTO);
mailer.setData(Uri.parse("mailto:"));
mailer.putExtra(Intent.EXTRA_EMAIL, new String[]{"sms@stel.com"});
mailer.putExtra(Intent.EXTRA_EMAIL, new String[]{"reports@stel.com"});
mailer.putExtra(Intent.EXTRA_SUBJECT, "Android cancel account deletion issue " + version + " " + phone);
mailer.putExtra(Intent.EXTRA_TEXT, "Phone: " + phone + "\nApp version: " + version + "\nOS version: SDK " + Build.VERSION.SDK_INT + "\nDevice Name: " + Build.MANUFACTURER + Build.MODEL + "\nLocale: " + Locale.getDefault() + "\nError: " + lastError);
getContext().startActivity(Intent.createChooser(mailer, "Send email..."));

View file

@ -207,12 +207,14 @@ public class BotHelpCell extends View {
shadowDrawable.setBounds(x, y, width + x, height + y);
shadowDrawable.draw(canvas);
}
int w = AndroidUtilities.displaySize.x;
int h = AndroidUtilities.displaySize.y;
if (getParent() instanceof View) {
View view = (View) getParent();
w = view.getMeasuredWidth();
h = view.getMeasuredHeight();
}
Theme.chat_msgInMediaDrawable.setTop((int) getY(), h, false, false);
Theme.chat_msgInMediaDrawable.setTop((int) getY(), w, h, false, false);
Theme.chat_msgInMediaDrawable.setBounds(x, y, width + x, height + y);
Theme.chat_msgInMediaDrawable.draw(canvas);
Theme.chat_msgTextPaint.setColor(Theme.getColor(Theme.key_chat_messageTextIn));

View file

@ -68,6 +68,8 @@ import android.widget.Toast;
import androidx.core.graphics.ColorUtils;
import com.google.android.exoplayer2.util.Log;
import org.telegram.PhoneFormat.PhoneFormat;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ChatObject;
@ -348,6 +350,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
private boolean needNewVisiblePart;
private boolean fullyDraw;
private int parentWidth;
private int parentHeight;
public float parentViewTopOffset;
@ -1518,7 +1521,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
private boolean checkInstantButtonMotionEvent(MotionEvent event) {
if (!drawInstantView || currentMessageObject.type == 0) {
if (!currentMessageObject.isSponsored() && (!drawInstantView || currentMessageObject.type == 0)) {
return false;
}
int x = (int) event.getX();
@ -2555,7 +2558,18 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
fullyDraw = draw;
}
public void setVisiblePart(int position, int height, int parent, float parentOffset, float visibleTop, int parentH) {
public void setParentViewSize(int parentW, int parentH) {
parentWidth = parentW;
parentHeight = parentH;
backgroundHeight = parentH;
if (currentMessageObject != null && (Theme.hasGradientService() && currentMessageObject.shouldDrawWithoutBackground() || drawSideButton != 0 || !botButtons.isEmpty()) || currentBackgroundDrawable != null && currentBackgroundDrawable.getGradientShader() != null) {
invalidate();
}
}
public void setVisiblePart(int position, int height, int parent, float parentOffset, float visibleTop, int parentW, int parentH) {
parentWidth = parentW;
parentHeight = parentH;
backgroundHeight = parentH;
viewTop = visibleTop;
@ -3032,9 +3046,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (messageObject.checkLayout() || currentPosition != null && lastHeight != AndroidUtilities.displaySize.y) {
currentMessageObject = null;
}
boolean widthChanged = lastWidth != AndroidUtilities.displaySize.x;
boolean widthChanged = lastWidth != getParentWidth();
lastHeight = AndroidUtilities.displaySize.y;
lastWidth = AndroidUtilities.displaySize.x;
lastWidth = getParentWidth();
isRoundVideo = messageObject != null && messageObject.isRoundVideo();
TLRPC.Message newReply = messageObject.hasValidReplyMessageObject() ? messageObject.replyMessageObject.messageOwner : null;
boolean messageIdChanged = currentMessageObject == null || currentMessageObject.getId() != messageObject.getId();
@ -3272,7 +3286,6 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (currentMessageObject != null && currentMessageObject.textLayoutBlocks != null && currentMessageObject.textLayoutBlocks.size() > 1) {
needNewVisiblePart = true;
}
}
boolean linked = false;
@ -3383,14 +3396,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (AndroidUtilities.isTablet()) {
maxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(122);
} else {
maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(122);
maxWidth = Math.min(getParentWidth(), AndroidUtilities.displaySize.y) - AndroidUtilities.dp(122);
}
drawName = true;
} else {
if (AndroidUtilities.isTablet()) {
maxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(80);
} else {
maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(80);
maxWidth = Math.min(getParentWidth(), AndroidUtilities.displaySize.y) - AndroidUtilities.dp(80);
}
drawName = isPinnedChat || messageObject.messageOwner.peer_id.channel_id != 0 && (!messageObject.isOutOwner() || messageObject.isSupergroup()) || messageObject.isImportedForward() && messageObject.messageOwner.fwd_from.from_id == null;
}
@ -3417,7 +3430,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
TLRPC.Document androidThemeDocument = null;
TLRPC.TL_themeSettings androidThemeSettings = null;
if (!drawInstantView) {
if ("telegram_voicechat".equals(webpageType)) {
if ("telegram_livestream".equals(webpageType)) {
drawInstantView = true;
drawInstantViewType = 11;
} else if ("telegram_voicechat".equals(webpageType)) {
drawInstantView = true;
drawInstantViewType = 9;
} else if ("telegram_channel".equals(webpageType)) {
@ -3583,9 +3599,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
} else {
if (drawAvatar) {
linkPreviewMaxWidth = AndroidUtilities.displaySize.x - AndroidUtilities.dp(132);
linkPreviewMaxWidth = getParentWidth() - AndroidUtilities.dp(132);
} else {
linkPreviewMaxWidth = AndroidUtilities.displaySize.x - AndroidUtilities.dp(80);
linkPreviewMaxWidth = getParentWidth() - AndroidUtilities.dp(80);
}
}
if (drawSideButton != 0) {
@ -3625,8 +3641,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
boolean isSmallImageType = "app".equals(type) || "profile".equals(type) || "article".equals(type) ||
"telegram_bot".equals(type) || "telegram_user".equals(type) || "telegram_channel".equals(type) || "telegram_megagroup".equals(type) || "telegram_voicechat".equals(type);
smallImage = !slideshow && (!drawInstantView || drawInstantViewType == 9) && document == null && isSmallImageType;
isSmallImage = !slideshow && (!drawInstantView || drawInstantViewType == 9) && document == null && description != null && type != null && isSmallImageType && currentMessageObject.photoThumbs != null;
smallImage = !slideshow && (!drawInstantView || drawInstantViewType == 9 || drawInstantViewType == 11) && document == null && isSmallImageType;
isSmallImage = !slideshow && (!drawInstantView || drawInstantViewType == 9 || drawInstantViewType == 11) && document == null && description != null && type != null && isSmallImageType && currentMessageObject.photoThumbs != null;
} else if (hasInvoicePreview) {
TLRPC.TL_messageMediaInvoice invoice = (TLRPC.TL_messageMediaInvoice) messageObject.messageOwner.media;
site_name = messageObject.messageOwner.media.title;
@ -3658,9 +3674,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
isSmallImage = false;
smallImage = false;
}
if (drawInstantViewType == 9) {
if (drawInstantViewType == 11) {
site_name = LocaleController.getString("VoipChannelVoiceChat", R.string.VoipChannelVoiceChat);
} else if (drawInstantViewType == 9) {
site_name = LocaleController.getString("VoipGroupVoiceChat", R.string.VoipGroupVoiceChat);
TLRPC.TL_webPage webPage = (TLRPC.TL_webPage) messageObject.messageOwner.media.webpage;
} else if (drawInstantViewType == 6) {
site_name = LocaleController.getString("ChatBackground", R.string.ChatBackground);
} else if ("telegram_theme".equals(webpageType)) {
@ -4001,7 +4018,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (AndroidUtilities.isTablet()) {
maxChildWidth = Math.max(maxChildWidth, Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(drawAvatar ? 52 : 0), AndroidUtilities.dp(220)) - AndroidUtilities.dp(30) + additinalWidth);
} else {
maxChildWidth = Math.max(maxChildWidth, Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(drawAvatar ? 52 : 0), AndroidUtilities.dp(220)) - AndroidUtilities.dp(30) + additinalWidth);
maxChildWidth = Math.max(maxChildWidth, Math.min(getParentWidth() - AndroidUtilities.dp(drawAvatar ? 52 : 0), AndroidUtilities.dp(220)) - AndroidUtilities.dp(30) + additinalWidth);
}
calcBackgroundWidth(maxWidth, timeMore, maxChildWidth);
} else if (MessageObject.isMusicDocument(document)) {
@ -4072,7 +4089,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (AndroidUtilities.isTablet()) {
maxPhotoWidth = (int) (AndroidUtilities.getMinTabletSide() * 0.5f);
} else {
maxPhotoWidth = (int) (AndroidUtilities.displaySize.x * 0.5f);
maxPhotoWidth = (int) (getParentWidth() * 0.5f);
}
} else if (documentAttachType == DOCUMENT_ATTACH_TYPE_ROUND) {
maxPhotoWidth = AndroidUtilities.roundMessageSize;
@ -4314,7 +4331,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (AndroidUtilities.isTablet()) {
backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(270));
} else {
backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(270));
backgroundWidth = Math.min(getParentWidth() - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(270));
}
availableTimeWidth = backgroundWidth - AndroidUtilities.dp(31);
@ -4384,7 +4401,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (AndroidUtilities.isTablet()) {
backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(270));
} else {
backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(270));
backgroundWidth = Math.min(getParentWidth() - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(270));
}
availableTimeWidth = backgroundWidth - AndroidUtilities.dp(31);
@ -4463,7 +4480,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (AndroidUtilities.isTablet()) {
backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(270));
} else {
backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(270));
backgroundWidth = Math.min(getParentWidth() - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(270));
}
createDocumentLayout(backgroundWidth, messageObject);
@ -4478,7 +4495,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (AndroidUtilities.isTablet()) {
backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(270));
} else {
backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(270));
backgroundWidth = Math.min(getParentWidth() - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(270));
}
createDocumentLayout(backgroundWidth, messageObject);
@ -4768,7 +4785,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
}
} else {
drawForwardedName = messageObject.messageOwner.fwd_from != null && !messageObject.isAnyKindOfSticker();
drawForwardedName = messageObject.messageOwner.fwd_from != null && !(messageObject.isAnyKindOfSticker() && messageObject.isDice());
if (!messageObject.isAnyKindOfSticker() && messageObject.type != MessageObject.TYPE_ROUND_VIDEO) {
drawName = (messageObject.isFromGroup() && messageObject.isSupergroup() || messageObject.isImportedForward() && messageObject.messageOwner.fwd_from.from_id == null) && (currentPosition == null || (currentPosition.flags & MessageObject.POSITION_FLAG_TOP) != 0);
}
@ -4799,7 +4816,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (AndroidUtilities.isTablet()) {
backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(300));
} else {
backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(300));
backgroundWidth = Math.min(getParentWidth() - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(300));
}
if (checkNeedDrawShareButton(messageObject)) {
backgroundWidth -= AndroidUtilities.dp(20);
@ -4896,7 +4913,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (AndroidUtilities.isTablet()) {
backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(252 + 37));
} else {
backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(252 + 37));
backgroundWidth = Math.min(getParentWidth() - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(252 + 37));
}
backgroundWidth -= AndroidUtilities.dp(4);
if (checkNeedDrawShareButton(messageObject)) {
@ -4946,7 +4963,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (AndroidUtilities.isTablet()) {
backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(252 + 37));
} else {
backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(252 + 37));
backgroundWidth = Math.min(getParentWidth() - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(252 + 37));
}
backgroundWidth -= AndroidUtilities.dp(4);
if (checkNeedDrawShareButton(messageObject)) {
@ -4980,7 +4997,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (AndroidUtilities.isTablet()) {
backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(252 + 37));
} else {
backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(252 + 37));
backgroundWidth = Math.min(getParentWidth() - AndroidUtilities.dp(drawAvatar ? 102 : 50), AndroidUtilities.dp(252 + 37));
}
backgroundWidth -= AndroidUtilities.dp(4);
if (checkNeedDrawShareButton(messageObject)) {
@ -5042,7 +5059,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (AndroidUtilities.isTablet()) {
maxHeight = maxWidth = AndroidUtilities.getMinTabletSide() * 0.4f;
} else {
maxHeight = maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.5f;
maxHeight = maxWidth = Math.min(getParentWidth(), AndroidUtilities.displaySize.y) * 0.5f;
}
String filter;
if (messageObject.isAnimatedEmoji() || messageObject.isDice()) {
@ -5130,10 +5147,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
photoWidth = (int) (AndroidUtilities.getMinTabletSide() * 0.7f);
} else {
if (currentPhotoObject != null && (messageObject.type == MessageObject.TYPE_PHOTO || messageObject.type == MessageObject.TYPE_VIDEO || messageObject.type == 8) && currentPhotoObject.w >= currentPhotoObject.h) {
photoWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(64 + (checkNeedDrawShareButton(messageObject) ? 10 : 0));
photoWidth = Math.min(getParentWidth(), AndroidUtilities.displaySize.y) - AndroidUtilities.dp(64 + (checkNeedDrawShareButton(messageObject) ? 10 : 0));
useFullWidth = true;
} else {
photoWidth = (int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.7f);
photoWidth = (int) (Math.min(getParentWidth(), AndroidUtilities.displaySize.y) * 0.7f);
}
}
}
@ -5289,14 +5306,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (AndroidUtilities.isTablet()) {
w = h = (int) (AndroidUtilities.getMinTabletSide() * 0.5f);
} else {
w = h = (int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.5f);
w = h = (int) (Math.min(getParentWidth(), AndroidUtilities.displaySize.y) * 0.5f);
}
}
int widthForCaption = 0;
boolean fixPhotoWidth = false;
if (currentMessagesGroup != null) {
float maxHeight = Math.max(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.5f;
float maxHeight = Math.max(getParentWidth(), AndroidUtilities.displaySize.y) * 0.5f;
int dWidth = getGroupPhotosWidth();
w = (int) Math.ceil(currentPosition.pw / 1000.0f * dWidth);
if (currentPosition.minY != 0 && (messageObject.isOutOwner() && (currentPosition.flags & MessageObject.POSITION_FLAG_LEFT) != 0 || !messageObject.isOutOwner() && (currentPosition.flags & MessageObject.POSITION_FLAG_RIGHT) != 0)) {
@ -5407,7 +5424,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (AndroidUtilities.isTablet()) {
minCaptionWidth = (int) (AndroidUtilities.getMinTabletSide() * 0.65f);
} else {
minCaptionWidth = (int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.65f);
minCaptionWidth = (int) (Math.min(getParentWidth(), AndroidUtilities.displaySize.y) * 0.65f);
}
if (!messageObject.needDrawBluredPreview() && currentCaption != null && photoWidth < minCaptionWidth) {
widthForCaption = minCaptionWidth;
@ -5801,6 +5818,19 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
}
if (messageObject.isSponsored()) {
drawInstantView = true;
drawInstantViewType = 1;
long id = MessageObject.getPeerId(messageObject.messageOwner.from_id);
if (id > 0) {
TLRPC.User user = MessagesController.getInstance(currentAccount).getUser((int) id); //TODO long
if (user != null && user.bot) {
drawInstantViewType = 10;
}
}
createInstantViewButton();
}
botButtons.clear();
if (messageIdChanged) {
botButtonsByData.clear();
@ -5823,7 +5853,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (AndroidUtilities.isTablet()) {
maxButtonWidth += AndroidUtilities.getMinTabletSide();
} else {
maxButtonWidth += Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(5);
maxButtonWidth += Math.min(getParentWidth(), AndroidUtilities.displaySize.y) - AndroidUtilities.dp(5);
}
widthForButtons = Math.max(backgroundWidth, Math.min(messageObject.wantedBotKeyboardWidth, maxButtonWidth));
}
@ -6804,6 +6834,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
instantWidth = AndroidUtilities.dp(12 + 9 + 12);
if (drawInstantViewType == 1) {
str = LocaleController.getString("OpenChannel", R.string.OpenChannel);
} else if (drawInstantViewType == 10) {
str = LocaleController.getString("OpenBot", R.string.OpenBot);
} else if (drawInstantViewType == 2) {
str = LocaleController.getString("OpenGroup", R.string.OpenGroup);
} else if (drawInstantViewType == 3) {
@ -6820,7 +6852,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
} else {
str = LocaleController.getString("PollSubmitVotes", R.string.PollSubmitVotes);
}
} else if (drawInstantViewType == 9) {
} else if (drawInstantViewType == 9 || drawInstantViewType == 11) {
TLRPC.TL_webPage webPage = (TLRPC.TL_webPage) currentMessageObject.messageOwner.media.webpage;
if (webPage != null && webPage.url.contains("voicechat=")) {
str = LocaleController.getString("VoipGroupJoinAsSpeaker", R.string.VoipGroupJoinAsSpeaker);
@ -6877,14 +6909,18 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
private int getGroupPhotosWidth() {
int width = getParentWidth();
if (currentMessageObject != null && currentMessageObject.preview) {
width = parentWidth;
}
if (!AndroidUtilities.isInMultiwindow && AndroidUtilities.isTablet() && (!AndroidUtilities.isSmallTablet() || getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)) {
int leftWidth = AndroidUtilities.displaySize.x / 100 * 35;
int leftWidth = width / 100 * 35;
if (leftWidth < AndroidUtilities.dp(320)) {
leftWidth = AndroidUtilities.dp(320);
}
return AndroidUtilities.displaySize.x - leftWidth;
return width - leftWidth;
} else {
return AndroidUtilities.displaySize.x;
return width;
}
}
@ -7309,7 +7345,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
private void drawContent(Canvas canvas) {
if (needNewVisiblePart && currentMessageObject.type == 0) {
getLocalVisibleRect(scrollRect);
setVisiblePart(scrollRect.top, scrollRect.bottom - scrollRect.top, parentHeight, parentViewTopOffset, viewTop, backgroundHeight);
setVisiblePart(scrollRect.top, scrollRect.bottom - scrollRect.top, parentHeight, parentViewTopOffset, viewTop, parentWidth, backgroundHeight);
needNewVisiblePart = false;
}
@ -7387,7 +7423,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
textX += diff - getExtraTimeX();
}
}
if (!enterTransitionInPorgress) {
if (!enterTransitionInPorgress && currentMessageObject != null && !currentMessageObject.preview) {
if (transitionParams.animateChangeProgress != 1.0f && transitionParams.animateMessageText) {
canvas.save();
if (currentBackgroundDrawable != null) {
@ -7687,7 +7723,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (captionLayout != null) {
updateCaptionLayout();
}
if ((currentPosition == null || currentMessagesGroup != null && currentMessagesGroup.isDocuments) && !transitionParams.transformGroupToSingleMessage && !(enterTransitionInPorgress && currentMessageObject.isVoice())) {
if (!currentMessageObject.preview && (currentPosition == null || currentMessagesGroup != null && currentMessagesGroup.isDocuments) && !transitionParams.transformGroupToSingleMessage && !(enterTransitionInPorgress && currentMessageObject.isVoice())) {
drawCaptionLayout(canvas, false, 1f);
}
@ -7864,7 +7900,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
public void drawLinkPreview(Canvas canvas, float alpha) {
if (!hasLinkPreview && !hasGamePreview && !hasInvoicePreview) {
if (!currentMessageObject.isSponsored() && !hasLinkPreview && !hasGamePreview && !hasInvoicePreview) {
return;
}
int startY;
@ -7875,6 +7911,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
} else if (hasInvoicePreview) {
startY = AndroidUtilities.dp(14) + namesOffset;
linkX = unmovedTextX + AndroidUtilities.dp(1);
} else if (currentMessageObject.isSponsored()) {
startY = textY + currentMessageObject.textHeight - AndroidUtilities.dp(2);
linkX = unmovedTextX + AndroidUtilities.dp(1);
} else {
startY = textY + currentMessageObject.textHeight + AndroidUtilities.dp(8);
linkX = unmovedTextX + AndroidUtilities.dp(1);
@ -7882,7 +7921,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
int linkPreviewY = startY;
int smallImageStartY = 0;
if (!hasInvoicePreview) {
if (!hasInvoicePreview && !currentMessageObject.isSponsored()) {
Theme.chat_replyLinePaint.setColor(Theme.getColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outPreviewLine : Theme.key_chat_inPreviewLine));
if (alpha != 1f) {
Theme.chat_replyLinePaint.setAlpha((int) (alpha * Theme.chat_replyLinePaint.getAlpha()));
@ -8048,7 +8087,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
linkPreviewY += descriptionLayout.getLineBottom(descriptionLayout.getLineCount() - 1);
}
if (drawPhotoImage && (!drawInstantView || drawInstantViewType == 9)) {
if (drawPhotoImage && (!drawInstantView || drawInstantViewType == 9 || drawInstantViewType == 11)) {
if (linkPreviewY != startY) {
linkPreviewY += AndroidUtilities.dp(2);
}
@ -8267,8 +8306,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
canvas.restore();
}
@SuppressLint("Range")
public void drawMessageText(Canvas canvas, ArrayList<MessageObject.TextLayoutBlock> textLayoutBlocks, boolean origin, float alpha, boolean drawOnlyText) {
if (textLayoutBlocks == null || textLayoutBlocks.isEmpty()) {
if (textLayoutBlocks == null || textLayoutBlocks.isEmpty() || alpha == 0) {
return;
}
int firstVisibleBlockNum;
@ -8285,12 +8325,19 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
lastVisibleBlockNum = textLayoutBlocks.size();
}
float textY = this.textY;
if (transitionParams.animateText) {
textY = transitionParams.animateFromTextY * (1f - transitionParams.animateChangeProgress) + this.textY * transitionParams.animateChangeProgress;
}
textY += transitionYOffsetForDrawables;
if (firstVisibleBlockNum >= 0) {
int restore = Integer.MIN_VALUE;
int oldAlpha = -1;
int oldLinkAlpha = -1;
int oldAlpha = 0;
int oldLinkAlpha = 0;
boolean needRestoreColor = false;
if (alpha != 1.0f) {
if (drawOnlyText) {
needRestoreColor = true;
oldAlpha = Theme.chat_msgTextPaint.getAlpha();
oldLinkAlpha = Color.alpha(Theme.chat_msgTextPaint.linkColor);
Theme.chat_msgTextPaint.setAlpha((int) (oldAlpha * alpha));
@ -8344,7 +8391,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
canvas.restore();
}
if (oldAlpha >= 0) {
if (needRestoreColor) {
Theme.chat_msgTextPaint.setAlpha(oldAlpha);
Theme.chat_msgTextPaint.linkColor = ColorUtils.setAlphaComponent(Theme.chat_msgTextPaint.linkColor, oldLinkAlpha);
}
@ -8478,9 +8525,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
} else {
if (isChat && !isThreadPost && !currentMessageObject.isOutOwner() && currentMessageObject.needDrawAvatar()) {
maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(42);
maxWidth = Math.min(getParentWidth(), AndroidUtilities.displaySize.y) - AndroidUtilities.dp(42);
} else {
maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y);
maxWidth = Math.min(getParentWidth(), AndroidUtilities.displaySize.y);
}
}
if (isPlayingRound) {
@ -8494,7 +8541,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (AndroidUtilities.isTablet()) {
dWidth = AndroidUtilities.getMinTabletSide();
} else {
dWidth = AndroidUtilities.displaySize.x;
dWidth = getParentWidth();
}
int firstLineWidth = 0;
for (int a = 0; a < currentMessagesGroup.posArray.size(); a++) {
@ -9445,7 +9492,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
}
}
if (currentMessageObject.scheduled && currentMessageObject.messageOwner.date == 0x7FFFFFFE) {
if (currentMessageObject.isSponsored()) {
timeString = LocaleController.getString("SponsoredMessage", R.string.SponsoredMessage);
} else if (currentMessageObject.scheduled && currentMessageObject.messageOwner.date == 0x7FFFFFFE) {
timeString = "";
} else if (edited) {
timeString = LocaleController.getString("EditedMessage", R.string.EditedMessage) + " " + LocaleController.getInstance().formatterDay.format((long) (messageObject.messageOwner.date) * 1000);
@ -9583,7 +9632,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
private void setMessageObjectInternal(MessageObject messageObject) {
if (((messageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0 || messageObject.messageOwner.replies != null) && !currentMessageObject.scheduled) {
if (((messageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0 || messageObject.messageOwner.replies != null) && !currentMessageObject.scheduled && !currentMessageObject.isSponsored()) {
if (!currentMessageObject.viewsReloaded) {
MessagesController.getInstance(currentAccount).addToViewsQueue(currentMessageObject);
currentMessageObject.viewsReloaded = true;
@ -9657,7 +9706,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
} else if (isMegagroup && currentChat != null && currentMessageObject.isForwardedChannelPost()) {
adminString = LocaleController.getString("DiscussChannel", R.string.DiscussChannel);
adminWidth = (int) Math.ceil(Theme.chat_adminPaint.measureText(adminString));
nameWidth -= adminWidth;
nameWidth -= adminWidth; //TODO
} else if (currentUser != null && !currentMessageObject.isOutOwner() && !currentMessageObject.isAnyKindOfSticker() && currentMessageObject.type != 5 && delegate != null && (adminLabel = delegate.getAdminRank(currentUser.id)) != null) {
if (adminLabel.length() == 0) {
adminLabel = LocaleController.getString("ChatAdmin", R.string.ChatAdmin);
@ -9808,7 +9857,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
forwardNameOffsetX[0] = forwardedNameLayout[0].getLineLeft(0);
forwardNameOffsetX[1] = forwardedNameLayout[1].getLineLeft(0);
if (messageObject.type != 5) {
if (messageObject.type != 5 && !messageObject.isAnyKindOfSticker()) {
namesOffset += AndroidUtilities.dp(36);
}
} catch (Exception e) {
@ -10076,7 +10125,13 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
public void drawCheckBox(Canvas canvas) {
if (currentMessageObject != null && !currentMessageObject.isSending() && !currentMessageObject.isSendError() && checkBox != null && (checkBoxVisible || checkBoxAnimationInProgress) && (currentPosition == null || (currentPosition.flags & MessageObject.POSITION_FLAG_BOTTOM) != 0 && (currentPosition.flags & MessageObject.POSITION_FLAG_LEFT) != 0)) {
canvas.save();
canvas.translate(0, getTop());
float y = getY();
if (currentMessagesGroup != null && currentMessagesGroup.messages.size() > 1) {
y = getTop() + currentMessagesGroup.transitionParams.offsetTop - getTranslationY();
} else {
y += transitionParams.deltaTop;
}
canvas.translate(0, y + transitionYOffsetForDrawables);
checkBox.draw(canvas);
canvas.restore();
}
@ -10091,20 +10146,41 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
if (drawable == null) {
continue;
}
int w = parentWidth;
int h = parentHeight;
if (h == 0) {
w = getParentWidth();
h = AndroidUtilities.displaySize.y;
if (getParent() instanceof View) {
View view = (View) getParent();
w = view.getMeasuredWidth();
h = view.getMeasuredHeight();
}
}
drawable.setTop((int) ((fromParent ? getY() : getTop()) + parentViewTopOffset), h, pinnedTop, pinnedBottom || transitionParams.changePinnedBottomProgress != 1);
drawable.setTop((int) ((fromParent ? getY() : getTop()) + parentViewTopOffset), w, h, (int) parentViewTopOffset, pinnedTop, pinnedBottom || transitionParams.changePinnedBottomProgress != 1);
}
}
public void setBackgroundTopY(int offset) {
Theme.MessageDrawable drawable = currentBackgroundDrawable;
int w = parentWidth;
int h = parentHeight;
if (h == 0) {
w = getParentWidth();
h = AndroidUtilities.displaySize.y;
if (getParent() instanceof View) {
View view = (View) getParent();
w = view.getMeasuredWidth();
h = view.getMeasuredHeight();
}
}
drawable.setTop((int) (getTop() + parentViewTopOffset + offset), w, h, (int) parentViewTopOffset, pinnedTop, pinnedBottom || transitionParams.changePinnedBottomProgress != 1);
}
float transitionYOffsetForDrawables;
public void setDrawableBoundsInner(Drawable drawable, int x, int y, int w, int h) {
if (drawable != null) {
transitionYOffsetForDrawables = (y + h + transitionParams.deltaBottom) - ((int) (y + h + transitionParams.deltaBottom));
drawable.setBounds((int) (x + transitionParams.deltaLeft), (int) (y + transitionParams.deltaTop), (int) (x + w + transitionParams.deltaRight), (int) (y + h + transitionParams.deltaBottom));
}
}
@ -10384,6 +10460,12 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
}
boolean needRestore = false;
if (transitionYOffsetForDrawables != 0) {
needRestore = true;
canvas.save();
canvas.translate(0, transitionYOffsetForDrawables);
}
if (drawBackground && currentBackgroundDrawable != null && (currentPosition == null || isDrawSelectionBackground() && (currentMessageObject.isMusic() || currentMessageObject.isDocument())) && !(enterTransitionInPorgress && !currentMessageObject.isVoice())) {
if (isHighlightedAnimated) {
currentBackgroundDrawable.setAlpha((int) (255 * alphaInternal));
@ -10433,15 +10515,18 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
drawable.setBounds(rect.left, rect.top, rect.right + AndroidUtilities.dp(6), rect.bottom);
canvas.save();
canvas.clipRect(rect.right - AndroidUtilities.dp(12), rect.bottom - AndroidUtilities.dp(16), rect.right + AndroidUtilities.dp(12), rect.bottom);
int w = parentWidth;
int h = parentHeight;
if (h == 0) {
w = getParentWidth();
h = AndroidUtilities.displaySize.y;
if (getParent() instanceof View) {
View view = (View) getParent();
w = view.getMeasuredWidth();
h = view.getMeasuredHeight();
}
}
drawable.setTop((int) (getY() + parentViewTopOffset), h, pinnedTop, pinnedBottom);
drawable.setTop((int) (getY() + parentViewTopOffset), w, h, (int) parentViewTopOffset, pinnedTop, pinnedBottom);
float alpha = !mediaBackground && !pinnedBottom ? transitionParams.changePinnedBottomProgress : (1f - transitionParams.changePinnedBottomProgress);
drawable.setAlpha((int) (255 * alpha));
drawable.draw(canvas);
@ -10466,6 +10551,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
}
}
if (needRestore) {
canvas.restore();
}
if (isHighlightedAnimated) {
long newTime = System.currentTimeMillis();
long dt = Math.abs(newTime - lastHighlightProgressTime);
@ -10802,7 +10890,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
return getBackgroundDrawableTop() + layoutHeight - offsetBottom + additionalBottom;
}
public void drawBackground(Canvas canvas, int left, int top, int right, int bottom, boolean pinnedTop, boolean pinnedBottom, boolean selected) {
public void drawBackground(Canvas canvas, int left, int top, int right, int bottom, boolean pinnedTop, boolean pinnedBottom, boolean selected, int keyboardHeight) {
if (currentMessageObject.isOutOwner()) {
if (!mediaBackground && !pinnedBottom) {
currentBackgroundDrawable = selected ? Theme.chat_msgOutSelectedDrawable : Theme.chat_msgOutDrawable;
@ -10817,17 +10905,20 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
}
int w = parentWidth;
int h = parentHeight;
if (h == 0) {
w = getParentWidth();
h = AndroidUtilities.displaySize.y;
if (getParent() instanceof View) {
View view = (View) getParent();
w = view.getMeasuredWidth();
h = view.getMeasuredHeight();
}
}
if (currentBackgroundDrawable != null) {
currentBackgroundDrawable.setTop(0, h, pinnedTop, pinnedBottom);
currentBackgroundDrawable.setTop(keyboardHeight, w, h, (int) parentViewTopOffset, pinnedTop, pinnedBottom);
Drawable currentBackgroundShadowDrawable = currentBackgroundDrawable.getShadowDrawable();
if (currentBackgroundShadowDrawable != null) {
currentBackgroundShadowDrawable.setAlpha((int) (getAlpha() * 255));
@ -11006,24 +11097,62 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
}
}
if (drawForwardedName && forwardedNameLayout[0] != null && forwardedNameLayout[1] != null && (currentPosition == null || currentPosition.minY == 0 && currentPosition.minX == 0)) {
if (currentMessageObject.type == MessageObject.TYPE_ROUND_VIDEO) {
boolean drawForwardedNameLocal = drawForwardedName;
StaticLayout[] forwardedNameLayoutLocal = forwardedNameLayout;
float animatingAlpha = 1f;
int forwardedNameWidthLocal = forwardedNameWidth;
if (transitionParams.animateForwardedLayout) {
if (!currentMessageObject.needDrawForwarded()) {
drawForwardedNameLocal = true;
forwardedNameLayoutLocal = transitionParams.animatingForwardedNameLayout;
animatingAlpha = 1f - transitionParams.animateChangeProgress;
forwardedNameWidthLocal = transitionParams.animateForwardNameWidth;
} else {
animatingAlpha = transitionParams.animateChangeProgress;
}
}
float forwardNameXLocal;
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(Theme.getColor(Theme.key_chat_stickerReplyNameText));
if (currentMessageObject.isOutOwner()) {
forwardNameX = AndroidUtilities.dp(23);
if (currentMessageObject.needDrawForwarded()) {
if (currentMessageObject.isOutOwner()) {
forwardNameXLocal = forwardNameX = AndroidUtilities.dp(23);
} else {
forwardNameXLocal = forwardNameX = backgroundDrawableLeft + backgroundDrawableRight + AndroidUtilities.dp(17);
}
} else {
forwardNameX = backgroundDrawableLeft + backgroundDrawableRight + AndroidUtilities.dp(17);
forwardNameXLocal = transitionParams.animateForwardNameX;
}
if (currentMessageObject.isOutOwner() && currentMessageObject.type == MessageObject.TYPE_ROUND_VIDEO && transitionParams.animatePlayingRound || isPlayingRound) {
forwardNameXLocal -= AndroidUtilities.dp (78) * (isPlayingRound ? transitionParams.animateChangeProgress : (1f - transitionParams.animateChangeProgress));
}
forwardNameY = AndroidUtilities.dp(12);
int backWidth = forwardedNameWidth + AndroidUtilities.dp(14);
int backWidth = forwardedNameWidthLocal + AndroidUtilities.dp(14);
rect.set((int) forwardNameX - AndroidUtilities.dp(7), forwardNameY - AndroidUtilities.dp(6), (int) forwardNameX - AndroidUtilities.dp(7) + backWidth, forwardNameY + AndroidUtilities.dp(38));
rect.set((int) forwardNameXLocal - AndroidUtilities.dp(7), forwardNameY - AndroidUtilities.dp(6), (int) forwardNameXLocal - AndroidUtilities.dp(7) + backWidth, forwardNameY + AndroidUtilities.dp(38));
Theme.applyServiceShaderMatrix(getMeasuredWidth(), backgroundHeight, getX(), viewTop);
int oldAlpha1 = -1, oldAlpha2 = -1;
if (animatingAlpha != 1f) {
oldAlpha1 = Theme.chat_actionBackgroundPaint.getAlpha();
Theme.chat_actionBackgroundPaint.setAlpha((int) (oldAlpha1 * animatingAlpha));
}
canvas.drawRoundRect(rect, AndroidUtilities.dp(6), AndroidUtilities.dp(6), Theme.chat_actionBackgroundPaint);
if (Theme.hasGradientService()) {
if (animatingAlpha != 1f) {
oldAlpha2 = Theme.chat_actionBackgroundGradientDarkenPaint.getAlpha();
Theme.chat_actionBackgroundGradientDarkenPaint.setAlpha((int) (oldAlpha2 * animatingAlpha));
}
canvas.drawRoundRect(rect, AndroidUtilities.dp(6), AndroidUtilities.dp(6), Theme.chat_actionBackgroundGradientDarkenPaint);
}
if (oldAlpha1 >= 0) {
Theme.chat_actionBackgroundPaint.setAlpha(oldAlpha1);
}
if (oldAlpha2 >= 0) {
Theme.chat_actionBackgroundGradientDarkenPaint.setAlpha(oldAlpha2);
}
} else {
forwardNameY = AndroidUtilities.dp(10 + (drawNameLayout ? 19 : 0));
if (currentMessageObject.isOutOwner()) {
@ -11032,24 +11161,62 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
} else {
Theme.chat_forwardNamePaint.setColor(Theme.getColor(Theme.key_chat_outForwardedNameText));
}
forwardNameX = backgroundDrawableLeft + AndroidUtilities.dp(11) + getExtraTextX();
if (currentMessageObject.needDrawForwarded()) {
forwardNameXLocal = forwardNameX = backgroundDrawableLeft + AndroidUtilities.dp(11) + getExtraTextX();
} else {
forwardNameXLocal = transitionParams.animateForwardNameX;
}
} else {
if (hasPsaHint) {
Theme.chat_forwardNamePaint.setColor(Theme.getColor(Theme.key_chat_inPsaNameText));
} else {
Theme.chat_forwardNamePaint.setColor(Theme.getColor(Theme.key_chat_inForwardedNameText));
}
if (mediaBackground) {
forwardNameX = backgroundDrawableLeft + AndroidUtilities.dp(11) + getExtraTextX();
if (currentMessageObject.needDrawForwarded()) {
if (mediaBackground) {
forwardNameXLocal = forwardNameX = backgroundDrawableLeft + AndroidUtilities.dp(11) + getExtraTextX();
} else {
forwardNameXLocal = forwardNameX = backgroundDrawableLeft + AndroidUtilities.dp(drawPinnedBottom ? 11 : 17) + getExtraTextX();
}
} else {
forwardNameX = backgroundDrawableLeft + AndroidUtilities.dp(drawPinnedBottom ? 11 : 17) + getExtraTextX();
forwardNameXLocal = transitionParams.animateForwardNameX;
}
}
}
boolean clipContent = false;
if (transitionParams.animateForwardedLayout) {
if (currentBackgroundDrawable != null && currentMessagesGroup == null && currentMessageObject.type != MessageObject.TYPE_ROUND_VIDEO && !currentMessageObject.isAnyKindOfSticker()) {
Rect r = currentBackgroundDrawable.getBounds();
canvas.save();
if (currentMessageObject.isOutOwner() && !mediaBackground && !pinnedBottom) {
canvas.clipRect(
r.left + AndroidUtilities.dp(4), r.top + AndroidUtilities.dp(4),
r.right - AndroidUtilities.dp(10), r.bottom - AndroidUtilities.dp(4)
);
} else {
canvas.clipRect(
r.left + AndroidUtilities.dp(4), r.top + AndroidUtilities.dp(4),
r.right - AndroidUtilities.dp(4), r.bottom - AndroidUtilities.dp(4)
);
}
clipContent = true;
}
}
for (int a = 0; a < 2; a++) {
canvas.save();
canvas.translate(forwardNameX - forwardNameOffsetX[a], forwardNameY + AndroidUtilities.dp(16) * a);
forwardedNameLayout[a].draw(canvas);
canvas.translate(forwardNameXLocal - forwardNameOffsetX[a], forwardNameY + AndroidUtilities.dp(16) * a);
if (animatingAlpha != 1f) {
int oldAlpha = forwardedNameLayoutLocal[a].getPaint().getAlpha();
forwardedNameLayoutLocal[a].getPaint().setAlpha((int) (oldAlpha * animatingAlpha));
forwardedNameLayoutLocal[a].draw(canvas);
forwardedNameLayoutLocal[a].getPaint().setAlpha(oldAlpha);
} else {
forwardedNameLayoutLocal[a].draw(canvas);
}
canvas.restore();
}
if (clipContent) {
canvas.restore();
}
@ -11209,7 +11376,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
checkBox.onAttachedToWindow();
}
}
if (visible && mediaCheckBox == null && currentMessagesGroup != null && currentMessagesGroup.messages.size() > 1) {
if (visible && mediaCheckBox == null && ((currentMessagesGroup != null && currentMessagesGroup.messages.size() > 1) || (groupedMessagesToSet != null && groupedMessagesToSet.messages.size() > 1))) {
mediaCheckBox = new CheckBoxBase(this, 21);
mediaCheckBox.setUseDefaultCheck(true);
if (attachedToWindow) {
@ -11780,7 +11947,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
canvas.restore();
Theme.chat_timePaint.setAlpha(255);
} else {
timeYOffset = -(drawCommentButton ? AndroidUtilities.dp(43) : 0);
if (currentMessageObject.isSponsored()) {
timeYOffset = -AndroidUtilities.dp(48);
} else {
timeYOffset = -(drawCommentButton ? AndroidUtilities.dp(43) : 0);
}
float additionalX = -timeLayout.getLineLeft(0);
if (ChatObject.isChannel(currentChat) && !currentChat.megagroup || (currentMessageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0 || (repliesLayout != null || transitionParams.animateReplies) || (isPinned || transitionParams.animatePinned)) {
additionalX += this.timeWidth - timeLayout.getLineWidth(0);
@ -11870,6 +12041,12 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
boolean drawClock = (currentStatus & 4) != 0;
boolean drawError = (currentStatus & 8) != 0;
boolean isBroadcast = (currentStatus & 16) != 0;
boolean needRestore = false;
if (transitionYOffsetForDrawables != 0) {
needRestore = true;
canvas.save();
canvas.translate(0, transitionYOffsetForDrawables);
}
if (statusDrawableAnimationInProgress) {
boolean outDrawCheck1 = (animateFromStatusDrawableParams & 1) != 0;
boolean outDrawCheck2 = (animateFromStatusDrawableParams & 2) != 0;
@ -11885,6 +12062,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
} else {
drawStatusDrawable(canvas, drawCheck1, drawCheck2, drawClock, drawError, isBroadcast, alpha, bigRadius, timeYOffset, layoutHeight, 1, false, drawSelectionBackground);
}
if (needRestore) {
canvas.restore();
}
transitionParams.lastStatusDrawableParams = transitionParams.createStatusDrawableParams();
if (fromParent && drawClock && getParent() != null) {
((View) getParent()).invalidate();
@ -12787,6 +12967,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
Theme.chat_livePaint.setColor(color2);
Theme.chat_locationAddressPaint.setColor(color2);
canvas.save();
if (transitionParams.animateForwardedLayout) {
float y = namesOffset * transitionParams.animateChangeProgress + transitionParams.animateForwardedNamesOffset * (1f - transitionParams.animateChangeProgress);
if (currentMessageObject.needDrawForwarded()) {
y -= namesOffset;
}
canvas.translate(0, y);
}
int x;
if (currentMessageObject.isOutOwner()) {
x = layoutWidth - backgroundWidth + AndroidUtilities.dp(11);
@ -13060,6 +13248,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
canvas.restore();
}
updatePollAnimations(dt);
canvas.restore();
} else if (currentMessageObject.type == 12) {
if (currentMessageObject.isOutOwner()) {
Theme.chat_contactNamePaint.setColor(Theme.getColor(Theme.key_chat_outContactNameText));
@ -14236,6 +14425,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
return isRoundVideo && isPlayingRound;
}
public int getParentWidth() {
MessageObject object = currentMessageObject == null ? messageObjectToSet : currentMessageObject;
if (object != null && object.preview && parentWidth > 0) {
return parentWidth;
}
return AndroidUtilities.displaySize.x;
}
public class TransitionParams {
public float lastDrawingImageX, lastDrawingImageY, lastDrawingImageW, lastDrawingImageH;
@ -14354,8 +14551,24 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
private StaticLayout lastTimeLayout;
private boolean lastIsPlayingRound;
public boolean animatePlayingRound;
public boolean animateText;
public float lastDrawingTextY;
public float lastDrawingTextX;
public float animateFromTextY;
public int lastTopOffset;
public boolean animateForwardedLayout;
public int animateForwardedNamesOffset;
public int lastForwardedNamesOffset;
public boolean lastDrawnForwardedName;
public StaticLayout[] lastDrawnForwardedNameLayout = new StaticLayout[2];
public StaticLayout[] animatingForwardedNameLayout = new StaticLayout[2];
float animateForwardNameX;
float lastForwardNameX;
int animateForwardNameWidth;
int lastForwardNameWidth;
public void recordDrawingState() {
wasDraw = true;
@ -14414,8 +14627,26 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
lastLocatinIsExpired = locationExpired;
lastIsPlayingRound = isPlayingRound;
lastDrawingTextY = textY;
lastDrawingTextX = textX;
lastDrawnForwardedNameLayout[0] = forwardedNameLayout[0];
lastDrawnForwardedNameLayout[1] = forwardedNameLayout[1];
lastDrawnForwardedName = currentMessageObject.needDrawForwarded();
lastForwardNameX = forwardNameX;
lastForwardedNamesOffset = namesOffset;
lastForwardNameWidth = forwardedNameWidth;
}
public void recordDrawingStatePreview() {
lastDrawnForwardedNameLayout[0] = forwardedNameLayout[0];
lastDrawnForwardedNameLayout[1] = forwardedNameLayout[1];
lastDrawnForwardedName = currentMessageObject.needDrawForwarded();
lastForwardNameX = forwardNameX;
lastForwardedNamesOffset = namesOffset;
lastForwardNameWidth = forwardedNameWidth;
}
public boolean animateChange() {
if (!wasDraw) {
return false;
@ -14473,6 +14704,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
animateReplaceCaptionLayout = true;
animateOutCaptionLayout = lastDrawingCaptionLayout;
changed = true;
} else {
updateCaptionLayout();
if (lastDrawingCaptionX != captionX || lastDrawingCaptionY != captionY) {
moveCaption = true;
captionFromX = lastDrawingCaptionX;
captionFromY = lastDrawingCaptionY;
changed = true;
}
}
} else if (captionLayout != null && lastDrawingCaptionLayout != null) {
updateCaptionLayout();
@ -14571,6 +14810,24 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
animatePlayingRound = true;
changed = true;
}
if (lastDrawingTextY != textY) {
animateText = true;
animateFromTextY = lastDrawingTextY;
changed = true;
}
if (currentMessageObject != null) {
if (lastDrawnForwardedName != currentMessageObject.needDrawForwarded()) {
animateForwardedLayout = true;
animatingForwardedNameLayout[0] = lastDrawnForwardedNameLayout[0];
animatingForwardedNameLayout[1] = lastDrawnForwardedNameLayout[1];
animateForwardNameX = lastForwardNameX;
animateForwardedNamesOffset = lastForwardedNamesOffset;
animateForwardNameWidth = lastForwardNameWidth;
changed = true;
}
}
return changed;
}
@ -14627,6 +14884,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate
animateDrawingTimeAlpha = false;
animateLocationIsExpired = false;
animatePlayingRound = false;
animateText = false;
animateForwardedLayout = false;
animatingForwardedNameLayout[0] = null;
animatingForwardedNameLayout[1] = null;
}
public boolean supportChangeAnimation() {

View file

@ -377,10 +377,25 @@ public class DialogCell extends BaseCell {
user = newUser;
}
}
boolean isOnline = user != null && !user.self && (user.status != null && user.status.expires > ConnectionsManager.getInstance(currentAccount).getCurrentTime() || MessagesController.getInstance(currentAccount).onlinePrivacy.containsKey(user.id));
boolean isOnline = isOnline();
onlineProgress = isOnline ? 1.0f : 0.0f;
}
private boolean isOnline() {
if (user == null || user.self) {
return false;
}
if (user.status != null && user.status.expires <= 0) {
if (MessagesController.getInstance(currentAccount).onlinePrivacy.containsKey(user.id)) {
return true;
}
}
if (user.status != null && user.status.expires > ConnectionsManager.getInstance(currentAccount).getCurrentTime()) {
return true;
}
return false;
}
private void checkGroupCall() {
hasCall = chat != null && chat.call_active && chat.call_not_empty;
chatCallProgress = hasCall ? 1.0f : 0.0f;
@ -598,6 +613,7 @@ public class DialogCell extends BaseCell {
boolean showChecks = !UserObject.isUserSelf(user) && !useMeForMyMessages;
boolean drawTime = true;
printingStringType = -1;
int printigStingReplaceIndex = -1;
String messageFormat;
boolean hasNameInMessage;
@ -875,7 +891,15 @@ public class DialogCell extends BaseCell {
startPadding = statusDrawable.getIntrinsicWidth() + AndroidUtilities.dp(3);
}
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
spannableStringBuilder.append(" ").append(TextUtils.replace(printingString, new String[]{"..."}, new String[]{""})).setSpan(new FixedWidthSpan(startPadding), 0, 1, 0);
if (printingStringType == 5) {
printigStingReplaceIndex = printingString.toString().indexOf("**oo**");
}
if (printigStingReplaceIndex > 0) {
spannableStringBuilder.append(TextUtils.replace(printingString, new String[]{"..."}, new String[]{""})).setSpan(new FixedWidthSpan(Theme.getChatStatusDrawable(printingStringType).getIntrinsicWidth()), printigStingReplaceIndex, printigStingReplaceIndex + 6, 0);
} else {
spannableStringBuilder.append(" ").append(TextUtils.replace(printingString, new String[]{"..."}, new String[]{""})).setSpan(new FixedWidthSpan(startPadding), 0, 1, 0);
}
messageString = spannableStringBuilder;
currentMessagePaint = Theme.dialogs_messagePrintingPaint[paintIndex];
@ -1725,8 +1749,14 @@ public class DialogCell extends BaseCell {
}
}
if (messageLayout != null && printingStringType >= 0) {
float x1 = messageLayout.getPrimaryHorizontal(0);
float x2 = messageLayout.getPrimaryHorizontal(1);
float x1, x2;
if (printigStingReplaceIndex >= 0){
x1 = messageLayout.getPrimaryHorizontal(printigStingReplaceIndex);
x2 = messageLayout.getPrimaryHorizontal(printigStingReplaceIndex + 1);
} else {
x1 = messageLayout.getPrimaryHorizontal(0);
x2 = messageLayout.getPrimaryHorizontal(1);
}
if (x1 < x2) {
statusDrawableLeft = (int) (messageLeft + x1);
} else {
@ -2802,7 +2832,7 @@ public class DialogCell extends BaseCell {
if (isDialogCell && currentDialogFolderId == 0) {
if (user != null && !MessagesController.isSupportUser(user) && !user.bot) {
boolean isOnline = !user.self && (user.status != null && user.status.expires > ConnectionsManager.getInstance(currentAccount).getCurrentTime() || MessagesController.getInstance(currentAccount).onlinePrivacy.containsKey(user.id));
boolean isOnline = isOnline();
if (isOnline || onlineProgress != 0) {
int top = (int) (avatarImage.getImageY2() - AndroidUtilities.dp(useForceThreeLines || SharedConfig.useThreeLinesLayout ? 6 : 8));
int left;

View file

@ -27,6 +27,7 @@ import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Components.AvatarDrawable;
import org.telegram.ui.Components.BackupImageView;
import org.telegram.ui.Components.CheckBox2;
import org.telegram.ui.Components.CounterView;
import org.telegram.ui.Components.LayoutHelper;
@ -46,9 +47,12 @@ public class HintDialogCell extends FrameLayout {
boolean wasDraw;
CounterView counterView;
CheckBox2 checkBox;
private final boolean drawCheckbox;
public HintDialogCell(Context context) {
public HintDialogCell(Context context, boolean drawCheckbox) {
super(context);
this.drawCheckbox = drawCheckbox;
imageView = new BackupImageView(context);
imageView.setRoundRadius(AndroidUtilities.dp(27));
@ -67,12 +71,28 @@ public class HintDialogCell extends FrameLayout {
addView(counterView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 28, Gravity.TOP,0 ,4,0,0));
counterView.setColors(Theme.key_chats_unreadCounterText, Theme.key_chats_unreadCounter);
counterView.setGravity(Gravity.RIGHT);
if (drawCheckbox) {
checkBox = new CheckBox2(context, 21);
checkBox.setColor(Theme.key_dialogRoundCheckBox, Theme.key_dialogBackground, Theme.key_dialogRoundCheckBoxCheck);
checkBox.setDrawUnchecked(false);
checkBox.setDrawBackgroundAsArc(4);
checkBox.setProgressDelegate(progress -> {
float scale = 1.0f - (1.0f - 0.857f) * checkBox.getProgress();
imageView.setScaleX(scale);
imageView.setScaleY(scale);
invalidate();
});
addView(checkBox, LayoutHelper.createFrame(24, 24, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 19, 42, 0, 0));
checkBox.setChecked(true, false);
setWillNotDraw(false);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(86), MeasureSpec.EXACTLY));
counterView.horizontalPadding = AndroidUtilities.dp(13);
counterView.counterDrawable.horizontalPadding = AndroidUtilities.dp(13);
}
public void update(int mask) {
@ -181,4 +201,25 @@ public class HintDialogCell extends FrameLayout {
}
return result;
}
@Override
protected void onDraw(Canvas canvas) {
if (drawCheckbox) {
int cx = imageView.getLeft() + imageView.getMeasuredWidth() / 2;
int cy = imageView.getTop() + imageView.getMeasuredHeight() / 2;
Theme.checkboxSquare_checkPaint.setColor(Theme.getColor(Theme.key_dialogRoundCheckBox));
Theme.checkboxSquare_checkPaint.setAlpha((int) (checkBox.getProgress() * 255));
canvas.drawCircle(cx, cy, AndroidUtilities.dp(28), Theme.checkboxSquare_checkPaint);
}
}
public void setChecked(boolean checked, boolean animated) {
if (drawCheckbox) {
checkBox.setChecked(checked, animated);
}
}
public long getDialogId() {
return dialog_id;
}
}

View file

@ -36,6 +36,9 @@ public class LanguageCell extends FrameLayout {
public LanguageCell(Context context, boolean dialog) {
super(context);
if (Theme.dividerPaint == null) {
Theme.createCommonResources(context);
}
setWillNotDraw(false);
isDialog = dialog;

View file

@ -9,16 +9,12 @@ import android.text.SpannableStringBuilder;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.style.ReplacementSpan;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.DownloadController;
import org.telegram.messenger.Emoji;
@ -206,7 +202,7 @@ public class SharedAudioCell extends FrameLayout implements DownloadController.F
currentMessageObject = messageObject;
TLRPC.Document document = messageObject.getDocument();
TLRPC.PhotoSize thumb = document != null ? FileLoader.getClosestPhotoSizeWithSize(document.thumbs, 240) : null;
TLRPC.PhotoSize thumb = document != null ? FileLoader.getClosestPhotoSizeWithSize(document.thumbs, 360) : null;
if (thumb instanceof TLRPC.TL_photoSize || thumb instanceof TLRPC.TL_photoSizeProgressive) {
radialProgress.setImageOverlay(thumb, document, messageObject);
} else {

View file

@ -77,7 +77,7 @@ public class StickerCell extends FrameLayout {
return clearsInputField;
}
public void setSticker(TLRPC.Document document, Object parent, int side) {
public void setSticker(TLRPC.Document document, Object parent) {
parentObject = parent;
if (document != null) {
TLRPC.PhotoSize thumb = FileLoader.getClosestPhotoSizeWithSize(document.thumbs, 90);
@ -103,19 +103,6 @@ public class StickerCell extends FrameLayout {
}
}
sticker = document;
if (side == -1) {
setBackgroundResource(R.drawable.stickers_back_left);
setPadding(AndroidUtilities.dp(7), 0, 0, 0);
} else if (side == 0) {
setBackgroundResource(R.drawable.stickers_back_center);
setPadding(0, 0, 0, 0);
} else if (side == 1) {
setBackgroundResource(R.drawable.stickers_back_right);
setPadding(0, 0, AndroidUtilities.dp(7), 0);
} else if (side == 2) {
setBackgroundResource(R.drawable.stickers_back_all);
setPadding(AndroidUtilities.dp(3), 0, AndroidUtilities.dp(3), 0);
}
Drawable background = getBackground();
if (background != null) {
background.setAlpha(230);

View file

@ -140,11 +140,11 @@ public class StickerEmojiCell extends FrameLayout {
SvgHelper.SvgDrawable svgThumb = DocumentObject.getSvgThumb(document, fromEmojiPanel ? Theme.key_emptyListPlaceholder : Theme.key_windowBackgroundGray, fromEmojiPanel ? 0.2f : 1.0f);
if (MessageObject.canAutoplayAnimatedSticker(document)) {
if (svgThumb != null) {
imageView.setImage(ImageLocation.getForDocument(document), "80_80", null, svgThumb, parentObject);
imageView.setImage(ImageLocation.getForDocument(document), "66_66", null, svgThumb, parentObject);
} else if (thumb != null) {
imageView.setImage(ImageLocation.getForDocument(document), "80_80", ImageLocation.getForDocument(thumb, document), null, 0, parentObject);
imageView.setImage(ImageLocation.getForDocument(document), "66_66", ImageLocation.getForDocument(thumb, document), null, 0, parentObject);
} else {
imageView.setImage(ImageLocation.getForDocument(document), "80_80", null, null, parentObject);
imageView.setImage(ImageLocation.getForDocument(document), "66_66", null, null, parentObject);
}
} else {
if (svgThumb != null) {

View file

@ -985,7 +985,7 @@ public class ChangePhoneActivity extends BaseFragment {
Intent mailer = new Intent(Intent.ACTION_SENDTO);
mailer.setData(Uri.parse("mailto:"));
mailer.putExtra(Intent.EXTRA_EMAIL, new String[]{"sms@stel.com"});
mailer.putExtra(Intent.EXTRA_EMAIL, new String[]{"reports@stel.com"});
mailer.putExtra(Intent.EXTRA_SUBJECT, "Android registration/login issue " + version + " " + emailPhone);
mailer.putExtra(Intent.EXTRA_TEXT, "Phone: " + requestPhone + "\nApp version: " + version + "\nOS version: SDK " + Build.VERSION.SDK_INT + "\nDevice Name: " + Build.MANUFACTURER + Build.MODEL + "\nLocale: " + Locale.getDefault() + "\nError: " + lastError);
getContext().startActivity(Intent.createChooser(mailer, "Send email..."));

View file

@ -1755,7 +1755,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio
if (viewBottom > height) {
viewBottom = viewTop + height;
}
messageCell.setVisiblePart(viewTop, viewBottom - viewTop, contentView.getHeightWithKeyboard() - AndroidUtilities.dp(48) - chatListView.getTop(), 0, view.getY() + actionBar.getMeasuredHeight() - contentView.getBackgroundTranslationY(), contentView.getBackgroundSizeY());
messageCell.setVisiblePart(viewTop, viewBottom - viewTop, contentView.getHeightWithKeyboard() - AndroidUtilities.dp(48) - chatListView.getTop(), 0, view.getY() + actionBar.getMeasuredHeight() - contentView.getBackgroundTranslationY(), contentView.getMeasuredWidth(), contentView.getBackgroundSizeY());
MessageObject messageObject = messageCell.getMessageObject();
if (roundVideoContainer != null && messageObject.isRoundVideo() && MediaController.getInstance().isPlayingMessage(messageObject)) {
@ -2518,7 +2518,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio
}
if (holder.itemView instanceof ChatMessageCell) {
((ChatMessageCell) view).setVisiblePart(viewTop, viewBottom - viewTop, contentView.getHeightWithKeyboard() - AndroidUtilities.dp(48) - chatListView.getTop(), 0, view.getY() + actionBar.getMeasuredHeight() - contentView.getBackgroundTranslationY(), contentView.getBackgroundSizeY());
((ChatMessageCell) view).setVisiblePart(viewTop, viewBottom - viewTop, contentView.getHeightWithKeyboard() - AndroidUtilities.dp(48) - chatListView.getTop(), 0, view.getY() + actionBar.getMeasuredHeight() - contentView.getBackgroundTranslationY(), contentView.getMeasuredWidth(), contentView.getBackgroundSizeY());
} else if (holder.itemView instanceof ChatActionCell) {
if (actionBar != null && contentView != null) {
((ChatActionCell) view).setVisiblePart(view.getY() + actionBar.getMeasuredHeight() - contentView.getBackgroundTranslationY(), contentView.getBackgroundSizeY());
@ -2750,7 +2750,9 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio
themeDescriptions.add(new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, Theme.chat_msgOutDrawable.getShadowDrawables(), null, Theme.key_chat_outBubbleShadow));
themeDescriptions.add(new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, Theme.chat_msgOutMediaDrawable.getShadowDrawables(), null, Theme.key_chat_outBubbleShadow));
themeDescriptions.add(new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgOutDrawable, Theme.chat_msgOutMediaDrawable}, null, Theme.key_chat_outBubble));
themeDescriptions.add(new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgOutDrawable, Theme.chat_msgOutMediaDrawable}, null, Theme.key_chat_outBubbleGradient));
themeDescriptions.add(new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgOutDrawable, Theme.chat_msgOutMediaDrawable}, null, Theme.key_chat_outBubbleGradient1));
themeDescriptions.add(new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgOutDrawable, Theme.chat_msgOutMediaDrawable}, null, Theme.key_chat_outBubbleGradient2));
themeDescriptions.add(new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgOutDrawable, Theme.chat_msgOutMediaDrawable}, null, Theme.key_chat_outBubbleGradient3));
themeDescriptions.add(new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgOutSelectedDrawable, Theme.chat_msgOutMediaSelectedDrawable}, null, Theme.key_chat_outBubbleSelected));
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));

View file

@ -499,8 +499,12 @@ public class ChannelCreateActivity extends BaseFragment implements NotificationC
avatarEditor.setAnimation(cameraDrawable);
cameraDrawable.setCurrentFrame(0);
}, dialog -> {
cameraDrawable.setCustomEndFrame(86);
avatarEditor.playAnimation();
if (!imageUpdater.isUploadingImage()) {
cameraDrawable.setCustomEndFrame(86);
avatarEditor.playAnimation();
} else {
cameraDrawable.setCurrentFrame(0, false);
}
});
cameraDrawable.setCurrentFrame(0);
cameraDrawable.setCustomEndFrame(43);

File diff suppressed because it is too large Load diff

View file

@ -592,8 +592,13 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image
cameraDrawable.setCurrentFrame(0);
setAvatarCell.imageView.playAnimation();
}, dialogInterface -> {
cameraDrawable.setCustomEndFrame(86);
setAvatarCell.imageView.playAnimation();
if (!imageUpdater.isUploadingImage()) {
cameraDrawable.setCustomEndFrame(86);
setAvatarCell.imageView.playAnimation();
} else {
cameraDrawable.setCurrentFrame(0, false);
}
});
cameraDrawable.setCurrentFrame(0);
cameraDrawable.setCustomEndFrame(43);
@ -1037,6 +1042,12 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image
} else {
avatarImage.setImage(ImageLocation.getForLocal(avatar), "50_50", avatarDrawable, currentChat);
setAvatarCell.setTextAndIcon(LocaleController.getString("ChatSetNewPhoto", R.string.ChatSetNewPhoto), R.drawable.menu_camera2, true);
if (cameraDrawable == null) {
cameraDrawable = new RLottieDrawable(R.raw.camera_outline, "" + R.raw.camera_outline, AndroidUtilities.dp(50), AndroidUtilities.dp(50), false, null);
}
setAvatarCell.imageView.setTranslationY(-AndroidUtilities.dp(9));
setAvatarCell.imageView.setTranslationX(-AndroidUtilities.dp(8));
setAvatarCell.imageView.setAnimation(cameraDrawable);
showAvatarProgress(true, false);
}
});

View file

@ -0,0 +1,671 @@
package org.telegram.ui;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.View;
import com.google.android.exoplayer2.util.Log;
import org.telegram.messenger.AccountInstance;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.DialogObject;
import org.telegram.messenger.ImageLocation;
import org.telegram.messenger.ImageReceiver;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MessagesController;
import org.telegram.messenger.NotificationCenter;
import org.telegram.messenger.R;
import org.telegram.messenger.UserConfig;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Components.AvatarDrawable;
import org.telegram.ui.Components.CounterView;
import org.telegram.ui.Components.CubicBezierInterpolator;
import java.util.ArrayList;
public class ChatPullingDownDrawable implements NotificationCenter.NotificationCenterDelegate {
public int dialogFolderId;
public int dialogFilterId;
int lastWidth;
float circleRadius;
Paint arrowPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
TextPaint textPaint2 = new TextPaint(Paint.ANTI_ALIAS_FLAG);
private Paint xRefPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
Path path = new Path();
StaticLayout chatNameLayout;
StaticLayout layout1;
StaticLayout layout2;
int chatNameWidth;
int layout1Width;
int layout2Width;
ImageReceiver imageReceiver = new ImageReceiver();
TLRPC.Chat nextChat;
AnimatorSet showReleaseAnimator;
float swipeToReleaseProgress;
float bounceProgress;
boolean animateSwipeToRelease;
boolean animateCheck;
float checkProgress;
long lastHapticTime;
float lastProgress;
boolean emptyStub;
float progressToBottomPannel;
private final View fragmentView;
boolean drawFolderBackground;
Runnable onAnimationFinishRunnable;
public long nextDialogId;
View parentView;
CounterView.CounterDrawable counterDrawable = new CounterView.CounterDrawable(null);
int params[] = new int[3];
private final int currentAccount;
private final int folderId;
private final int filterId;
private final long currentDialog;
public ChatPullingDownDrawable(int currentAccount, View fragmentView, long currentDialog, int folderId, int filterId) {
this.fragmentView = fragmentView;
this.currentAccount = currentAccount;
this.currentDialog = currentDialog;
this.folderId = folderId;
this.filterId = filterId;
arrowPaint.setStrokeWidth(AndroidUtilities.dpf2(2.8f));
arrowPaint.setStrokeCap(Paint.Cap.ROUND);
counterDrawable.gravity = Gravity.LEFT;
counterDrawable.setType(CounterView.CounterDrawable.TYPE_CHAT_PULLING_DOWN);
counterDrawable.circlePaint = Theme.chat_actionBackgroundPaint;
counterDrawable.textPaint = textPaint;
textPaint.setTextSize(AndroidUtilities.dp(13));
textPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
textPaint2.setTextSize(AndroidUtilities.dp(14));
xRefPaint.setColor(0xff000000);
xRefPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
updateDialog();
}
public void updateDialog() {
TLRPC.Dialog dialog = getNextUnreadDialog(currentDialog, folderId, filterId, true, params);
if (dialog != null) {
nextDialogId = dialog.id;
drawFolderBackground = params[0] == 1;
dialogFolderId = params[1];
dialogFilterId = params[2];
emptyStub = false;
nextChat = MessagesController.getInstance(currentAccount).getChat((int) -dialog.id);
if (nextChat == null) {
MessagesController.getInstance(currentAccount).getChat((int) dialog.id);
}
AvatarDrawable avatarDrawable = new AvatarDrawable();
avatarDrawable.setInfo(nextChat);
imageReceiver.setImage(ImageLocation.getForChat(nextChat, ImageLocation.TYPE_SMALL), "50_50", avatarDrawable, null, UserConfig.getInstance(0).getCurrentUser(), 0);
MessagesController.getInstance(currentAccount).ensureMessagesLoaded(dialog.id, 0, null);
counterDrawable.setCount(dialog.unread_count, false);
} else {
drawFolderBackground = false;
emptyStub = true;
}
}
public void setWidth(int width) {
if (width != lastWidth) {
circleRadius = AndroidUtilities.dp(56) / 2f;
lastWidth = width;
String nameStr = nextChat != null ? nextChat.title : LocaleController.getString("SwipeToGoNextChannelEnd", R.string.SwipeToGoNextChannelEnd);
chatNameWidth = (int) textPaint.measureText(nameStr);
chatNameWidth = Math.min(chatNameWidth, lastWidth - AndroidUtilities.dp(60));
chatNameLayout = new StaticLayout(nameStr, textPaint, chatNameWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
String str1 = null;
String str2 = null;
if (drawFolderBackground && dialogFolderId != folderId && dialogFolderId != 0) {
str1 = LocaleController.getString("SwipeToGoNextArchive", R.string.SwipeToGoNextArchive);
str2 = LocaleController.getString("ReleaseToGoNextArchive", R.string.ReleaseToGoNextArchive);
} else if (drawFolderBackground) {
str1 = LocaleController.getString("SwipeToGoNextFolder", R.string.SwipeToGoNextFolder);
str2 = LocaleController.getString("ReleaseToGoNextFolder", R.string.ReleaseToGoNextFolder);
} else {
str1 = LocaleController.getString("SwipeToGoNextChannel", R.string.SwipeToGoNextChannel);
str2 = LocaleController.getString("ReleaseToGoNextChannel", R.string.ReleaseToGoNextChannel);
}
layout1Width = (int) textPaint2.measureText(str1);
layout1Width = Math.min(layout1Width, lastWidth - AndroidUtilities.dp(60));
layout1 = new StaticLayout(str1, textPaint2, layout1Width, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
layout2Width = (int) textPaint2.measureText(str2);
layout2Width = Math.min(layout2Width, lastWidth - AndroidUtilities.dp(60));
layout2 = new StaticLayout(str2, textPaint2, layout2Width, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
float cx = lastWidth / 2f;
float cy = AndroidUtilities.dp(12) + circleRadius;
imageReceiver.setImageCoords(cx - AndroidUtilities.dp(40) / 2f, cy - AndroidUtilities.dp(40) / 2f, AndroidUtilities.dp(40), AndroidUtilities.dp(40));
imageReceiver.setRoundRadius((int) (AndroidUtilities.dp(40) / 2f));
counterDrawable.setSize(AndroidUtilities.dp(28), AndroidUtilities.dp(100));
}
}
public void draw(Canvas canvas, View parent, float progress, float alpha) {
this.parentView = parent;
counterDrawable.setParent(parent);
int oldAlpha, oldAlpha1, oldAlpha2, oldAlpha3;
float offset = AndroidUtilities.dp(110) * progress;
if (offset < AndroidUtilities.dp(8)) {
return;
}
if (progress < 0.2f) {
alpha *= progress * 5f;
}
Theme.applyServiceShaderMatrix(lastWidth, parent.getMeasuredHeight(), 0, parent.getMeasuredHeight() - offset);
textPaint.setColor(Theme.getColor(Theme.key_chat_serviceText));
arrowPaint.setColor(Theme.getColor(Theme.key_chat_serviceText));
textPaint2.setColor(Theme.getColor(Theme.key_chat_messagePanelHint));
oldAlpha = Theme.chat_actionBackgroundPaint.getAlpha();
oldAlpha1 = Theme.chat_actionBackgroundGradientDarkenPaint.getAlpha();
oldAlpha2 = textPaint.getAlpha();
oldAlpha3 = arrowPaint.getAlpha();
Theme.chat_actionBackgroundGradientDarkenPaint.setAlpha((int) (oldAlpha1 * alpha));
Theme.chat_actionBackgroundPaint.setAlpha((int) (oldAlpha * alpha));
textPaint.setAlpha((int) (oldAlpha2 * alpha));
imageReceiver.setAlpha(alpha);
if ((progress >= 1f && lastProgress < 1f) || (progress < 1f && lastProgress == 1f)) {
long time = System.currentTimeMillis();
if (time - lastHapticTime > 100) {
parent.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
lastHapticTime = time;
}
lastProgress = progress;
}
if (progress == 1f && !animateSwipeToRelease) {
animateSwipeToRelease = true;
animateCheck = true;
showReleaseState(true, parent);
} else if (progress != 1f && animateSwipeToRelease) {
animateSwipeToRelease = false;
showReleaseState(false, parent);
}
float cx = lastWidth / 2f;
float bounceOffset = bounceProgress * -AndroidUtilities.dp(4);
if (emptyStub) {
offset -= bounceOffset;
}
float widthRadius = Math.max(0, Math.min(circleRadius, offset / 2f - AndroidUtilities.dp(16) * progress - AndroidUtilities.dp(4)));
float widthRadius2 = Math.max(0, Math.min(circleRadius * progress, offset / 2f - AndroidUtilities.dp(8) * progress));
float size = (widthRadius2 * 2 - AndroidUtilities.dp2(16)) * (1f - swipeToReleaseProgress) + AndroidUtilities.dp(56) * swipeToReleaseProgress;
if (swipeToReleaseProgress < 1f || emptyStub) {
float bottom = -AndroidUtilities.dp(8) * (1f - swipeToReleaseProgress) + (-offset + AndroidUtilities.dp(56)) * swipeToReleaseProgress;
AndroidUtilities.rectTmp.set(cx - widthRadius, -offset, cx + widthRadius, bottom);
if (swipeToReleaseProgress > 0 && !emptyStub) {
float inset = AndroidUtilities.dp(16) * swipeToReleaseProgress;
AndroidUtilities.rectTmp.inset(inset, inset);
}
drawBackground(canvas, AndroidUtilities.rectTmp);
float arrowCy = -offset + AndroidUtilities.dp(24) + AndroidUtilities.dp(8) * (1f - progress) - AndroidUtilities.dp(36) * swipeToReleaseProgress;
canvas.save();
AndroidUtilities.rectTmp.inset(AndroidUtilities.dp(1), AndroidUtilities.dp(1));
canvas.clipRect(AndroidUtilities.rectTmp);
if (swipeToReleaseProgress > 0f) {
arrowPaint.setAlpha((int) ((1f - swipeToReleaseProgress) * 255));
}
drawArrow(canvas, cx, arrowCy, AndroidUtilities.dp(24) * progress);
if (emptyStub) {
float top = (-AndroidUtilities.dp(8) - AndroidUtilities.dp2(8) * progress - size) * (1f - swipeToReleaseProgress) + (-offset - AndroidUtilities.dp(2)) * swipeToReleaseProgress + bounceOffset;
arrowPaint.setAlpha(oldAlpha3);
drawCheck(canvas, cx, top + AndroidUtilities.dp(28));
}
canvas.restore();
}
if (chatNameLayout != null && swipeToReleaseProgress > 0) {
Theme.chat_actionBackgroundPaint.setAlpha((int) (oldAlpha * alpha));
textPaint.setAlpha((int) (oldAlpha2 * alpha));
float y = AndroidUtilities.dp(20) * (1f - swipeToReleaseProgress) - AndroidUtilities.dp(36) * swipeToReleaseProgress + bounceOffset;
AndroidUtilities.rectTmp.set((lastWidth - chatNameWidth) / 2f, y, lastWidth - (lastWidth - chatNameWidth) / 2f, y + chatNameLayout.getHeight());
AndroidUtilities.rectTmp.inset(-AndroidUtilities.dp(8), -AndroidUtilities.dp(4));
canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(15), AndroidUtilities.dp(15), Theme.chat_actionBackgroundPaint);
if (Theme.hasGradientService()) {
canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(15), AndroidUtilities.dp(15), Theme.chat_actionBackgroundGradientDarkenPaint);
}
canvas.save();
canvas.translate((lastWidth - chatNameWidth) / 2f, y);
chatNameLayout.draw(canvas);
canvas.restore();
}
if (!emptyStub && size > 0) {
float top = (-AndroidUtilities.dp(8) - AndroidUtilities.dp2(8) * progress - size) * (1f - swipeToReleaseProgress) + (-offset + AndroidUtilities.dp(4)) * swipeToReleaseProgress + bounceOffset;
imageReceiver.setRoundRadius((int) (size / 2f));
imageReceiver.setImageCoords(cx - size / 2f, top, size, size);
if (swipeToReleaseProgress > 0) {
canvas.saveLayerAlpha(imageReceiver.getImageX(), imageReceiver.getImageY(), imageReceiver.getImageX() + imageReceiver.getImageWidth(), imageReceiver.getImageY() + imageReceiver.getImageHeight(), 255, Canvas.ALL_SAVE_FLAG);
imageReceiver.draw(canvas);
canvas.scale(swipeToReleaseProgress, swipeToReleaseProgress, cx + AndroidUtilities.dp(12) + counterDrawable.getCenterX(), top - AndroidUtilities.dp(6) + AndroidUtilities.dp(14));
canvas.translate(cx + AndroidUtilities.dp(12), top - AndroidUtilities.dp(6));
counterDrawable.updateBackgroundRect();
counterDrawable.rectF.inset(-AndroidUtilities.dp(2), -AndroidUtilities.dp(2));
canvas.drawRoundRect(counterDrawable.rectF, counterDrawable.rectF.height() / 2f, counterDrawable.rectF.height() / 2f, xRefPaint);
canvas.restore();
canvas.save();
canvas.scale(swipeToReleaseProgress, swipeToReleaseProgress, cx + AndroidUtilities.dp(12) + counterDrawable.getCenterX(), top - AndroidUtilities.dp(6) + AndroidUtilities.dp(14));
canvas.translate(cx + AndroidUtilities.dp(12), top - AndroidUtilities.dp(6));
counterDrawable.draw(canvas);
canvas.restore();
} else {
imageReceiver.draw(canvas);
}
}
Theme.chat_actionBackgroundPaint.setAlpha(oldAlpha);
Theme.chat_actionBackgroundGradientDarkenPaint.setAlpha(oldAlpha1);
textPaint.setAlpha(oldAlpha2);
arrowPaint.setAlpha(oldAlpha3);
imageReceiver.setAlpha(1f);
}
private void drawCheck(Canvas canvas, float cx, float cy) {
if (!animateCheck) {
return;
}
if (checkProgress < 1f) {
checkProgress += 16 / 220f;
if (checkProgress > 1f) {
checkProgress = 1f;
}
}
float p1 = checkProgress > 0.5f ? 1f : checkProgress / 0.5f;
float p2 = checkProgress < 0.5f ? 0 : (checkProgress - 0.5f) / 0.5f;
canvas.save();
canvas.clipRect(AndroidUtilities.rectTmp);
canvas.translate(cx - AndroidUtilities.dp(24), cy - AndroidUtilities.dp(24));
float x1 = AndroidUtilities.dp(16);
float y1 = AndroidUtilities.dp(26);
float x2 = AndroidUtilities.dp(22);
float y2 = AndroidUtilities.dp(32);
float x3 = AndroidUtilities.dp(32);
float y3 = AndroidUtilities.dp(20);
canvas.drawLine(x1, y1, x1 * (1f - p1) + x2 * p1, y1 * (1f - p1) + y2 * p1, arrowPaint);
if (p2 > 0) {
canvas.drawLine(x2, y2, x2 * (1f - p2) + x3 * p2, y2 * (1f - p2) + y3 * p2, arrowPaint);
}
canvas.restore();
}
private void drawBackground(Canvas canvas, RectF rectTmp) {
if (drawFolderBackground) {
path.reset();
float roundRadius = rectTmp.width() * 0.2f;
float folderOffset = rectTmp.width() * 0.1f;
float folderOffset2 = rectTmp.width() * 0.03f;
float roundRadius2 = folderOffset / 2f;
float h = rectTmp.height() - folderOffset;
path.moveTo(rectTmp.right, rectTmp.top + roundRadius + folderOffset);
path.rQuadTo(0, -roundRadius, -roundRadius, -roundRadius);
path.rLineTo(-(rectTmp.width() - (2 * roundRadius)) / 2 + roundRadius2 * 2 - folderOffset2, 0);
path.rQuadTo(-roundRadius2 / 2, 0, -roundRadius2 * 2, -folderOffset / 2);
path.rQuadTo(-roundRadius2 / 2, -folderOffset / 2, -roundRadius2 * 2, -folderOffset / 2);
path.rLineTo(-(rectTmp.width() - (2 * roundRadius)) / 2 + roundRadius2 * 2 + folderOffset2, 0);
path.rQuadTo(-roundRadius, 0, -roundRadius, roundRadius);
path.rLineTo(0, (h + folderOffset - (2 * roundRadius)));
path.rQuadTo(0, roundRadius, roundRadius, roundRadius);
path.rLineTo((rectTmp.width() - (2 * roundRadius)), 0);
path.rQuadTo(roundRadius, 0, roundRadius, -roundRadius);
path.rLineTo(0, -(h - (2 * roundRadius)));
path.close();
canvas.drawPath(path, Theme.chat_actionBackgroundPaint);
if (Theme.hasGradientService()) {
canvas.drawPath(path, Theme.chat_actionBackgroundGradientDarkenPaint);
}
} else {
canvas.drawRoundRect(AndroidUtilities.rectTmp, circleRadius, circleRadius, Theme.chat_actionBackgroundPaint);
if (Theme.hasGradientService()) {
canvas.drawRoundRect(AndroidUtilities.rectTmp, circleRadius, circleRadius, Theme.chat_actionBackgroundGradientDarkenPaint);
}
}
}
private void showReleaseState(boolean show, View parent) {
if (showReleaseAnimator != null) {
showReleaseAnimator.removeAllListeners();
showReleaseAnimator.cancel();
}
if (show) {
ValueAnimator out = ValueAnimator.ofFloat(swipeToReleaseProgress, 1f);
out.addUpdateListener(animation -> {
swipeToReleaseProgress = (float) animation.getAnimatedValue();
parent.invalidate();
fragmentView.invalidate();
});
out.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT);
out.setDuration(250);
bounceProgress = 0;
ValueAnimator bounceUp = ValueAnimator.ofFloat(0f, 1f);
bounceUp.addUpdateListener(animation -> {
bounceProgress = (float) animation.getAnimatedValue();
parent.invalidate();
});
bounceUp.setInterpolator(CubicBezierInterpolator.EASE_BOTH);
bounceUp.setDuration(180);
ValueAnimator bounceDown = ValueAnimator.ofFloat(1f, -0.5f);
bounceDown.addUpdateListener(animation -> {
bounceProgress = (float) animation.getAnimatedValue();
parent.invalidate();
});
bounceDown.setInterpolator(CubicBezierInterpolator.EASE_BOTH);
bounceDown.setDuration(120);
ValueAnimator bounceOut = ValueAnimator.ofFloat(-0.5f, 0f);
bounceOut.addUpdateListener(animation -> {
bounceProgress = (float) animation.getAnimatedValue();
parent.invalidate();
});
bounceOut.setInterpolator(CubicBezierInterpolator.EASE_BOTH);
bounceOut.setDuration(100);
showReleaseAnimator = new AnimatorSet();
showReleaseAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
bounceProgress = 0f;
swipeToReleaseProgress = 1f;
parent.invalidate();
fragmentView.invalidate();
if (onAnimationFinishRunnable != null) {
onAnimationFinishRunnable.run();
onAnimationFinishRunnable = null;
}
}
});
AnimatorSet bounce = new AnimatorSet();
bounce.playSequentially(bounceUp, bounceDown, bounceOut);
showReleaseAnimator.playTogether(out, bounce);
showReleaseAnimator.start();
} else {
ValueAnimator out = ValueAnimator.ofFloat(swipeToReleaseProgress, 0f);
out.addUpdateListener(animation -> {
swipeToReleaseProgress = (float) animation.getAnimatedValue();
fragmentView.invalidate();
parent.invalidate();
});
out.setInterpolator(CubicBezierInterpolator.DEFAULT);
out.setDuration(220);
showReleaseAnimator = new AnimatorSet();
showReleaseAnimator.playTogether(out);
showReleaseAnimator.start();
}
}
private void drawArrow(Canvas canvas, float cx, float cy, float size) {
canvas.save();
float s = size / AndroidUtilities.dpf2(24);
canvas.scale(s, s, cx, cy - AndroidUtilities.dp(20));
canvas.translate(cx - AndroidUtilities.dp2(12), cy - AndroidUtilities.dp(12));
canvas.drawLine(AndroidUtilities.dpf2(12.5f), AndroidUtilities.dpf2(4f), AndroidUtilities.dpf2(12.5f), AndroidUtilities.dpf2(22), arrowPaint);
canvas.drawLine(AndroidUtilities.dpf2(3.5f), AndroidUtilities.dpf2(12), AndroidUtilities.dpf2(12.5f), AndroidUtilities.dpf2(3.5f), arrowPaint);
canvas.drawLine(AndroidUtilities.dpf2(25 - 3.5f), AndroidUtilities.dpf2(12), AndroidUtilities.dpf2(12.5f), AndroidUtilities.dpf2(3.5f), arrowPaint);
canvas.restore();
}
public void onAttach() {
imageReceiver.onAttachedToWindow();
NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.updateInterfaces);
}
public void onDetach() {
NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.updateInterfaces);
imageReceiver.onDetachedFromWindow();
lastProgress = 0;
lastHapticTime = 0;
}
@Override
public void didReceivedNotification(int id, int account, Object... args) {
if (nextDialogId !=0 ) {
TLRPC.Dialog dialog = MessagesController.getInstance(currentAccount).dialogs_dict.get(nextDialogId);
if (dialog != null) {
counterDrawable.setCount(dialog.unread_count, true);
if (parentView != null) {
parentView.invalidate();
}
}
}
}
public static TLRPC.Dialog getNextUnreadDialog(long currentDialogId, int folderId, int filterId) {
return getNextUnreadDialog(currentDialogId, folderId, filterId, true, null);
}
public static TLRPC.Dialog getNextUnreadDialog(long currentDialogId, int folderId, int filterId, boolean searchNext, int[] params) {
MessagesController messagesController = AccountInstance.getInstance(UserConfig.selectedAccount).getMessagesController();
if (params != null) {
params[0] = 0;
params[1] = folderId;
params[2] = filterId;
}
ArrayList<TLRPC.Dialog> dialogs = null;
if (filterId != 0) {
dialogs = messagesController.dialogFiltersById.get(filterId).dialogs;
} else {
dialogs = messagesController.getDialogs(folderId);
}
if (dialogs == null) {
return null;
}
for (int i = 0; i < dialogs.size(); i++) {
TLRPC.Dialog dialog = dialogs.get(i);
int lower_id = (int) dialog.id;
TLRPC.Chat chat = messagesController.getChat(-lower_id);
if (chat != null && dialog.id != currentDialogId && dialog.unread_count > 0 && DialogObject.isChannel(dialog) && !chat.megagroup && !messagesController.isPromoDialog(dialog.id, false)) {
return dialog;
}
}
if (searchNext) {
if (filterId != 0) {
for (int i = 0; i < messagesController.dialogFilters.size(); i++) {
int newFilterId = messagesController.dialogFilters.get(i).id;
if (filterId != newFilterId) {
TLRPC.Dialog dialog = getNextUnreadDialog(currentDialogId, folderId, newFilterId, false, params);
if (dialog != null) {
if (params != null) {
params[0] = 1;
}
return dialog;
}
}
}
}
for (int i = 0; i < messagesController.dialogsByFolder.size(); i++) {
int newFolderId = messagesController.dialogsByFolder.keyAt(i);
if (folderId != newFolderId) {
TLRPC.Dialog dialog = getNextUnreadDialog(currentDialogId, newFolderId, 0, false, params);
if (dialog != null) {
if (params != null) {
params[0] = 1;
}
return dialog;
}
}
}
}
return null;
}
public int getChatId() {
return nextChat.id;
}
public void drawBottomPanel(Canvas canvas, int top, int bottom, int width) {
if (showBottomPanel && progressToBottomPannel != 1f) {
progressToBottomPannel += 16f / 150f;
if (progressToBottomPannel > 1f) {
progressToBottomPannel = 1f;
} else {
fragmentView.invalidate();
}
} else if (!showBottomPanel && progressToBottomPannel != 0) {
progressToBottomPannel -= 16f / 150f;
if (progressToBottomPannel < 0) {
progressToBottomPannel = 0;
} else {
fragmentView.invalidate();
}
}
int oldAlpha = Theme.chat_composeBackgroundPaint.getAlpha();
int oldAlphaText = textPaint2.getAlpha();
Theme.chat_composeBackgroundPaint.setAlpha((int) (oldAlpha * progressToBottomPannel));
canvas.drawRect(0, top, width, bottom, Theme.chat_composeBackgroundPaint);
if (layout1 != null && swipeToReleaseProgress < 1f) {
textPaint2.setAlpha((int) (oldAlphaText * (1f - swipeToReleaseProgress) * progressToBottomPannel));
float y = top + AndroidUtilities.dp(18) - AndroidUtilities.dp(10) * swipeToReleaseProgress;
canvas.save();
canvas.translate((lastWidth - layout1Width) / 2f, y);
layout1.draw(canvas);
canvas.restore();
}
if (layout2 != null && swipeToReleaseProgress > 0) {
textPaint2.setAlpha((int) (oldAlphaText * swipeToReleaseProgress * progressToBottomPannel));
float y = top + AndroidUtilities.dp(18) + AndroidUtilities.dp(10) * (1f - swipeToReleaseProgress);
canvas.save();
canvas.translate((lastWidth - layout2Width) / 2f, y);
layout2.draw(canvas);
canvas.restore();
}
textPaint2.setAlpha(oldAlpha);
Theme.chat_composeBackgroundPaint.setAlpha(oldAlpha);
}
boolean showBottomPanel;
public void showBottomPanel(boolean b) {
showBottomPanel = b;
fragmentView.invalidate();
}
public boolean needDrawBottomPanel() {
return (showBottomPanel || progressToBottomPannel > 0) && !emptyStub;
}
public boolean animationIsRunning() {
return swipeToReleaseProgress != 1f;
}
public void runOnAnimationFinish(Runnable runnable) {
if (showReleaseAnimator != null) {
showReleaseAnimator.removeAllListeners();
showReleaseAnimator.cancel();
}
onAnimationFinishRunnable = runnable;
showReleaseAnimator = new AnimatorSet();
ValueAnimator out = ValueAnimator.ofFloat(swipeToReleaseProgress, 1f);
out.addUpdateListener(animation -> {
swipeToReleaseProgress = (float) animation.getAnimatedValue();
fragmentView.invalidate();
if (parentView != null) {
parentView.invalidate();
}
});
ValueAnimator bounceOut = ValueAnimator.ofFloat(bounceProgress, 0f);
bounceOut.addUpdateListener(animation -> {
bounceProgress = (float) animation.getAnimatedValue();
if (parentView != null) {
parentView.invalidate();
}
});
showReleaseAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
bounceProgress = 0f;
swipeToReleaseProgress = 1f;
if (parentView != null) {
parentView.invalidate();
}
fragmentView.invalidate();
if (onAnimationFinishRunnable != null) {
onAnimationFinishRunnable.run();
onAnimationFinishRunnable = null;
}
}
});
showReleaseAnimator.playTogether(out, bounceOut);
showReleaseAnimator.setDuration(120);
showReleaseAnimator.setInterpolator(CubicBezierInterpolator.DEFAULT);
showReleaseAnimator.start();
}
public void reset() {
checkProgress = 0;
animateCheck = false;
}
}

View file

@ -1355,7 +1355,7 @@ public class AudioPlayerAlert extends BottomSheet implements NotificationCenter.
if (message != null) {
SendMessagesHelper.getInstance(currentAccount).sendMessage(message.toString(), did, null, null, null, true, null, null, null, true, 0, null);
}
SendMessagesHelper.getInstance(currentAccount).sendMessage(fmessages, did, true, 0);
SendMessagesHelper.getInstance(currentAccount).sendMessage(fmessages, did, false, false, true, 0);
}
fragment1.finishFragment();
} else {
@ -1935,7 +1935,7 @@ public class AudioPlayerAlert extends BottomSheet implements NotificationCenter.
private ImageLocation getArtworkThumbImageLocation(MessageObject messageObject) {
final TLRPC.Document document = messageObject.getDocument();
TLRPC.PhotoSize thumb = document != null ? FileLoader.getClosestPhotoSizeWithSize(document.thumbs, 240) : null;
TLRPC.PhotoSize thumb = document != null ? FileLoader.getClosestPhotoSizeWithSize(document.thumbs, 360) : null;
if (!(thumb instanceof TLRPC.TL_photoSize) && !(thumb instanceof TLRPC.TL_photoSizeProgressive)) {
thumb = null;
}

View file

@ -381,7 +381,10 @@ public class AvatarDrawable extends Drawable {
Theme.avatarDrawables[1].draw(canvas);
} else {
if (textLayout != null) {
float scale = size / (float) AndroidUtilities.dp(50);
canvas.scale(scale, scale, size / 2f, size / 2f) ;
canvas.translate((size - textWidth) / 2 - textLeft, (size - textHeight) / 2);
textLayout.draw(canvas);
}
}

View file

@ -20,6 +20,7 @@ public class BlurBehindDrawable {
DispatchQueue queue;
private final int type;
public static final int TAG_DRAWING_AS_BACKGROUND = (1 << 26) + 3;
public static final int STATIC_CONTENT = 0;
@ -41,6 +42,7 @@ public class BlurBehindDrawable {
private float blurAlpha;
private boolean show;
private boolean error;
private boolean animateAlpha = true;
private final float DOWN_SCALE = 6f;
private int lastH;
@ -57,7 +59,8 @@ public class BlurBehindDrawable {
Paint emptyPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
Paint errorBlackoutPaint = new Paint();
public BlurBehindDrawable(View behindView, View parentView) {
public BlurBehindDrawable(View behindView, View parentView, int type) {
this.type = type;
this.behindView = behindView;
this.parentView = parentView;
@ -66,8 +69,12 @@ public class BlurBehindDrawable {
public void draw(Canvas canvas) {
if (type == 1 && !wasDraw && !animateAlpha) {
generateBlurredBitmaps();
invalidate = false;
}
final Bitmap[] bitmap = renderingBitmap;
if (bitmap != null || error) {
if ((bitmap != null || error) && animateAlpha) {
if (show && blurAlpha != 1f) {
blurAlpha += 0.09f;
if (blurAlpha > 1f) {
@ -83,18 +90,28 @@ public class BlurBehindDrawable {
}
}
float alpha = animateAlpha ? blurAlpha : 1f;
if (bitmap == null && error) {
errorBlackoutPaint.setAlpha((int) (50 * blurAlpha));
errorBlackoutPaint.setAlpha((int) (50 * alpha));
canvas.drawPaint(errorBlackoutPaint);
return;
}
canvas.saveLayerAlpha(0, 0, parentView.getMeasuredWidth(), parentView.getMeasuredHeight(), (int) (blurAlpha * 255), ALL_SAVE_FLAG);
if (alpha == 1f) {
canvas.save();
} else {
canvas.saveLayerAlpha(0, 0, parentView.getMeasuredWidth(), parentView.getMeasuredHeight(), (int) (alpha * 255), ALL_SAVE_FLAG);
}
if (bitmap != null) {
emptyPaint.setAlpha((int) (255 * blurAlpha));
emptyPaint.setAlpha((int) (255 * alpha));
if (type == 1) {
canvas.translate(0, panTranslationY);
}
canvas.drawBitmap(bitmap[1], 0, 0, null);
canvas.save();
canvas.translate(0, panTranslationY);
if (type == 0) {
canvas.translate(0, panTranslationY);
}
canvas.drawBitmap(bitmap[0], 0, 0, null);
canvas.restore();
wasDraw = true;
@ -126,8 +143,12 @@ public class BlurBehindDrawable {
});
return;
}
} else {
blurredBitmapTmp[i].eraseColor(Color.TRANSPARENT);
}
if (i == 1) {
blurredBitmapTmp[i].eraseColor(Theme.getColor(Theme.key_windowBackgroundWhite));
}
blurCanvas[i].save();
blurCanvas[i].scale(1f / DOWN_SCALE, 1f / DOWN_SCALE, 0, 0);
Drawable backDrawable = behindView.getBackground();
@ -220,7 +241,7 @@ public class BlurBehindDrawable {
}
public boolean isFullyDrawing() {
return !skipDraw && wasDraw && blurAlpha == 1f && show;
return !skipDraw && wasDraw && (blurAlpha == 1f || !animateAlpha) && show && parentView.getAlpha() == 1f;
}
public void checkSizes() {
@ -228,6 +249,22 @@ public class BlurBehindDrawable {
if (bitmap == null || parentView.getMeasuredHeight() == 0 || parentView.getMeasuredWidth() == 0) {
return;
}
generateBlurredBitmaps();
lastH = parentView.getMeasuredHeight();
lastW = parentView.getMeasuredWidth();
}
private void generateBlurredBitmaps() {
Bitmap[] bitmap = renderingBitmap;
if (bitmap == null) {
bitmap = renderingBitmap = new Bitmap[2];
renderingBitmapCanvas = new Canvas[2];
}
if (blurredBitmapTmp == null) {
blurredBitmapTmp = new Bitmap[2];
blurCanvas = new Canvas[2];
}
blurBackgroundTask.canceled = true;
blurBackgroundTask = new BlurBackgroundTask();
@ -237,17 +274,20 @@ public class BlurBehindDrawable {
toolbarH = AndroidUtilities.statusBarHeight + AndroidUtilities.dp(100);
int h = i == 0 ? toolbarH : lastH;
if (bitmap[i].getHeight() != h || bitmap[i].getWidth() != parentView.getMeasuredWidth()) {
if (bitmap[i] == null || bitmap[i].getHeight() != h || bitmap[i].getWidth() != parentView.getMeasuredWidth()) {
if (queue != null) {
queue.cleanupQueue();
}
blurredBitmapTmp[i] = Bitmap.createBitmap((int) (lastW / DOWN_SCALE), (int) (h / DOWN_SCALE), Bitmap.Config.ARGB_8888);
if (i == 1) {
blurredBitmapTmp[i].eraseColor(Theme.getColor(Theme.key_windowBackgroundWhite));
}
blurCanvas[i] = new Canvas(blurredBitmapTmp[i]);
renderingBitmap[i] = Bitmap.createBitmap(lastW, i == 0 ? toolbarH : lastH, Bitmap.Config.ARGB_8888);
renderingBitmapCanvas[i] = new Canvas(renderingBitmap[i]);
renderingBitmapCanvas[i].scale(DOWN_SCALE, DOWN_SCALE);
renderingBitmapCanvas[i].scale((float) renderingBitmap[i].getWidth() / (float) blurredBitmapTmp[i].getWidth(), (float) renderingBitmap[i].getHeight() / (float) blurredBitmapTmp[i].getHeight());
blurCanvas[i].save();
blurCanvas[i].scale(1f / DOWN_SCALE, 1f / DOWN_SCALE, 0, 0);
@ -274,18 +314,22 @@ public class BlurBehindDrawable {
Utilities.stackBlurBitmap(blurredBitmapTmp[i], getBlurRadius());
emptyPaint.setAlpha(255);
if (i == 1) {
renderingBitmap[i].eraseColor(Theme.getColor(Theme.key_windowBackgroundWhite));
}
renderingBitmapCanvas[i].drawBitmap(blurredBitmapTmp[i], 0, 0, emptyPaint);
}
}
lastH = parentView.getMeasuredHeight();
lastW = parentView.getMeasuredWidth();
}
public void show(boolean show) {
this.show = show;
}
public void setAnimateAlpha(boolean animateAlpha) {
this.animateAlpha = animateAlpha;
}
public void onPanTranslationUpdate(float y) {
panTranslationY = y;
parentView.invalidate();
@ -317,13 +361,19 @@ public class BlurBehindDrawable {
try {
backgroundBitmap[i] = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
backgroundBitmapCanvas[i] = new Canvas(backgroundBitmap[i]);
backgroundBitmapCanvas[i].scale(DOWN_SCALE, DOWN_SCALE);
backgroundBitmapCanvas[i].scale(width / (float) blurredBitmapTmp[i].getWidth(), h / (float) blurredBitmapTmp[i].getHeight());
} catch (Throwable e) {
FileLog.e(e);
}
}
if (i == 1) {
backgroundBitmap[i].eraseColor(Theme.getColor(Theme.key_windowBackgroundWhite));
} else {
backgroundBitmap[i].eraseColor(Color.TRANSPARENT);
}
emptyPaint.setAlpha(255);
Utilities.stackBlurBitmap(blurredBitmapTmp[i], getBlurRadius());
if (backgroundBitmapCanvas[i] != null) {
backgroundBitmapCanvas[i].drawBitmap(blurredBitmapTmp[i], 0, 0, emptyPaint);
}

View file

@ -0,0 +1,36 @@
package org.telegram.ui.Components;
import android.content.Context;
import android.graphics.Canvas;
import android.view.View;
public class BluredView extends View {
public final BlurBehindDrawable drawable;
public BluredView(Context context, View parentView) {
super(context);
drawable = new BlurBehindDrawable(parentView, this, 1);
drawable.setAnimateAlpha(false);
drawable.show(true);
}
@Override
protected void onDraw(Canvas canvas) {
drawable.draw(canvas);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
drawable.checkSizes();
}
public void update() {
drawable.invalidate();
}
public boolean fullyDrawing() {
return drawable.isFullyDrawing() && getVisibility() == View.VISIBLE;
}
}

View file

@ -243,6 +243,9 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
public BotCommandsMenuContainer botCommandsMenuContainer;
private BotCommandsMenuView.BotCommandsAdapter botCommandsAdapter;
private ValueAnimator searchAnimator;
private float searchToOpenProgress;
private HashMap<View, Float> animationParamsX = new HashMap<>();
private class SeekBarWaveformView extends View {
@ -379,6 +382,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
private ReplaceableIconDrawable botButtonDrawable;
private CharSequence draftMessage;
private boolean draftSearchWebpage;
private boolean isPaste;
@ -502,7 +506,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
emojiTabOpen = curPage == 0;
if (stickersExpanded) {
if (searchingType != 0) {
searchingType = curPage == 0 ? 2 : 1;
setSearchingTypeInternal(curPage == 0 ? 2 : 1, true);
checkStickresExpandHeight();
} else if (!stickersTabOpen) {
setStickersExpanded(false, true, false);
@ -1742,7 +1746,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
emojiView.onOpen(messageEditText.length() > 0);
} else {
if (searchingType != 0) {
searchingType = 0;
setSearchingTypeInternal(0, true);
if (emojiView != null) {
emojiView.closeSearch(false);
}
@ -1809,11 +1813,14 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
return false;
}
}
if (isInScheduleMode()) {
AlertsCreator.createScheduleDatePickerDialog(parentActivity, parentFragment.getDialogId(), (notify, scheduleDate) -> send(inputContentInfo, notify, scheduleDate));
if (inputContentInfo.getDescription().hasMimeType("image/gif") || SendMessagesHelper.shouldSendWebPAsSticker(null, inputContentInfo.getContentUri())) {
if (isInScheduleMode()) {
AlertsCreator.createScheduleDatePickerDialog(parentActivity, parentFragment.getDialogId(), (notify, scheduleDate) -> send(inputContentInfo, notify, scheduleDate));
} else {
send(inputContentInfo, true, 0);
}
} else {
send(inputContentInfo, true, 0);
editPhoto(inputContentInfo.getContentUri(), inputContentInfo.getDescription().getMimeType(0));
}
return true;
};
@ -1832,7 +1839,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
if (isPopupShowing() && event.getAction() == MotionEvent.ACTION_DOWN) {
boolean rez = false;
if (searchingType != 0) {
searchingType = 0;
setSearchingTypeInternal(0, false);
emojiView.closeSearch(false);
requestFocus();
rez = true;
@ -1907,70 +1914,76 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
ClipData clipData = clipboard.getPrimaryClip();
if (clipData != null) {
if (clipData.getItemCount() == 1 && clipData.getDescription().hasMimeType("image/*")) {
final File file = AndroidUtilities.generatePicturePath(fragment.isSecretChat(), MimeTypeMap.getSingleton().getExtensionFromMimeType(clipData.getDescription().getMimeType(0)));
Uri uri = clipData.getItemAt(0).getUri();
Utilities.globalQueue.postRunnable(() -> {
try {
InputStream in = context.getContentResolver().openInputStream(uri);
FileOutputStream fos = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int lengthRead;
while ((lengthRead = in.read(buffer)) > 0) {
fos.write(buffer, 0, lengthRead);
fos.flush();
}
in.close();
fos.close();
MediaController.PhotoEntry photoEntry = new MediaController.PhotoEntry(0, -1, 0, file.getAbsolutePath(), 0, false, 0, 0, 0);
ArrayList<Object> entries = new ArrayList<>();
entries.add(photoEntry);
AndroidUtilities.runOnUIThread(() -> {
PhotoViewer.getInstance().setParentActivity(parentActivity);
PhotoViewer.getInstance().openPhotoForSelect(entries, 0, 2, false, new PhotoViewer.EmptyPhotoViewerProvider() {
boolean sending;
@Override
public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo, boolean notify, int scheduleDate, boolean forceDocument) {
ArrayList<SendMessagesHelper.SendingMediaInfo> photos = new ArrayList<>();
SendMessagesHelper.SendingMediaInfo info = new SendMessagesHelper.SendingMediaInfo();
if (!photoEntry.isVideo && photoEntry.imagePath != null) {
info.path = photoEntry.imagePath;
} else if (photoEntry.path != null) {
info.path = photoEntry.path;
}
info.thumbPath = photoEntry.thumbPath;
info.isVideo = photoEntry.isVideo;
info.caption = photoEntry.caption != null ? photoEntry.caption.toString() : null;
info.entities = photoEntry.entities;
info.masks = photoEntry.stickers;
info.ttl = photoEntry.ttl;
info.videoEditedInfo = videoEditedInfo;
info.canDeleteAfter = true;
photos.add(info);
photoEntry.reset();
sending = true;
SendMessagesHelper.prepareSendingMedia(accountInstance, photos, dialog_id, replyingMessageObject, getThreadMessage(), null, false, false, editingMessageObject, notify, scheduleDate);
}
@Override
public void willHidePhotoViewer() {
if (!sending) {
try {
file.delete();
} catch (Throwable ignore) {
}
}
}
}, parentFragment);
});
} catch (Throwable e) {
e.printStackTrace();
}
});
editPhoto(clipData.getItemAt(0).getUri(), clipData.getDescription().getMimeType(0));
}
}
return super.onTextContextMenuItem(id);
}
private void editPhoto(Uri uri, String mime) {
final File file = AndroidUtilities.generatePicturePath(fragment.isSecretChat(), MimeTypeMap.getSingleton().getExtensionFromMimeType(mime));
Utilities.globalQueue.postRunnable(() -> {
try {
InputStream in = context.getContentResolver().openInputStream(uri);
FileOutputStream fos = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int lengthRead;
while ((lengthRead = in.read(buffer)) > 0) {
fos.write(buffer, 0, lengthRead);
fos.flush();
}
in.close();
fos.close();
MediaController.PhotoEntry photoEntry = new MediaController.PhotoEntry(0, -1, 0, file.getAbsolutePath(), 0, false, 0, 0, 0);
ArrayList<Object> entries = new ArrayList<>();
entries.add(photoEntry);
AndroidUtilities.runOnUIThread(() -> {
PhotoViewer.getInstance().setParentActivity(parentActivity);
PhotoViewer.getInstance().openPhotoForSelect(entries, 0, 2, false, new PhotoViewer.EmptyPhotoViewerProvider() {
boolean sending;
@Override
public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo, boolean notify, int scheduleDate, boolean forceDocument) {
ArrayList<SendMessagesHelper.SendingMediaInfo> photos = new ArrayList<>();
SendMessagesHelper.SendingMediaInfo info = new SendMessagesHelper.SendingMediaInfo();
if (!photoEntry.isVideo && photoEntry.imagePath != null) {
info.path = photoEntry.imagePath;
} else if (photoEntry.path != null) {
info.path = photoEntry.path;
}
info.thumbPath = photoEntry.thumbPath;
info.isVideo = photoEntry.isVideo;
info.caption = photoEntry.caption != null ? photoEntry.caption.toString() : null;
info.entities = photoEntry.entities;
info.masks = photoEntry.stickers;
info.ttl = photoEntry.ttl;
info.videoEditedInfo = videoEditedInfo;
info.canDeleteAfter = true;
photos.add(info);
photoEntry.reset();
sending = true;
SendMessagesHelper.prepareSendingMedia(accountInstance, photos, dialog_id, replyingMessageObject, getThreadMessage(), null, false, false, editingMessageObject, notify, scheduleDate);
if (delegate != null) {
delegate.onMessageSend(null, true, scheduleDate);
}
}
@Override
public void willHidePhotoViewer() {
if (!sending) {
try {
file.delete();
} catch (Throwable ignore) {
}
}
}
}, parentFragment);
});
} catch (Throwable e) {
e.printStackTrace();
}
});
}
};
messageEditText.setDelegate(() -> {
if (delegate != null) {
@ -2012,7 +2025,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
preferences.edit().putInt("hidekeyboard_" + dialog_id, botButtonsMessageObject.getId()).commit();
}
if (searchingType != 0) {
searchingType = 0;
setSearchingTypeInternal(0, true);
if (emojiView != null) {
emojiView.closeSearch(true);
}
@ -2305,7 +2318,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
attachLayout.addView(botButton, LayoutHelper.createLinear(48, 48));
botButton.setOnClickListener(v -> {
if (searchingType != 0) {
searchingType = 0;
setSearchingTypeInternal(0, false);
emojiView.closeSearch(false);
messageEditText.requestFocus();
}
@ -2921,7 +2934,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
}
if (stickersExpanded) {
if (searchingType != 0) {
searchingType = 0;
setSearchingTypeInternal(0, true);
emojiView.closeSearch(true);
emojiView.hideSearchKeyboard();
if (emojiTabOpen) {
@ -3699,7 +3712,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
hideKeyboardRunnable = null;
}
int visibility = getVisibility();
if (showKeyboardOnResume) {
if (showKeyboardOnResume && parentFragment.isLastFragment()) {
showKeyboardOnResume = false;
if (searchingType == 0) {
messageEditText.requestFocus();
@ -4167,7 +4180,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
return;
}
if (searchingType != 0) {
searchingType = 0;
setSearchingTypeInternal(0, true);
emojiView.closeSearch(false);
if (stickersExpanded) {
@ -5805,7 +5818,9 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
}
if (draftMessage == null && !hadEditingMessage) {
draftMessage = messageEditText.length() > 0 ? messageEditText.getText() : null;
draftSearchWebpage = messageWebPageSearch;
}
messageWebPageSearch = editingMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage;
if (!keyboardVisible) {
AndroidUtilities.runOnUIThread(setTextFieldRunnable = () -> {
setFieldText(textToSetWithKeyboard);
@ -5896,6 +5911,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
scheduledButton.setVisibility(VISIBLE);
}
messageEditText.setText(draftMessage);
messageWebPageSearch = draftSearchWebpage;
messageEditText.setSelection(messageEditText.length());
if (getVisibility() == VISIBLE) {
delegate.onAttachButtonShow();
@ -6433,7 +6449,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
if (emojiView != null) {
return;
}
emojiView = new EmojiView(allowStickers, allowGifs, parentActivity, true, info) {
emojiView = new EmojiView(allowStickers, allowGifs, getContext(), true, info, sizeNotifierLayout) {
@Override
public void setTranslationY(float translationY) {
super.setTranslationY(translationY);
@ -6443,6 +6459,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
}
};
emojiView.setVisibility(GONE);
emojiView.setShowing(false);
emojiView.setDelegate(new EmojiView.EmojiViewDelegate() {
@Override
@ -6487,7 +6504,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
}
if (stickersExpanded) {
if (searchingType != 0) {
searchingType = 0;
setSearchingTypeInternal(0, true);
emojiView.closeSearch(true, MessageObject.getStickerSetId(sticker));
emojiView.hideSearchKeyboard();
}
@ -6550,7 +6567,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
SendMessagesHelper.prepareSendingBotContextResult(accountInstance, result, params, dialog_id, replyingMessageObject, getThreadMessage(), notify, scheduleDate);
if (searchingType != 0) {
searchingType = 0;
setSearchingTypeInternal(0, true);
emojiView.closeSearch(true);
emojiView.hideSearchKeyboard();
}
@ -6621,13 +6638,9 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
@Override
public void onSearchOpenClose(int type) {
searchingType = type;
setSearchingTypeInternal(type, true);
if (type != 0) {
// expandStickersWithKeyboard = true;
// if (expandStickersWithKeyboard) {
// expandStickersWithKeyboard = false;
setStickersExpanded(true, true, false);
// }
}
if (emojiTabOpen && searchingType == 2) {
checkStickresExpandHeight();
@ -6659,6 +6672,11 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
return dialog_id;
}
@Override
public int getThreadId() {
return getThreadMessageId();
}
@Override
public void showTrendingStickersAlert(TrendingStickersLayout layout) {
if (parentActivity != null && parentFragment != null) {
@ -6680,6 +6698,16 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
trendingStickersAlert.show();
}
}
@Override
public void invalidateEnterView() {
invalidate();
}
@Override
public float getProgressToSearchOpened() {
return searchToOpenProgress;
}
});
emojiView.setDragListener(new EmojiView.DragListener() {
@ -6752,7 +6780,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
return stickersTabOpen && !(!stickersExpanded && messageEditText.length() > 0) && emojiView.areThereAnyStickers() && !waitingForKeyboardOpen;
}
});
sizeNotifierLayout.addView(emojiView, sizeNotifierLayout.getChildCount() - 1);
sizeNotifierLayout.addView(emojiView, sizeNotifierLayout.getChildCount() - 5);
checkChannelRights();
}
@ -6768,7 +6796,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
return;
}
if (searchingType != 0) {
searchingType = 0;
setSearchingTypeInternal(0, true);
emojiView.closeSearch(true);
emojiView.hideSearchKeyboard();
}
@ -6803,6 +6831,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
if (!emojiViewVisible && emojiView != null && emojiView.getVisibility() != GONE) {
sizeNotifierLayout.removeView(emojiView);
emojiView.setVisibility(GONE);
emojiView.setShowing(false);
}
}
@ -6828,7 +6857,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
int previusHeight = 0;
if (contentType == 0) {
if (emojiView.getParent() == null) {
sizeNotifierLayout.addView(emojiView, sizeNotifierLayout.getChildCount() - 1);
sizeNotifierLayout.addView(emojiView, sizeNotifierLayout.getChildCount() - 5);
}
samePannelWasVisible = emojiViewVisible && emojiView.getVisibility() == View.VISIBLE;
emojiView.setVisibility(VISIBLE);
@ -6839,6 +6868,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
anotherPanelWasVisible = true;
previusHeight = botKeyboardView.getMeasuredHeight();
}
emojiView.setShowing(true);
currentView = emojiView;
animatingContentType = 0;
} else if (contentType == 1) {
@ -6847,6 +6877,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
if (emojiView != null && emojiView.getVisibility() != GONE) {
sizeNotifierLayout.removeView(emojiView);
emojiView.setVisibility(GONE);
emojiView.setShowing(false);
emojiViewVisible = false;
anotherPanelWasVisible = true;
previusHeight = emojiView.getMeasuredHeight();
@ -6918,6 +6949,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
if (emojiViewVisible = true) {
animatingContentType = 0;
}
emojiView.setShowing(false);
panelAnimation = new AnimatorSet();
panelAnimation.playTogether(ObjectAnimator.ofFloat(emojiView, View.TRANSLATION_Y, emojiView.getMeasuredHeight()));
panelAnimation.setInterpolator(AdjustPanLayoutHelper.keyboardInterpolator);
@ -6955,6 +6987,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
emojiPadding = 0;
sizeNotifierLayout.removeView(emojiView);
emojiView.setVisibility(GONE);
emojiView.setShowing(false);
}
} else {
removeEmojiViewAfterAnimation = false;
@ -6966,7 +6999,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
}
emojiViewVisible = false;
}
if (botKeyboardView != null) {
if (botKeyboardView != null && botKeyboardView.getVisibility() == View.VISIBLE) {
if (show != 2 || AndroidUtilities.usingHardwareInput || AndroidUtilities.isInMultiwindow) {
if (smoothKeyboard && !keyboardVisible) {
if (botKeyboardViewVisible) {
@ -7124,7 +7157,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
preferences.edit().putInt("hidekeyboard_" + dialog_id, botButtonsMessageObject.getId()).commit();
}
if (byBackButton && searchingType != 0) {
searchingType = 0;
setSearchingTypeInternal(0, true);
if (emojiView != null) {
emojiView.closeSearch(true);
}
@ -7135,7 +7168,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
}
} else {
if (searchingType != 0) {
searchingType = 0;
setSearchingTypeInternal(0, false);
emojiView.closeSearch(false);
messageEditText.requestFocus();
}
@ -7144,6 +7177,45 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
}
}
private void setSearchingTypeInternal(int searchingType, boolean animated) {
boolean showSearchingNew = searchingType != 0;
boolean showSearchingOld = this.searchingType != 0;
if (showSearchingNew != showSearchingOld) {
if (searchAnimator != null) {
searchAnimator.removeAllListeners();
searchAnimator.cancel();
}
if (!animated) {
searchToOpenProgress = showSearchingNew ? 1f : 0f;
if (emojiView != null) {
emojiView.searchProgressChanged();
}
} else {
searchAnimator = ValueAnimator.ofFloat(searchToOpenProgress, showSearchingNew ? 1f : 0f);
searchAnimator.addUpdateListener(valueAnimator -> {
searchToOpenProgress = (float) valueAnimator.getAnimatedValue();
if (emojiView != null) {
emojiView.searchProgressChanged();
}
});
searchAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
searchToOpenProgress = showSearchingNew ? 1f : 0f;
if (emojiView != null) {
emojiView.searchProgressChanged();
}
}
});
searchAnimator.setDuration(220);
searchAnimator.setInterpolator(CubicBezierInterpolator.DEFAULT);
searchAnimator.start();
}
}
this.searchingType = searchingType;
}
private void openKeyboardInternal() {
showPopup(AndroidUtilities.usingHardwareInput || AndroidUtilities.isInMultiwindow || parentFragment != null && parentFragment.isInBubbleMode() || isPaused ? 0 : 2, 0);
messageEditText.requestFocus();
@ -7206,7 +7278,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (w != oldw && stickersExpanded) {
searchingType = 0;
setSearchingTypeInternal(0, false);
emojiView.closeSearch(false);
setStickersExpanded(false, false, false);
}
@ -7349,6 +7421,9 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
if (botKeyboardView != null) {
botKeyboardView.invalidateViews();
}
if (messageEditText != null) {
messageEditText.postInvalidate();
}
} else if (id == NotificationCenter.recordProgressChanged) {
int guid = (Integer) args[0];
if (guid != recordingGuid) {
@ -8404,4 +8479,17 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
AndroidUtilities.cancelRunOnUIThread(runEmojiPanelAnimation);
runEmojiPanelAnimation.run();
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (emojiView == null || emojiView.getVisibility() != View.VISIBLE || emojiView.getStickersExpandOffset() == 0) {
super.dispatchDraw(canvas);
} else {
canvas.save();
canvas.clipRect(0, AndroidUtilities.dp(2), getMeasuredWidth(), getMeasuredHeight());
canvas.translate(0, -emojiView.getStickersExpandOffset());
super.dispatchDraw(canvas);
canvas.restore();
}
}
}

View file

@ -13,6 +13,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
@ -53,7 +54,7 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent
private ImageView timeItem;
private TimerDrawable timerDrawable;
private ChatActivity parentFragment;
private StatusDrawable[] statusDrawables = new StatusDrawable[5];
private StatusDrawable[] statusDrawables = new StatusDrawable[6];
private AvatarDrawable avatarDrawable = new AvatarDrawable();
private int currentAccount = UserConfig.selectedAccount;
private boolean occupyStatusBar = true;
@ -156,6 +157,7 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent
statusDrawables[2] = new SendingFileDrawable(false);
statusDrawables[3] = new PlayingGameDrawable(false);
statusDrawables[4] = new RoundStatusDrawable(false);
statusDrawables[5] = new ChoosingStickerStatusDrawable(false);
for (int a = 0; a < statusDrawables.length; a++) {
statusDrawables[a].setIsChat(chat != null);
}
@ -380,8 +382,14 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent
private void setTypingAnimation(boolean start) {
if (start) {
try {
Integer type = MessagesController.getInstance(currentAccount).getPrintingStringType(parentFragment.getDialogId(), parentFragment.getThreadId());
subtitleTextView.setLeftDrawable(statusDrawables[type]);
int type = MessagesController.getInstance(currentAccount).getPrintingStringType(parentFragment.getDialogId(), parentFragment.getThreadId());
if (type == 5) {
subtitleTextView.replaceTextWithDrawable(statusDrawables[type], "**oo**");
subtitleTextView.setLeftDrawable(null);
} else {
subtitleTextView.replaceTextWithDrawable(null, null);
subtitleTextView.setLeftDrawable(statusDrawables[type]);
}
for (int a = 0; a < statusDrawables.length; a++) {
if (a == type) {
statusDrawables[a].start();
@ -394,6 +402,7 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent
}
} else {
subtitleTextView.setLeftDrawable(null);
subtitleTextView.replaceTextWithDrawable(null, null);
for (int a = 0; a < statusDrawables.length; a++) {
statusDrawables[a].stop();
}
@ -745,4 +754,8 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent
public SharedMediaLayout.SharedMediaPreloader getSharedMediaPreloader() {
return sharedMediaPreloader;
}
public BackupImageView getAvatarImageView() {
return avatarImageView;
}
}

View file

@ -6,7 +6,6 @@ import android.animation.ObjectAnimator;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
@ -16,6 +15,7 @@ import android.graphics.Rect;
import android.graphics.RectF;
import androidx.annotation.Keep;
import android.graphics.Shader;
import android.text.TextPaint;
import android.view.View;
@ -314,9 +314,11 @@ public class CheckBoxBase {
if (backgroundType == 12 || backgroundType == 13) {
backgroundPaint.setStyle(Paint.Style.FILL);
if (messageDrawable != null && messageDrawable.hasGradient()) {
LinearGradient shader = messageDrawable.getGradientShader();
Shader shader = messageDrawable.getGradientShader();
Matrix matrix = messageDrawable.getMatrix();
matrix.setTranslate(0, -messageDrawable.getTopY() + bounds.top);
matrix.reset();
messageDrawable.applyMatrixScale();
matrix.postTranslate(0, -messageDrawable.getTopY() + bounds.top);
shader.setLocalMatrix(matrix);
backgroundPaint.setShader(shader);
} else {

View file

@ -0,0 +1,134 @@
package org.telegram.ui.Components;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.ui.ActionBar.Theme;
public class ChoosingStickerStatusDrawable extends StatusDrawable {
Paint strokePaint;
Paint fillPaint;
private boolean isChat = false;
private long lastUpdateTime = 0;
private boolean started = false;
float progress;
boolean increment = true;
public ChoosingStickerStatusDrawable(boolean createPaint) {
if (createPaint) {
strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
fillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
strokePaint.setStyle(Paint.Style.STROKE);
strokePaint.setStrokeWidth(AndroidUtilities.dpf2(1.2f));
}
}
@Override
public void start() {
lastUpdateTime = System.currentTimeMillis();
started = true;
invalidateSelf();
}
@Override
public void stop() {
started = false;
}
@Override
public void setIsChat(boolean value) {
this.isChat = value;
}
@Override
public void setColor(int color) {
fillPaint.setColor(color);
strokePaint.setColor(color);
}
@Override
public void draw(@NonNull Canvas canvas) {
float animationProgress = Math.min(progress, 1f);
float k = 0.3f;
float p = CubicBezierInterpolator.EASE_IN.getInterpolation(animationProgress < k ? animationProgress / k : 1f);
float p2 = CubicBezierInterpolator.EASE_OUT.getInterpolation(animationProgress < k ? 0 : (animationProgress - k) / (1f - k));
float cx, xOffset;
if (increment) {
cx = AndroidUtilities.dp(2.1f) * p + (AndroidUtilities.dp(7) - AndroidUtilities.dp(2.1f)) * (1f - p);
xOffset = AndroidUtilities.dpf2(1.5f) * (1f - CubicBezierInterpolator.EASE_OUT.getInterpolation(progress / 2));
} else {
cx = AndroidUtilities.dp(2.1f) * (1f - p) + (AndroidUtilities.dp(7) - AndroidUtilities.dp(2.1f)) * p;
xOffset = AndroidUtilities.dpf2(1.5f) * CubicBezierInterpolator.EASE_OUT_QUINT.getInterpolation(progress / 2);
}
float cy = AndroidUtilities.dp(11) / 2f;
float r = AndroidUtilities.dpf2(2f);
float scaleOffset = AndroidUtilities.dpf2(0.5f) * p - AndroidUtilities.dpf2(0.5f) * p2;
Paint strokePaint = this.strokePaint != null ? this.strokePaint : Theme.chat_statusRecordPaint;
Paint paint = this.fillPaint != null ? this.fillPaint : Theme.chat_statusPaint;
if (strokePaint.getStrokeWidth() != AndroidUtilities.dp(0.8f)) {
strokePaint.setStrokeWidth(AndroidUtilities.dp(0.8f));
}
for (int i = 0; i < 2; i++) {
canvas.save();
canvas.translate(strokePaint.getStrokeWidth() / 2f + xOffset + AndroidUtilities.dp(9) * i + getBounds().left + AndroidUtilities.dpf2(0.2f), strokePaint.getStrokeWidth() / 2f + AndroidUtilities.dpf2(2f) + getBounds().top);
AndroidUtilities.rectTmp.set(0, scaleOffset, AndroidUtilities.dp(7), AndroidUtilities.dp(11) - scaleOffset);
canvas.drawOval(AndroidUtilities.rectTmp, strokePaint);
canvas.drawCircle(cx, cy, r, paint);
canvas.restore();
}
if (started) {
update();
}
}
private void update() {
long newTime = System.currentTimeMillis();
long dt = newTime - lastUpdateTime;
lastUpdateTime = newTime;
if (dt > 16) {
dt = 16;
}
progress += dt / 500f;
if (progress >= 2f) {
progress = 0;
increment = !increment;
}
invalidateSelf();
}
@Override
public void setAlpha(int i) {
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return 0;
}
@Override
public int getIntrinsicWidth() {
return AndroidUtilities.dp(20);
}
@Override
public int getIntrinsicHeight() {
return AndroidUtilities.dp(18);
}
}

View file

@ -75,7 +75,6 @@ public class ColorPicker extends FrameLayout {
private EditTextBoldCursor[] colorEditText;
private ImageView clearButton;
private ImageView addButton;
private ImageView exchangeButton;
private TextView resetButton;
private ActionBarMenuItem menuItem;
@ -389,24 +388,6 @@ public class ColorPicker extends FrameLayout {
}
}
exchangeButton = new ImageView(getContext());
exchangeButton.setBackground(Theme.createSelectorDrawable(Theme.getColor(Theme.key_dialogButtonSelector), 1));
exchangeButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText), PorterDuff.Mode.MULTIPLY));
exchangeButton.setScaleType(ImageView.ScaleType.CENTER);
exchangeButton.setVisibility(GONE);
exchangeButton.setImageResource(R.drawable.themes_swapcolor);
exchangeButton.setOnClickListener(v -> {
if (exchangeButton.getAlpha() != 1.0f) {
return;
}
int color = radioButton[0].getColor();
radioButton[0].setColor(radioButton[1].getColor());
radioButton[1].setColor(color);
delegate.setColor(radioButton[0].getColor(), 0, false);
delegate.setColor(radioButton[1].getColor(), 1, true);
});
radioContainer.addView(exchangeButton, 1, LayoutHelper.createFrame(30, 30, Gravity.LEFT | Gravity.TOP, 0, 1, 0, 0));
addButton = new ImageView(getContext());
addButton.setBackground(Theme.createSelectorDrawable(Theme.getColor(Theme.key_dialogButtonSelector), 1));
addButton.setImageResource(R.drawable.themes_addcolor);
@ -432,19 +413,7 @@ public class ColorPicker extends FrameLayout {
animators.add(ObjectAnimator.ofFloat(clearButton, View.SCALE_X, 1.0f));
animators.add(ObjectAnimator.ofFloat(clearButton, View.SCALE_Y, 1.0f));
animators.add(ObjectAnimator.ofFloat(addButton, View.TRANSLATION_X, AndroidUtilities.dp(30) + AndroidUtilities.dp(13)));
if (myMessagesColor) {
exchangeButton.setVisibility(VISIBLE);
animators.add(ObjectAnimator.ofFloat(addButton, View.ALPHA, 0.0f));
animators.add(ObjectAnimator.ofFloat(addButton, View.SCALE_X, 0.0f));
animators.add(ObjectAnimator.ofFloat(addButton, View.SCALE_Y, 0.0f));
animators.add(ObjectAnimator.ofFloat(exchangeButton, View.ALPHA, 1.0f));
animators.add(ObjectAnimator.ofFloat(exchangeButton, View.SCALE_X, 1.0f));
animators.add(ObjectAnimator.ofFloat(exchangeButton, View.SCALE_Y, 1.0f));
}
} else if (colorsCount == 2) {
if (myMessagesColor) {
return;
}
colorsCount = 3;
if (radioButton[2].getColor() == 0) {
int color = radioButton[0].getColor();
@ -461,9 +430,6 @@ public class ColorPicker extends FrameLayout {
animators.add(ObjectAnimator.ofFloat(addButton, View.TRANSLATION_X, AndroidUtilities.dp(30) * 2 + AndroidUtilities.dp(13) * 2));
delegate.setColor(radioButton[2].getColor(), 2, true);
} else if (colorsCount == 3) {
if (myMessagesColor) {
return;
}
colorsCount = 4;
if (radioButton[3].getColor() == 0) {
radioButton[3].setColor(generateGradientColors(radioButton[2].getColor()));
@ -486,7 +452,7 @@ public class ColorPicker extends FrameLayout {
colorsAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (colorsCount == 4 || myMessagesColor && colorsCount == 2) {
if (colorsCount == 4) {
addButton.setVisibility(INVISIBLE);
}
colorsAnimator = null;
@ -524,15 +490,6 @@ public class ColorPicker extends FrameLayout {
animators.add(ObjectAnimator.ofFloat(clearButton, View.SCALE_X, 0.0f));
animators.add(ObjectAnimator.ofFloat(clearButton, View.SCALE_Y, 0.0f));
animators.add(ObjectAnimator.ofFloat(addButton, View.TRANSLATION_X, 0));
if (myMessagesColor) {
addButton.setVisibility(VISIBLE);
animators.add(ObjectAnimator.ofFloat(exchangeButton, View.ALPHA, 0.0f));
animators.add(ObjectAnimator.ofFloat(exchangeButton, View.SCALE_X, 0.0f));
animators.add(ObjectAnimator.ofFloat(exchangeButton, View.SCALE_Y, 0.0f));
animators.add(ObjectAnimator.ofFloat(addButton, View.ALPHA, 1.0f));
animators.add(ObjectAnimator.ofFloat(addButton, View.SCALE_X, 1.0f));
animators.add(ObjectAnimator.ofFloat(addButton, View.SCALE_Y, 1.0f));
}
} else if (colorsCount == 3) {
colorsCount = 2;
animators = new ArrayList<>();
@ -573,9 +530,6 @@ public class ColorPicker extends FrameLayout {
public void onAnimationEnd(Animator animation) {
if (colorsCount == 1) {
clearButton.setVisibility(INVISIBLE);
if (myMessagesColor) {
exchangeButton.setVisibility(INVISIBLE);
}
}
for (int a = 0; a < radioButton.length; a++) {
if (radioButton[a].getTag(R.id.index_tag) == null) {
@ -641,9 +595,6 @@ public class ColorPicker extends FrameLayout {
private void updateColorsPosition(ArrayList<Animator> animators, int hidingIndex, boolean hiding, int width) {
int allX = 0;
int count = colorsCount;
if (myMessagesColor && colorsCount == 2) {
count++;
}
int visibleX = count * AndroidUtilities.dp(30) + (count - 1) * AndroidUtilities.dp(13);
int left = radioContainer.getLeft() + visibleX;
int w = width - AndroidUtilities.dp(currentResetType == 1 ? 50 : 0);
@ -661,10 +612,6 @@ public class ColorPicker extends FrameLayout {
for (int a = 0; a < radioButton.length; a++) {
boolean wasVisible = radioButton[a].getTag(R.id.index_tag) != null;
if (a < colorsCount) {
if (a == 1 && myMessagesColor) {
exchangeButton.setTranslationX(allX);
allX += AndroidUtilities.dp(30) + AndroidUtilities.dp(13);
}
radioButton[a].setVisibility(VISIBLE);
if (animators != null) {
if (!wasVisible) {
@ -969,7 +916,7 @@ public class ColorPicker extends FrameLayout {
addButton.setVisibility(GONE);
clearButton.setVisibility(GONE);
} else {
if (newColorsCount < (myMessages ? 2 : 4)) {
if (newColorsCount < 4) {
addButton.setVisibility(VISIBLE);
addButton.setScaleX(1.0f);
addButton.setScaleY(1.0f);
@ -986,14 +933,6 @@ public class ColorPicker extends FrameLayout {
clearButton.setVisibility(GONE);
}
}
if (myMessages) {
exchangeButton.setVisibility(newColorsCount == 2 ? VISIBLE : INVISIBLE);
exchangeButton.setAlpha(newColorsCount == 2 ? 1.0f : 0.0f);
exchangeButton.setScaleX(newColorsCount == 2 ? 1.0f : 0.0f);
exchangeButton.setScaleY(newColorsCount == 2 ? 1.0f : 0.0f);
} else {
exchangeButton.setVisibility(GONE);
}
linearLayout.invalidate();
updateColorsPosition(null, 0, false, getMeasuredWidth());
@ -1004,61 +943,6 @@ public class ColorPicker extends FrameLayout {
animators = null;
}
/*if (!twoColors || !hasSecondColor) {
colorEditText[1].requestFocus();
}
for (int a = 2; a < colorEditText.length; a++) {
if (animated) {
if (twoColors) {
colorEditText[a].setVisibility(VISIBLE);
}
animators.add(ObjectAnimator.ofFloat(colorEditText[a], View.ALPHA, twoColors && hasSecondColor ? 1.0f : 0.0f));
} else {
colorEditText[a].setVisibility(twoColors ? VISIBLE : GONE);
colorEditText[a].setAlpha(twoColors && hasSecondColor ? 1.0f : 0.0f);
}
colorEditText[a].setTag(twoColors ? 1 : null);
}*/
/*if (animated) {
if (twoColors) {
exchangeButton.setVisibility(VISIBLE);
}
animators.add(ObjectAnimator.ofFloat(exchangeButton, View.ALPHA, twoColors && hasSecondColor ? 1.0f : 0.0f));
} else {
exchangeButton.setVisibility(twoColors ? VISIBLE : GONE);
exchangeButton.setAlpha(twoColors && hasSecondColor ? 1.0f : 0.0f);
}
if (twoColors) {
clearButton.setTag(hasSecondColor ? 1 : null);
clearButton.setRotation(hasSecondColor ? 0 : 45);
}
if (animated) {
if (twoColors) {
clearButton.setVisibility(VISIBLE);
}
animators.add(ObjectAnimator.ofFloat(clearButton, View.ALPHA, twoColors ? 1.0f : 0.0f));
} else {
clearButton.setVisibility(twoColors ? VISIBLE : GONE);
clearButton.setAlpha(twoColors ? 1.0f : 0.0f);
}*/
/*resetButton.setTag(hasChanges ? 1 : null);
resetButton.setText(resetType == 1 ? LocaleController.getString("ColorPickerResetAll", R.string.ColorPickerResetAll) : LocaleController.getString("ColorPickerReset", R.string.ColorPickerReset));
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) resetButton.getLayoutParams();
layoutParams.rightMargin = AndroidUtilities.dp(resetType == 1 ? 14 : (14 + 47));
if (animated) {
if (!hasChanges || resetButton.getVisibility() == VISIBLE && hasSecondColor) {
animators.add(ObjectAnimator.ofFloat(resetButton, View.ALPHA, 0.0f));
} else if (resetButton.getVisibility() != VISIBLE && !hasSecondColor) {
resetButton.setVisibility(VISIBLE);
animators.add(ObjectAnimator.ofFloat(resetButton, View.ALPHA, 1.0f));
}
} else {
resetButton.setAlpha(!hasChanges || hasSecondColor ? 0.0f : 1.0f);
resetButton.setVisibility(!hasChanges || hasSecondColor ? GONE : VISIBLE);
}*/
if (animators != null && !animators.isEmpty()) {
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(animators);
@ -1066,12 +950,8 @@ public class ColorPicker extends FrameLayout {
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
/*if (!hasChanges || hasSecondColor) {
resetButton.setVisibility(GONE);
}*/
if (!fewColors) {
clearButton.setVisibility(GONE);
exchangeButton.setVisibility(GONE);
}
}
});
@ -1135,8 +1015,6 @@ public class ColorPicker extends FrameLayout {
}
arrayList.add(new ThemeDescription(clearButton, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
arrayList.add(new ThemeDescription(clearButton, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_dialogButtonSelector));
arrayList.add(new ThemeDescription(exchangeButton, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
arrayList.add(new ThemeDescription(exchangeButton, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_dialogButtonSelector));
if (menuItem != null) {
ThemeDescription.ThemeDescriptionDelegate delegate = () -> {
menuItem.setIconColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));

View file

@ -23,292 +23,384 @@ import org.telegram.ui.ActionBar.Theme;
public class CounterView extends View {
private final static int ANIMATION_TYPE_IN = 0;
private final static int ANIMATION_TYPE_OUT = 1;
private final static int ANIMATION_TYPE_REPLACE = 2;
int animationType = -1;
Paint circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
RectF rectF = new RectF();
int currentCount;
private boolean countAnimationIncrement;
private ValueAnimator countAnimator;
private float countChangeProgress = 1f;
private StaticLayout countLayout;
private StaticLayout countOldLayout;
private StaticLayout countAnimationStableLayout;
private StaticLayout countAnimationInLayout;
private int countWidthOld;
private int countWidth;
private int circleColor;
private int textColor;
private String textColorKey = Theme.key_chat_goDownButtonCounter;
private String circleColorKey = Theme.key_chat_goDownButtonCounterBackground;
int lastH;
int gravity = Gravity.CENTER;
float countLeft;
float x;
private boolean reverseAnimation;
public float horizontalPadding;
public CounterDrawable counterDrawable = new CounterDrawable(this);
public CounterView(Context context) {
super(context);
setVisibility(View.GONE);
circlePaint.setColor(Color.BLACK);
textPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
textPaint.setTextSize(AndroidUtilities.dp(13));
counterDrawable.updateVisibility = true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getMeasuredHeight() != lastH) {
int count = currentCount;
currentCount = -1;
setCount(count, animationType == ANIMATION_TYPE_IN);
lastH = getMeasuredHeight();
}
counterDrawable.setSize(getMeasuredHeight(), getMeasuredWidth());
}
public void setCount(int count, boolean animated) {
if (count == currentCount) {
return;
}
if (countAnimator != null) {
countAnimator.cancel();
}
if (count > 0) {
setVisibility(View.VISIBLE);
}
if (Math.abs(count - currentCount) > 99) {
animated = false;
}
if (!animated) {
currentCount = count;
if (count == 0) {
setVisibility(View.GONE);
return;
}
String newStr = String.valueOf(count);
countWidth = Math.max(AndroidUtilities.dp(12), (int) Math.ceil(textPaint.measureText(newStr)));
countLayout = new StaticLayout(newStr, textPaint, countWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
invalidate();
}
String newStr = String.valueOf(count);
if (animated) {
if (countAnimator != null) {
countAnimator.cancel();
}
countChangeProgress = 0f;
countAnimator = ValueAnimator.ofFloat(0, 1f);
countAnimator.addUpdateListener(valueAnimator -> {
countChangeProgress = (float) valueAnimator.getAnimatedValue();
invalidate();
});
countAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
animationType = -1;
countChangeProgress = 1f;
countOldLayout = null;
countAnimationStableLayout = null;
countAnimationInLayout = null;
if (currentCount == 0) {
setVisibility(View.GONE);
}
invalidate();
}
});
if (currentCount <= 0) {
animationType = ANIMATION_TYPE_IN;
countAnimator.setDuration(220);
countAnimator.setInterpolator(new OvershootInterpolator());
} else if (count == 0) {
animationType = ANIMATION_TYPE_OUT;
countAnimator.setDuration(150);
countAnimator.setInterpolator(CubicBezierInterpolator.DEFAULT);
} else {
animationType = ANIMATION_TYPE_REPLACE;
countAnimator.setDuration(430);
countAnimator.setInterpolator(CubicBezierInterpolator.DEFAULT);
}
if (countLayout != null) {
String oldStr = String.valueOf(currentCount);
if (oldStr.length() == newStr.length()) {
SpannableStringBuilder oldSpannableStr = new SpannableStringBuilder(oldStr);
SpannableStringBuilder newSpannableStr = new SpannableStringBuilder(newStr);
SpannableStringBuilder stableStr = new SpannableStringBuilder(newStr);
for (int i = 0; i < oldStr.length(); i++) {
if (oldStr.charAt(i) == newStr.charAt(i)) {
oldSpannableStr.setSpan(new EmptyStubSpan(), i, i + 1, 0);
newSpannableStr.setSpan(new EmptyStubSpan(), i, i + 1, 0);
} else {
stableStr.setSpan(new EmptyStubSpan(), i, i + 1, 0);
}
}
int countOldWidth = Math.max(AndroidUtilities.dp(12), (int) Math.ceil(textPaint.measureText(oldStr)));
countOldLayout = new StaticLayout(oldSpannableStr, textPaint, countOldWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
countAnimationStableLayout = new StaticLayout(stableStr, textPaint, countOldWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
countAnimationInLayout = new StaticLayout(newSpannableStr, textPaint, countOldWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
} else {
countOldLayout = countLayout;
}
}
countWidthOld = countWidth;
countAnimationIncrement = count > currentCount;
countAnimator.start();
}
if (count > 0) {
countWidth = Math.max(AndroidUtilities.dp(12), (int) Math.ceil(textPaint.measureText(newStr)));
countLayout = new StaticLayout(newStr, textPaint, countWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
}
currentCount = count;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int textColor = Theme.getColor(textColorKey);
int circleColor = Theme.getColor(circleColorKey);
if (this.textColor != textColor) {
this.textColor = textColor;
textPaint.setColor(textColor);
}
if (this.circleColor != circleColor) {
this.circleColor = circleColor;
circlePaint.setColor(circleColor);
}
if (countChangeProgress != 1f) {
if (animationType == ANIMATION_TYPE_IN || animationType == ANIMATION_TYPE_OUT) {
updateX(countWidth);
float cx = countLeft + countWidth / 2f;
float cy = getMeasuredHeight() / 2f;
canvas.save();
float progress = animationType == ANIMATION_TYPE_IN ? countChangeProgress : (1f - countChangeProgress);
canvas.scale(progress, progress, cx, cy);
drawInternal(canvas);
canvas.restore();
} else {
float progressHalf = countChangeProgress * 2;
if (progressHalf > 1f) {
progressHalf = 1f;
}
float countTop = (getMeasuredHeight() - AndroidUtilities.dp(23)) / 2f;
float countWidth;
if (this.countWidth == this.countWidthOld) {
countWidth = this.countWidth;
} else {
countWidth = this.countWidth * progressHalf + this.countWidthOld * (1f - progressHalf);
}
updateX(countWidth);
float scale = 1f;
if (countAnimationIncrement) {
if (countChangeProgress <= 0.5f) {
scale += 0.1f * CubicBezierInterpolator.EASE_OUT.getInterpolation(countChangeProgress * 2);
} else {
scale += 0.1f * CubicBezierInterpolator.EASE_IN.getInterpolation((1f - (countChangeProgress - 0.5f) * 2));
}
}
rectF.set(x, countTop, x + countWidth + AndroidUtilities.dp(11), countTop + AndroidUtilities.dp(23));
canvas.save();
canvas.scale(scale, scale, rectF.centerX(), rectF.centerY());
canvas.drawRoundRect(rectF, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, circlePaint);
canvas.clipRect(rectF);
boolean increment = reverseAnimation != countAnimationIncrement;
if (countAnimationInLayout != null) {
canvas.save();
canvas.translate(countLeft, countTop + AndroidUtilities.dp(4) + (increment ? AndroidUtilities.dp(13) : -AndroidUtilities.dp(13)) * (1f - progressHalf));
textPaint.setAlpha((int) (255 * progressHalf));
countAnimationInLayout.draw(canvas);
canvas.restore();
} else if (countLayout != null) {
canvas.save();
canvas.translate(countLeft, countTop + AndroidUtilities.dp(4) + (increment ? AndroidUtilities.dp(13) : -AndroidUtilities.dp(13)) * (1f - progressHalf));
textPaint.setAlpha((int) (255 * progressHalf));
countLayout.draw(canvas);
canvas.restore();
}
if (countOldLayout != null) {
canvas.save();
canvas.translate(countLeft, countTop + AndroidUtilities.dp(4) + (increment ? -AndroidUtilities.dp(13) : AndroidUtilities.dp(13)) * (progressHalf));
textPaint.setAlpha((int) (255 * (1f - progressHalf)));
countOldLayout.draw(canvas);
canvas.restore();
}
if (countAnimationStableLayout != null) {
canvas.save();
canvas.translate(countLeft, countTop + AndroidUtilities.dp(4));
textPaint.setAlpha(255);
countAnimationStableLayout.draw(canvas);
canvas.restore();
}
textPaint.setAlpha(255);
canvas.restore();
}
} else {
drawInternal(canvas);
}
counterDrawable.draw(canvas);
}
private void updateX(float countWidth) {
if (gravity == Gravity.RIGHT) {
countLeft = getMeasuredWidth() - AndroidUtilities.dp(5.5f);
if (horizontalPadding != 0) {
countLeft -= Math.max(horizontalPadding + countWidth / 2f, countWidth);
} else {
countLeft -= countWidth;
}
} else if (gravity == Gravity.LEFT) {
countLeft = AndroidUtilities.dp(5.5f);
} else {
countLeft = (int) ((getMeasuredWidth() - countWidth) / 2f);
}
x = countLeft - AndroidUtilities.dp(5.5f);
}
private void drawInternal(Canvas canvas) {
float countTop = (getMeasuredHeight() - AndroidUtilities.dp(23)) / 2f;
updateX(countWidth);
rectF.set(x, countTop, x + countWidth + AndroidUtilities.dp(11), countTop + AndroidUtilities.dp(23));
canvas.drawRoundRect(rectF, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, circlePaint);
if (countLayout != null) {
canvas.save();
canvas.translate(countLeft, countTop + AndroidUtilities.dp(4));
countLayout.draw(canvas);
canvas.restore();
}
}
public void setColors(String textKey, String circleKey){
this.textColorKey = textKey;
this.circleColorKey = circleKey;
counterDrawable.textColorKey = textKey;
counterDrawable.circleColorKey = circleKey;
}
public void setGravity(int gravity) {
this.gravity = gravity;
counterDrawable.gravity = gravity;
}
public void setReverse(boolean b) {
reverseAnimation = b;
counterDrawable.reverseAnimation = b;
}
public void setCount(int count, boolean animated) {
counterDrawable.setCount(count, animated);
}
public static class CounterDrawable {
private final static int ANIMATION_TYPE_IN = 0;
private final static int ANIMATION_TYPE_OUT = 1;
private final static int ANIMATION_TYPE_REPLACE = 2;
int animationType = -1;
public Paint circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
public RectF rectF = new RectF();
int currentCount;
private boolean countAnimationIncrement;
private ValueAnimator countAnimator;
private float countChangeProgress = 1f;
private StaticLayout countLayout;
private StaticLayout countOldLayout;
private StaticLayout countAnimationStableLayout;
private StaticLayout countAnimationInLayout;
private int countWidthOld;
private int countWidth;
private int circleColor;
private int textColor;
private String textColorKey = Theme.key_chat_goDownButtonCounter;
private String circleColorKey = Theme.key_chat_goDownButtonCounterBackground;
int lastH;
int width;
public int gravity = Gravity.CENTER;
float countLeft;
float x;
private boolean reverseAnimation;
public float horizontalPadding;
boolean updateVisibility;
private View parent;
public final static int TYPE_DEFAULT = 0;
public final static int TYPE_CHAT_PULLING_DOWN = 1;
int type = TYPE_DEFAULT;
public CounterDrawable(View parent) {
this.parent = parent;
circlePaint.setColor(Color.BLACK);
textPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
textPaint.setTextSize(AndroidUtilities.dp(13));
}
public void setSize(int h, int w) {
if (h != lastH) {
int count = currentCount;
currentCount = -1;
setCount(count, animationType == ANIMATION_TYPE_IN);
lastH = h;
}
width = w;
}
private void drawInternal(Canvas canvas) {
float countTop = (lastH - AndroidUtilities.dp(23)) / 2f;
updateX(countWidth);
rectF.set(x, countTop, x + countWidth + AndroidUtilities.dp(11), countTop + AndroidUtilities.dp(23));
canvas.drawRoundRect(rectF, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, circlePaint);
if (Theme.hasGradientService()) {
canvas.drawRoundRect(rectF, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, Theme.chat_actionBackgroundGradientDarkenPaint);
}
if (countLayout != null) {
canvas.save();
canvas.translate(countLeft, countTop + AndroidUtilities.dp(4));
countLayout.draw(canvas);
canvas.restore();
}
}
public void setCount(int count, boolean animated) {
if (count == currentCount) {
return;
}
if (countAnimator != null) {
countAnimator.cancel();
}
if (count > 0 && updateVisibility && parent != null) {
parent.setVisibility(View.VISIBLE);
}
if (Math.abs(count - currentCount) > 99) {
animated = false;
}
if (!animated) {
currentCount = count;
if (count == 0) {
if (updateVisibility && parent != null) {
parent.setVisibility(View.GONE);
}
return;
}
String newStr = String.valueOf(count);
countWidth = Math.max(AndroidUtilities.dp(12), (int) Math.ceil(textPaint.measureText(newStr)));
countLayout = new StaticLayout(newStr, textPaint, countWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
if (parent != null) {
parent.invalidate();
}
}
String newStr = String.valueOf(count);
if (animated) {
if (countAnimator != null) {
countAnimator.cancel();
}
countChangeProgress = 0f;
countAnimator = ValueAnimator.ofFloat(0, 1f);
countAnimator.addUpdateListener(valueAnimator -> {
countChangeProgress = (float) valueAnimator.getAnimatedValue();
if (parent != null) {
parent.invalidate();
}
});
countAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
animationType = -1;
countChangeProgress = 1f;
countOldLayout = null;
countAnimationStableLayout = null;
countAnimationInLayout = null;
if (parent != null) {
if (currentCount == 0 && updateVisibility) {
parent.setVisibility(View.GONE);
}
parent.invalidate();
}
}
});
if (currentCount <= 0) {
animationType = ANIMATION_TYPE_IN;
countAnimator.setDuration(220);
countAnimator.setInterpolator(new OvershootInterpolator());
} else if (count == 0) {
animationType = ANIMATION_TYPE_OUT;
countAnimator.setDuration(150);
countAnimator.setInterpolator(CubicBezierInterpolator.DEFAULT);
} else {
animationType = ANIMATION_TYPE_REPLACE;
countAnimator.setDuration(430);
countAnimator.setInterpolator(CubicBezierInterpolator.DEFAULT);
}
if (countLayout != null) {
String oldStr = String.valueOf(currentCount);
if (oldStr.length() == newStr.length()) {
SpannableStringBuilder oldSpannableStr = new SpannableStringBuilder(oldStr);
SpannableStringBuilder newSpannableStr = new SpannableStringBuilder(newStr);
SpannableStringBuilder stableStr = new SpannableStringBuilder(newStr);
for (int i = 0; i < oldStr.length(); i++) {
if (oldStr.charAt(i) == newStr.charAt(i)) {
oldSpannableStr.setSpan(new EmptyStubSpan(), i, i + 1, 0);
newSpannableStr.setSpan(new EmptyStubSpan(), i, i + 1, 0);
} else {
stableStr.setSpan(new EmptyStubSpan(), i, i + 1, 0);
}
}
int countOldWidth = Math.max(AndroidUtilities.dp(12), (int) Math.ceil(textPaint.measureText(oldStr)));
countOldLayout = new StaticLayout(oldSpannableStr, textPaint, countOldWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
countAnimationStableLayout = new StaticLayout(stableStr, textPaint, countOldWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
countAnimationInLayout = new StaticLayout(newSpannableStr, textPaint, countOldWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
} else {
countOldLayout = countLayout;
}
}
countWidthOld = countWidth;
countAnimationIncrement = count > currentCount;
countAnimator.start();
}
if (count > 0) {
countWidth = Math.max(AndroidUtilities.dp(12), (int) Math.ceil(textPaint.measureText(newStr)));
countLayout = new StaticLayout(newStr, textPaint, countWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
}
currentCount = count;
if (parent != null) {
parent.invalidate();
}
}
public void draw(Canvas canvas) {
if (type != TYPE_CHAT_PULLING_DOWN) {
int textColor = Theme.getColor(textColorKey);
int circleColor = Theme.getColor(circleColorKey);
if (this.textColor != textColor) {
this.textColor = textColor;
textPaint.setColor(textColor);
}
if (this.circleColor != circleColor) {
this.circleColor = circleColor;
circlePaint.setColor(circleColor);
}
}
if (countChangeProgress != 1f) {
if (animationType == ANIMATION_TYPE_IN || animationType == ANIMATION_TYPE_OUT) {
updateX(countWidth);
float cx = countLeft + countWidth / 2f;
float cy = lastH / 2f;
canvas.save();
float progress = animationType == ANIMATION_TYPE_IN ? countChangeProgress : (1f - countChangeProgress);
canvas.scale(progress, progress, cx, cy);
drawInternal(canvas);
canvas.restore();
} else {
float progressHalf = countChangeProgress * 2;
if (progressHalf > 1f) {
progressHalf = 1f;
}
float countTop = (lastH - AndroidUtilities.dp(23)) / 2f;
float countWidth;
if (this.countWidth == this.countWidthOld) {
countWidth = this.countWidth;
} else {
countWidth = this.countWidth * progressHalf + this.countWidthOld * (1f - progressHalf);
}
updateX(countWidth);
float scale = 1f;
if (countAnimationIncrement) {
if (countChangeProgress <= 0.5f) {
scale += 0.1f * CubicBezierInterpolator.EASE_OUT.getInterpolation(countChangeProgress * 2);
} else {
scale += 0.1f * CubicBezierInterpolator.EASE_IN.getInterpolation((1f - (countChangeProgress - 0.5f) * 2));
}
}
rectF.set(x, countTop, x + countWidth + AndroidUtilities.dp(11), countTop + AndroidUtilities.dp(23));
canvas.save();
canvas.scale(scale, scale, rectF.centerX(), rectF.centerY());
canvas.drawRoundRect(rectF, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, circlePaint);
if (Theme.hasGradientService()) {
canvas.drawRoundRect(rectF, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, Theme.chat_actionBackgroundGradientDarkenPaint);
}
canvas.clipRect(rectF);
boolean increment = reverseAnimation != countAnimationIncrement;
if (countAnimationInLayout != null) {
canvas.save();
canvas.translate(countLeft, countTop + AndroidUtilities.dp(4) + (increment ? AndroidUtilities.dp(13) : -AndroidUtilities.dp(13)) * (1f - progressHalf));
textPaint.setAlpha((int) (255 * progressHalf));
countAnimationInLayout.draw(canvas);
canvas.restore();
} else if (countLayout != null) {
canvas.save();
canvas.translate(countLeft, countTop + AndroidUtilities.dp(4) + (increment ? AndroidUtilities.dp(13) : -AndroidUtilities.dp(13)) * (1f - progressHalf));
textPaint.setAlpha((int) (255 * progressHalf));
countLayout.draw(canvas);
canvas.restore();
}
if (countOldLayout != null) {
canvas.save();
canvas.translate(countLeft, countTop + AndroidUtilities.dp(4) + (increment ? -AndroidUtilities.dp(13) : AndroidUtilities.dp(13)) * (progressHalf));
textPaint.setAlpha((int) (255 * (1f - progressHalf)));
countOldLayout.draw(canvas);
canvas.restore();
}
if (countAnimationStableLayout != null) {
canvas.save();
canvas.translate(countLeft, countTop + AndroidUtilities.dp(4));
textPaint.setAlpha(255);
countAnimationStableLayout.draw(canvas);
canvas.restore();
}
textPaint.setAlpha(255);
canvas.restore();
}
} else {
drawInternal(canvas);
}
}
public void updateBackgroundRect() {
if (countChangeProgress != 1f) {
if (animationType == ANIMATION_TYPE_IN || animationType == ANIMATION_TYPE_OUT) {
updateX(countWidth);
float countTop = (lastH - AndroidUtilities.dp(23)) / 2f;
rectF.set(x, countTop, x + countWidth + AndroidUtilities.dp(11), countTop + AndroidUtilities.dp(23));
} else {
float progressHalf = countChangeProgress * 2;
if (progressHalf > 1f) {
progressHalf = 1f;
}
float countTop = (lastH - AndroidUtilities.dp(23)) / 2f;
float countWidth;
if (this.countWidth == this.countWidthOld) {
countWidth = this.countWidth;
} else {
countWidth = this.countWidth * progressHalf + this.countWidthOld * (1f - progressHalf);
}
updateX(countWidth);
rectF.set(x, countTop, x + countWidth + AndroidUtilities.dp(11), countTop + AndroidUtilities.dp(23));
}
} else {
updateX(countWidth);
float countTop = (lastH - AndroidUtilities.dp(23)) / 2f;
rectF.set(x, countTop, x + countWidth + AndroidUtilities.dp(11), countTop + AndroidUtilities.dp(23));
}
}
private void updateX(float countWidth) {
if (gravity == Gravity.RIGHT) {
countLeft = width - AndroidUtilities.dp(5.5f);
if (horizontalPadding != 0) {
countLeft -= Math.max(horizontalPadding + countWidth / 2f, countWidth);
} else {
countLeft -= countWidth;
}
} else if (gravity == Gravity.LEFT) {
countLeft = AndroidUtilities.dp(5.5f);
} else {
countLeft = (int) ((width - countWidth) / 2f);
}
x = countLeft - AndroidUtilities.dp(5.5f);
}
public float getCenterX() {
updateX(countWidth);
return countLeft + countWidth / 2f;
}
public void setType(int type) {
this.type = type;
}
public void setParent(View parent) {
this.parent = parent;
}
}
}

View file

@ -572,9 +572,9 @@ public class EditTextBoldCursor extends EditText {
}
if (supportRtlHint && LocaleController.isRTL) {
float offset = getMeasuredWidth() - hintWidth;
canvas.translate(left + getScrollX() + offset, lineY - hintLayout.getHeight() - AndroidUtilities.dp(6));
canvas.translate(left + getScrollX() + offset, lineY - hintLayout.getHeight() - AndroidUtilities.dp(7));
} else {
canvas.translate(left + getScrollX(), lineY - hintLayout.getHeight() - AndroidUtilities.dp(6));
canvas.translate(left + getScrollX(), lineY - hintLayout.getHeight() - AndroidUtilities.dp2(7));
}
if (transformHintToHeader) {
float scale = 1.0f - 0.3f * headerAnimationProgress;

View file

@ -470,7 +470,7 @@ public class EditTextEmoji extends FrameLayout implements NotificationCenter.Not
if (emojiView != null) {
return;
}
emojiView = new EmojiView(false, false, getContext(), false, null);
emojiView = new EmojiView(false, false, getContext(), false, null, null);
emojiView.setVisibility(GONE);
if (AndroidUtilities.isTablet()) {
emojiView.setForseMultiwindowLayout(true);

View file

@ -15,6 +15,7 @@ import android.animation.ObjectAnimator;
import android.animation.StateListAnimator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.DataSetObserver;
@ -24,6 +25,7 @@ import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
@ -37,10 +39,12 @@ import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.LinearSmoothScrollerCustom;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import android.text.Editable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@ -67,6 +71,8 @@ import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.TextView;
import com.google.android.exoplayer2.util.Log;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.BuildVars;
import org.telegram.messenger.ChatObject;
@ -139,9 +145,11 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
private EmojiSearchAdapter emojiSearchAdapter;
private SearchField emojiSearchField;
private AnimatorSet emojiTabShadowAnimator;
private RecyclerAnimationScrollHelper scrollHelper;
private boolean firstEmojiAttach = true;
private boolean needEmojiSearch;
private int hasRecentEmoji = -1;
private boolean hasChatStickers;
private FrameLayout gifContainer;
private RecyclerListView gifGridView;
@ -163,19 +171,34 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
private StickersSearchGridAdapter stickersSearchGridAdapter;
private RecyclerListView.OnItemClickListener stickersOnItemClickListener;
private ScrollSlidingTabStrip stickersTab;
private FrameLayout stickersTabContainer;
private RecyclerListView stickersGridView;
private GridLayoutManager stickersLayoutManager;
private TrendingAdapter trendingAdapter;
private SearchField stickersSearchField;
private int stickersMinusDy;
private boolean firstStickersAttach = true;
private boolean ignoreStickersScroll;
private boolean stickersContainerAttached;
private AnimatorSet searchAnimation;
private TextView mediaBanTooltip;
private DragListener dragListener;
private boolean showing;
private final int[] tabsMinusDy = new int[3];
private ObjectAnimator[] tabsYAnimators = new ObjectAnimator[3];
private boolean firstTabUpdate;
public void setShowing(boolean showing) {
this.showing = showing;
updateStickerTabsPosition();
}
public void onMessageSend() {
chooseStickerActionTracker.reset();
}
@IntDef({Type.STICKERS, Type.EMOJIS, Type.GIFS})
@Retention(RetentionPolicy.SOURCE)
@ -205,6 +228,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
private ArrayList<TLRPC.Document> recentGifs = new ArrayList<>();
private ArrayList<TLRPC.Document> recentStickers = new ArrayList<>();
private ArrayList<TLRPC.Document> favouriteStickers = new ArrayList<>();
private ArrayList<TLRPC.StickerSetCovered> featuredStickerSets = new ArrayList<>();
private Paint dotPaint;
@ -249,6 +273,18 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
private float emojiLastY;
private float emojiTouchedX;
private float emojiTouchedY;
private float lastStickersX;
private boolean expandStickersByDragg;
private Runnable checkExpandStickerTabsRunnable = new Runnable() {
@Override
public void run() {
if (!stickersTab.isDragging()) {
expandStickersByDragg = false;
updateStickerTabsPosition();
}
}
};
public interface EmojiViewDelegate {
default boolean onBackspace() {
@ -319,15 +355,30 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
return 0;
}
default int getThreadId() {
return 0;
}
default void showTrendingStickersAlert(TrendingStickersLayout layout) {
}
default void invalidateEnterView() {
}
default float getProgressToSearchOpened() {
return 0f;
}
}
public interface DragListener {
void onDragStart();
void onDragEnd(float velocity);
void onDragCancel();
void onDrag(int offset);
}
@ -389,6 +440,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
};
private static final Field superListenerField;
static {
Field f = null;
try {
@ -583,9 +635,15 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
if (!smoothScrolling) {
animateTabsY(type);
}
if (ignoreStickersScroll) {
ignoreStickersScroll = false;
}
smoothScrolling = false;
} else {
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
if (ignoreStickersScroll) {
ignoreStickersScroll = false;
}
final SearchField searchField = getSearchFieldForType(type);
if (searchField != null) {
searchField.hideKeyboard();
@ -595,6 +653,9 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
if (!smoothScrolling) {
stopAnimatingTabsY(type);
}
if (type == Type.STICKERS) {
chooseStickerActionTracker.doSomeAction();
}
}
}
@ -642,6 +703,9 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (isDragging()) {
return super.onInterceptTouchEvent(ev);
}
if (getParent() != null) {
getParent().requestDisallowInterceptTouchEvent(true);
}
@ -668,18 +732,27 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (isDragging()) {
return super.onTouchEvent(ev);
}
if (first) {
first = false;
lastX = ev.getX();
}
if (ev.getAction() == MotionEvent.ACTION_DOWN || ev.getAction() == MotionEvent.ACTION_MOVE) {
lastStickersX = ev.getRawX();
}
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
draggingVertically = draggingHorizontally = false;
downX = ev.getRawX();
downY = ev.getRawY();
} else {
if (!draggingVertically && !draggingHorizontally && dragListener != null) {
if (Math.abs(ev.getRawX() - downX) >= touchSlop) {
if (Math.abs(ev.getRawX() - downX) >= touchSlop && canScrollHorizontally((int) (downX - ev.getRawX()))) {
draggingHorizontally = true;
AndroidUtilities.cancelRunOnUIThread(checkExpandStickerTabsRunnable);
expandStickersByDragg = true;
updateStickerTabsPosition();
} else if (Math.abs(ev.getRawY() - downY) >= touchSlop) {
draggingVertically = true;
downY = ev.getRawY();
@ -691,6 +764,9 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
}
}
}
if (expandStickersByDragg && (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL)) {
AndroidUtilities.runOnUIThread(checkExpandStickerTabsRunnable, 1500);
}
if (draggingVertically) {
if (vTracker == null) {
vTracker = VelocityTracker.obtain();
@ -711,6 +787,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
} else {
dragListener.onDrag(Math.round(ev.getRawY() - downY));
}
cancelLongPress();
return true;
}
float newTranslationX = getTranslationX();
@ -999,7 +1076,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
}
}
public EmojiView(boolean needStickers, boolean needGif, final Context context, boolean needSearch, final TLRPC.ChatFull chatFull) {
public EmojiView(boolean needStickers, boolean needGif, final Context context, boolean needSearch, final TLRPC.ChatFull chatFull, ViewGroup parentView) {
super(context);
int color = Theme.getColor(Theme.key_chat_emojiBottomPanelIcon);
@ -1027,12 +1104,12 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
};
stickerIcons = new Drawable[]{
Theme.createEmojiIconSelectorDrawable(context, R.drawable.stickers_recent, Theme.getColor(Theme.key_chat_emojiBottomPanelIcon), Theme.getColor(Theme.key_chat_emojiPanelIconSelected)),
Theme.createEmojiIconSelectorDrawable(context, R.drawable.stickers_favorites, Theme.getColor(Theme.key_chat_emojiBottomPanelIcon), Theme.getColor(Theme.key_chat_emojiPanelIconSelected)),
Theme.createEmojiIconSelectorDrawable(context, R.drawable.stickers_trending3, Theme.getColor(Theme.key_chat_emojiBottomPanelIcon), Theme.getColor(Theme.key_chat_emojiPanelIconSelected)),
Theme.createEmojiIconSelectorDrawable(context, R.drawable.emoji_tabs_recent, Theme.getColor(Theme.key_chat_emojiBottomPanelIcon), Theme.getColor(Theme.key_chat_emojiPanelIconSelected)),
Theme.createEmojiIconSelectorDrawable(context, R.drawable.emoji_tabs_faves, Theme.getColor(Theme.key_chat_emojiBottomPanelIcon), Theme.getColor(Theme.key_chat_emojiPanelIconSelected)),
Theme.createEmojiIconSelectorDrawable(context, R.drawable.emoji_tabs_new3, Theme.getColor(Theme.key_chat_emojiBottomPanelIcon), Theme.getColor(Theme.key_chat_emojiPanelIconSelected)),
new LayerDrawable(new Drawable[]{
Theme.createEmojiIconSelectorDrawable(context, R.drawable.stickers_trending1, Theme.getColor(Theme.key_chat_emojiBottomPanelIcon), Theme.getColor(Theme.key_chat_emojiPanelIconSelected)),
Theme.createEmojiIconSelectorDrawable(context, R.drawable.stickers_trending2, Theme.getColor(Theme.key_chat_emojiPanelStickerPackSelectorLine), Theme.getColor(Theme.key_chat_emojiPanelStickerPackSelectorLine))
Theme.createEmojiIconSelectorDrawable(context, R.drawable.emoji_tabs_new1, Theme.getColor(Theme.key_chat_emojiBottomPanelIcon), Theme.getColor(Theme.key_chat_emojiPanelIconSelected)),
Theme.createEmojiIconSelectorDrawable(context, R.drawable.emoji_tabs_new2, Theme.getColor(Theme.key_chat_emojiPanelStickerPackSelectorLine), Theme.getColor(Theme.key_chat_emojiPanelStickerPackSelectorLine))
})
};
@ -1551,7 +1628,24 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
gifAdapter.loadTrendingGifs();
}
stickersContainer = new FrameLayout(context);
stickersContainer = new FrameLayout(context) {
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
stickersContainerAttached = true;
updateStickerTabsPosition();
chooseStickerActionTracker.checkVisibility();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
stickersContainerAttached = false;
updateStickerTabsPosition();
chooseStickerActionTracker.checkVisibility();
}
};
MediaDataController.getInstance(currentAccount).checkStickers(MediaDataController.TYPE_IMAGE);
MediaDataController.getInstance(currentAccount).checkFeaturedStickers();
@ -1591,7 +1685,29 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
}
};
stickersGridView.setLayoutManager(stickersLayoutManager = new GridLayoutManager(context, 5));
stickersGridView.setLayoutManager(stickersLayoutManager = new GridLayoutManager(context, 5) {
@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
try {
LinearSmoothScrollerCustom linearSmoothScroller = new LinearSmoothScrollerCustom(recyclerView.getContext(), LinearSmoothScrollerCustom.POSITION_TOP);
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
} catch (Exception e) {
FileLog.e(e);
}
}
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
int i = super.scrollVerticallyBy(dy, recycler, state);
if (i != 0 && stickersGridView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) {
expandStickersByDragg = false;
updateStickerTabsPosition();
}
chooseStickerActionTracker.doSomeAction();
return i;
}
});
stickersLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
@ -1647,31 +1763,122 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
stickersGridView.setOnItemClickListener(stickersOnItemClickListener);
stickersGridView.setGlowColor(Theme.getColor(Theme.key_chat_emojiPanelBackground));
stickersContainer.addView(stickersGridView);
scrollHelper = new RecyclerAnimationScrollHelper(stickersGridView, stickersLayoutManager);
stickersSearchField = new SearchField(context, 0);
stickersContainer.addView(stickersSearchField, new FrameLayout.LayoutParams(LayoutHelper.MATCH_PARENT, searchFieldHeight + AndroidUtilities.getShadowHeight()));
stickersTab = new DraggableScrollSlidingTabStrip(context);
stickersTab = new DraggableScrollSlidingTabStrip(context) {
@Override
protected void updatePosition() {
updateStickerTabsPosition();
stickersTabContainer.invalidate();
invalidate();
if (delegate != null) {
delegate.invalidateEnterView();
}
}
@Override
protected void stickerSetPositionChanged(int fromPosition, int toPosition) {
int index1 = fromPosition - stickersTabOffset;
int index2 = toPosition - stickersTabOffset;
final MediaDataController mediaDataController = MediaDataController.getInstance(currentAccount);
swapListElements(stickerSets, index1, index2);
if (hasChatStickers) {
swapListElements(mediaDataController.getStickerSets(MediaDataController.TYPE_IMAGE), index1 - 1, index2 - 1);
} else {
swapListElements(mediaDataController.getStickerSets(MediaDataController.TYPE_IMAGE), index1, index2);
}
reloadStickersAdapter();
AndroidUtilities.cancelRunOnUIThread(checkExpandStickerTabsRunnable);
AndroidUtilities.runOnUIThread(checkExpandStickerTabsRunnable, 1500);
sendReorder();
updateStickerTabs();
}
private void swapListElements(List<TLRPC.TL_messages_stickerSet> list, int index1, int index2) {
final TLRPC.TL_messages_stickerSet set1 = list.remove(index1);
list.add(index2, set1);
}
private void sendReorder() {
MediaDataController.getInstance(currentAccount).calcNewHash(MediaDataController.TYPE_IMAGE);
TLRPC.TL_messages_reorderStickerSets req = new TLRPC.TL_messages_reorderStickerSets();
req.masks = false;
for (int a = hasChatStickers ? 1 : 0; a < stickerSets.size(); a++) {
req.order.add(stickerSets.get(a).set.id);
}
ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> { });
NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.stickersDidLoad, MediaDataController.TYPE_IMAGE);
}
@Override
protected void invalidateOverlays() {
stickersTabContainer.invalidate();
}
};
stickersTab.setDragEnabled(true);
stickersTab.setWillNotDraw(false);
stickersTab.setType(ScrollSlidingTabStrip.Type.TAB);
stickersTab.setUnderlineHeight(AndroidUtilities.getShadowHeight());
stickersTab.setIndicatorColor(Theme.getColor(Theme.key_chat_emojiPanelStickerPackSelectorLine));
stickersTab.setUnderlineColor(Theme.getColor(Theme.key_chat_emojiPanelShadowLine));
stickersTab.setBackgroundColor(Theme.getColor(Theme.key_chat_emojiPanelBackground));
stickersContainer.addView(stickersTab, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.TOP));
if (parentView != null) {
stickersTabContainer = new FrameLayout(context) {
Paint paint = new Paint();
@Override
protected void dispatchDraw(Canvas canvas) {
float searchProgress = delegate.getProgressToSearchOpened();
float searchProgressOffset = AndroidUtilities.dp(50) * delegate.getProgressToSearchOpened();
if (searchProgressOffset > getMeasuredHeight()) {
return;
}
canvas.save();
if (searchProgressOffset != 0) {
canvas.clipRect(0, searchProgressOffset, getMeasuredWidth(), getMeasuredHeight());
}
paint.setColor(Theme.getColor(Theme.key_chat_emojiPanelBackground));
canvas.drawRect(0, 0, getMeasuredWidth(), AndroidUtilities.dp(48) + stickersTab.getExpandedOffset(), paint);
super.dispatchDraw(canvas);
stickersTab.drawOverlays(canvas);
canvas.restore();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
updateStickerTabsPosition();
}
};
stickersTabContainer.addView(stickersTab, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.TOP));
parentView.addView(stickersTabContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
} else {
stickersContainer.addView(stickersTab, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.TOP));
}
updateStickerTabs();
stickersTab.setDelegate(page -> {
if (firstTabUpdate) {
return;
}
if (page == trendingTabNum) {
openTrendingStickers(null);
return;
} else if (page == recentTabBum) {
stickersGridView.stopScroll();
stickersLayoutManager.scrollToPositionWithOffset(stickersGridAdapter.getPositionForPack("recent"), 0);
scrollStickersToPosition(stickersGridAdapter.getPositionForPack("recent"), 0);
resetTabsY(Type.STICKERS);
stickersTab.onPageScrolled(recentTabBum, recentTabBum > 0 ? recentTabBum : stickersTabOffset);
return;
} else if (page == favTabBum) {
stickersGridView.stopScroll();
stickersLayoutManager.scrollToPositionWithOffset(stickersGridAdapter.getPositionForPack("fav"), 0);
scrollStickersToPosition(stickersGridAdapter.getPositionForPack("fav"), 0);
resetTabsY(Type.STICKERS);
stickersTab.onPageScrolled(favTabBum, favTabBum > 0 ? favTabBum : stickersTabOffset);
return;
@ -1686,9 +1893,20 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
}
firstStickersAttach = false;
stickersGridView.stopScroll();
stickersLayoutManager.scrollToPositionWithOffset(stickersGridAdapter.getPositionForPack(stickerSets.get(index)), 0);
scrollStickersToPosition(stickersGridAdapter.getPositionForPack(stickerSets.get(index)), 0);
resetTabsY(Type.STICKERS);
checkScroll(Type.STICKERS);
int firstTab;
if (favTabBum > 0) {
firstTab = favTabBum;
} else if (recentTabBum > 0) {
firstTab = recentTabBum;
} else {
firstTab = stickersTabOffset;
}
stickersTab.onPageScrolled(page, firstTab);
expandStickersByDragg = false;
updateStickerTabsPosition();
});
stickersGridView.setOnScrollListener(new TypedScrollListener(Type.STICKERS));
@ -1809,6 +2027,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
typeTabs.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
checkGridVisibility(position, positionOffset);
EmojiView.this.onPageScrolled(position, getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), positionOffsetPixels);
showBottomTab(true, true);
SearchField currentField;
@ -1837,6 +2056,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
field.searchEditText.setSelection(currentFieldText.length());
}
startStopVisibleGifs((position == 0 && positionOffset > 0) || position == 1);
updateStickerTabsPosition();
}
@Override
@ -1972,6 +2192,31 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
}
}
private void checkGridVisibility(int position, float positionOffset) {
if (stickersContainer == null || gifContainer == null) {
return;
}
if (position == 0) {
emojiGridView.setVisibility(View.VISIBLE);
gifGridView.setVisibility(positionOffset == 0 ? View.GONE : View.VISIBLE);
gifTabs.setVisibility(positionOffset == 0 ? View.GONE : View.VISIBLE);
stickersGridView.setVisibility(View.GONE);
stickersTab.setVisibility(View.GONE);
} else if (position == 1) {
emojiGridView.setVisibility(View.GONE);
gifGridView.setVisibility(View.VISIBLE);
gifTabs.setVisibility(View.VISIBLE);
stickersGridView.setVisibility(positionOffset == 0 ? View.GONE : View.VISIBLE);
stickersTab.setVisibility(positionOffset == 0 ? View.GONE : View.VISIBLE);
} else if (position == 2) {
emojiGridView.setVisibility(View.GONE);
gifGridView.setVisibility(View.GONE);
gifTabs.setVisibility(View.GONE);
stickersGridView.setVisibility(View.VISIBLE);
stickersTab.setVisibility(View.VISIBLE);
}
}
private static String addColorToCode(String code, String color) {
String end = null;
int length = code.length();
@ -2051,6 +2296,41 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
public void setTranslationY(float translationY) {
super.setTranslationY(translationY);
updateBottomTabContainerPosition();
updateStickerTabsPosition();
}
Rect rect = new Rect();
private void updateStickerTabsPosition() {
if (stickersTabContainer == null) {
return;
}
boolean visible = getVisibility() == View.VISIBLE && stickersContainerAttached && delegate.getProgressToSearchOpened() != 1f;
stickersTabContainer.setVisibility(visible ? View.VISIBLE : View.GONE);
if (visible) {
rect.setEmpty();
pager.getChildVisibleRect(stickersContainer, rect, null);
float searchProgressOffset = AndroidUtilities.dp(50) * delegate.getProgressToSearchOpened();
int left = rect.left;
if (left != 0 || searchProgressOffset != 0) {
expandStickersByDragg = false;
}
stickersTabContainer.setTranslationX(left);
float y = getTop() + getTranslationY() - stickersTabContainer.getTop() - stickersTab.getExpandedOffset() - searchProgressOffset;
if (stickersTabContainer.getTranslationY() != y) {
stickersTabContainer.setTranslationY(y);
stickersTabContainer.invalidate();
}
}
if (expandStickersByDragg && (visible && showing)) {
stickersTab.expandStickers(lastStickersX, true);
} else {
expandStickersByDragg = false;
stickersTab.expandStickers(lastStickersX, false);
}
}
private void updateBottomTabContainerPosition() {
@ -2059,7 +2339,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
if (parent != null) {
float y = getY() - parent.getHeight();
if (getLayoutParams().height > 0) {
y += getLayoutParams().height;
y += getLayoutParams().height;
} else {
y += getMeasuredHeight();
}
@ -2189,7 +2469,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
if (searchField == currentField && delegate != null && delegate.isExpanded()) {
searchAnimation = new AnimatorSet();
if (tabStrip != null) {
if (tabStrip != null && a != 2) {
searchAnimation.playTogether(
ObjectAnimator.ofFloat(tabStrip, View.TRANSLATION_Y, -AndroidUtilities.dp(48)),
ObjectAnimator.ofFloat(gridView, View.TRANSLATION_Y, -AndroidUtilities.dp(48)),
@ -2199,8 +2479,8 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
ObjectAnimator.ofFloat(gridView, View.TRANSLATION_Y, -AndroidUtilities.dp(48)),
ObjectAnimator.ofFloat(currentField, View.TRANSLATION_Y, AndroidUtilities.dp(0)));
}
searchAnimation.setDuration(200);
searchAnimation.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT);
searchAnimation.setDuration(220);
searchAnimation.setInterpolator(CubicBezierInterpolator.DEFAULT);
searchAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@ -2225,7 +2505,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
searchAnimation.start();
} else {
currentField.setTranslationY(AndroidUtilities.dp(0));
if (tabStrip != null) {
if (tabStrip != null && a != 2) {
tabStrip.setTranslationY(-AndroidUtilities.dp(48));
}
if (gridView == stickersGridView) {
@ -2276,6 +2556,18 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
closeSearch(animated, -1);
}
private void scrollStickersToPosition(int p, int offset) {
View view = stickersLayoutManager.findViewByPosition(p);
int firstPosition = stickersLayoutManager.findFirstVisibleItemPosition();
if (view == null && Math.abs(p - firstPosition) > 40) {
scrollHelper.setScrollDirection(stickersLayoutManager.findFirstVisibleItemPosition() < p ? RecyclerAnimationScrollHelper.SCROLL_DIRECTION_DOWN : RecyclerAnimationScrollHelper.SCROLL_DIRECTION_UP);
scrollHelper.scrollToPosition(p, offset, false, true);
} else {
ignoreStickersScroll = true;
stickersGridView.smoothScrollToPosition(p);
}
}
public void closeSearch(boolean animated, long scrollToSet) {
if (searchAnimation != null) {
searchAnimation.cancel();
@ -2287,8 +2579,8 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
TLRPC.TL_messages_stickerSet set = MediaDataController.getInstance(currentAccount).getStickerSetById(scrollToSet);
if (set != null) {
int pos = stickersGridAdapter.getPositionForPack(set);
if (pos >= 0) {
stickersLayoutManager.scrollToPositionWithOffset(pos, AndroidUtilities.dp(48 + 12));
if (pos >= 0 && pos < stickersGridAdapter.getItemCount()) {
scrollStickersToPosition(pos, AndroidUtilities.dp(48 + 12));
}
}
}
@ -2328,14 +2620,14 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
if (a == currentItem && animated) {
searchAnimation = new AnimatorSet();
if (tabStrip != null) {
if (tabStrip != null && a != 2) {
searchAnimation.playTogether(
ObjectAnimator.ofFloat(tabStrip, View.TRANSLATION_Y, 0),
ObjectAnimator.ofFloat(gridView, View.TRANSLATION_Y, AndroidUtilities.dp(48) - searchFieldHeight),
ObjectAnimator.ofFloat(currentField, View.TRANSLATION_Y, AndroidUtilities.dp(48) - searchFieldHeight));
} else {
searchAnimation.playTogether(
ObjectAnimator.ofFloat(gridView, View.TRANSLATION_Y, -searchFieldHeight),
ObjectAnimator.ofFloat(gridView, View.TRANSLATION_Y, AndroidUtilities.dp(48) - searchFieldHeight),
ObjectAnimator.ofFloat(currentField, View.TRANSLATION_Y, -searchFieldHeight));
}
searchAnimation.setDuration(200);
@ -2375,7 +2667,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
searchAnimation.start();
} else {
currentField.setTranslationY(AndroidUtilities.dp(48) - searchFieldHeight);
if (tabStrip != null) {
if (tabStrip != null && a != 2) {
tabStrip.setTranslationY(0);
}
if (gridView == stickersGridView) {
@ -2537,7 +2829,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
checkEmojiTabY(emojiGridView, dy);
return;
}
if (delegate != null && delegate.isSearchOpened()) {
if (delegate != null && delegate.isSearchOpened() || ignoreStickersScroll) {
return;
}
final RecyclerListView listView = getListViewForType(type);
@ -2553,18 +2845,22 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
} else if (tabsMinusDy[type] < -AndroidUtilities.dp(48 * 6)) {
tabsMinusDy[type] = -AndroidUtilities.dp(48 * 6);
}
getTabsForType(type).setTranslationY(Math.max(-AndroidUtilities.dp(48), tabsMinusDy[type]));
if (type == 0) {
updateStickerTabsPosition();
} else {
getTabsForType(type).setTranslationY(Math.max(-AndroidUtilities.dp(48), tabsMinusDy[type]));
}
}
private void resetTabsY(@Type int type) {
if (delegate != null && delegate.isSearchOpened()) {
if (delegate != null && delegate.isSearchOpened() || type == Type.STICKERS) {
return;
}
getTabsForType(type).setTranslationY(tabsMinusDy[type] = 0);
}
private void animateTabsY(@Type int type) {
if (delegate != null && delegate.isSearchOpened()) {
if ((delegate != null && delegate.isSearchOpened()) || type == Type.STICKERS) {
return;
}
final float tabsHeight = AndroidUtilities.dpf2(type == Type.EMOJIS ? 38 : 48);
@ -2635,9 +2931,12 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
private ScrollSlidingTabStrip getTabsForType(@Type int type) {
switch (type) {
case Type.STICKERS: return stickersTab;
case Type.EMOJIS: return emojiTabs;
case Type.GIFS: return gifTabs;
case Type.STICKERS:
return stickersTab;
case Type.EMOJIS:
return emojiTabs;
case Type.GIFS:
return gifTabs;
default:
throw new IllegalArgumentException("Unexpected argument: " + type);
}
@ -2645,9 +2944,12 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
private RecyclerListView getListViewForType(@Type int type) {
switch (type) {
case Type.STICKERS: return stickersGridView;
case Type.EMOJIS: return emojiGridView;
case Type.GIFS: return gifGridView;
case Type.STICKERS:
return stickersGridView;
case Type.EMOJIS:
return emojiGridView;
case Type.GIFS:
return gifGridView;
default:
throw new IllegalArgumentException("Unexpected argument: " + type);
}
@ -2655,9 +2957,12 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
private GridLayoutManager getLayoutManagerForType(@Type int type) {
switch (type) {
case Type.STICKERS: return stickersLayoutManager;
case Type.EMOJIS: return emojiLayoutManager;
case Type.GIFS: return gifLayoutManager;
case Type.STICKERS:
return stickersLayoutManager;
case Type.EMOJIS:
return emojiLayoutManager;
case Type.GIFS:
return gifLayoutManager;
default:
throw new IllegalArgumentException("Unexpected argument: " + type);
}
@ -2665,9 +2970,12 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
private SearchField getSearchFieldForType(@Type int type) {
switch (type) {
case Type.STICKERS: return stickersSearchField;
case Type.EMOJIS: return emojiSearchField;
case Type.GIFS: return gifSearchField;
case Type.STICKERS:
return stickersSearchField;
case Type.EMOJIS:
return emojiSearchField;
case Type.GIFS:
return gifSearchField;
default:
throw new IllegalArgumentException("Unexpected argument: " + type);
}
@ -2770,6 +3078,9 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
private void checkScroll(@Type int type) {
if (type == Type.STICKERS) {
if (ignoreStickersScroll) {
return;
}
int firstVisibleItem = stickersLayoutManager.findFirstVisibleItemPosition();
if (firstVisibleItem == RecyclerView.NO_POSITION) {
return;
@ -2881,12 +3192,13 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
}
private void updateStickerTabs() {
if (stickersTab == null) {
if (stickersTab == null || stickersTab.isDragging()) {
return;
}
recentTabBum = -2;
favTabBum = -2;
trendingTabNum = -2;
hasChatStickers = false;
stickersTabOffset = 0;
int lastPosition = stickersTab.getCurrentPosition();
@ -2895,10 +3207,22 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
final MediaDataController mediaDataController = MediaDataController.getInstance(currentAccount);
SharedPreferences preferences = MessagesController.getEmojiSettings(currentAccount);
featuredStickerSets.clear();
ArrayList<TLRPC.StickerSetCovered> featured = mediaDataController.getFeaturedStickerSets();
if (!featured.isEmpty() && (!BuildVars.DEBUG_PRIVATE_VERSION || (mediaDataController.getUnreadStickerSets().isEmpty() || preferences.getLong("featured_hidden", 0) == featured.get(0).set.id))) {
for (int a = 0, N = featured.size(); a < N; a++) {
TLRPC.StickerSetCovered set = featured.get(a);
if (mediaDataController.isStickerPackInstalled(set.set.id)) {
continue;
}
featuredStickerSets.add(set);
}
if (trendingAdapter != null) {
trendingAdapter.notifyDataSetChanged();
}
if (!featured.isEmpty() && (featuredStickerSets.isEmpty() || preferences.getLong("featured_hidden", 0) == featured.get(0).set.id)) {
final int id = mediaDataController.getUnreadStickerSets().isEmpty() ? 2 : 3;
final ImageView trendingStickersTabView = stickersTab.addIconTab(id, stickerIcons[id]);
final StickerTabView trendingStickersTabView = stickersTab.addStickerIconTab(id, stickerIcons[id]);
trendingStickersTabView.textView.setText(LocaleController.getString("FeaturedStickersShort", R.string.FeaturedStickersShort));
trendingStickersTabView.setContentDescription(LocaleController.getString("FeaturedStickers", R.string.FeaturedStickers));
trendingTabNum = stickersTabOffset;
stickersTabOffset++;
@ -2907,13 +3231,17 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
if (!favouriteStickers.isEmpty()) {
favTabBum = stickersTabOffset;
stickersTabOffset++;
stickersTab.addIconTab(1, stickerIcons[1]).setContentDescription(LocaleController.getString("FavoriteStickers", R.string.FavoriteStickers));
StickerTabView stickerTabView = stickersTab.addStickerIconTab(1, stickerIcons[1]);
stickerTabView.textView.setText(LocaleController.getString("FavoriteStickersShort", R.string.FavoriteStickersShort));
stickerTabView.setContentDescription(LocaleController.getString("FavoriteStickers", R.string.FavoriteStickers));
}
if (!recentStickers.isEmpty()) {
recentTabBum = stickersTabOffset;
stickersTabOffset++;
stickersTab.addIconTab(0, stickerIcons[0]).setContentDescription(LocaleController.getString("RecentStickers", R.string.RecentStickers));
StickerTabView stickerTabView = stickersTab.addStickerIconTab(0, stickerIcons[0]);
stickerTabView.textView.setText(LocaleController.getString("RecentStickersShort", R.string.RecentStickersShort));
stickerTabView.setContentDescription(LocaleController.getString("RecentStickers", R.string.RecentStickers));
}
stickerSets.clear();
@ -2990,17 +3318,17 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
stickerSets.remove(0);
a--;
} else {
hasChatStickers = true;
stickersTab.addStickerTab(chat);
}
} else {
TLRPC.TL_messages_stickerSet stickerSet = stickerSets.get(a);
TLRPC.Document document = stickerSet.documents.get(0);
TLObject thumb = FileLoader.getClosestPhotoSizeWithSize(stickerSet.set.thumbs, 90);
SvgHelper.SvgDrawable svgThumb = DocumentObject.getSvgThumb(stickerSet.set.thumbs, Theme.key_emptyListPlaceholder, 0.2f);
if (thumb == null) {
thumb = document;
}
stickersTab.addStickerTab(thumb, svgThumb, document, stickerSet).setContentDescription(stickerSet.set.title + ", " + LocaleController.getString("AccDescrStickerSet", R.string.AccDescrStickerSet));
stickersTab.addStickerTab(thumb, document, stickerSet).setContentDescription(stickerSet.set.title + ", " + LocaleController.getString("AccDescrStickerSet", R.string.AccDescrStickerSet));
}
}
stickersTab.commitUpdate();
@ -3060,8 +3388,9 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
final Emoji.EmojiDrawable emojiDrawable = Emoji.getEmojiDrawable(emoji);
if (emojiDrawable != null) {
gifTabsCount++;
final ImageView iconTab = gifTabs.addIconTab(3 + i, emojiDrawable);
iconTab.setPadding(hPadding, vPadding, hPadding, vPadding);
TLRPC.Document document = MediaDataController.getInstance(currentAccount).getEmojiAnimatedSticker(emoji);
final View iconTab = gifTabs.addEmojiTab(3 + i, emojiDrawable, document);
// iconTab.setPadding(hPadding, vPadding, hPadding, vPadding);
iconTab.setContentDescription(emoji);
}
}
@ -3286,6 +3615,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
}
}
super.onLayout(changed, left, top, right, bottom);
updateStickerTabsPosition();
}
private void reloadStickersAdapter() {
@ -3339,6 +3669,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
pager.setCurrentItem(2, false);
}
if (stickersTab != null) {
firstTabUpdate = true;
if (favTabBum >= 0) {
stickersTab.selectTab(favTabBum);
} else if (recentTabBum >= 0) {
@ -3346,6 +3677,8 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
} else {
stickersTab.selectTab(stickersTabOffset);
}
firstTabUpdate = false;
stickersLayoutManager.scrollToPositionWithOffset(1, 0);
}
} else if (currentPage == 2) {
showBackspaceButton(false, false);
@ -3397,6 +3730,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
MediaDataController.getInstance(currentAccount).loadRecents(MediaDataController.TYPE_IMAGE, false, true, false);
MediaDataController.getInstance(currentAccount).loadRecents(MediaDataController.TYPE_FAVE, false, true, false);
}
chooseStickerActionTracker.checkVisibility();
}
public int getCurrentPage() {
@ -3684,7 +4018,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
BackupImageView imageView = (BackupImageView) holder.itemView;
TLRPC.StickerSetCovered set = MediaDataController.getInstance(currentAccount).getFeaturedStickerSets().get(position);
TLRPC.StickerSetCovered set = featuredStickerSets.get(position);
imageView.setTag(set);
TLRPC.Document document = set.cover;
@ -3693,6 +4027,9 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
}
TLObject object = FileLoader.getClosestPhotoSizeWithSize(set.set.thumbs, 90);
SvgHelper.SvgDrawable svgThumb = DocumentObject.getSvgThumb(set.set.thumbs, Theme.key_emptyListPlaceholder, 0.2f);
if (svgThumb != null) {
svgThumb.overrideWidthAndHeight(512, 512);
}
if (object == null) {
object = document;
}
@ -3731,7 +4068,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
@Override
public int getItemCount() {
return MediaDataController.getInstance(currentAccount).getFeaturedStickerSets().size();
return featuredStickerSets.size();
}
}
@ -3882,12 +4219,12 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
((StickerSetNameCell) view).setOnIconClickListener(v -> {
MediaDataController mediaDataController = MediaDataController.getInstance(currentAccount);
ArrayList<TLRPC.StickerSetCovered> featured = mediaDataController.getFeaturedStickerSets();
if (!featured.isEmpty() && !mediaDataController.getUnreadStickerSets().isEmpty()) {
if (!featured.isEmpty()) {
MessagesController.getEmojiSettings(currentAccount).edit().putLong("featured_hidden", featured.get(0).set.id).commit();
updateStickerTabs();
if (stickersGridAdapter != null) {
stickersGridAdapter.notifyItemRangeRemoved(1, 2);
}
updateStickerTabs();
}
});
break;
@ -3902,6 +4239,8 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
return super.onInterceptTouchEvent(e);
}
};
horizontalListView.setSelectorRadius(AndroidUtilities.dp(4));
horizontalListView.setSelectorDrawableColor(Theme.getColor(Theme.key_listSelector));
horizontalListView.setTag(9);
horizontalListView.setItemAnimator(null);
horizontalListView.setLayoutAnimation(null);
@ -3913,7 +4252,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
};
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
horizontalListView.setLayoutManager(layoutManager);
horizontalListView.setAdapter(new TrendingAdapter());
horizontalListView.setAdapter(trendingAdapter = new TrendingAdapter());
horizontalListView.setOnItemClickListener((view1, position) -> {
openTrendingStickers((TLRPC.StickerSetCovered) view1.getTag());
});
@ -4037,7 +4376,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
MediaDataController mediaDataController = MediaDataController.getInstance(currentAccount);
SharedPreferences preferences = MessagesController.getEmojiSettings(currentAccount);
ArrayList<TLRPC.StickerSetCovered> featured = mediaDataController.getFeaturedStickerSets();
if (BuildVars.DEBUG_PRIVATE_VERSION && !featured.isEmpty() && !mediaDataController.getUnreadStickerSets().isEmpty() && preferences.getLong("featured_hidden", 0) != featured.get(0).set.id) {
if (!featuredStickerSets.isEmpty() && preferences.getLong("featured_hidden", 0) != featured.get(0).set.id) {
cache.put(totalItems++, "trend1");
cache.put(totalItems++, "trend2");
startRow += 2;
@ -4475,7 +4814,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
}
public CharSequence getPageTitle(int position) {
switch(position) {
switch (position) {
case 0:
return LocaleController.getString("Emoji", R.string.Emoji);
case 1:
@ -4505,12 +4844,6 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
return view == object;
}
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
if (observer != null) {
super.unregisterDataSetObserver(observer);
}
}
}
@ -5649,4 +5982,51 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific
super.notifyDataSetChanged();
}
}
public void searchProgressChanged() {
updateStickerTabsPosition();
}
public float getStickersExpandOffset() {
return stickersTab == null ? 0 : stickersTab.getExpandedOffset();
}
private final ChooseStickerActionTracker chooseStickerActionTracker = new ChooseStickerActionTracker();
private class ChooseStickerActionTracker {
boolean visible = false;
boolean typingWasSent;
long lastActionTime = -1;
void doSomeAction() {
if (visible) {
if (lastActionTime == -1) {
lastActionTime = System.currentTimeMillis();
return;
}
if (System.currentTimeMillis() - lastActionTime > 2000) {
typingWasSent = true;
MessagesController.getInstance(currentAccount).sendTyping(delegate.getDialogId(), delegate.getThreadId(), 10, 0);
}
}
}
void checkVisibility() {
if (delegate == null) {
return;
}
visible = getVisibility() == View.VISIBLE && stickersContainerAttached;
if (!visible) {
reset();
}
}
private void reset() {
if (typingWasSent) {
MessagesController.getInstance(currentAccount).sendTyping(delegate.getDialogId(), delegate.getThreadId(), 2, 0);
}
lastActionTime = -1;
}
}
}

View file

@ -0,0 +1,998 @@
package org.telegram.ui.Components;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ChatListItemAnimator;
import androidx.recyclerview.widget.GridLayoutManagerFixed;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.BuildVars;
import org.telegram.messenger.ChatObject;
import org.telegram.messenger.ContactsController;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.ForwardingMessagesParams;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MessageObject;
import org.telegram.messenger.NotificationCenter;
import org.telegram.messenger.R;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.ActionBar;
import org.telegram.ui.ActionBar.ActionBarMenuSubItem;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Cells.BotHelpCell;
import org.telegram.ui.Cells.ChatMessageCell;
import java.util.ArrayList;
public class ForwardingPreviewView extends FrameLayout {
SizeNotifierFrameLayout chatPreviewContainer;
ActionBar actionBar;
RecyclerListView chatListView;
ChatListItemAnimator itemAnimator;
GridLayoutManagerFixed chatLayoutManager;
ForwardingMessagesParams forwardingMessagesParams;
Adapter adapter;
ScrollView menuScrollView;
LinearLayout menuContainer;
LinearLayout buttonsLayout;
LinearLayout buttonsLayout2;
ActionBarMenuSubItem showSendersNameView;
ActionBarMenuSubItem hideSendersNameView;
ActionBarMenuSubItem showCaptionView;
ActionBarMenuSubItem hideCaptionView;
ActionBarMenuSubItem changeRecipientView;
ActionBarMenuSubItem sendMessagesView;
int chatTopOffset;
float yOffset;
int currentTopOffset;
float currentYOffset;
ArrayList<ActionBarMenuSubItem> actionItems = new ArrayList<>();
Rect rect = new Rect();
private boolean firstLayout = true;
ValueAnimator offsetsAnimator;
TLRPC.User currentUser;
TLRPC.Chat currentChat;
boolean showing;
boolean isLandscapeMode;
private final int currentAccount;
boolean returnSendersNames;
Runnable changeBoundsRunnable = new Runnable() {
@Override
public void run() {
if (offsetsAnimator != null && !offsetsAnimator.isRunning()) {
offsetsAnimator.start();
}
}
};
private final ArrayList<MessageObject.GroupedMessages> drawingGroups = new ArrayList<>(10);
@SuppressLint("ClickableViewAccessibility")
public ForwardingPreviewView(@NonNull Context context, ForwardingMessagesParams params, TLRPC.User user, TLRPC.Chat chat, int currentAccount) {
super(context);
this.currentAccount = currentAccount;
currentUser = user;
currentChat = chat;
forwardingMessagesParams = params;
chatPreviewContainer = new SizeNotifierFrameLayout(context);
chatPreviewContainer.setBackgroundImage(Theme.getCachedWallpaper(), Theme.isWallpaperMotion());
chatPreviewContainer.setOccupyStatusBar(false);
if (Build.VERSION.SDK_INT >= 21) {
chatPreviewContainer.setOutlineProvider(new ViewOutlineProvider() {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(0, (int) (currentTopOffset + 1), view.getMeasuredWidth(), view.getMeasuredHeight(), AndroidUtilities.dp(6));
}
});
chatPreviewContainer.setClipToOutline(true);
chatPreviewContainer.setElevation(AndroidUtilities.dp(4));
}
actionBar = new ActionBar(context);
actionBar.setBackgroundColor(Theme.getColor(Theme.key_actionBarDefault));
actionBar.setOccupyStatusBar(false);
chatListView = new RecyclerListView(context) {
@Override
public boolean drawChild(Canvas canvas, View child, long drawingTime) {
if (child instanceof ChatMessageCell) {
ChatMessageCell cell = (ChatMessageCell) child;
boolean r = super.drawChild(canvas, child, drawingTime);
cell.drawCheckBox(canvas);
canvas.save();
canvas.translate(cell.getX(), cell.getY());
cell.drawMessageText(canvas, cell.getMessageObject().textLayoutBlocks, true, 1f, false);
if (cell.getCurrentMessagesGroup() != null || cell.getTransitionParams().animateBackgroundBoundsInner) {
cell.drawNamesLayout(canvas, 1f);
}
if ((cell.getCurrentPosition() != null && cell.getCurrentPosition().last) || cell.getTransitionParams().animateBackgroundBoundsInner) {
cell.drawTime(canvas, 1f, true);
}
if ((cell.getCurrentPosition() != null && cell.getCurrentPosition().last) || cell.getCurrentPosition() == null) {
cell.drawCaptionLayout(canvas, false, 1f);
}
cell.getTransitionParams().recordDrawingStatePreview();
canvas.restore();
return r;
}
return true;
}
@Override
protected void dispatchDraw(Canvas canvas) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child instanceof ChatMessageCell) {
ChatMessageCell cell = (ChatMessageCell) child;
cell.setParentViewSize(chatPreviewContainer.getMeasuredWidth(), chatPreviewContainer.getBackgroundSizeY());
}
}
drawChatBackgroundElements(canvas);
super.dispatchDraw(canvas);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
updatePositions();
}
private void drawChatBackgroundElements(Canvas canvas) {
int count = getChildCount();
MessageObject.GroupedMessages lastDrawnGroup = null;
for (int a = 0; a < count; a++) {
View child = getChildAt(a);
if (child instanceof ChatMessageCell) {
ChatMessageCell cell = (ChatMessageCell) child;
MessageObject.GroupedMessages group = cell.getCurrentMessagesGroup();
if (group != null && group == lastDrawnGroup) {
continue;
}
lastDrawnGroup = group;
MessageObject.GroupedMessagePosition position = cell.getCurrentPosition();
MessageBackgroundDrawable backgroundDrawable = cell.getBackgroundDrawable();
}
}
MessageObject.GroupedMessages scrimGroup = null;
for (int k = 0; k < 3; k++) {
drawingGroups.clear();
if (k == 2 && !chatListView.isFastScrollAnimationRunning()) {
continue;
}
for (int i = 0; i < count; i++) {
View child = chatListView.getChildAt(i);
if (child instanceof ChatMessageCell) {
ChatMessageCell cell = (ChatMessageCell) child;
if (child.getY() > chatListView.getHeight() || child.getY() + child.getHeight() < 0) {
continue;
}
MessageObject.GroupedMessages group = cell.getCurrentMessagesGroup();
if (group == null || (k == 0 && group.messages.size() == 1) || (k == 1 && !group.transitionParams.drawBackgroundForDeletedItems)) {
continue;
}
if ((k == 0 && cell.getMessageObject().deleted) || (k == 1 && !cell.getMessageObject().deleted)) {
continue;
}
if ((k == 2 && !cell.willRemovedAfterAnimation()) || (k != 2 && cell.willRemovedAfterAnimation())) {
continue;
}
if (!drawingGroups.contains(group)) {
group.transitionParams.left = 0;
group.transitionParams.top = 0;
group.transitionParams.right = 0;
group.transitionParams.bottom = 0;
group.transitionParams.pinnedBotton = false;
group.transitionParams.pinnedTop = false;
group.transitionParams.cell = cell;
drawingGroups.add(group);
}
group.transitionParams.pinnedTop = cell.isPinnedTop();
group.transitionParams.pinnedBotton = cell.isPinnedBottom();
int left = (cell.getLeft() + cell.getBackgroundDrawableLeft());
int right = (cell.getLeft() + cell.getBackgroundDrawableRight());
int top = (cell.getTop() + cell.getBackgroundDrawableTop());
int bottom = (cell.getTop() + cell.getBackgroundDrawableBottom());
if ((cell.getCurrentPosition().flags & MessageObject.POSITION_FLAG_TOP) == 0) {
top -= AndroidUtilities.dp(10);
}
if ((cell.getCurrentPosition().flags & MessageObject.POSITION_FLAG_BOTTOM) == 0) {
bottom += AndroidUtilities.dp(10);
}
if (cell.willRemovedAfterAnimation()) {
group.transitionParams.cell = cell;
}
if (group.transitionParams.top == 0 || top < group.transitionParams.top) {
group.transitionParams.top = top;
}
if (group.transitionParams.bottom == 0 || bottom > group.transitionParams.bottom) {
group.transitionParams.bottom = bottom;
}
if (group.transitionParams.left == 0 || left < group.transitionParams.left) {
group.transitionParams.left = left;
}
if (group.transitionParams.right == 0 || right > group.transitionParams.right) {
group.transitionParams.right = right;
}
}
}
for (int i = 0; i < drawingGroups.size(); i++) {
MessageObject.GroupedMessages group = drawingGroups.get(i);
if (group == scrimGroup) {
continue;
}
float x = group.transitionParams.cell.getNonAnimationTranslationX(true);
float l = (group.transitionParams.left + x + group.transitionParams.offsetLeft);
float t = (group.transitionParams.top + group.transitionParams.offsetTop);
float r = (group.transitionParams.right + x + group.transitionParams.offsetRight);
float b = (group.transitionParams.bottom + group.transitionParams.offsetBottom);
if (!group.transitionParams.backgroundChangeBounds) {
t += group.transitionParams.cell.getTranslationY();
b += group.transitionParams.cell.getTranslationY();
}
if (t < -AndroidUtilities.dp(20)) {
t = -AndroidUtilities.dp(20);
}
if (b > chatListView.getMeasuredHeight() + AndroidUtilities.dp(20)) {
b = chatListView.getMeasuredHeight() + AndroidUtilities.dp(20);
}
boolean useScale = group.transitionParams.cell.getScaleX() != 1f || group.transitionParams.cell.getScaleY() != 1f;
if (useScale) {
canvas.save();
canvas.scale(group.transitionParams.cell.getScaleX(), group.transitionParams.cell.getScaleY(), l + (r - l) / 2, t + (b - t) / 2);
}
group.transitionParams.cell.drawBackground(canvas, (int) l, (int) t, (int) r, (int) b, group.transitionParams.pinnedTop, group.transitionParams.pinnedBotton, false, 0);
group.transitionParams.cell = null;
group.transitionParams.drawCaptionLayout = group.hasCaption;
if (useScale) {
canvas.restore();
for (int ii = 0; ii < count; ii++) {
View child = chatListView.getChildAt(ii);
if (child instanceof ChatMessageCell && ((ChatMessageCell) child).getCurrentMessagesGroup() == group) {
ChatMessageCell cell = ((ChatMessageCell) child);
int left = cell.getLeft();
int top = cell.getTop();
child.setPivotX(l - left + (r - l) / 2);
child.setPivotY(t - top + (b - t) / 2);
}
}
}
}
}
}
};
chatListView.setItemAnimator(itemAnimator = new ChatListItemAnimator(null, chatListView) {
int scrollAnimationIndex = -1;
@Override
public void onAnimationStart() {
super.onAnimationStart();
AndroidUtilities.cancelRunOnUIThread(changeBoundsRunnable);
changeBoundsRunnable.run();
if (scrollAnimationIndex == -1) {
scrollAnimationIndex = NotificationCenter.getInstance(currentAccount).setAnimationInProgress(scrollAnimationIndex, null, false);
}
if (finishRunnable != null) {
AndroidUtilities.cancelRunOnUIThread(finishRunnable);
finishRunnable = null;
}
}
Runnable finishRunnable;
@Override
protected void onAllAnimationsDone() {
super.onAllAnimationsDone();
if (finishRunnable != null) {
AndroidUtilities.cancelRunOnUIThread(finishRunnable);
}
AndroidUtilities.runOnUIThread(finishRunnable = () -> {
if (scrollAnimationIndex != -1) {
NotificationCenter.getInstance(currentAccount).onAnimationFinish(scrollAnimationIndex);
scrollAnimationIndex = -1;
}
});
if (updateAfterAnimations) {
updateAfterAnimations = false;
AndroidUtilities.runOnUIThread(() -> updateMessages());
}
}
@Override
public void endAnimations() {
super.endAnimations();
if (finishRunnable != null) {
AndroidUtilities.cancelRunOnUIThread(finishRunnable);
}
AndroidUtilities.runOnUIThread(finishRunnable = () -> {
if (scrollAnimationIndex != -1) {
NotificationCenter.getInstance(currentAccount).onAnimationFinish(scrollAnimationIndex);
scrollAnimationIndex = -1;
}
});
}
});
chatListView.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
for (int i = 0; i < chatListView.getChildCount(); i++) {
ChatMessageCell cell = (ChatMessageCell) chatListView.getChildAt(i);
cell.setParentViewSize(chatPreviewContainer.getMeasuredWidth(), chatPreviewContainer.getBackgroundSizeY());
}
}
});
chatListView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
if (forwardingMessagesParams.previewMessages.size() <= 1) {
return;
}
int id = params.previewMessages.get(position).getId();
boolean newSelected = !params.selectedIds.get(id, false);
if (forwardingMessagesParams.selectedIds.size() == 1 && !newSelected) {
return;
}
if (!newSelected) {
params.selectedIds.delete(id);
} else {
params.selectedIds.put(id, newSelected);
}
ChatMessageCell chatMessageCell = (ChatMessageCell) view;
chatMessageCell.setChecked(newSelected, newSelected, true);
actionBar.setTitle(LocaleController.formatPluralString("PreviewForwardMessagesCount", params.selectedIds.size()));
}
});
chatListView.setAdapter(adapter = new Adapter());
chatListView.setPadding(0, AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4));
chatLayoutManager = new GridLayoutManagerFixed(context, 1000, LinearLayoutManager.VERTICAL, true) {
@Override
public boolean shouldLayoutChildFromOpositeSide(View child) {
return false;
}
@Override
protected boolean hasSiblingChild(int position) {
MessageObject message = params.previewMessages.get(position);
MessageObject.GroupedMessages group = getValidGroupedMessage(message);
if (group != null) {
MessageObject.GroupedMessagePosition pos = group.positions.get(message);
if (pos.minX == pos.maxX || pos.minY != pos.maxY || pos.minY == 0) {
return false;
}
int count = group.posArray.size();
for (int a = 0; a < count; a++) {
MessageObject.GroupedMessagePosition p = group.posArray.get(a);
if (p == pos) {
continue;
}
if (p.minY <= pos.minY && p.maxY >= pos.minY) {
return true;
}
}
}
return false;
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (BuildVars.DEBUG_PRIVATE_VERSION) {
super.onLayoutChildren(recycler, state);
} else {
try {
super.onLayoutChildren(recycler, state);
} catch (Exception e) {
FileLog.e(e);
AndroidUtilities.runOnUIThread(() -> adapter.notifyDataSetChanged());
}
}
}
};
chatLayoutManager.setSpanSizeLookup(new GridLayoutManagerFixed.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int idx = position;
if (idx >= 0 && idx < params.previewMessages.size()) {
MessageObject message = params.previewMessages.get(idx);
MessageObject.GroupedMessages groupedMessages = getValidGroupedMessage(message);
if (groupedMessages != null) {
return groupedMessages.positions.get(message).spanSize;
}
}
return 1000;
}
});
chatListView.setClipToPadding(false);
chatListView.setLayoutManager(chatLayoutManager);
chatListView.addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.bottom = 0;
if (view instanceof ChatMessageCell) {
ChatMessageCell cell = (ChatMessageCell) view;
MessageObject.GroupedMessages group = cell.getCurrentMessagesGroup();
if (group != null) {
MessageObject.GroupedMessagePosition position = cell.getCurrentPosition();
if (position != null && position.siblingHeights != null) {
float maxHeight = Math.max(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.5f;
int h = cell.getExtraInsetHeight();
for (int a = 0; a < position.siblingHeights.length; a++) {
h += (int) Math.ceil(maxHeight * position.siblingHeights[a]);
}
h += (position.maxY - position.minY) * Math.round(7 * AndroidUtilities.density);
int count = group.posArray.size();
for (int a = 0; a < count; a++) {
MessageObject.GroupedMessagePosition pos = group.posArray.get(a);
if (pos.minY != position.minY || pos.minX == position.minX && pos.maxX == position.maxX && pos.minY == position.minY && pos.maxY == position.maxY) {
continue;
}
if (pos.minY == position.minY) {
h -= (int) Math.ceil(maxHeight * pos.ph) - AndroidUtilities.dp(4);
break;
}
}
outRect.bottom = -h;
}
}
}
}
});
chatPreviewContainer.addView(chatListView);
addView(chatPreviewContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 400, 0, 8, 0, 8, 0));
chatPreviewContainer.addView(actionBar, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
menuScrollView = new ScrollView(context);
addView(menuScrollView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT));
menuContainer = new LinearLayout(context);
menuContainer.setOrientation(LinearLayout.VERTICAL);
menuScrollView.addView(menuContainer);
buttonsLayout = new LinearLayout(context);
buttonsLayout.setOrientation(LinearLayout.VERTICAL);
Drawable shadowDrawable = getContext().getResources().getDrawable(R.drawable.popup_fixed_alert).mutate();
shadowDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogBackground), PorterDuff.Mode.MULTIPLY));
buttonsLayout.setBackground(shadowDrawable);
menuContainer.addView(buttonsLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
showSendersNameView = new ActionBarMenuSubItem(context, true, true, false);
buttonsLayout.addView(showSendersNameView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48));
showSendersNameView.setTextAndIcon(forwardingMessagesParams.multiplyUsers ? LocaleController.getString("ShowSenderNames", R.string.ShowSenderNames) : LocaleController.getString("ShowSendersName", R.string.ShowSendersName), 0);
showSendersNameView.setChecked(true);
hideSendersNameView = new ActionBarMenuSubItem(context, true, false, !params.hasCaption);
buttonsLayout.addView(hideSendersNameView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48));
hideSendersNameView.setTextAndIcon(forwardingMessagesParams.multiplyUsers ? LocaleController.getString("HideSenderNames", R.string.HideSenderNames) : LocaleController.getString("HideSendersName", R.string.HideSendersName), 0);
hideSendersNameView.setChecked(false);
if (forwardingMessagesParams.hasCaption) {
View dividerView = new View(context) {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(2, MeasureSpec.EXACTLY));
}
};
dividerView.setBackgroundColor(Theme.getColor(Theme.key_divider));
buttonsLayout.addView(dividerView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
showCaptionView = new ActionBarMenuSubItem(context, true, false, false);
buttonsLayout.addView(showCaptionView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48));
showCaptionView.setTextAndIcon(LocaleController.getString("ShowCaption", R.string.ShowCaption), 0);
showCaptionView.setChecked(true);
hideCaptionView = new ActionBarMenuSubItem(context, true, false, true);
buttonsLayout.addView(hideCaptionView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48));
hideCaptionView.setTextAndIcon(LocaleController.getString("HideCaption", R.string.HideCaption), 0);
hideCaptionView.setChecked(false);
}
buttonsLayout2 = new LinearLayout(context);
buttonsLayout2.setOrientation(LinearLayout.VERTICAL);
shadowDrawable = getContext().getResources().getDrawable(R.drawable.popup_fixed_alert).mutate();
shadowDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogBackground), PorterDuff.Mode.MULTIPLY));
buttonsLayout2.setBackground(shadowDrawable);
menuContainer.addView(buttonsLayout2, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 0, forwardingMessagesParams.hasSenders ? -8 : 0, 0, 0));
changeRecipientView = new ActionBarMenuSubItem(context, true, false);
buttonsLayout2.addView(changeRecipientView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48));
changeRecipientView.setTextAndIcon(LocaleController.getString("ChangeRecipient", R.string.ChangeRecipient), R.drawable.msg_forward_replace);
sendMessagesView = new ActionBarMenuSubItem(context, false, true);
buttonsLayout2.addView(sendMessagesView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48));
sendMessagesView.setTextAndIcon(LocaleController.getString("ForwardSendMessages", R.string.ForwardSendMessages), R.drawable.msg_forward_send);
if (forwardingMessagesParams.hasSenders) {
actionItems.add(showSendersNameView);
actionItems.add(hideSendersNameView);
if (params.hasCaption) {
actionItems.add(showCaptionView);
actionItems.add(hideCaptionView);
}
}
actionItems.add(changeRecipientView);
actionItems.add(sendMessagesView);
showSendersNameView.setOnClickListener(view -> {
if (params.hideForwardSendersName) {
returnSendersNames = false;
showSendersNameView.setChecked(true);
hideSendersNameView.setChecked(false);
if (showCaptionView != null) {
showCaptionView.setChecked(true);
hideCaptionView.setChecked(false);
}
params.hideForwardSendersName = false;
params.hideCaption = false;
updateMessages();
updateSubtitle();
}
});
hideSendersNameView.setOnClickListener(view -> {
if (!params.hideForwardSendersName) {
returnSendersNames = false;
showSendersNameView.setChecked(false);
hideSendersNameView.setChecked(true);
params.hideForwardSendersName = true;
updateMessages();
updateSubtitle();
}
});
if (params.hasCaption) {
showCaptionView.setOnClickListener(view -> {
if (params.hideCaption) {
if (returnSendersNames) {
params.hideForwardSendersName = false;
}
returnSendersNames = false;
showCaptionView.setChecked(true);
hideCaptionView.setChecked(false);
showSendersNameView.setChecked(!params.hideForwardSendersName);
hideSendersNameView.setChecked(params.hideForwardSendersName);
params.hideCaption = false;
updateMessages();
updateSubtitle();
}
});
hideCaptionView.setOnClickListener(view -> {
if (!params.hideCaption) {
showCaptionView.setChecked(false);
hideCaptionView.setChecked(true);
showSendersNameView.setChecked(false);
hideSendersNameView.setChecked(true);
if (!params.hideForwardSendersName) {
params.hideForwardSendersName = true;
returnSendersNames = true;
}
params.hideCaption = true;
updateMessages();
updateSubtitle();
}
});
}
showSendersNameView.setChecked(!params.hideForwardSendersName);
hideSendersNameView.setChecked(params.hideForwardSendersName);
if (params.hasCaption) {
showCaptionView.setChecked(!params.hideCaption);
hideCaptionView.setChecked(params.hideCaption);
}
if (!params.hasSenders) {
buttonsLayout.setVisibility(View.GONE);
}
sendMessagesView.setOnClickListener(View -> didSendPressed());
changeRecipientView.setOnClickListener(view -> selectAnotherChat());
updateMessages();
updateSubtitle();
actionBar.setTitle(LocaleController.formatPluralString("PreviewForwardMessagesCount", params.selectedIds.size()));
menuScrollView.setOnTouchListener((view, motionEvent) -> {
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
dismiss(true);
}
return true;
});
setOnTouchListener((view, motionEvent) -> {
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
dismiss(true);
}
return true;
});
showing = true;
setAlpha(0);
setScaleX(0.95f);
setScaleY(0.95f);
animate().alpha(1f).scaleX(1f).setDuration(ChatListItemAnimator.DEFAULT_DURATION).setInterpolator(ChatListItemAnimator.DEFAULT_INTERPOLATOR).scaleY(1f);
updateColors();
}
private void updateSubtitle() {
if (!forwardingMessagesParams.hasSenders) {
if (forwardingMessagesParams.willSeeSenders) {
if (currentUser != null) {
actionBar.setSubtitle(LocaleController.formatString("ForwardPreviewSendersNameVisible", R.string.ForwardPreviewSendersNameVisible, ContactsController.formatName(currentUser.first_name, currentUser.last_name)));
} else {
if (ChatObject.isChannel(currentChat) && !currentChat.megagroup) {
actionBar.setSubtitle(LocaleController.getString("ForwardPreviewSendersNameVisibleChannel", R.string.ForwardPreviewSendersNameVisibleChannel));
} else {
actionBar.setSubtitle(LocaleController.getString("ForwardPreviewSendersNameVisibleGroup", R.string.ForwardPreviewSendersNameVisibleGroup));
}
}
} else {
if (currentUser != null) {
actionBar.setSubtitle(LocaleController.formatString("ForwardPreviewSendersNameVisible", R.string.ForwardPreviewSendersNameVisible, ContactsController.formatName(currentUser.first_name, currentUser.last_name)));
} else {
if (ChatObject.isChannel(currentChat) && !currentChat.megagroup) {
actionBar.setSubtitle(LocaleController.getString("ForwardPreviewSendersNameHiddenChannel", R.string.ForwardPreviewSendersNameHiddenChannel));
} else {
actionBar.setSubtitle(LocaleController.getString("ForwardPreviewSendersNameHiddenGroup", R.string.ForwardPreviewSendersNameHiddenGroup));
}
}
}
} else if (!forwardingMessagesParams.hideForwardSendersName) {
if (currentUser != null) {
actionBar.setSubtitle(LocaleController.formatString("ForwardPreviewSendersNameVisible", R.string.ForwardPreviewSendersNameVisible, ContactsController.formatName(currentUser.first_name, currentUser.last_name)));
} else {
if (ChatObject.isChannel(currentChat) && !currentChat.megagroup) {
actionBar.setSubtitle(LocaleController.getString("ForwardPreviewSendersNameVisibleChannel", R.string.ForwardPreviewSendersNameVisibleChannel));
} else {
actionBar.setSubtitle(LocaleController.getString("ForwardPreviewSendersNameVisibleGroup", R.string.ForwardPreviewSendersNameVisibleGroup));
}
}
} else {
if (currentUser != null) {
actionBar.setSubtitle(LocaleController.formatString("ForwardPreviewSendersNameHidden", R.string.ForwardPreviewSendersNameHidden, ContactsController.formatName(currentUser.first_name, currentUser.last_name)));
} else {
if (ChatObject.isChannel(currentChat) && !currentChat.megagroup) {
actionBar.setSubtitle(LocaleController.getString("ForwardPreviewSendersNameHiddenChannel", R.string.ForwardPreviewSendersNameHiddenChannel));
} else {
actionBar.setSubtitle(LocaleController.getString("ForwardPreviewSendersNameHiddenGroup", R.string.ForwardPreviewSendersNameHiddenGroup));
}
}
}
}
private void updateColors() {
}
public void dismiss(boolean canShowKeyboard) {
if (showing) {
showing = false;
animate().alpha(0).scaleX(0.95f).scaleY(0.95f).setDuration(ChatListItemAnimator.DEFAULT_DURATION).setInterpolator(ChatListItemAnimator.DEFAULT_INTERPOLATOR).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (getParent() != null) {
ViewGroup parent = (ViewGroup) getParent();
parent.removeView(ForwardingPreviewView.this);
}
}
});
onDismiss(canShowKeyboard);
}
}
protected void onDismiss(boolean canShowKeyboard) {
}
boolean updateAfterAnimations;
private void updateMessages() {
if (itemAnimator.isRunning()) {
updateAfterAnimations = true;
return;
}
for (int i = 0; i < forwardingMessagesParams.previewMessages.size(); i++) {
MessageObject messageObject = forwardingMessagesParams.previewMessages.get(i);
messageObject.forceUpdate = true;
if (!forwardingMessagesParams.hideForwardSendersName) {
messageObject.messageOwner.flags |= TLRPC.MESSAGE_FLAG_FWD;
} else {
messageObject.messageOwner.flags &= ~TLRPC.MESSAGE_FLAG_FWD;
}
if (forwardingMessagesParams.hideCaption) {
messageObject.caption = null;
} else {
messageObject.generateCaption();
}
if (messageObject.isPoll()) {
ForwardingMessagesParams.PreviewMediaPoll mediaPoll = (ForwardingMessagesParams.PreviewMediaPoll) messageObject.messageOwner.media;
mediaPoll.results.total_voters = forwardingMessagesParams.hideCaption ? 0 : mediaPoll.totalVotersCached;
}
}
for (int i = 0; i < forwardingMessagesParams.pollChoosenAnswers.size(); i++) {
forwardingMessagesParams.pollChoosenAnswers.get(i).chosen = !forwardingMessagesParams.hideForwardSendersName;
}
for (int i = 0; i < forwardingMessagesParams.groupedMessagesMap.size(); i++) {
itemAnimator.groupWillChanged(forwardingMessagesParams.groupedMessagesMap.valueAt(i));
}
adapter.notifyItemRangeChanged(0, forwardingMessagesParams.previewMessages.size());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int maxActionWidth = 0;
isLandscapeMode = MeasureSpec.getSize(widthMeasureSpec) > MeasureSpec.getSize(heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
if (isLandscapeMode) {
width = (int) (MeasureSpec.getSize(widthMeasureSpec) * 0.38f);
}
for (int i = 0; i < actionItems.size(); i++) {
actionItems.get(i).measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED));
if (actionItems.get(i).getMeasuredWidth() > maxActionWidth) {
maxActionWidth = actionItems.get(i).getMeasuredWidth();
}
}
buttonsLayout.getBackground().getPadding(rect);
int buttonsWidth = maxActionWidth + rect.left + rect.right;
buttonsLayout.getLayoutParams().width = buttonsWidth;
buttonsLayout2.getLayoutParams().width = buttonsWidth;
buttonsLayout.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED));
buttonsLayout2.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED));
((MarginLayoutParams) chatListView.getLayoutParams()).topMargin = ActionBar.getCurrentActionBarHeight();
if (isLandscapeMode) {
chatPreviewContainer.getLayoutParams().height = LayoutHelper.MATCH_PARENT;
((MarginLayoutParams) chatPreviewContainer.getLayoutParams()).topMargin = AndroidUtilities.dp(8);
((MarginLayoutParams) chatPreviewContainer.getLayoutParams()).bottomMargin = AndroidUtilities.dp(8);
chatPreviewContainer.getLayoutParams().width = (int) Math.min(MeasureSpec.getSize(widthMeasureSpec), Math.max(AndroidUtilities.dp(340), MeasureSpec.getSize(widthMeasureSpec) * 0.6f));
menuScrollView.getLayoutParams().height = LayoutHelper.MATCH_PARENT;
} else {
((MarginLayoutParams) chatPreviewContainer.getLayoutParams()).topMargin = 0;
((MarginLayoutParams) chatPreviewContainer.getLayoutParams()).bottomMargin = 0;
chatPreviewContainer.getLayoutParams().height = MeasureSpec.getSize(heightMeasureSpec) - AndroidUtilities.dp(16 - 10) - buttonsLayout.getMeasuredHeight() - buttonsLayout2.getMeasuredHeight();
if (chatPreviewContainer.getLayoutParams().height < MeasureSpec.getSize(heightMeasureSpec) * 0.5f) {
chatPreviewContainer.getLayoutParams().height = (int) (MeasureSpec.getSize(heightMeasureSpec) * 0.5f);
}
chatPreviewContainer.getLayoutParams().width = LayoutHelper.MATCH_PARENT;
menuScrollView.getLayoutParams().height = MeasureSpec.getSize(heightMeasureSpec) - chatPreviewContainer.getLayoutParams().height;
}
int size = MeasureSpec.getSize(widthMeasureSpec) + MeasureSpec.getSize(heightMeasureSpec) << 16;
if (lastSize != size) {
for (int i = 0; i < forwardingMessagesParams.previewMessages.size(); i++) {
if (isLandscapeMode) {
forwardingMessagesParams.previewMessages.get(i).parentWidth = chatPreviewContainer.getLayoutParams().width;
} else {
forwardingMessagesParams.previewMessages.get(i).parentWidth = MeasureSpec.getSize(widthMeasureSpec) - AndroidUtilities.dp(16);
}
forwardingMessagesParams.previewMessages.get(i).resetLayout();
forwardingMessagesParams.previewMessages.get(i).forceUpdate = true;
if (adapter != null) {
adapter.notifyDataSetChanged();
}
}
firstLayout = true;
}
lastSize = size;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
int lastSize;
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
updatePositions();
firstLayout = false;
}
private void updatePositions() {
int lastTopOffset = chatTopOffset;
float lastYOffset = yOffset;
if (!isLandscapeMode) {
if (chatListView.getChildCount() == 0 || chatListView.getChildCount() > forwardingMessagesParams.previewMessages.size()) {
chatTopOffset = 0;
} else {
int minTop = chatListView.getChildAt(0).getTop();
for (int i = 1; i < chatListView.getChildCount(); i++) {
if (chatListView.getChildAt(i).getTop() < minTop) {
minTop = chatListView.getChildAt(i).getTop();
}
}
minTop -= AndroidUtilities.dp(4);
if (minTop < 0) {
chatTopOffset = 0;
} else {
chatTopOffset = minTop;
}
}
float totalViewsHeight = buttonsLayout.getMeasuredHeight() + buttonsLayout2.getMeasuredHeight() - AndroidUtilities.dp(8) + (chatPreviewContainer.getMeasuredHeight() - chatTopOffset);
float totalHeight = getMeasuredHeight() - AndroidUtilities.dp(16);
yOffset = AndroidUtilities.dp(8) + (totalHeight - totalViewsHeight) / 2 - chatTopOffset;
if (yOffset > AndroidUtilities.dp(8)) {
yOffset = AndroidUtilities.dp(8);
}
float buttonX = getMeasuredWidth() - menuScrollView.getMeasuredWidth();
menuScrollView.setTranslationX(buttonX);
} else {
yOffset = 0;
chatTopOffset = 0;
menuScrollView.setTranslationX(chatListView.getMeasuredWidth() + AndroidUtilities.dp(8));
}
if (!firstLayout && (chatTopOffset != lastTopOffset || yOffset != lastYOffset)) {
if (offsetsAnimator != null) {
offsetsAnimator.cancel();
}
offsetsAnimator = ValueAnimator.ofFloat(0, 1f);
offsetsAnimator.addUpdateListener(valueAnimator -> {
float p = (float) valueAnimator.getAnimatedValue();
currentTopOffset = (int) (lastTopOffset * (1f - p) + chatTopOffset * p);
currentYOffset = lastYOffset * (1f - p) + yOffset * p;
setOffset(currentYOffset, currentTopOffset);
});
offsetsAnimator.setDuration(ChatListItemAnimator.DEFAULT_DURATION);
offsetsAnimator.setInterpolator(ChatListItemAnimator.DEFAULT_INTERPOLATOR);
offsetsAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
offsetsAnimator = null;
setOffset(yOffset, chatTopOffset);
}
});
AndroidUtilities.runOnUIThread(changeBoundsRunnable, 50);
currentTopOffset = lastTopOffset;
currentYOffset = lastYOffset;
setOffset(lastYOffset, lastTopOffset);
} else if (firstLayout) {
setOffset(currentYOffset = yOffset, currentTopOffset = chatTopOffset);
}
}
private void setOffset(float yOffset, int chatTopOffset) {
if (isLandscapeMode) {
actionBar.setTranslationY(0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
chatPreviewContainer.invalidateOutline();
}
chatPreviewContainer.setTranslationY(0);
menuScrollView.setTranslationY(0);
} else {
actionBar.setTranslationY(chatTopOffset);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
chatPreviewContainer.invalidateOutline();
}
chatPreviewContainer.setTranslationY(yOffset);
menuScrollView.setTranslationY(yOffset + chatPreviewContainer.getMeasuredHeight() - AndroidUtilities.dp(2));
}
}
public boolean isShowing() {
return showing;
}
private class Adapter extends RecyclerView.Adapter {
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ChatMessageCell chatMessageCell = new ChatMessageCell(parent.getContext());
return new RecyclerListView.Holder(chatMessageCell);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
ChatMessageCell cell = (ChatMessageCell) holder.itemView;
cell.setParentViewSize(chatListView.getMeasuredWidth(), chatListView.getMeasuredHeight());
int id = cell.getMessageObject() != null ? cell.getMessageObject().getId() : 0;
cell.setMessageObject(forwardingMessagesParams.previewMessages.get(position), forwardingMessagesParams.groupedMessagesMap.get(forwardingMessagesParams.previewMessages.get(position).getGroupId()), true, true);
cell.setDelegate(new ChatMessageCell.ChatMessageCellDelegate() {
});
if (forwardingMessagesParams.previewMessages.size() > 1) {
cell.setCheckBoxVisible(true, false);
boolean animated = id == forwardingMessagesParams.previewMessages.get(position).getId();
boolean checked = forwardingMessagesParams.selectedIds.get(forwardingMessagesParams.previewMessages.get(position).getId(), false);
cell.setChecked(checked, checked, animated);
}
}
@Override
public int getItemCount() {
return forwardingMessagesParams.previewMessages.size();
}
}
protected void selectAnotherChat() {
}
protected void didSendPressed() {
}
private MessageObject.GroupedMessages getValidGroupedMessage(MessageObject message) {
MessageObject.GroupedMessages groupedMessages = null;
if (message.getGroupId() != 0) {
groupedMessages = forwardingMessagesParams.groupedMessagesMap.get(message.getGroupId());
if (groupedMessages != null && (groupedMessages.messages.size() <= 1 || groupedMessages.positions.get(message) == null)) {
groupedMessages = null;
}
}
return groupedMessages;
}
}

View file

@ -122,7 +122,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent
private StaticLayout timeLayout;
private RectF rect = new RectF();
private boolean scheduleRunnableScheduled;
private Runnable updateScheduleTimeRunnable = new Runnable() {
private final Runnable updateScheduleTimeRunnable = new Runnable() {
@Override
public void run() {
if (gradientTextPaint == null || !(fragment instanceof ChatActivity)) {
@ -1382,6 +1382,9 @@ public class FragmentContextView extends FrameLayout implements NotificationCent
return;
}
if (visible) {
if (playbackSpeedButton != null && playbackSpeedButton.isSubMenuShowing()) {
playbackSpeedButton.toggleSubMenu();
}
visible = false;
if (create) {
if (getVisibility() != GONE) {
@ -1802,6 +1805,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent
updateStyle(4);
ChatObject.Call call = ((ChatActivity) fragment).getGroupCall();
TLRPC.Chat chat = ((ChatActivity) fragment).getCurrentChat();
if (call.isScheduled()) {
if (gradientPaint == null) {
gradientTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
@ -1818,7 +1822,11 @@ public class FragmentContextView extends FrameLayout implements NotificationCent
if (!TextUtils.isEmpty(call.call.title)) {
titleTextView.setText(call.call.title, false);
} else {
titleTextView.setText(LocaleController.getString("VoipGroupScheduledVoiceChat", R.string.VoipGroupScheduledVoiceChat), false);
if (ChatObject.isChannelOrGiga(chat)) {
titleTextView.setText(LocaleController.getString("VoipChannelScheduledVoiceChat", R.string.VoipChannelScheduledVoiceChat), false);
} else {
titleTextView.setText(LocaleController.getString("VoipGroupScheduledVoiceChat", R.string.VoipGroupScheduledVoiceChat), false);
}
}
subtitleTextView.setText(LocaleController.formatStartsTime(call.call.schedule_date, 4), false);
if (!scheduleRunnableScheduled) {
@ -1828,7 +1836,11 @@ public class FragmentContextView extends FrameLayout implements NotificationCent
} else {
timeLayout = null;
joinButton.setVisibility(VISIBLE);
titleTextView.setText(LocaleController.getString("VoipGroupVoiceChat", R.string.VoipGroupVoiceChat), false);
if (ChatObject.isChannelOrGiga(chat)) {
titleTextView.setText(LocaleController.getString("VoipChannelVoiceChat", R.string.VoipChannelVoiceChat), false);
} else {
titleTextView.setText(LocaleController.getString("VoipGroupVoiceChat", R.string.VoipGroupVoiceChat), false);
}
if (call.call.participants_count == 0) {
subtitleTextView.setText(LocaleController.getString("MembersTalkingNobody", R.string.MembersTalkingNobody), false);
} else {
@ -2080,7 +2092,12 @@ public class FragmentContextView extends FrameLayout implements NotificationCent
titleTextView.setText(service.groupCall.call.title, false);
} else {
if (fragment instanceof ChatActivity && ((ChatActivity) fragment).getCurrentChat() != null && ((ChatActivity) fragment).getCurrentChat().id == service.getChat().id) {
titleTextView.setText(LocaleController.getString("VoipGroupViewVoiceChat", R.string.VoipGroupViewVoiceChat), false);
TLRPC.Chat chat = ((ChatActivity) fragment).getCurrentChat();
if (ChatObject.isChannelOrGiga(chat)) {
titleTextView.setText(LocaleController.getString("VoipChannelViewVoiceChat", R.string.VoipChannelViewVoiceChat), false);
} else {
titleTextView.setText(LocaleController.getString("VoipGroupViewVoiceChat", R.string.VoipGroupViewVoiceChat), false);
}
} else {
titleTextView.setText(service.getChat().title, false);
}

View file

@ -22,6 +22,7 @@ import android.widget.TextView;
import androidx.core.graphics.ColorUtils;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ChatObject;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.ImageLocation;
import org.telegram.messenger.LocaleController;
@ -75,7 +76,12 @@ public class GroupCallPipAlertView extends LinearLayout implements VoIPService.S
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
info.addAction(new AccessibilityNodeInfo.AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, LocaleController.getString("VoipGroupOpenVoiceChat", R.string.VoipGroupOpenVoiceChat)));
VoIPService service = VoIPService.getSharedInstance();
if (service != null && ChatObject.isChannelOrGiga(service.getChat())) {
info.addAction(new AccessibilityNodeInfo.AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, LocaleController.getString("VoipChannelOpenVoiceChat", R.string.VoipChannelOpenVoiceChat)));
} else {
info.addAction(new AccessibilityNodeInfo.AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, LocaleController.getString("VoipGroupOpenVoiceChat", R.string.VoipGroupOpenVoiceChat)));
}
}
}
};

View file

@ -407,7 +407,13 @@ public class GroupCallPipButton extends FrameLayout implements NotificationCente
}
wavesEnter = showWaves ? 1f : 0f;
}
String contentDescription = LocaleController.getString("VoipGroupVoiceChat", R.string.VoipGroupVoiceChat);
String contentDescription;
VoIPService voIPService = VoIPService.getSharedInstance();
if (voIPService != null && ChatObject.isChannelOrGiga(voIPService.getChat())) {
contentDescription = LocaleController.getString("VoipChannelVoiceChat", R.string.VoipChannelVoiceChat);
} else {
contentDescription = LocaleController.getString("VoipGroupVoiceChat", R.string.VoipGroupVoiceChat);
}
if (state == MUTE_BUTTON_STATE_UNMUTE) {
contentDescription += ", " + LocaleController.getString("VoipTapToMute", R.string.VoipTapToMute);
} else if (state == MUTE_BUTTON_STATE_RECONNECT) {

View file

@ -0,0 +1,346 @@
package org.telegram.ui.Components;
import android.app.Dialog;
import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Shader;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.os.Parcelable;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ChatObject;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.R;
import org.telegram.messenger.SvgHelper;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.BottomSheet;
import org.telegram.ui.ActionBar.Theme;
import androidx.core.graphics.ColorUtils;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
public class GroupCallRecordAlert extends BottomSheet {
private ViewPager viewPager;
private TextView positiveButton;
private LinearLayout titlesLayout;
private TextView[] titles;
private float pageOffset;
private int currentPage;
public GroupCallRecordAlert(Context context, TLRPC.Chat chat) {
super(context, false);
int color = Theme.getColor(Theme.key_voipgroup_inviteMembersBackground);
shadowDrawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY));
containerView = new FrameLayout(context) {
boolean ignoreLayout;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
boolean isLandscape = View.MeasureSpec.getSize(widthMeasureSpec) > View.MeasureSpec.getSize(heightMeasureSpec);
ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) positiveButton.getLayoutParams();
if (isLandscape) {
marginLayoutParams.rightMargin = marginLayoutParams.leftMargin = AndroidUtilities.dp(80);
} else {
marginLayoutParams.rightMargin = marginLayoutParams.leftMargin = AndroidUtilities.dp(16);
}
int width = MeasureSpec.getSize(widthMeasureSpec);
int padding = (width - AndroidUtilities.dp(200)) / 2;
viewPager.setPadding(padding, 0, padding, 0);
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(370), MeasureSpec.EXACTLY));
measureChildWithMargins(titlesLayout, View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), 0, View.MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(64), View.MeasureSpec.EXACTLY), 0);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
updateTitlesLayout();
}
@Override
public void requestLayout() {
if (ignoreLayout) {
return;
}
super.requestLayout();
}
};
containerView.setWillNotDraw(false);
containerView.setClipChildren(false);
containerView.setBackgroundDrawable(shadowDrawable);
containerView.setPadding(backgroundPaddingLeft, 0, backgroundPaddingLeft, 0);
TextView titleTextView = new TextView(getContext());
if (ChatObject.isChannelOrGiga(chat)) {
titleTextView.setText(LocaleController.getString("VoipChannelRecordVoiceChat", R.string.VoipChannelRecordVoiceChat));
} else {
titleTextView.setText(LocaleController.getString("VoipRecordVoiceChat", R.string.VoipRecordVoiceChat));
}
titleTextView.setTextColor(0xffffffff);
titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20);
titleTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
titleTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP);
containerView.addView(titleTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 24, 29, 24, 0));
TextView infoTextView = new TextView(getContext());
infoTextView.setText(LocaleController.getString("VoipRecordVoiceChatInfo", R.string.VoipRecordVoiceChatInfo));
infoTextView.setTextColor(0xffffffff);
infoTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
infoTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP);
containerView.addView(infoTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 24, 62, 24, 0));
titles = new TextView[3];
viewPager = new ViewPager(context);
viewPager.setClipChildren(false);
viewPager.setOffscreenPageLimit(4);
viewPager.setClipToPadding(false);
AndroidUtilities.setViewPagerEdgeEffectColor(viewPager, 0x7f000000);
viewPager.setAdapter(new Adapter());
viewPager.setPageMargin(0);
containerView.addView(viewPager, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER_HORIZONTAL, 0, 100, 0, 130));
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
currentPage = position;
pageOffset = positionOffset;
updateTitlesLayout();
}
@Override
public void onPageSelected(int i) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
View leftView = new View(getContext());
leftView.setBackground(new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, new int[]{color, 0}));
containerView.addView(leftView, LayoutHelper.createFrame(120, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 100, 0, 130));
View rightView = new View(getContext());
rightView.setBackground(new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, new int[]{0, color}));
containerView.addView(rightView, LayoutHelper.createFrame(120, LayoutHelper.MATCH_PARENT, Gravity.RIGHT | Gravity.TOP, 0, 100, 0, 130));
positiveButton = new TextView(getContext()) {
private Paint[] gradientPaint = new Paint[titles.length];
{
for (int a = 0; a < gradientPaint.length; a++) {
gradientPaint[a] = new Paint(Paint.ANTI_ALIAS_FLAG);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
for (int a = 0; a < gradientPaint.length; a++) {
int color1;
int color2;
int color3;
if (a == 0) {
color1 = 0xff57A4FE;
color2 = 0xff766EE9;
color3 = 0;
} else if (a == 1) {
color1 = 0xff77E55C;
color2 = 0xff56C7FE;
color3 = 0;
} else {
color1 = 0xff766EE9;
color2 = 0xffF05459;
color3 = 0xffE4A756;
}
Shader gradient;
if (color3 != 0) {
gradient = new LinearGradient(0, 0, getMeasuredWidth(), 0, new int[]{color1, color2, color3}, null, Shader.TileMode.CLAMP);
} else {
gradient = new LinearGradient(0, 0, getMeasuredWidth(), 0, new int[]{color1, color2}, null, Shader.TileMode.CLAMP);
}
gradientPaint[a].setShader(gradient);
}
}
@Override
protected void onDraw(Canvas canvas) {
AndroidUtilities.rectTmp.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
gradientPaint[currentPage].setAlpha(255);
canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(6), AndroidUtilities.dp(6), gradientPaint[currentPage]);
if (pageOffset > 0 && currentPage + 1 < gradientPaint.length) {
gradientPaint[currentPage + 1].setAlpha((int) (255 * pageOffset));
canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(6), AndroidUtilities.dp(6), gradientPaint[currentPage + 1]);
}
super.onDraw(canvas);
}
};
positiveButton.setMinWidth(AndroidUtilities.dp(64));
positiveButton.setTag(Dialog.BUTTON_POSITIVE);
positiveButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
positiveButton.setTextColor(Theme.getColor(Theme.key_voipgroup_nameText));
positiveButton.setGravity(Gravity.CENTER);
positiveButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
positiveButton.setText(LocaleController.getString("VoipRecordStart", R.string.VoipRecordStart));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
positiveButton.setForeground(Theme.createSimpleSelectorRoundRectDrawable(AndroidUtilities.dp(6), Color.TRANSPARENT, ColorUtils.setAlphaComponent(Theme.getColor(Theme.key_voipgroup_nameText), (int) (255 * 0.3f))));
}
positiveButton.setPadding(0, AndroidUtilities.dp(12), 0, AndroidUtilities.dp(12));
positiveButton.setOnClickListener(view -> {
onStartRecord(currentPage);
dismiss();
});
containerView.addView(positiveButton, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM, 0, 0, 0, 64));
titlesLayout = new LinearLayout(context);
containerView.addView(titlesLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 64, Gravity.BOTTOM));
for (int a = 0; a < titles.length; a++) {
titles[a] = new TextView(context);
titles[a].setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12);
titles[a].setTextColor(0xffffffff);
titles[a].setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
titles[a].setPadding(AndroidUtilities.dp(10), 0, AndroidUtilities.dp(10), 0);
titles[a].setGravity(Gravity.CENTER_VERTICAL);
titles[a].setSingleLine(true);
titlesLayout.addView(titles[a], LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT));
if (a == 0) {
titles[a].setText(LocaleController.getString("VoipRecordAudio", R.string.VoipRecordAudio));
} else if (a == 1) {
titles[a].setText(LocaleController.getString("VoipRecordPortrait", R.string.VoipRecordPortrait));
} else {
titles[a].setText(LocaleController.getString("VoipRecordLandscape", R.string.VoipRecordLandscape));
}
int num = a;
titles[a].setOnClickListener(view -> viewPager.setCurrentItem(num, true));
}
}
private void updateTitlesLayout() {
View current = titles[currentPage];
View next = currentPage < titles.length - 1 ? titles[currentPage + 1] : null;
float cx = containerView.getMeasuredWidth() / 2;
float currentCx = current.getLeft() + current.getMeasuredWidth() / 2;
float tx = containerView.getMeasuredWidth() / 2 - currentCx;
if (next != null) {
float nextCx = next.getLeft() + next.getMeasuredWidth() / 2;
tx -= (nextCx - currentCx) * pageOffset;
}
for (int a = 0; a < titles.length; a++) {
float alpha;
float scale;
if (a < currentPage || a > currentPage + 1) {
alpha = 0.7f;
scale = 0.9f;
} else if (a == currentPage) {
alpha = 1.0f - 0.3f * pageOffset;
scale = 1.0f - 0.1f * pageOffset;
} else {
alpha = 0.7f + 0.3f * pageOffset;
scale = 0.9f + 0.1f * pageOffset;
}
titles[a].setAlpha(alpha);
titles[a].setScaleX(scale);
titles[a].setScaleY(scale);
}
titlesLayout.setTranslationX(tx);
positiveButton.invalidate();
}
protected void onStartRecord(int type) {
}
private class Adapter extends PagerAdapter {
@Override
public int getCount() {
return titles.length;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
View view;
ImageView imageView = new ImageView(getContext());
imageView.setTag(position);
imageView.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0);
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
imageView.setLayoutParams(new ViewGroup.LayoutParams(AndroidUtilities.dp(200), ViewGroup.LayoutParams.MATCH_PARENT));
view = imageView;
int res;
if (position == 0) {
res = R.raw.record_audio;
} else if (position == 1) {
res = R.raw.record_video_p;
} else {
res = R.raw.record_video_l;
}
String svg = RLottieDrawable.readRes(null, res);
SvgHelper.SvgDrawable drawable = SvgHelper.getDrawable(svg);
drawable.setAspectFill(false);
imageView.setImageDrawable(drawable);
if (view.getParent() != null) {
ViewGroup parent = (ViewGroup) view.getParent();
parent.removeView(view);
}
container.addView(view, 0);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view.equals(object);
}
@Override
public void restoreState(Parcelable arg0, ClassLoader arg1) {
}
@Override
public Parcelable saveState() {
return null;
}
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
if (observer != null) {
super.unregisterDataSetObserver(observer);
}
}
}
}

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