1
0
mirror of https://github.com/MGislv/NekoX.git synced 2024-07-02 10:33:36 +00:00

Database & Configure

This commit is contained in:
世界 2020-06-25 15:25:17 +00:00
parent 9196691935
commit 54ba1537ee
No known key found for this signature in database
GPG Key ID: CD109927C34A63C4
33 changed files with 7618 additions and 324 deletions

View File

@ -0,0 +1,993 @@
/*
* This is the source code of Telegram for Android v. 5.x.x.
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* Copyright Nikolai Kudashov, 2013-2018.
*/
package org.telegram.messenger;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Base64;
import android.util.SparseArray;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import org.json.JSONArray;
import org.json.JSONObject;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.NativeByteBuffer;
import org.telegram.tgnet.TLRPC;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
public class GcmPushListenerService extends FirebaseMessagingService {
public static final int NOTIFICATION_ID = 1;
private CountDownLatch countDownLatch = new CountDownLatch(1);
@Override
public void onMessageReceived(RemoteMessage message) {
String from = message.getFrom();
final Map data = message.getData();
final long time = message.getSentTime();
final long receiveTime = SystemClock.elapsedRealtime();
if (BuildVars.LOGS_ENABLED) {
FileLog.d("GCM received data: " + data + " from: " + from);
}
AndroidUtilities.runOnUIThread(() -> {
ApplicationLoader.postInitApplication();
Utilities.stageQueue.postRunnable(() -> {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("GCM START PROCESSING");
}
int currentAccount = -1;
String loc_key = null;
String jsonString = null;
try {
Object value = data.get("p");
if (!(value instanceof String)) {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("GCM DECRYPT ERROR 1");
}
onDecryptError();
return;
}
byte[] bytes = Base64.decode((String) value, Base64.URL_SAFE);
NativeByteBuffer buffer = new NativeByteBuffer(bytes.length);
buffer.writeBytes(bytes);
buffer.position(0);
if (SharedConfig.pushAuthKeyId == null) {
SharedConfig.pushAuthKeyId = new byte[8];
byte[] authKeyHash = Utilities.computeSHA1(SharedConfig.pushAuthKey);
System.arraycopy(authKeyHash, authKeyHash.length - 8, SharedConfig.pushAuthKeyId, 0, 8);
}
byte[] inAuthKeyId = new byte[8];
buffer.readBytes(inAuthKeyId, true);
if (!Arrays.equals(SharedConfig.pushAuthKeyId, inAuthKeyId)) {
onDecryptError();
if (BuildVars.LOGS_ENABLED) {
FileLog.d(String.format(Locale.US, "GCM DECRYPT ERROR 2 k1=%s k2=%s, key=%s", Utilities.bytesToHex(SharedConfig.pushAuthKeyId), Utilities.bytesToHex(inAuthKeyId), Utilities.bytesToHex(SharedConfig.pushAuthKey)));
}
return;
}
byte[] messageKey = new byte[16];
buffer.readBytes(messageKey, true);
MessageKeyData messageKeyData = MessageKeyData.generateMessageKeyData(SharedConfig.pushAuthKey, messageKey, true, 2);
Utilities.aesIgeEncryption(buffer.buffer, messageKeyData.aesKey, messageKeyData.aesIv, false, false, 24, bytes.length - 24);
byte[] messageKeyFull = Utilities.computeSHA256(SharedConfig.pushAuthKey, 88 + 8, 32, buffer.buffer, 24, buffer.buffer.limit());
if (!Utilities.arraysEquals(messageKey, 0, messageKeyFull, 8)) {
onDecryptError();
if (BuildVars.LOGS_ENABLED) {
FileLog.d(String.format("GCM DECRYPT ERROR 3, key = %s", Utilities.bytesToHex(SharedConfig.pushAuthKey)));
}
return;
}
int len = buffer.readInt32(true);
byte[] strBytes = new byte[len];
buffer.readBytes(strBytes, true);
jsonString = new String(strBytes);
JSONObject json = new JSONObject(jsonString);
if (json.has("loc_key")) {
loc_key = json.getString("loc_key");
} else {
loc_key = "";
}
JSONObject custom;
Object object = json.get("custom");
if (object instanceof JSONObject) {
custom = json.getJSONObject("custom");
} else {
custom = new JSONObject();
}
Object userIdObject;
if (json.has("user_id")) {
userIdObject = json.get("user_id");
} else {
userIdObject = null;
}
int accountUserId;
if (userIdObject == null) {
accountUserId = UserConfig.getInstance(UserConfig.selectedAccount).getClientUserId();
} else {
if (userIdObject instanceof Integer) {
accountUserId = (Integer) userIdObject;
} else if (userIdObject instanceof String) {
accountUserId = Utilities.parseInt((String) userIdObject);
} else {
accountUserId = UserConfig.getInstance(UserConfig.selectedAccount).getClientUserId();
}
}
int account = UserConfig.selectedAccount;
for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) {
if (UserConfig.getInstance(a).getClientUserId() == accountUserId) {
account = a;
break;
}
}
final int accountFinal = currentAccount = account;
if (!UserConfig.getInstance(currentAccount).isClientActivated()) {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("GCM ACCOUNT NOT ACTIVATED");
}
countDownLatch.countDown();
return;
}
Object obj = data.get("google.sent_time");
switch (loc_key) {
case "DC_UPDATE": {
int dc = custom.getInt("dc");
String addr = custom.getString("addr");
String[] parts = addr.split(":");
if (parts.length != 2) {
countDownLatch.countDown();
return;
}
String ip = parts[0];
int port = Integer.parseInt(parts[1]);
ConnectionsManager.getInstance(currentAccount).applyDatacenterAddress(dc, ip, port);
ConnectionsManager.getInstance(currentAccount).resumeNetworkMaybe();
countDownLatch.countDown();
return;
}
case "MESSAGE_ANNOUNCEMENT": {
TLRPC.TL_updateServiceNotification update = new TLRPC.TL_updateServiceNotification();
update.popup = false;
update.flags = 2;
update.inbox_date = (int) (time / 1000);
update.message = json.getString("message");
update.type = "announcement";
update.media = new TLRPC.TL_messageMediaEmpty();
final TLRPC.TL_updates updates = new TLRPC.TL_updates();
updates.updates.add(update);
Utilities.stageQueue.postRunnable(() -> MessagesController.getInstance(accountFinal).processUpdates(updates, false));
ConnectionsManager.getInstance(currentAccount).resumeNetworkMaybe();
countDownLatch.countDown();
return;
}
case "SESSION_REVOKE": {
AndroidUtilities.runOnUIThread(() -> {
if (UserConfig.getInstance(accountFinal).getClientUserId() != 0) {
UserConfig.getInstance(accountFinal).clearConfig();
MessagesController.getInstance(accountFinal).performLogout(0);
}
});
countDownLatch.countDown();
return;
}
case "GEO_LIVE_PENDING": {
//Utilities.stageQueue.postRunnable(() -> LocationController.getInstance(accountFinal).setNewLocationEndWatchTime());
countDownLatch.countDown();
return;
}
}
int channel_id;
int chat_id;
int user_id;
long dialog_id = 0;
boolean scheduled;
if (custom.has("channel_id")) {
channel_id = custom.getInt("channel_id");
dialog_id = -channel_id;
} else {
channel_id = 0;
}
if (custom.has("from_id")) {
user_id = custom.getInt("from_id");
dialog_id = user_id;
} else {
user_id = 0;
}
if (custom.has("chat_id")) {
chat_id = custom.getInt("chat_id");
dialog_id = -chat_id;
} else {
chat_id = 0;
}
if (custom.has("encryption_id")) {
dialog_id = ((long) custom.getInt("encryption_id")) << 32;
}
if (custom.has("schedule")) {
scheduled = custom.getInt("schedule") == 1;
} else {
scheduled = false;
}
if (dialog_id == 0 && "ENCRYPTED_MESSAGE".equals(loc_key)) {
dialog_id = -(1L << 32);
}
boolean canRelease = true;
if (dialog_id != 0) {
if ("READ_HISTORY".equals(loc_key)) {
int max_id = custom.getInt("max_id");
final ArrayList<TLRPC.Update> updates = new ArrayList<>();
if (BuildVars.LOGS_ENABLED) {
FileLog.d("GCM received read notification max_id = " + max_id + " for dialogId = " + dialog_id);
}
if (channel_id != 0) {
TLRPC.TL_updateReadChannelInbox update = new TLRPC.TL_updateReadChannelInbox();
update.channel_id = channel_id;
update.max_id = max_id;
updates.add(update);
} else {
TLRPC.TL_updateReadHistoryInbox update = new TLRPC.TL_updateReadHistoryInbox();
if (user_id != 0) {
update.peer = new TLRPC.TL_peerUser();
update.peer.user_id = user_id;
} else {
update.peer = new TLRPC.TL_peerChat();
update.peer.chat_id = chat_id;
}
update.max_id = max_id;
updates.add(update);
}
MessagesController.getInstance(accountFinal).processUpdateArray(updates, null, null, false, 0);
} else if ("MESSAGE_DELETED".equals(loc_key)) {
String messages = custom.getString("messages");
String[] messagesArgs = messages.split(",");
SparseArray<ArrayList<Integer>> deletedMessages = new SparseArray<>();
ArrayList<Integer> ids = new ArrayList<>();
for (int a = 0; a < messagesArgs.length; a++) {
ids.add(Utilities.parseInt(messagesArgs[a]));
}
deletedMessages.put(channel_id, ids);
NotificationsController.getInstance(currentAccount).removeDeletedMessagesFromNotifications(deletedMessages);
MessagesController.getInstance(currentAccount).deleteMessagesByPush(dialog_id, ids, channel_id);
if (BuildVars.LOGS_ENABLED) {
FileLog.d("GCM received " + loc_key + " for dialogId = " + dialog_id + " mids = " + TextUtils.join(",", ids));
}
} else if (!TextUtils.isEmpty(loc_key)) {
int msg_id;
if (custom.has("msg_id")) {
msg_id = custom.getInt("msg_id");
} else {
msg_id = 0;
}
long random_id;
if (custom.has("random_id")) {
random_id = Utilities.parseLong(custom.getString("random_id"));
} else {
random_id = 0;
}
boolean processNotification = false;
if (msg_id != 0) {
Integer currentReadValue = MessagesController.getInstance(currentAccount).dialogs_read_inbox_max.get(dialog_id);
if (currentReadValue == null) {
currentReadValue = MessagesStorage.getInstance(currentAccount).getDialogReadMax(false, dialog_id);
MessagesController.getInstance(accountFinal).dialogs_read_inbox_max.put(dialog_id, currentReadValue);
}
if (msg_id > currentReadValue) {
processNotification = true;
}
} else if (random_id != 0) {
if (!MessagesStorage.getInstance(account).checkMessageByRandomId(random_id)) {
processNotification = true;
}
}
if (processNotification) {
int chat_from_id;
if (custom.has("chat_from_id")) {
chat_from_id = custom.getInt("chat_from_id");
} else {
chat_from_id = 0;
}
boolean mention = custom.has("mention") && custom.getInt("mention") != 0;
boolean silent = custom.has("silent") && custom.getInt("silent") != 0;
String[] args;
if (json.has("loc_args")) {
JSONArray loc_args = json.getJSONArray("loc_args");
args = new String[loc_args.length()];
for (int a = 0; a < args.length; a++) {
args[a] = loc_args.getString(a);
}
} else {
args = null;
}
String messageText = null;
String message1 = null;
String name = args[0];
String userName = null;
boolean localMessage = false;
boolean supergroup = false;
boolean pinned = false;
boolean channel = false;
boolean edited = custom.has("edit_date");
if (loc_key.startsWith("CHAT_")) {
supergroup = channel_id != 0;
userName = name;
name = args[1];
} else if (loc_key.startsWith("PINNED_")) {
supergroup = chat_from_id != 0;
pinned = true;
} else if (loc_key.startsWith("CHANNEL_")) {
channel = true;
}
if (BuildVars.LOGS_ENABLED) {
FileLog.d("GCM received message notification " + loc_key + " for dialogId = " + dialog_id + " mid = " + msg_id);
}
switch (loc_key) {
case "MESSAGE_TEXT":
case "CHANNEL_MESSAGE_TEXT": {
messageText = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, args[0], args[1]);
message1 = args[1];
break;
}
case "MESSAGE_NOTEXT": {
messageText = LocaleController.formatString("NotificationMessageNoText", R.string.NotificationMessageNoText, args[0]);
message1 = LocaleController.getString("Message", R.string.Message);
break;
}
case "MESSAGE_PHOTO": {
messageText = LocaleController.formatString("NotificationMessagePhoto", R.string.NotificationMessagePhoto, args[0]);
message1 = LocaleController.getString("AttachPhoto", R.string.AttachPhoto);
break;
}
case "MESSAGE_PHOTO_SECRET": {
messageText = LocaleController.formatString("NotificationMessageSDPhoto", R.string.NotificationMessageSDPhoto, args[0]);
message1 = LocaleController.getString("AttachDestructingPhoto", R.string.AttachDestructingPhoto);
break;
}
case "MESSAGE_VIDEO": {
messageText = LocaleController.formatString("NotificationMessageVideo", R.string.NotificationMessageVideo, args[0]);
message1 = LocaleController.getString("AttachVideo", R.string.AttachVideo);
break;
}
case "MESSAGE_VIDEO_SECRET": {
messageText = LocaleController.formatString("NotificationMessageSDVideo", R.string.NotificationMessageSDVideo, args[0]);
message1 = LocaleController.getString("AttachDestructingVideo", R.string.AttachDestructingVideo);
break;
}
case "MESSAGE_SCREENSHOT": {
messageText = LocaleController.getString("ActionTakeScreenshoot", R.string.ActionTakeScreenshoot).replace("un1", args[0]);
break;
}
case "MESSAGE_ROUND": {
messageText = LocaleController.formatString("NotificationMessageRound", R.string.NotificationMessageRound, args[0]);
message1 = LocaleController.getString("AttachRound", R.string.AttachRound);
break;
}
case "MESSAGE_DOC": {
messageText = LocaleController.formatString("NotificationMessageDocument", R.string.NotificationMessageDocument, args[0]);
message1 = LocaleController.getString("AttachDocument", R.string.AttachDocument);
break;
}
case "MESSAGE_STICKER": {
if (args.length > 1 && !TextUtils.isEmpty(args[1])) {
messageText = LocaleController.formatString("NotificationMessageStickerEmoji", R.string.NotificationMessageStickerEmoji, args[0], args[1]);
message1 = args[1] + " " + LocaleController.getString("AttachSticker", R.string.AttachSticker);
} else {
messageText = LocaleController.formatString("NotificationMessageSticker", R.string.NotificationMessageSticker, args[0]);
message1 = LocaleController.getString("AttachSticker", R.string.AttachSticker);
}
break;
}
case "MESSAGE_AUDIO": {
messageText = LocaleController.formatString("NotificationMessageAudio", R.string.NotificationMessageAudio, args[0]);
message1 = LocaleController.getString("AttachAudio", R.string.AttachAudio);
break;
}
case "MESSAGE_CONTACT": {
messageText = LocaleController.formatString("NotificationMessageContact2", R.string.NotificationMessageContact2, args[0], args[1]);
message1 = LocaleController.getString("AttachContact", R.string.AttachContact);
break;
}
case "MESSAGE_QUIZ": {
messageText = LocaleController.formatString("NotificationMessageQuiz2", R.string.NotificationMessageQuiz2, args[0], args[1]);
message1 = LocaleController.getString("QuizPoll", R.string.QuizPoll);
break;
}
case "MESSAGE_POLL": {
messageText = LocaleController.formatString("NotificationMessagePoll2", R.string.NotificationMessagePoll2, args[0], args[1]);
message1 = LocaleController.getString("Poll", R.string.Poll);
break;
}
case "MESSAGE_GEO": {
messageText = LocaleController.formatString("NotificationMessageMap", R.string.NotificationMessageMap, args[0]);
message1 = LocaleController.getString("AttachLocation", R.string.AttachLocation);
break;
}
case "MESSAGE_GEOLIVE": {
messageText = LocaleController.formatString("NotificationMessageLiveLocation", R.string.NotificationMessageLiveLocation, args[0]);
message1 = LocaleController.getString("AttachLiveLocation", R.string.AttachLiveLocation);
break;
}
case "MESSAGE_GIF": {
messageText = LocaleController.formatString("NotificationMessageGif", R.string.NotificationMessageGif, args[0]);
message1 = LocaleController.getString("AttachGif", R.string.AttachGif);
break;
}
case "MESSAGE_GAME": {
messageText = LocaleController.formatString("NotificationMessageGame", R.string.NotificationMessageGame, args[0], args[1]);
message1 = LocaleController.getString("AttachGame", R.string.AttachGame);
break;
}
case "MESSAGE_GAME_SCORE":
case "CHANNEL_MESSAGE_GAME_SCORE": {
messageText = LocaleController.formatString("NotificationMessageGameScored", R.string.NotificationMessageGameScored, args[0], args[1], args[2]);
break;
}
case "MESSAGE_INVOICE": {
messageText = LocaleController.formatString("NotificationMessageInvoice", R.string.NotificationMessageInvoice, args[0], args[1]);
message1 = LocaleController.getString("PaymentInvoice", R.string.PaymentInvoice);
break;
}
case "MESSAGE_FWDS": {
messageText = LocaleController.formatString("NotificationMessageForwardFew", R.string.NotificationMessageForwardFew, args[0], LocaleController.formatPluralString("messages", Utilities.parseInt(args[1])));
localMessage = true;
break;
}
case "MESSAGE_PHOTOS": {
messageText = LocaleController.formatString("NotificationMessageFew", R.string.NotificationMessageFew, args[0], LocaleController.formatPluralString("Photos", Utilities.parseInt(args[1])));
localMessage = true;
break;
}
case "MESSAGE_VIDEOS": {
messageText = LocaleController.formatString("NotificationMessageFew", R.string.NotificationMessageFew, args[0], LocaleController.formatPluralString("Videos", Utilities.parseInt(args[1])));
localMessage = true;
break;
}
case "MESSAGES": {
messageText = LocaleController.formatString("NotificationMessageAlbum", R.string.NotificationMessageAlbum, args[0]);
localMessage = true;
break;
}
case "CHANNEL_MESSAGE_NOTEXT": {
messageText = LocaleController.formatString("ChannelMessageNoText", R.string.ChannelMessageNoText, args[0]);
message1 = LocaleController.getString("Message", R.string.Message);
break;
}
case "CHANNEL_MESSAGE_PHOTO": {
messageText = LocaleController.formatString("ChannelMessagePhoto", R.string.ChannelMessagePhoto, args[0]);
message1 = LocaleController.getString("AttachPhoto", R.string.AttachPhoto);
break;
}
case "CHANNEL_MESSAGE_VIDEO": {
messageText = LocaleController.formatString("ChannelMessageVideo", R.string.ChannelMessageVideo, args[0]);
message1 = LocaleController.getString("AttachVideo", R.string.AttachVideo);
break;
}
case "CHANNEL_MESSAGE_ROUND": {
messageText = LocaleController.formatString("ChannelMessageRound", R.string.ChannelMessageRound, args[0]);
message1 = LocaleController.getString("AttachRound", R.string.AttachRound);
break;
}
case "CHANNEL_MESSAGE_DOC": {
messageText = LocaleController.formatString("ChannelMessageDocument", R.string.ChannelMessageDocument, args[0]);
message1 = LocaleController.getString("AttachDocument", R.string.AttachDocument);
break;
}
case "CHANNEL_MESSAGE_STICKER": {
if (args.length > 1 && !TextUtils.isEmpty(args[1])) {
messageText = LocaleController.formatString("ChannelMessageStickerEmoji", R.string.ChannelMessageStickerEmoji, args[0], args[1]);
message1 = args[1] + " " + LocaleController.getString("AttachSticker", R.string.AttachSticker);
} else {
messageText = LocaleController.formatString("ChannelMessageSticker", R.string.ChannelMessageSticker, args[0]);
message1 = LocaleController.getString("AttachSticker", R.string.AttachSticker);
}
break;
}
case "CHANNEL_MESSAGE_AUDIO": {
messageText = LocaleController.formatString("ChannelMessageAudio", R.string.ChannelMessageAudio, args[0]);
message1 = LocaleController.getString("AttachAudio", R.string.AttachAudio);
break;
}
case "CHANNEL_MESSAGE_CONTACT": {
messageText = LocaleController.formatString("ChannelMessageContact2", R.string.ChannelMessageContact2, args[0], args[1]);
message1 = LocaleController.getString("AttachContact", R.string.AttachContact);
break;
}
case "CHANNEL_MESSAGE_QUIZ": {
messageText = LocaleController.formatString("ChannelMessageQuiz2", R.string.ChannelMessageQuiz2, args[0], args[1]);
message1 = LocaleController.getString("QuizPoll", R.string.QuizPoll);
break;
}
case "CHANNEL_MESSAGE_POLL": {
messageText = LocaleController.formatString("ChannelMessagePoll2", R.string.ChannelMessagePoll2, args[0], args[1]);
message1 = LocaleController.getString("Poll", R.string.Poll);
break;
}
case "CHANNEL_MESSAGE_GEO": {
messageText = LocaleController.formatString("ChannelMessageMap", R.string.ChannelMessageMap, args[0]);
message1 = LocaleController.getString("AttachLocation", R.string.AttachLocation);
break;
}
case "CHANNEL_MESSAGE_GEOLIVE": {
messageText = LocaleController.formatString("ChannelMessageLiveLocation", R.string.ChannelMessageLiveLocation, args[0]);
message1 = LocaleController.getString("AttachLiveLocation", R.string.AttachLiveLocation);
break;
}
case "CHANNEL_MESSAGE_GIF": {
messageText = LocaleController.formatString("ChannelMessageGIF", R.string.ChannelMessageGIF, args[0]);
message1 = LocaleController.getString("AttachGif", R.string.AttachGif);
break;
}
case "CHANNEL_MESSAGE_GAME": {
messageText = LocaleController.formatString("NotificationMessageGame", R.string.NotificationMessageGame, args[0]);
message1 = LocaleController.getString("AttachGame", R.string.AttachGame);
break;
}
case "CHANNEL_MESSAGE_FWDS": {
messageText = LocaleController.formatString("ChannelMessageFew", R.string.ChannelMessageFew, args[0], LocaleController.formatPluralString("ForwardedMessageCount", Utilities.parseInt(args[1])).toLowerCase());
localMessage = true;
break;
}
case "CHANNEL_MESSAGE_PHOTOS": {
messageText = LocaleController.formatString("ChannelMessageFew", R.string.ChannelMessageFew, args[0], LocaleController.formatPluralString("Photos", Utilities.parseInt(args[1])));
localMessage = true;
break;
}
case "CHANNEL_MESSAGE_VIDEOS": {
messageText = LocaleController.formatString("ChannelMessageFew", R.string.ChannelMessageFew, args[0], LocaleController.formatPluralString("Videos", Utilities.parseInt(args[1])));
localMessage = true;
break;
}
case "CHANNEL_MESSAGES": {
messageText = LocaleController.formatString("ChannelMessageAlbum", R.string.ChannelMessageAlbum, args[0]);
localMessage = true;
break;
}
case "CHAT_MESSAGE_TEXT": {
messageText = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, args[0], args[1], args[2]);
message1 = args[2];
break;
}
case "CHAT_MESSAGE_NOTEXT": {
messageText = LocaleController.formatString("NotificationMessageGroupNoText", R.string.NotificationMessageGroupNoText, args[0], args[1]);
message1 = LocaleController.getString("Message", R.string.Message);
break;
}
case "CHAT_MESSAGE_PHOTO": {
messageText = LocaleController.formatString("NotificationMessageGroupPhoto", R.string.NotificationMessageGroupPhoto, args[0], args[1]);
message1 = LocaleController.getString("AttachPhoto", R.string.AttachPhoto);
break;
}
case "CHAT_MESSAGE_VIDEO": {
messageText = LocaleController.formatString("NotificationMessageGroupVideo", R.string.NotificationMessageGroupVideo, args[0], args[1]);
message1 = LocaleController.getString("AttachVideo", R.string.AttachVideo);
break;
}
case "CHAT_MESSAGE_ROUND": {
messageText = LocaleController.formatString("NotificationMessageGroupRound", R.string.NotificationMessageGroupRound, args[0], args[1]);
message1 = LocaleController.getString("AttachRound", R.string.AttachRound);
break;
}
case "CHAT_MESSAGE_DOC": {
messageText = LocaleController.formatString("NotificationMessageGroupDocument", R.string.NotificationMessageGroupDocument, args[0], args[1]);
message1 = LocaleController.getString("AttachDocument", R.string.AttachDocument);
break;
}
case "CHAT_MESSAGE_STICKER": {
if (args.length > 2 && !TextUtils.isEmpty(args[2])) {
messageText = LocaleController.formatString("NotificationMessageGroupStickerEmoji", R.string.NotificationMessageGroupStickerEmoji, args[0], args[1], args[2]);
message1 = args[2] + " " + LocaleController.getString("AttachSticker", R.string.AttachSticker);
} else {
messageText = LocaleController.formatString("NotificationMessageGroupSticker", R.string.NotificationMessageGroupSticker, args[0], args[1]);
message1 = args[1] + " " + LocaleController.getString("AttachSticker", R.string.AttachSticker);
}
break;
}
case "CHAT_MESSAGE_AUDIO": {
messageText = LocaleController.formatString("NotificationMessageGroupAudio", R.string.NotificationMessageGroupAudio, args[0], args[1]);
message1 = LocaleController.getString("AttachAudio", R.string.AttachAudio);
break;
}
case "CHAT_MESSAGE_CONTACT": {
messageText = LocaleController.formatString("NotificationMessageGroupContact2", R.string.NotificationMessageGroupContact2, args[0], args[1], args[2]);
message1 = LocaleController.getString("AttachContact", R.string.AttachContact);
break;
}
case "CHAT_MESSAGE_QUIZ": {
messageText = LocaleController.formatString("NotificationMessageGroupQuiz2", R.string.NotificationMessageGroupQuiz2, args[0], args[1], args[2]);
message1 = LocaleController.getString("PollQuiz", R.string.PollQuiz);
break;
}
case "CHAT_MESSAGE_POLL": {
messageText = LocaleController.formatString("NotificationMessageGroupPoll2", R.string.NotificationMessageGroupPoll2, args[0], args[1], args[2]);
message1 = LocaleController.getString("Poll", R.string.Poll);
break;
}
case "CHAT_MESSAGE_GEO": {
messageText = LocaleController.formatString("NotificationMessageGroupMap", R.string.NotificationMessageGroupMap, args[0], args[1]);
message1 = LocaleController.getString("AttachLocation", R.string.AttachLocation);
break;
}
case "CHAT_MESSAGE_GEOLIVE": {
messageText = LocaleController.formatString("NotificationMessageGroupLiveLocation", R.string.NotificationMessageGroupLiveLocation, args[0], args[1]);
message1 = LocaleController.getString("AttachLiveLocation", R.string.AttachLiveLocation);
break;
}
case "CHAT_MESSAGE_GIF": {
messageText = LocaleController.formatString("NotificationMessageGroupGif", R.string.NotificationMessageGroupGif, args[0], args[1]);
message1 = LocaleController.getString("AttachGif", R.string.AttachGif);
break;
}
case "CHAT_MESSAGE_GAME": {
messageText = LocaleController.formatString("NotificationMessageGroupGame", R.string.NotificationMessageGroupGame, args[0], args[1], args[2]);
message1 = LocaleController.getString("AttachGame", R.string.AttachGame);
break;
}
case "CHAT_MESSAGE_GAME_SCORE": {
messageText = LocaleController.formatString("NotificationMessageGroupGameScored", R.string.NotificationMessageGroupGameScored, args[0], args[1], args[2], args[3]);
break;
}
case "CHAT_MESSAGE_INVOICE": {
messageText = LocaleController.formatString("NotificationMessageGroupInvoice", R.string.NotificationMessageGroupInvoice, args[0], args[1], args[2]);
message1 = LocaleController.getString("PaymentInvoice", R.string.PaymentInvoice);
break;
}
case "CHAT_CREATED":
case "CHAT_ADD_YOU": {
messageText = LocaleController.formatString("NotificationInvitedToGroup", R.string.NotificationInvitedToGroup, args[0], args[1]);
break;
}
case "CHAT_TITLE_EDITED": {
messageText = LocaleController.formatString("NotificationEditedGroupName", R.string.NotificationEditedGroupName, args[0], args[1]);
break;
}
case "CHAT_PHOTO_EDITED": {
messageText = LocaleController.formatString("NotificationEditedGroupPhoto", R.string.NotificationEditedGroupPhoto, args[0], args[1]);
break;
}
case "CHAT_ADD_MEMBER": {
messageText = LocaleController.formatString("NotificationGroupAddMember", R.string.NotificationGroupAddMember, args[0], args[1], args[2]);
break;
}
case "CHAT_DELETE_MEMBER": {
messageText = LocaleController.formatString("NotificationGroupKickMember", R.string.NotificationGroupKickMember, args[0], args[1]);
break;
}
case "CHAT_DELETE_YOU": {
messageText = LocaleController.formatString("NotificationGroupKickYou", R.string.NotificationGroupKickYou, args[0], args[1]);
break;
}
case "CHAT_LEFT": {
messageText = LocaleController.formatString("NotificationGroupLeftMember", R.string.NotificationGroupLeftMember, args[0], args[1]);
break;
}
case "CHAT_RETURNED": {
messageText = LocaleController.formatString("NotificationGroupAddSelf", R.string.NotificationGroupAddSelf, args[0], args[1]);
break;
}
case "CHAT_JOINED": {
messageText = LocaleController.formatString("NotificationGroupAddSelfMega", R.string.NotificationGroupAddSelfMega, args[0], args[1]);
break;
}
case "CHAT_MESSAGE_FWDS": {
messageText = LocaleController.formatString("NotificationGroupForwardedFew", R.string.NotificationGroupForwardedFew, args[0], args[1], LocaleController.formatPluralString("messages", Utilities.parseInt(args[2])));
localMessage = true;
break;
}
case "CHAT_MESSAGE_PHOTOS": {
messageText = LocaleController.formatString("NotificationGroupFew", R.string.NotificationGroupFew, args[0], args[1], LocaleController.formatPluralString("Photos", Utilities.parseInt(args[2])));
localMessage = true;
break;
}
case "CHAT_MESSAGE_VIDEOS": {
messageText = LocaleController.formatString("NotificationGroupFew", R.string.NotificationGroupFew, args[0], args[1], LocaleController.formatPluralString("Videos", Utilities.parseInt(args[2])));
localMessage = true;
break;
}
case "CHAT_MESSAGES": {
messageText = LocaleController.formatString("NotificationGroupAlbum", R.string.NotificationGroupAlbum, args[0], args[1]);
localMessage = true;
break;
}
case "PINNED_TEXT": {
if (chat_from_id != 0) {
messageText = LocaleController.formatString("NotificationActionPinnedText", R.string.NotificationActionPinnedText, args[0], args[1], args[2]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedTextChannel", R.string.NotificationActionPinnedTextChannel, args[0], args[1]);
}
break;
}
case "PINNED_NOTEXT": {
if (chat_from_id != 0) {
messageText = LocaleController.formatString("NotificationActionPinnedNoText", R.string.NotificationActionPinnedNoText, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedNoTextChannel", R.string.NotificationActionPinnedNoTextChannel, args[0]);
}
break;
}
case "PINNED_PHOTO": {
if (chat_from_id != 0) {
messageText = LocaleController.formatString("NotificationActionPinnedPhoto", R.string.NotificationActionPinnedPhoto, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedPhotoChannel", R.string.NotificationActionPinnedPhotoChannel, args[0]);
}
break;
}
case "PINNED_VIDEO": {
if (chat_from_id != 0) {
messageText = LocaleController.formatString("NotificationActionPinnedVideo", R.string.NotificationActionPinnedVideo, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedVideoChannel", R.string.NotificationActionPinnedVideoChannel, args[0]);
}
break;
}
case "PINNED_ROUND": {
if (chat_from_id != 0) {
messageText = LocaleController.formatString("NotificationActionPinnedRound", R.string.NotificationActionPinnedRound, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedRoundChannel", R.string.NotificationActionPinnedRoundChannel, args[0]);
}
break;
}
case "PINNED_DOC": {
if (chat_from_id != 0) {
messageText = LocaleController.formatString("NotificationActionPinnedFile", R.string.NotificationActionPinnedFile, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedFileChannel", R.string.NotificationActionPinnedFileChannel, args[0]);
}
break;
}
case "PINNED_STICKER": {
if (chat_from_id != 0) {
if (args.length > 2 && !TextUtils.isEmpty(args[2])) {
messageText = LocaleController.formatString("NotificationActionPinnedStickerEmoji", R.string.NotificationActionPinnedStickerEmoji, args[0], args[2], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedSticker", R.string.NotificationActionPinnedSticker, args[0], args[1]);
}
} else {
if (args.length > 1 && !TextUtils.isEmpty(args[1])) {
messageText = LocaleController.formatString("NotificationActionPinnedStickerEmojiChannel", R.string.NotificationActionPinnedStickerEmojiChannel, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedStickerChannel", R.string.NotificationActionPinnedStickerChannel, args[0]);
}
}
break;
}
case "PINNED_AUDIO": {
if (chat_from_id != 0) {
messageText = LocaleController.formatString("NotificationActionPinnedVoice", R.string.NotificationActionPinnedVoice, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedVoiceChannel", R.string.NotificationActionPinnedVoiceChannel, args[0]);
}
break;
}
case "PINNED_CONTACT": {
if (chat_from_id != 0) {
messageText = LocaleController.formatString("NotificationActionPinnedContact2", R.string.NotificationActionPinnedContact2, args[0], args[2], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedContactChannel2", R.string.NotificationActionPinnedContactChannel2, args[0], args[1]);
}
break;
}
case "PINNED_QUIZ": {
if (chat_from_id != 0) {
messageText = LocaleController.formatString("NotificationActionPinnedQuiz2", R.string.NotificationActionPinnedQuiz2, args[0], args[2], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedQuizChannel2", R.string.NotificationActionPinnedQuizChannel2, args[0], args[1]);
}
break;
}
case "PINNED_POLL": {
if (chat_from_id != 0) {
messageText = LocaleController.formatString("NotificationActionPinnedPoll2", R.string.NotificationActionPinnedPoll2, args[0], args[2], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedPollChannel2", R.string.NotificationActionPinnedPollChannel2, args[0], args[1]);
}
break;
}
case "PINNED_GEO": {
if (chat_from_id != 0) {
messageText = LocaleController.formatString("NotificationActionPinnedGeo", R.string.NotificationActionPinnedGeo, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedGeoChannel", R.string.NotificationActionPinnedGeoChannel, args[0]);
}
break;
}
case "PINNED_GEOLIVE": {
if (chat_from_id != 0) {
messageText = LocaleController.formatString("NotificationActionPinnedGeoLive", R.string.NotificationActionPinnedGeoLive, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedGeoLiveChannel", R.string.NotificationActionPinnedGeoLiveChannel, args[0]);
}
break;
}
case "PINNED_GAME": {
if (chat_from_id != 0) {
messageText = LocaleController.formatString("NotificationActionPinnedGame", R.string.NotificationActionPinnedGame, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedGameChannel", R.string.NotificationActionPinnedGameChannel, args[0]);
}
break;
}
case "PINNED_GAME_SCORE": {
if (chat_from_id != 0) {
messageText = LocaleController.formatString("NotificationActionPinnedGameScore", R.string.NotificationActionPinnedGameScore, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedGameScoreChannel", R.string.NotificationActionPinnedGameScoreChannel, args[0]);
}
break;
}
case "PINNED_INVOICE": {
if (chat_from_id != 0) {
messageText = LocaleController.formatString("NotificationActionPinnedInvoice", R.string.NotificationActionPinnedInvoice, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedInvoiceChannel", R.string.NotificationActionPinnedInvoiceChannel, args[0]);
}
break;
}
case "PINNED_GIF": {
if (chat_from_id != 0) {
messageText = LocaleController.formatString("NotificationActionPinnedGif", R.string.NotificationActionPinnedGif, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedGifChannel", R.string.NotificationActionPinnedGifChannel, args[0]);
}
break;
}
case "ENCRYPTED_MESSAGE": {
messageText = LocaleController.getString("YouHaveNewMessage", R.string.YouHaveNewMessage);
name = LocaleController.getString("SecretChatName", R.string.SecretChatName);
localMessage = true;
break;
}
case "CONTACT_JOINED":
case "AUTH_UNKNOWN":
case "AUTH_REGION":
case "LOCKED_MESSAGE":
case "ENCRYPTION_REQUEST":
case "ENCRYPTION_ACCEPT":
case "PHONE_CALL_REQUEST":
case "MESSAGE_MUTED":
case "PHONE_CALL_MISSED": {
//ignored
break;
}
default: {
if (BuildVars.LOGS_ENABLED) {
FileLog.w("unhandled loc_key = " + loc_key);
}
break;
}
}
if (messageText != null) {
TLRPC.TL_message messageOwner = new TLRPC.TL_message();
messageOwner.id = msg_id;
messageOwner.random_id = random_id;
messageOwner.message = message1 != null ? message1 : messageText;
messageOwner.date = (int) (time / 1000);
if (pinned) {
messageOwner.action = new TLRPC.TL_messageActionPinMessage();
}
if (supergroup) {
messageOwner.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP;
}
messageOwner.dialog_id = dialog_id;
if (channel_id != 0) {
messageOwner.to_id = new TLRPC.TL_peerChannel();
messageOwner.to_id.channel_id = channel_id;
} else if (chat_id != 0) {
messageOwner.to_id = new TLRPC.TL_peerChat();
messageOwner.to_id.chat_id = chat_id;
} else {
messageOwner.to_id = new TLRPC.TL_peerUser();
messageOwner.to_id.user_id = user_id;
}
messageOwner.flags |= 256;
messageOwner.from_id = chat_from_id;
messageOwner.mentioned = mention || pinned;
messageOwner.silent = silent;
messageOwner.from_scheduled = scheduled;
MessageObject messageObject = new MessageObject(currentAccount, messageOwner, messageText, name, userName, localMessage, channel, edited);
ArrayList<MessageObject> arrayList = new ArrayList<>();
arrayList.add(messageObject);
canRelease = false;
NotificationsController.getInstance(currentAccount).processNewMessages(arrayList, true, true, countDownLatch);
}
}
}
}
if (canRelease) {
countDownLatch.countDown();
}
ConnectionsManager.onInternalPushReceived(currentAccount);
ConnectionsManager.getInstance(currentAccount).resumeNetworkMaybe();
} catch (Throwable e) {
if (currentAccount != -1) {
ConnectionsManager.onInternalPushReceived(currentAccount);
ConnectionsManager.getInstance(currentAccount).resumeNetworkMaybe();
countDownLatch.countDown();
} else {
onDecryptError();
}
if (BuildVars.LOGS_ENABLED) {
FileLog.e("error in loc_key = " + loc_key + " json " + jsonString);
}
FileLog.e(e);
}
});
});
try {
countDownLatch.await();
} catch (Throwable ignore) {
}
if (BuildVars.DEBUG_VERSION) {
FileLog.d("finished GCM service, time = " + (SystemClock.elapsedRealtime() - receiveTime));
}
}
private void onDecryptError() {
for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) {
if (UserConfig.getInstance(a).isClientActivated()) {
ConnectionsManager.onInternalPushReceived(a);
ConnectionsManager.getInstance(a).resumeNetworkMaybe();
}
}
countDownLatch.countDown();
}
@Override
public void onNewToken(String token) {
AndroidUtilities.runOnUIThread(() -> {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("Refreshed token: " + token);
}
ApplicationLoader.postInitApplication();
sendRegistrationToServer(token);
});
}
public static void sendRegistrationToServer(final String token) {
Utilities.stageQueue.postRunnable(() -> {
ConnectionsManager.setRegId(token, SharedConfig.pushStringStatus);
if (token == null) {
return;
}
SharedConfig.pushString = token;
for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) {
UserConfig userConfig = UserConfig.getInstance(a);
userConfig.registeredForPush = false;
userConfig.saveConfig(false);
if (userConfig.getClientUserId() != 0) {
final int currentAccount = a;
AndroidUtilities.runOnUIThread(() -> MessagesController.getInstance(currentAccount).registerForPush(token));
}
}
});
}
}

View File

@ -0,0 +1,240 @@
package org.telegram.ui.Cells;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.util.Property;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.TextView;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.R;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Components.AnimationProperties;
import org.telegram.ui.Components.CubicBezierInterpolator;
import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.Switch;
import java.util.ArrayList;
public class DrawerActionCheckCell extends FrameLayout {
private TextView textView;
public Switch checkBox;
private int height = 48;
private int animatedColorBackground;
private float animationProgress;
private Paint animationPaint;
private float lastTouchX;
private ObjectAnimator animator;
private boolean drawCheckRipple;
public static final Property<DrawerActionCheckCell, Float> ANIMATION_PROGRESS = new AnimationProperties.FloatProperty<DrawerActionCheckCell>("animationProgress") {
@Override
public void setValue(DrawerActionCheckCell object, float value) {
object.setAnimationProgress(value);
object.invalidate();
}
@Override
public Float get(DrawerActionCheckCell object) {
return object.animationProgress;
}
};
public DrawerActionCheckCell(Context context) {
this(context, 21);
}
public DrawerActionCheckCell(Context context, int padding) {
super(context);
textView = new TextView(context);
textView.setTextColor(Theme.getColor(Theme.key_chats_menuItemText));
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
textView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
textView.setLines(1);
textView.setMaxLines(1);
textView.setSingleLine(true);
addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, padding, 0, 70, 0));
checkBox = new Switch(context);
checkBox.setColors(Theme.key_switchTrack, Theme.key_switchTrackChecked, Theme.key_windowBackgroundWhite, Theme.key_windowBackgroundWhite);
addView(checkBox, LayoutHelper.createFrame(37, 20, Gravity.RIGHT | Gravity.CENTER_VERTICAL, 22, 0, 22, 0));
setClipChildren(false);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(height), MeasureSpec.EXACTLY));
}
@Override
public boolean onTouchEvent(MotionEvent event) {
lastTouchX = event.getX();
return super.onTouchEvent(event);
}
public void setTextAndCheck(String text, boolean checked, boolean divider) {
textView.setText(text);
checkBox.setChecked(checked, false);
LayoutParams layoutParams = (LayoutParams) textView.getLayoutParams();
layoutParams.height = LayoutParams.MATCH_PARENT;
layoutParams.topMargin = 0;
textView.setLayoutParams(layoutParams);
setWillNotDraw(!divider);
}
public void setColors(String key, String switchKey, String switchKeyChecked, String switchThumb, String switchThumbChecked) {
textView.setTextColor(Theme.getColor(key));
checkBox.setColors(switchKey, switchKeyChecked, switchThumb, switchThumbChecked);
textView.setTag(key);
}
public void setTypeface(Typeface typeface) {
textView.setTypeface(typeface);
}
public void setHeight(int value) {
height = value;
}
public void setDrawCheckRipple(boolean value) {
drawCheckRipple = value;
}
@Override
public void setPressed(boolean pressed) {
if (drawCheckRipple) {
checkBox.setDrawRipple(pressed);
}
super.setPressed(pressed);
}
public void setOnCheckChangeListener(Switch.OnCheckedChangeListener listener) {
checkBox.setOnCheckedChangeListener(listener);
}
public void setOnCheckClickListener(View.OnClickListener listener) {
checkBox.setOnClickListener(listener);
}
public void setTextAndValueAndCheck(String text, int resId, String value, boolean checked, boolean multiline, boolean divider) {
textView.setText(text);
Drawable drawable = getResources().getDrawable(resId).mutate();
drawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chats_menuItemIcon), PorterDuff.Mode.SRC_IN));
textView.setCompoundDrawablePadding(AndroidUtilities.dp(29));
textView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null);
checkBox.setChecked(checked, false);
LayoutParams layoutParams = (LayoutParams) textView.getLayoutParams();
layoutParams.height = LayoutParams.WRAP_CONTENT;
layoutParams.topMargin = AndroidUtilities.dp(10);
textView.setLayoutParams(layoutParams);
setWillNotDraw(!divider);
}
public void setEnabled(boolean value, ArrayList<Animator> animators) {
super.setEnabled(value);
if (animators != null) {
animators.add(ObjectAnimator.ofFloat(textView, "alpha", value ? 1.0f : 0.5f));
animators.add(ObjectAnimator.ofFloat(checkBox, "alpha", value ? 1.0f : 0.5f));
} else {
textView.setAlpha(value ? 1.0f : 0.5f);
checkBox.setAlpha(value ? 1.0f : 0.5f);
}
}
public void setChecked(boolean checked) {
checkBox.setChecked(checked, true);
}
public boolean isChecked() {
return checkBox.isChecked();
}
@Override
public void setBackgroundColor(int color) {
clearAnimation();
animatedColorBackground = 0;
super.setBackgroundColor(color);
}
public void setBackgroundColorAnimated(boolean checked, int color) {
if (animator != null) {
animator.cancel();
animator = null;
}
if (animatedColorBackground != 0) {
setBackgroundColor(animatedColorBackground);
}
if (animationPaint == null) {
animationPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
checkBox.setOverrideColor(checked ? 1 : 2);
animatedColorBackground = color;
animationPaint.setColor(animatedColorBackground);
animationProgress = 0.0f;
animator = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS, 0.0f, 1.0f);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
setBackgroundColor(animatedColorBackground);
animatedColorBackground = 0;
invalidate();
}
});
animator.setInterpolator(CubicBezierInterpolator.EASE_OUT);
animator.setDuration(240).start();
}
private void setAnimationProgress(float value) {
animationProgress = value;
float rad = Math.max(lastTouchX, getMeasuredWidth() - lastTouchX) + AndroidUtilities.dp(40);
float cx = lastTouchX;
int cy = getMeasuredHeight() / 2;
float animatedRad = rad * animationProgress;
checkBox.setOverrideColorProgress(cx, cy, animatedRad);
}
@Override
protected void onDraw(Canvas canvas) {
if (animatedColorBackground != 0) {
float rad = Math.max(lastTouchX, getMeasuredWidth() - lastTouchX) + AndroidUtilities.dp(40);
float cx = lastTouchX;
int cy = getMeasuredHeight() / 2;
float animatedRad = rad * animationProgress;
canvas.drawCircle(cx, cy, animatedRad, animationPaint);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
textView.setTextColor(Theme.getColor(Theme.key_chats_menuItemText));
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName("android.widget.Switch");
info.setCheckable(true);
info.setChecked(checkBox.isChecked());
info.setContentDescription(checkBox.isChecked() ? LocaleController.getString("NotificationsOn", R.string.NotificationsOn) : LocaleController.getString("NotificationsOff", R.string.NotificationsOff));
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,349 @@
package tw.nekomimi.nekogram
import android.content.Context
import android.text.TextUtils
import android.util.TypedValue
import android.view.Gravity
import android.widget.*
import org.telegram.messenger.AndroidUtilities
import org.telegram.messenger.LocaleController
import org.telegram.messenger.R
import org.telegram.ui.ActionBar.BottomSheet
import org.telegram.ui.ActionBar.Theme
import org.telegram.ui.Cells.*
import org.telegram.ui.Components.LayoutHelper
import java.util.*
class BottomBuilder(val ctx: Context) {
val builder = BottomSheet.Builder(ctx, true)
private val rootView = LinearLayout(ctx).apply {
orientation = LinearLayout.VERTICAL
}
private val _root = LinearLayout(ctx).apply {
addView(ScrollView(ctx).apply {
addView(this@BottomBuilder.rootView)
isFillViewport = true
isVerticalScrollBarEnabled = false
}, LinearLayout.LayoutParams(-1, -1))
builder.setCustomView(this)
}
private val buttonsView by lazy {
FrameLayout(ctx).apply {
setBackgroundColor(Theme.getColor(Theme.key_dialogBackground))
this@BottomBuilder.rootView.addView(this, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 50, Gravity.LEFT or Gravity.BOTTOM))
addView(rightButtonsView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP or Gravity.RIGHT));
}
}
private val rightButtonsView by lazy {
LinearLayout(ctx).apply {
orientation = LinearLayout.HORIZONTAL
weightSum = 1F
}
}
@JvmOverloads
fun addTitle(title: CharSequence, bigTitle: Boolean = false): HeaderCell {
return addTitle(title, bigTitle, null)
}
fun addTitle(title: CharSequence, subTitle: CharSequence): HeaderCell {
return addTitle(title, true, subTitle)
}
fun addTitle(title: CharSequence, bigTitle: Boolean, subTitle: CharSequence?): HeaderCell {
val headerCell = HeaderCell(ctx)
headerCell.setText(if (title is String) AndroidUtilities.replaceTags(title) else title)
subTitle?.also {
headerCell.setText2(it)
}
rootView.addView(headerCell, LayoutHelper.createLinear(-1, -2).apply {
bottomMargin = AndroidUtilities.dp(12F)
})
rootView.addView(ShadowSectionCell(ctx, 3))
return headerCell
}
@JvmOverloads
fun addCheckItem(text: String, value: Boolean, switch: Boolean = false, valueText: String? = null, listener: (cell: TextCheckCell) -> Unit): TextCheckCell {
val checkBoxCell = TextCheckCell(ctx, 21, !switch)
checkBoxCell.setBackgroundDrawable(Theme.getSelectorDrawable(false))
checkBoxCell.minimumHeight = AndroidUtilities.dp(50F)
rootView.addView(checkBoxCell, LayoutHelper.createLinear(-1, -2))
if (valueText == null) {
checkBoxCell.setTextAndCheck(text, value, true)
} else {
checkBoxCell.setTextAndValueAndCheck(text, valueText, value, true, true)
}
checkBoxCell.setOnClickListener {
listener.invoke(checkBoxCell)
}
return checkBoxCell
}
@JvmOverloads
fun addCheckItems(text: Array<String>, value: (Int) -> Boolean, switch: Boolean = false, valueText: ((Int) -> String)? = null, listener: (index: Int, text: String, cell: TextCheckCell) -> Unit): List<TextCheckCell> {
val list = mutableListOf<TextCheckCell>()
text.forEachIndexed { index, textI ->
list.add(addCheckItem(textI, value(index), switch, valueText?.invoke(index)) { cell ->
listener(index, textI, cell)
})
}
return list
}
private val radioButtonGroup by lazy { LinkedList<RadioButtonCell>() }
fun doRadioCheck(cell: RadioButtonCell) {
if (!cell.isChecked) {
radioButtonGroup.forEach {
if (it.isChecked) {
it.setChecked(false, true)
}
}
cell.setChecked(true, true)
}
}
@JvmOverloads
fun addRadioItem(text: String, value: Boolean, valueText: String? = null, listener: (cell: RadioButtonCell) -> Unit): RadioButtonCell {
val checkBoxCell = RadioButtonCell(ctx, true)
checkBoxCell.setBackgroundDrawable(Theme.getSelectorDrawable(false))
checkBoxCell.minimumHeight = AndroidUtilities.dp(50F)
rootView.addView(checkBoxCell, LayoutHelper.createLinear(-1, -2))
if (valueText == null) {
checkBoxCell.setTextAndValue(text, true, value)
} else {
checkBoxCell.setTextAndValueAndCheck(text, valueText, true, value)
}
radioButtonGroup.add(checkBoxCell)
checkBoxCell.setOnClickListener {
listener(checkBoxCell)
}
return checkBoxCell
}
@JvmOverloads
fun addRadioItems(text: Array<String>, value: (Int, String) -> Boolean, valueText: ((Int, String) -> String)? = null, listener: (index: Int, text: String, cell: RadioButtonCell) -> Unit): List<RadioButtonCell> {
val list = mutableListOf<RadioButtonCell>()
text.forEachIndexed { index, textI ->
list.add(addRadioItem(textI, value(index, textI), valueText?.invoke(index, textI)) { cell ->
listener(index, textI, cell)
})
}
return list
}
@JvmOverloads
fun addCancelItem() {
addItem(LocaleController.getString("Cancel", R.string.Cancel), R.drawable.baseline_cancel_24) { dismiss() }
}
@JvmOverloads
fun addCancelButton(left: Boolean = true) {
addButton(LocaleController.getString("Cancel", R.string.Cancel), left = left) { dismiss() }
}
@JvmOverloads
fun addOkButton(listener: ((TextView) -> Unit)) {
addButton(LocaleController.getString("OK", R.string.OK)) { listener(it); }
}
@JvmOverloads
fun addButton(text: String, red: Boolean = false, left: Boolean = false, listener: ((TextView) -> Unit)): TextView {
return TextView(ctx).apply {
setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14f)
setTextColor(Theme.getColor(Theme.key_dialogTextBlue4))
gravity = Gravity.CENTER
isSingleLine = true
ellipsize = TextUtils.TruncateAt.END
setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_dialogButtonSelector), 0))
setPadding(AndroidUtilities.dp(18f), 0, AndroidUtilities.dp(18f), 0)
setText(text)
typeface = AndroidUtilities.getTypeface("fonts/rmedium.ttf")
(if (left) buttonsView else rightButtonsView).addView(this, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP or Gravity.LEFT))
setOnClickListener { listener(this) }
}
}
fun addItem(): TextCell {
return TextCell(ctx).apply {
this@BottomBuilder.rootView.addView(this, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT or Gravity.TOP))
}
}
@JvmOverloads
fun addItem(text: String, icon: Int = 0, red: Boolean = false, listener: ((cell: TextCell) -> Unit)?): TextCell {
return TextCell(ctx).apply {
textView.setGravity(Gravity.LEFT)
background = Theme.getSelectorDrawable(false)
setTextAndIcon(text, icon, true)
setOnClickListener {
if (listener == null) dismiss() else listener(this)
}
if (red) {
setColors("key_dialogTextRed2", "key_dialogTextRed2")
}
this@BottomBuilder.rootView.addView(this, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT or Gravity.TOP))
}
}
@JvmOverloads
fun addItems(text: Array<String>, icon: (Int) -> Int = { 0 }, listener: (index: Int, text: String, cell: TextCell) -> Unit): List<TextCell> {
val list = mutableListOf<TextCell>()
text.forEachIndexed { index, textI ->
list.add(addItem(textI, icon(index)) { cell ->
listener(index, textI, cell)
})
}
return list
}
@JvmOverloads
fun addEditText(hintText: String? = null): EditText {
return EditText(ctx).apply {
setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14f)
setTextColor(Theme.getColor(Theme.key_dialogTextBlack))
setHintTextColor(Theme.getColor(Theme.key_dialogTextBlue4))
hintText?.also { hint = it }
isSingleLine = true
isFocusable = true
setBackgroundDrawable(null)
this@BottomBuilder.rootView.addView(this, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, -2, if (LocaleController.isRTL) Gravity.RIGHT else Gravity.LEFT, AndroidUtilities.dp(8F), 0, 0, 0))
}
}
fun create() = builder.create()
fun show() = builder.show()
fun dismiss() {
builder.dismissRunnable.run()
}
}

View File

@ -0,0 +1,109 @@
package tw.nekomimi.nekogram
import cn.hutool.crypto.digest.DigestUtil
import org.telegram.messenger.MessagesController
import org.telegram.tgnet.ConnectionsManager
import org.telegram.tgnet.SerializedData
import org.telegram.tgnet.TLRPC
import java.math.BigInteger
import java.nio.ByteBuffer
import java.security.interfaces.RSAPublicKey
object DataCenter {
// func calcAuthKeyId(keyData []byte) int64 {
// sha1 := Sha1Digest(keyData)
// // Lower 64 bits = 8 bytes of 20 byte SHA1 hash.
// return int64(binary.LittleEndian.Uint64(sha1[12:]))
// }
@JvmStatic
fun calcAuthKeyId(publicKey: RSAPublicKey): Long {
val key = SerializedData()
key.writeByteArray(publicKey.modulus.toByteArray())
key.writeByteArray(publicKey.publicExponent.toByteArray())
return BigInteger(DigestUtil.sha1(key.toByteArray()).slice(12 until 20).toByteArray()).toLong()
}
@JvmStatic
fun applyOfficalDataCanter(account: Int) {
MessagesController.getMainSettings(account).edit().remove("layer").remove("custom_dc").apply()
if (ConnectionsManager.native_isTestBackend(account) != 0) {
ConnectionsManager.getInstance(account).switchBackend()
}
applyDataCanter(account, 1, "149.154.175.50", "2001:b28:f23d:f001:0000:0000:0000:000a")
applyDataCanter(account, 2, "149.154.167.51", "2001:67c:4e8:f002:0000:0000:0000:000a")
applyDataCanter(account, 3, "149.154.175.100", "2001:b28:f23d:f003:0000:0000:0000:000a")
applyDataCanter(account, 4, "149.154.167.91", "2001:67c:4e8:f004:0000:0000:0000:000a")
applyDataCanter(account, 5, "149.154.171.5", "2001:67c:4e8:f005:0000:0000:0000:000a")
ConnectionsManager.native_cleanUp(account, true)
ConnectionsManager.native_setLayer(account, TLRPC.LAYER)
repeat(5) {
ConnectionsManager.native_setDatacenterPublicKey(account, it + 1, "", 0)
}
}
@JvmStatic
fun applyTestDataCenter(account: Int) {
MessagesController.getMainSettings(account).edit().remove("layer").remove("custom_dc").apply()
if (ConnectionsManager.native_isTestBackend(account) == 0) {
ConnectionsManager.getInstance(account).switchBackend()
}
ConnectionsManager.native_setLayer(account, TLRPC.LAYER)
}
@JvmStatic
fun applyCustomDataCenter(account: Int, ipv4Address: String = "", ipv6Address: String = "", port: Int, layer: Int, publicKey: String, fingerprint: Long) {
MessagesController.getMainSettings(account).edit().putInt("layer", layer).putBoolean("custom_dc", true).apply()
if (ConnectionsManager.native_isTestBackend(account) != 0) {
ConnectionsManager.getInstance(account).switchBackend()
}
repeat(5) {
ConnectionsManager.native_setDatacenterAddress(account, it + 1, ipv4Address, ipv6Address, port)
}
ConnectionsManager.native_saveDatacenters(account)
ConnectionsManager.native_setLayer(account, layer)
repeat(5) {
ConnectionsManager.native_setDatacenterPublicKey(account, it + 1, publicKey, fingerprint);
}
}
private fun applyDataCanter(account: Int, dataCenterId: Int, ipv4Address: String, ipv6Address: String, port: Int = 443) {
ConnectionsManager.native_setDatacenterAddress(account, dataCenterId, ipv4Address, ipv4Address, port)
}
}

View File

@ -0,0 +1,176 @@
package tw.nekomimi.nekogram;
import android.app.Activity;
import android.content.IntentSender;
import android.text.TextUtils;
import android.view.WindowManager;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.play.core.appupdate.AppUpdateManager;
import com.google.android.play.core.appupdate.AppUpdateManagerFactory;
import com.google.android.play.core.install.InstallStateUpdatedListener;
import com.google.android.play.core.install.model.AppUpdateType;
import com.google.android.play.core.install.model.InstallStatus;
import com.google.android.play.core.install.model.UpdateAvailability;
import com.google.firebase.crashlytics.FirebaseCrashlytics;
import com.google.firebase.iid.FirebaseInstanceId;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.BuildConfig;
import org.telegram.messenger.BuildVars;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.GcmPushListenerService;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.R;
import org.telegram.messenger.SharedConfig;
import org.telegram.messenger.Utilities;
import org.telegram.tgnet.ConnectionsManager;
import javax.validation.constraints.NotNull;
import kotlin.Unit;
import tw.nekomimi.nekogram.utils.UIUtil;
public class ExternalGcm {
@SuppressWarnings("ConstantConditions")
private static boolean noGcm = !"release".equals(BuildConfig.BUILD_TYPE);
private static Boolean hasPlayServices;
public static void initPlayServices() {
AndroidUtilities.runOnUIThread(() -> {
if (hasPlayServices = checkPlayServices()) {
final String currentPushString = SharedConfig.pushString;
if (!TextUtils.isEmpty(currentPushString)) {
if (BuildVars.DEBUG_PRIVATE_VERSION && BuildVars.LOGS_ENABLED) {
FileLog.d("GCM regId = " + currentPushString);
}
} else {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("GCM Registration not found.");
}
}
Utilities.globalQueue.postRunnable(() -> {
try {
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(instanceIdResult -> {
String token = instanceIdResult.getToken();
if (!TextUtils.isEmpty(token)) {
GcmPushListenerService.sendRegistrationToServer(token);
}
}).addOnFailureListener(e -> {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("Failed to get regid");
}
SharedConfig.pushStringStatus = "__FIREBASE_FAILED__";
GcmPushListenerService.sendRegistrationToServer(null);
});
} catch (Throwable e) {
FileLog.e(e);
}
});
} else {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("No valid Google Play Services APK found.");
}
SharedConfig.pushStringStatus = "__NO_GOOGLE_PLAY_SERVICES__";
ConnectionsManager.setRegId(null, SharedConfig.pushStringStatus);
}
}, 1000);
}
public static boolean checkPlayServices() {
if (noGcm) return false;
if (hasPlayServices != null) return hasPlayServices;
try {
int resultCode = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(ApplicationLoader.applicationContext);
hasPlayServices = resultCode == ConnectionResult.SUCCESS;
} catch (Exception e) {
hasPlayServices = false;
FileLog.e(e);
}
return hasPlayServices;
}
public static void sendRegistrationToServer() {
if (!checkPlayServices()) return;
GcmPushListenerService.sendRegistrationToServer(SharedConfig.pushString);
}
public static void reportLog(@NotNull String report) {
if (!checkPlayServices()) return;
UIUtil.runOnIoDispatcher(() -> FirebaseCrashlytics.getInstance().log(report));
}
public static void recordException(@NotNull Throwable throwable) {
if (!checkPlayServices()) return;
UIUtil.runOnIoDispatcher(() -> FirebaseCrashlytics.getInstance().recordException(throwable));
}
public static void checkUpdate(Activity ctx) {
if (!checkPlayServices()) return;
AppUpdateManager manager = AppUpdateManagerFactory.create(ctx);
InstallStateUpdatedListener listener = (installState) -> {
if (installState.installStatus() == InstallStatus.DOWNLOADED) {
BottomBuilder builder = new BottomBuilder(ctx);
builder.addTitle(LocaleController.getString("UpdateDownloaded", R.string.UpdateDownloaded), false);
builder.addItem(LocaleController.getString("UpdateUpdate", R.string.UpdateUpdate), R.drawable.baseline_system_update_24, false, (it) -> {
manager.completeUpdate();
return Unit.INSTANCE;
});
builder.addItem(LocaleController.getString("UpdateLater", R.string.UpdateLater), R.drawable.baseline_watch_later_24, false, null);
try {
builder.show();
} catch (WindowManager.BadTokenException e) {
manager.completeUpdate();
}
}
};
manager.registerListener(listener);
manager.getAppUpdateInfo().addOnSuccessListener((appUpdateInfo) -> {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE && appUpdateInfo.availableVersionCode() <= BuildConfig.VERSION_CODE) {
FileLog.d("update available");
try {
manager.startUpdateFlowForResult(appUpdateInfo, AppUpdateType.FLEXIBLE, ctx, 114514);
} catch (IntentSender.SendIntentException ignored) {
}
} else {
FileLog.d("no updates");
}
});
}
}

View File

@ -0,0 +1,188 @@
package tw.nekomimi.nekogram;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MessagesController;
import org.telegram.messenger.R;
import org.telegram.tgnet.TLRPC;
import java.util.LinkedList;
public class InternalFilters {
public static LinkedList<TLRPC.TL_dialogFilterSuggested> internalFilters = new LinkedList<>();
public static final TLRPC.TL_dialogFilter usersFilter;
public static final TLRPC.TL_dialogFilter contactsFilter;
public static final TLRPC.TL_dialogFilter groupsFilter;
public static final TLRPC.TL_dialogFilter channelsFilter;
public static final TLRPC.TL_dialogFilter botsFilter;
public static final TLRPC.TL_dialogFilter unmutedFilter;
public static final TLRPC.TL_dialogFilter unreadFilter;
public static final TLRPC.TL_dialogFilter unmutedAndUnreadFilter;
static {
usersFilter = mkFilter(LocaleController.getString("NotificationsUsers", R.string.FilterNameUsers),
LocaleController.getString("FilterNameUsersDescription", R.string.FilterNameUsersDescription),
MessagesController.DIALOG_FILTER_FLAG_CONTACTS |
MessagesController.DIALOG_FILTER_FLAG_NON_CONTACTS |
MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED,
(it) -> {
it.contacts = true;
it.non_contacts = true;
it.exclude_archived = true;
});
contactsFilter = mkFilter(LocaleController.getString("FilterNameContacts", R.string.FilterNameContacts),
LocaleController.getString("FilterNameContactsDescription", R.string.FilterNameContactsDescription),
MessagesController.DIALOG_FILTER_FLAG_CONTACTS |
MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED,
(it) -> {
it.contacts = true;
it.exclude_archived = true;
});
groupsFilter = mkFilter(LocaleController.getString("FilterNameGroups", R.string.FilterNameGroups),
LocaleController.getString("FilterNameContactsDescription", R.string.FilterNameGroupsDescription),
MessagesController.DIALOG_FILTER_FLAG_GROUPS |
MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED,
(it) -> {
it.groups = true;
it.exclude_archived = true;
});
channelsFilter = mkFilter(LocaleController.getString("FilterNameChannels", R.string.FilterNameChannels),
LocaleController.getString("FilterNameChannelsDescription", R.string.FilterNameChannelsDescription),
MessagesController.DIALOG_FILTER_FLAG_CHANNELS |
MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED,
(it) -> {
it.broadcasts = true;
it.exclude_archived = true;
});
botsFilter = mkFilter(LocaleController.getString("FilterNameBots", R.string.FilterNameBots),
LocaleController.getString("FilterNameBotsDescription", R.string.FilterNameBotsDescription),
MessagesController.DIALOG_FILTER_FLAG_BOTS |
MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED,
(it) -> {
it.bots = true;
it.exclude_archived = true;
});
unmutedFilter = mkFilter(LocaleController.getString("FilterNameUnmuted", R.string.FilterNameUnmuted),
LocaleController.getString("FilterNameUnmutedDescription", R.string.FilterNameUnmutedDescription),
MessagesController.DIALOG_FILTER_FLAG_CONTACTS |
MessagesController.DIALOG_FILTER_FLAG_NON_CONTACTS |
MessagesController.DIALOG_FILTER_FLAG_GROUPS |
MessagesController.DIALOG_FILTER_FLAG_CHANNELS |
MessagesController.DIALOG_FILTER_FLAG_BOTS |
MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED |
MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED,
(it) -> {
it.contacts = true;
it.non_contacts = true;
it.groups = true;
it.broadcasts = true;
it.bots = true;
it.exclude_muted = true;
it.exclude_archived = true;
});
unreadFilter = mkFilter(LocaleController.getString("FilterNameUnread2", R.string.FilterNameUnread2),
LocaleController.getString("FilterNameUnreadDescription", R.string.FilterNameUnreadDescription),
MessagesController.DIALOG_FILTER_FLAG_CONTACTS |
MessagesController.DIALOG_FILTER_FLAG_NON_CONTACTS |
MessagesController.DIALOG_FILTER_FLAG_GROUPS |
MessagesController.DIALOG_FILTER_FLAG_CHANNELS |
MessagesController.DIALOG_FILTER_FLAG_BOTS |
MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_READ |
MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED,
(it) -> {
it.contacts = true;
it.non_contacts = true;
it.groups = true;
it.broadcasts = true;
it.bots = true;
it.exclude_read = true;
it.exclude_archived = true;
});
unmutedAndUnreadFilter = mkFilter(LocaleController.getString("FilterNameUnmutedAndUnread", R.string.FilterNameUnmutedAndUnread),
LocaleController.getString("FilterNameUnmutedAndUnreadDescription", R.string.FilterNameUnmutedAndUnreadDescription),
MessagesController.DIALOG_FILTER_FLAG_CONTACTS |
MessagesController.DIALOG_FILTER_FLAG_NON_CONTACTS |
MessagesController.DIALOG_FILTER_FLAG_GROUPS |
MessagesController.DIALOG_FILTER_FLAG_CHANNELS |
MessagesController.DIALOG_FILTER_FLAG_BOTS |
MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED |
MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_READ |
MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED,
(it) -> {
it.contacts = true;
it.non_contacts = true;
it.groups = true;
it.broadcasts = true;
it.bots = true;
it.exclude_muted = true;
it.exclude_read = true;
it.exclude_archived = true;
});
}
@FunctionalInterface
interface FilterBuilder {
void apply(TLRPC.TL_dialogFilter filter);
}
private static int currId = 10;
private static TLRPC.TL_dialogFilter mkFilter(String name, String description, int flag, FilterBuilder builder) {
TLRPC.TL_dialogFilterSuggested suggestedFilter = new TLRPC.TL_dialogFilterSuggested();
suggestedFilter.description = description != null ? description : "Nya ~";
suggestedFilter.filter = new TLRPC.TL_dialogFilter();
suggestedFilter.filter.id = currId;
suggestedFilter.filter.title = name;
suggestedFilter.filter.flags = flag;
builder.apply(suggestedFilter.filter);
internalFilters.add(suggestedFilter);
currId++;
return suggestedFilter.filter;
}
}

View File

@ -1,101 +1,41 @@
package tw.nekomimi.nekogram;
import android.annotation.SuppressLint;
import android.content.Context;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.BaseController;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MessageObject;
import org.telegram.messenger.R;
import org.telegram.messenger.UserConfig;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.AlertDialog;
import org.telegram.ui.Cells.ChatMessageCell;
import java.util.ArrayList;
import java.util.HashSet;
import tw.nekomimi.nekogram.settings.NekoGeneralSettingsActivity;
import tw.nekomimi.nekogram.translator.TranslateBottomSheet;
import tw.nekomimi.nekogram.translator.Translator;
import tw.nekomimi.nekogram.utils.AlertUtil;
public class MessageHelper extends BaseController {
private static volatile MessageHelper[] Instance = new MessageHelper[UserConfig.MAX_ACCOUNT_COUNT];
@SuppressLint("StaticFieldLeak")
private static AlertDialog progressDialog;
private int lastReqId;
public MessageHelper(int num) {
super(num);
}
public static void resetMessageContent(MessageObject messageObject, ChatMessageCell chatMessageCell) {
messageObject.forceUpdate = true;
public static void resetMessageContent(MessageObject messageObject) {
if (messageObject.caption != null) {
messageObject.caption = null;
messageObject.generateCaption();
messageObject.forceUpdate = true;
}
messageObject.applyNewText();
messageObject.resetLayout();
chatMessageCell.requestLayout();
chatMessageCell.invalidate();
}
public static void showTranslateDialog(Context context, String query) {
if (NekoConfig.translationProvider < 0) {
TranslateBottomSheet.show(context, query);
} else {
if (progressDialog != null) {
progressDialog.dismiss();
}
progressDialog = new AlertDialog(context, 3);
progressDialog.showDelayed(400);
Translator.translate(query, new Translator.TranslateCallBack() {
@Override
public void onSuccess(Object translation) {
if (progressDialog != null) {
progressDialog.dismiss();
}
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage((String) translation);
builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null);
builder.setNeutralButton(LocaleController.getString("Copy", R.string.Copy), (dialog, which) -> AndroidUtilities.addToClipboard((String) translation));
builder.show();
}
@Override
public void onError(Throwable e) {
if (progressDialog != null) {
progressDialog.dismiss();
}
AlertDialog.Builder builder = new AlertDialog.Builder(context);
if (e != null && e.getLocalizedMessage() != null) {
builder.setTitle(LocaleController.getString("TranslateFailed", R.string.TranslateFailed));
builder.setMessage(e.getLocalizedMessage());
} else {
builder.setMessage(LocaleController.getString("TranslateFailed", R.string.TranslateFailed));
}
builder.setNeutralButton(LocaleController.getString("TranslationProvider", R.string.TranslationProvider), (dialog, which) -> NekoGeneralSettingsActivity.getTranslationProviderAlert(context).show());
builder.setPositiveButton(LocaleController.getString("Retry", R.string.Retry), (dialog, which) -> showTranslateDialog(context, query));
builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null);
builder.show();
}
@Override
public void onUnsupported() {
if (progressDialog != null) {
progressDialog.dismiss();
}
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(LocaleController.getString("TranslateApiUnsupported", R.string.TranslateApiUnsupported));
builder.setPositiveButton(LocaleController.getString("TranslationProvider", R.string.TranslationProvider), (dialog, which) -> NekoGeneralSettingsActivity.getTranslationProviderAlert(context).show());
builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null);
builder.show();
}
});
}
public static void resetMessageContent(ChatMessageCell chatMessageCell) {
chatMessageCell.onAttachedToWindow();
chatMessageCell.requestLayout();
chatMessageCell.invalidate();
}
public static MessageHelper getInstance(int num) {
@ -163,4 +103,97 @@ public class MessageHelper extends BaseController {
}
}), ConnectionsManager.RequestFlagFailOnServerErrors);
}
public void deleteChannelHistory(final long dialog_id, TLRPC.Chat chat, final int offset_id) {
final TLRPC.TL_messages_getHistory req = new TLRPC.TL_messages_getHistory();
req.peer = getMessagesController().getInputPeer((int) dialog_id);
if (req.peer == null) {
return;
}
req.limit = 100;
req.offset_id = offset_id;
final int currentReqId = ++lastReqId;
getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> {
if (error == null) {
int lastMessageId = offset_id;
if (currentReqId == lastReqId) {
if (response != null) {
TLRPC.messages_Messages res = (TLRPC.messages_Messages) response;
int size = res.messages.size();
if (size == 0) {
return;
}
/*
ArrayList<Integer> ids = new ArrayList<>();
ArrayList<Long> random_ids = new ArrayList<>();
int channelId = 0;
for (int a = 0; a < res.messages.size(); a++) {
TLRPC.Message message = res.messages.get(a);
ids.add(message.id);
if (message.random_id != 0) {
random_ids.add(message.random_id);
}
if (message.to_id.channel_id != 0) {
channelId = message.to_id.channel_id;
}
if (message.id > lastMessageId) {
lastMessageId = message.id;
}
}
getMessagesController().deleteMessages(ids, random_ids, null, dialog_id, channelId, true, false);
*/
HashSet<Integer> ids = new HashSet<>();
ArrayList<Integer> msgIds = new ArrayList<>();
ArrayList<Long> random_ids = new ArrayList<>();
int channelId = 0;
for (int a = 0; a < res.messages.size(); a++) {
TLRPC.Message message = res.messages.get(a);
ids.add(message.id);
if (message.from_id > 0) {
ids.add(message.from_id);
} else {
msgIds.add(message.id);
if (message.random_id != 0) {
random_ids.add(message.random_id);
}
}
if (message.id > lastMessageId) {
lastMessageId = message.id;
}
}
for (int userId : ids) {
deleteUserChannelHistory(chat, userId, 0);
}
if (!msgIds.isEmpty()) {
getMessagesController().deleteMessages(msgIds, random_ids, null, dialog_id, channelId, true, false);
}
deleteChannelHistory(dialog_id, chat, lastMessageId);
}
}
} else {
AlertUtil.showToast(error.code + ": " + error.text);
}
}), ConnectionsManager.RequestFlagFailOnServerErrors);
}
public void deleteUserChannelHistory(final TLRPC.Chat chat, int userId, int offset) {
if (offset == 0) {
getMessagesStorage().deleteUserChannelHistory(chat.id, userId);
}
TLRPC.TL_channels_deleteUserHistory req = new TLRPC.TL_channels_deleteUserHistory();
req.channel = getMessagesController().getInputChannel(chat.id);
req.user_id = getMessagesController().getInputUser(userId);
getConnectionsManager().sendRequest(req, (response, error) -> {
if (error == null) {
TLRPC.TL_messages_affectedHistory res = (TLRPC.TL_messages_affectedHistory) response;
if (res.offset > 0) {
deleteUserChannelHistory(chat, userId, res.offset);
}
getMessagesController().processNewChannelDifferenceParams(res.pts, res.pts_count, chat.id);
}
});
}
}

View File

@ -1,190 +1,215 @@
package tw.nekomimi.nekogram;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.BuildVars;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.NotificationCenter;
import org.telegram.messenger.NotificationsService;
import org.telegram.messenger.SharedConfig;
import org.telegram.messenger.R;
import java.util.Locale;
import cn.hutool.core.util.StrUtil;
@SuppressLint("ApplySharedPref")
public class NekoConfig {
public static final int TITLE_TYPE_TEXT = 0;
public static final int TITLE_TYPE_ICON = 1;
public static final int TITLE_TYPE_MIX = 2;
private static final Object sync = new Object();
public static boolean useIPv6 = false;
public static boolean showHiddenFeature = false;
private static SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
public static boolean useSystemEmoji = SharedConfig.useSystemEmoji;
public static boolean ignoreBlocked = false;
public static boolean hideProxySponsorChannel = false;
public static boolean saveCacheToPrivateDirectory = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
public static boolean disablePhotoSideAction = true;
public static boolean hideKeyboardOnChatScroll = false;
public static boolean rearVideoMessages = false;
public static boolean hideAllTab = false;
public static boolean confirmAVMessage = true;
public static boolean askBeforeCall = true;
public static boolean disableNumberRounding = false;
public static int mapPreviewProvider = 0;
public static float stickerSize = 14.0f;
public static int translationProvider = 1;
public static int tabsTitleType = TITLE_TYPE_TEXT;
public static boolean useIPv6;
public static boolean showAddToSavedMessages = true;
public static boolean showReport = false;
public static boolean showPrPr = false;
public static boolean showViewHistory = true;
public static boolean showAdminActions = true;
public static boolean showChangePermissions = true;
public static boolean showDeleteDownloadedFile = true;
public static boolean showMessageDetails = false;
public static boolean showTranslate = true;
public static boolean showRepeat = true;
public static boolean useSystemEmoji;
public static boolean ignoreBlocked;
public static boolean hideProxySponsorChannel;
public static boolean disablePhotoSideAction;
public static boolean hideKeyboardOnChatScroll;
public static boolean rearVideoMessages;
public static boolean hideAllTab;
public static boolean confirmAVMessage;
public static boolean askBeforeCall;
public static boolean disableNumberRounding;
public static int mapPreviewProvider;
public static float stickerSize;
public static int translationProvider;
public static int tabsTitleType;
public static boolean hidePhone = true;
public static int typeface = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 1 : 0;
public static boolean transparentStatusBar = false;
public static boolean forceTablet = false;
public static boolean openArchiveOnPull = false;
public static boolean avatarAsDrawerBackground = false;
public static boolean showTabsOnForward = false;
public static int nameOrder = 1;
public static int eventType = 0;
public static boolean newYear = false;
public static int actionBarDecoration = 0;
public static boolean unlimitedFavedStickers = false;
public static boolean unlimitedPinnedDialogs = false;
public static boolean disableAppBarShadow = false;
public static boolean mediaPreview = false;
public static boolean showAddToSavedMessages;
public static boolean showReport;
public static boolean showViewHistory;
public static boolean showAdminActions;
public static boolean showChangePermissions;
public static boolean showDeleteDownloadedFile;
public static boolean showMessageDetails;
public static boolean showTranslate;
public static boolean showRepeat;
public static boolean residentNotification = false;
public static boolean hidePhone;
public static int typeface;
public static boolean transparentStatusBar;
public static boolean forceTablet;
public static boolean openArchiveOnPull;
public static boolean avatarAsDrawerBackground;
public static boolean showTabsOnForward;
public static int nameOrder;
public static int eventType;
public static boolean newYear;
public static int actionBarDecoration;
public static boolean unlimitedFavedStickers;
public static boolean unlimitedPinnedDialogs;
public static boolean residentNotification;
public static boolean shouldNOTTrustMe = false;
public static boolean disableChatAction;
public static boolean sortByUnread;
public static boolean sortByUnmuted;
public static boolean sortByUser;
public static boolean sortByContacts;
private static boolean configLoaded;
public static boolean disableUndo;
public static boolean filterUsers;
public static boolean filterContacts;
public static boolean filterGroups;
public static boolean filterChannels;
public static boolean filterBots;
public static boolean filterAdmins;
public static boolean filterUnmuted;
public static boolean filterUnread;
public static boolean filterUnmutedAndUnread;
public static boolean ignoreMutedCount;
public static boolean disableSystemAccount;
public static boolean disableProxyWhenVpnEnabled;
public static boolean skipOpenLinkConfirm;
public static boolean removeTitleEmoji;
public static boolean useDefaultTheme;
public static boolean showIdAndDc;
public static String googleCloudTranslateKey;
public static String cachePath;
public static String translateToLang;
public static String translateInputLang;
public static boolean hideProxyByDefault;
public static boolean useProxyItem;
public static boolean disableAppBarShadow;
public static boolean mediaPreview;
public static String formatLang(String name) {
if (name == null) {
return LocaleController.getString("Default", R.string.Default);
} else {
if (name.contains("-")) {
return new Locale(StrUtil.subBefore(name, "-", false), StrUtil.subAfter(name, "-", false)).getDisplayName(LocaleController.getInstance().currentLocale);
} else {
return new Locale(name).getDisplayName(LocaleController.getInstance().currentLocale);
}
}
}
static {
loadConfig();
}
public static void saveConfig() {
synchronized (sync) {
try {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfing", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("useIPv6", useIPv6);
editor.putBoolean("hidePhone", hidePhone);
editor.putBoolean("ignoreBlocked", ignoreBlocked);
editor.putBoolean("forceTablet", forceTablet);
editor.putBoolean("transparentStatusBar", transparentStatusBar);
editor.putBoolean("residentNotification", residentNotification);
editor.putBoolean("hideProxySponsorChannel", hideProxySponsorChannel);
editor.putBoolean("saveCacheToPrivateDirectory", saveCacheToPrivateDirectory);
editor.putBoolean("showAddToSavedMessages", showAddToSavedMessages);
editor.putBoolean("showReport", showReport);
editor.putBoolean("showPrPr", showPrPr);
editor.putBoolean("showViewHistory", showViewHistory);
editor.putBoolean("showAdminActions", showAdminActions);
editor.putBoolean("showChangePermissions", showChangePermissions);
editor.putBoolean("showDeleteDownloadedFile", showDeleteDownloadedFile);
editor.putBoolean("showMessageDetails", showMessageDetails);
editor.putBoolean("showTranslate", showTranslate);
editor.putBoolean("showRepeat", showRepeat);
editor.putBoolean("newYear", newYear);
editor.putBoolean("unlimitedFavedStickers", unlimitedFavedStickers);
editor.putBoolean("unlimitedPinnedDialogs", unlimitedPinnedDialogs);
editor.putBoolean("disablePhotoSideAction", disablePhotoSideAction);
editor.putBoolean("hideKeyboardOnChatScroll", hideKeyboardOnChatScroll);
editor.putBoolean("openArchiveOnPull", openArchiveOnPull);
editor.putBoolean("showHiddenFeature2", showHiddenFeature);
editor.putBoolean("avatarAsDrawerBackground", avatarAsDrawerBackground);
editor.putBoolean("useSystemEmoji", useSystemEmoji);
editor.putBoolean("showTabsOnForward", showTabsOnForward);
editor.putBoolean("rearVideoMessages", rearVideoMessages);
editor.putBoolean("hideAllTab", hideAllTab);
editor.putBoolean("confirmAVMessage", confirmAVMessage);
editor.putBoolean("askBeforeCall", askBeforeCall);
editor.putBoolean("shouldNOTTrustMe", shouldNOTTrustMe);
editor.putBoolean("disableNumberRounding", disableNumberRounding);
editor.putBoolean("disableAppBarShadow", disableAppBarShadow);
editor.putBoolean("mediaPreview", mediaPreview);
editor.putFloat("stickerSize", stickerSize);
editor.putInt("typeface", typeface);
editor.putInt("nameOrder", nameOrder);
editor.putInt("mapPreviewProvider", mapPreviewProvider);
editor.putInt("translationProvider", translationProvider);
editor.putInt("eventType", eventType);
editor.putInt("actionBarDecoration", actionBarDecoration);
editor.putInt("tabsTitleType", tabsTitleType);
editor.commit();
} catch (Exception e) {
FileLog.e(e);
}
}
}
useIPv6 = preferences.getBoolean("useIPv6", false);
hidePhone = preferences.getBoolean("hidePhone", true);
ignoreBlocked = preferences.getBoolean("ignoreBlocked", false);
forceTablet = preferences.getBoolean("forceTablet", false);
typeface = preferences.getInt("typeface", 0);
nameOrder = preferences.getInt("nameOrder", 1);
mapPreviewProvider = preferences.getInt("mapPreviewProvider", 0);
transparentStatusBar = preferences.getBoolean("transparentStatusBar", false);
residentNotification = preferences.getBoolean("residentNotification", false);
hideProxySponsorChannel = preferences.getBoolean("hideProxySponsorChannel", false);
showAddToSavedMessages = preferences.getBoolean("showAddToSavedMessages", true);
showReport = preferences.getBoolean("showReport", false);
showViewHistory = preferences.getBoolean("showViewHistory", true);
showAdminActions = preferences.getBoolean("showAdminActions", true);
showChangePermissions = preferences.getBoolean("showChangePermissions", true);
showDeleteDownloadedFile = preferences.getBoolean("showDeleteDownloadedFile", true);
showMessageDetails = preferences.getBoolean("showMessageDetails", false);
showTranslate = preferences.getBoolean("showTranslate", true);
showRepeat = preferences.getBoolean("showRepeat", true);
eventType = preferences.getInt("eventType", 0);
actionBarDecoration = preferences.getInt("actionBarDecoration", 0);
newYear = preferences.getBoolean("newYear", false);
stickerSize = preferences.getFloat("stickerSize", 14.0f);
unlimitedFavedStickers = preferences.getBoolean("unlimitedFavedStickers", false);
unlimitedPinnedDialogs = preferences.getBoolean("unlimitedPinnedDialogs", false);
translationProvider = preferences.getInt("translationProvider", 1);
disablePhotoSideAction = preferences.getBoolean("disablePhotoSideAction", true);
openArchiveOnPull = preferences.getBoolean("openArchiveOnPull", false);
//showHiddenFeature = preferences.getBoolean("showHiddenFeature", false);
hideKeyboardOnChatScroll = preferences.getBoolean("hideKeyboardOnChatScroll", false);
avatarAsDrawerBackground = preferences.getBoolean("avatarAsDrawerBackground", true);
useSystemEmoji = preferences.getBoolean("useSystemEmoji", false);
showTabsOnForward = preferences.getBoolean("showTabsOnForward", showTabsOnForward);
rearVideoMessages = preferences.getBoolean("rearVideoMessages", false);
hideAllTab = preferences.getBoolean("hideAllTab", false);
public static void loadConfig() {
synchronized (sync) {
if (configLoaded) {
return;
}
disableChatAction = preferences.getBoolean("disable_chat_action", false);
sortByUnread = preferences.getBoolean("sort_by_unread", false);
sortByUnmuted = preferences.getBoolean("sort_by_unmuted", true);
sortByUser = preferences.getBoolean("sort_by_user", true);
sortByContacts = preferences.getBoolean("sort_by_contacts", true);
disableUndo = preferences.getBoolean("disable_undo", false);
filterUsers = preferences.getBoolean("filter_users", true);
filterContacts = preferences.getBoolean("filter_contacts", true);
filterGroups = preferences.getBoolean("filter_groups", true);
filterChannels = preferences.getBoolean("filter_channels", true);
filterBots = preferences.getBoolean("filter_bots", true);
filterAdmins = preferences.getBoolean("filter_admins", true);
filterUnmuted = preferences.getBoolean("filter_unmuted", true);
filterUnread = preferences.getBoolean("filter_unread", true);
filterUnmutedAndUnread = preferences.getBoolean("filter_unmuted_and_unread", true);
disableSystemAccount = preferences.getBoolean("disable_system_account", false);
disableProxyWhenVpnEnabled = preferences.getBoolean("disable_proxy_when_vpn_enabled", false);
skipOpenLinkConfirm = preferences.getBoolean("skip_open_link_confirm", false);
removeTitleEmoji = preferences.getBoolean("remove_title_emoji", true);
ignoreMutedCount = preferences.getBoolean("ignore_muted_count", true);
useDefaultTheme = preferences.getBoolean("use_default_theme", false);
showIdAndDc = preferences.getBoolean("show_id_and_dc", false);
googleCloudTranslateKey = preferences.getString("google_cloud_translate_key", null);
cachePath = preferences.getString("cache_path", null);
translateToLang = preferences.getString("trans_to_lang", null);
translateInputLang = preferences.getString("trans_input_to_lang", "en");
tabsTitleType = preferences.getInt("tabsTitleType", TITLE_TYPE_TEXT);
confirmAVMessage = preferences.getBoolean("confirmAVMessage", false);
askBeforeCall = preferences.getBoolean("askBeforeCall", false);
disableNumberRounding = preferences.getBoolean("disableNumberRounding", false);
hideProxyByDefault = preferences.getBoolean("hide_proxy_by_default", BuildVars.isMini);
useProxyItem = preferences.getBoolean("use_proxy_item",false);
disableAppBarShadow = preferences.getBoolean("disableAppBarShadow", false);
mediaPreview = preferences.getBoolean("mediaPreview", false);
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
useIPv6 = preferences.getBoolean("useIPv6", false);
hidePhone = preferences.getBoolean("hidePhone", true);
ignoreBlocked = preferences.getBoolean("ignoreBlocked", false);
forceTablet = preferences.getBoolean("forceTablet", false);
typeface = preferences.getInt("typeface", Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 1 : 0);
nameOrder = preferences.getInt("nameOrder", 1);
mapPreviewProvider = preferences.getInt("mapPreviewProvider", 0);
transparentStatusBar = preferences.getBoolean("transparentStatusBar", false);
residentNotification = preferences.getBoolean("residentNotification", false);
hideProxySponsorChannel = preferences.getBoolean("hideProxySponsorChannel", false);
saveCacheToPrivateDirectory = preferences.getBoolean("saveCacheToPrivateDirectory", Build.VERSION.SDK_INT >= Build.VERSION_CODES.N);
showAddToSavedMessages = preferences.getBoolean("showAddToSavedMessages", true);
showReport = preferences.getBoolean("showReport", false);
showPrPr = preferences.getBoolean("showPrPr", false);
showViewHistory = preferences.getBoolean("showViewHistory", true);
showAdminActions = preferences.getBoolean("showAdminActions", true);
showChangePermissions = preferences.getBoolean("showChangePermissions", true);
showDeleteDownloadedFile = preferences.getBoolean("showDeleteDownloadedFile", true);
showMessageDetails = preferences.getBoolean("showMessageDetails", false);
showTranslate = preferences.getBoolean("showTranslate", true);
showRepeat = preferences.getBoolean("showRepeat", true);
eventType = preferences.getInt("eventType", 0);
actionBarDecoration = preferences.getInt("actionBarDecoration", 0);
newYear = preferences.getBoolean("newYear", false);
stickerSize = preferences.getFloat("stickerSize", 14.0f);
unlimitedFavedStickers = preferences.getBoolean("unlimitedFavedStickers", false);
unlimitedPinnedDialogs = preferences.getBoolean("unlimitedPinnedDialogs", false);
translationProvider = preferences.getInt("translationProvider", 1);
disablePhotoSideAction = preferences.getBoolean("disablePhotoSideAction", true);
openArchiveOnPull = preferences.getBoolean("openArchiveOnPull", false);
showHiddenFeature = preferences.getBoolean("showHiddenFeature2", false);
hideKeyboardOnChatScroll = preferences.getBoolean("hideKeyboardOnChatScroll", false);
avatarAsDrawerBackground = preferences.getBoolean("avatarAsDrawerBackground", false);
useSystemEmoji = preferences.getBoolean("useSystemEmoji", SharedConfig.useSystemEmoji);
showTabsOnForward = preferences.getBoolean("showTabsOnForward", false);
rearVideoMessages = preferences.getBoolean("rearVideoMessages", false);
hideAllTab = preferences.getBoolean("hideAllTab", false);
tabsTitleType = preferences.getInt("tabsTitleType", TITLE_TYPE_TEXT);
confirmAVMessage = preferences.getBoolean("confirmAVMessage", true);
askBeforeCall = preferences.getBoolean("askBeforeCall", true);
shouldNOTTrustMe = preferences.getBoolean("shouldNOTTrustMe", false);
disableNumberRounding = preferences.getBoolean("disableNumberRounding", false);
disableAppBarShadow = preferences.getBoolean("disableAppBarShadow", false);
mediaPreview = preferences.getBoolean("mediaPreview", false);
configLoaded = true;
}
}
public static void toggleShowAddToSavedMessages() {
@ -192,7 +217,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("showAddToSavedMessages", showAddToSavedMessages);
editor.commit();
editor.apply();
}
public static void toggleShowReport() {
@ -200,24 +225,15 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("showReport", showReport);
editor.commit();
editor.apply();
}
public static void toggleShowViewHistory() {
showViewHistory = !showViewHistory;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("showViewHistory", showViewHistory);
editor.commit();
}
public static void toggleShowPrPr() {
showPrPr = !showPrPr;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("showPrPr", showPrPr);
editor.commit();
editor.apply();
}
public static void toggleShowAdminActions() {
@ -225,7 +241,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("showAdminActions", showAdminActions);
editor.commit();
editor.apply();
}
public static void toggleShowChangePermissions() {
@ -233,7 +249,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("showChangePermissions", showChangePermissions);
editor.commit();
editor.apply();
}
public static void toggleShowDeleteDownloadedFile() {
@ -241,7 +257,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("showDeleteDownloadedFile", showDeleteDownloadedFile);
editor.commit();
editor.apply();
}
public static void toggleShowMessageDetails() {
@ -249,7 +265,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("showMessageDetails", showMessageDetails);
editor.commit();
editor.apply();
}
public static void toggleShowRepeat() {
@ -257,7 +273,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("showRepeat", showRepeat);
editor.commit();
editor.apply();
}
public static void toggleIPv6() {
@ -265,7 +281,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("useIPv6", useIPv6);
editor.commit();
editor.apply();
}
public static void toggleHidePhone() {
@ -273,7 +289,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("hidePhone", hidePhone);
editor.commit();
editor.apply();
}
public static void toggleIgnoreBlocked() {
@ -281,7 +297,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("ignoreBlocked", ignoreBlocked);
editor.commit();
editor.apply();
}
public static void toggleForceTablet() {
@ -289,7 +305,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("forceTablet", forceTablet);
editor.commit();
editor.apply();
}
public static void toggleTypeface() {
@ -297,7 +313,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putInt("typeface", typeface);
editor.commit();
editor.apply();
}
public static void setNameOrder(int order) {
@ -305,7 +321,9 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putInt("nameOrder", nameOrder);
editor.commit();
editor.apply();
LocaleController.getInstance().recreateFormatters();
}
public static void setMapPreviewProvider(int provider) {
@ -313,7 +331,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putInt("mapPreviewProvider", mapPreviewProvider);
editor.commit();
editor.apply();
}
public static void toggleTransparentStatusBar() {
@ -321,7 +339,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("transparentStatusBar", transparentStatusBar);
editor.commit();
editor.apply();
}
public static void toggleResidentNotification() {
@ -329,7 +347,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("residentNotification", residentNotification);
editor.commit();
editor.apply();
ApplicationLoader.applicationContext.stopService(new Intent(ApplicationLoader.applicationContext, NotificationsService.class));
ApplicationLoader.startPushService();
}
@ -339,15 +357,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("hideProxySponsorChannel", hideProxySponsorChannel);
editor.commit();
}
public static void toggleSaveCacheToPrivateDirectory() {
saveCacheToPrivateDirectory = !saveCacheToPrivateDirectory;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("saveCacheToPrivateDirectory", saveCacheToPrivateDirectory);
editor.commit();
editor.apply();
}
public static void setEventType(int type) {
@ -355,7 +365,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putInt("eventType", eventType);
editor.commit();
editor.apply();
}
public static void setActionBarDecoration(int decoration) {
@ -363,7 +373,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putInt("actionBarDecoration", actionBarDecoration);
editor.commit();
editor.apply();
}
public static void toggleNewYear() {
@ -371,7 +381,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("newYear", newYear);
editor.commit();
editor.apply();
}
public static void toggleUnlimitedFavedStickers() {
@ -379,7 +389,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("unlimitedFavedStickers", unlimitedFavedStickers);
editor.commit();
editor.apply();
}
public static void toggleUnlimitedPinnedDialogs() {
@ -387,7 +397,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("unlimitedPinnedDialogs", unlimitedPinnedDialogs);
editor.commit();
editor.apply();
}
public static void toggleShowTranslate() {
@ -395,7 +405,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("showTranslate", showTranslate);
editor.commit();
editor.apply();
}
public static void setStickerSize(float size) {
@ -403,7 +413,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putFloat("stickerSize", stickerSize);
editor.commit();
editor.apply();
}
public static void setTranslationProvider(int provider) {
@ -411,7 +421,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putInt("translationProvider", translationProvider);
editor.commit();
editor.apply();
}
public static void toggleDisablePhotoSideAction() {
@ -419,7 +429,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("disablePhotoSideAction", disablePhotoSideAction);
editor.commit();
editor.apply();
}
public static void toggleOpenArchiveOnPull() {
@ -427,23 +437,23 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("openArchiveOnPull", openArchiveOnPull);
editor.commit();
editor.apply();
}
public static void toggleShowHiddenFeature() {
/*public static void toggleShowHiddenFeature() {
showHiddenFeature = !showHiddenFeature;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("showHiddenFeature2", showHiddenFeature);
editor.putBoolean("showHiddenFeature", showHiddenFeature);
editor.commit();
}
} */
public static void toggleHideKeyboardOnChatScroll() {
hideKeyboardOnChatScroll = !hideKeyboardOnChatScroll;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("hideKeyboardOnChatScroll", hideKeyboardOnChatScroll);
editor.commit();
editor.apply();
}
public static void toggleAvatarAsDrawerBackground() {
@ -451,15 +461,21 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("avatarAsDrawerBackground", avatarAsDrawerBackground);
editor.commit();
editor.apply();
}
public static void toggleUseSystemEmoji() {
useSystemEmoji = !useSystemEmoji;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("useSystemEmoji", useSystemEmoji);
editor.commit();
preferences.edit().putBoolean("useSystemEmoji", useSystemEmoji = !useSystemEmoji).apply();
}
public static void toggleDisableChatAction() {
preferences.edit().putBoolean("disable_chat_action", disableChatAction = !disableChatAction).apply();
}
public static void toggleSortByUnread() {
preferences.edit().putBoolean("sort_by_unread", sortByUnread = !sortByUnread).apply();
}
public static void toggleShowTabsOnForward() {
@ -467,7 +483,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("showTabsOnForward", showTabsOnForward);
editor.commit();
editor.apply();
}
public static void toggleRearVideoMessages() {
@ -475,7 +491,7 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("rearVideoMessages", rearVideoMessages);
editor.commit();
editor.apply();
}
public static void toggleHideAllTab() {
@ -483,62 +499,202 @@ public class NekoConfig {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("hideAllTab", hideAllTab);
editor.commit();
editor.apply();
}
public static void toggleSortByUnmuted() {
preferences.edit().putBoolean("sort_by_unmuted", sortByUnmuted = !sortByUnmuted).apply();
}
public static void toggleSortByUser() {
preferences.edit().putBoolean("sort_by_user", sortByUser = !sortByUser).apply();
}
public static void toggleSortByContacts() {
preferences.edit().putBoolean("sort_by_contacts", sortByContacts = !sortByContacts).apply();
}
public static void toggleDisableUndo() {
preferences.edit().putBoolean("disable_undo", disableUndo = !disableUndo).apply();
}
public static void toggleFilterUsers() {
preferences.edit().putBoolean("filter_users", filterUsers = !filterUsers).apply();
}
public static void toggleFilterContacts() {
preferences.edit().putBoolean("filter_contacts", filterContacts = !filterContacts).apply();
}
public static void toggleFilterGroups() {
preferences.edit().putBoolean("filterGroups", filterGroups = !filterGroups).apply();
}
public static void toggleFilterChannels() {
preferences.edit().putBoolean("filter_channels", filterChannels = !filterChannels).apply();
}
public static void toggleFilterBots() {
preferences.edit().putBoolean("filter_bots", filterBots = !filterBots).apply();
}
public static void toggleFilterAdmins() {
preferences.edit().putBoolean("filter_admins", filterAdmins = !filterAdmins).apply();
}
public static void toggleFilterUnmuted() {
preferences.edit().putBoolean("filter_unmuted", filterUnmuted = !filterUnmuted).apply();
}
public static void toggleDisableFilterUnread() {
preferences.edit().putBoolean("filter_unread", filterUnread = !filterUnread).apply();
}
public static void toggleFilterUnmutedAndUnread() {
preferences.edit().putBoolean("filter_unmuted_and_unread", filterUnmutedAndUnread = !filterUnmutedAndUnread).apply();
}
public static void toggleDisableSystemAccount() {
preferences.edit().putBoolean("disable_system_account", disableSystemAccount = !disableSystemAccount).apply();
}
public static void toggleDisableProxyWhenVpnEnabled() {
preferences.edit().putBoolean("disable_proxy_when_vpn_enabled", disableProxyWhenVpnEnabled = !disableProxyWhenVpnEnabled).apply();
}
public static void toggleSkipOpenLinkConfirm() {
preferences.edit().putBoolean("skip_open_link_confirm", skipOpenLinkConfirm = !skipOpenLinkConfirm).apply();
}
public static void toggleRemoveTitleEmoji() {
preferences.edit().putBoolean("remove_title_emoji", removeTitleEmoji = !removeTitleEmoji).apply();
}
public static void toggleIgnoredMutedCount() {
preferences.edit().putBoolean("ignore_muted_count", ignoreMutedCount = !ignoreMutedCount).apply();
}
public static void toggleUseDefaultTheme() {
preferences.edit().putBoolean("use_default_theme", useDefaultTheme = !useDefaultTheme).apply();
}
public static void toggleShowIdAndDc() {
preferences.edit().putBoolean("show_id_and_dc", showIdAndDc = !showIdAndDc).apply();
}
public static void setGoogleTranslateKey(String key) {
preferences.edit().putString("google_cloud_translate_key", googleCloudTranslateKey = key).apply();
}
public static void setCachePath(String cachePath) {
preferences.edit().putString("cache_path", NekoConfig.cachePath = cachePath).apply();
}
public static void setTranslateToLang(String toLang) {
preferences.edit().putString("trans_to_lang", translateToLang = toLang).apply();
}
public static void setTranslateInputToLang(String toLang) {
preferences.edit().putString("trans_input_to_lang", translateInputLang = toLang).apply();
}
public static void setTabsTitleType(int type) {
tabsTitleType = type;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putInt("tabsTitleType", tabsTitleType);
editor.commit();
preferences.edit().putInt("tabsTitleType", tabsTitleType = type).apply();
}
public static void toggleConfirmAVMessage() {
confirmAVMessage = !confirmAVMessage;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("confirmAVMessage", confirmAVMessage);
editor.commit();
preferences.edit().putBoolean("confirmAVMessage", confirmAVMessage = !confirmAVMessage).apply();
}
public static void toggleAskBeforeCall() {
askBeforeCall = !askBeforeCall;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("askBeforeCall", askBeforeCall);
editor.commit();
preferences.edit().putBoolean("askBeforeCall", askBeforeCall = !askBeforeCall).apply();
}
public static void toggleShouldNOTTrustMe() {
shouldNOTTrustMe = !shouldNOTTrustMe;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("shouldNOTTrustMe", shouldNOTTrustMe);
editor.commit();
public static void toggleHideProxyByDefault() {
preferences.edit().putBoolean("hide_proxy_by_default", hideProxyByDefault = !hideProxyByDefault).apply();
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxySettingsChanged);
}
public static void toggleUseProxyItem() {
preferences.edit().putBoolean("use_proxy_item",useProxyItem = !useProxyItem).apply();
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxySettingsChanged);
}
public static void toggleDisableNumberRounding() {
disableNumberRounding = !disableNumberRounding;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("disableNumberRounding", disableNumberRounding);
editor.commit();
preferences.edit().putBoolean("disableNumberRounding",disableNumberRounding = !disableNumberRounding).apply();
}
public static void toggleDisableAppBarShadow() {
disableAppBarShadow = !disableAppBarShadow;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("disableAppBarShadow", disableAppBarShadow);
editor.commit();
preferences.edit().putBoolean("disableAppBarShadow",disableAppBarShadow = !disableAppBarShadow).apply();
}
public static void toggleMediaPreview() {
mediaPreview = !mediaPreview;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("mediaPreview", mediaPreview);
editor.commit();
preferences.edit().putBoolean("mediaPreview",mediaPreview = !mediaPreview).apply();
}
}
}

View File

@ -0,0 +1,118 @@
package tw.nekomimi.nekogram;
import android.content.SharedPreferences;
import org.telegram.messenger.BuildConfig;
import org.telegram.messenger.BuildVars;
import tw.nekomimi.nekogram.database.NitritesKt;
public class NekoXConfig {
public static String FAQ_URL = "https://telegra.ph/NekoX-FAQ-03-31";
public static SharedPreferences preferences = NitritesKt.openMainSharedPreference("nekox_config");
public static boolean developerModeEntrance;
public static boolean developerMode = preferences.getBoolean("developer_mode", false);
public static boolean disableFlagSecure = preferences.getBoolean("disable_flag_secure", false);
public static boolean disableScreenshotDetection = preferences.getBoolean("disable_screenshot_detection", false);
public static void toggleDeveloperMode() {
preferences.edit().putBoolean("developer_mode", developerMode = !developerMode).apply();
if (!developerMode) {
preferences.edit()
.putBoolean("disable_flag_secure", disableFlagSecure = false)
.putBoolean("disable_screenshot_detection", disableScreenshotDetection = false)
.apply();
}
}
public static void toggleDisableFlagSecure() {
preferences.edit().putBoolean("disable_flag_secure", disableFlagSecure = !disableFlagSecure).apply();
}
public static void toggleDisableScreenshotDetection() {
preferences.edit().putBoolean("disable_screenshot_detection", disableScreenshotDetection = !disableScreenshotDetection).apply();
}
public static int customApi = preferences.getInt("custom_api", 0);
public static int customAppId = preferences.getInt("custom_app_id", 0);
public static String customAppHash = preferences.getString("custom_app_hash", "");
public static int currentAppId() {
switch (customApi) {
case 0:
return BuildConfig.APP_ID;
case 1:
return BuildVars.OFFICAL_APP_ID;
case 2:
return BuildVars.TGX_APP_ID;
default:
return customAppId;
}
}
public static String currentAppHash() {
switch (customApi) {
case 0:
return BuildConfig.APP_HASH;
case 1:
return BuildVars.OFFICAL_APP_HASH;
case 2:
return BuildVars.TGX_APP_HASH;
default:
return customAppHash;
}
}
public static void saveCustomApi() {
preferences.edit()
.putInt("custom_api", customApi)
.putInt("custom_app_id", customAppId)
.putString("custom_app_hash", customAppHash)
.apply();
}
public static String customDcIpv4 = preferences.getString("custom_dc_v4", "");
public static String customDcIpv6 = preferences.getString("custom_dc_v6", "");
public static int customDcPort = preferences.getInt("custom_dc_port", 0);
public static int customDcLayer = preferences.getInt("custom_dc_layer", 0);
public static String customDcPublicKey = preferences.getString("custom_dc_public_key", "");
public static long customDcFingerprint = preferences.getLong("custom_dc_fingerprint", 0L);
public static void saveCustomDc() {
preferences.edit()
.putString("custom_dc_v4", customDcIpv4)
.putString("custom_dc_v6", customDcIpv6)
.putInt("custom_dc_port", customDcPort)
.putInt("custom_dc_layer", customDcLayer)
.putString("custom_dc_public_key", customDcPublicKey)
.putLong("custom_dc_fingerprint", customDcFingerprint)
.apply();
}
}

View File

@ -0,0 +1,32 @@
package tw.nekomimi.nekogram
import android.annotation.SuppressLint
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.provider.Settings
import android.service.notification.NotificationListenerService
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import org.telegram.messenger.ApplicationLoader
import org.telegram.messenger.KeepAliveJob
import org.telegram.messenger.LocaleController
import org.telegram.messenger.R
@SuppressLint("OverrideAbstract")
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
class NekoXPushService : NotificationListenerService() {
override fun onCreate() {
super.onCreate()
ApplicationLoader.postInitApplication()
KeepAliveJob.startJob()
}
}

View File

@ -0,0 +1,354 @@
package tw.nekomimi.nekogram;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.R;
import org.telegram.ui.ActionBar.ActionBar;
import org.telegram.ui.ActionBar.AlertDialog;
import org.telegram.ui.ActionBar.BaseFragment;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.ActionBar.ThemeDescription;
import org.telegram.ui.Cells.EmptyCell;
import org.telegram.ui.Cells.HeaderCell;
import org.telegram.ui.Cells.NotificationsCheckCell;
import org.telegram.ui.Cells.ShadowSectionCell;
import org.telegram.ui.Cells.TextCheckCell;
import org.telegram.ui.Cells.TextDetailSettingsCell;
import org.telegram.ui.Cells.TextSettingsCell;
import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.RecyclerListView;
import java.io.File;
import java.util.ArrayList;
import tw.nekomimi.nekogram.parts.UpdateChecksKt;
import tw.nekomimi.nekogram.utils.AlertUtil;
import tw.nekomimi.nekogram.utils.FileUtil;
import tw.nekomimi.nekogram.utils.LocaleUtil;
import tw.nekomimi.nekogram.utils.ShareUtil;
import tw.nekomimi.nekogram.utils.UIUtil;
import tw.nekomimi.nekogram.utils.ZipUtil;
public class NekoXSettingActivity extends BaseFragment {
private RecyclerListView listView;
private ListAdapter listAdapter;
private int rowCount;
private int developerSettingsRow;
private int enableRow;
private int disableFlagSecureRow;
private int disableScreenshotDetectionRow;
private int fetchAndExportLangRow;
private int checkUpdateRepoForceRow;
@Override
public boolean onFragmentCreate() {
super.onFragmentCreate();
updateRows();
return true;
}
@Override
public View createView(Context context) {
actionBar.setBackButtonImage(R.drawable.ic_ab_back);
actionBar.setTitle(LocaleController.getString("NekoSettings", R.string.NekoSettings));
if (AndroidUtilities.isTablet()) {
actionBar.setOccupyStatusBar(false);
}
actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() {
@Override
public void onItemClick(int id) {
if (id == -1) {
finishFragment();
}
}
});
listAdapter = new ListAdapter(context);
fragmentView = new FrameLayout(context);
fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray));
FrameLayout frameLayout = (FrameLayout) fragmentView;
listView = new RecyclerListView(context);
listView.setVerticalScrollBarEnabled(false);
listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) {
@Override
public boolean supportsPredictiveItemAnimations() {
return false;
}
});
listView.setGlowColor(Theme.getColor(Theme.key_avatar_backgroundActionBarBlue));
listView.setAdapter(listAdapter);
listView.setItemAnimator(null);
listView.setLayoutAnimation(null);
frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT));
listView.setOnItemClickListener((view, position, x, y) -> {
if (position == fetchAndExportLangRow) {
fetchAndExportLang();
} else if (position == checkUpdateRepoForceRow) {
UpdateChecksKt.checkUpdate(getParentActivity(), true);
}
if (position == enableRow) {
NekoXConfig.toggleDeveloperMode();
updateRows();
} else if (position == disableFlagSecureRow) {
NekoXConfig.toggleDisableFlagSecure();
if (view instanceof TextCheckCell) {
((TextCheckCell) view).setChecked(NekoXConfig.disableFlagSecure);
}
} else if (position == disableScreenshotDetectionRow) {
NekoXConfig.toggleDisableScreenshotDetection();
if (view instanceof TextCheckCell) {
((TextCheckCell) view).setChecked(NekoXConfig.disableScreenshotDetection);
}
}
});
return fragmentView;
}
@Override
public void onResume() {
super.onResume();
if (listAdapter != null) {
listAdapter.notifyDataSetChanged();
}
}
private void updateRows() {
rowCount = 0;
developerSettingsRow = rowCount++;
enableRow = rowCount++;
disableFlagSecureRow = rowCount++;
disableScreenshotDetectionRow = rowCount++;
fetchAndExportLangRow = rowCount++;
checkUpdateRepoForceRow = rowCount++;
if (listAdapter != null) {
listAdapter.notifyDataSetChanged();
}
}
public void fetchAndExportLang() {
AlertDialog pro = new AlertDialog(getParentActivity(), 3);
pro.show();
UIUtil.runOnIoDispatcher(() -> {
LocaleUtil.fetchAndExportLang();
File zipFile = new File(ApplicationLoader.applicationContext.getCacheDir(), "languages.zip");
FileUtil.delete(zipFile);
File[] files = LocaleUtil.cacheDir.listFiles();
if (files != null) {
try {
ZipUtil.makeZip(zipFile, LocaleUtil.cacheDir);
AndroidUtilities.runOnUIThread(() -> {
pro.dismiss();
ShareUtil.shareFile(getParentActivity(), zipFile);
});
} catch (Exception e) {
AlertUtil.showToast(e);
}
} else {
AlertUtil.showToast("No files");
AndroidUtilities.runOnUIThread(pro::dismiss);
}
});
}
@Override
public ArrayList<ThemeDescription> getThemeDescriptions() {
ArrayList<ThemeDescription> themeDescriptions = new ArrayList<>();
themeDescriptions.add(new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{EmptyCell.class, TextSettingsCell.class, TextCheckCell.class, HeaderCell.class, TextDetailSettingsCell.class, NotificationsCheckCell.class}, null, null, null, Theme.key_windowBackgroundWhite));
themeDescriptions.add(new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray));
themeDescriptions.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_avatar_backgroundActionBarBlue));
themeDescriptions.add(new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_avatar_backgroundActionBarBlue));
themeDescriptions.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_avatar_actionBarIconBlue));
themeDescriptions.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle));
themeDescriptions.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_avatar_actionBarSelectorBlue));
themeDescriptions.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SUBMENUBACKGROUND, null, null, null, null, Theme.key_actionBarDefaultSubmenuBackground));
themeDescriptions.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SUBMENUITEM, null, null, null, null, Theme.key_actionBarDefaultSubmenuItem));
themeDescriptions.add(new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector));
themeDescriptions.add(new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider));
themeDescriptions.add(new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow));
themeDescriptions.add(new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
themeDescriptions.add(new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteValueText));
themeDescriptions.add(new ThemeDescription(listView, 0, new Class[]{NotificationsCheckCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
themeDescriptions.add(new ThemeDescription(listView, 0, new Class[]{NotificationsCheckCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2));
themeDescriptions.add(new ThemeDescription(listView, 0, new Class[]{NotificationsCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrack));
themeDescriptions.add(new ThemeDescription(listView, 0, new Class[]{NotificationsCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrackChecked));
themeDescriptions.add(new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
themeDescriptions.add(new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2));
themeDescriptions.add(new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrack));
themeDescriptions.add(new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrackChecked));
themeDescriptions.add(new ThemeDescription(listView, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader));
themeDescriptions.add(new ThemeDescription(listView, 0, new Class[]{TextDetailSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
themeDescriptions.add(new ThemeDescription(listView, 0, new Class[]{TextDetailSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2));
return themeDescriptions;
}
private class ListAdapter extends RecyclerListView.SelectionAdapter {
private Context mContext;
public ListAdapter(Context context) {
mContext = context;
}
@Override
public int getItemCount() {
return rowCount;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
// init
switch (holder.getItemViewType()) {
case 4: {
HeaderCell headerCell = (HeaderCell) holder.itemView;
if (position == developerSettingsRow) {
headerCell.setText(LocaleController.getString("DeveloperSettings", R.string.DeveloperSettings));
}
break;
}
case 3: {
TextCheckCell textCell = (TextCheckCell) holder.itemView;
textCell.setEnabled(true, null);
if (position == enableRow) {
textCell.setTextAndCheck("Enable", NekoXConfig.developerMode, true);
} else {
if (!NekoXConfig.developerMode) {
textCell.setEnabled(false);
}
if (position == disableFlagSecureRow) {
textCell.setTextAndCheck("Disable Flag Secure", NekoXConfig.disableFlagSecure, true);
} else if (position == disableScreenshotDetectionRow) {
textCell.setTextAndCheck("Disable Screenshot Detection", NekoXConfig.disableScreenshotDetection, false);
}
}
break;
}
case 2: {
TextSettingsCell textCell = (TextSettingsCell) holder.itemView;
if (!NekoXConfig.developerMode) {
textCell.setEnabled(false);
}
if (position == fetchAndExportLangRow) {
textCell.setText("Export Builtin Languages", true);
} else if (position == checkUpdateRepoForceRow) {
textCell.setText("Force Update (Repo)", false);
}
}
}
}
@Override
public boolean isEnabled(RecyclerView.ViewHolder holder) {
int type = holder.getItemViewType();
return (type == 2 || type == 3) && holder.itemView.isEnabled();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = null;
switch (viewType) {
case 1:
view = new ShadowSectionCell(mContext);
break;
case 2:
view = new TextSettingsCell(mContext);
view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite));
break;
case 3:
view = new TextCheckCell(mContext);
view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite));
break;
case 4:
view = new HeaderCell(mContext);
view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite));
break;
case 5:
view = new NotificationsCheckCell(mContext);
view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite));
break;
case 6:
view = new TextDetailSettingsCell(mContext);
view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite));
break;
}
view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT));
return new RecyclerListView.Holder(view);
}
@Override
public int getItemViewType(int position) {
if (position == developerSettingsRow) {
return 4;
} else if (position == fetchAndExportLangRow || position == checkUpdateRepoForceRow) {
return 2;
}
return 3;
}
}
}

View File

@ -0,0 +1,145 @@
package tw.nekomimi.nekogram.database
import android.content.SharedPreferences
import org.dizitart.no2.*
import org.dizitart.no2.filters.Filters
import org.telegram.messenger.FileLog
import tw.nekomimi.nekogram.utils.UIUtil
class DbPref(val connection: NitriteCollection) : SharedPreferences {
init {
if (!connection.hasIndex("key")) {
connection.createIndex("key", IndexOptions.indexOptions(IndexType.Unique))
}
}
val listeners = LinkedHashSet<SharedPreferences.OnSharedPreferenceChangeListener>()
val isEmpty get() = connection.find(FindOptions.limit(0, 1)).count() == 0
private inline fun <reified T> getAs(key: String, defValue: T): T {
connection.find(Filters.eq("key", key)).apply {
runCatching {
return first().get("value", T::class.java)
}
}
return defValue
}
override fun contains(key: String): Boolean {
return connection.find(Filters.eq("key", key)).count() > 0
}
override fun getBoolean(key: String, defValue: Boolean) = getAs(key, defValue)
override fun getInt(key: String, defValue: Int) = getAs(key, defValue)
override fun getAll(): MutableMap<String, *> {
val allValues = HashMap<String, Any>()
connection.find().forEach {
allValues[it.get("key", String::class.java)] = it["value"]
}
return allValues
}
override fun getLong(key: String, defValue: Long) = getAs(key, defValue)
override fun getFloat(key: String, defValue: Float) = getAs(key, defValue)
override fun getString(key: String, defValue: String?) = getAs(key, defValue)
override fun getStringSet(key: String, defValues: MutableSet<String>?) = getAs(key, defValues)
override fun unregisterOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
listeners.remove(listener)
}
override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener?) {
listener?.apply { listeners.add(this) }
}
override fun edit(): PrefEditor {
return PrefEditor()
}
inner class PrefEditor : SharedPreferences.Editor {
private var clear = false
private val toRemove = HashSet<String>()
private val toApply = HashMap<String, Any?>()
override fun clear(): PrefEditor {
clear = true
return this
}
override fun putLong(key: String, value: Long): PrefEditor {
toApply[key] = value
return this
}
override fun putInt(key: String, value: Int): PrefEditor {
toApply[key] = value
return this
}
override fun remove(key: String): PrefEditor {
toApply.remove(key)
toRemove.add(key)
return this
}
override fun putBoolean(key: String, value: Boolean): PrefEditor {
toApply[key] = value
return this
}
override fun putStringSet(key: String, values: MutableSet<String>?): PrefEditor {
toApply[key] = values
return this
}
override fun putFloat(key: String, value: Float): PrefEditor {
toApply[key] = value
return this
}
override fun putString(key: String, value: String?): PrefEditor {
toApply[key] = value
return this
}
override fun commit(): Boolean {
try {
if (clear) {
connection.remove(Filters.ALL)
} else {
toRemove.forEach {
connection.remove(Filters.eq("key", it))
}
}
toApply.forEach { (key, value) ->
if (value == null) {
connection.remove(Filters.eq("key", key))
} else {
connection.update(Filters.eq("key", key), Document().apply {
put("key", key)
put("value", value)
}, UpdateOptions.updateOptions(true))
}
}
return true
} catch (ex: Exception) {
FileLog.e(ex)
return false
}
}
override fun apply() {
UIUtil.runOnIoDispatcher(Runnable { commit() } )
}
}
}

View File

@ -0,0 +1,72 @@
package tw.nekomimi.nekogram.database
import org.dizitart.no2.Nitrite
import org.telegram.messenger.ApplicationLoader
import tw.nekomimi.nekogram.utils.FileUtil
import java.io.File
fun mkDatabase(name: String): Nitrite {
File("${ApplicationLoader.getDataDirFixed()}/database").apply {
if (exists()) deleteRecursively()
}
val file = File("${ApplicationLoader.getDataDirFixed()}/databases/$name.db")
FileUtil.initDir(file.parentFile!!)
runCatching {
return Nitrite.builder().compressed()
.filePath(file.path)
.openOrCreate()!!
}
file.deleteRecursively()
return Nitrite.builder().compressed()
.filePath(file.path)
.openOrCreate()!!
}
fun mkCacheDatabase(name: String): Nitrite {
val file = File("${ApplicationLoader.getDataDirFixed()}/cache/$name.db")
FileUtil.initDir(file.parentFile!!)
runCatching {
return Nitrite.builder().compressed()
.filePath(file.path)
.openOrCreate()!!
}
file.deleteRecursively()
return Nitrite.builder().compressed()
.filePath(file.path)
.openOrCreate()!!
}
fun Nitrite.openSharedPreference(name: String) = DbPref(getCollection(name))
private lateinit var mainSharedPreferencesDatabase: Nitrite
fun openMainSharedPreference(name: String): DbPref {
if (!::mainSharedPreferencesDatabase.isInitialized) {
mainSharedPreferencesDatabase = mkDatabase("shared_preferences")
}
return mainSharedPreferencesDatabase.openSharedPreference(name)
}

View File

@ -0,0 +1,52 @@
package tw.nekomimi.nekogram.database
import android.content.SharedPreferences
class WarppedPref(val origin: SharedPreferences) : SharedPreferences by origin {
override fun getBoolean(key: String, defValue: Boolean): Boolean {
return try {
origin.getBoolean(key, defValue)
} catch (e: ClassCastException) {
edit().remove(key).apply()
defValue
}
}
override fun getInt(key: String, defValue: Int): Int {
return try {
origin.getInt(key, defValue)
} catch (e: ClassCastException) {
edit().remove(key).apply()
defValue
}
}
override fun getLong(key: String, defValue: Long): Long {
return try {
origin.getLong(key, defValue)
} catch (e: ClassCastException) {
edit().remove(key).apply()
defValue
}
}
override fun getFloat(key: String, defValue: Float): Float {
return try {
origin.getFloat(key, defValue)
} catch (e: java.lang.ClassCastException) {
edit().remove(key).apply()
defValue
}
}
override fun getString(key: String, defValue: String?): String? {
return try {
origin.getString(key, defValue)
} catch (e: java.lang.ClassCastException) {
edit().remove(key).apply()
defValue
}
}
}

View File

@ -0,0 +1,99 @@
package tw.nekomimi.nekogram.parts;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
public class PKCS1Pub {
private static final int SEQUENCE_TAG = 0x30;
private static final int BIT_STRING_TAG = 0x03;
private static final byte[] NO_UNUSED_BITS = new byte[] { 0x00 };
private static final byte[] RSA_ALGORITHM_IDENTIFIER_SEQUENCE =
{(byte) 0x30, (byte) 0x0d,
(byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x01,
(byte) 0x05, (byte) 0x00};
public static RSAPublicKey decodePKCS1PublicKey(byte[] pkcs1PublicKeyEncoding)
throws NoSuchAlgorithmException, InvalidKeySpecException
{
byte[] subjectPublicKeyInfo2 = createSubjectPublicKeyInfoEncoding(pkcs1PublicKeyEncoding);
KeyFactory rsaKeyFactory = KeyFactory.getInstance("RSA");
RSAPublicKey generatePublic = (RSAPublicKey) rsaKeyFactory.generatePublic(new X509EncodedKeySpec(subjectPublicKeyInfo2));
return generatePublic;
}
public static byte[] createSubjectPublicKeyInfoEncoding(byte[] pkcs1PublicKeyEncoding)
{
byte[] subjectPublicKeyBitString = createDEREncoding(BIT_STRING_TAG, concat(NO_UNUSED_BITS, pkcs1PublicKeyEncoding));
byte[] subjectPublicKeyInfoValue = concat(RSA_ALGORITHM_IDENTIFIER_SEQUENCE, subjectPublicKeyBitString);
byte[] subjectPublicKeyInfoSequence = createDEREncoding(SEQUENCE_TAG, subjectPublicKeyInfoValue);
return subjectPublicKeyInfoSequence;
}
private static byte[] concat(byte[] ... bas)
{
int len = 0;
for (int i = 0; i < bas.length; i++)
{
len += bas[i].length;
}
byte[] buf = new byte[len];
int off = 0;
for (int i = 0; i < bas.length; i++)
{
System.arraycopy(bas[i], 0, buf, off, bas[i].length);
off += bas[i].length;
}
return buf;
}
private static byte[] createDEREncoding(int tag, byte[] value)
{
if (tag < 0 || tag >= 0xFF)
{
throw new IllegalArgumentException("Currently only single byte tags supported");
}
byte[] lengthEncoding = createDERLengthEncoding(value.length);
int size = 1 + lengthEncoding.length + value.length;
byte[] derEncodingBuf = new byte[size];
int off = 0;
derEncodingBuf[off++] = (byte) tag;
System.arraycopy(lengthEncoding, 0, derEncodingBuf, off, lengthEncoding.length);
off += lengthEncoding.length;
System.arraycopy(value, 0, derEncodingBuf, off, value.length);
return derEncodingBuf;
}
private static byte[] createDERLengthEncoding(int size)
{
if (size <= 0x7F)
{
// single byte length encoding
return new byte[] { (byte) size };
}
else if (size <= 0xFF)
{
// double byte length encoding
return new byte[] { (byte) 0x81, (byte) size };
}
else if (size <= 0xFFFF)
{
// triple byte length encoding
return new byte[] { (byte) 0x82, (byte) (size >> Byte.SIZE), (byte) size };
}
throw new IllegalArgumentException("size too large, only up to 64KiB length encoding supported: " + size);
}
}

View File

@ -0,0 +1,232 @@
package tw.nekomimi.nekogram.parts
import android.app.Activity
import android.content.IntentSender
import com.google.android.play.core.appupdate.AppUpdateManagerFactory
import com.google.android.play.core.install.InstallStateUpdatedListener
import com.google.android.play.core.install.model.AppUpdateType
import com.google.android.play.core.install.model.InstallStatus
import com.google.android.play.core.install.model.UpdateAvailability
import org.json.JSONObject
import org.telegram.messenger.BuildConfig
import org.telegram.messenger.LocaleController
import org.telegram.messenger.R
import org.telegram.ui.Cells.TextCell
import tw.nekomimi.nekogram.BottomBuilder
import tw.nekomimi.nekogram.ExternalGcm
import tw.nekomimi.nekogram.NekoXConfig
import tw.nekomimi.nekogram.utils.*
import java.util.*
fun Activity.switchVersion() {
val builder = BottomBuilder(this)
builder.addItems(arrayOf(
"Mini Release",
"Mini Release NoGcm",
"Full Release",
"Full Release NoGcm"
).filterIndexed { index, text ->
!(BuildConfig.BUILD_TYPE == when {
text.endsWith("NoGcm") -> "releaseNoGcm"
else -> "release"
} && BuildConfig.FLAVOR == text.substringBefore(" ").toLowerCase())
}.toTypedArray()) { index: Int, text: String, _: TextCell ->
builder.dismiss()
val buildType = when {
text.endsWith("NoGcm") -> "releaseNoGcm"
else -> "release"
}
val flavor = text.substringBefore(" ").toLowerCase()
val progress = AlertUtil.showProgress(this)
progress.show()
UIUtil.runOnIoDispatcher {
val ex = mutableListOf<Throwable>()
UpdateUtil.updateUrls.forEach { url ->
runCatching {
val updateInfo = JSONObject(HttpUtil.get("$url/update.json"))
val code = updateInfo.getInt("versionCode")
UIUtil.runOnUIThread {
progress.dismiss()
UpdateUtil.doUpdate(this, code, updateInfo.getString("defaultFlavor"), buildType, flavor)
}
return@runOnIoDispatcher
}.onFailure {
ex.add(it)
}
}
progress.dismiss()
AlertUtil.showToast(ex.joinToString("\n") { it.message ?: it.javaClass.simpleName })
}
}
builder.show()
}
@JvmOverloads
fun Activity.checkUpdate(force: Boolean = false) {
val progress = AlertUtil.showProgress(this)
progress.show()
UIUtil.runOnIoDispatcher {
if (ExternalGcm.checkPlayServices() && !force) {
progress.uUpdate(LocaleController.getString("Checking", R.string.Checking) + " (Play Store)")
val manager = AppUpdateManagerFactory.create(this)
manager.registerListener(InstallStateUpdatedListener {
if (it.installStatus() == InstallStatus.DOWNLOADED) {
val builder = BottomBuilder(this)
builder.addTitle(LocaleController.getString("UpdateDownloaded", R.string.UpdateDownloaded), false)
builder.addItem(LocaleController.getString("UpdateUpdate", R.string.UpdateUpdate), R.drawable.baseline_system_update_24, false) {
manager.completeUpdate()
}
builder.addItem(LocaleController.getString("UpdateLater", R.string.UpdateLater), R.drawable.baseline_watch_later_24, false, null)
builder.show()
}
})
manager.appUpdateInfo.addOnSuccessListener {
progress.dismiss()
if (it.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE && it.availableVersionCode() > BuildConfig.VERSION_CODE) {
try {
manager.startUpdateFlowForResult(it, AppUpdateType.FLEXIBLE, this, 114514)
} catch (ignored: IntentSender.SendIntentException) {
}
} else {
AlertUtil.showToast(LocaleController.getString("NoUpdate", R.string.NoUpdate))
}
}.addOnFailureListener {
progress.uDismiss()
AlertUtil.showToast(it.message ?: it.javaClass.simpleName)
}
return@runOnIoDispatcher
}
progress.uUpdate(LocaleController.getString("Checking", R.string.Checking) + " (Repo)")
val ex = LinkedList<Throwable>()
UpdateUtil.updateUrls.forEach { url ->
runCatching {
val updateInfo = JSONObject(HttpUtil.get("$url/update.json"))
val code = updateInfo.getInt("versionCode")
progress.uDismiss()
if (code > BuildConfig.VERSION_CODE || force) UIUtil.runOnUIThread {
val builder = BottomBuilder(this)
builder.addTitle(LocaleController.getString("UpdateAvailable", R.string.UpdateAvailable), updateInfo.getString("version"))
builder.addItem(LocaleController.getString("UpdateUpdate", R.string.UpdateUpdate), R.drawable.baseline_system_update_24, false) {
UpdateUtil.doUpdate(this, code, updateInfo.getString("defaultFlavor"))
builder.dismiss()
NekoXConfig.preferences.edit().remove("ignored_update_at").remove("ignore_update_at").apply()
}
builder.addItem(LocaleController.getString("UpdateLater", R.string.UpdateLater), R.drawable.baseline_watch_later_24, false) {
builder.dismiss()
NekoXConfig.preferences.edit().putLong("ignored_update_at", System.currentTimeMillis()).apply()
}
builder.addItem(LocaleController.getString("Ignore", R.string.Ignore), R.drawable.baseline_block_24, true) {
builder.dismiss()
NekoXConfig.preferences.edit().putInt("ignore_update", code).apply()
}
builder.show()
} else {
AlertUtil.showToast(LocaleController.getString("NoUpdate", R.string.NoUpdate))
}
return@runOnIoDispatcher
}.onFailure {
ex.add(it)
}
}
progress.uDismiss()
AlertUtil.showToast(ex.joinToString("\n") { it.message ?: it.javaClass.simpleName })
}
}

View File

@ -0,0 +1,303 @@
package tw.nekomimi.nekogram.utils
import android.content.Context
import android.widget.LinearLayout
import android.widget.Toast
import org.telegram.messenger.AndroidUtilities
import org.telegram.messenger.ApplicationLoader
import org.telegram.messenger.LocaleController
import org.telegram.messenger.R
import org.telegram.tgnet.TLRPC
import org.telegram.ui.ActionBar.AlertDialog
import org.telegram.ui.Components.EditTextBoldCursor
import org.telegram.ui.Components.NumberPicker
import tw.nekomimi.nekogram.BottomBuilder
import tw.nekomimi.nekogram.NekoConfig
import java.util.*
import java.util.concurrent.atomic.AtomicReference
object AlertUtil {
@JvmStatic
fun showToast(e: Throwable) = showToast(e.message ?: e.javaClass.simpleName)
@JvmStatic
fun showToast(e: TLRPC.TL_error) = showToast("${e.code}: ${e.text}")
@JvmStatic
fun showToast(text: String) = UIUtil.runOnUIThread(Runnable {
Toast.makeText(
ApplicationLoader.applicationContext,
text.takeIf { it.isNotBlank() }
?: "喵 !",
Toast.LENGTH_LONG
).show()
})
@JvmStatic
@JvmOverloads
fun showSimpleAlert(ctx: Context?, text: String, listener: ((AlertDialog.Builder) -> Unit)? = null) {
showSimpleAlert(ctx,null, text, listener)
}
@JvmStatic
@JvmOverloads
fun showSimpleAlert(ctx: Context?,title: String?, text: String, listener: ((AlertDialog.Builder) -> Unit)? = null) = UIUtil.runOnUIThread(Runnable {
if(ctx == null) return@Runnable
val builder = AlertDialog.Builder(ctx)
builder.setTitle(title ?: LocaleController.getString("NekoX", R.string.NekoX))
builder.setMessage(text)
if (listener != null) {
builder.setPositiveButton(LocaleController.getString("OK", R.string.OK)) { _, _ ->
builder.dismissRunnable.run()
}
}
builder.show()
})
@JvmStatic
fun showCopyAlert(ctx: Context, text: String) = UIUtil.runOnUIThread(Runnable {
val builder = AlertDialog.Builder(ctx)
builder.setTitle(LocaleController.getString("Translate", R.string.Translate))
builder.setMessage(text)
builder.setNegativeButton(LocaleController.getString("Copy", R.string.Copy)) { _, _ ->
AndroidUtilities.addToClipboard(text)
AlertUtil.showToast(LocaleController.getString("TextCopied", R.string.TextCopied))
builder.dismissRunnable.run()
}
builder.setPositiveButton(LocaleController.getString("OK", R.string.OK)) { _, _ ->
builder.dismissRunnable.run()
}
builder.show()
})
@JvmOverloads
@JvmStatic
fun showProgress(ctx: Context, text: String = LocaleController.getString("Loading", R.string.Loading)): AlertDialog {
return AlertDialog.Builder(ctx, 1).apply {
setMessage(text)
}.create()
}
fun showInput(ctx: Context, title: String, hint: String, onInput: (AlertDialog.Builder, String) -> String) = UIUtil.runOnUIThread(Runnable {
val builder = AlertDialog.Builder(ctx)
builder.setTitle(title)
builder.setView(EditTextBoldCursor(ctx).apply {
setHintText(hint)
})
})
@JvmStatic
@JvmOverloads
fun showConfirm(ctx: Context, title: String, text: String? = null, icon: Int, button: String, red: Boolean, listener: Runnable) = UIUtil.runOnUIThread(Runnable {
/*
val builder = AlertDialog.Builder(ctx)
builder.setTitle(title)
builder.setMessage(AndroidUtilities.replaceTags(text))
builder.setPositiveButton(button, listener)
builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null)
val alertDialog = builder.show()
if (red) {
(alertDialog.getButton(DialogInterface.BUTTON_POSITIVE) as TextView?)?.setTextColor(Theme.getColor(Theme.key_dialogTextRed2))
}
*/
val builder = BottomBuilder(ctx)
if (text != null) {
builder.addTitle(title, text)
} else {
builder.addTitle(title)
}
builder.addItem(button, icon, red) {
builder.dismiss()
listener.run()
}
builder.addCancelItem()
builder.show()
})
@JvmStatic
@JvmOverloads
fun showTransFailedDialog(ctx: Context, noRetry: Boolean, noWeb: Boolean = false, message: String, retryRunnable: Runnable) = UIUtil.runOnUIThread(Runnable {
ctx.setTheme(R.style.Theme_TMessages)
val builder = AlertDialog.Builder(ctx)
builder.setTitle(LocaleController.getString("TranslateFailed", R.string.TranslateFailed))
builder.setMessage(message)
val reference = AtomicReference<AlertDialog>()
builder.setNeutralButton(LocaleController.getString("ChangeTranslateProvider", R.string.ChangeTranslateProvider)) {
_, _ ->
val view = reference.get().getButton(AlertDialog.BUTTON_NEUTRAL)
val popup = PopupBuilder(view, true)
val items = LinkedList<String>()
items.addAll(arrayOf(
LocaleController.getString("ProviderGoogleTranslate", R.string.ProviderGoogleTranslate),
LocaleController.getString("ProviderGoogleTranslateCN", R.string.ProviderGoogleTranslateCN),
LocaleController.getString("ProviderYandexTranslate", R.string.ProviderYandexTranslate),
LocaleController.getString("ProviderLingocloud", R.string.ProviderLingocloud)
))
if (!noWeb) {
items.addAll(arrayOf(
LocaleController.getString("ProviderGoogleTranslateWeb", R.string.ProviderGoogleTranslateWeb),
LocaleController.getString("ProviderGoogleTranslateCNWeb", R.string.ProviderGoogleTranslateCNWeb),
LocaleController.getString("ProviderBaiduFanyiWeb", R.string.ProviderBaiduFanyiWeb),
LocaleController.getString("ProviderDeepLWeb", R.string.ProviderDeepLWeb)
))
}
popup.setItems(items.toTypedArray()) { item, _ ->
reference.get().dismiss()
NekoConfig.setTranslationProvider(if (item < 4) item + 1 else -item + 3)
retryRunnable.run()
}
popup.show()
}
if (noRetry) {
builder.setPositiveButton(LocaleController.getString("Cancel", R.string.Cancel)) { _, _ ->
reference.get().dismiss()
}
} else {
builder.setPositiveButton(LocaleController.getString("Retry", R.string.Retry)) { _, _ ->
reference.get().dismiss()
retryRunnable.run()
}
builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel)) { _, _ ->
reference.get().dismiss()
}
}
reference.set(builder.create().apply {
setDismissDialogByButtons(false)
show()
})
})
fun showTimePicker(ctx: Context, title: String, callback: (Long) -> Unit) {
ctx.setTheme(R.style.Theme_TMessages)
val builder = AlertDialog.Builder(ctx)
builder.setTitle(title)
builder.setView(LinearLayout(ctx).apply {
orientation = LinearLayout.HORIZONTAL
addView(NumberPicker(ctx).apply {
minValue = 0
maxValue = 60
}, LinearLayout.LayoutParams(-2, -2).apply {
weight = 1F
})
addView(NumberPicker(ctx).apply {
minValue = 0
maxValue = 60
}, LinearLayout.LayoutParams(-2, -2).apply {
weight = 1F
})
})
}
}

View File

@ -0,0 +1,104 @@
package tw.nekomimi.nekogram.utils
import okhttp3.Dns
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.dnsoverhttps.DnsOverHttps
import org.telegram.tgnet.ConnectionsManager
import org.xbill.DNS.DohResolver
import org.xbill.DNS.Lookup
import org.xbill.DNS.TXTRecord
import org.xbill.DNS.Type
import java.net.InetAddress
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.collections.ArrayList
open class DnsFactory : Dns {
companion object : DnsFactory() {
init {
addProvider("https://mozilla.cloudflare-dns.com/dns-query")
addProvider("https://dns.google/dns-query")
addProvider("https://dns.twnic.tw/dns-query")
addProvider("https://dns.adguard.com/dns-query")
}
}
val providers = LinkedList<DnsOverHttps>()
val dnsJavaProviders = LinkedList<DohResolver>()
val client = OkHttpClient.Builder().connectTimeout(3,TimeUnit.SECONDS).build()
fun addProvider(url: String) {
providers.add(DnsOverHttps.Builder()
.client(client)
.url(url.toHttpUrl())
.includeIPv6(ConnectionsManager.useIpv6Address())
.build())
}
override fun lookup(hostname: String): List<InetAddress> {
providers.forEach {
runCatching {
return it.lookup(hostname)
}
}
runCatching {
return Dns.SYSTEM.lookup(hostname)
}
return listOf()
}
fun getTxts(domain: String): ArrayList<String> {
val results = ArrayList<String>()
dnsJavaProviders.forEach {
runCatching {
val lookup = Lookup(domain, Type.TXT)
lookup.setResolver(it)
lookup.run()
if (lookup.result == Lookup.SUCCESSFUL) {
lookup.answers.forEach {
(it as TXTRecord).strings.forEach {
results.add(it)
}
}
}
}
}
return results
}
}

View File

@ -0,0 +1,81 @@
package tw.nekomimi.nekogram.utils
import android.content.Context
import android.os.storage.StorageManager
import org.telegram.messenger.ApplicationLoader
import org.telegram.messenger.FileLog
import tw.nekomimi.nekogram.NekoConfig
import java.io.File
import java.util.*
object EnvUtil {
@JvmStatic
@Suppress("UNCHECKED_CAST")
val rootDirectories by lazy {
val mStorageManager = ApplicationLoader.applicationContext.getSystemService(Context.STORAGE_SERVICE) as StorageManager
(mStorageManager.javaClass.getMethod("getVolumePaths").invoke(mStorageManager) as Array<String>).map { File(it) }
}
@JvmStatic
val availableDirectories
get() = LinkedList<File>().apply {
add(File(ApplicationLoader.getDataDirFixed(), "files/media"))
add(File(ApplicationLoader.getDataDirFixed(), "cache/media"))
rootDirectories.forEach {
add(File(it, "Android/data/" + ApplicationLoader.applicationContext.packageName + "/files"))
add(File(it, "Android/data/" + ApplicationLoader.applicationContext.packageName + "/cache"))
}
}.map { it.path }.toTypedArray()
@JvmStatic
fun getTelegramPath(): File {
if (NekoConfig.cachePath == null) {
NekoConfig.setCachePath(ApplicationLoader.applicationContext.getExternalFilesDir(null)!!.path)
}
var telegramPath = File(NekoConfig.cachePath)
if (telegramPath.isDirectory || telegramPath.mkdirs()) {
return telegramPath
}
telegramPath = ApplicationLoader.applicationContext.getExternalFilesDir(null) ?: File(ApplicationLoader.getDataDirFixed(), "cache/files")
if (telegramPath.isDirectory || telegramPath.mkdirs()) {
return telegramPath
}
telegramPath = File(ApplicationLoader.getDataDirFixed(), "cache/files")
if (!telegramPath.isDirectory) telegramPath.mkdirs();
return telegramPath;
}
@JvmStatic
fun doTest() {
FileLog.d("rootDirectories: ${rootDirectories.size}")
rootDirectories.forEach { FileLog.d(it.path) }
}
}

View File

@ -0,0 +1,279 @@
package tw.nekomimi.nekogram.utils
import android.os.Build
import cn.hutool.core.io.resource.ResourceUtil
import org.telegram.messenger.ApplicationLoader
import org.telegram.messenger.FileLog
import java.io.File
import java.util.zip.ZipFile
object FileUtil {
@JvmStatic
fun initDir(dir: File) {
var parentDir: File? = dir
while (parentDir != null) {
if (parentDir.isDirectory) break
if (parentDir.isFile) parentDir.deleteRecursively()
parentDir = parentDir.parentFile
}
dir.mkdirs()
// ignored
}
@JvmStatic
@JvmOverloads
fun delete(file: File?, filter: (File) -> Boolean = { true }) {
runCatching {
file?.takeIf { filter(it) }?.deleteRecursively()
}
}
@JvmStatic
fun initFile(file: File) {
file.parentFile?.also { initDir(it) }
if (!file.isFile) {
if (file.isDirectory) file.deleteRecursively()
if (!file.isFile) {
if (!file.createNewFile() && !file.isFile) {
error("unable to create file ${file.path}")
}
}
}
}
@JvmStatic
fun readUtf8String(file: File) = file.readText()
@JvmStatic
fun writeUtf8String(text: String, save: File) {
initFile(save)
save.writeText(text)
}
@JvmStatic
fun saveAsset(path: String, saveTo: File) {
val assets = ApplicationLoader.applicationContext.assets
saveTo.outputStream().use {
IoUtil.copy(assets.open(path), it)
}
}
@JvmStatic
@Suppress("DEPRECATION") val abi by lazy {
val libDirs = mutableListOf<String>()
ZipFile(ApplicationLoader.applicationContext.applicationInfo.sourceDir).use {
it.getEntry("lib/") ?: return@use
for (entry in it.entries()) {
if (entry.isDirectory && it.name.length > 4 && it.name.startsWith("lib/")) {
libDirs.add(entry.name.substringAfter("lib/").substringBefore("/"))
}
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ApplicationLoader.applicationContext.applicationInfo.splitSourceDirs?.forEach { split ->
ZipFile(split).use {
it.getEntry("lib/") ?: return@use
for (entry in it.entries()) {
if (entry.isDirectory && it.name.length > 4 && it.name.startsWith("lib/")) {
libDirs.add(entry.name.substringAfter("lib/").substringBefore("/"))
}
}
}
}
}
if (libDirs.size == 1) libDirs[0] else {
Build.CPU_ABI.toLowerCase()
}.also {
FileLog.d("current abi: $it")
}
}
@JvmStatic
fun extLib(name: String): File {
val execFile = File(ApplicationLoader.applicationContext.applicationInfo.nativeLibraryDir, "lib$name.so")
if (execFile.isFile && execFile.canExecute()) return execFile
val newFile = File(ApplicationLoader.getDataDirFixed(), "cache/lib/${execFile.name}")
if (newFile.isFile) {
FileLog.d("lib already extracted: $newFile")
if (!newFile.canExecute()) {
newFile.setExecutable(true)
}
return newFile
}
if (execFile.isFile) {
FileLog.w("$execFile not executable")
if (!newFile.isFile) {
execFile.copyTo(newFile)
}
if (!newFile.canExecute()) {
newFile.setExecutable(true)
}
return newFile
}
if (!newFile.isFile) {
runCatching {
saveNonAsset("lib/${Build.CPU_ABI}/${execFile.name}", execFile)
FileLog.d("lib extracted with default abi: $execFile, ${Build.CPU_ABI}")
}.recover {
saveNonAsset("lib/${Build.CPU_ABI2}/${execFile.name}", execFile)
FileLog.d("lib extracted with abi2: $execFile, ${Build.CPU_ABI2}")
}
}
if (!execFile.canExecute()) {
execFile.setExecutable(true)
}
return execFile
}
@JvmStatic
fun saveNonAsset(path: String, saveTo: File) {
runCatching {
ResourceUtil.getStream(path).use {
FileLog.d("found nonAsset in resources: $path")
IoUtil.copy(it, saveTo)
return
}
}
ZipFile(ApplicationLoader.applicationContext.applicationInfo.sourceDir).use {
it.getInputStream(it.getEntry(path) ?: return@use).use { ins ->
FileLog.d("found nonAsset in main apk: $path")
IoUtil.copy(ins, saveTo)
return
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ApplicationLoader.applicationContext.applicationInfo.splitSourceDirs?.forEach { split ->
ZipFile(split).use {
it.getInputStream(it.getEntry(path) ?: return@use).use { ins ->
FileLog.d("found nonAsset in split apk: $path, $split")
IoUtil.copy(ins, saveTo)
return
}
}
}
}
error("res not found: $path")
}
}

View File

@ -0,0 +1,85 @@
package tw.nekomimi.nekogram.utils
import okhttp3.OkHttpClient
import okhttp3.Request
import org.telegram.messenger.SharedConfig
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.concurrent.TimeUnit
fun Request.Builder.applyUserAgent(): Request.Builder {
header("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A5297c Safari/602.1")
header("X-Requested-With", "XMLHttpRequest")
return this
}
object HttpUtil {
@JvmField
val okHttpClient = OkHttpClient().newBuilder()
.dns(DnsFactory)
.connectTimeout(3, TimeUnit.SECONDS)
.readTimeout(3, TimeUnit.SECONDS)
.build()
val okHttpClientNoDoh = OkHttpClient()
.newBuilder()
.connectTimeout(3, TimeUnit.SECONDS)
.readTimeout(3, TimeUnit.SECONDS)
.build()
@JvmStatic
val okHttpClientWithCurrProxy: OkHttpClient
get() {
return if (!SharedConfig.proxyEnabled || SharedConfig.currentProxy?.secret != null) {
okHttpClient
} else {
okHttpClient.newBuilder()
.proxy(Proxy(Proxy.Type.SOCKS, InetSocketAddress(SharedConfig.currentProxy.address, SharedConfig.currentProxy.port)))
.build()
}
}
@JvmStatic
fun get(url: String): String {
val request = Request.Builder().url(url)
.applyUserAgent()
.build()
okHttpClient.newCall(request).execute().apply {
val body = body
return body?.string() ?: error("HTTP ERROR $code")
}
}
@JvmStatic
fun getByteArray(url: String): ByteArray {
val request = Request.Builder()
.url(url)
.applyUserAgent()
.build()
okHttpClient.newCall(request).execute().apply {
return body?.bytes() ?: error("HTTP ERROR $code")
}
}
}

View File

@ -0,0 +1,46 @@
package tw.nekomimi.nekogram.utils
import java.io.File
import java.io.InputStream
import java.io.OutputStream
object IoUtil {
@JvmStatic
fun copy(inS: InputStream, outS: OutputStream) = inS.copyTo(outS)
@JvmStatic
fun copy(inS: InputStream, outF: File) {
outF.parentFile?.also { FileUtil.initDir(it) }
FileUtil.delete(outF)
outF.createNewFile()
outF.outputStream().use {
inS.copyTo(it)
}
}
@JvmStatic
fun copy(process: Process, outF: File) {
outF.parentFile?.also { FileUtil.initDir(it) }
FileUtil.delete(outF)
outF.createNewFile()
outF.outputStream().use {
process.inputStream.copyTo(it, 512)
}
}
}

View File

@ -0,0 +1,149 @@
package tw.nekomimi.nekogram.utils
import cn.hutool.core.collection.CollUtil
import cn.hutool.core.util.ArrayUtil
import cn.hutool.core.util.StrUtil
import org.telegram.ui.ActionBar.AlertDialog
import java.math.BigInteger
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.atomic.AtomicReference
import kotlin.collections.HashMap
import kotlin.reflect.KMutableProperty0
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0
/**
* 一些基于语言特性的全局函数
*/
fun <T> T.applyIf(boolean: Boolean, block: (T.() -> Unit)?): T {
if (boolean) block?.invoke(this)
return this
}
fun <T> T.applyIfNot(boolean: Boolean, block: (T.() -> Unit)?): T {
if (!boolean) block?.invoke(this)
return this
}
fun String.input(vararg params: Any): String {
return StrUtil.format(this, *params)
}
val Number.asByteArray get() = BigInteger.valueOf(toLong()).toByteArray()!!
val ByteArray.asLong get() = BigInteger(this).toLong()
val ByteArray.asInt get() = BigInteger(this).toInt()
fun <T> Array<T>.shift(): Array<T> {
return shift(1)
}
fun <T> Array<T>.shift(size: Int): Array<T> {
return ArrayUtil.sub(this, size, this.size)
}
fun <T> Collection<T>.shift() = shift(1)
fun <T> Collection<T>.shift(size: Int): Collection<T> {
return LinkedList(CollUtil.sub(this, size, this.size))
}
class WriteOnlyField<T>(val setter: (T) -> Unit) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = error("WriteOnlyField : ${property.name}")
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
setter.invoke(value)
}
}
class WeakField<T> {
private var value: T? = null
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value
?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
this.value = value
}
}
fun <T, R> receive(getter: T.() -> R) = Receiver(getter)
class Receiver<T, R>(val getter: T.() -> R) {
@Suppress("UNCHECKED_CAST")
operator fun getValue(thisRef: Any?, property: KProperty<*>): R {
return getter(thisRef as T)
}
}
fun <T : Any, R> receiveLazy(initializer: T.() -> R) = LazyReceiver(initializer)
class LazyReceiver<T : Any, R>(val initializer: T.() -> R) {
private val isInitialized = HashMap<T, Unit>()
private val cache = HashMap<T, R>()
@Suppress("UNCHECKED_CAST")
operator fun getValue(thisRef: Any, property: KProperty<*>): R {
if (isInitialized[thisRef] != null) return cache[thisRef] as R
synchronized(this) {
if (isInitialized[thisRef] != null) return cache[thisRef] as R
return initializer(thisRef as T).apply {
cache[thisRef] = this
isInitialized[thisRef] = Unit
}
}
}
}
operator fun <F> KProperty0<F>.getValue(thisRef: Any?, property: KProperty<*>): F = get()
operator fun <F> KMutableProperty0<F>.setValue(thisRef: Any?, property: KProperty<*>, value: F) = set(value)
operator fun AtomicBoolean.getValue(thisRef: Any?, property: KProperty<*>): Boolean = get()
operator fun AtomicBoolean.setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) = set(value)
operator fun AtomicInteger.getValue(thisRef: Any?, property: KProperty<*>): Int = get()
operator fun AtomicInteger.setValue(thisRef: Any?, property: KProperty<*>, value: Int) = set(value)
operator fun AtomicLong.getValue(thisRef: Any?, property: KProperty<*>): Long = get()
operator fun AtomicLong.setValue(thisRef: Any?, property: KProperty<*>, value: Long) = set(value)
operator fun <T> AtomicReference<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get()
operator fun <T> AtomicReference<T>.setValue(thisRef: Any?, property: KProperty<*>, value: T) = set(value)
fun AlertDialog.uUpdate(message: String) = UIUtil.runOnUIThread { setMessage(message) }
fun AlertDialog.uDismiss() = UIUtil.runOnUIThread { dismiss() }

View File

@ -0,0 +1,38 @@
package tw.nekomimi.nekogram.utils
import kotlinx.coroutines.runBlocking
import org.telegram.messenger.ApplicationLoader
import org.telegram.messenger.LocaleController
import tw.nekomimi.nekogram.utils.FileUtil.delete
import tw.nekomimi.nekogram.utils.FileUtil.initDir
import java.io.File
object LocaleUtil {
@JvmField
val cacheDir = File(ApplicationLoader.applicationContext.cacheDir, "builtIn_lang_export")
@JvmStatic
fun fetchAndExportLang() = runBlocking {
delete(cacheDir)
initDir(cacheDir)
for (localeInfo in LocaleController.getInstance().languages) {
if (!localeInfo.builtIn || localeInfo.pathToFile != "unofficial") continue
if (localeInfo.hasBaseLang()) {
localeInfo.pathToBaseFile.takeIf { it.isFile }?.copyTo(File(cacheDir, localeInfo.pathToBaseFile.name))
}
localeInfo.getPathToFile()?.takeIf { it.isFile }?.copyTo(File(cacheDir, localeInfo.getPathToFile().name))
}
}
}

View File

@ -0,0 +1,45 @@
package tw.nekomimi.nekogram.utils
import android.annotation.SuppressLint
import android.view.View
import org.telegram.ui.ActionBar.ActionBarMenuItem
import org.telegram.ui.ActionBar.Theme
@SuppressLint("ViewConstructor")
class PopupBuilder @JvmOverloads constructor(anchor: View, dialog: Boolean = false) : ActionBarMenuItem(anchor.context, null, Theme.ACTION_BAR_WHITE_SELECTOR_COLOR, -0x4c4c4d) {
init {
setAnchor(anchor)
isShowOnTop = dialog
isVerticalScrollBarEnabled = true
}
fun setItems(items: Array<CharSequence>, listener: (Int,CharSequence) -> Unit) {
removeAllSubItems()
items.forEachIndexed { i, v ->
addSubItem(i, v)
}
setDelegate {
listener(it,items[it])
}
}
fun show() {
toggleSubMenu()
}
}

View File

@ -0,0 +1,270 @@
package tw.nekomimi.nekogram.utils
import android.content.Context
import android.content.DialogInterface
import android.widget.TextView
import org.telegram.messenger.AndroidUtilities
import org.telegram.messenger.LocaleController
import org.telegram.messenger.MessagesController
import org.telegram.messenger.R
import org.telegram.tgnet.ConnectionsManager
import org.telegram.tgnet.TLRPC
import org.telegram.ui.ActionBar.AlertDialog
import org.telegram.ui.ActionBar.Theme
import org.telegram.ui.LaunchActivity
import org.telegram.ui.TwoStepVerificationActivity
object PrivacyUtil {
@JvmStatic
fun postCheckAll(ctx: Context, account: Int) {
if (!MessagesController.getMainSettings(account).getBoolean("privacy_warning_skip_phone_number", false)) {
postCheckPhoneNumberVisible(ctx, account)
}
if (!MessagesController.getMainSettings(account).getBoolean("privacy_warning_skip_add_by_phone", false)) {
postCheckAddMeByPhone(ctx, account)
}
if (!MessagesController.getMainSettings(account).getBoolean("privacy_warning_skip_p2p", false)) {
postCheckAllowP2p(ctx, account)
}
if (!MessagesController.getMainSettings(account).getBoolean("privacy_warning_skip_2fa", false)) {
postCheckAllow2fa(ctx, account)
}
}
private fun postCheckPhoneNumberVisible(ctx: Context, account: Int) {
ConnectionsManager.getInstance(account).sendRequest(TLRPC.TL_account_getPrivacy().apply {
key = TLRPC.TL_inputPrivacyKeyPhoneNumber()
}, { response, _ ->
if (response is TLRPC.TL_account_privacyRules) {
if (response.rules.isEmpty()) {
showPrivacyAlert(ctx, account, 0)
} else {
response.rules.forEach {
if (it is TLRPC.TL_privacyValueAllowAll) {
showPrivacyAlert(ctx, account, 0)
return@forEach
}
}
}
}
}, ConnectionsManager.RequestFlagFailOnServerErrors)
}
private fun postCheckAddMeByPhone(ctx: Context, account: Int) {
ConnectionsManager.getInstance(account).sendRequest(TLRPC.TL_account_getPrivacy().apply {
key = TLRPC.TL_inputPrivacyKeyAddedByPhone()
}, { response, _ ->
if (response is TLRPC.TL_account_privacyRules) {
if (response.rules.isEmpty()) {
showPrivacyAlert(ctx, account, 1)
} else {
response.rules.forEach {
if (it is TLRPC.TL_privacyValueAllowAll) {
showPrivacyAlert(ctx, account, 1)
return@forEach
}
}
}
}
}, ConnectionsManager.RequestFlagFailOnServerErrors)
}
private fun postCheckAllowP2p(ctx: Context, account: Int) {
ConnectionsManager.getInstance(account).sendRequest(TLRPC.TL_account_getPrivacy().apply {
key = TLRPC.TL_inputPrivacyKeyPhoneP2P()
}, { response, _ ->
if (response is TLRPC.TL_account_privacyRules) {
if (response.rules.isEmpty()) {
showPrivacyAlert(ctx, account, 2)
} else {
response.rules.forEach {
if (it is TLRPC.TL_privacyValueAllowAll) {
showPrivacyAlert(ctx, account, 2)
return@forEach
}
}
}
}
}, ConnectionsManager.RequestFlagFailOnServerErrors)
}
private fun postCheckAllow2fa(ctx: Context, account: Int) {
ConnectionsManager.getInstance(account).sendRequest(TLRPC.TL_account_getPassword(), { response, _ ->
if (response is TLRPC.TL_account_password) {
if (!response.has_password) {
show2faAlert(ctx, account, response)
}
}
}, ConnectionsManager.RequestFlagFailOnServerErrors)
}
private fun showPrivacyAlert(ctx: Context, account: Int, type: Int) {
val builder = AlertDialog.Builder(ctx)
builder.setTitle(LocaleController.getString("", R.string.PrivacyNotice))
builder.setMessage(AndroidUtilities.replaceTags(when (type) {
0 -> LocaleController.getString("PrivacyNoticePhoneVisible", R.string.PrivacyNoticePhoneVisible)
1 -> LocaleController.getString("PrivacyNoticeAddByPhone", R.string.PrivacyNoticeAddByPhone)
else -> LocaleController.getString("PrivacyNoticeP2p", R.string.PrivacyNoticeP2p)
}))
builder.setPositiveButton(LocaleController.getString("ApplySuggestion", R.string.ApplySuggestion)) { _, _ ->
ConnectionsManager.getInstance(account).sendRequest(TLRPC.TL_account_setPrivacy().apply {
key = when (type) {
0 -> TLRPC.TL_inputPrivacyKeyPhoneNumber()
1 -> TLRPC.TL_inputPrivacyKeyAddedByPhone()
else -> TLRPC.TL_inputPrivacyKeyPhoneP2P()
}
rules = arrayListOf(when (type) {
0 -> TLRPC.TL_inputPrivacyValueDisallowAll()
1 -> TLRPC.TL_inputPrivacyValueAllowContacts()
else -> TLRPC.TL_inputPrivacyValueDisallowAll()
})
}) { _, _ -> /* ignored */ }
}
builder.setNeutralButton(LocaleController.getString("Cancel", R.string.Cancel), null)
builder.setNeutralButton(LocaleController.getString("DoNotRemindAgain", R.string.DoNotRemindAgain)) { _, _ ->
MessagesController.getMainSettings(account).edit().putBoolean("privacy_warning_skip_${when (type) {
0 -> "phone_number"
1 -> "add_by_phone"
2 -> "p2p"
else -> "2fa"
}
}", true).apply()
}
runCatching {
(builder.show().getButton(DialogInterface.BUTTON_NEUTRAL) as TextView?)?.setTextColor(Theme.getColor(Theme.key_dialogTextRed2))
}
}
private fun show2faAlert(ctx: Context, account: Int, password: TLRPC.TL_account_password) {
val builder = AlertDialog.Builder(ctx)
builder.setTitle(LocaleController.getString("", R.string.PrivacyNotice))
builder.setMessage(AndroidUtilities.replaceTags(LocaleController.getString("PrivacyNotice2fa", R.string.PrivacyNotice2fa)))
builder.setPositiveButton(LocaleController.getString("Set", R.string.Set)) { _, _ ->
if (ctx is LaunchActivity) {
UIUtil.runOnUIThread(Runnable {
ctx.presentFragment(TwoStepVerificationActivity(account, password))
})
}
}
builder.setNeutralButton(LocaleController.getString("Cancel", R.string.Cancel), null)
builder.setNeutralButton(LocaleController.getString("DoNotRemindAgain", R.string.DoNotRemindAgain)) { _, _ ->
MessagesController.getMainSettings(account).edit().putBoolean("privacy_warning_skip_2fa", true).apply()
}
runCatching {
(builder.show().getButton(DialogInterface.BUTTON_NEUTRAL) as TextView?)?.setTextColor(Theme.getColor(Theme.key_dialogTextRed2))
}
}
}

View File

@ -0,0 +1,105 @@
package tw.nekomimi.nekogram.utils
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.webkit.MimeTypeMap
import androidx.core.content.FileProvider
import org.telegram.messenger.BuildConfig
import org.telegram.ui.LaunchActivity
import java.io.File
object ShareUtil {
@JvmStatic
@JvmOverloads
fun shareText(ctx: Context, text: String, choose: Boolean = false) {
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, text)
}
if (!choose) {
intent.setClass(ctx, LaunchActivity::class.java)
ctx.startActivity(intent)
} else {
ctx.startActivity(Intent.createChooser(intent, text))
}
}
@JvmOverloads
@JvmStatic
fun shareFile(ctx: Context, fileToShare: File, caption: String = "") {
val uri = if (Build.VERSION.SDK_INT >= 24) {
FileProvider.getUriForFile(ctx, BuildConfig.APPLICATION_ID + ".provider", fileToShare)
} else {
Uri.fromFile(fileToShare)
}
val i = Intent(Intent.ACTION_SEND)
if (Build.VERSION.SDK_INT >= 24) {
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
i.type = "message/rfc822"
i.putExtra(Intent.EXTRA_EMAIL, "")
if (caption.isNotBlank()) i.putExtra(Intent.EXTRA_SUBJECT, caption)
i.putExtra(Intent.EXTRA_STREAM, uri)
i.setClass(ctx, LaunchActivity::class.java)
ctx.startActivity(i)
}
@JvmOverloads
@JvmStatic
fun openFile(ctx: Context, fileToOpen: File) {
val uri = if (Build.VERSION.SDK_INT >= 24) {
FileProvider.getUriForFile(ctx, BuildConfig.APPLICATION_ID + ".provider", fileToOpen)
} else {
Uri.fromFile(fileToOpen)
}
val intent = Intent(Intent.ACTION_VIEW)
if (Build.VERSION.SDK_INT >= 24) {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
if (fileToOpen.extension.isBlank()) {
intent.type = "application/octet-stream"
} else {
intent.type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileToOpen.extension)
}
intent.data = uri
ctx.startActivity(intent)
}
}

View File

@ -0,0 +1,294 @@
package tw.nekomimi.nekogram.utils
import com.google.gson.JsonObject
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.telegram.messenger.AndroidUtilities
import org.telegram.messenger.MediaDataController
import org.telegram.messenger.NotificationCenter
import org.telegram.tgnet.ConnectionsManager
import org.telegram.tgnet.TLRPC.*
import org.telegram.ui.ActionBar.AlertDialog
import org.telegram.ui.ActionBar.BaseFragment
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
object StickersUtil {
private fun AlertDialog.updateStatus(message: CharSequence) = AndroidUtilities.runOnUIThread { setMessage(message); }
@JvmStatic
fun importStickers(stickerObj: JsonObject, f: BaseFragment, progress: AlertDialog) = runBlocking {
val cancel = AtomicBoolean()
progress.setOnCancelListener {
cancel.set(true)
}
val installed = f.mediaDataController.getStickerSets(MediaDataController.TYPE_IMAGE)
val finishLoad = AtomicBoolean()
val archivedSets = LinkedList<StickerSetCovered>()
fun loadStickers() {
val req = TL_messages_getArchivedStickers()
req.offset_id = if (archivedSets.isEmpty()) 0 else archivedSets[archivedSets.size - 1].set.id
req.limit = 100
req.masks = false
f.connectionsManager.sendRequest(req) { response, _ ->
if (response is TL_messages_archivedStickers) {
archivedSets.addAll(response.sets)
if (response.sets.size < 100) {
finishLoad.set(true)
} else {
loadStickers()
}
}
}
}
loadStickers()
stickerObj.getAsJsonObject("stickerSets")?.also {
val stickerSets = LinkedList(it.entrySet().map {
object : Map.Entry<String, String> {
override val key: String get() = it.key
override val value: String get() = it.value.asString
}
}).apply { reverse() }
val waitLock = AtomicBoolean()
install@ for (stickerSetObj in stickerSets) {
if (cancel.get()) return@runBlocking
for (s in installed) if (s.set.short_name == stickerSetObj.value) continue@install
for (s in archivedSets) if (s.set.short_name == stickerSetObj.value) continue@install
waitLock.set(false)
f.connectionsManager.sendRequest(TL_messages_installStickerSet().apply {
stickerset = TL_inputStickerSetShortName().apply {
short_name = stickerSetObj.value
}
}) { response, error ->
if (response is TL_messages_stickerSetInstallResultSuccess) {
f.mediaDataController.loadStickers(MediaDataController.TYPE_IMAGE, false, true)
progress.updateStatus("Installed: ${stickerSetObj.key}")
} else if (response is TL_messages_stickerSetInstallResultArchive) {
f.mediaDataController.loadStickers(MediaDataController.TYPE_IMAGE, false, true)
AndroidUtilities.runOnUIThread {
f.notificationCenter.postNotificationName(NotificationCenter.needAddArchivedStickers, response.sets)
}
progress.updateStatus("Archived: ${stickerSetObj.key}")
} else if (error != null) {
progress.updateStatus("Error ${error.code}: ${error.text}")
}
waitLock.set(true)
}
while (!waitLock.get() && !cancel.get()) delay(100L)
}
}
stickerObj.getAsJsonObject("archivedStickers")?.also {
val stickerSets = LinkedList(it.entrySet().map {
object : Map.Entry<String, String> {
override val key: String get() = it.key
override val value: String get() = it.value.asString
}
}).apply { reverse() }
val waitLock = AtomicBoolean()
install@ for (stickerSetObj in stickerSets) {
if (cancel.get()) return@runBlocking
waitLock.set(false)
for (s in installed) if (s.set.short_name == stickerSetObj.value) continue@install
for (s in archivedSets) if (s.set.short_name == stickerSetObj.value) continue@install
f.connectionsManager.sendRequest(TL_messages_installStickerSet().apply {
stickerset = TL_inputStickerSetShortName().apply {
short_name = stickerSetObj.value
archived = true
}
}) { response, error ->
if (response is TL_messages_stickerSetInstallResultArchive) {
f.mediaDataController.loadStickers(MediaDataController.TYPE_IMAGE, false, true)
AndroidUtilities.runOnUIThread {
f.notificationCenter.postNotificationName(NotificationCenter.needAddArchivedStickers, response.sets)
}
progress.updateStatus("Archived: ${stickerSetObj.key}")
} else if (error != null) {
progress.updateStatus("Error ${error.code}: ${error.text}")
}
waitLock.set(true)
}
while (!waitLock.get() && !cancel.get()) delay(100L)
}
}
return@runBlocking
}
@JvmStatic
fun exportStickers(account: Int, exportSets: Boolean, exportArchived: Boolean) = runBlocking {
val exportObj = JsonObject()
if (exportSets) {
exportObj.add("stickerSets", JsonObject().apply {
MediaDataController.getInstance(account).getStickerSets(MediaDataController.TYPE_IMAGE).forEach {
addProperty(it.set.title, it.set.short_name)
}
})
}
if (exportArchived) {
val finishLoad = AtomicBoolean()
val archivedSets = LinkedList<StickerSetCovered>()
fun loadStickers() {
val req = TL_messages_getArchivedStickers()
req.offset_id = if (archivedSets.isEmpty()) 0 else archivedSets[archivedSets.size - 1].set.id
req.limit = 100
req.masks = false
ConnectionsManager.getInstance(account).sendRequest(req) { response, _ ->
if (response is TL_messages_archivedStickers) {
archivedSets.addAll(response.sets)
if (response.sets.size < 100) {
finishLoad.set(true)
} else {
loadStickers()
}
}
}
}
loadStickers()
while (!finishLoad.get()) delay(100L)
exportObj.add("archivedStickers", JsonObject().apply {
archivedSets.forEach {
addProperty(it.set.title, it.set.short_name)
}
})
}
return@runBlocking exportObj
}
@JvmStatic
fun exportStickers(exportSets: Collection<StickerSet>) = runBlocking {
val exportObj = JsonObject()
exportObj.add("stickerSets", JsonObject().apply {
exportSets.forEach {
addProperty(it.title, it.short_name)
}
})
return@runBlocking exportObj
}
}

View File

@ -0,0 +1,8 @@
package tw.nekomimi.nekogram.utils
object ThreadUtil {
@JvmStatic
fun sleep(time: Long) = Thread.sleep(time)
}

View File

@ -0,0 +1,36 @@
package tw.nekomimi.nekogram.utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.telegram.messenger.ApplicationLoader
object UIUtil {
@JvmStatic
fun runOnUIThread(runnable: Runnable) = ApplicationLoader.applicationHandler.post(runnable)
fun runOnUIThread(runnable: () -> Unit) = ApplicationLoader.applicationHandler.post(runnable)
@JvmStatic
fun runOnIoDispatcher(runnable: Runnable) {
GlobalScope.launch(Dispatchers.IO) {
runnable.run()
}
}
fun runOnIoDispatcher(runnable: () -> Unit) {
GlobalScope.launch(Dispatchers.IO) {
runnable()
}
}
}

View File

@ -0,0 +1,282 @@
package tw.nekomimi.nekogram.utils
import android.app.Activity
import android.os.Build
import cn.hutool.core.io.IoUtil
import cn.hutool.core.io.StreamProgress
import okhttp3.Request
import okhttp3.Response
import okhttp3.internal.closeQuietly
import org.json.JSONObject
import org.telegram.messenger.BuildConfig
import org.telegram.messenger.BuildVars
import org.telegram.messenger.FileLog
import org.telegram.messenger.LocaleController
import org.telegram.messenger.R
import org.tukaani.xz.XZInputStream
import tw.nekomimi.nekogram.BottomBuilder
import tw.nekomimi.nekogram.ExternalGcm
import tw.nekomimi.nekogram.NekoXConfig
import java.io.File
import java.util.zip.ZipInputStream
object UpdateUtil {
val updateUrls = arrayOf("https://raw.githubusercontent.com/NekoX-Dev/Resources/master")
@JvmStatic
fun checkUpdate(ctx: Activity) = UIUtil.runOnIoDispatcher {
if (BuildVars.isUnknown) {
FileLog.d("${BuildConfig.BUILD_TYPE} version, skip update check.")
return@runOnIoDispatcher
}
if (ExternalGcm.checkPlayServices()) {
FileLog.d("checking updates from google play")
ExternalGcm.checkUpdate(ctx)
return@runOnIoDispatcher
}
FileLog.d("checking updates from repo")
if (System.currentTimeMillis() - NekoXConfig.preferences.getLong("ignored_update_at", -1) < 1 * 60 * 60 * 1000L) {
FileLog.d("ignored")
return@runOnIoDispatcher
}
updateUrls.forEach { url ->
runCatching {
val updateInfo = JSONObject(HttpUtil.get("$url/update.json"))
val code = updateInfo.getInt("versionCode")
if (code > BuildConfig.VERSION_CODE.coerceAtLeast(NekoXConfig.preferences.getInt("ignore_update", -1))) UIUtil.runOnUIThread {
FileLog.d("update available")
val builder = BottomBuilder(ctx)
builder.addTitle(LocaleController.getString("UpdateAvailable", R.string.UpdateAvailable), updateInfo.getString("version"))
builder.addItem(LocaleController.getString("UpdateUpdate", R.string.UpdateUpdate), R.drawable.baseline_system_update_24, false) {
doUpdate(ctx, code, updateInfo.getString("defaultFlavor"))
builder.dismiss()
NekoXConfig.preferences.edit().remove("ignored_update_at").remove("ignore_update_at").apply()
}
builder.addItem(LocaleController.getString("UpdateLater", R.string.UpdateLater), R.drawable.baseline_watch_later_24, false) {
builder.dismiss()
NekoXConfig.preferences.edit().putLong("ignored_update_at", System.currentTimeMillis()).apply()
}
builder.addItem(LocaleController.getString("Ignore", R.string.Ignore), R.drawable.baseline_block_24, true) {
builder.dismiss()
NekoXConfig.preferences.edit().putInt("ignore_update", code).apply()
}
runCatching {
builder.show()
}
} else {
FileLog.d("no updates")
}
return@runOnIoDispatcher
}.onFailure {
FileLog.d(it.toString())
}
}
}
fun doUpdate(ctx: Activity, targetVer: Int, defFlavor: String, buildType: String = BuildConfig.BUILD_TYPE, flavor: String = BuildConfig.FLAVOR) {
val pro = AlertUtil.showProgress(ctx)
pro.setCanCacnel(false)
pro.show()
fun update(message: String) = UIUtil.runOnUIThread { pro.setMessage(message) }
fun dismiss() = UIUtil.runOnUIThread { pro.dismiss() }
var exception: Exception? = null
UIUtil.runOnIoDispatcher {
var fileName = "NekoX-$flavor-${Build.CPU_ABI}-$buildType.apk.xz"
var response: Response? = null
runCatching {
for (url in updateUrls) {
try {
response = HttpUtil.okHttpClient.newCall(Request.Builder().url("$url/$fileName").build()).execute()
if (response!!.code != 200) error("HTTP ${response!!.code} :${response!!.body!!.string()}")
break
} catch (e: Exception) {
exception = e
}
}
if (response == null) {
for (url in updateUrls) {
try {
fileName = "NekoX-$defFlavor-${Build.CPU_ABI}-$buildType.apk.xz"
response = HttpUtil.okHttpClient.newCall(Request.Builder().url("$url/$fileName").build()).execute()
if (response!!.code != 200) error("HTTP ${response!!.code} :${response!!.body!!.string()}")
break
} catch (e: Exception) {
exception = e
}
}
}
}.onFailure {
dismiss()
AlertUtil.showSimpleAlert(ctx, LocaleController.getString("DownloadFailed", R.string.DownloadFailed), it.toString())
return@runOnIoDispatcher
}
if (response == null) {
dismiss()
AlertUtil.showSimpleAlert(ctx, LocaleController.getString("DownloadFailed", R.string.DownloadFailed), exception!!.toString())
return@runOnIoDispatcher
}
val body = response!!.body!!
val size = body.contentLength()
val target = File(ctx.externalCacheDir, "nekox-$targetVer-$flavor-$buildType.apk")
update("Downloading...")
if (target.isFile) {
runCatching {
ZipInputStream(target.inputStream()).use { it.nextEntry }
dismiss()
ShareUtil.openFile(ctx, target)
return@runOnIoDispatcher
}.onFailure {
target.delete()
}
}
runCatching {
val input = XZInputStream(body.byteStream())
FileUtil.initFile(target)
target.outputStream().use {
IoUtil.copy(input, it, IoUtil.DEFAULT_BUFFER_SIZE, object : StreamProgress {
override fun progress(progressSize: Long) {
update("$progressSize / $size")
}
override fun start() {
update("0 / $size")
}
override fun finish() {
update("Finish")
}
})
}
input.closeQuietly()
dismiss()
ShareUtil.openFile(ctx, target)
}.onFailure {
dismiss()
AlertUtil.showSimpleAlert(ctx, LocaleController.getString("DownloadFailed", R.string.DownloadFailed), it.toString())
}
}
}
}

View File

@ -0,0 +1,67 @@
package tw.nekomimi.nekogram.utils
import cn.hutool.core.util.ZipUtil
import java.io.File
import java.io.InputStream
import java.util.zip.ZipInputStream
object ZipUtil {
@JvmStatic
@JvmOverloads
fun makeZip(zipFile: File, withSrcDirs: Boolean = false, vararg contents: File) {
ZipUtil.zip(zipFile, withSrcDirs, *contents)
}
fun read(input: InputStream, path: String): ByteArray {
ZipInputStream(input).use { zip ->
while (true) {
val entry = zip.nextEntry ?: break
if (entry.name == path) return zip.readBytes()
}
}
error("path not found")
}
@JvmStatic
fun unzip(input: InputStream, output: File) {
ZipInputStream(input).use { zip ->
while (true) {
val entry = zip.nextEntry ?: break
val entryFile = File(output, entry.name)
if (entry.isDirectory) {
entryFile.mkdirs()
} else {
entryFile.outputStream().use {
zip.copyTo(it)
}
}
}
}
}
}