From 8309ac8d7cb23a36ac34c797bb2b7376956076e1 Mon Sep 17 00:00:00 2001 From: xaxtix Date: Thu, 21 Apr 2022 21:03:20 +0300 Subject: [PATCH] Update to 8.7.1 --- TMessagesProj/build.gradle | 4 +- .../jni/voip/tgcalls/EncryptedConnection.cpp | 180 +- .../jni/voip/tgcalls/EncryptedConnection.h | 12 + TMessagesProj/jni/voip/tgcalls/Instance.h | 1 + TMessagesProj/jni/voip/tgcalls/Message.cpp | 25 + TMessagesProj/jni/voip/tgcalls/Message.h | 16 +- .../jni/voip/tgcalls/NetworkManager.cpp | 4 +- .../jni/voip/tgcalls/StaticThreads.cpp | 14 +- .../tgcalls/group/GroupNetworkManager.cpp | 4 +- .../jni/voip/tgcalls/v2/InstanceV2Impl.cpp | 148 +- .../tgcalls/v2/InstanceV2ReferenceImpl.cpp | 1476 ++++++++++++----- .../voip/tgcalls/v2/NativeNetworkingImpl.cpp | 49 +- .../voip/tgcalls/v2/NativeNetworkingImpl.h | 4 +- .../jni/voip/tgcalls/v2/Signaling.cpp | 7 + TMessagesProj/jni/voip/tgcalls/v2/Signaling.h | 1 + .../telegram/messenger/AndroidUtilities.java | 2 +- .../org/telegram/messenger/BuildVars.java | 4 +- .../messenger/DownloadController.java | 4 +- .../telegram/messenger/MediaController.java | 1 + .../org/telegram/messenger/MessageObject.java | 2 +- .../messenger/MessagesController.java | 29 +- .../messenger/NotificationCenter.java | 2 + .../messenger/NotificationsController.java | 8 +- .../telegram/messenger/SharedPrefsHelper.java | 12 +- .../messenger/ringtone/RingtoneDataStore.java | 8 +- .../video/MediaCodecVideoConvertor.java | 12 +- .../telegram/ui/ActionBar/ActionBarMenu.java | 13 +- .../ui/ActionBar/ActionBarMenuItem.java | 4 +- .../ui/ActionBar/ActionBarPopupWindow.java | 15 +- .../telegram/ui/ActionBar/BottomSheet.java | 4 - .../org/telegram/ui/CalendarActivity.java | 4 +- .../org/telegram/ui/CameraScanActivity.java | 5 +- .../org/telegram/ui/Cells/DialogCell.java | 3 +- .../java/org/telegram/ui/ChatActivity.java | 29 +- .../org/telegram/ui/ChooseSpeedLayout.java | 80 + .../telegram/ui/Components/AlertsCreator.java | 151 +- .../ui/Components/BotCommandsMenuView.java | 3 + .../ui/Components/BotWebViewContainer.java | 228 ++- .../Components/BotWebViewMenuContainer.java | 142 +- .../ui/Components/BotWebViewSheet.java | 79 +- .../ChatActivityBotWebViewButton.java | 16 +- .../ui/Components/ChatActivityEnterView.java | 34 +- .../ui/Components/ChatAttachAlert.java | 83 +- .../ChatAttachAlertBotWebViewLayout.java | 118 +- .../ChatNotificationsPopupWrapper.java | 25 +- .../ui/Components/CrossfadeDrawable.java | 2 +- .../Components/MotionBackgroundDrawable.java | 2 +- .../ui/Components/PopupSwipeBackLayout.java | 11 +- .../Components/SearchDownloadsContainer.java | 49 +- .../ui/Components/SharedMediaLayout.java | 12 + .../telegram/ui/Components/TimerDrawable.java | 5 +- .../org/telegram/ui/DownloadProgressIcon.java | 13 +- .../java/org/telegram/ui/IntroActivity.java | 6 +- .../java/org/telegram/ui/LaunchActivity.java | 6 +- .../java/org/telegram/ui/LoginActivity.java | 2 +- .../NotificationsCustomSettingsActivity.java | 7 +- .../ui/NotificationsSoundActivity.java | 28 +- .../java/org/telegram/ui/PhotoViewer.java | 113 +- .../java/org/telegram/ui/ProfileActivity.java | 39 +- .../ui/ProfileNotificationsActivity.java | 80 +- .../org/telegram/ui/SessionsActivity.java | 5 - .../res/drawable-hdpi/msg_mini_autodelete.png | Bin 755 -> 898 bytes .../msg_mini_autodelete_empty.png | Bin 638 -> 809 bytes .../res/drawable-mdpi/msg_mini_autodelete.png | Bin 509 -> 586 bytes .../msg_mini_autodelete_empty.png | Bin 444 -> 532 bytes .../drawable-xhdpi/msg_mini_autodelete.png | Bin 984 -> 1149 bytes .../msg_mini_autodelete_empty.png | Bin 829 -> 1030 bytes .../drawable-xxhdpi/msg_mini_autodelete.png | Bin 1438 -> 1713 bytes .../msg_mini_autodelete_empty.png | Bin 1190 -> 1475 bytes TMessagesProj/src/main/res/values/strings.xml | 78 +- 70 files changed, 2480 insertions(+), 1043 deletions(-) create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/ChooseSpeedLayout.java diff --git a/TMessagesProj/build.gradle b/TMessagesProj/build.gradle index b700ac90aff..3727bdd7783 100644 --- a/TMessagesProj/build.gradle +++ b/TMessagesProj/build.gradle @@ -300,7 +300,7 @@ android { } } - defaultConfig.versionCode = 2622 + defaultConfig.versionCode = 2629 applicationVariants.all { variant -> variant.outputs.all { output -> @@ -319,7 +319,7 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 30 - versionName "8.7.0" + versionName "8.7.1" vectorDrawables.generatedDensities = ['mdpi', 'hdpi', 'xhdpi', 'xxhdpi'] diff --git a/TMessagesProj/jni/voip/tgcalls/EncryptedConnection.cpp b/TMessagesProj/jni/voip/tgcalls/EncryptedConnection.cpp index 15f2e86d1b5..81b528212f1 100644 --- a/TMessagesProj/jni/voip/tgcalls/EncryptedConnection.cpp +++ b/TMessagesProj/jni/voip/tgcalls/EncryptedConnection.cpp @@ -33,6 +33,7 @@ constexpr auto kServiceCauseResend = 2; static constexpr uint8_t kAckId = uint8_t(-1); static constexpr uint8_t kEmptyId = uint8_t(-2); +static constexpr uint8_t kCustomId = uint8_t(127); void AppendSeq(rtc::CopyOnWriteBuffer &buffer, uint32_t seq) { const auto bytes = rtc::HostToNetwork32(seq); @@ -68,6 +69,22 @@ bool ConstTimeIsDifferent(const void *a, const void *b, size_t size) { return different; } +rtc::CopyOnWriteBuffer SerializeRawMessageWithSeq( + const rtc::CopyOnWriteBuffer &message, + uint32_t seq, + bool singleMessagePacket) { + rtc::ByteBufferWriter writer; + writer.WriteUInt32(seq); + writer.WriteUInt8(kCustomId); + writer.WriteUInt32((uint32_t)message.size()); + writer.WriteBytes((const char *)message.data(), message.size()); + + auto result = rtc::CopyOnWriteBuffer(); + result.AppendData(writer.Data(), writer.Length()); + + return result; +} + } // namespace EncryptedConnection::EncryptedConnection( @@ -142,7 +159,7 @@ auto EncryptedConnection::prepareForSending(const Message &message) const auto messageRequiresAck = absl::visit([](const auto &data) { return std::decay_t::kRequiresAck; }, message.data); - + // If message requires ack, then we can't serialize it as a single // message packet, because later it may be sent as a part of big packet. const auto singleMessagePacket = !haveAdditionalMessages() && !messageRequiresAck; @@ -152,6 +169,25 @@ auto EncryptedConnection::prepareForSending(const Message &message) } const auto seq = *maybeSeq; auto serialized = SerializeMessageWithSeq(message, seq, singleMessagePacket); + + return prepareForSendingMessageInternal(serialized, seq, messageRequiresAck); +} + +absl::optional EncryptedConnection::prepareForSendingRawMessage(rtc::CopyOnWriteBuffer &message, bool messageRequiresAck) { + // If message requires ack, then we can't serialize it as a single + // message packet, because later it may be sent as a part of big packet. + const auto singleMessagePacket = !haveAdditionalMessages() && !messageRequiresAck; + const auto maybeSeq = computeNextSeq(messageRequiresAck, singleMessagePacket); + if (!maybeSeq) { + return absl::nullopt; + } + const auto seq = *maybeSeq; + auto serialized = SerializeRawMessageWithSeq(message, seq, singleMessagePacket); + + return prepareForSendingMessageInternal(serialized, seq, messageRequiresAck); +} + +absl::optional EncryptedConnection::prepareForSendingMessageInternal(rtc::CopyOnWriteBuffer &serialized, uint32_t seq, bool messageRequiresAck) { if (!enoughSpaceInPacket(serialized, 0)) { return LogError("Too large packet: ", std::to_string(serialized.size())); } @@ -404,6 +440,41 @@ auto EncryptedConnection::handleIncomingPacket(const char *bytes, size_t size) return processPacket(decryptionBuffer, incomingSeq); } +absl::optional EncryptedConnection::handleIncomingRawPacket(const char *bytes, size_t size) { + if (size < 21 || size > kMaxIncomingPacketSize) { + return LogError("Bad incoming packet size: ", std::to_string(size)); + } + + const auto x = (_key.isOutgoing ? 8 : 0) + (_type == Type::Signaling ? 128 : 0); + const auto key = _key.value->data(); + const auto msgKey = reinterpret_cast(bytes); + const auto encryptedData = msgKey + 16; + const auto dataSize = size - 16; + + auto aesKeyIv = PrepareAesKeyIv(key, msgKey, x); + + auto decryptionBuffer = rtc::Buffer(dataSize); + AesProcessCtr( + MemorySpan{ encryptedData, dataSize }, + decryptionBuffer.data(), + std::move(aesKeyIv)); + + const auto msgKeyLarge = ConcatSHA256( + MemorySpan{ key + 88 + x, 32 }, + MemorySpan{ decryptionBuffer.data(), decryptionBuffer.size() }); + if (ConstTimeIsDifferent(msgKeyLarge.data() + 8, msgKey, 16)) { + return LogError("Bad incoming data hash."); + } + + const auto incomingSeq = ReadSeq(decryptionBuffer.data()); + const auto incomingCounter = CounterFromSeq(incomingSeq); + if (!registerIncomingCounter(incomingCounter)) { + // We've received that packet already. + return LogError("Already handled packet received.", std::to_string(incomingCounter)); + } + return processRawPacket(decryptionBuffer, incomingSeq); +} + auto EncryptedConnection::processPacket( const rtc::Buffer &fullBuffer, uint32_t packetSeq) @@ -490,6 +561,98 @@ auto EncryptedConnection::processPacket( return result; } +auto EncryptedConnection::processRawPacket( + const rtc::Buffer &fullBuffer, + uint32_t packetSeq) +-> absl::optional { + assert(fullBuffer.size() >= 5); + + auto additionalMessage = false; + auto firstMessageRequiringAck = true; + auto newRequiringAckReceived = false; + + auto currentSeq = packetSeq; + auto currentCounter = CounterFromSeq(currentSeq); + rtc::ByteBufferReader reader( + reinterpret_cast(fullBuffer.data() + 4), // Skip seq. + fullBuffer.size() - 4); + + auto result = absl::optional(); + while (true) { + const auto type = uint8_t(*reader.Data()); + const auto singleMessagePacket = ((currentSeq & kSingleMessagePacketSeqBit) != 0); + if (singleMessagePacket && additionalMessage) { + return LogError("Single message packet bit in not first message."); + } + + if (type == kEmptyId) { + if (additionalMessage) { + return LogError("Empty message should be only the first one in the packet."); + } + RTC_LOG(LS_INFO) << logHeader() + << "Got RECV:empty" << "#" << currentCounter; + reader.Consume(1); + } else if (type == kAckId) { + if (!additionalMessage) { + return LogError("Ack message must not be the first one in the packet."); + } + ackMyMessage(currentSeq); + reader.Consume(1); + } else if (type == kCustomId) { + reader.Consume(1); + + if (auto message = DeserializeRawMessage(reader, singleMessagePacket)) { + const auto messageRequiresAck = ((currentSeq & kMessageRequiresAckSeqBit) != 0); + const auto skipMessage = messageRequiresAck + ? !registerSentAck(currentCounter, firstMessageRequiringAck) + : (additionalMessage && !registerIncomingCounter(currentCounter)); + if (messageRequiresAck) { + firstMessageRequiringAck = false; + if (!skipMessage) { + newRequiringAckReceived = true; + } + sendAckPostponed(currentSeq); + RTC_LOG(LS_INFO) << logHeader() + << (skipMessage ? "Repeated RECV:type" : "Got RECV:type") << type << "#" << currentCounter; + } + if (!skipMessage) { + appendReceivedRawMessage(result, std::move(*message), currentSeq); + } + } else { + return LogError("Could not parse message from packet, type: ", std::to_string(type)); + } + } else { + return LogError("Could not parse message from packet, type: ", std::to_string(type)); + } + if (!reader.Length()) { + break; + } else if (singleMessagePacket) { + return LogError("Single message didn't fill the entire packet."); + } else if (reader.Length() < 5) { + return LogError("Bad remaining data size: ", std::to_string(reader.Length())); + } + const auto success = reader.ReadUInt32(¤tSeq); + assert(success); + (void)success; + currentCounter = CounterFromSeq(currentSeq); + + additionalMessage = true; + } + + if (!_acksToSendSeqs.empty()) { + if (newRequiringAckReceived) { + _requestSendService(0, 0); + } else if (!_sendAcksTimerActive) { + _sendAcksTimerActive = true; + _requestSendService( + _delayIntervals.maxDelayBeforeAckResend, + kServiceCauseAcks); + } + } + + return result; +} + void EncryptedConnection::appendReceivedMessage( absl::optional &to, Message &&message, @@ -505,6 +668,21 @@ void EncryptedConnection::appendReceivedMessage( } } +void EncryptedConnection::appendReceivedRawMessage( + absl::optional &to, + rtc::CopyOnWriteBuffer &&message, + uint32_t incomingSeq) { + auto decrypted = DecryptedRawMessage{ + std::move(message), + CounterFromSeq(incomingSeq) + }; + if (to) { + to->additional.push_back(std::move(decrypted)); + } else { + to = DecryptedRawPacket{ std::move(decrypted) }; + } +} + const char *EncryptedConnection::logHeader() const { return (_type == Type::Signaling) ? "(signaling) " : "(transport) "; } diff --git a/TMessagesProj/jni/voip/tgcalls/EncryptedConnection.h b/TMessagesProj/jni/voip/tgcalls/EncryptedConnection.h index 5c3e653c064..50a1fd8539b 100644 --- a/TMessagesProj/jni/voip/tgcalls/EncryptedConnection.h +++ b/TMessagesProj/jni/voip/tgcalls/EncryptedConnection.h @@ -26,13 +26,19 @@ class EncryptedConnection final { uint32_t counter = 0; }; absl::optional prepareForSending(const Message &message); + absl::optional prepareForSendingRawMessage(rtc::CopyOnWriteBuffer &serialized, bool messageRequiresAck); absl::optional prepareForSendingService(int cause); struct DecryptedPacket { DecryptedMessage main; std::vector additional; }; + struct DecryptedRawPacket { + DecryptedRawMessage main; + std::vector additional; + }; absl::optional handleIncomingPacket(const char *bytes, size_t size); + absl::optional handleIncomingRawPacket(const char *bytes, size_t size); absl::optional encryptRawPacket(rtc::CopyOnWriteBuffer const &buffer); absl::optional decryptRawPacket(rtc::CopyOnWriteBuffer const &buffer); @@ -57,6 +63,7 @@ class EncryptedConnection final { EncryptedPacket encryptPrepared(const rtc::CopyOnWriteBuffer &buffer); bool registerIncomingCounter(uint32_t incomingCounter); absl::optional processPacket(const rtc::Buffer &fullBuffer, uint32_t packetSeq); + absl::optional processRawPacket(const rtc::Buffer &fullBuffer, uint32_t packetSeq); bool registerSentAck(uint32_t counter, bool firstInPacket); void ackMyMessage(uint32_t counter); void sendAckPostponed(uint32_t incomingSeq); @@ -66,6 +73,11 @@ class EncryptedConnection final { absl::optional &to, Message &&message, uint32_t incomingSeq); + void appendReceivedRawMessage( + absl::optional &to, + rtc::CopyOnWriteBuffer &&message, + uint32_t incomingSeq); + absl::optional prepareForSendingMessageInternal(rtc::CopyOnWriteBuffer &serialized, uint32_t seq, bool messageRequiresAck); const char *logHeader() const; diff --git a/TMessagesProj/jni/voip/tgcalls/Instance.h b/TMessagesProj/jni/voip/tgcalls/Instance.h index 7d87919ad90..fc111636553 100644 --- a/TMessagesProj/jni/voip/tgcalls/Instance.h +++ b/TMessagesProj/jni/voip/tgcalls/Instance.h @@ -215,6 +215,7 @@ template bool Register(); struct Descriptor { + std::string version; Config config; PersistentState persistentState; std::vector endpoints; diff --git a/TMessagesProj/jni/voip/tgcalls/Message.cpp b/TMessagesProj/jni/voip/tgcalls/Message.cpp index 7ee4c53329f..86a53bb257c 100644 --- a/TMessagesProj/jni/voip/tgcalls/Message.cpp +++ b/TMessagesProj/jni/voip/tgcalls/Message.cpp @@ -378,4 +378,29 @@ absl::optional DeserializeMessage( : absl::nullopt; } +absl::optional DeserializeRawMessage( + rtc::ByteBufferReader &reader, + bool singleMessagePacket) { + if (!reader.Length()) { + return absl::nullopt; + } + + uint32_t length = 0; + if (!reader.ReadUInt32(&length)) { + return absl::nullopt; + } + + if (length < 0 || length > 1024 * 1024) { + return absl::nullopt; + } + + rtc::CopyOnWriteBuffer result; + result.SetSize(length); + if (!reader.ReadBytes((char *)result.MutableData(), result.size())) { + return absl::nullopt; + } + + return result; +} + } // namespace tgcalls diff --git a/TMessagesProj/jni/voip/tgcalls/Message.h b/TMessagesProj/jni/voip/tgcalls/Message.h index 62add213b59..17db84ea87c 100644 --- a/TMessagesProj/jni/voip/tgcalls/Message.h +++ b/TMessagesProj/jni/voip/tgcalls/Message.h @@ -18,13 +18,14 @@ enum class AudioState; struct PeerIceParameters { std::string ufrag; std::string pwd; + bool supportsRenomination = false; PeerIceParameters() = default; - PeerIceParameters(const PeerIceParameters &other) = default; - PeerIceParameters(std::string ufrag_, std::string pwd_) : + PeerIceParameters(std::string ufrag_, std::string pwd_, bool supportsRenomination_) : ufrag(ufrag_), - pwd(pwd_) { + pwd(pwd_), + supportsRenomination(supportsRenomination_) { } }; @@ -127,12 +128,21 @@ rtc::CopyOnWriteBuffer SerializeMessageWithSeq( absl::optional DeserializeMessage( rtc::ByteBufferReader &reader, bool singleMessagePacket); +absl::optional DeserializeRawMessage( + rtc::ByteBufferReader &reader, + bool singleMessagePacket); struct DecryptedMessage { Message message; uint32_t counter = 0; }; +struct DecryptedRawMessage { + rtc::CopyOnWriteBuffer message; + uint32_t counter = 0; +}; + + } // namespace tgcalls #endif diff --git a/TMessagesProj/jni/voip/tgcalls/NetworkManager.cpp b/TMessagesProj/jni/voip/tgcalls/NetworkManager.cpp index fd87da11222..e8053db566a 100644 --- a/TMessagesProj/jni/voip/tgcalls/NetworkManager.cpp +++ b/TMessagesProj/jni/voip/tgcalls/NetworkManager.cpp @@ -88,7 +88,7 @@ _isOutgoing(encryptionKey.isOutgoing), _stateUpdated(std::move(stateUpdated)), _transportMessageReceived(std::move(transportMessageReceived)), _sendSignalingMessage(std::move(sendSignalingMessage)), -_localIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH)) { +_localIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH), false) { assert(_thread->IsCurrent()); _networkMonitorFactory = PlatformInterface::SharedInstance()->createNetworkMonitorFactory(); @@ -207,7 +207,7 @@ void NetworkManager::receiveSignalingMessage(DecryptedMessage &&message) { assert(list != nullptr); if (!_remoteIceParameters.has_value()) { - PeerIceParameters parameters(list->iceParameters.ufrag, list->iceParameters.pwd); + PeerIceParameters parameters(list->iceParameters.ufrag, list->iceParameters.pwd, false); _remoteIceParameters = parameters; cricket::IceParameters remoteIceParameters( diff --git a/TMessagesProj/jni/voip/tgcalls/StaticThreads.cpp b/TMessagesProj/jni/voip/tgcalls/StaticThreads.cpp index 0cc9ac56e31..0f34ae7e3a1 100644 --- a/TMessagesProj/jni/voip/tgcalls/StaticThreads.cpp +++ b/TMessagesProj/jni/voip/tgcalls/StaticThreads.cpp @@ -61,16 +61,20 @@ class ThreadsImpl : public Threads { explicit ThreadsImpl(size_t i) { auto suffix = i == 0 ? "" : "#" + std::to_string(i); media_ = create("tgc-media" + suffix); - //worker_ = create("tgc-work" + suffix); - worker_ = create_network("tgc-work" + suffix); - //network_ = create_network("tgc-net" + suffix); + worker_ = create("tgc-work" + suffix); + network_ = create_network("tgc-net" + suffix); + + media_->AllowInvokesToThread(worker_.get()); + media_->AllowInvokesToThread(network_.get()); + worker_->AllowInvokesToThread(network_.get()); + //network_->DisallowAllInvokes(); //worker_->DisallowAllInvokes(); //worker_->AllowInvokesToThread(network_.get()); } rtc::Thread *getNetworkThread() override { - return worker_.get(); + return network_.get(); } rtc::Thread *getMediaThread() override { return media_.get(); @@ -90,7 +94,7 @@ class ThreadsImpl : public Threads { } private: - //Thread network_; + Thread network_; Thread media_; Thread worker_; rtc::scoped_refptr shared_module_thread_; diff --git a/TMessagesProj/jni/voip/tgcalls/group/GroupNetworkManager.cpp b/TMessagesProj/jni/voip/tgcalls/group/GroupNetworkManager.cpp index 8f133e9b656..ecdf6c20132 100644 --- a/TMessagesProj/jni/voip/tgcalls/group/GroupNetworkManager.cpp +++ b/TMessagesProj/jni/voip/tgcalls/group/GroupNetworkManager.cpp @@ -318,7 +318,7 @@ _dataChannelMessageReceived(dataChannelMessageReceived), _audioActivityUpdated(audioActivityUpdated) { assert(_threads->getNetworkThread()->IsCurrent()); - _localIceParameters = PeerIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH)); + _localIceParameters = PeerIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH), false); _localCertificate = rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(rtc::KT_ECDSA), absl::nullopt); @@ -452,7 +452,7 @@ void GroupNetworkManager::stop() { _transportChannel.reset(); _portAllocator.reset(); - _localIceParameters = PeerIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH)); + _localIceParameters = PeerIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH), false); _localCertificate = rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(rtc::KT_ECDSA), absl::nullopt); diff --git a/TMessagesProj/jni/voip/tgcalls/v2/InstanceV2Impl.cpp b/TMessagesProj/jni/voip/tgcalls/v2/InstanceV2Impl.cpp index a8c92081561..c3752ba0c7d 100644 --- a/TMessagesProj/jni/voip/tgcalls/v2/InstanceV2Impl.cpp +++ b/TMessagesProj/jni/voip/tgcalls/v2/InstanceV2Impl.cpp @@ -57,6 +57,23 @@ namespace tgcalls { namespace { +enum class SignalingProtocolVersion { + V1, + V2 +}; + +SignalingProtocolVersion signalingProtocolVersion(std::string const &version) { + if (version == "4.0.1") { + return SignalingProtocolVersion::V1; + } else if (version == "4.0.2") { + return SignalingProtocolVersion::V2; + } else { + RTC_LOG(LS_ERROR) << "signalingProtocolVersion: unknown version " << version; + + return SignalingProtocolVersion::V2; + } +} + static VideoCaptureInterfaceObject *GetVideoCaptureAssumingSameThread(VideoCaptureInterface *videoCapture) { return videoCapture ? static_cast(videoCapture)->object()->getSyncAssumingSameThread() @@ -777,6 +794,7 @@ struct NetworkBitrateLogRecord { class InstanceV2ImplInternal : public std::enable_shared_from_this { public: InstanceV2ImplInternal(Descriptor &&descriptor, std::shared_ptr threads) : + _signalingProtocolVersion(signalingProtocolVersion(descriptor.version)), _threads(threads), _rtcServers(descriptor.rtcServers), _proxy(std::move(descriptor.proxy)), @@ -927,7 +945,7 @@ class InstanceV2ImplInternal : public std::enable_shared_from_thisgetNetworkThread() ); - webrtc::Call::Config callConfig(_eventLog.get()); + webrtc::Call::Config callConfig(_eventLog.get(), _threads->getNetworkThread()); callConfig.task_queue_factory = _taskQueueFactory.get(); callConfig.trials = &_fieldTrials; callConfig.audio_state = _channelManager->media_engine()->voice().GetAudioState(); @@ -1033,27 +1051,86 @@ class InstanceV2ImplInternal : public std::enable_shared_from_this const &data) { RTC_LOG(LS_INFO) << "sendSignalingMessage: " << std::string(data.begin(), data.end()); - if (_signalingEncryption) { - if (const auto encryptedData = _signalingEncryption->encryptOutgoing(data)) { - _signalingDataEmitted(std::vector(encryptedData->data(), encryptedData->data() + encryptedData->size())); - } else { - RTC_LOG(LS_ERROR) << "sendSignalingMessage: failed to encrypt payload"; + if (_signalingEncryptedConnection) { + switch (_signalingProtocolVersion) { + case SignalingProtocolVersion::V1: { + if (const auto message = _signalingEncryptedConnection->encryptRawPacket(rtc::CopyOnWriteBuffer(data.data(), data.size()))) { + _signalingDataEmitted(std::vector(message.value().data(), message.value().data() + message.value().size())); + } else { + RTC_LOG(LS_ERROR) << "Could not encrypt signaling message"; + } + break; + } + case SignalingProtocolVersion::V2: { + rtc::CopyOnWriteBuffer message; + message.AppendData(data.data(), data.size()); + + commitSendSignalingMessage(_signalingEncryptedConnection->prepareForSendingRawMessage(message, true)); + + break; + } + default: { + RTC_DCHECK_NOTREACHED(); + + break; + } } } else { - _signalingDataEmitted(data); + RTC_LOG(LS_ERROR) << "sendSignalingMessage encryption not available"; } } + + void commitSendSignalingMessage(absl::optional packet) { + if (!packet) { + return; + } + + _signalingDataEmitted(packet.value().bytes); + } void beginSignaling() { - _signalingEncryption.reset(new SignalingEncryption(_encryptionKey)); + const auto weak = std::weak_ptr(shared_from_this()); + + _signalingEncryptedConnection = std::make_unique( + EncryptedConnection::Type::Signaling, + _encryptionKey, + [weak, threads = _threads](int delayMs, int cause) { + if (delayMs == 0) { + threads->getMediaThread()->PostTask(RTC_FROM_HERE, [weak, cause]() { + const auto strong = weak.lock(); + if (!strong) { + return; + } + + strong->sendPendingSignalingServiceData(cause); + }); + } else { + threads->getMediaThread()->PostDelayedTask(RTC_FROM_HERE, [weak, cause]() { + const auto strong = weak.lock(); + if (!strong) { + return; + } + + strong->sendPendingSignalingServiceData(cause); + }, delayMs); + } + } + ); if (_encryptionKey.isOutgoing) { sendInitialSetup(); } } + + void sendPendingSignalingServiceData(int cause) { + commitSendSignalingMessage(_signalingEncryptedConnection->prepareForSendingService(cause)); + } void createNegotiatedChannels() { const auto coordinatedState = _contentNegotiationContext->coordinatedState(); @@ -1295,8 +1372,9 @@ class InstanceV2ImplInternal : public std::enable_shared_from_thisgetLocalIceParameters(); std::string ufrag = localIceParams.ufrag; std::string pwd = localIceParams.pwd; + bool supportsRenomination = localIceParams.supportsRenomination; - threads->getMediaThread()->PostTask(RTC_FROM_HERE, [weak, ufrag, pwd, hash, fingerprint, setup, localIceParams]() { + threads->getMediaThread()->PostTask(RTC_FROM_HERE, [weak, ufrag, pwd, supportsRenomination, hash, fingerprint, setup, localIceParams]() { const auto strong = weak.lock(); if (!strong) { return; @@ -1306,6 +1384,7 @@ class InstanceV2ImplInternal : public std::enable_shared_from_this &data) { - std::vector decryptedData; - - if (_signalingEncryption) { - const auto rawDecryptedData = _signalingEncryption->decryptIncoming(data); - if (!rawDecryptedData) { - RTC_LOG(LS_ERROR) << "receiveSignalingData: could not decrypt payload"; - - return; + if (_signalingEncryptedConnection) { + switch (_signalingProtocolVersion) { + case SignalingProtocolVersion::V1: { + if (const auto message = _signalingEncryptedConnection->decryptRawPacket(rtc::CopyOnWriteBuffer(data.data(), data.size()))) { + processSignalingMessage(message.value()); + } else { + RTC_LOG(LS_ERROR) << "receiveSignalingData could not decrypt signaling data"; + } + + break; + } + case SignalingProtocolVersion::V2: { + if (const auto packet = _signalingEncryptedConnection->handleIncomingRawPacket((const char *)data.data(), data.size())) { + processSignalingMessage(packet.value().main.message); + + for (const auto &additional : packet.value().additional) { + processSignalingMessage(additional.message); + } + } + + break; + } + default: { + RTC_DCHECK_NOTREACHED(); + + break; + } } - - decryptedData = std::vector(rawDecryptedData->data(), rawDecryptedData->data() + rawDecryptedData->size()); } else { - decryptedData = data; + RTC_LOG(LS_ERROR) << "receiveSignalingData encryption not available"; } - + } + + void processSignalingMessage(rtc::CopyOnWriteBuffer const &data) { + std::vector decryptedData = std::vector(data.data(), data.data() + data.size()); processSignalingData(decryptedData); } @@ -1365,6 +1464,7 @@ class InstanceV2ImplInternal : public std::enable_shared_from_thisufrag; remoteIceParameters.pwd = initialSetup->pwd; + remoteIceParameters.supportsRenomination = initialSetup->supportsRenomination; std::unique_ptr fingerprint; std::string sslSetup; @@ -1828,6 +1928,7 @@ class InstanceV2ImplInternal : public std::enable_shared_from_this _threads; std::vector _rtcServers; std::unique_ptr _proxy; @@ -1843,7 +1944,7 @@ class InstanceV2ImplInternal : public std::enable_shared_from_this(webrtc::TaskQueueFactory*)> _createAudioDeviceModule; FilePath _statsLogPath; - std::unique_ptr _signalingEncryption; + std::unique_ptr _signalingEncryptedConnection; int64_t _startTimestamp = 0; @@ -1993,6 +2094,7 @@ void InstanceV2Impl::setEchoCancellationStrength(int strength) { std::vector InstanceV2Impl::GetVersions() { std::vector result; result.push_back("4.0.1"); + result.push_back("4.0.2"); return result; } diff --git a/TMessagesProj/jni/voip/tgcalls/v2/InstanceV2ReferenceImpl.cpp b/TMessagesProj/jni/voip/tgcalls/v2/InstanceV2ReferenceImpl.cpp index 6cc896d0555..7505762614e 100644 --- a/TMessagesProj/jni/voip/tgcalls/v2/InstanceV2ReferenceImpl.cpp +++ b/TMessagesProj/jni/voip/tgcalls/v2/InstanceV2ReferenceImpl.cpp @@ -36,6 +36,9 @@ #include "pc/media_session.h" #include "rtc_base/rtc_certificate_generator.h" #include "pc/peer_connection.h" +#include "pc/peer_connection_proxy.h" +#include "api/rtc_event_log/rtc_event_log_factory.h" +#include "api/stats/rtc_stats_report.h" #include "AudioFrame.h" #include "ThreadLocalObject.h" @@ -52,6 +55,9 @@ #endif #include #include +#include + +#include "third-party/json11.hpp" namespace tgcalls { namespace { @@ -62,6 +68,138 @@ static VideoCaptureInterfaceObject *GetVideoCaptureAssumingSameThread(VideoCaptu : nullptr; } +class SetSessionDescriptionObserver : public webrtc::SetLocalDescriptionObserverInterface, public webrtc::SetRemoteDescriptionObserverInterface { +public: + SetSessionDescriptionObserver(std::function &&completion) : + _completion(std::move(completion)) { + } + + virtual void OnSetLocalDescriptionComplete(webrtc::RTCError error) override { + OnCompelete(error); + } + + virtual void OnSetRemoteDescriptionComplete(webrtc::RTCError error) override { + OnCompelete(error); + } + +private: + void OnCompelete(webrtc::RTCError error) { + _completion(error); + } + + std::function _completion; +}; + +class PeerConnectionDelegateAdapter: public webrtc::PeerConnectionObserver { +public: + struct Parameters { + std::function onRenegotiationNeeded; + std::function onIceCandidate; + std::function onSignalingChange; + std::function onConnectionChange; + std::function)> onDataChannel; + std::function)> onTransceiverAdded; + std::function)> onTransceiverRemoved; + std::function onCandidatePairChangeEvent; + }; + +public: + PeerConnectionDelegateAdapter( + Parameters &¶meters + ) : _parameters(std::move(parameters)) { + } + + ~PeerConnectionDelegateAdapter() override { + } + + void OnSignalingChange(webrtc::PeerConnectionInterface::SignalingState new_state) override { + if (_parameters.onSignalingChange) { + _parameters.onSignalingChange(new_state); + } + } + + void OnAddStream(rtc::scoped_refptr stream) override { + } + + void OnRemoveStream(rtc::scoped_refptr stream) override { + } + + void OnTrack(rtc::scoped_refptr transceiver) override { + if (_parameters.onTransceiverAdded) { + _parameters.onTransceiverAdded(transceiver); + } + } + + void OnDataChannel(rtc::scoped_refptr data_channel) override { + if (_parameters.onDataChannel) { + _parameters.onDataChannel(data_channel); + } + } + + void OnRenegotiationNeeded() override { + if (_parameters.onRenegotiationNeeded) { + _parameters.onRenegotiationNeeded(); + } + } + + void OnIceConnectionChange(webrtc::PeerConnectionInterface::IceConnectionState new_state) override { + } + + void OnStandardizedIceConnectionChange(webrtc::PeerConnectionInterface::IceConnectionState new_state) override { + } + + void OnConnectionChange(webrtc::PeerConnectionInterface::PeerConnectionState new_state) override { + if (_parameters.onConnectionChange) { + _parameters.onConnectionChange(new_state); + } + } + + void OnIceGatheringChange(webrtc::PeerConnectionInterface::IceGatheringState new_state) override { + } + + void OnIceCandidate(const webrtc::IceCandidateInterface *candidate) override { + if (_parameters.onIceCandidate) { + _parameters.onIceCandidate(candidate); + } + } + + void OnIceCandidatesRemoved(const std::vector &candidates) override { + } + + void OnIceSelectedCandidatePairChanged(const cricket::CandidatePairChangeEvent &event) override { + if (_parameters.onCandidatePairChangeEvent) { + _parameters.onCandidatePairChangeEvent(event); + } + } + + void OnAddTrack(rtc::scoped_refptr receiver, const std::vector> &streams) override { + + } + + void OnRemoveTrack(rtc::scoped_refptr receiver) override { + if (_parameters.onTransceiverRemoved) { + _parameters.onTransceiverRemoved(receiver); + } + } + +private: + Parameters _parameters; +}; + +class StatsCollectorCallbackAdapter : public webrtc::RTCStatsCollectorCallback { +public: + StatsCollectorCallbackAdapter(std::function &)> &&completion_) : + completion(std::move(completion_)) { + } + + void OnStatsDelivered(const rtc::scoped_refptr &report) override { + completion(report); + } + +private: + std::function &)> completion; +}; + class VideoSinkImpl : public rtc::VideoSinkInterface { public: VideoSinkImpl() { @@ -108,6 +246,76 @@ class VideoSinkImpl : public rtc::VideoSinkInterface { absl::optional _lastFrame; }; +class DataChannelObserverImpl : public webrtc::DataChannelObserver { +public: + struct Parameters { + std::function onStateChange; + std::function onMessage; + }; + +public: + DataChannelObserverImpl(Parameters &¶meters) : + _parameters(std::move(parameters)) { + } + + virtual void OnStateChange() override { + if (_parameters.onStateChange) { + _parameters.onStateChange(); + } + } + + virtual void OnMessage(webrtc::DataBuffer const &buffer) override { + if (_parameters.onMessage) { + _parameters.onMessage(buffer); + } + } + + virtual ~DataChannelObserverImpl() { + } + +private: + Parameters _parameters; +}; + +template +struct StateLogRecord { + int64_t timestamp = 0; + T record; + + explicit StateLogRecord(int32_t timestamp_, T &&record_) : + timestamp(timestamp_), + record(std::move(record_)) { + } +}; + +struct NetworkStateLogRecord { + bool isConnected = false; + bool isFailed = false; + absl::optional route; + absl::optional connection; + + bool operator==(NetworkStateLogRecord const &rhs) const { + if (isConnected != rhs.isConnected) { + return false; + } + if (isFailed != rhs.isFailed) { + return false; + } + if (route != rhs.route) { + return false; + } + if (connection != rhs.connection) { + return false; + } + + return true; + } +}; + +struct NetworkBitrateLogRecord { + int32_t bitrate = 0; +}; + } // namespace class InstanceV2ReferenceImplInternal : public std::enable_shared_from_this { @@ -126,6 +334,7 @@ class InstanceV2ReferenceImplInternal : public std::enable_shared_from_this()), _taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()), _videoCapture(descriptor.videoCapture), @@ -133,490 +342,736 @@ class InstanceV2ReferenceImplInternal : public std::enable_shared_from_thisgetWorkerThread()->Invoke(RTC_FROM_HERE, [&]() { + _audioDeviceModule = nullptr; + }); + + if (_dataChannel) { + _dataChannel->UnregisterObserver(); + _dataChannel = nullptr; + } + _dataChannelObserver.reset(); + + _peerConnection = nullptr; + _peerConnectionObserver.reset(); + _peerConnectionFactory = nullptr; } void start() { + PlatformInterface::SharedInstance()->configurePlatformAudio(); + const auto weak = std::weak_ptr(shared_from_this()); PlatformInterface::SharedInstance()->configurePlatformAudio(); RTC_DCHECK(_threads->getMediaThread()->IsCurrent()); - - //_threads->getWorkerThread()->Invoke(RTC_FROM_HERE, [&]() { - cricket::MediaEngineDependencies mediaDeps; - mediaDeps.task_queue_factory = _taskQueueFactory.get(); - mediaDeps.audio_encoder_factory = webrtc::CreateAudioEncoderFactory(); - mediaDeps.audio_decoder_factory = webrtc::CreateAudioDecoderFactory(); - - mediaDeps.video_encoder_factory = PlatformInterface::SharedInstance()->makeVideoEncoderFactory(_platformContext, true); - mediaDeps.video_decoder_factory = PlatformInterface::SharedInstance()->makeVideoDecoderFactory(_platformContext); + _threads->getWorkerThread()->Invoke(RTC_FROM_HERE, [&]() { _audioDeviceModule = createAudioDeviceModule(); + }); - mediaDeps.adm = _audioDeviceModule; - - std::unique_ptr mediaEngine = cricket::CreateMediaEngine(std::move(mediaDeps)); - //}); - webrtc::PeerConnectionFactoryDependencies peerConnectionFactoryDependencies; peerConnectionFactoryDependencies.signaling_thread = _threads->getMediaThread(); peerConnectionFactoryDependencies.worker_thread = _threads->getWorkerThread(); - peerConnectionFactoryDependencies.task_queue_factory = std::move(_taskQueueFactory); + peerConnectionFactoryDependencies.task_queue_factory = webrtc::CreateDefaultTaskQueueFactory(); + peerConnectionFactoryDependencies.network_monitor_factory = PlatformInterface::SharedInstance()->createNetworkMonitorFactory(); + + cricket::MediaEngineDependencies mediaDeps; + mediaDeps.adm = _audioDeviceModule; + mediaDeps.task_queue_factory = peerConnectionFactoryDependencies.task_queue_factory.get(); + mediaDeps.audio_encoder_factory = webrtc::CreateAudioEncoderFactory(); + mediaDeps.audio_decoder_factory = webrtc::CreateAudioDecoderFactory(); + + mediaDeps.video_encoder_factory = PlatformInterface::SharedInstance()->makeVideoEncoderFactory(_platformContext, true); + mediaDeps.video_decoder_factory = PlatformInterface::SharedInstance()->makeVideoDecoderFactory(_platformContext); + + std::unique_ptr mediaEngine = cricket::CreateMediaEngine(std::move(mediaDeps)); peerConnectionFactoryDependencies.media_engine = std::move(mediaEngine); - auto peerConnectionFactory = webrtc::PeerConnectionFactory::Create(std::move(peerConnectionFactoryDependencies)); + + peerConnectionFactoryDependencies.call_factory = webrtc::CreateCallFactory(); + peerConnectionFactoryDependencies.event_log_factory = std::make_unique(peerConnectionFactoryDependencies.task_queue_factory.get()); + + _peerConnectionFactory = webrtc::CreateModularPeerConnectionFactory(std::move(peerConnectionFactoryDependencies)); webrtc::PeerConnectionDependencies peerConnectionDependencies(nullptr); + PeerConnectionDelegateAdapter::Parameters delegateParameters; + delegateParameters.onRenegotiationNeeded = [weak, threads = _threads]() { + threads->getMediaThread()->PostTask(RTC_FROM_HERE, [weak]() { + const auto strong = weak.lock(); + if (!strong) { + return; + } + + if (strong->_didBeginNegotiation && !strong->_isPerformingConfiguration) { + strong->sendLocalDescription(); + } else { + RTC_LOG(LS_INFO) << "onRenegotiationNeeded: not sending local description"; + } + }); + }; + delegateParameters.onIceCandidate = [weak](const webrtc::IceCandidateInterface *iceCandidate) { + const auto strong = weak.lock(); + if (!strong) { + return; + } + strong->sendIceCandidate(iceCandidate); + }; + delegateParameters.onSignalingChange = [weak](webrtc::PeerConnectionInterface::SignalingState state) { + const auto strong = weak.lock(); + if (!strong) { + return; + } + /*switch (state) { + case webrtc::PeerConnectionInterface::SignalingState::kStable: { + State mappedState = State::Established; + strong->_stateUpdated(mappedState); + break; + } + default: { + State mappedState = State::Reconnecting; + strong->_stateUpdated(mappedState); + break; + } + }*/ + }; + delegateParameters.onConnectionChange = [weak](webrtc::PeerConnectionInterface::PeerConnectionState state) { + const auto strong = weak.lock(); + if (!strong) { + return; + } + + bool isConnected = false; + bool isFailed = false; + + switch (state) { + case webrtc::PeerConnectionInterface::PeerConnectionState::kConnected: { + isConnected = true; + break; + } + case webrtc::PeerConnectionInterface::PeerConnectionState::kFailed: { + isFailed = true; + break; + } + default: { + break; + } + } + + if (strong->_isConnected != isConnected || strong->_isFailed != isFailed) { + strong->_isConnected = isConnected; + strong->_isFailed = isFailed; + + strong->onNetworkStateUpdated(); + } + }; + delegateParameters.onDataChannel = [weak](rtc::scoped_refptr dataChannel) { + const auto strong = weak.lock(); + if (!strong) { + return; + } + + if (!strong->_dataChannel) { + strong->attachDataChannel(dataChannel); + } else { + RTC_LOG(LS_WARNING) << "onDataChannel invoked, but data channel already exists"; + } + }; + delegateParameters.onTransceiverAdded = [weak](rtc::scoped_refptr transceiver) { + const auto strong = weak.lock(); + if (!strong) { + return; + } + + if (!transceiver->mid()) { + return; + } + std::string mid = transceiver->mid().value(); + + switch (transceiver->media_type()) { + case cricket::MediaType::MEDIA_TYPE_VIDEO: { + if (strong->_incomingVideoTransceivers.find(mid) == strong->_incomingVideoTransceivers.end()) { + strong->_incomingVideoTransceivers.insert(std::make_pair(mid, transceiver)); + + strong->connectIncomingVideoSink(transceiver); + } + break; + } + default: { + break; + } + } + }; + delegateParameters.onTransceiverRemoved = [weak](rtc::scoped_refptr receiver) { + const auto strong = weak.lock(); + if (!strong) { + return; + } + + std::string mid = receiver->track()->id(); + if (mid.empty()) { + return; + } + + const auto transceiver = strong->_incomingVideoTransceivers.find(mid); + if (transceiver != strong->_incomingVideoTransceivers.end()) { + strong->disconnectIncomingVideoSink(); + + strong->_incomingVideoTransceivers.erase(transceiver); + } + }; + delegateParameters.onCandidatePairChangeEvent = [weak](const cricket::CandidatePairChangeEvent &event) { + const auto strong = weak.lock(); + if (!strong) { + return; + } + + NativeNetworkingImpl::ConnectionDescription connectionDescription; + + connectionDescription.local = NativeNetworkingImpl::connectionDescriptionFromCandidate(event.selected_candidate_pair.local); + connectionDescription.remote = NativeNetworkingImpl::connectionDescriptionFromCandidate(event.selected_candidate_pair.remote); + + if (!strong->_currentConnectionDescription || strong->_currentConnectionDescription.value() != connectionDescription) { + strong->_currentConnectionDescription = std::move(connectionDescription); + strong->onNetworkStateUpdated(); + } + }; + _peerConnectionObserver = std::make_unique(std::move(delegateParameters)); + + peerConnectionDependencies.observer = _peerConnectionObserver.get(); + webrtc::PeerConnectionInterface::RTCConfiguration peerConnectionConfiguration; - - auto peerConnectionOrError = peerConnectionFactory->CreatePeerConnectionOrError(peerConnectionConfiguration, std::move(peerConnectionDependencies)); + if (_enableP2P) { + peerConnectionConfiguration.type = webrtc::PeerConnectionInterface::IceTransportsType::kAll; + } else { + peerConnectionConfiguration.type = webrtc::PeerConnectionInterface::IceTransportsType::kRelay; + } + peerConnectionConfiguration.enable_ice_renomination = true; + peerConnectionConfiguration.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan; + peerConnectionConfiguration.bundle_policy = webrtc::PeerConnectionInterface::kBundlePolicyMaxBundle; + peerConnectionConfiguration.rtcp_mux_policy = webrtc::PeerConnectionInterface::RtcpMuxPolicy::kRtcpMuxPolicyRequire; + peerConnectionConfiguration.enable_implicit_rollback = true; + peerConnectionConfiguration.continual_gathering_policy = webrtc::PeerConnectionInterface::ContinualGatheringPolicy::GATHER_CONTINUALLY; + peerConnectionConfiguration.audio_jitter_buffer_fast_accelerate = true; + + for (auto &server : _rtcServers) { + rtc::SocketAddress address(server.host, server.port); + if (!address.IsComplete()) { + RTC_LOG(LS_ERROR) << "Invalid ICE server host: " << server.host; + continue; + } + + if (server.isTurn) { + webrtc::PeerConnectionInterface::IceServer mappedServer; + + std::ostringstream uri; + uri << "turn:" << address.HostAsURIString() << ":" << server.port; + + mappedServer.urls.push_back(uri.str()); + mappedServer.username = server.login; + mappedServer.password = server.password; + + peerConnectionConfiguration.servers.push_back(mappedServer); + } else { + webrtc::PeerConnectionInterface::IceServer mappedServer; + + std::ostringstream uri; + uri << "stun:" << address.HostAsURIString() << ":" << server.port; + + mappedServer.urls.push_back(uri.str()); + + peerConnectionConfiguration.servers.push_back(mappedServer); + } + } + + auto peerConnectionOrError = _peerConnectionFactory->CreatePeerConnectionOrError(peerConnectionConfiguration, std::move(peerConnectionDependencies)); if (peerConnectionOrError.ok()) { _peerConnection = peerConnectionOrError.value(); } + if (_peerConnection) { + RTC_LOG(LS_INFO) << "Creating Data Channel"; + + if (_encryptionKey.isOutgoing) { + webrtc::DataChannelInit dataChannelInit; + webrtc::RTCErrorOr> dataChannelOrError = _peerConnection->CreateDataChannelOrError("data", &dataChannelInit); + if (dataChannelOrError.ok()) { + attachDataChannel(dataChannelOrError.value()); + } + } + + webrtc::RtpTransceiverInit transceiverInit; + transceiverInit.stream_ids = { "0" }; + + cricket::AudioOptions audioSourceOptions; + rtc::scoped_refptr audioSource = _peerConnectionFactory->CreateAudioSource(audioSourceOptions); + + rtc::scoped_refptr audioTrack = _peerConnectionFactory->CreateAudioTrack("0", audioSource); + webrtc::RTCErrorOr> audioTransceiverOrError = _peerConnection->AddTransceiver(audioTrack, transceiverInit); + if (audioTransceiverOrError.ok()) { + _outgoingAudioTrack = audioTrack; + _outgoingAudioTransceiver = audioTransceiverOrError.value(); + + webrtc::RtpParameters parameters = _outgoingAudioTransceiver->sender()->GetParameters(); + if (parameters.encodings.empty()) { + parameters.encodings.push_back(webrtc::RtpEncodingParameters()); + } + parameters.encodings[0].max_bitrate_bps = 32 * 1024; + _outgoingAudioTransceiver->sender()->SetParameters(parameters); + + _outgoingAudioTrack->set_enabled(true); + } + } + if (_videoCapture) { setVideoCapture(_videoCapture); } + _signalingEncryptedConnection = std::make_unique( + EncryptedConnection::Type::Signaling, + _encryptionKey, + [weak, threads = _threads](int delayMs, int cause) { + if (delayMs == 0) { + threads->getMediaThread()->PostTask(RTC_FROM_HERE, [weak, cause]() { + const auto strong = weak.lock(); + if (!strong) { + return; + } + + strong->sendPendingSignalingServiceData(cause); + }); + } else { + threads->getMediaThread()->PostDelayedTask(RTC_FROM_HERE, [weak, cause]() { + const auto strong = weak.lock(); + if (!strong) { + return; + } + + strong->sendPendingSignalingServiceData(cause); + }, delayMs); + } + } + ); + beginSignaling(); + + beginLogTimer(0); + } + + void sendPendingSignalingServiceData(int cause) { + commitSendSignalingMessage(_signalingEncryptedConnection->prepareForSendingService(cause)); } void sendSignalingMessage(signaling::Message const &message) { auto data = message.serialize(); + sendRawSignalingMessage(data); + } + void sendRawSignalingMessage(std::vector const &data) { RTC_LOG(LS_INFO) << "sendSignalingMessage: " << std::string(data.begin(), data.end()); - if (_signalingEncryption) { - if (const auto encryptedData = _signalingEncryption->encryptOutgoing(data)) { - _signalingDataEmitted(std::vector(encryptedData->data(), encryptedData->data() + encryptedData->size())); - } else { - RTC_LOG(LS_ERROR) << "sendSignalingMessage: failed to encrypt payload"; - } + if (_signalingEncryptedConnection) { + rtc::CopyOnWriteBuffer message; + message.AppendData(data.data(), data.size()); + commitSendSignalingMessage(_signalingEncryptedConnection->prepareForSendingRawMessage(message, true)); } else { - _signalingDataEmitted(data); + RTC_LOG(LS_ERROR) << "sendSignalingMessage encryption not available"; } } - void beginSignaling() { - _signalingEncryption.reset(new SignalingEncryption(_encryptionKey)); - - if (_encryptionKey.isOutgoing) { - sendInitialSetup(); - } - } - - void createNegotiatedChannels() { - /*const auto coordinatedState = _contentNegotiationContext->coordinatedState(); - if (!coordinatedState) { + void commitSendSignalingMessage(absl::optional packet) { + if (!packet) { return; } - if (_outgoingAudioChannelId) { - const auto audioSsrc = _contentNegotiationContext->outgoingChannelSsrc(_outgoingAudioChannelId.value()); - if (audioSsrc) { - if (_outgoingAudioChannel && _outgoingAudioChannel->ssrc() != audioSsrc.value()) { - _outgoingAudioChannel.reset(); - } - - absl::optional outgoingAudioContent; - for (const auto &content : coordinatedState->outgoingContents) { - if (content.type == signaling::MediaContent::Type::Audio && content.ssrc == audioSsrc.value()) { - outgoingAudioContent = content; - break; - } - } - - if (outgoingAudioContent) { - if (!_outgoingAudioChannel) { - _outgoingAudioChannel.reset(new OutgoingAudioChannel( - _call.get(), - _channelManager.get(), - _uniqueRandomIdGenerator.get(), - &_audioSource, - _rtpTransport, - outgoingAudioContent.value(), - _threads - )); - } - } - } - } - - if (_outgoingVideoChannelId) { - const auto videoSsrc = _contentNegotiationContext->outgoingChannelSsrc(_outgoingVideoChannelId.value()); - if (videoSsrc) { - if (_outgoingVideoChannel && _outgoingVideoChannel->ssrc() != videoSsrc.value()) { - _outgoingVideoChannel.reset(); - } - - absl::optional outgoingVideoContent; - for (const auto &content : coordinatedState->outgoingContents) { - if (content.type == signaling::MediaContent::Type::Video && content.ssrc == videoSsrc.value()) { - outgoingVideoContent = content; - break; - } - } - - if (outgoingVideoContent) { - if (!_outgoingVideoChannel) { - const auto weak = std::weak_ptr(shared_from_this()); - - _outgoingVideoChannel.reset(new OutgoingVideoChannel( - _threads, - _channelManager.get(), - _call.get(), - _rtpTransport, - _uniqueRandomIdGenerator.get(), - _videoBitrateAllocatorFactory.get(), - [threads = _threads, weak]() { - threads->getMediaThread()->PostTask(RTC_FROM_HERE, [=] { - const auto strong = weak.lock(); - if (!strong) { - return; - } - strong->sendMediaState(); - }); - }, - outgoingVideoContent.value(), - false - )); - - if (_videoCapture) { - _outgoingVideoChannel->setVideoCapture(_videoCapture); - } - } - } - } - } - - if (_outgoingScreencastChannelId) { - const auto screencastSsrc = _contentNegotiationContext->outgoingChannelSsrc(_outgoingScreencastChannelId.value()); - if (screencastSsrc) { - if (_outgoingScreencastChannel && _outgoingScreencastChannel->ssrc() != screencastSsrc.value()) { - _outgoingScreencastChannel.reset(); - } - - absl::optional outgoingScreencastContent; - for (const auto &content : coordinatedState->outgoingContents) { - if (content.type == signaling::MediaContent::Type::Video && content.ssrc == screencastSsrc.value()) { - outgoingScreencastContent = content; - break; - } - } - - if (outgoingScreencastContent) { - if (!_outgoingScreencastChannel) { - const auto weak = std::weak_ptr(shared_from_this()); - - _outgoingScreencastChannel.reset(new OutgoingVideoChannel( - _threads, - _channelManager.get(), - _call.get(), - _rtpTransport, - _uniqueRandomIdGenerator.get(), - _videoBitrateAllocatorFactory.get(), - [threads = _threads, weak]() { - threads->getMediaThread()->PostTask(RTC_FROM_HERE, [=] { - const auto strong = weak.lock(); - if (!strong) { - return; - } - strong->sendMediaState(); - }); - }, - outgoingScreencastContent.value(), - true - )); - - if (_screencastCapture) { - _outgoingScreencastChannel->setVideoCapture(_screencastCapture); - } - } - } + _signalingDataEmitted(packet.value().bytes); + } + + void beginLogTimer(int delayMs) { + const auto weak = std::weak_ptr(shared_from_this()); + _threads->getMediaThread()->PostDelayedTask(RTC_FROM_HERE, [weak]() { + auto strong = weak.lock(); + if (!strong) { + return; } + + strong->writeStateLogRecords(); + + strong->beginLogTimer(1000); + }, delayMs); + } + + void writeStateLogRecords() { + const auto weak = std::weak_ptr(shared_from_this()); + auto call = ((webrtc::PeerConnectionProxyWithInternal *)_peerConnection.get())->internal()->call_ptr(); + if (!call) { + return; } - - for (const auto &content : coordinatedState->incomingContents) { - switch (content.type) { - case signaling::MediaContent::Type::Audio: { - if (_incomingAudioChannel && _incomingAudioChannel->ssrc() != content.ssrc) { - _incomingAudioChannel.reset(); - } - - if (!_incomingAudioChannel) { - _incomingAudioChannel.reset(new IncomingV2AudioChannel( - _channelManager.get(), - _call.get(), - _rtpTransport, - _uniqueRandomIdGenerator.get(), - content, - _threads - )); - } - - break; + + _threads->getWorkerThread()->PostTask(RTC_FROM_HERE, [weak, call]() { + auto strong = weak.lock(); + if (!strong) { + return; + } + + auto stats = call->GetStats(); + float sendBitrateKbps = ((float)stats.send_bandwidth_bps / 1024.0f); + + strong->_threads->getMediaThread()->PostTask(RTC_FROM_HERE, [weak, sendBitrateKbps]() { + auto strong = weak.lock(); + if (!strong) { + return; } - case signaling::MediaContent::Type::Video: { - if (_incomingVideoChannel && _incomingVideoChannel->ssrc() != content.ssrc) { - _incomingVideoChannel.reset(); - } - - if (!_incomingVideoChannel) { - _incomingVideoChannel.reset(new IncomingV2VideoChannel( - _channelManager.get(), - _call.get(), - _rtpTransport, - _uniqueRandomIdGenerator.get(), - content, - _threads - )); - _incomingVideoChannel->addSink(_currentSink); - } - - break; + + float bitrateNorm = 16.0f; + if (strong->_outgoingVideoTransceiver) { + bitrateNorm = 600.0f; } - default: { - RTC_FATAL() << "Unknown media type"; - break; + + float signalBarsNorm = 4.0f; + float adjustedQuality = sendBitrateKbps / bitrateNorm; + adjustedQuality = fmaxf(0.0f, adjustedQuality); + adjustedQuality = fminf(1.0f, adjustedQuality); + if (strong->_signalBarsUpdated) { + strong->_signalBarsUpdated((int)(adjustedQuality * signalBarsNorm)); } - } - } - adjustBitratePreferences(true); - sendMediaState();*/ + NetworkBitrateLogRecord networkBitrateLogRecord; + networkBitrateLogRecord.bitrate = (int32_t)sendBitrateKbps; + + strong->_networkBitrateLogRecords.emplace_back(rtc::TimeMillis(), std::move(networkBitrateLogRecord)); + }); + }); } - void sendInitialSetup() { + void sendLocalDescription() { const auto weak = std::weak_ptr(shared_from_this()); - /*_networking->perform(RTC_FROM_HERE, [weak, threads = _threads, isOutgoing = _encryptionKey.isOutgoing](NativeNetworkingImpl *networking) { - auto localFingerprint = networking->getLocalFingerprint(); - std::string hash = localFingerprint->algorithm; - std::string fingerprint = localFingerprint->GetRfc4572Fingerprint(); - std::string setup; - if (isOutgoing) { - setup = "actpass"; - } else { - setup = "passive"; - } - - auto localIceParams = networking->getLocalIceParameters(); - std::string ufrag = localIceParams.ufrag; - std::string pwd = localIceParams.pwd; + _isMakingOffer = true; - threads->getMediaThread()->PostTask(RTC_FROM_HERE, [weak, ufrag, pwd, hash, fingerprint, setup, localIceParams]() { + rtc::scoped_refptr observer(new rtc::RefCountedObject([threads = _threads, weak](webrtc::RTCError error) { + threads->getMediaThread()->PostTask(RTC_FROM_HERE, [weak]() { const auto strong = weak.lock(); if (!strong) { return; } - signaling::InitialSetupMessage data; + strong->sentLocalDescription(); + + strong->_isMakingOffer = false; + }); + })); + RTC_LOG(LS_INFO) << "Calling SetLocalDescription"; + _peerConnection->SetLocalDescription(observer); + } - data.ufrag = ufrag; - data.pwd = pwd; + void sendIceCandidate(const webrtc::IceCandidateInterface *iceCandidate) { + std::string sdp; + iceCandidate->ToString(&sdp); - signaling::DtlsFingerprint dtlsFingerprint; - dtlsFingerprint.hash = hash; - dtlsFingerprint.fingerprint = fingerprint; - dtlsFingerprint.setup = setup; - data.fingerprints.push_back(std::move(dtlsFingerprint)); + json11::Json::object jsonCandidate; + jsonCandidate.insert(std::make_pair("@type", json11::Json("candidate"))); + jsonCandidate.insert(std::make_pair("sdp", json11::Json(sdp))); + jsonCandidate.insert(std::make_pair("mid", json11::Json(iceCandidate->sdp_mid()))); + jsonCandidate.insert(std::make_pair("mline", json11::Json(iceCandidate->sdp_mline_index()))); - signaling::Message message; - message.data = std::move(data); - strong->sendSignalingMessage(message); - }); - });*/ + auto jsonData = json11::Json(std::move(jsonCandidate)); + auto jsonResult = jsonData.dump(); + sendRawSignalingMessage(std::vector(jsonResult.begin(), jsonResult.end())); } - void sendOfferIfNeeded() { - /*if (const auto offer = _contentNegotiationContext->getPendingOffer()) { - signaling::NegotiateChannelsMessage data; - - data.exchangeId = offer->exchangeId; + void sentLocalDescription() { + auto localDescription = _peerConnection->local_description(); + if (localDescription) { + std::string sdp; + localDescription->ToString(&sdp); + std::string type = localDescription->type(); + + json11::Json::object jsonDescription; + jsonDescription.insert(std::make_pair("@type", json11::Json(type))); + jsonDescription.insert(std::make_pair("sdp", json11::Json(sdp))); - data.contents = offer->contents; + auto jsonData = json11::Json(std::move(jsonDescription)); + auto jsonResult = jsonData.dump(); + sendRawSignalingMessage(std::vector(jsonResult.begin(), jsonResult.end())); + } + } - signaling::Message message; - message.data = std::move(data); - sendSignalingMessage(message); - }*/ + void beginSignaling() { + if (_encryptionKey.isOutgoing) { + _didBeginNegotiation = true; + sendLocalDescription(); + } } void receiveSignalingData(const std::vector &data) { - std::vector decryptedData; - - if (_signalingEncryption) { - const auto rawDecryptedData = _signalingEncryption->decryptIncoming(data); - if (!rawDecryptedData) { - RTC_LOG(LS_ERROR) << "receiveSignalingData: could not decrypt payload"; + if (_signalingEncryptedConnection) { + if (const auto packet = _signalingEncryptedConnection->handleIncomingRawPacket((const char *)data.data(), data.size())) { + processSignalingMessage(packet.value().main.message); - return; + for (const auto &additional : packet.value().additional) { + processSignalingMessage(additional.message); + } } - - decryptedData = std::vector(rawDecryptedData->data(), rawDecryptedData->data() + rawDecryptedData->size()); } else { - decryptedData = data; + RTC_LOG(LS_ERROR) << "receiveSignalingData encryption not available"; } + } + void processSignalingMessage(rtc::CopyOnWriteBuffer const &data) { + std::vector decryptedData = std::vector(data.data(), data.data() + data.size()); processSignalingData(decryptedData); } void processSignalingData(const std::vector &data) { RTC_LOG(LS_INFO) << "processSignalingData: " << std::string(data.begin(), data.end()); - /*const auto message = signaling::Message::parse(data); - if (!message) { + std::string parsingError; + auto json = json11::Json::parse(std::string(data.begin(), data.end()), parsingError); + if (json.type() != json11::Json::OBJECT) { + RTC_LOG(LS_ERROR) << "Signaling: message must be an object"; return; } - const auto messageData = &message->data; - if (const auto initialSetup = absl::get_if(messageData)) { - PeerIceParameters remoteIceParameters; - remoteIceParameters.ufrag = initialSetup->ufrag; - remoteIceParameters.pwd = initialSetup->pwd; - std::unique_ptr fingerprint; - std::string sslSetup; - if (initialSetup->fingerprints.size() != 0) { - fingerprint = rtc::SSLFingerprint::CreateUniqueFromRfc4572(initialSetup->fingerprints[0].hash, initialSetup->fingerprints[0].fingerprint); - sslSetup = initialSetup->fingerprints[0].setup; - } + const auto jsonType = json.object_items().find("@type"); + if (jsonType == json.object_items().end()) { + RTC_LOG(LS_ERROR) << "Signaling: @type is missing"; + return; + } + std::string type = jsonType->second.string_value(); - _networking->perform(RTC_FROM_HERE, [threads = _threads, remoteIceParameters = std::move(remoteIceParameters), fingerprint = std::move(fingerprint), sslSetup = std::move(sslSetup)](NativeNetworkingImpl *networking) { - networking->setRemoteParams(remoteIceParameters, fingerprint.get(), sslSetup); - }); + if (type == "offer" || type == "answer") { + const auto jsonSdp = json.object_items().find("sdp"); + if (jsonSdp == json.object_items().end()) { + RTC_LOG(LS_ERROR) << "Signaling: sdp is missing"; + return; + } + std::string sdp = jsonSdp->second.string_value(); - if (_encryptionKey.isOutgoing) { - sendOfferIfNeeded(); + bool offerCollision = (type == "offer") && (_isMakingOffer || _peerConnection->signaling_state() != webrtc::PeerConnectionInterface::SignalingState::kStable); + bool ignoreOffer = _encryptionKey.isOutgoing && offerCollision; + if (ignoreOffer) { + return; } else { - sendInitialSetup(); + applyRemoteSdp(type, sdp); + } + } else if (type == "candidate") { + auto jsonMid = json.object_items().find("mid"); + if (jsonMid == json.object_items().end()) { + return; } - _handshakeCompleted = true; - commitPendingIceCandidates(); - } else if (const auto offerAnwer = absl::get_if(messageData)) { - auto negotiationContents = std::make_unique(); - negotiationContents->exchangeId = offerAnwer->exchangeId; - negotiationContents->contents = offerAnwer->contents; - - if (const auto response = _contentNegotiationContext->setRemoteNegotiationContent(std::move(negotiationContents))) { - signaling::NegotiateChannelsMessage data; + auto jsonMLineIndex = json.object_items().find("mline"); + if (jsonMLineIndex == json.object_items().end()) { + return; + } + + auto jsonSdp = json.object_items().find("sdp"); + if (jsonSdp == json.object_items().end()) { + return; + } - data.exchangeId = response->exchangeId; - data.contents = response->contents; + webrtc::SdpParseError parseError; + webrtc::IceCandidateInterface *iceCandidate = webrtc::CreateIceCandidate(jsonMid->second.string_value(), jsonMLineIndex->second.int_value(), jsonSdp->second.string_value(), &parseError); + if (iceCandidate) { + std::unique_ptr candidarePtr; + candidarePtr.reset(iceCandidate); - signaling::Message message; - message.data = std::move(data); - sendSignalingMessage(message); + if (_handshakeCompleted) { + _peerConnection->AddIceCandidate(candidarePtr.get()); + } else { + _pendingIceCandidates.push_back(std::move(candidarePtr)); + } } + } else { + const auto message = signaling::Message::parse(data); + if (!message) { + return; + } + const auto messageData = &message->data; - sendOfferIfNeeded(); - - createNegotiatedChannels(); - } else if (const auto candidatesList = absl::get_if(messageData)) { - for (const auto &candidate : candidatesList->iceCandidates) { - webrtc::JsepIceCandidate parseCandidate{ std::string(), 0 }; - if (!parseCandidate.Initialize(candidate.sdpString, nullptr)) { - RTC_LOG(LS_ERROR) << "Could not parse candidate: " << candidate.sdpString; - continue; + if (const auto mediaState = absl::get_if(messageData)) { + AudioState mappedAudioState; + if (mediaState->isMuted) { + mappedAudioState = AudioState::Muted; + } else { + mappedAudioState = AudioState::Active; } - _pendingIceCandidates.push_back(parseCandidate.candidate()); - } - if (_handshakeCompleted) { - commitPendingIceCandidates(); - } - } else if (const auto mediaState = absl::get_if(messageData)) { - AudioState mappedAudioState; - if (mediaState->isMuted) { - mappedAudioState = AudioState::Muted; - } else { - mappedAudioState = AudioState::Active; - } + VideoState mappedVideoState; + switch (mediaState->videoState) { + case signaling::MediaStateMessage::VideoState::Inactive: { + mappedVideoState = VideoState::Inactive; + break; + } + case signaling::MediaStateMessage::VideoState::Suspended: { + mappedVideoState = VideoState::Paused; + break; + } + case signaling::MediaStateMessage::VideoState::Active: { + mappedVideoState = VideoState::Active; + break; + } + default: { + RTC_FATAL() << "Unknown videoState"; + break; + } + } - VideoState mappedVideoState; - switch (mediaState->videoState) { - case signaling::MediaStateMessage::VideoState::Inactive: { - mappedVideoState = VideoState::Inactive; - break; + VideoState mappedScreencastState; + switch (mediaState->screencastState) { + case signaling::MediaStateMessage::VideoState::Inactive: { + mappedScreencastState = VideoState::Inactive; + break; + } + case signaling::MediaStateMessage::VideoState::Suspended: { + mappedScreencastState = VideoState::Paused; + break; + } + case signaling::MediaStateMessage::VideoState::Active: { + mappedScreencastState = VideoState::Active; + break; + } + default: { + RTC_FATAL() << "Unknown videoState"; + break; + } } - case signaling::MediaStateMessage::VideoState::Suspended: { - mappedVideoState = VideoState::Paused; - break; + + VideoState effectiveVideoState = mappedVideoState; + if (mappedScreencastState == VideoState::Active || mappedScreencastState == VideoState::Paused) { + effectiveVideoState = mappedScreencastState; } - case signaling::MediaStateMessage::VideoState::Active: { - mappedVideoState = VideoState::Active; - break; + + if (_remoteMediaStateUpdated) { + _remoteMediaStateUpdated(mappedAudioState, effectiveVideoState); } - default: { - RTC_FATAL() << "Unknown videoState"; - break; + + if (_remoteBatteryLevelIsLowUpdated) { + _remoteBatteryLevelIsLowUpdated(mediaState->isBatteryLow); } } + } + } - VideoState mappedScreencastState; - switch (mediaState->screencastState) { - case signaling::MediaStateMessage::VideoState::Inactive: { - mappedScreencastState = VideoState::Inactive; - break; - } - case signaling::MediaStateMessage::VideoState::Suspended: { - mappedScreencastState = VideoState::Paused; - break; - } - case signaling::MediaStateMessage::VideoState::Active: { - mappedScreencastState = VideoState::Active; - break; - } - default: { - RTC_FATAL() << "Unknown videoState"; - break; + void applyRemoteSdp(std::string const &type, std::string const &sdp) { + webrtc::SdpParseError sdpParseError; + std::unique_ptr remoteDescription(webrtc::CreateSessionDescription(type, sdp, &sdpParseError)); + const auto weak = std::weak_ptr(shared_from_this()); + rtc::scoped_refptr observer(new rtc::RefCountedObject([threads = _threads, weak, type](webrtc::RTCError error) { + threads->getMediaThread()->PostTask(RTC_FROM_HERE, [weak, type]() { + const auto strong = weak.lock(); + if (!strong) { + return; } - } - VideoState effectiveVideoState = mappedVideoState; - if (mappedScreencastState == VideoState::Active || mappedScreencastState == VideoState::Paused) { - effectiveVideoState = mappedScreencastState; - } + if (type == "offer") { + strong->_didBeginNegotiation = true; + strong->sendLocalDescription(); + } + }); + })); + RTC_LOG(LS_INFO) << "Calling SetRemoteDescription"; + _peerConnection->SetRemoteDescription(std::move(remoteDescription), observer); - if (_remoteMediaStateUpdated) { - _remoteMediaStateUpdated(mappedAudioState, effectiveVideoState); - } + if (!_handshakeCompleted) { + _handshakeCompleted = true; - if (_remoteBatteryLevelIsLowUpdated) { - _remoteBatteryLevelIsLowUpdated(mediaState->isBatteryLow); - } - }*/ + commitPendingIceCandidates(); + } } void commitPendingIceCandidates() { if (_pendingIceCandidates.size() == 0) { return; } - /*_networking->perform(RTC_FROM_HERE, [threads = _threads, parsedCandidates = _pendingIceCandidates](NativeNetworkingImpl *networking) { - networking->addCandidates(parsedCandidates); - }); - _pendingIceCandidates.clear();*/ + + for (const auto &candidate : _pendingIceCandidates) { + if (candidate) { + _peerConnection->AddIceCandidate(candidate.get()); + } + } + + _pendingIceCandidates.clear(); } - /*void onNetworkStateUpdated(NativeNetworkingImpl::State const &state) { + void onNetworkStateUpdated() { + NetworkStateLogRecord record; + record.isConnected = _isConnected; + record.connection = _currentConnectionDescription; + record.isFailed = _isFailed; + + if (!_currentNetworkStateLogRecord || !(_currentNetworkStateLogRecord.value() == record)) { + _currentNetworkStateLogRecord = record; + _networkStateLogRecords.emplace_back(rtc::TimeMillis(), std::move(record)); + } + State mappedState; - if (state.isReadyToSendData) { + if (_isFailed) { + mappedState = State::Failed; + } else if (_isConnected) { mappedState = State::Established; } else { mappedState = State::Reconnecting; } _stateUpdated(mappedState); - }*/ + } + + void attachDataChannel(rtc::scoped_refptr dataChannel) { + const auto weak = std::weak_ptr(shared_from_this()); + + DataChannelObserverImpl::Parameters dataChannelObserverParams; + dataChannelObserverParams.onStateChange = [threads = _threads, weak]() { + threads->getMediaThread()->PostTask(RTC_FROM_HERE, [weak]() { + const auto strong = weak.lock(); + if (!strong) { + return; + } + + strong->onDataChannelStateUpdated(); + }); + }; + dataChannelObserverParams.onMessage = [threads = _threads, weak](webrtc::DataBuffer const &buffer) { + const auto strong = weak.lock(); + if (!strong) { + return; + } + + std::string message(buffer.data.data(), buffer.data.data() + buffer.data.size()); + + if (!buffer.binary) { + RTC_LOG(LS_INFO) << "dataChannelMessage received: " << message; + std::vector data(message.begin(), message.end()); + strong->processSignalingData(data); + } else { + RTC_LOG(LS_INFO) << "dataChannelMessage rejecting binary message"; + } + }; + _dataChannelObserver = std::make_unique(std::move(dataChannelObserverParams)); + _dataChannel = dataChannel; - /*void onDataChannelStateUpdated(bool isDataChannelOpen) { - if (_isDataChannelOpen != isDataChannelOpen) { - _isDataChannelOpen = isDataChannelOpen; + onDataChannelStateUpdated(); + + _dataChannel->RegisterObserver(_dataChannelObserver.get()); + } - if (_isDataChannelOpen) { - sendMediaState(); + void onDataChannelStateUpdated() { + if (_dataChannel) { + switch (_dataChannel->state()) { + case webrtc::DataChannelInterface::DataState::kOpen: { + if (!_isDataChannelOpen) { + _isDataChannelOpen = true; + sendMediaState(); + } + break; + } + default: { + _isDataChannelOpen = false; + break; + } } } } @@ -629,47 +1084,37 @@ class InstanceV2ReferenceImplInternal : public std::enable_shared_from_thisperform(RTC_FROM_HERE, [stringData = std::move(stringData)](NativeNetworkingImpl *networking) { - networking->sendDataChannelMessage(stringData); - }); + if (_dataChannel) { + _dataChannel->Send(webrtc::DataBuffer(stringData)); + } } void onDataChannelMessage(std::string const &message) { RTC_LOG(LS_INFO) << "dataChannelMessage received: " << message; std::vector data(message.begin(), message.end()); processSignalingData(data); - }*/ + } void sendMediaState() { - /*if (!_isDataChannelOpen) { + if (!_isDataChannelOpen) { return; } signaling::Message message; signaling::MediaStateMessage data; data.isMuted = _isMicrophoneMuted; data.isBatteryLow = _isBatteryLow; - if (_outgoingVideoChannel) { - if (_outgoingVideoChannel->videoCapture()) { + if (_outgoingVideoTransceiver) { + if (_videoCapture) { data.videoState = signaling::MediaStateMessage::VideoState::Active; - } else{ + } else { data.videoState = signaling::MediaStateMessage::VideoState::Inactive; } - data.videoRotation = _outgoingVideoChannel->getRotation(); } else { data.videoState = signaling::MediaStateMessage::VideoState::Inactive; data.videoRotation = signaling::MediaStateMessage::VideoRotation::Rotation0; } - if (_outgoingScreencastChannel) { - if (_outgoingScreencastChannel->videoCapture()) { - data.screencastState = signaling::MediaStateMessage::VideoState::Active; - } else{ - data.screencastState = signaling::MediaStateMessage::VideoState::Inactive; - } - } else { - data.screencastState = signaling::MediaStateMessage::VideoState::Inactive; - } message.data = std::move(data); - sendDataChannelMessage(message);*/ + sendDataChannelMessage(message); } void sendCandidate(const cricket::Candidate &candidate) { @@ -697,72 +1142,82 @@ class InstanceV2ReferenceImplInternal : public std::enable_shared_from_this videoCapture) { - /*auto videoCaptureImpl = GetVideoCaptureAssumingSameThread(videoCapture.get()); - if (videoCaptureImpl) { - if (videoCaptureImpl->isScreenCapture()) { - _videoCapture = nullptr; - _screencastCapture = videoCapture; + _isPerformingConfiguration = true; - if (_outgoingVideoChannel) { - _outgoingVideoChannel->setVideoCapture(nullptr); - } - if (_outgoingVideoChannelId) { - _contentNegotiationContext->removeOutgoingChannel(_outgoingVideoChannelId.value()); - _outgoingVideoChannelId.reset(); - } + if (_outgoingVideoTransceiver) { + _peerConnection->RemoveTrackNew(_outgoingVideoTransceiver->sender()); + } + if (_outgoingVideoTrack) { + _outgoingVideoTrack = nullptr; + } + _outgoingVideoTransceiver = nullptr; - if (_outgoingScreencastChannel) { - _outgoingScreencastChannel->setVideoCapture(videoCapture); - } - if (!_outgoingScreencastChannelId) { - _outgoingScreencastChannelId = _contentNegotiationContext->addOutgoingChannel(signaling::MediaContent::Type::Video); - } + auto videoCaptureImpl = GetVideoCaptureAssumingSameThread(videoCapture.get()); + if (videoCaptureImpl) { + if (videoCaptureImpl->isScreenCapture()) { } else { _videoCapture = videoCapture; - _screencastCapture = nullptr; - if (_outgoingVideoChannel) { - _outgoingVideoChannel->setVideoCapture(videoCapture); - } - if (!_outgoingVideoChannelId) { - _outgoingVideoChannelId = _contentNegotiationContext->addOutgoingChannel(signaling::MediaContent::Type::Video); - } + auto videoTrack = _peerConnectionFactory->CreateVideoTrack("1", videoCaptureImpl->source()); + if (videoTrack) { + webrtc::RtpTransceiverInit transceiverInit; + transceiverInit.stream_ids = { "0" }; + + webrtc::RTCErrorOr> videoTransceiverOrError = _peerConnection->AddTransceiver(videoTrack, transceiverInit); + if (videoTransceiverOrError.ok()) { + _outgoingVideoTrack = videoTrack; + _outgoingVideoTransceiver = videoTransceiverOrError.value(); + + auto currentCapabilities = _peerConnectionFactory->GetRtpSenderCapabilities(cricket::MediaType::MEDIA_TYPE_VIDEO); + + std::vector codecPreferences = { + #ifndef WEBRTC_DISABLE_H265 + cricket::kH265CodecName, + #endif + cricket::kH264CodecName + }; + + for (const auto &codecCapability : currentCapabilities.codecs) { + if (std::find_if(codecPreferences.begin(), codecPreferences.end(), [&](std::string const &value) { + return value == codecCapability.name; + }) != codecPreferences.end()) { + continue; + } + codecPreferences.push_back(codecCapability.name); + } - if (_outgoingScreencastChannel) { - _outgoingScreencastChannel->setVideoCapture(nullptr); - } - if (_outgoingScreencastChannelId) { - _contentNegotiationContext->removeOutgoingChannel(_outgoingScreencastChannelId.value()); - _outgoingScreencastChannelId.reset(); - } - } - } else { - _videoCapture = nullptr; - _screencastCapture = nullptr; + std::vector codecCapabilities; + for (const auto &name : codecPreferences) { + for (const auto &codecCapability : currentCapabilities.codecs) { + if (codecCapability.name == name) { + codecCapabilities.push_back(codecCapability); - if (_outgoingVideoChannel) { - _outgoingVideoChannel->setVideoCapture(nullptr); - } + break; + } + } + } - if (_outgoingScreencastChannel) { - _outgoingScreencastChannel->setVideoCapture(nullptr); - } - - if (_outgoingVideoChannelId) { - _contentNegotiationContext->removeOutgoingChannel(_outgoingVideoChannelId.value()); - _outgoingVideoChannelId.reset(); - } - - if (_outgoingScreencastChannelId) { - _contentNegotiationContext->removeOutgoingChannel(_outgoingScreencastChannelId.value()); - _outgoingScreencastChannelId.reset(); + _outgoingVideoTransceiver->SetCodecPreferences(codecCapabilities); + + webrtc::RtpParameters parameters = _outgoingVideoTransceiver->sender()->GetParameters(); + if (parameters.encodings.empty()) { + parameters.encodings.push_back(webrtc::RtpEncodingParameters()); + } + parameters.encodings[0].max_bitrate_bps = 1200 * 1024; + _outgoingVideoTransceiver->sender()->SetParameters(parameters); + + _outgoingVideoTrack->set_enabled(true); + } + } } } - sendOfferIfNeeded(); - sendMediaState(); - adjustBitratePreferences(true); - createNegotiatedChannels();*/ + _isPerformingConfiguration = false; + + if (_didBeginNegotiation) { + sendMediaState(); + sendLocalDescription(); + } } void setRequestedVideoAspect(float aspect) { @@ -772,20 +1227,37 @@ class InstanceV2ReferenceImplInternal : public std::enable_shared_from_thissetIsMuted(muteMicrophone); + if (_outgoingAudioTrack) { + _outgoingAudioTrack->set_enabled(!_isMicrophoneMuted); } sendMediaState(); - }*/ + } + } + + void connectIncomingVideoSink(rtc::scoped_refptr transceiver) { + if (_currentStrongSink) { + webrtc::VideoTrackInterface *videoTrack = (webrtc::VideoTrackInterface *)transceiver->receiver()->track().get(); + videoTrack->AddOrUpdateSink(_currentStrongSink.get(), rtc::VideoSinkWants()); + } + } + + void disconnectIncomingVideoSink() { } void setIncomingVideoOutput(std::weak_ptr> sink) { - /*_currentSink = sink; - if (_incomingVideoChannel) { + _currentStrongSink = sink.lock(); + + if (_currentStrongSink) { + if (!_incomingVideoTransceivers.empty()) { + connectIncomingVideoSink(_incomingVideoTransceivers.begin()->second); + } + } + + /*if (_incomingVideoChannel) { _incomingVideoChannel->addSink(sink); } if (_incomingScreencastChannel) { @@ -807,7 +1279,87 @@ class InstanceV2ReferenceImplInternal : public std::enable_shared_from_this completion) { - completion({}); + _peerConnection->Close(); + + FinalState finalState; + + json11::Json::object statsLog; + + statsLog.insert(std::make_pair("v", std::move(3))); + + for (int i = (int)_networkStateLogRecords.size() - 1; i >= 1; i--) { + // coalesce events within 5ms + if (_networkStateLogRecords[i].timestamp - _networkStateLogRecords[i - 1].timestamp < 5) { + _networkStateLogRecords.erase(_networkStateLogRecords.begin() + i - 1); + } + } + + json11::Json::array jsonNetworkStateLogRecords; + int64_t baseTimestamp = 0; + for (const auto &record : _networkStateLogRecords) { + json11::Json::object jsonRecord; + + std::ostringstream timestampString; + + if (baseTimestamp == 0) { + baseTimestamp = record.timestamp; + } + timestampString << (record.timestamp - baseTimestamp); + + jsonRecord.insert(std::make_pair("t", json11::Json(timestampString.str()))); + jsonRecord.insert(std::make_pair("c", json11::Json(record.record.isConnected ? 1 : 0))); + if (record.record.route) { + jsonRecord.insert(std::make_pair("local", json11::Json(record.record.route->localDescription))); + jsonRecord.insert(std::make_pair("remote", json11::Json(record.record.route->remoteDescription))); + } + if (record.record.connection) { + json11::Json::object jsonConnection; + + auto serializeCandidate = [](NativeNetworkingImpl::ConnectionDescription::CandidateDescription const &candidate) -> json11::Json::object { + json11::Json::object jsonCandidate; + + jsonCandidate.insert(std::make_pair("type", json11::Json(candidate.type))); + jsonCandidate.insert(std::make_pair("protocol", json11::Json(candidate.protocol))); + jsonCandidate.insert(std::make_pair("address", json11::Json(candidate.address))); + + return jsonCandidate; + }; + + jsonConnection.insert(std::make_pair("local", serializeCandidate(record.record.connection->local))); + jsonConnection.insert(std::make_pair("remote", serializeCandidate(record.record.connection->remote))); + + jsonRecord.insert(std::make_pair("network", std::move(jsonConnection))); + } + if (record.record.isFailed) { + jsonRecord.insert(std::make_pair("failed", json11::Json(1))); + } + + jsonNetworkStateLogRecords.push_back(std::move(jsonRecord)); + } + statsLog.insert(std::make_pair("network", std::move(jsonNetworkStateLogRecords))); + + json11::Json::array jsonNetworkBitrateLogRecords; + for (const auto &record : _networkBitrateLogRecords) { + json11::Json::object jsonRecord; + + jsonRecord.insert(std::make_pair("b", json11::Json(record.record.bitrate))); + + jsonNetworkBitrateLogRecords.push_back(std::move(jsonRecord)); + } + statsLog.insert(std::make_pair("bitrate", std::move(jsonNetworkBitrateLogRecords))); + + auto jsonStatsLog = json11::Json(std::move(statsLog)); + + if (!_statsLogPath.data.empty()) { + std::ofstream file; + file.open(_statsLogPath.data); + + file << jsonStatsLog.dump(); + + file.close(); + } + + completion(finalState); } /*void adjustBitratePreferences(bool resetStartBitrate) { @@ -855,15 +1407,42 @@ class InstanceV2ReferenceImplInternal : public std::enable_shared_from_this _remotePrefferedAspectRatioUpdated; std::function &)> _signalingDataEmitted; std::function(webrtc::TaskQueueFactory*)> _createAudioDeviceModule; + FilePath _statsLogPath; + + std::unique_ptr _signalingEncryptedConnection; + + bool _isConnected = false; + bool _isFailed = false; + absl::optional _currentConnectionDescription; + + absl::optional _currentNetworkStateLogRecord; + std::vector> _networkStateLogRecords; + std::vector> _networkBitrateLogRecords; - std::unique_ptr _signalingEncryption; + bool _didBeginNegotiation = false; + bool _isMakingOffer = false; + bool _isPerformingConfiguration = false; + + rtc::scoped_refptr _outgoingAudioTrack; + rtc::scoped_refptr _outgoingAudioTransceiver; + bool _isMicrophoneMuted = false; + + rtc::scoped_refptr _outgoingVideoTrack; + rtc::scoped_refptr _outgoingVideoTransceiver; + + std::map> _incomingVideoTransceivers; bool _handshakeCompleted = false; - std::vector _pendingIceCandidates; + std::vector> _pendingIceCandidates; + + std::unique_ptr _dataChannelObserver; + rtc::scoped_refptr _dataChannel; bool _isDataChannelOpen = false; std::unique_ptr _eventLog; std::unique_ptr _taskQueueFactory; + rtc::scoped_refptr _peerConnectionFactory; + std::unique_ptr _peerConnectionObserver; rtc::scoped_refptr _peerConnection; webrtc::FieldTrialBasedConfig _fieldTrials; @@ -873,10 +1452,9 @@ class InstanceV2ReferenceImplInternal : public std::enable_shared_from_this> _currentSink; + std::shared_ptr> _currentStrongSink; std::shared_ptr _videoCapture; - std::shared_ptr _screencastCapture; std::shared_ptr _platformContext; }; @@ -974,7 +1552,7 @@ void InstanceV2ReferenceImpl::setEchoCancellationStrength(int strength) { std::vector InstanceV2ReferenceImpl::GetVersions() { std::vector result; - result.push_back("4.0.2"); + result.push_back("4.1.2"); return result; } diff --git a/TMessagesProj/jni/voip/tgcalls/v2/NativeNetworkingImpl.cpp b/TMessagesProj/jni/voip/tgcalls/v2/NativeNetworkingImpl.cpp index 3bf0a3c824f..4f20a7b8aaf 100644 --- a/TMessagesProj/jni/voip/tgcalls/v2/NativeNetworkingImpl.cpp +++ b/TMessagesProj/jni/voip/tgcalls/v2/NativeNetworkingImpl.cpp @@ -20,21 +20,12 @@ #include "SctpDataChannelProviderInterfaceImpl.h" #include "StaticThreads.h" #include "platform/PlatformInterface.h" +#include "p2p/base/turn_port.h" namespace tgcalls { namespace { -NativeNetworkingImpl::ConnectionDescription::CandidateDescription connectionDescriptionFromCandidate(cricket::Candidate const &candidate) { - NativeNetworkingImpl::ConnectionDescription::CandidateDescription result; - - result.type = candidate.type(); - result.protocol = candidate.protocol(); - result.address = candidate.address().ToString(); - - return result; -} - class CryptStringImpl : public rtc::CryptStringImpl { public: CryptStringImpl(std::string const &value) : @@ -192,6 +183,16 @@ class WrappedBasicPacketSocketFactory : public rtc::BasicPacketSocketFactory { } +NativeNetworkingImpl::ConnectionDescription::CandidateDescription NativeNetworkingImpl::connectionDescriptionFromCandidate(cricket::Candidate const &candidate) { + NativeNetworkingImpl::ConnectionDescription::CandidateDescription result; + + result.type = candidate.type(); + result.protocol = candidate.protocol(); + result.address = candidate.address().ToString(); + + return result; +} + webrtc::CryptoOptions NativeNetworkingImpl::getDefaulCryptoOptions() { auto options = webrtc::CryptoOptions(); options.srtp.enable_aes128_sha1_80_crypto_cipher = true; @@ -215,7 +216,7 @@ _dataChannelStateUpdated(configuration.dataChannelStateUpdated), _dataChannelMessageReceived(configuration.dataChannelMessageReceived) { assert(_threads->getNetworkThread()->IsCurrent()); - _localIceParameters = PeerIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH)); + _localIceParameters = PeerIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH), true); _localCertificate = rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(rtc::KT_ECDSA), absl::nullopt); @@ -310,7 +311,7 @@ void NativeNetworkingImpl::resetDtlsSrtpTransport() { } } - _portAllocator->SetConfiguration(stunServers, turnServers, 2, webrtc::NO_PRUNE, _turnCustomizer.get()); + _portAllocator->SetConfiguration(stunServers, turnServers, 0, webrtc::NO_PRUNE, _turnCustomizer.get()); _transportChannel = cricket::P2PTransportChannel::Create("transport", 0, _portAllocator.get(), _asyncResolverFactory.get()); @@ -324,7 +325,7 @@ void NativeNetworkingImpl::resetDtlsSrtpTransport() { cricket::IceParameters localIceParameters( _localIceParameters.ufrag, _localIceParameters.pwd, - false + _localIceParameters.supportsRenomination ); _transportChannel->SetIceParameters(localIceParameters); @@ -334,7 +335,6 @@ void NativeNetworkingImpl::resetDtlsSrtpTransport() { _transportChannel->SignalCandidateGathered.connect(this, &NativeNetworkingImpl::candidateGathered); _transportChannel->SignalIceTransportStateChanged.connect(this, &NativeNetworkingImpl::transportStateChanged); _transportChannel->SignalCandidatePairChanged.connect(this, &NativeNetworkingImpl::candidatePairChanged); - _transportChannel->SignalReadPacket.connect(this, &NativeNetworkingImpl::transportPacketReceived); _transportChannel->SignalNetworkRouteChanged.connect(this, &NativeNetworkingImpl::transportRouteChanged); webrtc::CryptoOptions cryptoOptions = NativeNetworkingImpl::getDefaulCryptoOptions(); @@ -384,7 +384,7 @@ void NativeNetworkingImpl::start() { _threads )); - _lastNetworkActivityMs = rtc::TimeMillis(); + _lastDisconnectedTimestamp = rtc::TimeMillis(); checkConnectionTimeout(); } @@ -404,7 +404,7 @@ void NativeNetworkingImpl::stop() { _transportChannel.reset(); _portAllocator.reset(); - _localIceParameters = PeerIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH)); + _localIceParameters = PeerIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH), true); _localCertificate = rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(rtc::KT_ECDSA), absl::nullopt); @@ -429,7 +429,7 @@ void NativeNetworkingImpl::setRemoteParams(PeerIceParameters const &remoteIcePar cricket::IceParameters parameters( remoteIceParameters.ufrag, remoteIceParameters.pwd, - false + remoteIceParameters.supportsRenomination ); _transportChannel->SetRemoteIceParameters(parameters); @@ -474,7 +474,9 @@ void NativeNetworkingImpl::checkConnectionTimeout() { int64_t currentTimestamp = rtc::TimeMillis(); const int64_t maxTimeout = 20000; - if (strong->_lastNetworkActivityMs + maxTimeout < currentTimestamp) { + if (!strong->_isConnected && strong->_lastDisconnectedTimestamp + maxTimeout < currentTimestamp) { + RTC_LOG(LS_INFO) << "NativeNetworkingImpl timeout " << (currentTimestamp - strong->_lastDisconnectedTimestamp) << " ms"; + strong->_isFailed = true; strong->notifyStateUpdated(); } @@ -527,13 +529,6 @@ void NativeNetworkingImpl::transportReadyToSend(cricket::IceTransportInternal *t assert(_threads->getNetworkThread()->IsCurrent()); } -void NativeNetworkingImpl::transportPacketReceived(rtc::PacketTransportInternal *transport, const char *bytes, size_t size, const int64_t ×tamp, int unused) { - assert(_threads->getNetworkThread()->IsCurrent()); - - _lastNetworkActivityMs = rtc::TimeMillis(); - _isFailed = false; -} - void NativeNetworkingImpl::transportRouteChanged(absl::optional route) { assert(_threads->getNetworkThread()->IsCurrent()); @@ -605,6 +600,10 @@ void NativeNetworkingImpl::UpdateAggregateStates_n() { if (_isConnected != isConnected) { _isConnected = isConnected; + + if (!isConnected) { + _lastDisconnectedTimestamp = rtc::TimeMillis(); + } notifyStateUpdated(); diff --git a/TMessagesProj/jni/voip/tgcalls/v2/NativeNetworkingImpl.h b/TMessagesProj/jni/voip/tgcalls/v2/NativeNetworkingImpl.h index 45af35e18f4..69b06e218d8 100644 --- a/TMessagesProj/jni/voip/tgcalls/v2/NativeNetworkingImpl.h +++ b/TMessagesProj/jni/voip/tgcalls/v2/NativeNetworkingImpl.h @@ -143,6 +143,7 @@ class NativeNetworkingImpl : public sigslot::has_slots<>, public std::enable_sha }; static webrtc::CryptoOptions getDefaulCryptoOptions(); + static ConnectionDescription::CandidateDescription connectionDescriptionFromCandidate(cricket::Candidate const &candidate); NativeNetworkingImpl(Configuration &&configuration); ~NativeNetworkingImpl(); @@ -168,7 +169,6 @@ class NativeNetworkingImpl : public sigslot::has_slots<>, public std::enable_sha void OnTransportReceivingState_n(rtc::PacketTransportInternal *transport); void transportStateChanged(cricket::IceTransportInternal *transport); void transportReadyToSend(cricket::IceTransportInternal *transport); - void transportPacketReceived(rtc::PacketTransportInternal *transport, const char *bytes, size_t size, const int64_t ×tamp, int unused); void transportRouteChanged(absl::optional route); void candidatePairChanged(cricket::CandidatePairChangeEvent const &event); void DtlsReadyToSend(bool DtlsReadyToSend); @@ -214,7 +214,7 @@ class NativeNetworkingImpl : public sigslot::has_slots<>, public std::enable_sha bool _isConnected = false; bool _isFailed = false; - int64_t _lastNetworkActivityMs = 0; + int64_t _lastDisconnectedTimestamp = 0; absl::optional _currentRouteDescription; absl::optional _currentConnectionDescription; }; diff --git a/TMessagesProj/jni/voip/tgcalls/v2/Signaling.cpp b/TMessagesProj/jni/voip/tgcalls/v2/Signaling.cpp index 1a981dbcafd..20893fac333 100644 --- a/TMessagesProj/jni/voip/tgcalls/v2/Signaling.cpp +++ b/TMessagesProj/jni/voip/tgcalls/v2/Signaling.cpp @@ -366,6 +366,7 @@ std::vector InitialSetupMessage_serialize(const InitialSetupMessage * c object.insert(std::make_pair("@type", json11::Json("InitialSetup"))); object.insert(std::make_pair("ufrag", json11::Json(message->ufrag))); object.insert(std::make_pair("pwd", json11::Json(message->pwd))); + object.insert(std::make_pair("renomination", json11::Json(message->supportsRenomination))); json11::Json::array jsonFingerprints; for (const auto &fingerprint : message->fingerprints) { @@ -393,6 +394,11 @@ absl::optional InitialSetupMessage_parse(json11::Json::obje RTC_LOG(LS_ERROR) << "Signaling: pwd must be a string"; return absl::nullopt; } + const auto renomination = object.find("renomination"); + bool renominationValue = false; + if (renomination != object.end() && renomination->second.is_bool()) { + renominationValue = renomination->second.bool_value(); + } const auto fingerprints = object.find("fingerprints"); if (fingerprints == object.end() || !fingerprints->second.is_array()) { RTC_LOG(LS_ERROR) << "Signaling: fingerprints must be an array"; @@ -431,6 +437,7 @@ absl::optional InitialSetupMessage_parse(json11::Json::obje InitialSetupMessage message; message.ufrag = ufrag->second.string_value(); message.pwd = pwd->second.string_value(); + message.supportsRenomination = renominationValue; message.fingerprints = std::move(parsedFingerprints); return message; diff --git a/TMessagesProj/jni/voip/tgcalls/v2/Signaling.h b/TMessagesProj/jni/voip/tgcalls/v2/Signaling.h index 686c2597290..488f39367d3 100644 --- a/TMessagesProj/jni/voip/tgcalls/v2/Signaling.h +++ b/TMessagesProj/jni/voip/tgcalls/v2/Signaling.h @@ -138,6 +138,7 @@ struct MediaContent { struct InitialSetupMessage { std::string ufrag; std::string pwd; + bool supportsRenomination = false; std::vector fingerprints; }; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java b/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java index 05bd16af4f4..62844c86e75 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java @@ -2455,7 +2455,7 @@ public static void checkForUpdates() { } public static void appCenterLog(Throwable e) { - + } public static boolean shouldShowClipboardToast() { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java b/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java index f9854a56ebf..a8e99ecc2b8 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java @@ -20,8 +20,8 @@ public class BuildVars { public static boolean USE_CLOUD_STRINGS = true; public static boolean CHECK_UPDATES = true; public static boolean NO_SCOPED_STORAGE = Build.VERSION.SDK_INT <= 29; - public static int BUILD_VERSION = 2622; - public static String BUILD_VERSION_STRING = "8.7.0"; + public static int BUILD_VERSION = 2629; + public static String BUILD_VERSION_STRING = "8.7.1"; public static int APP_ID = 4; public static String APP_HASH = "014b35b6184100b085b0d0572f9b5103"; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/DownloadController.java b/TMessagesProj/src/main/java/org/telegram/messenger/DownloadController.java index a88454f8488..315a224498f 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/DownloadController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/DownloadController.java @@ -1356,7 +1356,7 @@ public void deleteRecentFiles(ArrayList messageObjects) { for (int i = 0; i < messageObjects.size(); i++) { boolean found = false; for (int j = 0; j < recentDownloadingFiles.size(); j++) { - if (messageObjects.get(i).getId() == recentDownloadingFiles.get(j).getId()) { + if (messageObjects.get(i).getId() == recentDownloadingFiles.get(j).getId() && recentDownloadingFiles.get(j).getDialogId() == messageObjects.get(i).getDialogId()) { recentDownloadingFiles.remove(j); found = true; break; @@ -1364,7 +1364,7 @@ public void deleteRecentFiles(ArrayList messageObjects) { } if (!found) { for (int j = 0; j < downloadingFiles.size(); j++) { - if (messageObjects.get(i).getId() == downloadingFiles.get(j).getId()) { + if (messageObjects.get(i).getId() == downloadingFiles.get(j).getId() && downloadingFiles.get(j).getDialogId() == messageObjects.get(i).getDialogId()) { downloadingFiles.remove(j); found = true; break; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java index 93582704a9a..5f671fd0623 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java @@ -4912,6 +4912,7 @@ public void didWriteData(long availableSize, float progress) { info.mediaEntities, info.isPhoto, info.cropState, + info.roundVideo, callback); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java index 3d19da05e6f..bd81bace9b0 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java @@ -6417,7 +6417,7 @@ public boolean probablyRingtone() { for (int a = 0; a < getDocument().attributes.size(); a++) { TLRPC.DocumentAttribute attribute = getDocument().attributes.get(a); if (attribute instanceof TLRPC.TL_documentAttributeAudio) { - if (attribute.duration < MessagesController.getInstance(currentAccount).ringtoneDurationMax * 2) { + if (attribute.duration < 60) { return true; } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java index aa7be0def25..19d9102d28e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java @@ -835,7 +835,7 @@ public MessagesController(int num) { showFiltersTooltip = mainPreferences.getBoolean("showFiltersTooltip", false); autoarchiveAvailable = mainPreferences.getBoolean("autoarchiveAvailable", false); groupCallVideoMaxParticipants = mainPreferences.getInt("groipCallVideoMaxParticipants", 30); - chatReadMarkSizeThreshold = mainPreferences.getInt("chatReadMarkSizeThreshold", 50); + chatReadMarkSizeThreshold = mainPreferences.getInt("chatReadMarkSizeThreshold", 100); chatReadMarkExpirePeriod = mainPreferences.getInt("chatReadMarkExpirePeriod", 7 * 86400); ringtoneDurationMax = mainPreferences.getInt("ringtoneDurationMax", 5); ringtoneSizeMax = mainPreferences.getInt("ringtoneSizeMax", 1024_00); @@ -10696,19 +10696,22 @@ public void getDifference(int pts, int date, int qts, boolean slice) { arr.add(obj); } - AndroidUtilities.runOnUIThread(() -> { - for (int a = 0; a < messages.size(); a++) { - long key = messages.keyAt(a); - ArrayList value = messages.valueAt(a); - updateInterfaceWithMessages(key, value, false); - } - getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); - }); getMessagesStorage().getStorageQueue().postRunnable(() -> { if (!pushMessages.isEmpty()) { AndroidUtilities.runOnUIThread(() -> getNotificationsController().processNewMessages(pushMessages, !(res instanceof TLRPC.TL_updates_differenceSlice), false, null)); } getMessagesStorage().putMessages(res.new_messages, true, false, false, getDownloadController().getAutodownloadMask(), false); + + for (int a = 0; a < messages.size(); a++) { + long dialogId = messages.keyAt(a); + ArrayList arr = messages.valueAt(a); + getMediaDataController().loadReplyMessagesForMessages(arr, dialogId, false, () -> { + AndroidUtilities.runOnUIThread(() -> { + updateInterfaceWithMessages(dialogId, arr, false); + getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); + }); + }); + } }); getSecretChatHelper().processPendingEncMessages(); @@ -13086,6 +13089,11 @@ public boolean processUpdateArray(ArrayList updates, ArrayList(); } updatesOnMainThread.add(baseUpdate); + } else if (baseUpdate instanceof TLRPC.TL_updateBotMenuButton) { + if (updatesOnMainThread == null) { + updatesOnMainThread = new ArrayList<>(); + } + updatesOnMainThread.add(baseUpdate); } else if (baseUpdate instanceof TLRPC.TL_updateReadChannelDiscussionInbox) { if (updatesOnMainThread == null) { updatesOnMainThread = new ArrayList<>(); @@ -13721,6 +13729,9 @@ public boolean processUpdateArray(ArrayList updates, ArrayList userRingtones = new ArrayList<>(); private boolean loaded; - public final static HashSet ringtoneSupportedMimeType = new HashSet<>(Arrays.asList("audio/mpeg", "audio/ogg", "audio/m4a")); + public final static HashSet ringtoneSupportedMimeType = new HashSet<>(Arrays.asList("audio/mpeg3", "audio/mpeg", "audio/ogg", "audio/m4a")); public RingtoneDataStore(int currentAccount) { this.currentAccount = currentAccount; @@ -49,7 +49,9 @@ public RingtoneDataStore(int currentAccount) { } catch (Exception e) { FileLog.e(e); } - loadUserRingtones(); + AndroidUtilities.runOnUIThread(() -> { + loadUserRingtones(); + }); } public void loadUserRingtones() { @@ -251,7 +253,7 @@ public void checkRingtoneSoundsLoaded() { File file = FileLoader.getPathToAttach(document); if (file == null || !file.exists()) { AndroidUtilities.runOnUIThread(() -> { - FileLoader.getInstance(currentAccount).loadFile(document, null, 0, 0); + FileLoader.getInstance(currentAccount).loadFile(document, document, 0, 0); }); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/video/MediaCodecVideoConvertor.java b/TMessagesProj/src/main/java/org/telegram/messenger/video/MediaCodecVideoConvertor.java index f7a34449ca5..7037117577a 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/video/MediaCodecVideoConvertor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/video/MediaCodecVideoConvertor.java @@ -50,10 +50,11 @@ public boolean convertVideo(String videoPath, File cacheFile, ArrayList mediaEntities, boolean isPhoto, MediaController.CropState cropState, + boolean isRound, MediaController.VideoConvertorListener callback) { this.callback = callback; return convertVideoInternal(videoPath, cacheFile, rotationValue, isSecret, originalWidth, originalHeight, - resultWidth, resultHeight, framerate, bitrate, originalBitrate, startTime, endTime, avatarStartTime, duration, needCompress, false, savedFilterState, paintPath, mediaEntities, isPhoto, cropState); + resultWidth, resultHeight, framerate, bitrate, originalBitrate, startTime, endTime, avatarStartTime, duration, needCompress, false, savedFilterState, paintPath, mediaEntities, isPhoto, cropState, isRound); } public long getLastFrameTimestamp() { @@ -73,7 +74,8 @@ private boolean convertVideoInternal(String videoPath, File cacheFile, String paintPath, ArrayList mediaEntities, boolean isPhoto, - MediaController.CropState cropState) { + MediaController.CropState cropState, + boolean isRound) { long time = System.currentTimeMillis(); boolean error = false; @@ -407,7 +409,9 @@ private boolean convertVideoInternal(String videoPath, File cacheFile, decoder = MediaCodec.createDecoderByType(videoFormat.getString(MediaFormat.KEY_MIME)); outputSurface = new OutputSurface(savedFilterState, null, paintPath, mediaEntities, cropState, resultWidth, resultHeight, originalWidth, originalHeight, rotationValue, framerate, false); - outputSurface.changeFragmentShader(createFragmentShader(originalWidth, originalHeight, resultWidth, resultHeight, true), createFragmentShader(originalWidth, originalHeight, resultWidth, resultHeight, false)); + if (!isRound && Math.max(resultHeight, resultHeight) / (float) Math.max(originalHeight, originalWidth) < 0.9f) { + outputSurface.changeFragmentShader(createFragmentShader(originalWidth, originalHeight, resultWidth, resultHeight, true), createFragmentShader(originalWidth, originalHeight, resultWidth, resultHeight, false)); + } decoder.configure(videoFormat, outputSurface.getSurface(), null, 0); decoder.start(); @@ -800,7 +804,7 @@ private boolean convertVideoInternal(String videoPath, File cacheFile, originalWidth, originalHeight, resultWidth, resultHeight, framerate, bitrate, originalBitrate, startTime, endTime, avatarStartTime, duration, needCompress, true, savedFilterState, paintPath, mediaEntities, - isPhoto, cropState); + isPhoto, cropState, isRound); } long timeLeft = System.currentTimeMillis() - time; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenu.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenu.java index 2836704689a..7290a988a04 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenu.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenu.java @@ -14,10 +14,7 @@ import android.view.ViewGroup; import android.widget.LinearLayout; -import com.google.android.exoplayer2.util.Log; - import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.R; import org.telegram.ui.Adapters.FiltersView; import org.telegram.ui.Components.RLottieDrawable; @@ -94,15 +91,7 @@ public ActionBarMenuItem addItem(int id, int icon, CharSequence text, int backgr } public ActionBarMenuItem addItem(int id, int icon, CharSequence text, int backgroundColor, Drawable drawable, int width, CharSequence title, Theme.ResourcesProvider resourcesProvider) { - ActionBarMenuItem menuItem = new ActionBarMenuItem(getContext(), this, backgroundColor, isActionMode ? parentActionBar.itemsActionModeColor : parentActionBar.itemsColor, text != null, resourcesProvider) { - @Override - public void setVisibility(int visibility) { - super.setVisibility(visibility); - if (icon == R.drawable.ic_ab_other && (id == 0 || id == 14)) { - Log.d("kek", id + " " + (View.VISIBLE == visibility)); - } - } - }; + ActionBarMenuItem menuItem = new ActionBarMenuItem(getContext(), this, backgroundColor, isActionMode ? parentActionBar.itemsActionModeColor : parentActionBar.itemsColor, text != null, resourcesProvider); menuItem.setTag(id); if (text != null) { menuItem.textView.setText(text); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java index af93f999e0a..5288da6bf65 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java @@ -1914,9 +1914,9 @@ private int getThemedColor(String key) { } } - public View addColoredGap() { + public ActionBarPopupWindow.GapView addColoredGap() { createPopupLayout(); - View gap = new ActionBarPopupWindow.GapView(getContext(), Theme.key_graySection); + ActionBarPopupWindow.GapView gap = new ActionBarPopupWindow.GapView(getContext(), Theme.key_graySection); gap.setTag(R.id.fit_width_tag, 1); popupLayout.addView(gap, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 8)); return gap; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarPopupWindow.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarPopupWindow.java index 743185e4ae1..ead3ce1526f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarPopupWindow.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarPopupWindow.java @@ -551,6 +551,10 @@ public int getVisibleHeight() { public void setTopView(View topView) { this.topView = topView; } + + public void setSwipeBackForegroundColor(int color) { + getSwipeBack().setForegroundColor(color); + } } public ActionBarPopupWindow() { @@ -866,6 +870,7 @@ public static class GapView extends FrameLayout { Paint paint = new Paint(); String colorKey; + int color = 0; public GapView(Context context, String colorKey) { super(context); this.colorKey = colorKey; @@ -873,8 +878,16 @@ public GapView(Context context, String colorKey) { @Override protected void onDraw(Canvas canvas) { - paint.setColor(Theme.getColor(colorKey)); + if (color == 0) { + paint.setColor(Theme.getColor(colorKey)); + } else { + paint.setColor(color); + } canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint); } + + public void setColor(int color) { + this.color = color; + } } } \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java index 159923c2311..33e783443ef 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java @@ -17,7 +17,6 @@ import android.content.Context; import android.content.DialogInterface; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Insets; import android.graphics.Paint; import android.graphics.PorterDuff; @@ -920,9 +919,6 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Window window = getWindow(); - /*if (Build.VERSION.SDK_INT >= 30) { - window.setDecorFitsSystemWindows(true); - }*/ window.setWindowAnimations(R.style.DialogNoAnimation); setContentView(container, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CalendarActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CalendarActivity.java index dc9c3f422d9..5873ec75d4e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CalendarActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CalendarActivity.java @@ -312,7 +312,9 @@ public void onDraw(Canvas canvas) { @Override public void run(boolean forAll) { finishFragment(); - + if (parentLayout == null) { + return; + } if (parentLayout.fragmentsStack.size() >= 2) { BaseFragment fragment = parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 2); if (fragment instanceof ChatActivity) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CameraScanActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CameraScanActivity.java index 988847827b0..d07ec4d30cb 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CameraScanActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CameraScanActivity.java @@ -31,7 +31,6 @@ import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; -import android.text.StaticLayout; import android.text.TextUtils; import android.text.style.ClickableSpan; import android.util.SparseArray; @@ -39,9 +38,7 @@ import android.view.Gravity; import android.view.MotionEvent; import android.view.View; -import android.view.ViewConfiguration; import android.view.ViewGroup; -import android.view.Window; import android.view.WindowManager; import android.widget.ImageView; import android.widget.TextView; @@ -207,6 +204,7 @@ public void dismiss() { actionBarLayout[0] = null; } }; + bottomSheet.setUseLightStatusBar(false); AndroidUtilities.setLightNavigationBar(bottomSheet.getWindow(), false); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { bottomSheet.getWindow().setNavigationBarColor(0xff000000); @@ -436,6 +434,7 @@ private RectF aroundPoint(int x, int y, int r) { } else { actionBar.setBackgroundDrawable(null); actionBar.setAddToContainer(false); + actionBar.setTitleColor(0xffffffff); actionBar.setItemsColor(0xffffffff, false); actionBar.setItemsBackgroundColor(0x22ffffff, false); viewGroup.setBackgroundColor(Theme.getColor(Theme.key_wallet_blackBackground)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java index 74b1e0e685c..5f89d038c19 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java @@ -1056,8 +1056,7 @@ public void buildLayout() { checkMessage = false; messageString = formatArchivedDialogNames(); } else if (message.messageOwner instanceof TLRPC.TL_messageService) { - if (ChatObject.isChannelAndNotMegaGroup(chat) && (message.messageOwner.action instanceof TLRPC.TL_messageActionHistoryClear || - message.messageOwner.action instanceof TLRPC.TL_messageActionChannelMigrateFrom)) { + if (ChatObject.isChannelAndNotMegaGroup(chat) && (message.messageOwner.action instanceof TLRPC.TL_messageActionChannelMigrateFrom)) { messageString = ""; showChecks = false; } else { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java index 01fdb615167..2db0ab68fe8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java @@ -2763,9 +2763,18 @@ public void toggleSound() { @Override public void muteFor(int timeInSeconds) { - getNotificationsController().muteUntil(dialog_id, timeInSeconds); - if (BulletinFactory.canShowBulletin(ChatActivity.this)) { - BulletinFactory.createMuteBulletin(ChatActivity.this, NotificationsController.SETTING_MUTE_CUSTOM, timeInSeconds, getResourceProvider()).show(); + if (timeInSeconds == 0) { + if (getMessagesController().isDialogMuted(dialog_id)) { + ChatActivity.this.toggleMute(true); + } + if (BulletinFactory.canShowBulletin(ChatActivity.this)) { + BulletinFactory.createMuteBulletin(ChatActivity.this, NotificationsController.SETTING_MUTE_UNMUTE, timeInSeconds, getResourceProvider()).show(); + } + } else { + getNotificationsController().muteUntil(dialog_id, timeInSeconds); + if (BulletinFactory.canShowBulletin(ChatActivity.this)) { + BulletinFactory.createMuteBulletin(ChatActivity.this, NotificationsController.SETTING_MUTE_CUSTOM, timeInSeconds, getResourceProvider()).show(); + } } } @@ -4081,7 +4090,7 @@ protected Drawable getNewDrawable() { greetingsViewContainer.setBackground(Theme.createServiceDrawable(AndroidUtilities.dp(10), greetingsViewContainer, contentView, getThemedPaint(Theme.key_paint_chatActionBackground))); emptyViewContainer.addView(greetingsViewContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 68, 0, 68, 0)); } else if (currentEncryptedChat == null) { - if (!isThreadChat() && chatMode == 0 && (currentUser != null && currentUser.self || currentChat != null && currentChat.creator)) { + if (!isThreadChat() && chatMode == 0 && ((currentUser != null && currentUser.self) || (currentChat != null && currentChat.creator && !ChatObject.isChannelAndNotMegaGroup(currentChat)))) { bigEmptyView = new ChatBigEmptyView(context, contentView, currentChat != null ? ChatBigEmptyView.EMPTY_VIEW_TYPE_GROUP : ChatBigEmptyView.EMPTY_VIEW_TYPE_SAVED, themeDelegate); emptyViewContainer.addView(bigEmptyView, new FrameLayout.LayoutParams(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); if (currentChat != null) { @@ -14517,7 +14526,7 @@ public void didReceivedNotification(int id, int account, final Object... args) { endReached[0] = cacheEndReached[0] = true; forwardEndReached[0] = forwardEndReached[0] = true; } - if (ChatObject.isChannel(currentChat) && !getMessagesController().dialogs_dict.containsKey(dialog_id) && load_type == 2 && loadIndex == 0) { + if (!isThreadChat() && ChatObject.isChannel(currentChat) && !getMessagesController().dialogs_dict.containsKey(dialog_id) && load_type == 2 && loadIndex == 0) { forwardEndReached[0] = false; hideForwardEndReached = true; } @@ -19652,7 +19661,7 @@ public void onAnimationCancel(Animator animation) { } if (pinnedText != null) { if (pinnedText instanceof Spannable) { - MediaDataController.addTextStyleRuns(pinnedMessageObject, (Spannable) pinnedText); + MediaDataController.addTextStyleRuns(pinnedMessageObject, (Spannable) pinnedText, TextStyleSpan.FLAG_STYLE_SPOILER | TextStyleSpan.FLAG_STYLE_STRIKE); } messageTextView.setText(pinnedText); } @@ -21668,7 +21677,7 @@ public void setAutoDeleteHistory(int time, int action) { } else { isReactionsAvailable = !isSecretChat() && !isInScheduleMode() && message.isReactionsAvailable() && (chatInfo != null && !chatInfo.available_reactions.isEmpty() || (chatInfo == null && !ChatObject.isChannel(currentChat)) || currentUser != null) && !availableReacts.isEmpty(); } - boolean showMessageSeen = !isReactionsViewAvailable && !isInScheduleMode() && currentChat != null && message.isOutOwner() && message.isSent() && !message.isEditing() && !message.isSending() && !message.isSendError() && !message.isContentUnread() && !message.isUnread() && (ConnectionsManager.getInstance(currentAccount).getCurrentTime() - message.messageOwner.date < getMessagesController().chatReadMarkExpirePeriod) && (ChatObject.isMegagroup(currentChat) || !ChatObject.isChannel(currentChat)) && chatInfo != null && chatInfo.participants_count < getMessagesController().chatReadMarkSizeThreshold && !(message.messageOwner.action instanceof TLRPC.TL_messageActionChatJoinedByRequest) && (v instanceof ChatMessageCell); + boolean showMessageSeen = !isReactionsViewAvailable && !isInScheduleMode() && currentChat != null && message.isOutOwner() && message.isSent() && !message.isEditing() && !message.isSending() && !message.isSendError() && !message.isContentUnread() && !message.isUnread() && (ConnectionsManager.getInstance(currentAccount).getCurrentTime() - message.messageOwner.date < getMessagesController().chatReadMarkExpirePeriod) && (ChatObject.isMegagroup(currentChat) || !ChatObject.isChannel(currentChat)) && chatInfo != null && chatInfo.participants_count <= getMessagesController().chatReadMarkSizeThreshold && !(message.messageOwner.action instanceof TLRPC.TL_messageActionChatJoinedByRequest) && (v instanceof ChatMessageCell); int flags = 0; if (isReactionsViewAvailable || showMessageSeen) { @@ -21988,7 +21997,7 @@ public void onClick(View view) { addGap = true; } - if (message.probablyRingtone()) { + if (message.probablyRingtone() && currentEncryptedChat == null) { ActionBarMenuSubItem cell = new ActionBarMenuSubItem(getParentActivity(), true, false, themeDelegate); cell.setMinimumWidth(AndroidUtilities.dp(200)); cell.setTextAndIcon(LocaleController.getString("SaveForNotifications", R.string.SaveForNotifications), R.drawable.msg_tone_add); @@ -22000,7 +22009,7 @@ public void onClick(View view) { @Override public void run() { - if (clicked || dialog_id == 0 || getUserConfig().clientUserId == dialog_id) { + if (clicked) { return; } clicked = true; @@ -23474,6 +23483,8 @@ public boolean onBackPressed() { } else if (chatActivityEnterView != null && chatActivityEnterView.isPopupShowing()) { chatActivityEnterView.hidePopup(true); return false; + } else if (chatActivityEnterView != null && chatActivityEnterView.hasBotWebView() && chatActivityEnterView.botCommandsMenuIsShowing() && chatActivityEnterView.onBotWebViewBackPressed()) { + return false; } else if (chatActivityEnterView != null && chatActivityEnterView.botCommandsMenuIsShowing()) { chatActivityEnterView.hideBotCommands(); return false; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChooseSpeedLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/ChooseSpeedLayout.java new file mode 100644 index 00000000000..70473d6a8eb --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChooseSpeedLayout.java @@ -0,0 +1,80 @@ +package org.telegram.ui; + +import android.content.Context; + +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.ActionBarMenuSubItem; +import org.telegram.ui.ActionBar.ActionBarPopupWindow; +import org.telegram.ui.Components.PopupSwipeBackLayout; + +public class ChooseSpeedLayout { + + ActionBarPopupWindow.ActionBarPopupWindowLayout speedSwipeBackLayout; + + ActionBarMenuSubItem[] speedItems = new ActionBarMenuSubItem[5]; + public ChooseSpeedLayout(Context context, PopupSwipeBackLayout swipeBackLayout, Callback callback) { + speedSwipeBackLayout = new ActionBarPopupWindow.ActionBarPopupWindowLayout(context, 0, null); + speedSwipeBackLayout.setFitItems(true); + + ActionBarMenuSubItem backItem = ActionBarMenuItem.addItem(speedSwipeBackLayout, R.drawable.msg_arrow_back, LocaleController.getString("Back", R.string.Back), false, null); + backItem.setOnClickListener(view -> { + swipeBackLayout.closeForeground(); + }); + backItem.setColors(0xfffafafa, 0xfffafafa); + + ActionBarMenuSubItem item = ActionBarMenuItem.addItem(speedSwipeBackLayout, R.drawable.msg_speed_0_2, LocaleController.getString("SpeedVerySlow", R.string.SpeedVerySlow), false, null); + item.setColors(0xfffafafa, 0xfffafafa); + item.setOnClickListener((view) -> { + callback.onSpeedSelected(0.25f); + }); + speedItems[0] = item; + + item = ActionBarMenuItem.addItem(speedSwipeBackLayout, R.drawable.msg_speed_0_5, LocaleController.getString("SpeedSlow", R.string.SpeedSlow), false, null); + item.setColors(0xfffafafa, 0xfffafafa); + item.setOnClickListener((view) -> { + callback.onSpeedSelected(0.5f); + }); + speedItems[1] = item; + + item = ActionBarMenuItem.addItem(speedSwipeBackLayout, R.drawable.msg_speed_1, LocaleController.getString("SpeedNormal", R.string.SpeedNormal), false, null); + item.setColors(0xfffafafa, 0xfffafafa); + item.setOnClickListener((view) -> { + callback.onSpeedSelected(1f); + }); + speedItems[2] = item; + + item = ActionBarMenuItem.addItem(speedSwipeBackLayout, R.drawable.msg_speed_1_5, LocaleController.getString("SpeedFast", R.string.SpeedFast), false, null); + item.setColors(0xfffafafa, 0xfffafafa); + item.setOnClickListener((view) -> { + callback.onSpeedSelected(1.5f); + }); + speedItems[3] = item; + + item = ActionBarMenuItem.addItem(speedSwipeBackLayout, R.drawable.msg_speed_2, LocaleController.getString("SpeedVeryFast", R.string.SpeedVeryFast), false, null); + item.setColors(0xfffafafa, 0xfffafafa); + item.setOnClickListener((view) -> { + callback.onSpeedSelected(2f); + }); + speedItems[4] = item; + } + + public void update(float currentVideoSpeed) { + for (int a = 0; a < speedItems.length; a++) { + if (a == 0 && Math.abs(currentVideoSpeed - 0.25f) < 0.001f || + a == 1 && Math.abs(currentVideoSpeed - 0.5f) < 0.001f || + a == 2 && Math.abs(currentVideoSpeed - 1.0f) < 0.001f || + a == 3 && Math.abs(currentVideoSpeed - 1.5f) < 0.001f || + a == 4 && Math.abs(currentVideoSpeed - 2.0f) < 0.001f) { + speedItems[a].setColors(0xff6BB6F9, 0xff6BB6F9); + } else { + speedItems[a].setColors(0xfffafafa, 0xfffafafa); + } + } + } + + public interface Callback { + void onSpeedSelected(float speed); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java index b38a4e75573..0beccebe242 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java @@ -1442,7 +1442,11 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (UserObject.isUserSelf(user)) { messageTextView.setText(AndroidUtilities.replaceTags(LocaleController.getString("DeleteAllMessagesSavedAlert", R.string.DeleteAllMessagesSavedAlert))); } else { - messageTextView.setText(AndroidUtilities.replaceTags(LocaleController.getString("DeleteAllMessagesAlert", R.string.DeleteAllMessagesAlert))); + if (chat != null && ChatObject.isChannelAndNotMegaGroup(chat)) { + messageTextView.setText(AndroidUtilities.replaceTags(LocaleController.getString("DeleteAllMessagesChannelAlert", R.string.DeleteAllMessagesChannelAlert))); + } else { + messageTextView.setText(AndroidUtilities.replaceTags(LocaleController.getString("DeleteAllMessagesAlert", R.string.DeleteAllMessagesAlert))); + } } } else { if (clear) { @@ -1512,7 +1516,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (clearingCache) { actionText = LocaleController.getString("ClearHistoryCache", R.string.ClearHistoryCache); } else { - actionText = LocaleController.getString("ClearHistory", R.string.ClearHistory); + actionText = LocaleController.getString("ClearForMe", R.string.ClearForMe); } } else { if (admin) { @@ -1616,8 +1620,12 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (user != null) { messageTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("AreYouSureClearHistoryWithUser", R.string.AreYouSureClearHistoryWithUser, UserObject.getUserName(user)))); } else { - if (chat != null && canDeleteHistory) { - messageTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("AreYouSureClearHistoryWithChat", R.string.AreYouSureClearHistoryWithChat, chat.title))); + if (canDeleteHistory) { + if (ChatObject.isChannelAndNotMegaGroup(chat)) { + messageTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("AreYouSureClearHistoryWithChannel", R.string.AreYouSureClearHistoryWithChannel, chat.title))); + } else { + messageTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("AreYouSureClearHistoryWithChat", R.string.AreYouSureClearHistoryWithChat, chat.title))); + } } else if (chat.megagroup) { messageTextView.setText(LocaleController.getString("AreYouSureClearHistoryGroup", R.string.AreYouSureClearHistoryGroup)); } else { @@ -1633,7 +1641,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (chat != null && canDeleteHistory && !TextUtils.isEmpty(chat.username)) { deleteForAll[0] = true; } - if ((user != null && user.id != selfUserId) || (chat != null && canDeleteHistory && TextUtils.isEmpty(chat.username))) { + if ((user != null && user.id != selfUserId) || (chat != null && canDeleteHistory && TextUtils.isEmpty(chat.username) && !ChatObject.isChannelAndNotMegaGroup(chat))) { cell[0] = new CheckBoxCell(context, 1, resourcesProvider); cell[0].setBackgroundDrawable(Theme.getSelectorDrawable(false)); if (chat != null) { @@ -1653,7 +1661,11 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { }); } - builder.setPositiveButton(LocaleController.getString("Delete", R.string.Delete), (dialogInterface, i) -> { + String deleteText = LocaleController.getString("Delete", R.string.Delete); + if (chat != null && canDeleteHistory && !TextUtils.isEmpty(chat.username) && !ChatObject.isChannelAndNotMegaGroup(chat)) { + deleteText = LocaleController.getString("ClearForAll", R.string.ClearForAll); + } + builder.setPositiveButton(deleteText, (dialogInterface, i) -> { onProcessRunnable.run(deleteForAll[0]); }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); @@ -2803,7 +2815,6 @@ protected CharSequence getContentDescription(int index) { numberPicker.setMaxValue(values.length - 1); numberPicker.setTextColor(datePickerColors.textColor); numberPicker.setValue(0); - numberPicker.setWrapSelectorWheel(false); numberPicker.setFormatter(index -> { if (values[index] == 0) { return LocaleController.getString("AutoDeleteNever", R.string.AutoDeleteNever); @@ -3048,18 +3059,59 @@ public static BottomSheet.Builder createMuteForPickerDialog(Context context, fin BottomSheet.Builder builder = new BottomSheet.Builder(context, false); builder.setApplyBottomPadding(false); - final NumberPicker dayPicker = new NumberPicker(context); - dayPicker.setTextColor(datePickerColors.textColor); - dayPicker.setTextOffset(AndroidUtilities.dp(10)); - dayPicker.setItemCount(5); - final NumberPicker hourPicker = new NumberPicker(context) { + int[] values = new int[]{ + 0, + 60 * 24, + 2 * 60 * 24, + 3 * 60 * 24, + 4 * 60 * 24, + 5 * 60 * 24, + 6 * 60 * 24, + 7 * 60 * 24, + 2 * 7 * 60 * 24, + 3 * 7 * 60 * 24, + 31 * 60 * 24, + 2 * 31 * 60 * 24, + 3 * 31 * 60 * 24, + 4 * 31 * 60 * 24, + 5 * 31 * 60 * 24, + 6 * 31 * 60 * 24, + 365 * 60 * 24 + }; + + final NumberPicker numberPicker = new NumberPicker(context) { @Override - protected CharSequence getContentDescription(int value) { - return LocaleController.formatPluralString("Hours", value); + protected CharSequence getContentDescription(int index) { + if (values[index] == 0) { + return LocaleController.getString("MuteNever", R.string.MuteNever); + } else if (values[index] < 7 * 60 * 24) { + return LocaleController.formatPluralString("Days", values[index] / (60 * 24)); + } else if (values[index] < 31 * 60 * 24) { + return LocaleController.formatPluralString("Weeks", values[index] / (60 * 24)); + } else if (values[index] < 365 * 60 * 24) { + return LocaleController.formatPluralString("Months", values[index] / (7 * 60 * 24)); + } else { + return LocaleController.formatPluralString("Years", values[index] * 5 / 31 * 60 * 24); + } } }; - hourPicker.setItemCount(5); - hourPicker.setTextColor(datePickerColors.textColor); + numberPicker.setMinValue(0); + numberPicker.setMaxValue(values.length - 1); + numberPicker.setTextColor(datePickerColors.textColor); + numberPicker.setValue(0); + numberPicker.setFormatter(index -> { + if (values[index] == 0) { + return LocaleController.getString("MuteNever", R.string.MuteNever); + } else if (values[index] < 7 * 60 * 24) { + return LocaleController.formatPluralString("Days", values[index] / (60 * 24)); + } else if (values[index] < 31 * 60 * 24) { + return LocaleController.formatPluralString("Weeks", values[index] / (7 * 60 * 24)); + } else if (values[index] < 365 * 60 * 24) { + return LocaleController.formatPluralString("Months", values[index] / (31 * 60 * 24)); + } else { + return LocaleController.formatPluralString("Years", values[index] / (365 * 60 * 24)); + } + }); LinearLayout container = new LinearLayout(context) { @@ -3074,10 +3126,8 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { } else { count = 5; } - dayPicker.setItemCount(count); - hourPicker.setItemCount(count); - dayPicker.getLayoutParams().height = AndroidUtilities.dp(NumberPicker.DEFAULT_SIZE_PER_COUNT) * count; - hourPicker.getLayoutParams().height = AndroidUtilities.dp(NumberPicker.DEFAULT_SIZE_PER_COUNT) * count; + numberPicker.setItemCount(count); + numberPicker.getLayoutParams().height = AndroidUtilities.dp(NumberPicker.DEFAULT_SIZE_PER_COUNT) * count; ignoreLayout = false; super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @@ -3115,26 +3165,15 @@ public CharSequence getAccessibilityClassName() { } }; - linearLayout.addView(dayPicker, LayoutHelper.createLinear(0, 54 * 5, 0.5f)); - dayPicker.setMinValue(0); - dayPicker.setMaxValue(365); - dayPicker.setWrapSelectorWheel(false); - dayPicker.setFormatter(value -> LocaleController.formatPluralString("Days", value)); + linearLayout.addView(numberPicker, LayoutHelper.createLinear(0, 54 * 5, 1f)); final NumberPicker.OnValueChangeListener onValueChangeListener = (picker, oldVal, newVal) -> { try { container.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } catch (Exception ignore) { } - checkMuteForButton(dayPicker, hourPicker, buttonTextView, true); }; - dayPicker.setOnValueChangedListener(onValueChangeListener); - - hourPicker.setMinValue(0); - hourPicker.setMaxValue(23); - linearLayout.addView(hourPicker, LayoutHelper.createLinear(0, 54 * 5, 0.5f)); - hourPicker.setFormatter(value -> LocaleController.formatPluralString("Hours", value)); - hourPicker.setOnValueChangedListener(onValueChangeListener); + numberPicker.setOnValueChangedListener(onValueChangeListener); buttonTextView.setPadding(AndroidUtilities.dp(34), 0, AndroidUtilities.dp(34), 0); buttonTextView.setGravity(Gravity.CENTER); @@ -3142,10 +3181,10 @@ public CharSequence getAccessibilityClassName() { buttonTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); buttonTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); buttonTextView.setBackgroundDrawable(Theme.createSimpleSelectorRoundRectDrawable(AndroidUtilities.dp(4), datePickerColors.buttonBackgroundColor, datePickerColors.buttonBackgroundPressedColor)); - buttonTextView.setText(LocaleController.getString("SetTimeLimit", R.string.SetTimeLimit)); + buttonTextView.setText(LocaleController.getString("AutoDeleteConfirm", R.string.AutoDeleteConfirm)); container.addView(buttonTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.BOTTOM, 16, 15, 16, 16)); buttonTextView.setOnClickListener(v -> { - int time = hourPicker.getValue() * 60 + dayPicker.getValue() * 60 * 24; + int time = values[numberPicker.getValue()] * 60; datePickerDelegate.didSelectDate(true, time); builder.getDismissRunnable().run(); }); @@ -3153,7 +3192,7 @@ public CharSequence getAccessibilityClassName() { builder.setCustomView(container); BottomSheet bottomSheet = builder.show(); bottomSheet.setBackgroundColor(datePickerColors.backgroundColor); - checkMuteForButton(dayPicker, hourPicker, buttonTextView, false); + return builder; } @@ -3191,46 +3230,6 @@ private static void checkMuteForButton(NumberPicker dayPicker, NumberPicker hour } } - private static void checkAutoDeleteButton(NumberPicker dayPicker, NumberPicker hourPicker, NumberPicker minutePicker, TextView buttonTextView, boolean animated) { - StringBuilder stringBuilder = new StringBuilder(); - if (dayPicker.getValue() != 0) { - stringBuilder.append(dayPicker.getValue()).append(LocaleController.getString("SecretChatTimerDays", R.string.SecretChatTimerDays)); - } - if (hourPicker.getValue() != 0) { - if (stringBuilder.length() > 0) { - stringBuilder.append(" "); - } - stringBuilder.append(hourPicker.getValue()).append(LocaleController.getString("SecretChatTimerHours", R.string.SecretChatTimerHours)); - } - if (minutePicker.getValue() != 0) { - if (stringBuilder.length() > 0) { - stringBuilder.append(" "); - } - stringBuilder.append(minutePicker.getValue() * 5).append(LocaleController.getString("SecretChatTimerMinutes", R.string.SecretChatTimerMinutes)); - } - if (stringBuilder.length() == 0) { - buttonTextView.setText(LocaleController.formatString("ChooseTimeForAutoDelete", R.string.ChooseTimeForAutoDelete)); - if (buttonTextView.isEnabled()) { - buttonTextView.setEnabled(false); - if (animated) { - buttonTextView.animate().alpha(0.5f); - } else { - buttonTextView.setAlpha(0.5f); - } - } - } else { - buttonTextView.setText(LocaleController.formatString("AutoDeleteAfter", R.string.AutoDeleteAfter, stringBuilder.toString())); - if (!buttonTextView.isEnabled()) { - buttonTextView.setEnabled(true); - if (animated) { - buttonTextView.animate().alpha(1f); - } else { - buttonTextView.setAlpha(1f); - } - } - } - } - private static void checkCalendarDate(long minDate, NumberPicker dayPicker, NumberPicker monthPicker, NumberPicker yearPicker) { int day = dayPicker.getValue(); int month = monthPicker.getValue(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BotCommandsMenuView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BotCommandsMenuView.java index acf1394ed76..1ca563deb96 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BotCommandsMenuView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BotCommandsMenuView.java @@ -206,6 +206,9 @@ protected void onTranslationChanged(float translationX) { } public void setMenuText(String menuText) { + if (menuText == null) { + menuText = LocaleController.getString(R.string.BotsMenuTitle); + } this.menuText = menuText; menuTextLayout = null; requestLayout(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BotWebViewContainer.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BotWebViewContainer.java index ce812854f8f..9d835a017de 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BotWebViewContainer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BotWebViewContainer.java @@ -20,8 +20,10 @@ import android.net.Uri; import android.os.Build; import android.text.TextUtils; +import android.util.TypedValue; import android.view.Gravity; import android.view.View; +import android.view.inputmethod.InputMethodManager; import android.webkit.GeolocationPermissions; import android.webkit.JavascriptInterface; import android.webkit.PermissionRequest; @@ -31,14 +33,18 @@ import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.FrameLayout; +import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; +import androidx.core.graphics.ColorUtils; import androidx.core.util.Consumer; import org.json.JSONException; import org.json.JSONObject; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.ImageLocation; import org.telegram.messenger.ImageReceiver; @@ -57,6 +63,7 @@ import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.voip.CellFlickerDrawable; +import java.io.File; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Arrays; @@ -64,6 +71,7 @@ import java.util.Objects; public class BotWebViewContainer extends FrameLayout implements NotificationCenter.NotificationCenterDelegate { + private final static boolean WEB_VIEW_CAN_GO_BACK = false; private final static String DURGER_KING_USERNAME = "DurgerKingBot"; private final static int REQUEST_CODE_WEB_VIEW_FILE = 3000, REQUEST_CODE_WEB_PERMISSION = 4000; @@ -75,6 +83,9 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent private WebViewScrollListener webViewScrollListener; private Theme.ResourcesProvider resourcesProvider; + private TextView webViewNotAvailableText; + private boolean webViewNotAvailable; + private CellFlickerDrawable flickerDrawable = new CellFlickerDrawable(); private BackupImageView flickerView; private boolean isFlickeringCenter; @@ -83,13 +94,12 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent private ValueCallback mFilePathCallback; - private int lastButtonColor = Theme.getColor(Theme.key_featuredStickers_addButton); - private int lastButtonTextColor = Theme.getColor(Theme.key_featuredStickers_buttonText); + private int lastButtonColor = getColor(Theme.key_featuredStickers_addButton); + private int lastButtonTextColor = getColor(Theme.key_featuredStickers_buttonText); private String lastButtonText = ""; private String buttonData; private boolean isPageLoaded; - private int viewPortOffset; private boolean lastExpanded; private boolean hasUserPermissions; @@ -98,7 +108,6 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent private Activity parentActivity; - @SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface"}) public BotWebViewContainer(@NonNull Context context, Theme.ResourcesProvider resourcesProvider, int backgroundColor) { super(context); this.resourcesProvider = resourcesProvider; @@ -139,11 +148,47 @@ protected void onDraw(Canvas canvas) { } } }; - flickerView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_windowBackgroundGray), PorterDuff.Mode.SRC_IN)); + flickerView.setColorFilter(new PorterDuffColorFilter(getColor(Theme.key_dialogSearchHint), PorterDuff.Mode.SRC_IN)); flickerView.getImageReceiver().setAspectFit(true); addView(flickerView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP)); - webView = new WebView(context) { + webViewNotAvailableText = new TextView(context); + webViewNotAvailableText.setText(LocaleController.getString(R.string.BotWebViewNotAvailablePlaceholder)); + webViewNotAvailableText.setTextColor(getColor(Theme.key_windowBackgroundWhiteGrayText)); + webViewNotAvailableText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + webViewNotAvailableText.setGravity(Gravity.CENTER); + webViewNotAvailableText.setVisibility(GONE); + int padding = AndroidUtilities.dp(16); + webViewNotAvailableText.setPadding(padding, padding, padding, padding); + addView(webViewNotAvailableText, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); + + setFocusable(false); + } + + private void checkCreateWebView() { + if (webView == null && !webViewNotAvailable) { + try { + setupWebView(); + } catch (Throwable t) { + FileLog.e(t); + + flickerView.setVisibility(GONE); + webViewNotAvailable = true; + webViewNotAvailableText.setVisibility(VISIBLE); + if (webView != null) { + removeView(webView); + } + } + } + } + + @SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface"}) + private void setupWebView() { + if (webView != null) { + webView.destroy(); + removeView(webView); + } + webView = new WebView(getContext()) { private int prevScrollX, prevScrollY; @Override @@ -172,7 +217,7 @@ public void setScrollY(int value) { @Override public boolean onCheckIsTextEditor() { - return true; + return BotWebViewContainer.this.isFocusable(); } @Override @@ -180,12 +225,20 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY)); } }; - webView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + webView.setBackgroundColor(getColor(Theme.key_windowBackgroundWhite)); WebSettings settings = webView.getSettings(); settings.setJavaScriptEnabled(true); settings.setGeolocationEnabled(true); + settings.setDomStorageEnabled(true); + settings.setDatabaseEnabled(true); + + File databaseStorage = new File(ApplicationLoader.getFilesDirFixed(), "webview_database"); + if (databaseStorage.exists() && databaseStorage.isDirectory() || databaseStorage.mkdirs()) { + settings.setDatabasePath(databaseStorage.getAbsolutePath()); + } GeolocationPermissions.getInstance().clearAll(); + webView.setVerticalScrollBarEnabled(false); webView.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { @@ -197,19 +250,31 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) { override = true; if (WHITELISTED_SCHEMES.contains(uriNew.getScheme())) { - new AlertDialog.Builder(context, resourcesProvider) - .setTitle(LocaleController.getString(R.string.OpenUrlTitle)) - .setMessage(LocaleController.formatString(R.string.OpenUrlAlert2, uriNew.toString())) - .setPositiveButton(LocaleController.getString(R.string.Open), (dialog, which) -> { - boolean[] forceBrowser = {false}; - boolean internal = Browser.isInternalUri(uriNew, forceBrowser); - Browser.openUrl(getContext(), uriNew, true, false); - if (internal && delegate != null) { - delegate.onCloseRequested(); - } - }) - .setNegativeButton(LocaleController.getString(R.string.Cancel), null) - .show(); + boolean[] forceBrowser = {false}; + boolean internal = Browser.isInternalUri(uriNew, forceBrowser); + + if (internal) { + if (delegate != null) { + setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS); + BotWebViewContainer.this.setFocusable(false); + webView.setFocusable(false); + webView.setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS); + webView.clearFocus(); + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); + + delegate.onCloseRequested(()-> Browser.openUrl(getContext(), uriNew, true, false)); + } else { + Browser.openUrl(getContext(), uriNew, true, false); + } + } else { + new AlertDialog.Builder(getContext(), resourcesProvider) + .setTitle(LocaleController.getString(R.string.OpenUrlTitle)) + .setMessage(LocaleController.formatString(R.string.OpenUrlAlert2, uriNew.toString())) + .setPositiveButton(LocaleController.getString(R.string.Open), (dialog, which) -> Browser.openUrl(getContext(), uriNew, true, false)) + .setNegativeButton(LocaleController.getString(R.string.Cancel), null) + .show(); + } } } else { override = false; @@ -375,6 +440,36 @@ public void onPermissionRequestCanceled(PermissionRequest request) { } } + public static int getMainButtonRippleColor(int buttonColor) { + return ColorUtils.calculateLuminance(buttonColor) >= 0.3f ? 0x12000000 : 0x16FFFFFF; + } + + public static Drawable getMainButtonRippleDrawable(int buttonColor) { + return Theme.createSelectorWithBackgroundDrawable(buttonColor, getMainButtonRippleColor(buttonColor)); + } + + public void updateFlickerBackgroundColor(int backgroundColor) { + flickerDrawable.setColors(backgroundColor, 0x99, 0xCC); + } + + /** + * @return If this press was consumed + */ + public boolean onBackPressed() { + if (!WEB_VIEW_CAN_GO_BACK) { + return false; + } + + if (webView == null) { + return false; + } + if (webView.canGoBack()) { + webView.goBack(); + return true; + } + return false; + } + private void setPageLoaded(String url) { if (isPageLoaded) { return; @@ -393,6 +488,8 @@ public void onAnimationEnd(Animator animation) { set.start(); mUrl = url; isPageLoaded = true; + BotWebViewContainer.this.setFocusable(true); + delegate.onWebAppReady(); } public boolean hasUserPermissions() { @@ -419,6 +516,10 @@ private void runWithPermissions(String[] permissions, Consumer callback } } + public boolean isPageLoaded() { + return isPageLoaded; + } + public void setParentActivity(Activity parentActivity) { this.parentActivity = parentActivity; } @@ -467,11 +568,6 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } } - public void setViewPortOffset(int viewPortOffset) { - this.viewPortOffset = viewPortOffset; - invalidateViewPortHeight(true); - } - @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); @@ -487,6 +583,7 @@ public void invalidateViewPortHeight(boolean isStable) { } public void invalidateViewPortHeight(boolean isStable, boolean force) { + invalidate(); if (!isPageLoaded && !force) { return; } @@ -498,7 +595,7 @@ public void invalidateViewPortHeight(boolean isStable, boolean force) { lastExpanded = swipeContainer.getSwipeOffsetY() == -swipeContainer.getOffsetY() + swipeContainer.getTopActionBarOffsetY(); } - int viewPortHeight = (int) (swipeContainer.getMeasuredHeight() - swipeContainer.getOffsetY() - swipeContainer.getSwipeOffsetY() + swipeContainer.getTopActionBarOffsetY() + viewPortOffset); + int viewPortHeight = (int) (swipeContainer.getMeasuredHeight() - swipeContainer.getOffsetY() - swipeContainer.getSwipeOffsetY() + swipeContainer.getTopActionBarOffsetY()); try { JSONObject data = new JSONObject(); data.put("height", viewPortHeight / AndroidUtilities.density); @@ -529,6 +626,14 @@ protected boolean drawChild(Canvas canvas, View child, long drawingTime) { invalidate(); return draw; } + if (child == webViewNotAvailableText) { + canvas.save(); + View parent = (View) BotWebViewContainer.this.getParent(); + canvas.translate(0, (ActionBar.getCurrentActionBarHeight() - parent.getTranslationY()) / 2f); + boolean draw = super.drawChild(canvas, child, drawingTime); + canvas.restore(); + return draw; + } return super.drawChild(canvas, child, drawingTime); } @@ -552,7 +657,7 @@ public void loadFlicker(int currentAccount, long botId) { if (user.username != null && Objects.equals(user.username, DURGER_KING_USERNAME)) { flickerView.setVisibility(VISIBLE); flickerView.setAlpha(1f); - flickerView.setImageDrawable(SvgHelper.getDrawable(R.raw.durgerking_placeholder, Theme.getColor(Theme.key_windowBackgroundGray))); + flickerView.setImageDrawable(SvgHelper.getDrawable(R.raw.durgerking_placeholder, getColor(Theme.key_windowBackgroundGray))); setupFlickerParams(false); return; } @@ -616,11 +721,25 @@ private void setupFlickerParams(boolean center) { flickerView.requestLayout(); } + public void reload() { + checkCreateWebView(); + + isPageLoaded = false; + hasUserPermissions = false; + if (webView != null) { + webView.reload(); + } + } + public void loadUrl(String url) { + checkCreateWebView(); + isPageLoaded = false; hasUserPermissions = false; mUrl = url; - webView.loadUrl(url); + if (webView != null) { + webView.loadUrl(url); + } } @Override @@ -642,11 +761,18 @@ protected void onDetachedFromWindow() { } public void destroyWebView() { - webView.destroy(); + if (webView != null) { + webView.destroy(); + } } @SuppressWarnings("deprecation") public void evaluateJs(String script) { + checkCreateWebView(); + if (webView == null) { + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { webView.evaluateJavascript(script, value -> {}); } else { @@ -661,7 +787,11 @@ public void evaluateJs(String script) { @Override public void didReceivedNotification(int id, int account, Object... args) { if (id == NotificationCenter.didSetNewTheme) { - evaluateJs("window.Telegram.WebView.receiveEvent('theme_changed', {theme_params: " + buildThemeParams() + "});"); + if (webView != null) { + webView.setBackgroundColor(getColor(Theme.key_windowBackgroundWhite)); + } + flickerView.setColorFilter(new PorterDuffColorFilter(getColor(Theme.key_dialogSearchHint), PorterDuff.Mode.SRC_IN)); + notifyThemeChanged(); } else if (id == NotificationCenter.onActivityResultReceived) { onActivityResult((int) args[0], (int) args[1], (Intent) args[2]); } else if (id == NotificationCenter.onRequestPermissionResultReceived) { @@ -669,6 +799,10 @@ public void didReceivedNotification(int id, int account, Object... args) { } } + private void notifyThemeChanged() { + evaluateJs("window.Telegram.WebView.receiveEvent('theme_changed', {theme_params: " + buildThemeParams() + "});"); + } + public void setWebViewScrollListener(WebViewScrollListener webViewScrollListener) { this.webViewScrollListener = webViewScrollListener; } @@ -678,13 +812,21 @@ public void setDelegate(Delegate delegate) { } private void onEventReceived(String eventType, String eventData) { + if (webView == null || delegate == null) { + return; + } switch (eventType) { case "web_app_close": { - delegate.onCloseRequested(); + delegate.onCloseRequested(null); break; } case "web_app_data_send": { - delegate.onSendWebViewData(eventData); + try { + JSONObject jsonData = new JSONObject(eventData); + delegate.onSendWebViewData(jsonData.optString("data")); + } catch (JSONException e) { + FileLog.e(e); + } break; } case "web_app_expand": { @@ -696,6 +838,10 @@ private void onEventReceived(String eventType, String eventData) { invalidateViewPortHeight(!hasSwipeInProgress, true); break; } + case "web_app_request_theme": { + notifyThemeChanged(); + break; + } case "web_app_ready": { setPageLoaded(webView.getUrl()); break; @@ -740,11 +886,16 @@ private String buildThemeParams() { } } - private String formatColor(String colorKey) { + private int getColor(String colorKey) { Integer color = resourcesProvider != null ? resourcesProvider.getColor(colorKey) : Theme.getColor(colorKey); if (color == null) { color = Theme.getColor(colorKey); } + return color; + } + + private String formatColor(String colorKey) { + int color = getColor(colorKey); return "#" + hexFixed(Color.red(color)) + hexFixed(Color.green(color)) + hexFixed(Color.blue(color)); } @@ -778,14 +929,14 @@ public interface Delegate { /** * Called when WebView requests to close itself */ - void onCloseRequested(); + void onCloseRequested(@Nullable Runnable callback); /** * Called when WebView requests to send custom data * * @param data Custom data to send */ - void onSendWebViewData(String data); + default void onSendWebViewData(String data) {} /** * Called when WebView requests to expand viewport @@ -796,5 +947,10 @@ public interface Delegate { * Setups main button */ void onSetupMainButton(boolean isVisible, boolean isActive, String text, int color, int textColor, boolean isProgressVisible); + + /** + * Called when WebView is ready (Called web_app_ready or page load finished) + */ + default void onWebAppReady() {} } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BotWebViewMenuContainer.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BotWebViewMenuContainer.java index 5f90713d357..0ced05bc3e1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BotWebViewMenuContainer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BotWebViewMenuContainer.java @@ -28,7 +28,6 @@ import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; -import org.telegram.messenger.Utilities; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; @@ -45,21 +44,7 @@ public class BotWebViewMenuContainer extends FrameLayout implements Notification private final static SimpleFloatPropertyCompat ACTION_BAR_TRANSITION_PROGRESS_VALUE = new SimpleFloatPropertyCompat("actionBarTransitionProgress", obj -> obj.actionBarTransitionProgress, (obj, value) -> { obj.actionBarTransitionProgress = value; obj.invalidate(); - - int subtitleColor = ColorUtils.blendARGB(obj.getColor(Theme.key_actionBarDefaultSubtitle), obj.getColor(Theme.key_windowBackgroundWhiteGrayText), value); - ChatActivity chatActivity = obj.parentEnterView.getParentFragment(); - ActionBar actionBar = chatActivity.getActionBar(); - actionBar.setBackgroundColor(ColorUtils.blendARGB(obj.getColor(Theme.key_actionBarDefault), obj.getColor(Theme.key_windowBackgroundWhite), value)); - actionBar.setItemsColor(ColorUtils.blendARGB(obj.getColor(Theme.key_actionBarDefaultIcon), obj.getColor(Theme.key_windowBackgroundWhiteBlackText), value), false); - actionBar.setItemsBackgroundColor(ColorUtils.blendARGB(obj.getColor(Theme.key_actionBarDefaultSelector), obj.getColor(Theme.key_actionBarWhiteSelector), value), false); - actionBar.setSubtitleColor(subtitleColor); - - ChatAvatarContainer chatAvatarContainer = chatActivity.getAvatarContainer(); - chatAvatarContainer.getTitleTextView().setTextColor(ColorUtils.blendARGB(obj.getColor(Theme.key_actionBarDefaultTitle), obj.getColor(Theme.key_windowBackgroundWhiteBlackText), value)); - chatAvatarContainer.getSubtitleTextView().setTextColor(subtitleColor); - chatAvatarContainer.setOverrideSubtitleColor(value == 0 ? null : subtitleColor); - - obj.updateLightStatusBar(); + obj.invalidateActionBar(); }).setMultiplier(100f); private float actionBarTransitionProgress; @@ -133,33 +118,16 @@ public BotWebViewMenuContainer(@NonNull Context context, ChatActivityEnterView p actionBarOnItemClick = actionBar.getActionBarMenuOnItemClick(); webViewContainer = new BotWebViewContainer(context, parentEnterView.getParentFragment().getResourceProvider(), getColor(Theme.key_windowBackgroundWhite)); - webViewContainer.setViewPortOffset(-AndroidUtilities.dp(5)); webViewContainer.setDelegate(webViewDelegate = new BotWebViewContainer.Delegate() { - private boolean sentWebViewData; @Override - public void onCloseRequested() { - dismiss(); - } - - @Override - public void onSendWebViewData(String data) { - if (sentWebViewData) { - return; - } - sentWebViewData = true; - - TLRPC.TL_messages_sendWebViewData sendWebViewData = new TLRPC.TL_messages_sendWebViewData(); - sendWebViewData.bot = MessagesController.getInstance(currentAccount).getInputUser(botId); - sendWebViewData.random_id = Utilities.random.nextLong(); - sendWebViewData.button_text = "Menu"; - sendWebViewData.data = data; - ConnectionsManager.getInstance(currentAccount).sendRequest(sendWebViewData, (response, error) -> AndroidUtilities.runOnUIThread(()-> dismiss())); + public void onCloseRequested(Runnable callback) { + dismiss(callback); } @Override public void onWebAppExpand() { - if (System.currentTimeMillis() - lastSwipeTime <= 1000 || swipeContainer.isSwipeInProgress()) { + if (/* System.currentTimeMillis() - lastSwipeTime <= 1000 || */ swipeContainer.isSwipeInProgress()) { return; } swipeContainer.stickTo(-swipeContainer.getOffsetY() + swipeContainer.getTopActionBarOffsetY()); @@ -181,7 +149,6 @@ public void onSetupMainButton(boolean isVisible, boolean isActive, String text, linePaint.setStrokeCap(Paint.Cap.ROUND); dimPaint.setColor(0x40000000); - backgroundPaint.setColor(getColor(Theme.key_windowBackgroundWhite)); swipeContainer = new ChatAttachAlertBotWebViewLayout.WebViewSwipeContainer(context) { @Override @@ -207,7 +174,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (AndroidUtilities.isTablet() && !AndroidUtilities.isInMultiwindow && !AndroidUtilities.isSmallTablet()) { widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.8f), MeasureSpec.EXACTLY); } - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec) - ActionBar.getCurrentActionBarHeight() - AndroidUtilities.statusBarHeight + AndroidUtilities.dp(24), MeasureSpec.EXACTLY)); + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec) - ActionBar.getCurrentActionBarHeight() - AndroidUtilities.statusBarHeight + AndroidUtilities.dp(24) - AndroidUtilities.dp(5), MeasureSpec.EXACTLY)); } @Override @@ -218,7 +185,6 @@ public void requestLayout() { super.requestLayout(); } }; - swipeContainer.setWebView(webViewContainer.getWebView()); swipeContainer.setScrollListener(() -> { if (swipeContainer.getSwipeOffsetY() > 0) { dimPaint.setAlpha((int) (0x40 * (1f - Math.min(swipeContainer.getSwipeOffsetY(), swipeContainer.getHeight()) / (float)swipeContainer.getHeight()))); @@ -265,6 +231,27 @@ public void onAnimationEnd(Animator animation) { setWillNotDraw(false); } + private void invalidateActionBar() { + int subtitleColor = ColorUtils.blendARGB(getColor(Theme.key_actionBarDefaultSubtitle), getColor(Theme.key_windowBackgroundWhiteGrayText), actionBarTransitionProgress); + ChatActivity chatActivity = parentEnterView.getParentFragment(); + ActionBar actionBar = chatActivity.getActionBar(); + actionBar.setBackgroundColor(ColorUtils.blendARGB(getColor(Theme.key_actionBarDefault), getColor(Theme.key_windowBackgroundWhite), actionBarTransitionProgress)); + actionBar.setItemsColor(ColorUtils.blendARGB(getColor(Theme.key_actionBarDefaultIcon), getColor(Theme.key_windowBackgroundWhiteBlackText), actionBarTransitionProgress), false); + actionBar.setItemsBackgroundColor(ColorUtils.blendARGB(getColor(Theme.key_actionBarDefaultSelector), getColor(Theme.key_actionBarWhiteSelector), actionBarTransitionProgress), false); + actionBar.setSubtitleColor(subtitleColor); + + ChatAvatarContainer chatAvatarContainer = chatActivity.getAvatarContainer(); + chatAvatarContainer.getTitleTextView().setTextColor(ColorUtils.blendARGB(getColor(Theme.key_actionBarDefaultTitle), getColor(Theme.key_windowBackgroundWhiteBlackText), actionBarTransitionProgress)); + chatAvatarContainer.getSubtitleTextView().setTextColor(subtitleColor); + chatAvatarContainer.setOverrideSubtitleColor(actionBarTransitionProgress == 0 ? null : subtitleColor); + + updateLightStatusBar(); + } + + public boolean onBackPressed() { + return webViewContainer.onBackPressed(); + } + private void animateBotButton(boolean isVisible) { ChatActivityBotWebViewButton botWebViewButton = parentEnterView.getBotWebViewButton(); if (botWebViewButtonAnimator != null) { @@ -316,7 +303,7 @@ public void onAttachedToWindow() { chatAvatarContainer.getAvatarImageView().setClickable(value == 0); ActionBar actionBar = chatActivity.getActionBar(); - if (value == 100) { + if (value == 100 && parentEnterView.hasBotWebView()) { chatActivity.showHeaderItem(false); botMenuItem.setVisibility(VISIBLE); actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @@ -325,11 +312,19 @@ public void onItemClick(int id) { if (id == -1) { dismiss(); } else if (id == R.id.menu_reload_page) { - webViewContainer.getWebView().animate().cancel(); - webViewContainer.getWebView().animate().alpha(0).start(); + if (webViewContainer.getWebView() != null) { + webViewContainer.getWebView().animate().cancel(); + webViewContainer.getWebView().animate().alpha(0).start(); + } isLoaded = false; - loadWebView(); + progressView.setLoadProgress(0); + progressView.setAlpha(1f); + progressView.setVisibility(VISIBLE); + + webViewContainer.setBotUser(MessagesController.getInstance(currentAccount).getUser(botId)); + webViewContainer.loadFlicker(currentAccount, botId); + webViewContainer.reload(); } } }); @@ -341,6 +336,7 @@ public void onItemClick(int id) { }); } NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.webViewResultSent); + NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.didSetNewTheme); } @Override @@ -351,7 +347,9 @@ public void onDetachedFromWindow() { springAnimation.cancel(); springAnimation = null; } + actionBarTransitionProgress = 0f; NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.webViewResultSent); + NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.didSetNewTheme); } @Override @@ -377,24 +375,30 @@ public void onPanTransitionStart(boolean keyboardVisible, int contentHeight) { webViewScrollAnimator = null; } - int fromY = webViewContainer.getWebView().getScrollY(); - int toY = fromY + (oldh - contentHeight); - webViewScrollAnimator = ValueAnimator.ofInt(fromY, toY).setDuration(250); - webViewScrollAnimator.setInterpolator(ChatListItemAnimator.DEFAULT_INTERPOLATOR); - webViewScrollAnimator.addUpdateListener(animation -> { - int val = (int) animation.getAnimatedValue(); - webViewContainer.getWebView().setScrollY(val); - }); - webViewScrollAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - webViewContainer.getWebView().setScrollY(toY); - if (animation == webViewScrollAnimator) { - webViewScrollAnimator = null; + if (webViewContainer.getWebView() != null) { + int fromY = webViewContainer.getWebView().getScrollY(); + int toY = fromY + (oldh - contentHeight); + webViewScrollAnimator = ValueAnimator.ofInt(fromY, toY).setDuration(250); + webViewScrollAnimator.setInterpolator(ChatListItemAnimator.DEFAULT_INTERPOLATOR); + webViewScrollAnimator.addUpdateListener(animation -> { + int val = (int) animation.getAnimatedValue(); + if (webViewContainer.getWebView() != null) { + webViewContainer.getWebView().setScrollY(val); } - } - }); - webViewScrollAnimator.start(); + }); + webViewScrollAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (webViewContainer.getWebView() != null) { + webViewContainer.getWebView().setScrollY(toY); + } + if (animation == webViewScrollAnimator) { + webViewScrollAnimator = null; + } + } + }); + webViewScrollAnimator.start(); + } } public void onPanTransitionEnd() { @@ -426,6 +430,7 @@ private void updateLightStatusBar() { protected void onDraw(Canvas canvas) { super.onDraw(canvas); + backgroundPaint.setColor(getColor(Theme.key_windowBackgroundWhite)); AndroidUtilities.rectTmp.set(0, 0, getWidth(), getHeight()); canvas.drawRect(AndroidUtilities.rectTmp, dimPaint); @@ -437,7 +442,7 @@ protected void onDraw(Canvas canvas) { @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_DOWN && event.getY() <= AndroidUtilities.lerp(swipeContainer.getTranslationY(), 0, actionBarTransitionProgress)) { + if (event.getAction() == MotionEvent.ACTION_DOWN && event.getY() <= AndroidUtilities.lerp(swipeContainer.getTranslationY() + AndroidUtilities.dp(24), 0, actionBarTransitionProgress)) { dismiss(); return true; } @@ -448,7 +453,7 @@ public boolean onTouchEvent(MotionEvent event) { public void draw(Canvas canvas) { super.draw(canvas); - linePaint.setColor(Theme.getColor(Theme.key_dialogGrayLine)); + linePaint.setColor(getColor(Theme.key_dialogGrayLine)); linePaint.setAlpha((int) (linePaint.getAlpha() * (1f - Math.min(0.5f, actionBarTransitionProgress) / 0.5f))); canvas.save(); @@ -499,7 +504,10 @@ public void onLayoutChange(View v, int left, int top, int right, int bottom, int .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY) .setStiffness(500.0f) ) - .addEndListener((animation, canceled, value, velocity) -> webViewContainer.restoreButtonData()) + .addEndListener((animation, canceled, value, velocity) -> { + webViewContainer.restoreButtonData(); + webViewContainer.invalidateViewPortHeight(true); + }) .start(); } }); @@ -544,6 +552,7 @@ private void loadWebView() { TLRPC.TL_webViewResultUrl resultUrl = (TLRPC.TL_webViewResultUrl) response; queryId = resultUrl.query_id; webViewContainer.loadUrl(resultUrl.url); + swipeContainer.setWebView(webViewContainer.getWebView()); AndroidUtilities.runOnUIThread(pollRunnable, POLL_PERIOD); } @@ -594,7 +603,6 @@ public void onDismiss() { swipeContainer.removeView(webViewContainer); webViewContainer = new BotWebViewContainer(getContext(), parentEnterView.getParentFragment().getResourceProvider(), getColor(Theme.key_windowBackgroundWhite)); - webViewContainer.setViewPortOffset(-AndroidUtilities.dp(5)); webViewContainer.setDelegate(webViewDelegate); webViewContainer.setWebViewProgressListener(progress -> { progressView.setLoadProgressAnimated(progress); @@ -612,7 +620,6 @@ public void onAnimationEnd(Animator animation) { } }); swipeContainer.addView(webViewContainer); - swipeContainer.setWebView(webViewContainer.getWebView()); isLoaded = false; AndroidUtilities.cancelRunOnUIThread(pollRunnable); @@ -652,6 +659,11 @@ public void didReceivedNotification(int id, int account, Object... args) { if (this.queryId == queryId) { dismiss(); } + } else if (id == NotificationCenter.didSetNewTheme) { + webViewContainer.updateFlickerBackgroundColor(getColor(Theme.key_windowBackgroundWhite)); + invalidate(); + invalidateActionBar(); + AndroidUtilities.runOnUIThread(this::invalidateActionBar, 300); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BotWebViewSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BotWebViewSheet.java index a0dcc7ba230..89036c35b0d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BotWebViewSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BotWebViewSheet.java @@ -25,6 +25,7 @@ import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import androidx.core.graphics.ColorUtils; +import androidx.core.math.MathUtils; import androidx.dynamicanimation.animation.SpringAnimation; import androidx.dynamicanimation.animation.SpringForce; @@ -161,19 +162,17 @@ public void requestLayout() { } }; webViewContainer = new BotWebViewContainer(context, resourcesProvider, getColor(Theme.key_windowBackgroundWhite)); - - webViewContainer.getWebView().setVerticalScrollBarEnabled(false); webViewContainer.setDelegate(new BotWebViewContainer.Delegate() { private boolean sentWebViewData; @Override - public void onCloseRequested() { - dismiss(); + public void onCloseRequested(Runnable callback) { + dismiss(callback); } @Override public void onSendWebViewData(String data) { - if (sentWebViewData) { + if (queryId != 0 || sentWebViewData) { return; } sentWebViewData = true; @@ -188,7 +187,7 @@ public void onSendWebViewData(String data) { @Override public void onWebAppExpand() { - if (System.currentTimeMillis() - lastSwipeTime <= 1000 || swipeContainer.isSwipeInProgress()) { + if (/* System.currentTimeMillis() - lastSwipeTime <= 1000 || */ swipeContainer.isSwipeInProgress()) { return; } swipeContainer.stickTo(-swipeContainer.getOffsetY() + swipeContainer.getTopActionBarOffsetY()); @@ -199,7 +198,7 @@ public void onSetupMainButton(boolean isVisible, boolean isActive, String text, mainButton.setClickable(isActive); mainButton.setText(text); mainButton.setTextColor(textColor); - mainButton.setBackground(Theme.createSelectorWithBackgroundDrawable(color, Theme.getColor(Theme.key_listSelector))); + mainButton.setBackground(BotWebViewContainer.getMainButtonRippleDrawable(color)); if (isVisible != mainButtonWasVisible) { mainButtonWasVisible = isVisible; mainButton.animate().cancel(); @@ -245,7 +244,6 @@ public void onAnimationEnd(Animator animation) { linePaint.setStrokeWidth(AndroidUtilities.dp(4)); linePaint.setStrokeCap(Paint.Cap.ROUND); - backgroundPaint.setColor(getColor(Theme.key_windowBackgroundWhite)); dimPaint.setColor(0x40000000); frameLayout = new SizeNotifierFrameLayout(context) { { @@ -256,6 +254,7 @@ public void onAnimationEnd(Animator animation) { protected void onDraw(Canvas canvas) { super.onDraw(canvas); + backgroundPaint.setColor(getColor(Theme.key_windowBackgroundWhite)); AndroidUtilities.rectTmp.set(0, 0, getWidth(), getHeight()); canvas.drawRect(AndroidUtilities.rectTmp, dimPaint); @@ -287,7 +286,7 @@ public void draw(Canvas canvas) { @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_DOWN && event.getY() < AndroidUtilities.lerp(swipeContainer.getTranslationY(), 0, actionBarTransitionProgress)) { + if (event.getAction() == MotionEvent.ACTION_DOWN && event.getY() <= AndroidUtilities.lerp(swipeContainer.getTranslationY() + AndroidUtilities.dp(24), 0, actionBarTransitionProgress)) { dismiss(); return true; } @@ -327,10 +326,8 @@ public boolean onTouchEvent(MotionEvent event) { actionBar = new ActionBar(context, resourcesProvider); actionBar.setBackgroundColor(Color.TRANSPARENT); - actionBar.setTitleColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); - actionBar.setItemsColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText), false); - actionBar.setItemsBackgroundColor(Theme.getColor(Theme.key_actionBarWhiteSelector), false); actionBar.setBackButtonImage(R.drawable.ic_ab_back); + updateActionBarColors(); actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @Override public void onItemClick(int id) { @@ -360,10 +357,9 @@ public void onAnimationEnd(Animator animation) { }); swipeContainer.addView(webViewContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - swipeContainer.setWebView(webViewContainer.getWebView()); swipeContainer.setScrollListener(()->{ if (swipeContainer.getSwipeOffsetY() > 0) { - dimPaint.setAlpha((int) (0x40 * (1f - swipeContainer.getSwipeOffsetY() / (float)swipeContainer.getHeight()))); + dimPaint.setAlpha((int) (0x40 * (1f - MathUtils.clamp(swipeContainer.getSwipeOffsetY() / (float)swipeContainer.getHeight(), 0, 1)))); } else { dimPaint.setAlpha(0x40); } @@ -394,6 +390,16 @@ public void setParentActivity(Activity parentActivity) { this.parentActivity = parentActivity; } + private void updateActionBarColors() { + actionBar.setTitleColor(getColor(Theme.key_windowBackgroundWhiteBlackText)); + actionBar.setItemsColor(getColor(Theme.key_windowBackgroundWhiteBlackText), false); + actionBar.setItemsBackgroundColor(getColor(Theme.key_actionBarWhiteSelector), false); + actionBar.setPopupBackgroundColor(getColor(Theme.key_actionBarDefaultSubmenuBackground), false); + actionBar.setPopupItemsColor(getColor(Theme.key_actionBarDefaultSubmenuItem), false, false); + actionBar.setPopupItemsColor(getColor(Theme.key_actionBarDefaultSubmenuItemIcon), true, false); + actionBar.setPopupItemsSelectorColor(getColor(Theme.key_dialogButtonSelector), false); + } + private void updateLightStatusBar() { int color = Theme.getColor(Theme.key_windowBackgroundWhite, null, true); boolean lightStatusBar = ColorUtils.calculateLuminance(color) >= 0.9 && actionBarTransitionProgress >= 0.85f; @@ -454,6 +460,8 @@ protected void onCreate(Bundle savedInstanceState) { int color = Theme.getColor(Theme.key_windowBackgroundWhite, null, true); AndroidUtilities.setLightNavigationBar(window, ColorUtils.calculateLuminance(color) >= 0.9); } + + NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.didSetNewTheme); } @Override @@ -507,9 +515,18 @@ public void onItemClick(int id) { } dismiss(); } else if (id == R.id.menu_reload_page) { - webViewContainer.getWebView().animate().cancel(); - webViewContainer.getWebView().animate().alpha(0).start(); - requestWebView(currentAccount, peerId, botId, buttonText, buttonUrl, simple, replyToMsgId, silent); + if (webViewContainer.getWebView() != null) { + webViewContainer.getWebView().animate().cancel(); + webViewContainer.getWebView().animate().alpha(0).start(); + } + + progressView.setLoadProgress(0); + progressView.setAlpha(1f); + progressView.setVisibility(View.VISIBLE); + + webViewContainer.setBotUser(MessagesController.getInstance(currentAccount).getUser(botId)); + webViewContainer.loadFlicker(currentAccount, botId); + webViewContainer.reload(); } } }); @@ -545,7 +562,9 @@ public void onItemClick(int id) { ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(()->{ if (response instanceof TLRPC.TL_simpleWebViewResultUrl) { TLRPC.TL_simpleWebViewResultUrl resultUrl = (TLRPC.TL_simpleWebViewResultUrl) response; + queryId = 0; webViewContainer.loadUrl(resultUrl.url); + swipeContainer.setWebView(webViewContainer.getWebView()); } })); } else { @@ -573,6 +592,7 @@ public void onItemClick(int id) { TLRPC.TL_webViewResultUrl resultUrl = (TLRPC.TL_webViewResultUrl) response; queryId = resultUrl.query_id; webViewContainer.loadUrl(resultUrl.url); + swipeContainer.setWebView(webViewContainer.getWebView()); AndroidUtilities.runOnUIThread(pollRunnable, POLL_PERIOD); } @@ -612,8 +632,20 @@ public void onLayoutChange(View v, int left, int top, int right, int bottom, int super.show(); } + @Override + public void onBackPressed() { + if (webViewContainer.onBackPressed()) { + return; + } + super.onBackPressed(); + } + @Override public void dismiss() { + dismiss(null); + } + + public void dismiss(Runnable callback) { if (dismissed) { return; } @@ -622,8 +654,14 @@ public void dismiss() { webViewContainer.destroyWebView(); NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.webViewResultSent); + NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.didSetNewTheme); - swipeContainer.stickTo(swipeContainer.getHeight() + frameLayout.measureKeyboardHeight(), super::dismiss); + swipeContainer.stickTo(swipeContainer.getHeight() + frameLayout.measureKeyboardHeight(), ()->{ + super.dismiss(); + if (callback != null) { + callback.run(); + } + }); } @Override @@ -634,6 +672,11 @@ public void didReceivedNotification(int id, int account, Object... args) { if (this.queryId == queryId) { dismiss(); } + } else if (id == NotificationCenter.didSetNewTheme) { + frameLayout.invalidate(); + webViewContainer.updateFlickerBackgroundColor(getColor(Theme.key_windowBackgroundWhite)); + updateActionBarColors(); + updateLightStatusBar(); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityBotWebViewButton.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityBotWebViewButton.java index 234e51027a2..d4613ba2a5e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityBotWebViewButton.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityBotWebViewButton.java @@ -42,18 +42,18 @@ public ChatActivityBotWebViewButton(Context context) { textView.setAlpha(0f); textView.setGravity(Gravity.CENTER); textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT, 0, 0, 0, 4)); + addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT, 0, 0, 0, 0)); progressView = new RadialProgressView(context); progressView.setSize(AndroidUtilities.dp(18)); progressView.setAlpha(0f); progressView.setScaleX(0); progressView.setScaleY(0); - addView(progressView, LayoutHelper.createFrame(28, 28, Gravity.RIGHT | Gravity.CENTER_VERTICAL, 0, 0, 12, 4)); + addView(progressView, LayoutHelper.createFrame(28, 28, Gravity.RIGHT | Gravity.CENTER_VERTICAL, 0, 0, 12, 0)); rippleView = new View(context); - rippleView.setBackground(Theme.createSelectorDrawable(Theme.getColor(Theme.key_listSelector), 2)); - addView(rippleView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT, 0, -4, 0, 0)); + rippleView.setBackground(Theme.createSelectorDrawable(Theme.getColor(Theme.key_featuredStickers_addButtonPressed), 2)); + addView(rippleView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT, 0, 0, 0, 0)); setWillNotDraw(false); } @@ -70,6 +70,8 @@ public void setupButtonParams(boolean isActive, String text, int color, int text textView.setTextColor(textColor); buttonColor = color; + rippleView.setBackground(Theme.createSelectorDrawable(BotWebViewContainer.getMainButtonRippleColor(buttonColor), 2)); + progressView.setProgressColor(textColor); if (progressWasVisible != isProgressVisible) { progressWasVisible = isProgressVisible; @@ -91,6 +93,7 @@ public void onAnimationEnd(Animator animation) { } }).start(); } + invalidate(); } public void setProgress(float progress) { @@ -110,9 +113,10 @@ public void setMeasuredButtonWidth(int width) { @Override public void draw(Canvas canvas) { canvas.save(); + float menuY = (getHeight() - AndroidUtilities.dp(32)) / 2f; float offset = Math.max(getWidth() - menuButtonWidth - AndroidUtilities.dp(4), getHeight()) * progress; float rad = AndroidUtilities.dp(16) + offset; - AndroidUtilities.rectTmp.set(AndroidUtilities.dp(14) - offset, AndroidUtilities.dp(8) - offset, AndroidUtilities.dp(6) + menuButtonWidth + offset, getHeight() - AndroidUtilities.dp(12) + offset); + AndroidUtilities.rectTmp.set(AndroidUtilities.dp(14) - offset, menuY + AndroidUtilities.dp(4) - offset, AndroidUtilities.dp(6) + menuButtonWidth + offset, getHeight() - AndroidUtilities.dp(12) + offset); path.rewind(); path.addRoundRect(AndroidUtilities.rectTmp, rad, rad, Path.Direction.CW); @@ -120,7 +124,7 @@ public void draw(Canvas canvas) { canvas.drawColor(backgroundColor); canvas.saveLayerAlpha(AndroidUtilities.rectTmp, (int) ((1f - Math.min(0.5f, progress) / 0.5f) * 0xFF), Canvas.ALL_SAVE_FLAG); - canvas.translate(AndroidUtilities.dp(10), AndroidUtilities.dp(4)); + canvas.translate(AndroidUtilities.dp(10), menuY); if (menuButton != null) { menuButton.setDrawBackgroundDrawable(false); menuButton.draw(canvas); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java index ac087c86327..c11f445fbca 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java @@ -1720,6 +1720,7 @@ public ChatActivityEnterView(Activity context, SizeNotifierFrameLayout parent, C NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.messageReceivedByServer); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.sendingMessagesChanged); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.audioRecordTooShort); + NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.updateBotMenuButton); NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.emojiLoaded); parentActivity = context; @@ -4180,6 +4181,7 @@ public void onDestroy() { NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.messageReceivedByServer); NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.sendingMessagesChanged); NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.audioRecordTooShort); + NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.updateBotMenuButton); NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.emojiLoaded); if (emojiView != null) { emojiView.onDestroy(); @@ -6882,7 +6884,11 @@ public void onAnimationCancel(Animator animation) { } } - private boolean hasBotWebView() { + public boolean onBotWebViewBackPressed() { + return botWebViewMenuContainer != null && botWebViewMenuContainer.onBackPressed(); + } + + public boolean hasBotWebView() { return botMenuButtonType == BotMenuButtonType.WEB_VIEW; } @@ -8356,6 +8362,24 @@ public void didReceivedNotification(int id, int account, Object... args) { } } else if (id == NotificationCenter.audioRecordTooShort) { updateRecordIntefrace(RECORD_STATE_CANCEL_BY_TIME); + } else if (id == NotificationCenter.updateBotMenuButton) { + long botId = (long) args[0]; + TLRPC.BotMenuButton botMenuButton = (TLRPC.BotMenuButton) args[1]; + + if (botId == dialog_id) { + if (botMenuButton instanceof TLRPC.TL_botMenuButton) { + TLRPC.TL_botMenuButton webViewButton = (TLRPC.TL_botMenuButton) botMenuButton; + botMenuWebViewTitle = webViewButton.text; + botMenuWebViewUrl = webViewButton.url; + botMenuButtonType = BotMenuButtonType.WEB_VIEW; + } else if (hasBotCommands) { + botMenuButtonType = BotMenuButtonType.COMMANDS; + } else { + botMenuButtonType = BotMenuButtonType.NO_BUTTON; + } + + updateBotButton(false); + } } } @@ -9187,7 +9211,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (botCommandsMenuButton != null) { botWebViewButton.setMeasuredButtonWidth(botCommandsMenuButton.getMeasuredWidth()); } - botWebViewButton.getLayoutParams().height = messageEditText.getMeasuredHeight(); + botWebViewButton.getLayoutParams().height = getMeasuredHeight() - AndroidUtilities.dp(2); measureChild(botWebViewButton, widthMeasureSpec, heightMeasureSpec); } if (botWebViewMenuContainer != null) { @@ -9219,13 +9243,13 @@ public void setBotInfo(LongSparseArray botInfo) { if (botInfo.size() == 1 && botInfo.valueAt(0).user_id == dialog_id) { TLRPC.BotInfo info = botInfo.valueAt(0); TLRPC.BotMenuButton menuButton = info.menu_button; - if (menuButton instanceof TLRPC.TL_botMenuButtonCommands || menuButton instanceof TLRPC.TL_botMenuButtonDefault) { - botMenuButtonType = info.commands.isEmpty() ? BotMenuButtonType.NO_BUTTON : BotMenuButtonType.COMMANDS; - } else if (menuButton instanceof TLRPC.TL_botMenuButton) { + if (menuButton instanceof TLRPC.TL_botMenuButton) { TLRPC.TL_botMenuButton webViewButton = (TLRPC.TL_botMenuButton) menuButton; botMenuWebViewTitle = webViewButton.text; botMenuWebViewUrl = webViewButton.url; botMenuButtonType = BotMenuButtonType.WEB_VIEW; + } else if (!info.commands.isEmpty()) { + botMenuButtonType = BotMenuButtonType.COMMANDS; } else { botMenuButtonType = BotMenuButtonType.NO_BUTTON; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java index 006717e922d..c42a4fd1437 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java @@ -29,6 +29,7 @@ import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Build; +import android.os.Bundle; import android.os.Vibrator; import android.text.Editable; import android.text.TextPaint; @@ -136,16 +137,19 @@ public void showBotLayout(long id, String startCommand) { botAttachLayouts.put(id, webViewLayout); botAttachLayouts.get(id).setDelegate(new BotWebViewContainer.Delegate() { @Override - public void onCloseRequested() { + public void onCloseRequested(Runnable callback) { if (currentAttachLayout != webViewLayout) { return; } - dismiss(); - } + ChatAttachAlert.this.setFocusable(false); + ChatAttachAlert.this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING); - @Override - public void onSendWebViewData(String data) { - // Empty as it's delegate from attachments menu. Data is only available for buttons + dismiss(); + AndroidUtilities.runOnUIThread(()->{ + if (callback != null) { + callback.run(); + } + }, 150); } @Override @@ -167,7 +171,7 @@ public void onSetupMainButton(boolean isVisible, boolean isActive, String text, botMainButtonTextView.setClickable(isActive); botMainButtonTextView.setText(text); botMainButtonTextView.setTextColor(textColor); - botMainButtonTextView.setBackground(Theme.createSelectorWithBackgroundDrawable(color, Theme.getColor(Theme.key_listSelector))); + botMainButtonTextView.setBackground(BotWebViewContainer.getMainButtonRippleDrawable(color)); if (botButtonWasVisible != isVisible) { ValueAnimator animator = ValueAnimator.ofFloat(isVisible ? 0 : 1, isVisible ? 1 : 0).setDuration(250); animator.addUpdateListener(animation -> { @@ -184,6 +188,11 @@ public void onAnimationStart(Animator animation) { if (isVisible) { botMainButtonTextView.setAlpha(0f); botMainButtonTextView.setVisibility(View.VISIBLE); + + int offsetY = AndroidUtilities.dp(36); + for (int i = 0; i < botAttachLayouts.size(); i++) { + botAttachLayouts.valueAt(i).setMeasureOffsetY(offsetY); + } } else { buttonsRecyclerView.setAlpha(0f); buttonsRecyclerView.setVisibility(View.VISIBLE); @@ -915,7 +924,7 @@ public void setAttachBot(TLRPC.User user, TLRPC.TL_attachMenuBot bot) { TLRPC.Document iconDoc = icon.icon; imageView.getImageReceiver().setAllowStartLottieAnimation(false); - imageView.setImage(ImageLocation.getForDocument(iconDoc), "32_32", animated ? "tgs" : "svg", null, icon); + imageView.setImage(ImageLocation.getForDocument(iconDoc), String.valueOf(bot.bot_id), animated ? "tgs" : "svg", null, bot); } imageView.setSize(AndroidUtilities.dp(28), AndroidUtilities.dp(28)); @@ -1444,7 +1453,7 @@ protected void onDraw(Canvas canvas) { return; } int color1 = getThemedColor(forceDarkTheme ? Theme.key_voipgroup_listViewBackground : Theme.key_dialogBackground); - int finalColor = Color.argb((int) (255 * actionBar.getAlpha()), (int) (Color.red(color1) * 0.8f), (int) (Color.green(color1) * 0.8f), (int) (Color.blue(color1) * 0.8f)); + int finalColor = Color.argb((int) (255 * actionBar.getAlpha()), Color.red(color1), Color.green(color1), Color.blue(color1)); Theme.dialogs_onlineCirclePaint.setColor(finalColor); canvas.drawRect(backgroundPaddingLeft, currentPanTranslationY, getMeasuredWidth() - backgroundPaddingLeft, AndroidUtilities.statusBarHeight + currentPanTranslationY, Theme.dialogs_onlineCirclePaint); } @@ -1524,21 +1533,24 @@ public void onSizeChanged(int keyboardHeight, boolean isWidthGreater) { actionBar = new ActionBar(context, resourcesProvider) { @Override public void setAlpha(float alpha) { + float oldAlpha = getAlpha(); super.setAlpha(alpha); - containerView.invalidate(); - if (frameLayout2 != null && buttonsRecyclerView != null) { - if (frameLayout2.getTag() == null) { - if (currentAttachLayout == null || currentAttachLayout.shouldHideBottomButtons()) { - buttonsRecyclerView.setAlpha(1.0f - alpha); - shadow.setAlpha(1.0f - alpha); - buttonsRecyclerView.setTranslationY(AndroidUtilities.dp(44) * alpha); - } - frameLayout2.setTranslationY(AndroidUtilities.dp(48) * alpha); - shadow.setTranslationY(AndroidUtilities.dp(84) * alpha + botMainButtonOffsetY); - } else if (currentAttachLayout == null) { - float value = alpha == 0.0f ? 1.0f : 0.0f; - if (buttonsRecyclerView.getAlpha() != value) { - buttonsRecyclerView.setAlpha(value); + if (oldAlpha != alpha) { + containerView.invalidate(); + if (frameLayout2 != null && buttonsRecyclerView != null) { + if (frameLayout2.getTag() == null) { + if (currentAttachLayout == null || currentAttachLayout.shouldHideBottomButtons()) { + buttonsRecyclerView.setAlpha(1.0f - alpha); + shadow.setAlpha(1.0f - alpha); + buttonsRecyclerView.setTranslationY(AndroidUtilities.dp(44) * alpha); + } + frameLayout2.setTranslationY(AndroidUtilities.dp(48) * alpha); + shadow.setTranslationY(AndroidUtilities.dp(84) * alpha + botMainButtonOffsetY); + } else if (currentAttachLayout == null) { + float value = alpha == 0.0f ? 1.0f : 0.0f; + if (buttonsRecyclerView.getAlpha() != value) { + buttonsRecyclerView.setAlpha(value); + } } } } @@ -2324,11 +2336,24 @@ protected void onDraw(Canvas canvas) { } } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (baseFragment != null) { + AndroidUtilities.setLightStatusBar(getWindow(), baseFragment.isLightStatusBar()); + } + } + + private boolean isLightStatusBar() { + int color = getThemedColor(forceDarkTheme ? Theme.key_voipgroup_listViewBackground : Theme.key_dialogBackground); + return ColorUtils.calculateLuminance(color) > 0.7f; + } + public void onLongClickBotButton(TLRPC.TL_attachMenuBot attachMenuBot, TLRPC.User currentUser) { String botName = attachMenuBot != null ? attachMenuBot.short_name : UserObject.getUserName(currentUser); new AlertDialog.Builder(getContext()) - .setTitle(LocaleController.getString(attachMenuBot != null ? R.string.BotRemoveFromMenuTitle : R.string.AppName)) - .setMessage(AndroidUtilities.replaceTags(attachMenuBot != null ? LocaleController.formatString("BotRemoveFromMenu", R.string.BotRemoveFromMenu, botName) : LocaleController.formatString("ChatHintsDelete", R.string.ChatHintsDelete, botName))) + .setTitle(LocaleController.getString(R.string.BotRemoveFromMenuTitle)) + .setMessage(AndroidUtilities.replaceTags(attachMenuBot != null ? LocaleController.formatString("BotRemoveFromMenu", R.string.BotRemoveFromMenu, botName) : LocaleController.formatString("BotRemoveInlineFromMenu", R.string.BotRemoveInlineFromMenu, botName))) .setPositiveButton(LocaleController.getString("OK", R.string.OK), (dialogInterface, i) -> { if (attachMenuBot != null) { TLRPC.TL_messages_toggleBotInAttachMenu req = new TLRPC.TL_messages_toggleBotInAttachMenu(); @@ -3192,6 +3217,14 @@ private void updateActionBarVisibility(boolean show, boolean animated) { } else if (typeButtonsAvailable && frameLayout2.getTag() == null) { buttonsRecyclerView.setVisibility(View.VISIBLE); } + + if (getWindow() != null && baseFragment != null) { + if (show) { + AndroidUtilities.setLightStatusBar(getWindow(), isLightStatusBar()); + } else { + AndroidUtilities.setLightStatusBar(getWindow(), baseFragment.isLightStatusBar()); + } + } if (animated) { actionBarAnimation = new AnimatorSet(); actionBarAnimation.setDuration((long) (180 * Math.abs((show ? 1.0f : 0.0f) - actionBar.getAlpha()))); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertBotWebViewLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertBotWebViewLayout.java index e90fd74030a..6e824bb311a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertBotWebViewLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertBotWebViewLayout.java @@ -4,6 +4,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.Paint; import android.os.Build; @@ -114,9 +115,18 @@ public void onItemClick(int id) { parentAlert.baseFragment.presentFragment(new ChatActivity(bundle)); parentAlert.dismiss(); } else if (id == R.id.menu_reload_page) { - webViewContainer.getWebView().animate().cancel(); - webViewContainer.getWebView().animate().alpha(0).start(); - requestWebView(currentAccount, peerId, botId, silent, replyToMsgId, startCommand); + if (webViewContainer.getWebView() != null) { + webViewContainer.getWebView().animate().cancel(); + webViewContainer.getWebView().animate().alpha(0).start(); + } + + progressView.setLoadProgress(0); + progressView.setAlpha(1f); + progressView.setVisibility(VISIBLE); + + webViewContainer.setBotUser(MessagesController.getInstance(currentAccount).getUser(botId)); + webViewContainer.loadFlicker(currentAccount, botId); + webViewContainer.reload(); } else if (id == R.id.menu_delete_bot) { for (TLRPC.TL_attachMenuBot bot : MediaDataController.getInstance(currentAccount).getAttachMenuBots().bots) { if (bot.bot_id == botId) { @@ -140,7 +150,6 @@ public boolean dispatchTouchEvent(MotionEvent ev) { return super.dispatchTouchEvent(ev); } }; - webViewContainer.getWebView().setVerticalScrollBarEnabled(false); swipeContainer = new WebViewSwipeContainer(context) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { @@ -148,7 +157,6 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { } }; swipeContainer.addView(webViewContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - swipeContainer.setWebView(webViewContainer.getWebView()); swipeContainer.setScrollListener(() -> { parentAlert.updateLayout(this, true, 0); webViewContainer.invalidateViewPortHeight(); @@ -173,12 +181,16 @@ public void onAnimationEnd(Animator animation) { } }); animator.start(); + + requestEnableKeyboard(); } }); + + NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.didSetNewTheme); } public boolean canExpandByRequest() { - return System.currentTimeMillis() - lastSwipeTime > 1000 && !swipeContainer.isSwipeInProgress(); + return /* System.currentTimeMillis() - lastSwipeTime > 1000 && */ !swipeContainer.isSwipeInProgress(); } public void setMeasureOffsetY(int measureOffsetY) { @@ -214,24 +226,30 @@ public void onPanTransitionStart(boolean keyboardVisible, int contentHeight) { webViewScrollAnimator = null; } - int fromY = webViewContainer.getWebView().getScrollY(); - int toY = fromY + (oldh - contentHeight); - webViewScrollAnimator = ValueAnimator.ofInt(fromY, toY).setDuration(250); - webViewScrollAnimator.setInterpolator(ChatListItemAnimator.DEFAULT_INTERPOLATOR); - webViewScrollAnimator.addUpdateListener(animation -> { - int val = (int) animation.getAnimatedValue(); - webViewContainer.getWebView().setScrollY(val); - }); - webViewScrollAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - webViewContainer.getWebView().setScrollY(toY); - if (animation == webViewScrollAnimator) { - webViewScrollAnimator = null; + if (webViewContainer.getWebView() != null) { + int fromY = webViewContainer.getWebView().getScrollY(); + int toY = fromY + (oldh - contentHeight); + webViewScrollAnimator = ValueAnimator.ofInt(fromY, toY).setDuration(250); + webViewScrollAnimator.setInterpolator(ChatListItemAnimator.DEFAULT_INTERPOLATOR); + webViewScrollAnimator.addUpdateListener(animation -> { + int val = (int) animation.getAnimatedValue(); + if (webViewContainer.getWebView() != null) { + webViewContainer.getWebView().setScrollY(val); } - } - }); - webViewScrollAnimator.start(); + }); + webViewScrollAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (webViewContainer.getWebView() != null) { + webViewContainer.getWebView().setScrollY(toY); + } + if (animation == webViewScrollAnimator) { + webViewScrollAnimator = null; + } + } + }); + webViewScrollAnimator.start(); + } } @Override @@ -244,7 +262,9 @@ public void onPanTransitionEnd() { void onShow(ChatAttachAlert.AttachAlertLayout previousLayout) { parentAlert.actionBar.setTitle(UserObject.getUserName(MessagesController.getInstance(currentAccount).getUser(botId))); swipeContainer.setSwipeOffsetY(0); - webViewContainer.getWebView().scrollTo(0, 0); + if (webViewContainer.getWebView() != null) { + webViewContainer.getWebView().scrollTo(0, 0); + } if (parentAlert.getBaseFragment() != null) { webViewContainer.setParentActivity(parentAlert.getBaseFragment().getParentActivity()); } @@ -253,7 +273,9 @@ void onShow(ChatAttachAlert.AttachAlertLayout previousLayout) { @Override void onShown() { - requestEnableKeyboard(); + if (webViewContainer.isPageLoaded()) { + requestEnableKeyboard(); + } swipeContainer.setSwipeOffsetAnimationDisallowed(false); AndroidUtilities.runOnUIThread(() -> webViewContainer.restoreButtonData()); @@ -263,13 +285,13 @@ private void requestEnableKeyboard() { BaseFragment fragment = parentAlert.getBaseFragment(); if (fragment instanceof ChatActivity && ((ChatActivity) fragment).contentView.measureKeyboardHeight() > AndroidUtilities.dp(20)) { AndroidUtilities.hideKeyboard(parentAlert.baseFragment.getFragmentView()); - AndroidUtilities.runOnUIThread(this::requestEnableKeyboard, 150); + AndroidUtilities.runOnUIThread(this::requestEnableKeyboard, 250); return; } + parentAlert.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); setFocusable(true); parentAlert.setFocusable(true); - parentAlert.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); } @Override @@ -345,6 +367,7 @@ public void requestWebView(int currentAccount, long peerId, long botId, boolean TLRPC.TL_webViewResultUrl resultUrl = (TLRPC.TL_webViewResultUrl) response; queryId = resultUrl.query_id; webViewContainer.loadUrl(resultUrl.url); + swipeContainer.setWebView(webViewContainer.getWebView()); AndroidUtilities.runOnUIThread(pollRunnable); } @@ -356,6 +379,7 @@ public void requestWebView(int currentAccount, long peerId, long botId, boolean @Override void onDestroy() { NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.webViewResultSent); + NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.didSetNewTheme); ActionBarMenu menu = parentAlert.actionBar.createMenu(); otherItem.removeAllSubItems(); @@ -422,6 +446,11 @@ int getButtonsHideOffset() { return AndroidUtilities.dp(56); } + @Override + boolean onBackPressed() { + return webViewContainer.onBackPressed(); + } + @Override public void requestLayout() { if (ignoreLayout) { @@ -467,6 +496,8 @@ public void didReceivedNotification(int id, int account, Object... args) { needReload = true; parentAlert.dismiss(); } + } else if (id == NotificationCenter.didSetNewTheme) { + webViewContainer.updateFlickerBackgroundColor(getThemedColor(Theme.key_dialogBackground)); } } @@ -492,6 +523,8 @@ public static class WebViewSwipeContainer extends FrameLayout { private SpringAnimation scrollAnimator; + private int swipeStickyRange; + public WebViewSwipeContainer(@NonNull Context context) { super(context); @@ -502,10 +535,10 @@ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float ve if (isSwipeDisallowed) { return false; } - if (velocityY >= 700 && webView.getScrollY() == 0) { + if (velocityY >= 700 && (webView == null || webView.getScrollY() == 0)) { flingInProgress = true; - if (swipeOffsetY >= AndroidUtilities.dp(64)) { + if (swipeOffsetY >= swipeStickyRange) { if (delegate != null) { delegate.onDismiss(); } @@ -524,7 +557,7 @@ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float ve @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (!isScrolling && !isSwipeDisallowed) { - if (Math.abs(distanceY) >= touchSlop && Math.abs(distanceY) * 1.5f >= Math.abs(distanceX) && (swipeOffsetY != -offsetY + topActionBarOffsetY || distanceY < 0 && webView.getScrollY() == 0)) { + if (Math.abs(distanceY) >= touchSlop && Math.abs(distanceY) * 1.5f >= Math.abs(distanceX) && (swipeOffsetY != -offsetY + topActionBarOffsetY || webView == null || distanceY < 0 && webView.getScrollY() == 0)) { isScrolling = true; MotionEvent ev = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0); @@ -534,7 +567,7 @@ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float d ev.recycle(); return true; - } else if (webView.canScrollHorizontally(distanceX >= 0 ? 1 : -1)) { + } else if (webView != null && webView.canScrollHorizontally(distanceX >= 0 ? 1 : -1)) { isSwipeDisallowed = true; } } @@ -542,18 +575,20 @@ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float d if (distanceY < 0) { if (swipeOffsetY > -offsetY + topActionBarOffsetY) { swipeOffsetY -= distanceY; - } else { + } else if (webView != null) { float newWebScrollY = webView.getScrollY() + distanceY; webView.setScrollY((int) MathUtils.clamp(newWebScrollY, 0, Math.max(webView.getContentHeight(), webView.getHeight()) - topActionBarOffsetY)); if (newWebScrollY < 0) { swipeOffsetY -= newWebScrollY; } + } else { + swipeOffsetY -= distanceY; } } else { - swipeOffsetY = swipeOffsetY - distanceY; + swipeOffsetY -= distanceY; - if (swipeOffsetY < -offsetY + topActionBarOffsetY) { + if (webView != null && swipeOffsetY < -offsetY + topActionBarOffsetY) { float newWebScrollY = webView.getScrollY() - (swipeOffsetY + offsetY - topActionBarOffsetY); webView.setScrollY((int) MathUtils.clamp(newWebScrollY, 0, Math.max(webView.getContentHeight(), webView.getHeight()) - topActionBarOffsetY)); } @@ -567,6 +602,17 @@ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float d return true; } }); + updateStickyRange(); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + updateStickyRange(); + } + + private void updateStickyRange() { + swipeStickyRange = AndroidUtilities.dp(AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y ? 8 : 64); } @Override @@ -677,9 +723,9 @@ public boolean dispatchTouchEvent(MotionEvent ev) { if (flingInProgress) { flingInProgress = false; } else { - if (swipeOffsetY <= -AndroidUtilities.dp(64)) { + if (swipeOffsetY <= -swipeStickyRange) { stickTo(-offsetY + topActionBarOffsetY); - } else if (swipeOffsetY > -AndroidUtilities.dp(64) && swipeOffsetY <= AndroidUtilities.dp(64)) { + } else if (swipeOffsetY > -swipeStickyRange && swipeOffsetY <= swipeStickyRange) { stickTo(0); } else { if (delegate != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatNotificationsPopupWrapper.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatNotificationsPopupWrapper.java index ace816cb2dd..db1047ee6f1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatNotificationsPopupWrapper.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatNotificationsPopupWrapper.java @@ -69,19 +69,20 @@ public ChatNotificationsPopupWrapper(Context context, int currentAccount, PopupS ActionBarMenuSubItem item = ActionBarMenuItem.addItem(windowLayout, R.drawable.msg_mute_period, LocaleController.getString("MuteForPopup", R.string.MuteForPopup), false, resourcesProvider); item.setOnClickListener(view -> { dismiss(); - AlertsCreator.createMuteForPickerDialog(context, (notify, inMinutes) -> { + AlertsCreator.createMuteForPickerDialog(context, (notify, inSecond) -> { AndroidUtilities.runOnUIThread(() -> { - SharedPreferences sharedPreferences = MessagesController.getNotificationsSettings(currentAccount); - int time1 = sharedPreferences.getInt(LAST_SELECTED_TIME_KEY_1, 0); - int time2; - int timeInSeconds = inMinutes * 60; - time2 = time1; - time1 = timeInSeconds; - sharedPreferences.edit() - .putInt(LAST_SELECTED_TIME_KEY_1, time1) - .putInt(LAST_SELECTED_TIME_KEY_2, time2) - .apply(); - callback.muteFor(timeInSeconds); + if (inSecond != 0) { + SharedPreferences sharedPreferences = MessagesController.getNotificationsSettings(currentAccount); + int time1 = sharedPreferences.getInt(LAST_SELECTED_TIME_KEY_1, 0); + int time2; + time2 = time1; + time1 = inSecond; + sharedPreferences.edit() + .putInt(LAST_SELECTED_TIME_KEY_1, time1) + .putInt(LAST_SELECTED_TIME_KEY_2, time2) + .apply(); + } + callback.muteFor(inSecond); }, 16); }); }); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/CrossfadeDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/CrossfadeDrawable.java index 23fe1433b8a..a2a076971e2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/CrossfadeDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/CrossfadeDrawable.java @@ -74,7 +74,7 @@ public void setAlpha(int alpha) { @Override public void setColorFilter(ColorFilter colorFilter) { - + topDrawable.setColorFilter(colorFilter); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/MotionBackgroundDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/MotionBackgroundDrawable.java index 3dc69cb2a21..816fc0ca2ee 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/MotionBackgroundDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/MotionBackgroundDrawable.java @@ -324,7 +324,7 @@ public void setColors(int c1, int c2, int c3, int c4, int rotation, boolean inva } else { gradientDrawable = null; } - if (colors[0] == c1 || colors[1] == c2 || colors[2] == c3 || colors[3] == c4) { + if (colors[0] == c1 && colors[1] == c2 && colors[2] == c3 && colors[3] == c4) { return; } colors[0] = c1; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PopupSwipeBackLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PopupSwipeBackLayout.java index 00ff98eadd3..5be49f98938 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PopupSwipeBackLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PopupSwipeBackLayout.java @@ -41,6 +41,7 @@ public class PopupSwipeBackLayout extends FrameLayout { private boolean isSwipeDisallowed; private Paint overlayPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Paint foregroundPaint = new Paint(); + private int foregroundColor = 0; private Path mPath = new Path(); private RectF mRect = new RectF(); @@ -128,7 +129,11 @@ protected boolean drawChild(Canvas canvas, View child, long drawingTime) { int i = indexOfChild(child); int s = canvas.save(); if (i != 0) { - foregroundPaint.setColor(Theme.getColor(Theme.key_actionBarDefaultSubmenuBackground, resourcesProvider)); + if (foregroundColor == 0) { + foregroundPaint.setColor(Theme.getColor(Theme.key_actionBarDefaultSubmenuBackground, resourcesProvider)); + } else { + foregroundPaint.setColor(foregroundColor); + } canvas.drawRect(child.getX(), 0, child.getX() + child.getMeasuredWidth(), getMeasuredHeight(), foregroundPaint); } boolean b = super.drawChild(canvas, child, drawingTime); @@ -444,6 +449,10 @@ public void onAnimationStart(Animator animation) { foregroundAnimator = animator; } + public void setForegroundColor(int color) { + foregroundColor = color; + } + public interface OnSwipeBackProgressListener { void onSwipeBackProgress(PopupSwipeBackLayout layout, float toProgress, float progress); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SearchDownloadsContainer.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SearchDownloadsContainer.java index 3bc8b8befb6..99b5313b54b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SearchDownloadsContainer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SearchDownloadsContainer.java @@ -77,6 +77,8 @@ public class SearchDownloadsContainer extends FrameLayout implements Notificatio Runnable lastSearchRunnable; RecyclerItemsEnterAnimator itemsEnterAnimator; + boolean checkingFilesExist; + public SearchDownloadsContainer(BaseFragment fragment, int currentAccount) { super(fragment.getParentActivity()); this.parentFragment = fragment; @@ -177,7 +179,46 @@ public void onScrollStateChanged(RecyclerView recyclerView, int newState) { recyclerListView.setEmptyView(emptyView); FileLoader.getInstance(currentAccount).getCurrentLoadingFiles(currentLoadingFiles); - update(false); + } + + private void checkFilesExist() { + if (checkingFilesExist) { + return; + } + checkingFilesExist = true; + Utilities.searchQueue.postRunnable(() -> { + ArrayList currentLoadingFiles = new ArrayList<>(); + ArrayList recentLoadingFiles = new ArrayList<>(); + + ArrayList moveToRecent = new ArrayList<>(); + ArrayList removeFromRecent = new ArrayList<>(); + + FileLoader.getInstance(currentAccount).getCurrentLoadingFiles(currentLoadingFiles); + FileLoader.getInstance(currentAccount).getRecentLoadingFiles(recentLoadingFiles); + + for (int i = 0; i < currentLoadingFiles.size(); i++) { + if (FileLoader.getPathToMessage(currentLoadingFiles.get(i).messageOwner).exists()) { + moveToRecent.add(currentLoadingFiles.get(i)); + } + } + + for (int i = 0; i < recentLoadingFiles.size(); i++) { + if (!FileLoader.getPathToMessage(recentLoadingFiles.get(i).messageOwner).exists()) { + removeFromRecent.add(recentLoadingFiles.get(i)); + } + } + + AndroidUtilities.runOnUIThread(() -> { + for (int i = 0; i < moveToRecent.size(); i++) { + DownloadController.getInstance(currentAccount).onDownloadComplete(moveToRecent.get(i)); + } + if (!removeFromRecent.isEmpty()) { + DownloadController.getInstance(currentAccount).deleteRecentFiles(removeFromRecent); + } + checkingFilesExist = false; + update(true); + }); + }); } public void update(boolean animated) { @@ -185,6 +226,10 @@ public void update(boolean animated) { if (rowCount == 0) { itemsEnterAnimator.showItemsAnimated(0); } + if (checkingFilesExist) { + currentLoadingFilesTmp.clear(); + recentLoadingFilesTmp.clear(); + } FileLoader.getInstance(currentAccount).getCurrentLoadingFiles(currentLoadingFilesTmp); FileLoader.getInstance(currentAccount).getRecentLoadingFiles(recentLoadingFilesTmp); @@ -583,6 +628,8 @@ protected void onAttachedToWindow() { if (getVisibility() == View.VISIBLE) { DownloadController.getInstance(currentAccount).clearUnviewedDownloads(); } + checkFilesExist(); + update(false); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SharedMediaLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SharedMediaLayout.java index 2f001df6e5a..56c4a4ea617 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SharedMediaLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SharedMediaLayout.java @@ -6581,6 +6581,18 @@ public int getNextMediaColumnsCount(int mediaColumnsCount, boolean up) { return newColumnsCount; } + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + if (child == fragmentContextView) { + canvas.save(); + canvas.clipRect(0, mediaPages[0].getTop(), child.getMeasuredWidth(),mediaPages[0].getTop() + child.getMeasuredHeight() + AndroidUtilities.dp(12)); + boolean b = super.drawChild(canvas, child, drawingTime); + canvas.restore(); + return b; + } + return super.drawChild(canvas, child, drawingTime); + } + private class ScrollSlidingTextTabStripInner extends ScrollSlidingTextTabStrip { protected Paint backgroundPaint; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/TimerDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/TimerDrawable.java index e2f95ab6671..e866b7f63c2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/TimerDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/TimerDrawable.java @@ -150,8 +150,9 @@ public void draw(Canvas canvas) { currentTtlIcon.setColorFilter(new PorterDuffColorFilter(iconColor, PorterDuff.Mode.MULTIPLY)); } } - AndroidUtilities.rectTmp2.set(getBounds()); - AndroidUtilities.rectTmp2.inset(AndroidUtilities.dp(1f), AndroidUtilities.dp(1f)); + AndroidUtilities.rectTmp2.set((int) (getBounds().centerX() - AndroidUtilities.dp(10.5f)), (int) (getBounds().centerY() - AndroidUtilities.dp(10.5f)), + (int) (getBounds().centerX() - AndroidUtilities.dp(10.5f)) + currentTtlIcon.getIntrinsicWidth(), + (int) (getBounds().centerY() - AndroidUtilities.dp(10.5f)) + currentTtlIcon.getIntrinsicHeight()); currentTtlIcon.setBounds(AndroidUtilities.rectTmp2); currentTtlIcon.draw(canvas); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DownloadProgressIcon.java b/TMessagesProj/src/main/java/org/telegram/ui/DownloadProgressIcon.java index 4a0e9c576f0..000b5611a2e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/DownloadProgressIcon.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/DownloadProgressIcon.java @@ -3,6 +3,8 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.view.View; import org.telegram.messenger.AndroidUtilities; @@ -34,6 +36,7 @@ public class DownloadProgressIcon extends View implements NotificationCenter.Not RLottieDrawable downloadCompleteDrawable; boolean showCompletedIcon; boolean hasUnviewedDownloads; + int currentColor; public DownloadProgressIcon(int currentAccount, Context context) { super(context); @@ -65,9 +68,13 @@ protected void onDraw(Canvas canvas) { return; } - paint.setColor(Theme.getColor(Theme.key_actionBarDefaultIcon)); - paint2.setColor(Theme.getColor(Theme.key_actionBarDefaultIcon)); - paint2.setAlpha(100); + if (currentColor != Theme.getColor(Theme.key_actionBarDefaultIcon)) { + currentColor = Theme.getColor(Theme.key_actionBarDefaultIcon); + paint.setColor(Theme.getColor(Theme.key_actionBarDefaultIcon)); + paint2.setColor(Theme.getColor(Theme.key_actionBarDefaultIcon)); + downloadImageReceiver.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_actionBarDefaultIcon), PorterDuff.Mode.MULTIPLY)); + paint2.setAlpha(100); + } if (currentProgress != progress) { currentProgress += progressDt; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/IntroActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/IntroActivity.java index 3f1a53904a6..ee28e895c4d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/IntroActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/IntroActivity.java @@ -242,7 +242,11 @@ public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int hei Intro.setPage(currentViewPagerPage); Intro.setDate(time); Intro.onDrawFrame(0); - eglThread.egl10.eglSwapBuffers(eglThread.eglDisplay, eglThread.eglSurface); + if (eglThread != null && eglThread.isAlive() && eglThread.eglDisplay != null && eglThread.eglSurface != null) { + try { + eglThread.egl10.eglSwapBuffers(eglThread.eglDisplay, eglThread.eglSurface); + } catch (Exception ignored) {} // If display or surface already destroyed + } }); eglThread.postRunnable(eglThread.drawRunnable); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java index 04fe5a85839..f9f0e3f5ae1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java @@ -275,12 +275,12 @@ protected void onCreate(Bundle savedInstanceState) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { try { setTaskDescription(new ActivityManager.TaskDescription(null, null, Theme.getColor(Theme.key_actionBarDefault) | 0xff000000)); - } catch (Exception ignore) { + } catch (Throwable ignore) { } try { getWindow().setNavigationBarColor(0xff000000); - } catch (Exception ignore) { + } catch (Throwable ignore) { } } @@ -2941,6 +2941,8 @@ private void runLinkRequest(final int intentAccount, .setNegativeButton(LocaleController.getString(R.string.Cancel), null) .show(); } + } else { + BulletinFactory.of(mainFragmentsStack.get(mainFragmentsStack.size() - 1)).createErrorBulletin(LocaleController.getString(R.string.BotCantAddToAttachMenu)).show(); } })); } else { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java index c8fbf883b67..2bb9eb6f29a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java @@ -4749,8 +4749,8 @@ public LoginActivityNewPasswordView(Context context, int stage) { codeField[a].setPadding(padding, padding, padding, padding); if (stage == 0) { codeField[a].setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + codeField[a].setTransformationMethod(PasswordTransformationMethod.getInstance()); } - codeField[a].setTransformationMethod(PasswordTransformationMethod.getInstance()); codeField[a].setTypeface(Typeface.DEFAULT); codeField[a].setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/NotificationsCustomSettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/NotificationsCustomSettingsActivity.java index d9f09fe2c45..eb7bd5b74af 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/NotificationsCustomSettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/NotificationsCustomSettingsActivity.java @@ -37,6 +37,7 @@ import org.telegram.messenger.ChatObject; import org.telegram.messenger.ContactsController; import org.telegram.messenger.DialogObject; +import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.NotificationCenter; @@ -1247,7 +1248,11 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { } if (documentId != 0) { TLRPC.Document document = getMediaDataController().ringtoneDataStore.getDocument(documentId); - value = NotificationsSoundActivity.trimTitle(document, document.file_name_fixed); + if (document == null) { + value = LocaleController.getString("CustomSound", R.string.CustomSound); + } else { + value = NotificationsSoundActivity.trimTitle(document, FileLoader.getDocumentFileName(document)); + } } else if (value.equals("NoSound")) { value = LocaleController.getString("NoSound", R.string.NoSound); } else if (value.equals("Default")) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSoundActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSoundActivity.java index 4678b8d6e5d..b0d42f4f6ec 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSoundActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSoundActivity.java @@ -1,6 +1,5 @@ package org.telegram.ui; -import android.app.NotificationManager; import android.content.ClipData; import android.content.Context; import android.content.DialogInterface; @@ -284,8 +283,10 @@ private void deleteSelectedMessages() { avatarContainer.setTitle(chatLocal.title); } else { TLRPC.User user = getMessagesController().getUser(dialogId); - avatarContainer.setUserAvatar(user); - avatarContainer.setTitle(ContactsController.formatName(user.first_name, user.last_name)); + if (user != null) { + avatarContainer.setUserAvatar(user); + avatarContainer.setTitle(ContactsController.formatName(user.first_name, user.last_name)); + } } avatarContainer.setSubtitle(LocaleController.getString("NotificationsSound", R.string.NotificationsSound)); } @@ -323,7 +324,7 @@ private void deleteSelectedMessages() { } if (view instanceof ToneCell) { ToneCell cell = (ToneCell) view; - if (actionBar.isActionModeShowed()) { + if (actionBar.isActionModeShowed() || cell.tone == null) { checkSelection(cell.tone); return; } @@ -357,14 +358,13 @@ private void deleteSelectedMessages() { lastPlayedRingtone = r; r.play(); } else { - getFileLoader().loadFile(cell.tone.document, null, 2, 0); + getFileLoader().loadFile(cell.tone.document, cell.tone.document, 2, 0); } } startSelectedTone = null; selectedTone = cell.tone; selectedToneChanged = true; adapter.notifyItemRangeChanged(0, adapter.getItemCount()); - checkDisabledBySystem(); } }); @@ -415,6 +415,9 @@ private void updateActionMode() { private void loadTones() { getMediaDataController().ringtoneDataStore.loadUserRingtones(); + serverTones.clear(); + systemTones.clear(); + for (int i = 0; i < getMediaDataController().ringtoneDataStore.userRingtones.size(); i++) { RingtoneDataStore.CachedTone cachedTone = getMediaDataController().ringtoneDataStore.userRingtones.get(i); Tone tone = new Tone(); @@ -439,7 +442,7 @@ private void loadTones() { manager.setType(RingtoneManager.TYPE_NOTIFICATION); Cursor cursor = manager.getCursor(); - systemTones.clear(); + Tone noSoundTone = new Tone(); @@ -942,15 +945,4 @@ public Uri getUriForShare(int currentAccount) { return null; } } - - private void checkDisabledBySystem() { - NotificationManager manager = (NotificationManager) ApplicationLoader.applicationContext.getSystemService(Context.NOTIFICATION_SERVICE); - boolean notificationsEnabled = true; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - notificationsEnabled = manager.areNotificationsEnabled(); - } -// if (!notificationsEnabled) { -// BulletinFactory.of(this).createErrorBulletin(LocaleController.getString()).show(); -// } - } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java index a5a8eaa6046..dfb03103937 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java @@ -307,16 +307,15 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private TextView docNameTextView; private TextView docInfoTextView; private ActionBarMenuItem menuItem; - private ActionBarMenuItem menuItemSpeed; private ActionBarMenuSubItem allMediaItem; private ActionBarMenuSubItem speedItem; - private ActionBarMenuSubItem[] speedItems = new ActionBarMenuSubItem[5]; - private View speedGap; + private ActionBarPopupWindow.GapView speedGap; private ActionBarMenuItem sendItem; private ActionBarMenuItem pipItem; private ActionBarMenuItem masksItem; private ActionBarMenuItem shareItem; private LinearLayout itemsLayout; + ChooseSpeedLayout chooseSpeedLayout; private Map actionBarItemsVisibility = new HashMap<>(3); private LinearLayout bottomButtonsLayout; private ImageView shareButton; @@ -412,7 +411,7 @@ public void run() { @Override public void run() { if (videoPlayerControlVisible && isPlaying && !ApplicationLoader.mainInterfacePaused) { - if (menuItem != null && menuItem.isSubMenuShowing() || menuItemSpeed != null && menuItemSpeed.isSubMenuShowing()) { + if (menuItem != null && menuItem.isSubMenuShowing()) { return; } if (captionScrollView != null && captionScrollView.getScrollY() != 0) { @@ -1204,12 +1203,6 @@ public interface PageBlocksAdapter { private final static int gallery_menu_edit_avatar = 17; private final static int gallery_menu_share2 = 18; private final static int gallery_menu_speed = 19; - private final static int gallery_menu_gap = 20; - private final static int gallery_menu_speed_veryslow = 21; - private final static int gallery_menu_speed_slow = 22; - private final static int gallery_menu_speed_normal = 23; - private final static int gallery_menu_speed_fast = 24; - private final static int gallery_menu_speed_veryfast = 25; private static DecelerateInterpolator decelerateInterpolator; private static Paint progressPaint; @@ -4189,20 +4182,6 @@ public void onItemClick(int id) { } } else if (id == gallery_menu_share || id == gallery_menu_share2) { onSharePressed(); - } else if (id == gallery_menu_speed) { - menuItemSpeed.setVisibility(View.VISIBLE); - menuItemSpeed.toggleSubMenu(); - for (int a = 0; a < speedItems.length; a++) { - if (a == 0 && Math.abs(currentVideoSpeed - 0.25f) < 0.001f || - a == 1 && Math.abs(currentVideoSpeed - 0.5f) < 0.001f || - a == 2 && Math.abs(currentVideoSpeed - 1.0f) < 0.001f || - a == 3 && Math.abs(currentVideoSpeed - 1.5f) < 0.001f || - a == 4 && Math.abs(currentVideoSpeed - 2.0f) < 0.001f) { - speedItems[a].setColors(0xff6BB6F9, 0xff6BB6F9); - } else { - speedItems[a].setColors(0xfffafafa, 0xfffafafa); - } - } } else if (id == gallery_menu_openin) { try { if (isEmbedVideo) { @@ -4403,7 +4382,6 @@ public void dismiss() { @Override public boolean canOpenMenu() { - menuItemSpeed.setVisibility(View.INVISIBLE); if (currentMessageObject != null || currentSecureDocument != null) { return true; } else if (currentFileLocationVideo != null) { @@ -4428,62 +4406,41 @@ public boolean canOpenMenu() { shareItem.setContentDescription(LocaleController.getString("ShareFile", R.string.ShareFile)); menuItem = menu.addItem(0, R.drawable.ic_ab_other); - menuItemSpeed = new ActionBarMenuItem(parentActivity, null, 0, 0, resourcesProvider); - menuItemSpeed.setDelegate(id -> { - if (id >= gallery_menu_speed_veryslow && id <= gallery_menu_speed_veryfast) { - switch (id) { - case gallery_menu_speed_veryslow: - currentVideoSpeed = 0.25f; - break; - case gallery_menu_speed_slow: - currentVideoSpeed = 0.5f; - break; - case gallery_menu_speed_normal: - currentVideoSpeed = 1.0f; - break; - case gallery_menu_speed_fast: - currentVideoSpeed = 1.5f; - break; - case gallery_menu_speed_veryfast: - currentVideoSpeed = 2.0f; - break; - } - if (currentMessageObject != null) { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("playback_speed", Activity.MODE_PRIVATE); - if (Math.abs(currentVideoSpeed - 1.0f) < 0.001f) { - preferences.edit().remove("speed" + currentMessageObject.getDialogId() + "_" + currentMessageObject.getId()).commit(); - } else { - preferences.edit().putFloat("speed" + currentMessageObject.getDialogId() + "_" + currentMessageObject.getId(), currentVideoSpeed).commit(); + + menuItem.getPopupLayout().swipeBackGravityRight = true; + chooseSpeedLayout = new ChooseSpeedLayout(activityContext, menuItem.getPopupLayout().getSwipeBack(), new ChooseSpeedLayout.Callback() { + + @Override + public void onSpeedSelected(float speed) { + menuItem.toggleSubMenu(); + if (speed != currentVideoSpeed) { + currentVideoSpeed = speed; + if (currentMessageObject != null) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("playback_speed", Activity.MODE_PRIVATE); + if (Math.abs(currentVideoSpeed - 1.0f) < 0.001f) { + preferences.edit().remove("speed" + currentMessageObject.getDialogId() + "_" + currentMessageObject.getId()).commit(); + } else { + preferences.edit().putFloat("speed" + currentMessageObject.getDialogId() + "_" + currentMessageObject.getId(), currentVideoSpeed).commit(); + } } + if (videoPlayer != null) { + videoPlayer.setPlaybackSpeed(currentVideoSpeed); + } + if (photoViewerWebView != null) { + photoViewerWebView.setPlaybackSpeed(currentVideoSpeed); + } + setMenuItemIcon(); } - if (videoPlayer != null) { - videoPlayer.setPlaybackSpeed(currentVideoSpeed); - } - if (photoViewerWebView != null) { - photoViewerWebView.setPlaybackSpeed(currentVideoSpeed); - } - setMenuItemIcon(); - menuItemSpeed.setVisibility(View.INVISIBLE); } }); - menuItem.addView(menuItemSpeed); - menuItemSpeed.setVisibility(View.INVISIBLE); - - speedItem = menuItem.addSubItem(gallery_menu_speed, R.drawable.msg_speed, null, LocaleController.getString("Speed", R.string.Speed), true, false); + speedItem = menuItem.addSwipeBackItem(R.drawable.msg_speed, null, LocaleController.getString("Speed", R.string.Speed), chooseSpeedLayout.speedSwipeBackLayout); + menuItem.getPopupLayout().setSwipeBackForegroundColor(0xff222222); speedItem.setSubtext(LocaleController.getString("SpeedNormal", R.string.SpeedNormal)); - speedItem.setItemHeight(56); - speedItem.setTag(R.id.width_tag, 240); speedItem.setColors(0xfffafafa, 0xfffafafa); - speedItem.setRightIcon(R.drawable.msg_arrowright); - speedGap = menuItem.addGap(gallery_menu_gap); + speedGap = menuItem.addColoredGap(); + speedGap.setColor(0xff181818); menuItem.getPopupLayout().setFitItems(true); - speedItems[0] = menuItemSpeed.addSubItem(gallery_menu_speed_veryslow, R.drawable.msg_speed_0_2, LocaleController.getString("SpeedVerySlow", R.string.SpeedVerySlow)).setColors(0xfffafafa, 0xfffafafa); - speedItems[1] = menuItemSpeed.addSubItem(gallery_menu_speed_slow, R.drawable.msg_speed_0_5, LocaleController.getString("SpeedSlow", R.string.SpeedSlow)).setColors(0xfffafafa, 0xfffafafa); - speedItems[2] = menuItemSpeed.addSubItem(gallery_menu_speed_normal, R.drawable.msg_speed_1, LocaleController.getString("SpeedNormal", R.string.SpeedNormal)).setColors(0xfffafafa, 0xfffafafa); - speedItems[3] = menuItemSpeed.addSubItem(gallery_menu_speed_fast, R.drawable.msg_speed_1_5, LocaleController.getString("SpeedFast", R.string.SpeedFast)).setColors(0xfffafafa, 0xfffafafa); - speedItems[4] = menuItemSpeed.addSubItem(gallery_menu_speed_veryfast, R.drawable.msg_speed_2, LocaleController.getString("SpeedVeryFast", R.string.SpeedVeryFast)).setColors(0xfffafafa, 0xfffafafa); - menuItem.addSubItem(gallery_menu_openin, R.drawable.msg_openin, LocaleController.getString("OpenInExternalApp", R.string.OpenInExternalApp)).setColors(0xfffafafa, 0xfffafafa); menuItem.setContentDescription(LocaleController.getString("AccDescrMoreOptions", R.string.AccDescrMoreOptions)); allMediaItem = menuItem.addSubItem(gallery_menu_showall, R.drawable.msg_media, LocaleController.getString("ShowAllMedia", R.string.ShowAllMedia)); @@ -4498,7 +4455,6 @@ public boolean canOpenMenu() { menuItem.addSubItem(gallery_menu_delete, R.drawable.msg_delete, LocaleController.getString("Delete", R.string.Delete)).setColors(0xfffafafa, 0xfffafafa); menuItem.addSubItem(gallery_menu_cancel_loading, R.drawable.msg_cancel, LocaleController.getString("StopDownload", R.string.StopDownload)).setColors(0xfffafafa, 0xfffafafa); menuItem.redrawPopup(0xf9222222); - menuItemSpeed.redrawPopup(0xf9222222); setMenuItemIcon(); menuItem.setSubMenuDelegate(new ActionBarMenuItem.ActionBarSubMenuItemDelegate() { @@ -6161,6 +6117,7 @@ private void setMenuItemIcon() { menuItem.setIcon(R.drawable.msg_more_2); speedItem.setSubtext(LocaleController.getString("SpeedVeryFast", R.string.SpeedVeryFast)); } + chooseSpeedLayout.update(currentVideoSpeed); } private boolean checkInlinePermissions() { @@ -9976,7 +9933,7 @@ private void onPhotoShow(final MessageObject messageObject, final TLRPC.FileLoca menuItem.hideSubItem(gallery_menu_edit_avatar); menuItem.hideSubItem(gallery_menu_set_as_main); menuItem.hideSubItem(gallery_menu_delete); - menuItem.hideSubItem(gallery_menu_speed); + speedItem.setVisibility(View.GONE); speedGap.setVisibility(View.GONE); actionBar.setTranslationY(0); @@ -10398,6 +10355,7 @@ private void setIsAboutToSwitchToIndex(int index, boolean init, boolean animated } menuItem.checkHideMenuItem(); } else { + speedItem.setVisibility(View.GONE); speedGap.setVisibility(View.GONE); menuItem.hideSubItem(gallery_menu_openin); menuItem.checkHideMenuItem(); @@ -10989,10 +10947,11 @@ private void setImageIndex(int index, boolean init, boolean animateCaption) { } } if (isVideo || isEmbedVideo) { + speedItem.setVisibility(View.VISIBLE); speedGap.setVisibility(View.VISIBLE); menuItem.showSubItem(gallery_menu_speed); } else { - menuItem.hideSubItem(gallery_menu_speed); + speedItem.setVisibility(View.GONE); speedGap.setVisibility(View.GONE); menuItem.checkHideMenuItem(); } @@ -11453,7 +11412,7 @@ public void onAnimationEnd(Animator animation) { Theme.createChatResources(null, true); CharSequence str; if (messageObject != null && !messageObject.messageOwner.entities.isEmpty()) { - Spannable spannableString = SpannableString.valueOf(caption); + Spannable spannableString = new SpannableString(caption); messageObject.addEntitiesToText(spannableString, true, false); if (messageObject.isVideo()) { MessageObject.addUrlsByPattern(messageObject.isOutOwner(), spannableString, false, 3, messageObject.getDuration(), false); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java index cb150e18aff..6214f794646 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java @@ -60,6 +60,7 @@ import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.webkit.WebStorage; +import android.webkit.WebView; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; @@ -1525,6 +1526,14 @@ public boolean onTouchEvent(MotionEvent event) { } return super.onTouchEvent(event); } + + @Override + public void setItemsColor(int color, boolean isActionMode) { + super.setItemsColor(color, isActionMode); + if (!isActionMode && ttlIconView != null) { + ttlIconView.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + } }; actionBar.setBackgroundColor(Color.TRANSPARENT); actionBar.setItemsBackgroundColor(AvatarDrawable.getButtonColorForId(userId != 0 || ChatObject.isChannel(chatId, currentAccount) && !currentChat.megagroup ? 5 : chatId), false); @@ -2432,6 +2441,7 @@ public void onTextChanged(EditText editText) { editItem.setContentDescription(LocaleController.getString("Edit", R.string.Edit)); otherItem = menu.addItem(10, R.drawable.ic_ab_other); ttlIconView = new ImageView(context); + ttlIconView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_actionBarDefaultIcon), PorterDuff.Mode.MULTIPLY)); AndroidUtilities.updateViewVisibilityAnimated(ttlIconView, false, 0.8f, false); ttlIconView.setImageResource(R.drawable.msg_mini_autodelete_timer); otherItem.addView(ttlIconView, LayoutHelper.createFrame(12, 12, Gravity.CENTER_VERTICAL | Gravity.LEFT, 8, 2, 0, 0)); @@ -2729,12 +2739,21 @@ public void toggleSound() { @Override public void muteFor(int timeInSeconds) { - getNotificationsController().muteUntil(did, timeInSeconds); - if (BulletinFactory.canShowBulletin(ProfileActivity.this)) { - BulletinFactory.createMuteBulletin(ProfileActivity.this, NotificationsController.SETTING_MUTE_CUSTOM, timeInSeconds, getResourceProvider()).show(); - } - if (notificationsRow >= 0) { - listAdapter.notifyItemChanged(notificationsRow); + if (timeInSeconds == 0) { + if (getMessagesController().isDialogMuted(did)) { + toggleMute(); + } + if (BulletinFactory.canShowBulletin(ProfileActivity.this)) { + BulletinFactory.createMuteBulletin(ProfileActivity.this, NotificationsController.SETTING_MUTE_UNMUTE, timeInSeconds, getResourceProvider()).show(); + } + } else { + getNotificationsController().muteUntil(did, timeInSeconds); + if (BulletinFactory.canShowBulletin(ProfileActivity.this)) { + BulletinFactory.createMuteBulletin(ProfileActivity.this, NotificationsController.SETTING_MUTE_CUSTOM, timeInSeconds, getResourceProvider()).show(); + } + if (notificationsRow >= 0) { + listAdapter.notifyItemChanged(notificationsRow); + } } } @@ -2919,7 +2938,8 @@ public boolean onItemClick(View view, int position) { BuildVars.DEBUG_PRIVATE_VERSION ? "Clean app update" : null, BuildVars.DEBUG_PRIVATE_VERSION ? "Reset suggestions" : null, BuildVars.DEBUG_PRIVATE_VERSION ? LocaleController.getString(SharedConfig.forceRtmpStream ? R.string.DebugMenuDisableForceRtmpStreamFlag : R.string.DebugMenuEnableForceRtmpStreamFlag) : null, - BuildVars.DEBUG_PRIVATE_VERSION ? LocaleController.getString(R.string.DebugMenuClearWebViewCache) : null + BuildVars.DEBUG_PRIVATE_VERSION ? LocaleController.getString(R.string.DebugMenuClearWebViewCache) : null, + Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT ? LocaleController.getString(R.string.DebugMenuEnableWebViewDebug) : null }; builder.setItems(items, (dialog, which) -> { if (which == 0) { @@ -2996,6 +3016,11 @@ public boolean onItemClick(View view, int position) { ApplicationLoader.applicationContext.deleteDatabase("webview.db"); ApplicationLoader.applicationContext.deleteDatabase("webviewCache.db"); WebStorage.getInstance().deleteAllData(); + } else if (which == 19) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + WebView.setWebContentsDebuggingEnabled(true); + Toast.makeText(getParentActivity(), LocaleController.getString(R.string.DebugMenuWebViewDebugEnabled), Toast.LENGTH_SHORT).show(); + } } }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java index 887bd06716a..6947ba75edc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java @@ -504,7 +504,11 @@ public void onActivityResultFragment(int requestCode, int resultCode, Intent dat @Override public void didReceivedNotification(int id, int account, Object... args) { if (id == NotificationCenter.notificationsSettingsUpdated) { - adapter.notifyDataSetChanged(); + try { + adapter.notifyDataSetChanged(); + } catch (Exception e) { + + } } } @@ -520,8 +524,13 @@ private void checkRowsEnabled() { RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.getChildViewHolder(child); int type = holder.getItemViewType(); int position = holder.getAdapterPosition(); - if (position != customRow && position != enableRow && type != 0) { + if (position != customRow && position != enableRow) { switch (type) { + case 0: { + HeaderCell textCell = (HeaderCell) holder.itemView; + textCell.setEnabled(customEnabled && notificationsEnabled, animators); + break; + } case 1: { TextSettingsCell textCell = (TextSettingsCell) holder.itemView; textCell.setEnabled(customEnabled && notificationsEnabled, animators); @@ -681,7 +690,11 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { long documentId = preferences.getLong("sound_document_id_" + dialogId, 0); if (documentId != 0) { TLRPC.Document document = getMediaDataController().ringtoneDataStore.getDocument(documentId); - value = NotificationsSoundActivity.trimTitle(document, document.file_name_fixed); + if (document == null) { + value = LocaleController.getString("CustomSound", R.string.CustomSound); + } else { + value = NotificationsSoundActivity.trimTitle(document, document.file_name_fixed); + } } else if (value.equals("NoSound")) { value = LocaleController.getString("NoSound", R.string.NoSound); } else if (value.equals("Default")) { @@ -840,35 +853,38 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { @Override public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { - if (holder.getItemViewType() != 0) { - switch (holder.getItemViewType()) { - case 1: { - TextSettingsCell textCell = (TextSettingsCell) holder.itemView; - textCell.setEnabled(customEnabled && notificationsEnabled, null); - break; - } - case 2: { - TextInfoPrivacyCell textCell = (TextInfoPrivacyCell) holder.itemView; - textCell.setEnabled(customEnabled && notificationsEnabled, null); - break; - } - case 3: { - TextColorCell textCell = (TextColorCell) holder.itemView; - textCell.setEnabled(customEnabled && notificationsEnabled, null); - break; - } - case 4: { - RadioCell radioCell = (RadioCell) holder.itemView; - radioCell.setEnabled(customEnabled && notificationsEnabled, null); - break; - } - case 8: { - TextCheckCell checkCell = (TextCheckCell) holder.itemView; - if (holder.getAdapterPosition() == previewRow) { - checkCell.setEnabled(customEnabled && notificationsEnabled, null); - } else { - checkCell.setEnabled(true, null); - } + switch (holder.getItemViewType()) { + case 0: { + HeaderCell textCell = (HeaderCell) holder.itemView; + textCell.setEnabled(customEnabled && notificationsEnabled, null); + break; + } + case 1: { + TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + textCell.setEnabled(customEnabled && notificationsEnabled, null); + break; + } + case 2: { + TextInfoPrivacyCell textCell = (TextInfoPrivacyCell) holder.itemView; + textCell.setEnabled(customEnabled && notificationsEnabled, null); + break; + } + case 3: { + TextColorCell textCell = (TextColorCell) holder.itemView; + textCell.setEnabled(customEnabled && notificationsEnabled, null); + break; + } + case 4: { + RadioCell radioCell = (RadioCell) holder.itemView; + radioCell.setEnabled(customEnabled && notificationsEnabled, null); + break; + } + case 8: { + TextCheckCell checkCell = (TextCheckCell) holder.itemView; + if (holder.getAdapterPosition() == previewRow) { + checkCell.setEnabled(customEnabled && notificationsEnabled, null); + } else { + checkCell.setEnabled(true, null); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/SessionsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/SessionsActivity.java index b92af83b827..d05c111c524 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/SessionsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/SessionsActivity.java @@ -904,11 +904,6 @@ public void onClick(View view) { colors[6] = 0x212020; colors[7] = Theme.getColor(Theme.key_windowBackgroundWhite); -// imageView.replaceColors(colors); -// imageView.setAnimation(R.raw.qr_login, 230, 230, colors); -// imageView.setScaleType(ImageView.ScaleType.CENTER); -// imageView.playAnimation(); - textView = new TextView(context); addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 36, 152, 36, 0)); textView.setGravity(Gravity.CENTER_HORIZONTAL); diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_mini_autodelete.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_mini_autodelete.png index 4628a69ed5c77e081c58cece44fdec7211db64a7..407311479d49c3a7baeb6d17bf5d77bd38a4061c 100644 GIT binary patch delta 841 zcmV-P1GfD01%d}5iBL{Q4GJ0x0000DNk~Le0000W0000W2nGNE0CReJ^pPP*e;}X( z00aO40096103ZMW0069pV)Fn10_;gdK~z`?)t60(O<@?v=g!bDqox^}+gON@k1}jn zDmIK#jD>ve*w{(zM`@U>jM40-P!=}GN{yMM-Rss&*{DAo{xLD zGc`~B?(@FS^M9WAeB5*1dm9>Ne~XpNp8GM5r*Ig#97G>fMgR~ zgC}qV=2$Mbr?>#8;4Ap10~v>_&=2daIpcPi0<*K0%jKzVf$P@A`BA)XL2Ftw@(18O z$Oh81u{nygxbCspYX&{B;)EdjX6S}Of+Rky0`Sj#jQOH?b-pNlHb8r7e-#4Vb2BYI zH?^R~Bz<vHBgMtb#t*--3plhvbUmg}BJ8`7lNk1^M+)srK8 z?B{|pLA_-3c*#bq1T_Yz?S#wlE?PpY15U%nXik?%kC$0*l^`QPq0aCqd~!|pWRz5< zDH?Hwx(cb!d1YmAf7elSf8W*14M#PZbBZ3h24k6GCqS?F#X0!pYUN(Q{InkASHf;+ z3Uy4hvB<83!IS~IdGJ&Pp8aIpol>S--3a!U+%IjTD=0eu!TBt=6+yTMIPr->p)iqA zsP{^j)2x=a1NiQO3!Kk#Zz6(QEIt^CB~7PXVlotPSzdzdW*E#Ef1o`K_h1;>GUQkB%g!q3oNYb z$~2?rA<(0GKDIZBU0`-GR`icSnl6QJuK9Rk8wCC<^LtH)ycKE^d3r6djQJW^9}Jyvo1O3(esmov8Cj=4C&-d8bd$;c1 zdF(pyxi@d#{J7nHJ8!+ZDpDvEf9i1=gfVylozM)kp6C5!OfD2@fEoA$Q8I5;Se%C6 z@EKan{-BY7A;^I%XG~Th3zML$D+>Asn0z;T)sct{{P!bo8P}n=QXuQC@D0vkrjl>4 zhT}Uh$)ofKi)kt()~&NEIzVqrlArjOum-Qn@?g+rI0Z=_5*1Q%ax8*Gf6uxpD9A*+ zPNHX&%B>^MYq$Z)+hQ@4da&)i9{htE6yl%29E`eQ%s;|DJhx(cRwfm9otwyX0u}!Y zuA$HDks}TZAjw5K#2E2^3d^8dFZmSXS~BCbQ*`BpEmffp5pU7Js@ zRJV4`5lFI8&aMp~1#ZjPe-Rb!aQ$4JwF%$%P)+ukzxdB}iqS`~yPKfEJVQgii1C-% z@^`7&U*FM;{xov93R#yZv>UR8r25t4dKk;IX;c>9(q*FcH>#t zG4(mc61`_N{UM4a?jUgm$^8Mgpgr(m@)*6bpS;!^$K7I&(e*beeLsJcOSVu|kV6374tNNctKYHXPA++SCb<$kV-=IGv(1@YNM-U&v)!MrRPdz<6SFOHgzLc0eY7*K&39G#v%|K44g0g1%Z7 zKwTT$gQw6@%bZ{hlY7B74u%!`#NP+d58BmQ_D&|I8ndyz`Ao;C=pT&<@UMo6%l`$v zT4Uo_AD%Kj*0%A)MfrUV5b6SbPWflYVa4Kq%_L5w6Lj^GUpn3@YAW*T)!-DzJ)n*2 hILxuhEaMNR{R7=W{_RdZ-%$Vn002ovPDHLkV1i}jTFU?c delta 579 zcmV-J0=)gH2L1#giBL{Q4GJ0x0000DNk~Le0000R0000R2nGNE0FBi2Rgob`e;c3! z00aO40096102=@R008z6RQUh^0q99YK~zW$y_UO513?sqUC~0Z@=_SBZBzsiFE3y# z3U)ye0K?}Y;>!A8BYVIKom!0ZWUD(v*> z5LmCyR&Xh;ryXp2=fYp$!6EKCT)>ct>06oA-F2<%D?2DN$ckiy{Er*YQ({U%vg^vz^MhBW z&8NTANC&YKF8;G;9SgUJa}0M{S)XftbEWUX;wL2b>Y`htZ?Sh5u%y>ma+P7y*&$8EX1Q{0(WuUOXSL R57Phu002ovPDHLkV1j+h`)2?E diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_mini_autodelete.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_mini_autodelete.png index 431ba887271e6110546fd43a1504c4e68b436e81..9d5689ce4174cf55be091bfeaf8eaca7bc97534f 100644 GIT binary patch delta 526 zcmV+p0`dL*1Ih#;iBL{Q4GJ0x0000DNk~Le0000L0000L2nGNE0I3(HmXRSye-)qu z00aO40096102KfL000OZ5)J?W0kla(K~y*qos~UH13?r;U6m9O1<_VT1to$Y*jR{S zVJB81Y1G0>ECRv8pI~WYr=;;eSXfxvTBJy$g`$EVh=^c7Sqs%*_lJ;-LKeL`p(?sIROQ zwlq+K=(&SO=rw~0wCS>fN&e%Y>jU#}{7v|RannYix2%_vxQ9lFgGK|Me_$<^4_;!B zqc{w*k}Zn+)FPZM@T-)_Wa220Oof zmy!DVvQrp@9R;l#i`sw>WEe5M1Z|%vdUX%H!2{G!l%|tt>+I9#!2!&hRoV^(*IO!s zR=?c|yT7TAP%GBuB5QV^9{rVl{nXryhP0c14$Uq!oGd-TOM48BE&oUT0zvZDtA=1{ Qf&c&j07*qoM6N<$f}Y>xy#N3J delta 449 zcmV;y0Y3i91pNaciBL{Q4GJ0x0000DNk~Le0000I0000I2nGNE09MY9SCJt|e-fYr z00aO40096101^NI005e)vQ+>80cS}>K~y*qos_#u13?sqGm2tJBVLdo*hdRND}4YP z!3WSLjU=6gU||@*szR@ZkPW1w+xU}@|&+*>2v zjs`n26D!b8UNhA2@4~pZ^JL!#B7=MlF5wGaVLp`LFT%QhNzX|yOqxwxe@$LMOQ+to zobAM}U@&EYG5adb_tIx<8>=lY*N8RDC&q$NJ r)_Gi;^bzU;ber*qZJO9x7pmwNA(@@CHJiV100000NkvXXu0mjfKmoqJ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_mini_autodelete_empty.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_mini_autodelete_empty.png index 4df725ebee2e53a5c692ba08184c3b44bc0528b3..6af06598f022383502754e523f7f2be7ff29449e 100644 GIT binary patch delta 472 zcmV;}0Vn>v1C#_IiBL{Q4GJ0x0000DNk~Le0000L0000L2nGNE0I3(HmXRSye-)qu z00aO40096102KfL000OZ5)J?W0e(qDK~y*qt(8A71VI>w4RXbaLTC{rmz+c*(I`Zs z(24S*xocFSxJ2b6C^bsC#&=No7_QJz5EO{WH43tx-`*NyZ}#?Pd6VaM-ao%L@6PVd znV}quqG+0uIVi)0G3GNz;!o44e+-Av2G`c5Ag~3>Pxw2eq7-hzJ;_zQxQ42lP9|+$inX(80VYXAK~y*qt&~4X13?srGX{fcO=MYwTp)#Ds$9S( zH?Xn^v9k~?(uBY^xq$}|s{}2*!5%{p@d(-o>-SrnVRxO#{(JE4zBli^AG0&F)9H9D zNs@EeKn!p4RmQsyV^2$dh@ud7e<1q?2|dE>SLg;`cAxLeL?qAP9ljufVJ4lrj2*!( z%!@f&pqWVI118=g&mgszaN<$~`W(%qib#+8Pk6So(Zb3ir(DvZ=Nh(FNV}tj{j(+| z_0lqfKrGJ~Gp*8SYv~{02M0V(;ngN;yjS$RNT{h93AFKP+WtVtzLmw6RZx@dU(o;H z)`pl=*3OjoiUSuDKXU9savUuE@)l#a?er;l7Kl dOK#+A`T=LCPj}~_PJaLZ002ovPDHLkV1nC(q{aXM diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_mini_autodelete.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_mini_autodelete.png index 89e34b1426b993ffdf53a780bf0ad7a476591106..3bbdcb5370d9b62856c5a07d507353b2c7799a8a 100644 GIT binary patch delta 1094 zcmV-M1iAaz2mJ^kiBL{Q4GJ0x0000DNk~Le0000g0000g2nGNE0L8<}Tah71e=48@ z00aO40096104e|g007h)r3(N61LsLZK~z`?)tOC56;Tw&eHK#EmzGopVMS&{h0q7B zgD69a7DYt6G9ifC1qCX!YGVY6W(qCZ1@#uTD+mfgf|M5a0TpCIVW9PSSI`@Cho4Iq}-1mx#fBqB8B+WCI%T=MV4JsAG)SEMz%n0So8Od#i7U%~* zYqc}Pu@W}e{i(MJvL3P!#$1Uqp%zA*i4Q4v6(YehggfDf6UeI=rE>u8LMtrNiO{|P zw(ZwGiRviJReWhjpgjgX&;ZMVg5a|n-h#I4LGwb5qqqmm?J@zzskFoLe^4UIi8*p- zn&1Pxf{ki8UKJ5Z(!SLX=vx<0acUMCiFca`HH2!wzLH(^l}v?8zlMG(g!>RU%I&e& zNO&0Wan@>y}*+Go` zqO!w`O)DA98c#}jx)VP*f762abP#JT$d+fU$Q*LopzW*_eE?$or(HFr8?vfSZo^Fy+!bz7>p@`i+ZNzau0?kFR5d0d!`;DfkS= z_C=6zyP{BKRGhNG?Ge3&&K7uV>0XJhq-G#?$Px_v?pD)XRdg1>e`Sz8QQK1*b6n0* z8wi|Hilb5I8-FzF;hoYl)vr`SzlY*m3-ZBwVU5w3^?4DD4NMM1s4%$>?5k)r8zV?b zKIXxb`E4?{*b$Ipgs~x0iCCV*-{|S7w>XkS-RksHUm;>%!r@*DbSnP!L57a`4~HKy ziR!BucmKR*g&n7Te}t>~$?6Z&98*qmQ2b-8gVVAY982;r$vTzwbpzyN8q1g~TibJx zH>v7L5gG?UTimNUshM`g-1=TixZZ02J54U9TxMgbe1)d@>C6=s&8!mMNyamB`-wXr zW~*G>RR-x===oja+zF}~*k0SEZ zONsA<6bFcNAB@RSWP4tD9IhH~r|ixz7w3omESgdmitTSOZ=%X{uoUGZu_J9Q@X&gvOk{tnUl6(h5 z-e;L3hdg=ve;@5@AckWEt3Nmad7dcZv`$`56Ez+^YD zmx2x|C(UPY4=#cIL+)|4r~m)} delta 927 zcmV;Q17Q692-pW9iBL{Q4GJ0x0000DNk~Le0000a0000a2nGNE0O0_bn2{k!es142neK~z`?#hA}aR8bVieJX{EKrjoWVhAN$gv(S2 zjM9ah7PSZ$22s?eotxYk)L+of0&~&Eg@h4Ak}VP<2m&_|65%3C7hwbyMmc@XG-u|# zH}A)N^XAoo@7#0GkMBA2=Dz#xe;cnUr4)SuW5pW1}m9fbdZvmf7!V-Fi`s*4YxD0+6QdPgA%z+6o z28O^sTZDNtSOZq3ZH%)HgM7Q&mZ_{SGme5?E@Z?NzyjC-BG+80CH+sHfg zGrqtS6LNdOK#G)VD2c%|YJgnGk3<*hFitE?53 zWuG&{1j60m26zQpgH+hYf59_Q-N~8A24)`zqrt>_=5U?@`m-!JbC5I$RAdKs+Vr9d zkiQ??S3<^h;Qq)UcnYY@MHNc2Lm*fBD%esoMp>69lX8825@rm+gwN?XA-@m2G(4)_ za-ksj0=NTun)rAORK{EgYz&Fl!7n8+;|uhfg=}+0C#J{bEQw*oe;9^>32}4sPMch- zeqmCG%T*t7{U&i$PJdY>>VbKMljB+)#HHitN0ZoPbQg6cBxr_a(%c+dhGSiRvbq;F zxgL{-*yH}pbHLt?-o`+*f%TMcgt&Q9v5;r9b~e3%3WvQP?-8; zvYJu4LVNID1V^nsPWR%RVLr&^TlRxt(D1+X{s2eoHxU3Q_nQC!002ovPDHLkV1fbz BqE-L^ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_mini_autodelete_empty.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_mini_autodelete_empty.png index c3ff18c5ffd9688665c098134d41cfc5eebc7fbd..1758901a1ae59bfa4769e4bb3760f46a8ea9816c 100644 GIT binary patch delta 974 zcmV;<12O!)28IYBiBL{Q4GJ0x0000DNk~Le0000g0000g2nGNE0L8<}Tah71e=48@ z00aO40096104e|g007h)r3(N618_-1K~z`?)tNhJ6j2n0cY_2BiW&`Q1X&ehL{tJk zuogrGK`aytOJi#(C?o+Zn}|h73<=m^x{>0lP*#kw0VjiJP=z&so;#11wX$agv;8FNhP2^Y1(K!PTVG!0^A+&FR zYiF!atU8fp6<@m3!r_h9XA*t|&NL_7(4xirB~D#NfPe^L}h<(~rq7kSY+?5=ouXQn9@-^bOL9`Enqg)<$ zPhLx2mNz0f<+%F|Q_$`m^=aWW3f9Ja+!F61;Wv}Ceoh?a4!8SK_GGG;TR$;*@yP%9=*6=y$8@Rzv@^(O5;4}uOL2V+?G0Oqe6;xrh?9Eb7U~a^ncZ7z1cLxSbX`rPV zR~~xR=6T@#sEsU27Oe-K!zApiX`pcv)Mjb}8C4K+PsLCUi44CLf1S-ppG|A;`VK49 zIz@XZurrpSt}AL+F_ZJt`K$}3Eia1yjD2v$?1?$;&#FYrL3j!BB-K^ZLE{`)TM4UL z(u&N62!+=dS@1i|N} ziilbgOtyi$XX&aOB@ZYsL#Swcw!tmUGsADcHnr-Ti629fxNiXJirf*GZMkkR*YAn1Uel2$jq@w!oTZ wRK$MmAaQ}jB!|HL`(_~Xzi*mZk+^gI0=Se0*)X1K^#A|>07*qoM6N<$g0L*VtN;K2 delta 771 zcmV+e1N{7k2)za&iBL{Q4GJ0x0000DNk~Le0000a0000a2nGNE0O0_bn2{k!es0;fqtK~z`?&6qt%96=PvU6p`^AZP*+PsIMJTFBVzd+dC zLc&R~268(&71_Y_=U_NSe-6b%3p-#%cHojWz2cFhS;&`rP`Q5PYn@Sb=c*8DQ&~5a z?o8#ayaJy=cP&{TtIS7AccKaj63v@roiakrrXq!NLgds(l7tYtPjYh{)uWum>gI(l#BTY1&^@bFpTR6JF3W>qqpB8WJbx! zAs+${!70dq*LlggUHheT5R6X5~H{-icO(Oi**=M!I7dlTkcg_t~=}Eh@60~ zVz~?U6Cm=>1__M+SvJs;|58|xMWB;tvif98(AOlL#Oi63f4^f4p&zDGem2a3yF2a> zEXoh7didy>!dv;Ld;P{RPQ^5)g$(mMj1O002ovPDHLkV1lnu BSPK9E diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_mini_autodelete.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_mini_autodelete.png index d00bf8cb5bbaf3f5afeb736d0f2d7cfcbf79f73e..f5db66ad06248b638bdaf1be28baa98c93259977 100644 GIT binary patch delta 1662 zcmV-^27&pW3$YC$iBL{Q4GJ0x0000DNk~Le0000#0000#2nGNE09RiS-;p6me?OoD z00aO40096106zc#005um$Or%c1{+C4K~!i3?VC-ARb?2*@3k}&rzEopYcVmIk`%3~ zjW{5-NEmIIivmX^3bc*EfKn~owJ@V>q2MBdS}TPZLrOwm6v7Z@c8hGtp!}+)%yIht zulG2I_dMsG_k7-aM<4ie?)$vYf9HGNbI$v9YqeX{nHHFyp1!>^p09hYm+hPcSK?ze z&~-T&1dBi)kpC~ii}iZ_G}qoHlAQzAfCJ!H;6ulII%_h#3ygxFeX@0Au2436A^a!7 z5%3Mz1@7&dh9a0d!5iQ_$V%p&$Jk=f6J`934dK1 z!wu^0vGhvi;dCx|ANbN41owc=U=+v55Xo4U2C#V)vjia^T zQsCLRj>8)+#+z*g`vX8de?h1$aozE_#Pv+7d*5b<+{0Xw(lZ{{)h|=Gp@-9p51qvH zRv!ZvHDEp-uicp%V%iAa1NySe$s7gBdx1p_nEjazm!wEc8f=AWUnW;r&;Z6Fb@g1I zuS@}XC=u5=Bj9H|RA)pmpdo9+Fr=4T*FNJhF@Fz#{!DfWzYuo2e-ErC?MP7VXf+XT zYsKkaM!f<1sH{vk2(ZHKo{mydt+1XpKO08a$M!htL{H8&v36 zJu#4$0C7AI!mB|Ce^co8qx%H#dsGqB>$$9TAv1K*Xs z;c++*>;#j6^U(F#q8c}12Pc}mSX9Y?2FF9-TPpx~yCVS?$-u$OhT}6u`mk)_a|y5> zw;&vSy4tSzSawtKaPV&6Ep*+J%18=feAK}R_&wy${rMM+e-$arW)sE95!W;2&DpHA z*TSwIv15immo?JfQgST@mx7PMDX^^ROQ9P=1rd%^e&MU038^ zm(eOK-&!+yoL%9!HECJU5-#fUOM;=#so#|#lcqZE6RCV>h`Vo1V~c*~H8Z;kEYc_M z!I_Bi|H7WSxJ#D;%i(9KUWcZO7tto$R|#@ zR-<{U{LX9QqKyX)tTg?MetRPkW@BJmUzkJSEcg&Cj#z?O=?E_pKnED%Za;4LUuW=U zAB#KR(OF$5TJ*-9=jEU~uNRL?>s`idDc?xGmC&a~VjB$J>|@dOyJ`(DPYaXFLf|u^ zyzU0!f391-&C&qmu?1)?K=cJz;$;Ux9VfG>)Po0J!FdP~_*1|gMZV*q+8pd?NN5dp z$KhL(A1}LA2oayQ)K3DJ4qN`+7hbJ3Bz)TteiWYHo|mCq5o`=DcCg7P+ysn?d$I0U2FU>X0eRGN=nEpzV&n?T$yOeuM#GD>h-5YdXz0brf+ zfgPdPJDR{4 z00aO40096105$*s008qtE~Eee1qew*K~!i3-J0KvRaF$nyV@8gMn6!g&<0D#2aALt zeVd0MMd-ooVf;}KAsPrqLi94#!vv8gVLkK*XjWD}SR`unqzGNIUFYt5?%n6kxh?q4e>r>YwZChvIs5Fhf85G#k!5mnvJc89z;ZAE`oUdb zF8B?+RjE`)xt3o@<{q#W90B8?nfy-WeWlKd03QbXzzvW`2ED`}*a^nKCt#IlQ>YH7 z^T7MyFOXNej^yPVNB1;3~)?>YhJ>!(gQsCHi-PC+ZE4e|l}A=pbJJ zPI{h=?jq&AU=0`m8aRT2-CB?(Zv+Y2F~Hjj@N?l`f8AkDLSmQ=hN_s|Qge(oqEk}Ce25{Qx*W`a?Wi0)?v znoYe|`c7F<_nY=7rQnHUl?{&aaMQ#v(2T70g>4_RjxfP6+tX9CW|qFHxQ0COkcI zxEoXu?M_%zS8ba`6L@EUm%!ze4|{tJ#gcZM(oWyCV?gV-FTh;Qy*>k0vz?LkK=jNUGOeO6;gnj<@T;wu5&*&>l!g_S?33f69bC`KLe)2o!JSHM(kNpVpA3*Gpdq%ruLbNv)eU2SO}4>L&uW z!z48puxX`Q=LKoGuEegUHQdjkzqYg&Xxe$jgPyXgJI?i;CiQu$&Ku)%8axUc?%~!l z{vfb&9>shUe@xp&xE=y0fRr*NdPccOj>jPb%1Q(NyVI{F?Teg@`q7^kNO1ur5XaB3};lDhwQfv+6{TCAVI`9G5 zl-ZWb(ro5r2AQfLUOSqEnUj{&f3JBhtkdpsF;G%EWs(laKp?n1txkX^~@m?`$pA?}4!1K3&c6qh zW2rC1+N!waukHq4`V^>}*we)NFd9o*?`g08Fv$9m*o%$LrZl5 zQ4m8Ao~>IHltdtC8-oW%ZQ8Z)5B*svxQG_6T=*!$c%b+|FbczkS>B>}1=H`^|iRGjqSL#r| z3n##hl(7ZqxB(1wb&b3D^zrN}ybP@Oj#LABt9tOL?W-ts^fd^+J%JmK4E(poq+58K?1`Bn? z@emtFe}VG@;F(@X;Quz529~_lOWs}iQu;=qR-8@FaPft%GVWkZeoq0tJ^lr4ndD?m z2iIaQQ_yyBt6j2jyo7F~f0)wjT*!YNP)`u%EO51WC~)1A>fR67loQqxlL{NLdk^LcoL&YW0lirk&G}vedQt);RLU12H_VYT<@EZfrhMgBO^WBN_&q7IRAitf64vmI-FtbcOO(u zIncp%pqF8|uf@|pj7AZ|OJ8T!M~dF#rGA}>ANU6Na@GpodK&J*P?HV5j56Uc<15RQ zZt;5)PIohi_tb&8&c?4y(5tV42WSh z*aYHkRSfm<;LC*qf4Gz~3;&+qVX!=D=&Gfnx%Kg4Nht?3Mby6A@|s@uii!*V00o9z(XZojOpbdFQbd5Wit4AS~`e`&4`HG?Oy7$a*W^rzvR zlIouTS;bpx2G8s44(HZK%hG^zD(cXph~m8$d@B;Gi)$GD_z&hGDqY z&?TcH$Xk&8+9UkAm#)>g?@t>~#@!ji#1zD|4vc|c!DnD)CIplp0$&bi3@fB?yZt32 zuAPRidMcWHfBt^e@o-SfUo)C@m!YeK%I)?F=nEsN($3QxT=T2w`c<04TSh!jMArt! zRhhkggIN9ml5^nks7k<4+sQ2H>&Am9auG%le;!PG0UU{NZ4P!)Ua`@V`b95rCBL+S z_aAt*){yX!Vf-|@xHT`sd5%>3DaV>{{VKYAYHBH{e*<4iyGjdD?I6(xMPI&05x+{S zTEbz(&!v^t`g|F8WNZ!a-5TBMt|6Hvvoiy(YWcst3OMG*)73c{CAB99zPo_F-n3s& zUvRxm#*l$(G^yQK@Xb8PyJ~gUn9SzqKD>4%PyyRPXd@z*8rr$h*MSiEs}Ww&vCV7F zj;+E&f42aao6?WHw_C{BooSjO{ra2*-Y$qj^$ZvXousH;*Y5-h(l0FL-x+PLosPMj zDO8IAn&Q&_ml(N<(HPL$-(@ffG+TU=5oKs@U1JT+9$yF6n!;cB_VzR<%MLEJu8y4RM*7h~jWKl8^;)C}oFNaCe`#`Q<8!D$KJ!JGX z*pI)fmXNRZ*B=9~fzT>UPy7pUhMXouG$V8zl={EAzIPoeK=0)F@keDh-G_=9=)bhx caIKu}e~H$FS3#rdL;wH)07*qoM6N<$f^nm+d;kCd delta 1135 zcmV-#1d#i~3#JJniBL{Q4GJ0x0000DNk~Le0000s0000s2nGNE0E|Uy-H{R{4 z00aO40096105$*s008qtE~Eee1Q1C?K~!i3-I~pBOi>udjcSFa;vd`779wROArYIhsYHTC@Fz4%f<}Bq!WIn?7Kqpg5-SVzLw&^fJ?-2vdFQ<| zrtZw#+c$Z7=e+lv_dHL}x#ygFf6h(KirCWWbS)vZ8&|Z~^=PrJCDf1li#P zx{puYy1Ygub3rfo3Q93`&o|&EX!ls+KMOolZ`k0KiNr_nrQo4gv+=zne|;Wwf@YwB zqo|Q@D+sd_MFUJH1GQ}g@55@vukO_cHclsgP=@k*zz1M$SCA*ND>Zb3F@^=r3gzn)PP|Sn10t4DK`0BX`ZsC?pO7e z5_x!ZSaPI$3koBHRutak8=Geeel5V-HyrCi#c@;1Bx^&V1*&udg4Zlb(t|D?ZfHSk z*M6F(n%n|abi!!`us!RQONoqHrCaNFGsnOQ(F2C;={#Cw(EF5qfAw%z;dzCt=z#NH za0wg+HF;EUY6)YlFE3-#94OM8ueJYL&{q9_fSOvM$v4;DRh`&>5|~YSttRSCoEs*c zxTJ(>H_6%^>quH!!n8$Z?X7jhIn6VqdF>w1``dNUptaXkU`=~~i807uHUdto#q6HM z7#o@+iBUC0wAJu!f0HoVIv-7@BqVX8Bvt-jd<&0bePc|0fX54Fwx}wb6u-QI!e=EU%X|bek4Rpc%BG?`J z@8@RHHgoJ~e$*9*uq182t%UCg$h9Y~FgeMUlnv8cxr#Es2MP&G!WQ>tN7OjJ@Gb64 ztHu3L;*=xoSZHenHz*YMW29|H*mKfz0{#WBWQ_a4-$6J&0)t>#K{56w1!mq+Z6FeH z-C$-Nt{ZXQe<3@X)8+UaVli;j72P}t(W+&d)kqgQV zNQI?-a(HavM`HVe%;s9Tk2a}T zD-9?({{fcBg1 hour Automatically delete new messages sent in this chat after a certain period of time. Never + Never Set for this chat Messages in this chat are automatically\ndeleted %1$s after they are sent. New messages in this chat will be automatically deleted in %1$s. @@ -230,7 +231,7 @@ %1$s is using an older version of Telegram, so secret photos will be shown in compatibility mode.\n\nOnce %2$s updates Telegram, photos with timers for 1 minute or less will start working in \'Tap and hold to view\' mode, and you will be notified whenever the other party takes a screenshot. Messages Search - Mute notifications + Mute forever Mute for %1$s Mute for 1 hour Unmute @@ -2150,6 +2151,8 @@ Enable forced RTMP Stream Flag Disable forced RTMP Stream Flag Clear WebView cache + Enable WebView debug + Debug enabled! You can change your language later in Settings. Choose your language Other @@ -3335,6 +3338,7 @@ Sorry, the target user is a member of too many groups and channels. Please ask them to leave some first. Warning! This will **delete all messages** in this chat for **both** participants. + Warning! This will **delete all messages** in this channel. Warning! This will **delete all messages** in this chat. Delete All Stop loading? @@ -3406,7 +3410,8 @@ Do you want to stop the verification process? Are you sure you want to clear your chat history with **%1$s**? Are you sure you want to clear your secret chat history with **%1$s**? - Are you sure you want to clear the chat history in **%1$s**? + Are you sure you want to clear the chat history in **%1$s** for all users? + Are you sure you want to clear the channel history in **%1$s**? Are you sure you want to delete all messages in this chat? Are you sure you want to clear **Saved Messages**? Delete all cached text and media from this channel? @@ -5067,38 +5072,38 @@ Nobody viewed QR Code - %1$s: %2$s to your "%3$s" - %1$s: %2$s to your message - %1$s: %2$s to your photo - %1$s: %2$s to your video - %1$s: %2$s to your video message - %1$s: %2$s to your file - %1$s: %2$s to your %3$s sticker - %1$s: %2$s to your voice message - %1$s: %2$s to your contact %3$s - %1$s: %2$s to your map - %1$s: %2$s to your live location - %1$s: %2$s to your poll %3$s - %1$s: %2$s to your quiz %3$s - %1$s: %2$s to your game - %1$s: %2$s to your invoice - %1$s: %2$s to your GIF - %1$s: %3$s in %2$s to your "%4$s" - %1$s: %3$s to your message in %2$s - %1$s: %3$s to your photo in %2$s - %1$s: %3$s to your video in %2$s - %1$s: %3$s to your video message in %2$s - %1$s: %3$s to your file in %2$s - %1$s: %3$s to your %4$s sticker in %2$s - %1$s: %3$s to your voice message in %2$s - %1$s: %3$s to your contact %4$s in %2$s - %1$s: %3$s to your map in %2$s - %1$s: %3$s to your live location in %2$s - %1$s: %3$s to your poll %4$s in %2$s - %1$s: %3$s to your quiz %4$s in %2$s - %1$s: %3$s to your game in %2$s - %1$s: %3$s to your invoice in %2$s - %1$s: %3$s to your GIF in %2$s + %2$s to your "%3$s" + %2$s to your message + %2$s to your photo + %2$s to your video + %2$s to your video message + %2$s to your file + %2$s to your %3$s sticker + %2$s to your voice message + %2$s to your contact %3$s + %2$s to your map + %2$s to your live location + %2$s to your poll %3$s + %2$s to your quiz %3$s + %2$s to your game + %2$s to your invoice + %2$s to your GIF + %3$s in %2$s to your "%4$s" + %3$s to your message in %2$s + %3$s to your photo in %2$s + %3$s to your video in %2$s + %3$s to your video message in %2$s + %3$s to your file in %2$s + %3$s to your %4$s sticker in %2$s + %3$s to your voice message in %2$s + %3$s to your contact %4$s in %2$s + %3$s to your map in %2$s + %3$s to your live location in %2$s + %3$s to your poll %4$s in %2$s + %3$s to your quiz %4$s in %2$s + %3$s to your game in %2$s + %3$s to your invoice in %2$s + %3$s to your GIF in %2$s Blur in chat Allow background activity @@ -5186,6 +5191,7 @@ This bot can\'t be added to the attachment menu. This bot is already in your attachment menu. Remove **%1$s** from the attachment menu? + Remove **%1$s** from suggestions? Allow **%1$s** to access to your location?\n\nThe developer of **%1$s** will be able to access your location when this web app is open. Allow **%1$s** to access to your location?\n\nThe developer of **%1$s** will be able to access your location when this web app is open.\n\nGo to Settings > Permissions and turn **Location** on to share location data. Allow **%1$s** to access to your microphone?\n\nThe developer of **%1$s** will be able to access your microphone when this web app is open. @@ -5202,4 +5208,8 @@ Do you want to delete **%d notification sounds**? Do you want to delete **%d notification sounds**? Do you want to delete **%d notification sounds**? + Custom Sound + Clear for all + Clear for me + WebView not available. Please update it to use WebView bots.