Update to 8.7.1

This commit is contained in:
xaxtix 2022-04-21 21:03:20 +03:00
parent 1e50785b90
commit 8309ac8d7c
70 changed files with 2501 additions and 1064 deletions

View File

@ -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']

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -88,7 +88,7 @@ _isOutgoing(encryptionKey.isOutgoing),
_stateUpdated(std::move(stateUpdated)),
_transportMessageReceived(std::move(transportMessageReceived)),
_sendSignalingMessage(std::move(sendSignalingMessage)),
_localIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH)) {
_localIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH), false) {
assert(_thread->IsCurrent());
_networkMonitorFactory = PlatformInterface::SharedInstance()->createNetworkMonitorFactory();
@ -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(

View File

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

View File

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

View File

@ -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<VideoCaptureInterfaceImpl*>(videoCapture)->object()->getSyncAssumingSameThread()
@ -777,6 +794,7 @@ struct NetworkBitrateLogRecord {
class InstanceV2ImplInternal : public std::enable_shared_from_this<InstanceV2ImplInternal> {
public:
InstanceV2ImplInternal(Descriptor &&descriptor, std::shared_ptr<Threads> threads) :
_signalingProtocolVersion(signalingProtocolVersion(descriptor.version)),
_threads(threads),
_rtcServers(descriptor.rtcServers),
_proxy(std::move(descriptor.proxy)),
@ -927,7 +945,7 @@ public:
_threads->getNetworkThread()
);
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 @@ public:
void sendSignalingMessage(signaling::Message const &message) {
auto data = message.serialize();
sendRawSignalingMessage(data);
}
void sendRawSignalingMessage(std::vector<uint8_t> 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<uint8_t>(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<uint8_t>(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<EncryptedConnection::EncryptedPacket> packet) {
if (!packet) {
return;
}
_signalingDataEmitted(packet.value().bytes);
}
void beginSignaling() {
_signalingEncryption.reset(new SignalingEncryption(_encryptionKey));
const auto weak = std::weak_ptr<InstanceV2ImplInternal>(shared_from_this());
_signalingEncryptedConnection = std::make_unique<EncryptedConnection>(
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 @@ public:
auto localIceParams = networking->getLocalIceParameters();
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 @@ public:
data.ufrag = ufrag;
data.pwd = pwd;
data.supportsRenomination = supportsRenomination;
signaling::DtlsFingerprint dtlsFingerprint;
dtlsFingerprint.hash = hash;
@ -1335,21 +1414,41 @@ public:
}
void receiveSignalingData(const std::vector<uint8_t> &data) {
std::vector<uint8_t> 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<uint8_t>(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<uint8_t> decryptedData = std::vector<uint8_t>(data.data(), data.data() + data.size());
processSignalingData(decryptedData);
}
@ -1365,6 +1464,7 @@ public:
PeerIceParameters remoteIceParameters;
remoteIceParameters.ufrag = initialSetup->ufrag;
remoteIceParameters.pwd = initialSetup->pwd;
remoteIceParameters.supportsRenomination = initialSetup->supportsRenomination;
std::unique_ptr<rtc::SSLFingerprint> fingerprint;
std::string sslSetup;
@ -1828,6 +1928,7 @@ private:
}
private:
SignalingProtocolVersion _signalingProtocolVersion;
std::shared_ptr<Threads> _threads;
std::vector<RtcServer> _rtcServers;
std::unique_ptr<Proxy> _proxy;
@ -1843,7 +1944,7 @@ private:
std::function<rtc::scoped_refptr<webrtc::AudioDeviceModule>(webrtc::TaskQueueFactory*)> _createAudioDeviceModule;
FilePath _statsLogPath;
std::unique_ptr<SignalingEncryption> _signalingEncryption;
std::unique_ptr<EncryptedConnection> _signalingEncryptedConnection;
int64_t _startTimestamp = 0;
@ -1993,6 +2094,7 @@ void InstanceV2Impl::setEchoCancellationStrength(int strength) {
std::vector<std::string> InstanceV2Impl::GetVersions() {
std::vector<std::string> result;
result.push_back("4.0.1");
result.push_back("4.0.2");
return result;
}

File diff suppressed because it is too large Load Diff

View File

@ -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 @@ private:
}
NativeNetworkingImpl::ConnectionDescription::CandidateDescription NativeNetworkingImpl::connectionDescriptionFromCandidate(cricket::Candidate const &candidate) {
NativeNetworkingImpl::ConnectionDescription::CandidateDescription result;
result.type = candidate.type();
result.protocol = candidate.protocol();
result.address = candidate.address().ToString();
return result;
}
webrtc::CryptoOptions NativeNetworkingImpl::getDefaulCryptoOptions() {
auto options = webrtc::CryptoOptions();
options.srtp.enable_aes128_sha1_80_crypto_cipher = true;
@ -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 &timestamp, int unused) {
assert(_threads->getNetworkThread()->IsCurrent());
_lastNetworkActivityMs = rtc::TimeMillis();
_isFailed = false;
}
void NativeNetworkingImpl::transportRouteChanged(absl::optional<rtc::NetworkRoute> route) {
assert(_threads->getNetworkThread()->IsCurrent());
@ -605,6 +600,10 @@ void NativeNetworkingImpl::UpdateAggregateStates_n() {
if (_isConnected != isConnected) {
_isConnected = isConnected;
if (!isConnected) {
_lastDisconnectedTimestamp = rtc::TimeMillis();
}
notifyStateUpdated();

View File

@ -143,6 +143,7 @@ public:
};
static webrtc::CryptoOptions getDefaulCryptoOptions();
static ConnectionDescription::CandidateDescription connectionDescriptionFromCandidate(cricket::Candidate const &candidate);
NativeNetworkingImpl(Configuration &&configuration);
~NativeNetworkingImpl();
@ -168,7 +169,6 @@ private:
void OnTransportReceivingState_n(rtc::PacketTransportInternal *transport);
void transportStateChanged(cricket::IceTransportInternal *transport);
void transportReadyToSend(cricket::IceTransportInternal *transport);
void transportPacketReceived(rtc::PacketTransportInternal *transport, const char *bytes, size_t size, const int64_t &timestamp, int unused);
void transportRouteChanged(absl::optional<rtc::NetworkRoute> route);
void candidatePairChanged(cricket::CandidatePairChangeEvent const &event);
void DtlsReadyToSend(bool DtlsReadyToSend);
@ -214,7 +214,7 @@ private:
bool _isConnected = false;
bool _isFailed = false;
int64_t _lastNetworkActivityMs = 0;
int64_t _lastDisconnectedTimestamp = 0;
absl::optional<RouteDescription> _currentRouteDescription;
absl::optional<ConnectionDescription> _currentConnectionDescription;
};

View File

@ -366,6 +366,7 @@ std::vector<uint8_t> InitialSetupMessage_serialize(const InitialSetupMessage * c
object.insert(std::make_pair("@type", json11::Json("InitialSetup")));
object.insert(std::make_pair("ufrag", json11::Json(message->ufrag)));
object.insert(std::make_pair("pwd", json11::Json(message->pwd)));
object.insert(std::make_pair("renomination", json11::Json(message->supportsRenomination)));
json11::Json::array jsonFingerprints;
for (const auto &fingerprint : message->fingerprints) {
@ -393,6 +394,11 @@ absl::optional<InitialSetupMessage> 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> 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;

View File

@ -138,6 +138,7 @@ struct MediaContent {
struct InitialSetupMessage {
std::string ufrag;
std::string pwd;
bool supportsRenomination = false;
std::vector<DtlsFingerprint> fingerprints;
};

View File

@ -2455,7 +2455,7 @@ public class AndroidUtilities {
}
public static void appCenterLog(Throwable e) {
}
public static boolean shouldShowClipboardToast() {

View File

@ -20,8 +20,8 @@ public class BuildVars {
public static boolean USE_CLOUD_STRINGS = true;
public static boolean CHECK_UPDATES = true;
public static boolean NO_SCOPED_STORAGE = Build.VERSION.SDK_INT <= 29;
public static int BUILD_VERSION = 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";

View File

@ -1356,7 +1356,7 @@ public class DownloadController extends BaseController implements NotificationCe
for (int i = 0; i < messageObjects.size(); i++) {
boolean found = false;
for (int j = 0; j < recentDownloadingFiles.size(); j++) {
if (messageObjects.get(i).getId() == recentDownloadingFiles.get(j).getId()) {
if (messageObjects.get(i).getId() == recentDownloadingFiles.get(j).getId() && recentDownloadingFiles.get(j).getDialogId() == messageObjects.get(i).getDialogId()) {
recentDownloadingFiles.remove(j);
found = true;
break;
@ -1364,7 +1364,7 @@ public class DownloadController extends BaseController implements NotificationCe
}
if (!found) {
for (int j = 0; j < downloadingFiles.size(); j++) {
if (messageObjects.get(i).getId() == downloadingFiles.get(j).getId()) {
if (messageObjects.get(i).getId() == downloadingFiles.get(j).getId() && downloadingFiles.get(j).getDialogId() == messageObjects.get(i).getDialogId()) {
downloadingFiles.remove(j);
found = true;
break;

View File

@ -4912,6 +4912,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
info.mediaEntities,
info.isPhoto,
info.cropState,
info.roundVideo,
callback);

View File

@ -6417,7 +6417,7 @@ public class MessageObject {
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;
}
}

View File

@ -835,7 +835,7 @@ public class MessagesController extends BaseController implements NotificationCe
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 class MessagesController extends BaseController implements NotificationCe
arr.add(obj);
}
AndroidUtilities.runOnUIThread(() -> {
for (int a = 0; a < messages.size(); a++) {
long key = messages.keyAt(a);
ArrayList<MessageObject> value = messages.valueAt(a);
updateInterfaceWithMessages(key, value, false);
}
getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload);
});
getMessagesStorage().getStorageQueue().postRunnable(() -> {
if (!pushMessages.isEmpty()) {
AndroidUtilities.runOnUIThread(() -> getNotificationsController().processNewMessages(pushMessages, !(res instanceof TLRPC.TL_updates_differenceSlice), false, null));
}
getMessagesStorage().putMessages(res.new_messages, true, false, false, getDownloadController().getAutodownloadMask(), false);
for (int a = 0; a < messages.size(); a++) {
long dialogId = messages.keyAt(a);
ArrayList<MessageObject> arr = messages.valueAt(a);
getMediaDataController().loadReplyMessagesForMessages(arr, dialogId, false, () -> {
AndroidUtilities.runOnUIThread(() -> {
updateInterfaceWithMessages(dialogId, arr, false);
getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload);
});
});
}
});
getSecretChatHelper().processPendingEncMessages();
@ -13086,6 +13089,11 @@ public class MessagesController extends BaseController implements NotificationCe
updatesOnMainThread = new ArrayList<>();
}
updatesOnMainThread.add(baseUpdate);
} else if (baseUpdate instanceof TLRPC.TL_updateBotMenuButton) {
if (updatesOnMainThread == null) {
updatesOnMainThread = new ArrayList<>();
}
updatesOnMainThread.add(baseUpdate);
} else if (baseUpdate instanceof TLRPC.TL_updateReadChannelDiscussionInbox) {
if (updatesOnMainThread == null) {
updatesOnMainThread = new ArrayList<>();
@ -13721,6 +13729,9 @@ public class MessagesController extends BaseController implements NotificationCe
getNotificationCenter().postNotificationName(NotificationCenter.webViewResultSent, resultSent.query_id);
} else if (baseUpdate instanceof TLRPC.TL_updateAttachMenuBots) {
getMediaDataController().loadAttachMenuBots(false, true);
} else if (baseUpdate instanceof TLRPC.TL_updateBotMenuButton) {
TLRPC.TL_updateBotMenuButton updateBotMenuButton = (TLRPC.TL_updateBotMenuButton) baseUpdate;
getNotificationCenter().postNotificationName(NotificationCenter.updateBotMenuButton, updateBotMenuButton.bot_id, updateBotMenuButton.button);
} else if (baseUpdate instanceof TLRPC.TL_updateReadChannelDiscussionInbox) {
TLRPC.TL_updateReadChannelDiscussionInbox update = (TLRPC.TL_updateReadChannelDiscussionInbox) baseUpdate;
getNotificationCenter().postNotificationName(NotificationCenter.threadMessagesRead, -update.channel_id, update.top_msg_id, update.read_max_id, 0);

View File

@ -189,6 +189,8 @@ public class NotificationCenter {
public static final int filterSettingsUpdated = totalEvents++;
public static final int suggestedFiltersLoaded = totalEvents++;
public static final int updateBotMenuButton = totalEvents++;
//global
public static final int pushMessagesUpdated = totalEvents++;
public static final int stopEncodingService = totalEvents++;

View File

@ -3438,7 +3438,7 @@ public class NotificationsController extends BaseController {
MessageObject messageObject = pushMessages.get(0);
boolean[] text = new boolean[1];
String message = lastMessage = getStringForMessage(messageObject, false, text, null);
silent = messageObject.messageOwner.silent ? 1 : 0;
silent = isSilentMessage(messageObject) ? 1 : 0;
if (message == null) {
return;
}
@ -3469,7 +3469,7 @@ public class NotificationsController extends BaseController {
}
if (silent == 2) {
lastMessage = message;
silent = messageObject.messageOwner.silent ? 1 : 0;
silent = isSilentMessage(messageObject) ? 1 : 0;
}
if (pushDialogs.size() == 1) {
if (replace) {
@ -3859,6 +3859,10 @@ public class NotificationsController extends BaseController {
}
}
private boolean isSilentMessage(MessageObject messageObject) {
return messageObject.messageOwner.silent || messageObject.isReactionPush;
}
@SuppressLint("NewApi")
private void setNotificationChannel(Notification mainNotification, NotificationCompat.Builder builder, boolean useSummaryNotification) {
if (useSummaryNotification) {

View File

@ -21,13 +21,15 @@ public class SharedPrefsHelper {
}
public static void cleanupAccount(int account) {
SharedPreferences.Editor editor = webViewBotsPrefs.edit();
for (String key : webViewBotsPrefs.getAll().keySet()) {
if (key.startsWith("confirm_shown_" + account + "_")) {
editor.remove(key);
if (webViewBotsPrefs != null) {
SharedPreferences.Editor editor = webViewBotsPrefs.edit();
for (String key : webViewBotsPrefs.getAll().keySet()) {
if (key.startsWith("confirm_shown_" + account + "_")) {
editor.remove(key);
}
}
editor.apply();
}
editor.apply();
}
public static SharedPreferences getWebViewBotsPrefs() {

View File

@ -37,7 +37,7 @@ public class RingtoneDataStore {
public final ArrayList<CachedTone> userRingtones = new ArrayList<>();
private boolean loaded;
public final static HashSet<String> ringtoneSupportedMimeType = new HashSet<>(Arrays.asList("audio/mpeg", "audio/ogg", "audio/m4a"));
public final static HashSet<String> 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 class RingtoneDataStore {
} catch (Exception e) {
FileLog.e(e);
}
loadUserRingtones();
AndroidUtilities.runOnUIThread(() -> {
loadUserRingtones();
});
}
public void loadUserRingtones() {
@ -251,7 +253,7 @@ public class RingtoneDataStore {
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);
});
}
}

View File

@ -50,10 +50,11 @@ public class MediaCodecVideoConvertor {
ArrayList<VideoEditedInfo.MediaEntity> mediaEntities,
boolean isPhoto,
MediaController.CropState cropState,
boolean isRound,
MediaController.VideoConvertorListener callback) {
this.callback = callback;
return convertVideoInternal(videoPath, cacheFile, rotationValue, isSecret, originalWidth, originalHeight,
resultWidth, resultHeight, framerate, bitrate, originalBitrate, startTime, endTime, avatarStartTime, duration, needCompress, false, savedFilterState, paintPath, mediaEntities, isPhoto, cropState);
resultWidth, resultHeight, framerate, bitrate, originalBitrate, startTime, endTime, avatarStartTime, duration, needCompress, false, savedFilterState, paintPath, mediaEntities, isPhoto, cropState, isRound);
}
public long getLastFrameTimestamp() {
@ -73,7 +74,8 @@ public class MediaCodecVideoConvertor {
String paintPath,
ArrayList<VideoEditedInfo.MediaEntity> mediaEntities,
boolean isPhoto,
MediaController.CropState cropState) {
MediaController.CropState cropState,
boolean isRound) {
long time = System.currentTimeMillis();
boolean error = false;
@ -407,7 +409,9 @@ public class MediaCodecVideoConvertor {
decoder = MediaCodec.createDecoderByType(videoFormat.getString(MediaFormat.KEY_MIME));
outputSurface = new OutputSurface(savedFilterState, null, paintPath, mediaEntities, cropState, resultWidth, resultHeight, originalWidth, originalHeight, rotationValue, framerate, false);
outputSurface.changeFragmentShader(createFragmentShader(originalWidth, originalHeight, resultWidth, resultHeight, true), createFragmentShader(originalWidth, originalHeight, resultWidth, resultHeight, false));
if (!isRound && Math.max(resultHeight, resultHeight) / (float) Math.max(originalHeight, originalWidth) < 0.9f) {
outputSurface.changeFragmentShader(createFragmentShader(originalWidth, originalHeight, resultWidth, resultHeight, true), createFragmentShader(originalWidth, originalHeight, resultWidth, resultHeight, false));
}
decoder.configure(videoFormat, outputSurface.getSurface(), null, 0);
decoder.start();
@ -800,7 +804,7 @@ public class MediaCodecVideoConvertor {
originalWidth, originalHeight,
resultWidth, resultHeight, framerate, bitrate, originalBitrate, startTime, endTime, avatarStartTime, duration,
needCompress, true, savedFilterState, paintPath, mediaEntities,
isPhoto, cropState);
isPhoto, cropState, isRound);
}
long timeLeft = System.currentTimeMillis() - time;

View File

@ -14,10 +14,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.google.android.exoplayer2.util.Log;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.R;
import org.telegram.ui.Adapters.FiltersView;
import org.telegram.ui.Components.RLottieDrawable;
@ -94,15 +91,7 @@ public class ActionBarMenu extends LinearLayout {
}
public ActionBarMenuItem addItem(int id, int icon, CharSequence text, int backgroundColor, Drawable drawable, int width, CharSequence title, Theme.ResourcesProvider resourcesProvider) {
ActionBarMenuItem menuItem = new ActionBarMenuItem(getContext(), this, backgroundColor, isActionMode ? parentActionBar.itemsActionModeColor : parentActionBar.itemsColor, text != null, resourcesProvider) {
@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);

View File

@ -1914,9 +1914,9 @@ public class ActionBarMenuItem extends FrameLayout {
}
}
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;

View File

@ -551,6 +551,10 @@ public class ActionBarPopupWindow extends PopupWindow {
public void setTopView(View topView) {
this.topView = topView;
}
public void setSwipeBackForegroundColor(int color) {
getSwipeBack().setForegroundColor(color);
}
}
public ActionBarPopupWindow() {
@ -866,6 +870,7 @@ public class ActionBarPopupWindow extends PopupWindow {
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 class ActionBarPopupWindow extends PopupWindow {
@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;
}
}
}

View File

@ -17,7 +17,6 @@ import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.PorterDuff;
@ -920,9 +919,6 @@ public class BottomSheet extends Dialog {
super.onCreate(savedInstanceState);
Window window = getWindow();
/*if (Build.VERSION.SDK_INT >= 30) {
window.setDecorFitsSystemWindows(true);
}*/
window.setWindowAnimations(R.style.DialogNoAnimation);
setContentView(container, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

View File

@ -312,7 +312,9 @@ public class CalendarActivity extends BaseFragment {
@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) {

View File

@ -31,7 +31,6 @@ import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.StaticLayout;
import android.text.TextUtils;
import android.text.style.ClickableSpan;
import android.util.SparseArray;
@ -39,9 +38,7 @@ import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
@ -207,6 +204,7 @@ public class CameraScanActivity extends BaseFragment {
actionBarLayout[0] = null;
}
};
bottomSheet.setUseLightStatusBar(false);
AndroidUtilities.setLightNavigationBar(bottomSheet.getWindow(), false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
bottomSheet.getWindow().setNavigationBarColor(0xff000000);
@ -436,6 +434,7 @@ public class CameraScanActivity extends BaseFragment {
} else {
actionBar.setBackgroundDrawable(null);
actionBar.setAddToContainer(false);
actionBar.setTitleColor(0xffffffff);
actionBar.setItemsColor(0xffffffff, false);
actionBar.setItemsBackgroundColor(0x22ffffff, false);
viewGroup.setBackgroundColor(Theme.getColor(Theme.key_wallet_blackBackground));

View File

@ -1056,8 +1056,7 @@ public class DialogCell extends BaseCell {
checkMessage = false;
messageString = formatArchivedDialogNames();
} else if (message.messageOwner instanceof TLRPC.TL_messageService) {
if (ChatObject.isChannelAndNotMegaGroup(chat) && (message.messageOwner.action instanceof TLRPC.TL_messageActionHistoryClear ||
message.messageOwner.action instanceof TLRPC.TL_messageActionChannelMigrateFrom)) {
if (ChatObject.isChannelAndNotMegaGroup(chat) && (message.messageOwner.action instanceof TLRPC.TL_messageActionChannelMigrateFrom)) {
messageString = "";
showChecks = false;
} else {

View File

@ -2763,9 +2763,18 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
@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 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
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 class ChatActivity extends BaseFragment implements NotificationCenter.Not
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 class ChatActivity extends BaseFragment implements NotificationCenter.Not
}
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 class ChatActivity extends BaseFragment implements NotificationCenter.Not
} 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not
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 class ChatActivity extends BaseFragment implements NotificationCenter.Not
@Override
public void run() {
if (clicked || dialog_id == 0 || getUserConfig().clientUserId == dialog_id) {
if (clicked) {
return;
}
clicked = true;
@ -23474,6 +23483,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
} 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;

View File

@ -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);
}
}

View File

@ -1442,7 +1442,11 @@ public class AlertsCreator {
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 @@ public class AlertsCreator {
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 @@ public class AlertsCreator {
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 @@ public class AlertsCreator {
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 @@ public class AlertsCreator {
});
}
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 @@ public class AlertsCreator {
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 class AlertsCreator {
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 @@ public class AlertsCreator {
} 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 class AlertsCreator {
}
};
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 class AlertsCreator {
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 class AlertsCreator {
builder.setCustomView(container);
BottomSheet bottomSheet = builder.show();
bottomSheet.setBackgroundColor(datePickerColors.backgroundColor);
checkMuteForButton(dayPicker, hourPicker, buttonTextView, false);
return builder;
}
@ -3191,46 +3230,6 @@ public class AlertsCreator {
}
}
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();

View File

@ -206,6 +206,9 @@ public class BotCommandsMenuView extends View {
}
public void setMenuText(String menuText) {
if (menuText == null) {
menuText = LocaleController.getString(R.string.BotsMenuTitle);
}
this.menuText = menuText;
menuTextLayout = null;
requestLayout();

View File

@ -20,8 +20,10 @@ import android.graphics.drawable.Drawable;
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.WebSettings;
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.AlertDialog;
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.List;
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<Uri[]> 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 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent
}
}
};
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 class BotWebViewContainer extends FrameLayout implements NotificationCent
@Override
public boolean onCheckIsTextEditor() {
return true;
return BotWebViewContainer.this.isFocusable();
}
@Override
@ -180,12 +225,20 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent
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 class BotWebViewContainer extends FrameLayout implements NotificationCent
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 class BotWebViewContainer extends FrameLayout implements NotificationCent
}
}
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 class BotWebViewContainer extends FrameLayout implements NotificationCent
set.start();
mUrl = url;
isPageLoaded = true;
BotWebViewContainer.this.setFocusable(true);
delegate.onWebAppReady();
}
public boolean hasUserPermissions() {
@ -419,6 +516,10 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent
}
}
public boolean isPageLoaded() {
return isPageLoaded;
}
public void setParentActivity(Activity parentActivity) {
this.parentActivity = parentActivity;
}
@ -467,11 +568,6 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent
}
}
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 class BotWebViewContainer extends FrameLayout implements NotificationCent
}
public void invalidateViewPortHeight(boolean isStable, boolean force) {
invalidate();
if (!isPageLoaded && !force) {
return;
}
@ -498,7 +595,7 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent
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 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent
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 class BotWebViewContainer extends FrameLayout implements NotificationCent
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 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent
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 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent
}
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 class BotWebViewContainer extends FrameLayout implements NotificationCent
@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 class BotWebViewContainer extends FrameLayout implements NotificationCent
}
}
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 class BotWebViewContainer extends FrameLayout implements NotificationCent
}
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 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent
invalidateViewPortHeight(!hasSwipeInProgress, true);
break;
}
case "web_app_request_theme": {
notifyThemeChanged();
break;
}
case "web_app_ready": {
setPageLoaded(webView.getUrl());
break;
@ -740,11 +886,16 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent
}
}
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 class BotWebViewContainer extends FrameLayout implements NotificationCent
/**
* 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 class BotWebViewContainer extends FrameLayout implements NotificationCent
* 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() {}
}
}

View File

@ -28,7 +28,6 @@ import org.telegram.messenger.MessageObject;
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<BotWebViewMenuContainer> ACTION_BAR_TRANSITION_PROGRESS_VALUE = new SimpleFloatPropertyCompat<BotWebViewMenuContainer>("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 class BotWebViewMenuContainer extends FrameLayout implements Notification
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 class BotWebViewMenuContainer extends FrameLayout implements Notification
linePaint.setStrokeCap(Paint.Cap.ROUND);
dimPaint.setColor(0x40000000);
backgroundPaint.setColor(getColor(Theme.key_windowBackgroundWhite));
swipeContainer = new ChatAttachAlertBotWebViewLayout.WebViewSwipeContainer(context) {
@Override
@ -207,7 +174,7 @@ public class BotWebViewMenuContainer extends FrameLayout implements Notification
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 class BotWebViewMenuContainer extends FrameLayout implements Notification
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 class BotWebViewMenuContainer extends FrameLayout implements Notification
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 class BotWebViewMenuContainer extends FrameLayout implements Notification
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 class BotWebViewMenuContainer extends FrameLayout implements Notification
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 class BotWebViewMenuContainer extends FrameLayout implements Notification
});
}
NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.webViewResultSent);
NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.didSetNewTheme);
}
@Override
@ -351,7 +347,9 @@ public class BotWebViewMenuContainer extends FrameLayout implements Notification
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 class BotWebViewMenuContainer extends FrameLayout implements Notification
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 @@ public class BotWebViewMenuContainer extends FrameLayout implements Notification
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 @@ public class BotWebViewMenuContainer extends FrameLayout implements Notification
@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 class BotWebViewMenuContainer extends FrameLayout implements Notification
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 class BotWebViewMenuContainer extends FrameLayout implements Notification
.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 @@ public class BotWebViewMenuContainer extends FrameLayout implements Notification
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 class BotWebViewMenuContainer extends FrameLayout implements Notification
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 class BotWebViewMenuContainer extends FrameLayout implements Notification
}
});
swipeContainer.addView(webViewContainer);
swipeContainer.setWebView(webViewContainer.getWebView());
isLoaded = false;
AndroidUtilities.cancelRunOnUIThread(pollRunnable);
@ -652,6 +659,11 @@ public class BotWebViewMenuContainer extends FrameLayout implements Notification
if (this.queryId == queryId) {
dismiss();
}
} else if (id == NotificationCenter.didSetNewTheme) {
webViewContainer.updateFlickerBackgroundColor(getColor(Theme.key_windowBackgroundWhite));
invalidate();
invalidateActionBar();
AndroidUtilities.runOnUIThread(this::invalidateActionBar, 300);
}
}
}

View File

@ -25,6 +25,7 @@ import android.widget.TextView;
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 class BotWebViewSheet extends Dialog implements NotificationCenter.Notifi
}
};
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 class BotWebViewSheet extends Dialog implements NotificationCenter.Notifi
@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 class BotWebViewSheet extends Dialog implements NotificationCenter.Notifi
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 class BotWebViewSheet extends Dialog implements NotificationCenter.Notifi
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 class BotWebViewSheet extends Dialog implements NotificationCenter.Notifi
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 class BotWebViewSheet extends Dialog implements NotificationCenter.Notifi
@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 class BotWebViewSheet extends Dialog implements NotificationCenter.Notifi
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 class BotWebViewSheet extends Dialog implements NotificationCenter.Notifi
});
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 class BotWebViewSheet extends Dialog implements NotificationCenter.Notifi
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 @@ public class BotWebViewSheet extends Dialog implements NotificationCenter.Notifi
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 class BotWebViewSheet extends Dialog implements NotificationCenter.Notifi
}
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 class BotWebViewSheet extends Dialog implements NotificationCenter.Notifi
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 class BotWebViewSheet extends Dialog implements NotificationCenter.Notifi
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 class BotWebViewSheet extends Dialog implements NotificationCenter.Notifi
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 class BotWebViewSheet extends Dialog implements NotificationCenter.Notifi
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 class BotWebViewSheet extends Dialog implements NotificationCenter.Notifi
if (this.queryId == queryId) {
dismiss();
}
} else if (id == NotificationCenter.didSetNewTheme) {
frameLayout.invalidate();
webViewContainer.updateFlickerBackgroundColor(getColor(Theme.key_windowBackgroundWhite));
updateActionBarColors();
updateLightStatusBar();
}
}
}

View File

@ -42,18 +42,18 @@ public class ChatActivityBotWebViewButton extends FrameLayout {
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 class ChatActivityBotWebViewButton extends FrameLayout {
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 class ChatActivityBotWebViewButton extends FrameLayout {
}
}).start();
}
invalidate();
}
public void setProgress(float progress) {
@ -110,9 +113,10 @@ public class ChatActivityBotWebViewButton extends FrameLayout {
@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 class ChatActivityBotWebViewButton extends FrameLayout {
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);

View File

@ -1720,6 +1720,7 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific
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 class ChatActivityEnterView extends BlurredFrameLayout implements Notific
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 class ChatActivityEnterView extends BlurredFrameLayout implements Notific
}
}
private boolean hasBotWebView() {
public boolean onBotWebViewBackPressed() {
return botWebViewMenuContainer != null && botWebViewMenuContainer.onBackPressed();
}
public boolean hasBotWebView() {
return botMenuButtonType == BotMenuButtonType.WEB_VIEW;
}
@ -8356,6 +8362,24 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific
}
} 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 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific
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 class ChatActivityEnterView extends BlurredFrameLayout implements Notific
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;
}

View File

@ -29,6 +29,7 @@ import android.graphics.Rect;
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 class ChatAttachAlert extends BottomSheet implements NotificationCenter.N
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 class ChatAttachAlert extends BottomSheet implements NotificationCenter.N
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 class ChatAttachAlert extends BottomSheet implements NotificationCenter.N
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 class ChatAttachAlert extends BottomSheet implements NotificationCenter.N
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 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N
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 class ChatAttachAlert extends BottomSheet implements NotificationCenter.N
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 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N
}
}
@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 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N
} 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())));

View File

@ -4,6 +4,7 @@ import android.animation.Animator;
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 class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert
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 class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert
return super.dispatchTouchEvent(ev);
}
};
webViewContainer.getWebView().setVerticalScrollBarEnabled(false);
swipeContainer = new WebViewSwipeContainer(context) {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
@ -148,7 +157,6 @@ public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert
}
};
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 class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert
}
});
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 class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert
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 class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert
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 @@ public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert
@Override
void onShown() {
requestEnableKeyboard();
if (webViewContainer.isPageLoaded()) {
requestEnableKeyboard();
}
swipeContainer.setSwipeOffsetAnimationDisallowed(false);
AndroidUtilities.runOnUIThread(() -> webViewContainer.restoreButtonData());
@ -263,13 +285,13 @@ public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert
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 class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert
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 class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert
@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 @@ public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert
return AndroidUtilities.dp(56);
}
@Override
boolean onBackPressed() {
return webViewContainer.onBackPressed();
}
@Override
public void requestLayout() {
if (ignoreLayout) {
@ -467,6 +496,8 @@ public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert
needReload = true;
parentAlert.dismiss();
}
} else if (id == NotificationCenter.didSetNewTheme) {
webViewContainer.updateFlickerBackgroundColor(getThemedColor(Theme.key_dialogBackground));
}
}
@ -492,6 +523,8 @@ public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert
private SpringAnimation scrollAnimator;
private int swipeStickyRange;
public WebViewSwipeContainer(@NonNull Context context) {
super(context);
@ -502,10 +535,10 @@ public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert
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 class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert
@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 class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert
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 class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert
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 class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert
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 class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert
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) {

View File

@ -69,19 +69,20 @@ public class ChatNotificationsPopupWrapper {
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);
});
});

View File

@ -74,7 +74,7 @@ public class CrossfadeDrawable extends Drawable {
@Override
public void setColorFilter(ColorFilter colorFilter) {
topDrawable.setColorFilter(colorFilter);
}
@Override

View File

@ -324,7 +324,7 @@ public class MotionBackgroundDrawable extends Drawable {
} 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;

View File

@ -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 @@ public class PopupSwipeBackLayout extends FrameLayout {
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 class PopupSwipeBackLayout extends FrameLayout {
foregroundAnimator = animator;
}
public void setForegroundColor(int color) {
foregroundColor = color;
}
public interface OnSwipeBackProgressListener {
void onSwipeBackProgress(PopupSwipeBackLayout layout, float toProgress, float progress);
}

View File

@ -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 class SearchDownloadsContainer extends FrameLayout implements Notificatio
recyclerListView.setEmptyView(emptyView);
FileLoader.getInstance(currentAccount).getCurrentLoadingFiles(currentLoadingFiles);
update(false);
}
private void checkFilesExist() {
if (checkingFilesExist) {
return;
}
checkingFilesExist = true;
Utilities.searchQueue.postRunnable(() -> {
ArrayList<MessageObject> currentLoadingFiles = new ArrayList<>();
ArrayList<MessageObject> recentLoadingFiles = new ArrayList<>();
ArrayList<MessageObject> moveToRecent = new ArrayList<>();
ArrayList<MessageObject> 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 class SearchDownloadsContainer extends FrameLayout implements Notificatio
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 @@ public class SearchDownloadsContainer extends FrameLayout implements Notificatio
if (getVisibility() == View.VISIBLE) {
DownloadController.getInstance(currentAccount).clearUnviewedDownloads();
}
checkFilesExist();
update(false);
}
@Override

View File

@ -6581,6 +6581,18 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter
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;

View File

@ -150,8 +150,9 @@ public class TimerDrawable extends Drawable {
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);
}

View File

@ -3,6 +3,8 @@ package org.telegram.ui;
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 @@ public class DownloadProgressIcon extends View implements NotificationCenter.Not
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;

View File

@ -242,7 +242,11 @@ public class IntroActivity extends BaseFragment implements NotificationCenter.No
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);
}

View File

@ -275,12 +275,12 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar
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 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar
.setNegativeButton(LocaleController.getString(R.string.Cancel), null)
.show();
}
} else {
BulletinFactory.of(mainFragmentsStack.get(mainFragmentsStack.size() - 1)).createErrorBulletin(LocaleController.getString(R.string.BotCantAddToAttachMenu)).show();
}
}));
} else {

View File

@ -4749,8 +4749,8 @@ public class LoginActivity extends BaseFragment {
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);

View File

@ -37,6 +37,7 @@ import org.telegram.messenger.AndroidUtilities;
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 class NotificationsCustomSettingsActivity extends BaseFragment implements
}
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")) {

View File

@ -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 @@ public class NotificationsSoundActivity extends BaseFragment implements ChatAtta
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 @@ public class NotificationsSoundActivity extends BaseFragment implements ChatAtta
}
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 @@ public class NotificationsSoundActivity extends BaseFragment implements ChatAtta
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 @@ public class NotificationsSoundActivity extends BaseFragment implements ChatAtta
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 @@ public class NotificationsSoundActivity extends BaseFragment implements ChatAtta
manager.setType(RingtoneManager.TYPE_NOTIFICATION);
Cursor cursor = manager.getCursor();
systemTones.clear();
Tone noSoundTone = new Tone();
@ -942,15 +945,4 @@ public class NotificationsSoundActivity extends BaseFragment implements ChatAtta
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();
// }
}
}

View File

@ -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<View, Boolean> actionBarItemsVisibility = new HashMap<>(3);
private LinearLayout bottomButtonsLayout;
private ImageView shareButton;
@ -412,7 +411,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat
@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 class PhotoViewer implements NotificationCenter.NotificationCenterDelegat
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 class PhotoViewer implements NotificationCenter.NotificationCenterDelegat
}
} 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 class PhotoViewer implements NotificationCenter.NotificationCenterDelegat
@Override
public boolean canOpenMenu() {
menuItemSpeed.setVisibility(View.INVISIBLE);
if (currentMessageObject != null || currentSecureDocument != null) {
return true;
} else if (currentFileLocationVideo != null) {
@ -4428,62 +4406,41 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat
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 class PhotoViewer implements NotificationCenter.NotificationCenterDelegat
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 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat
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 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat
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 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat
}
menuItem.checkHideMenuItem();
} else {
speedItem.setVisibility(View.GONE);
speedGap.setVisibility(View.GONE);
menuItem.hideSubItem(gallery_menu_openin);
menuItem.checkHideMenuItem();
@ -10989,10 +10947,11 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat
}
}
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 class PhotoViewer implements NotificationCenter.NotificationCenterDelegat
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);

View File

@ -60,6 +60,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
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 class ProfileActivity extends BaseFragment implements NotificationCenter.
}
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 class ProfileActivity extends BaseFragment implements NotificationCenter.
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 class ProfileActivity extends BaseFragment implements NotificationCenter.
@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 class ProfileActivity extends BaseFragment implements NotificationCenter.
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 class ProfileActivity extends BaseFragment implements NotificationCenter.
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);

View File

@ -504,7 +504,11 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi
@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 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi
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 class ProfileNotificationsActivity extends BaseFragment implements Notifi
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 class ProfileNotificationsActivity extends BaseFragment implements Notifi
@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);
}
}
}

View File

@ -904,11 +904,6 @@ public class SessionsActivity extends BaseFragment implements NotificationCenter
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);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 755 B

After

Width:  |  Height:  |  Size: 898 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 638 B

After

Width:  |  Height:  |  Size: 809 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 509 B

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 444 B

After

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 984 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 829 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -186,6 +186,7 @@
<string name="AutoDelete1Hour">1 hour</string>
<string name="AutoDeleteInfo">Automatically delete new messages sent in this chat after a certain period of time.</string>
<string name="AutoDeleteNever">Never</string>
<string name="MuteNever">Never</string>
<string name="AutoDeleteSet">Set for this chat</string>
<string name="AutoDeleteSetInfo">Messages in this chat are automatically\ndeleted %1$s after they are sent.</string>
<string name="AutoDeleteHintOnText">New messages in this chat will be automatically deleted in %1$s.</string>
@ -230,7 +231,7 @@
<string name="CompatibilityChat">%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.</string>
<string name="SearchMessages">Messages</string>
<string name="Search">Search</string>
<string name="MuteNotifications">Mute notifications</string>
<string name="MuteNotifications">Mute forever</string>
<string name="MuteFor">Mute for %1$s</string>
<string name="MuteFor1h">Mute for 1 hour</string>
<string name="UnmuteNotifications">Unmute</string>
@ -2150,6 +2151,8 @@
<string name="DebugMenuEnableForceRtmpStreamFlag">Enable forced RTMP Stream Flag</string>
<string name="DebugMenuDisableForceRtmpStreamFlag">Disable forced RTMP Stream Flag</string>
<string name="DebugMenuClearWebViewCache">Clear WebView cache</string>
<string name="DebugMenuEnableWebViewDebug">Enable WebView debug</string>
<string name="DebugMenuWebViewDebugEnabled">Debug enabled!</string>
<string name="ChangeLanguageLater">You can change your language later in Settings.</string>
<string name="ChooseYourLanguage">Choose your language</string>
<string name="ChooseYourLanguageOther">Other</string>
@ -3335,6 +3338,7 @@
<string name="UserChannelTooMuchJoin">Sorry, the target user is a member of too many groups and channels. Please ask them to leave some first.</string>
<!--Alert messages-->
<string name="DeleteAllMessagesAlert">Warning! This will **delete all messages** in this chat for **both** participants.</string>
<string name="DeleteAllMessagesChannelAlert">Warning! This will **delete all messages** in this channel.</string>
<string name="DeleteAllMessagesSavedAlert">Warning! This will **delete all messages** in this chat.</string>
<string name="DeleteAll">Delete All</string>
<string name="StopLoading">Stop loading?</string>
@ -3406,7 +3410,8 @@
<string name="StopVerification">Do you want to stop the verification process?</string>
<string name="AreYouSureClearHistoryWithUser">Are you sure you want to clear your chat history with **%1$s**?</string>
<string name="AreYouSureClearHistoryWithSecretUser">Are you sure you want to clear your secret chat history with **%1$s**?</string>
<string name="AreYouSureClearHistoryWithChat">Are you sure you want to clear the chat history in **%1$s**?</string>
<string name="AreYouSureClearHistoryWithChat">Are you sure you want to clear the chat history in **%1$s** for all users?</string>
<string name="AreYouSureClearHistoryWithChannel">Are you sure you want to clear the channel history in **%1$s**?</string>
<string name="AreYouSureClearHistory">Are you sure you want to delete all messages in this chat?</string>
<string name="AreYouSureClearHistorySavedMessages">Are you sure you want to clear **Saved Messages**?</string>
<string name="AreYouSureClearHistoryChannel">Delete all cached text and media from this channel?</string>
@ -5067,38 +5072,38 @@
<string name="NobodyViewed">Nobody viewed</string>
<string name="QrCode">QR Code</string>
<string name="PushReactText">%1$s: %2$s to your "%3$s"</string>
<string name="PushReactNoText">%1$s: %2$s to your message</string>
<string name="PushReactPhoto">%1$s: %2$s to your photo</string>
<string name="PushReactVideo">%1$s: %2$s to your video</string>
<string name="PushReactRound">%1$s: %2$s to your video message</string>
<string name="PushReactDoc">%1$s: %2$s to your file</string>
<string name="PushReactSticker">%1$s: %2$s to your %3$s sticker</string>
<string name="PushReactAudio">%1$s: %2$s to your voice message</string>
<string name="PushReactContect">%1$s: %2$s to your contact %3$s</string>
<string name="PushReactGeo">%1$s: %2$s to your map</string>
<string name="PushReactGeoLocation">%1$s: %2$s to your live location</string>
<string name="PushReactPoll">%1$s: %2$s to your poll %3$s</string>
<string name="PushReactQuiz">%1$s: %2$s to your quiz %3$s</string>
<string name="PushReactGame">%1$s: %2$s to your game</string>
<string name="PushReactInvoice">%1$s: %2$s to your invoice</string>
<string name="PushReactGif">%1$s: %2$s to your GIF</string>
<string name="PushChatReactText">%1$s: %3$s in %2$s to your "%4$s"</string>
<string name="PushChatReactNotext">%1$s: %3$s to your message in %2$s</string>
<string name="PushChatReactPhoto">%1$s: %3$s to your photo in %2$s</string>
<string name="PushChatReactVideo">%1$s: %3$s to your video in %2$s</string>
<string name="PushChatReactRound">%1$s: %3$s to your video message in %2$s</string>
<string name="PushChatReactDoc">%1$s: %3$s to your file in %2$s</string>
<string name="PushChatReactSticker">%1$s: %3$s to your %4$s sticker in %2$s</string>
<string name="PushChatReactAudio">%1$s: %3$s to your voice message in %2$s</string>
<string name="PushChatReactContact">%1$s: %3$s to your contact %4$s in %2$s</string>
<string name="PushChatReactGeo">%1$s: %3$s to your map in %2$s</string>
<string name="PushChatReactGeoLive">%1$s: %3$s to your live location in %2$s</string>
<string name="PushChatReactPoll">%1$s: %3$s to your poll %4$s in %2$s</string>
<string name="PushChatReactQuiz">%1$s: %3$s to your quiz %4$s in %2$s</string>
<string name="PushChatReactGame">%1$s: %3$s to your game in %2$s</string>
<string name="PushChatReactInvoice">%1$s: %3$s to your invoice in %2$s</string>
<string name="PushChatReactGif">%1$s: %3$s to your GIF in %2$s</string>
<string name="PushReactText">%2$s to your "%3$s"</string>
<string name="PushReactNoText">%2$s to your message</string>
<string name="PushReactPhoto">%2$s to your photo</string>
<string name="PushReactVideo">%2$s to your video</string>
<string name="PushReactRound">%2$s to your video message</string>
<string name="PushReactDoc">%2$s to your file</string>
<string name="PushReactSticker">%2$s to your %3$s sticker</string>
<string name="PushReactAudio">%2$s to your voice message</string>
<string name="PushReactContect">%2$s to your contact %3$s</string>
<string name="PushReactGeo">%2$s to your map</string>
<string name="PushReactGeoLocation">%2$s to your live location</string>
<string name="PushReactPoll">%2$s to your poll %3$s</string>
<string name="PushReactQuiz">%2$s to your quiz %3$s</string>
<string name="PushReactGame">%2$s to your game</string>
<string name="PushReactInvoice">%2$s to your invoice</string>
<string name="PushReactGif">%2$s to your GIF</string>
<string name="PushChatReactText">%3$s in %2$s to your "%4$s"</string>
<string name="PushChatReactNotext">%3$s to your message in %2$s</string>
<string name="PushChatReactPhoto">%3$s to your photo in %2$s</string>
<string name="PushChatReactVideo">%3$s to your video in %2$s</string>
<string name="PushChatReactRound">%3$s to your video message in %2$s</string>
<string name="PushChatReactDoc">%3$s to your file in %2$s</string>
<string name="PushChatReactSticker">%3$s to your %4$s sticker in %2$s</string>
<string name="PushChatReactAudio">%3$s to your voice message in %2$s</string>
<string name="PushChatReactContact">%3$s to your contact %4$s in %2$s</string>
<string name="PushChatReactGeo">%3$s to your map in %2$s</string>
<string name="PushChatReactGeoLive">%3$s to your live location in %2$s</string>
<string name="PushChatReactPoll">%3$s to your poll %4$s in %2$s</string>
<string name="PushChatReactQuiz">%3$s to your quiz %4$s in %2$s</string>
<string name="PushChatReactGame">%3$s to your game in %2$s</string>
<string name="PushChatReactInvoice">%3$s to your invoice in %2$s</string>
<string name="PushChatReactGif">%3$s to your GIF in %2$s</string>
<string name="BlurInChat">Blur in chat</string>
<string name="AllowBackgroundActivity">Allow background activity</string>
@ -5186,6 +5191,7 @@
<string name="BotCantAddToAttachMenu">This bot can\'t be added to the attachment menu.</string>
<string name="BotAlreadyAddedToAttachMenu">This bot is already in your attachment menu.</string>
<string name="BotRemoveFromMenu">Remove **%1$s** from the attachment menu?</string>
<string name="BotRemoveInlineFromMenu">Remove **%1$s** from suggestions?</string>
<string name="BotWebViewRequestGeolocationPermission">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.</string>
<string name="BotWebViewRequestGeolocationPermissionWithHint">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.</string>
<string name="BotWebViewRequestMicrophonePermission">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.</string>
@ -5202,4 +5208,8 @@
<string name="DeleteTonesMessage_few">Do you want to delete **%d notification sounds**?</string>
<string name="DeleteTonesMessage_many">Do you want to delete **%d notification sounds**?</string>
<string name="DeleteTonesMessage_other">Do you want to delete **%d notification sounds**?</string>
<string name="CustomSound">Custom Sound</string>
<string name="ClearForAll">Clear for all</string>
<string name="ClearForMe">Clear for me</string>
<string name="BotWebViewNotAvailablePlaceholder">WebView not available. Please update it to use WebView bots.</string>
</resources>