mirror of
https://github.com/DrKLO/Telegram.git
synced 2024-12-21 22:15:16 +01:00
Update to 8.0.0 (2406)
This commit is contained in:
parent
ab221dafad
commit
368822d20f
191 changed files with 12004 additions and 2919 deletions
|
@ -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
|
||||
|
|
|
@ -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']
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>();
|
||||
}
|
||||
|
||||
}
|
41
TMessagesProj/jni/voip/tgcalls/group/AudioStreamingPart.h
Normal file
41
TMessagesProj/jni/voip/tgcalls/group/AudioStreamingPart.h
Normal 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
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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};
|
||||
|
|
869
TMessagesProj/jni/voip/tgcalls/group/StreamingMediaContext.cpp
Normal file
869
TMessagesProj/jni/voip/tgcalls/group/StreamingMediaContext.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
53
TMessagesProj/jni/voip/tgcalls/group/StreamingMediaContext.h
Normal file
53
TMessagesProj/jni/voip/tgcalls/group/StreamingMediaContext.h
Normal 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
|
|
@ -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
|
659
TMessagesProj/jni/voip/tgcalls/group/VideoStreamingPart.cpp
Normal file
659
TMessagesProj/jni/voip/tgcalls/group/VideoStreamingPart.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
53
TMessagesProj/jni/voip/tgcalls/group/VideoStreamingPart.h
Normal file
53
TMessagesProj/jni/voip/tgcalls/group/VideoStreamingPart.h
Normal 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
|
|
@ -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) {
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -203,7 +203,6 @@ public class MediaDataController extends BaseController {
|
|||
loadingStickers[a] = false;
|
||||
stickersLoaded[a] = false;
|
||||
}
|
||||
featuredStickerSets.clear();
|
||||
loadingPinnedMessages.clear();
|
||||
loadFeaturedDate = 0;
|
||||
loadFeaturedHash = 0;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(',');
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
@ -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() {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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..."));
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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..."));
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue