Merge official 8.4.1 & 8.4.2

This commit is contained in:
luvletter2333 2022-01-01 02:10:02 +08:00
commit 7474cd9718
No known key found for this signature in database
GPG Key ID: A26A8880836E1978
167 changed files with 33758 additions and 19145 deletions

View File

@ -3,15 +3,15 @@ import cn.hutool.core.util.RuntimeUtil
apply plugin: "com.android.application"
apply plugin: "kotlin-android"
def verName = "8.3.1"
def verCode = 510
def verName = "8.4.2-preview01"
def verCode = 520
if (System.getenv("DEBUG_BUILD") == "true") {
verName += "-" + RuntimeUtil.execForStr("git log --pretty=format:'%h' -n 1")
}
def officialVer = "8.3.1"
def officialCode = 2495
def officialVer = "8.4.2"
def officialCode = 2526
def serviceAccountCredentialsFile = rootProject.file("service_account_credentials.json")

View File

@ -69,8 +69,8 @@
-keep class tw.nekomimi.nekogram.InternalUpdater$NekoXReleaseNote { *; }
-keep class tw.nekomimi.nekogram.InternalUpdater$NekoXAPK { *; }
-keep class tw.nekomimi.nkmr.MiniCDNDrive$metaJSON { *; }
-keep class tw.nekomimi.nkmr.MiniCDNDrive$metaJSON_Block { *; }
## https://developers.google.com/ml-kit/known-issues#android_issues
#-keep class com.google.mlkit.nl.languageid.internal.LanguageIdentificationJni { *; }
# Constant folding for resource integers may mean that a resource passed to this method appears to be unused. Keep the method to prevent this from happening.
-keep class com.google.android.exoplayer2.upstream.RawResourceDataSource {

View File

@ -315,6 +315,11 @@ public class GcmPushListenerService extends FirebaseMessagingService {
processNotification = true;
}
}
if (loc_key.startsWith("REACT_") || loc_key.startsWith("CHAT_REACT_")) {
processNotification = true;
}
if (processNotification) {
long chat_from_id = custom.optLong("chat_from_id", 0);
long chat_from_broadcast_id = custom.optLong("chat_from_broadcast_id", 0);
@ -361,7 +366,9 @@ public class GcmPushListenerService extends FirebaseMessagingService {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("GCM received message notification " + loc_key + " for dialogId = " + dialogId + " mid = " + msg_id);
}
switch (loc_key) {
if (loc_key.startsWith("REACT_") || loc_key.startsWith("CHAT_REACT_")) {
messageText = getReactedText(loc_key, args);
} else {switch (loc_key) {
case "MESSAGE_TEXT":
case "CHANNEL_MESSAGE_TEXT": {
messageText = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, args[0], args[1]);
@ -745,274 +752,279 @@ public class GcmPushListenerService extends FirebaseMessagingService {
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_MESSAGE_PLAYLIST": {
messageText = LocaleController.formatString("NotificationGroupFew", R.string.NotificationGroupFew, args[0], args[1], LocaleController.formatPluralString("MusicFiles", Utilities.parseInt(args[2])));
localMessage = true;
break;
}
case "CHAT_MESSAGE_DOCS": {
messageText = LocaleController.formatString("NotificationGroupFew", R.string.NotificationGroupFew, args[0], args[1], LocaleController.formatPluralString("Files", 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 (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedTextUser", R.string.NotificationActionPinnedTextUser, args[0], args[1]);
} else {
if (isGroup) {
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]);
}
messageText = LocaleController.formatString("NotificationGroupForwardedFew", R.string.NotificationGroupForwardedFew, args[0], args[1], LocaleController.formatPluralString("messages", Utilities.parseInt(args[2])));
localMessage = true;
break;
}
break;
}
case "PINNED_NOTEXT": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedNoTextUser", R.string.NotificationActionPinnedNoTextUser, args[0], args[1]);
} else {
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedNoText", R.string.NotificationActionPinnedNoText, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedNoTextChannel", R.string.NotificationActionPinnedNoTextChannel, args[0]);
}
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;
}
break;
}
case "PINNED_PHOTO": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedPhotoUser", R.string.NotificationActionPinnedPhotoUser, args[0], args[1]);
} else {
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedPhoto", R.string.NotificationActionPinnedPhoto, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedPhotoChannel", R.string.NotificationActionPinnedPhotoChannel, args[0]);
}
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;
}
break;
}
case "PINNED_VIDEO": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedVideoUser", R.string.NotificationActionPinnedVideoUser, args[0], args[1]);
} else {
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedVideo", R.string.NotificationActionPinnedVideo, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedVideoChannel", R.string.NotificationActionPinnedVideoChannel, args[0]);
}
case "CHAT_MESSAGE_PLAYLIST": {
messageText = LocaleController.formatString("NotificationGroupFew", R.string.NotificationGroupFew, args[0], args[1], LocaleController.formatPluralString("MusicFiles", Utilities.parseInt(args[2])));
localMessage = true;
break;
}
break;
}
case "PINNED_ROUND": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedRoundUser", R.string.NotificationActionPinnedRoundUser, args[0], args[1]);
} else {
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedRound", R.string.NotificationActionPinnedRound, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedRoundChannel", R.string.NotificationActionPinnedRoundChannel, args[0]);
}
case "CHAT_MESSAGE_DOCS": {
messageText = LocaleController.formatString("NotificationGroupFew", R.string.NotificationGroupFew, args[0], args[1], LocaleController.formatPluralString("Files", Utilities.parseInt(args[2])));
localMessage = true;
break;
}
break;
}
case "PINNED_DOC": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedFileUser", R.string.NotificationActionPinnedFileUser, args[0], args[1]);
} else {
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedFile", R.string.NotificationActionPinnedFile, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedFileChannel", R.string.NotificationActionPinnedFileChannel, args[0]);
}
case "CHAT_MESSAGES": {
messageText = LocaleController.formatString("NotificationGroupAlbum", R.string.NotificationGroupAlbum, args[0], args[1]);
localMessage = true;
break;
}
break;
}
case "PINNED_STICKER": {
if (dialogId > 0) {
if (args.length > 1 && !TextUtils.isEmpty(args[1])) {
messageText = LocaleController.formatString("NotificationActionPinnedStickerEmojiUser", R.string.NotificationActionPinnedStickerEmojiUser, args[0], args[1]);
case "PINNED_TEXT": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedTextUser", R.string.NotificationActionPinnedTextUser, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedStickerUser", R.string.NotificationActionPinnedStickerUser, args[0]);
}
} else {
if (isGroup) {
if (args.length > 2 && !TextUtils.isEmpty(args[2])) {
messageText = LocaleController.formatString("NotificationActionPinnedStickerEmoji", R.string.NotificationActionPinnedStickerEmoji, args[0], args[2], args[1]);
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedText", R.string.NotificationActionPinnedText, args[0], args[1], args[2]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedSticker", R.string.NotificationActionPinnedSticker, args[0], args[1]);
messageText = LocaleController.formatString("NotificationActionPinnedTextChannel", R.string.NotificationActionPinnedTextChannel, args[0], args[1]);
}
}
break;
}
case "PINNED_NOTEXT": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedNoTextUser", R.string.NotificationActionPinnedNoTextUser, args[0], args[1]);
} else {
if (isGroup) {
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 (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedPhotoUser", R.string.NotificationActionPinnedPhotoUser, args[0], args[1]);
} else {
if (isGroup) {
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 (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedVideoUser", R.string.NotificationActionPinnedVideoUser, args[0], args[1]);
} else {
if (isGroup) {
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 (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedRoundUser", R.string.NotificationActionPinnedRoundUser, args[0], args[1]);
} else {
if (isGroup) {
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 (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedFileUser", R.string.NotificationActionPinnedFileUser, args[0], args[1]);
} else {
if (isGroup) {
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 (dialogId > 0) {
if (args.length > 1 && !TextUtils.isEmpty(args[1])) {
messageText = LocaleController.formatString("NotificationActionPinnedStickerEmojiChannel", R.string.NotificationActionPinnedStickerEmojiChannel, args[0], args[1]);
messageText = LocaleController.formatString("NotificationActionPinnedStickerEmojiUser", R.string.NotificationActionPinnedStickerEmojiUser, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedStickerChannel", R.string.NotificationActionPinnedStickerChannel, args[0]);
messageText = LocaleController.formatString("NotificationActionPinnedStickerUser", R.string.NotificationActionPinnedStickerUser, args[0]);
}
} else {
if (isGroup) {
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;
}
break;
}
case "PINNED_AUDIO": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedVoiceUser", R.string.NotificationActionPinnedVoiceUser, args[0], args[1]);
} else {
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedVoice", R.string.NotificationActionPinnedVoice, args[0], args[1]);
case "PINNED_AUDIO": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedVoiceUser", R.string.NotificationActionPinnedVoiceUser, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedVoiceChannel", R.string.NotificationActionPinnedVoiceChannel, args[0]);
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedVoice", R.string.NotificationActionPinnedVoice, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedVoiceChannel", R.string.NotificationActionPinnedVoiceChannel, args[0]);
}
}
break;
}
break;
}
case "PINNED_CONTACT": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedContactUser", R.string.NotificationActionPinnedContactUser, args[0], args[1]);
} else {
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedContact2", R.string.NotificationActionPinnedContact2, args[0], args[2], args[1]);
case "PINNED_CONTACT": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedContactUser", R.string.NotificationActionPinnedContactUser, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedContactChannel2", R.string.NotificationActionPinnedContactChannel2, args[0], args[1]);
if (isGroup) {
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;
}
break;
}
case "PINNED_QUIZ": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedQuizUser", R.string.NotificationActionPinnedQuizUser, args[0], args[1]);
} else {
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedQuiz2", R.string.NotificationActionPinnedQuiz2, args[0], args[2], args[1]);
case "PINNED_QUIZ": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedQuizUser", R.string.NotificationActionPinnedQuizUser, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedQuizChannel2", R.string.NotificationActionPinnedQuizChannel2, args[0], args[1]);
if (isGroup) {
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;
}
break;
}
case "PINNED_POLL": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedPollUser", R.string.NotificationActionPinnedPollUser, args[0], args[1]);
} else {
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedPoll2", R.string.NotificationActionPinnedPoll2, args[0], args[2], args[1]);
case "PINNED_POLL": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedPollUser", R.string.NotificationActionPinnedPollUser, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedPollChannel2", R.string.NotificationActionPinnedPollChannel2, args[0], args[1]);
if (isGroup) {
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;
}
break;
}
case "PINNED_GEO": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedGeoUser", R.string.NotificationActionPinnedGeoUser, args[0], args[1]);
} else {
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedGeo", R.string.NotificationActionPinnedGeo, args[0], args[1]);
case "PINNED_GEO": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedGeoUser", R.string.NotificationActionPinnedGeoUser, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedGeoChannel", R.string.NotificationActionPinnedGeoChannel, args[0]);
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedGeo", R.string.NotificationActionPinnedGeo, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedGeoChannel", R.string.NotificationActionPinnedGeoChannel, args[0]);
}
}
break;
}
break;
}
case "PINNED_GEOLIVE": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedGeoLiveUser", R.string.NotificationActionPinnedGeoLiveUser, args[0], args[1]);
} else {
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedGeoLive", R.string.NotificationActionPinnedGeoLive, args[0], args[1]);
case "PINNED_GEOLIVE": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedGeoLiveUser", R.string.NotificationActionPinnedGeoLiveUser, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedGeoLiveChannel", R.string.NotificationActionPinnedGeoLiveChannel, args[0]);
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedGeoLive", R.string.NotificationActionPinnedGeoLive, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedGeoLiveChannel", R.string.NotificationActionPinnedGeoLiveChannel, args[0]);
}
}
break;
}
break;
}
case "PINNED_GAME": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedGameUser", R.string.NotificationActionPinnedGameUser, args[0], args[1]);
} else {
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedGame", R.string.NotificationActionPinnedGame, args[0], args[1]);
case "PINNED_GAME": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedGameUser", R.string.NotificationActionPinnedGameUser, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedGameChannel", R.string.NotificationActionPinnedGameChannel, args[0]);
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedGame", R.string.NotificationActionPinnedGame, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedGameChannel", R.string.NotificationActionPinnedGameChannel, args[0]);
}
}
break;
}
break;
}
case "PINNED_GAME_SCORE": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedGameScoreUser", R.string.NotificationActionPinnedGameScoreUser, args[0], args[1]);
} else {
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedGameScore", R.string.NotificationActionPinnedGameScore, args[0], args[1]);
case "PINNED_GAME_SCORE": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedGameScoreUser", R.string.NotificationActionPinnedGameScoreUser, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedGameScoreChannel", R.string.NotificationActionPinnedGameScoreChannel, args[0]);
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedGameScore", R.string.NotificationActionPinnedGameScore, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedGameScoreChannel", R.string.NotificationActionPinnedGameScoreChannel, args[0]);
}
}
break;
}
break;
}
case "PINNED_INVOICE": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedInvoiceUser", R.string.NotificationActionPinnedInvoiceUser, args[0], args[1]);
} else {
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedInvoice", R.string.NotificationActionPinnedInvoice, args[0], args[1]);
case "PINNED_INVOICE": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedInvoiceUser", R.string.NotificationActionPinnedInvoiceUser, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedInvoiceChannel", R.string.NotificationActionPinnedInvoiceChannel, args[0]);
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedInvoice", R.string.NotificationActionPinnedInvoice, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedInvoiceChannel", R.string.NotificationActionPinnedInvoiceChannel, args[0]);
}
}
break;
}
break;
}
case "PINNED_GIF": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedGifUser", R.string.NotificationActionPinnedGifUser, args[0], args[1]);
} else {
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedGif", R.string.NotificationActionPinnedGif, args[0], args[1]);
case "PINNED_GIF": {
if (dialogId > 0) {
messageText = LocaleController.formatString("NotificationActionPinnedGifUser", R.string.NotificationActionPinnedGifUser, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedGifChannel", R.string.NotificationActionPinnedGifChannel, args[0]);
if (isGroup) {
messageText = LocaleController.formatString("NotificationActionPinnedGif", R.string.NotificationActionPinnedGif, args[0], args[1]);
} else {
messageText = LocaleController.formatString("NotificationActionPinnedGifChannel", R.string.NotificationActionPinnedGifChannel, args[0]);
}
}
break;
}
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);
case "ENCRYPTED_MESSAGE": {
messageText = LocaleController.getString("YouHaveNewMessage", R.string.YouHaveNewMessage);
name = LocaleController.getString("SecretChatName", R.string.SecretChatName);
localMessage = true;
break;
}
case "REACT_TEXT": {
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;
}
break;
}
}
if (messageText != null) {
@ -1095,6 +1107,108 @@ public class GcmPushListenerService extends FirebaseMessagingService {
}
}
private String getReactedText(String loc_key, String[] args) {
switch (loc_key) {
case "REACT_TEXT": {
return LocaleController.formatString("PushReactText", R.string.PushReactText, args);
}
case "REACT_NOTEXT": {
return LocaleController.formatString("PushReactNoText", R.string.PushReactNoText, args);
}
case "REACT_PHOTO": {
return LocaleController.formatString("PushReactPhoto", R.string.PushReactPhoto, args);
}
case "REACT_VIDEO": {
return LocaleController.formatString("PushReactVideo", R.string.PushReactVideo, args);
}
case "REACT_ROUND": {
return LocaleController.formatString("PushReactRound", R.string.PushReactRound, args);
}
case "REACT_DOC": {
return LocaleController.formatString("PushReactDoc", R.string.PushReactDoc, args);
}
case "REACT_STICKER": {
return LocaleController.formatString("PushReactSticker", R.string.PushReactSticker, args);
}
case "REACT_AUDIO": {
return LocaleController.formatString("PushReactAudio", R.string.PushReactAudio, args);
}
case "REACT_CONTACT": {
return LocaleController.formatString("PushReactContect", R.string.PushReactContect, args);
}
case "REACT_GEO": {
return LocaleController.formatString("PushReactGeo", R.string.PushReactGeo, args);
}
case "REACT_GEOLIVE": {
return LocaleController.formatString("PushReactGeoLocation", R.string.PushReactGeoLocation, args);
}
case "REACT_POLL": {
return LocaleController.formatString("PushReactPoll", R.string.PushReactPoll, args);
}
case "REACT_QUIZ": {
return LocaleController.formatString("PushReactQuiz", R.string.PushReactQuiz, args);
}
case "REACT_GAME": {
return LocaleController.formatString("PushReactGame", R.string.PushReactGame, args);
}
case "REACT_INVOICE": {
return LocaleController.formatString("PushReactInvoice", R.string.PushReactInvoice, args);
}
case "REACT_GIF": {
return LocaleController.formatString("PushReactGif", R.string.PushReactGif, args);
}
case "CHAT_REACT_TEXT": {
return LocaleController.formatString("PushChatReactText", R.string.PushChatReactText, args);
}
case "CHAT_REACT_NOTEXT": {
return LocaleController.formatString("PushChatReactNotext", R.string.PushChatReactNotext, args);
}
case "CHAT_REACT_PHOTO": {
return LocaleController.formatString("PushChatReactPhoto", R.string.PushChatReactPhoto, args);
}
case "CHAT_REACT_VIDEO": {
return LocaleController.formatString("PushChatReactVideo", R.string.PushChatReactVideo, args);
}
case "CHAT_REACT_ROUND": {
return LocaleController.formatString("PushChatReactRound", R.string.PushChatReactRound, args);
}
case "CHAT_REACT_DOC": {
return LocaleController.formatString("PushChatReactDoc", R.string.PushChatReactDoc, args);
}
case "CHAT_REACT_STICKER": {
return LocaleController.formatString("PushChatReactSticker", R.string.PushChatReactSticker, args);
}
case "CHAT_REACT_AUDIO": {
return LocaleController.formatString("PushChatReactAudio", R.string.PushChatReactAudio, args);
}
case "CHAT_REACT_CONTACT": {
return LocaleController.formatString("PushChatReactContact", R.string.PushChatReactContact, args);
}
case "CHAT_REACT_GEO": {
return LocaleController.formatString("PushChatReactGeo", R.string.PushChatReactGeo, args);
}
case "CHAT_REACT_GEOLIVE": {
return LocaleController.formatString("PushChatReactGeoLive", R.string.PushChatReactGeoLive, args);
}
case "CHAT_REACT_POLL": {
return LocaleController.formatString("PushChatReactPoll", R.string.PushChatReactPoll, args);
}
case "CHAT_REACT_QUIZ": {
return LocaleController.formatString("PushChatReactQuiz", R.string.PushChatReactQuiz, args);
}
case "CHAT_REACT_GAME": {
return LocaleController.formatString("PushChatReactGame", R.string.PushChatReactGame, args);
}
case "CHAT_REACT_INVOICE": {
return LocaleController.formatString("PushChatReactInvoice", R.string.PushChatReactInvoice, args);
}
case "CHAT_REACT_GIF": {
return LocaleController.formatString("PushChatReactGif", R.string.PushChatReactGif, args);
}
}
return null;
}
private void onDecryptError() {
for (int a : SharedConfig.activeAccounts) {
if (UserConfig.getInstance(a).isClientActivated()) {

View File

@ -97,7 +97,7 @@
<uses-permission android:name="com.oppo.launcher.permission.WRITE_SETTINGS"/>
<uses-permission android:name="me.everything.badger.permission.BADGE_COUNT_READ"/>
<uses-permission android:name="me.everything.badger.permission.BADGE_COUNT_WRITE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="replace" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
@ -112,6 +112,7 @@
android:manageSpaceActivity="org.telegram.ui.ExternalActionActivity"
android:networkSecurityConfig="@xml/network_security_config"
android:requestLegacyExternalStorage="true"
android:preserveLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="false"
android:theme="@style/Theme.TMessages.Start"
@ -508,9 +509,9 @@
</intent-filter>
</receiver>
<service android:name=".FeedWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS"
android:exported="false" />
<!-- <service android:name=".FeedWidgetService"-->
<!-- android:permission="android.permission.BIND_REMOTEVIEWS"-->
<!-- android:exported="false" />-->
<uses-library android:name="com.sec.android.app.multiwindow" android:required="false" />
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />

Binary file not shown.

View File

@ -5,7 +5,6 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.os.Build;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.view.animation.Interpolator;
@ -495,8 +494,9 @@ public class ChatListItemAnimator extends DefaultItemAnimator {
if (group == null && params.wasDraw) {
boolean isOut = chatMessageCell.getMessageObject().isOutOwner();
if ((isOut && params.lastDrawingBackgroundRect.left != chatMessageCell.getBackgroundDrawableLeft()) ||
(!isOut && params.lastDrawingBackgroundRect.right != chatMessageCell.getBackgroundDrawableRight()) ||
boolean widthChanged = (isOut && params.lastDrawingBackgroundRect.left != chatMessageCell.getBackgroundDrawableLeft()) ||
(!isOut && params.lastDrawingBackgroundRect.right != chatMessageCell.getBackgroundDrawableRight());
if (widthChanged ||
params.lastDrawingBackgroundRect.top != chatMessageCell.getBackgroundDrawableTop() ||
params.lastDrawingBackgroundRect.bottom != chatMessageCell.getBackgroundDrawableBottom()) {
moveInfo.deltaBottom = chatMessageCell.getBackgroundDrawableBottom() - params.lastDrawingBackgroundRect.bottom;
@ -509,6 +509,7 @@ public class ChatListItemAnimator extends DefaultItemAnimator {
moveInfo.animateBackgroundOnly = true;
params.animateBackgroundBoundsInner = true;
params.animateBackgroundWidth = widthChanged;
params.deltaLeft = -moveInfo.deltaLeft;
params.deltaRight = -moveInfo.deltaRight;
params.deltaTop = -moveInfo.deltaTop;
@ -1208,6 +1209,9 @@ public class ChatListItemAnimator extends DefaultItemAnimator {
}
public void groupWillChanged(MessageObject.GroupedMessages groupedMessages) {
if (groupedMessages == null) {
return;
}
if (groupedMessages.messages.size() == 0) {
groupedMessages.transitionParams.drawBackgroundForDeletedItems = true;
} else {

View File

@ -0,0 +1,42 @@
package com.carrotsearch.randomizedtesting;
/**
* Hash routines for primitive types. The implementation is based on the finalization step
* from Austin Appleby's <code>MurmurHash3</code>.
*
* @see "http://sites.google.com/site/murmurhash/"
*/
final class MurmurHash3
{
private MurmurHash3()
{
// no instances.
}
/**
* Hashes a 4-byte sequence (Java int).
*/
public static int hash(int k)
{
k ^= k >>> 16;
k *= 0x85ebca6b;
k ^= k >>> 13;
k *= 0xc2b2ae35;
k ^= k >>> 16;
return k;
}
/**
* Hashes an 8-byte sequence (Java long).
*/
public static long hash(long k)
{
k ^= k >>> 33;
k *= 0xff51afd7ed558ccdL;
k ^= k >>> 33;
k *= 0xc4ceb9fe1a85ec53L;
k ^= k >>> 33;
return k;
}
}

View File

@ -0,0 +1,97 @@
package com.carrotsearch.randomizedtesting;
import java.util.Random;
/**
* Implements Xoroshiro128PlusRandom. Not synchronized (anywhere).
*
* @see "http://xoroshiro.di.unimi.it/"
*/
@SuppressWarnings("serial")
public class Xoroshiro128PlusRandom extends Random {
private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53);
private static final float FLOAT_UNIT = 0x1.0p-24f; // 1.0 / (1L << 24);
private long s0, s1;
public Xoroshiro128PlusRandom(long seed) {
// Must be here, the only Random constructor. Has side-effects on setSeed, see below.
super(0);
s0 = MurmurHash3.hash(seed);
s1 = MurmurHash3.hash(s0);
if (s0 == 0 && s1 == 0) {
s0 = MurmurHash3.hash(0xdeadbeefL);
s1 = MurmurHash3.hash(s0);
}
}
@Override
public void setSeed(long seed) {
// Called from super constructor and observing uninitialized state?
if (s0 == 0 && s1 == 0) {
return;
}
throw new RuntimeException("No seed set");
}
@Override
public boolean nextBoolean() {
return nextLong() >= 0;
}
@Override
public void nextBytes(byte[] bytes) {
for (int i = 0, len = bytes.length; i < len; ) {
long rnd = nextInt();
for (int n = Math.min(len - i, 8); n-- > 0; rnd >>>= 8) {
bytes[i++] = (byte) rnd;
}
}
}
@Override
public double nextDouble() {
return (nextLong() >>> 11) * DOUBLE_UNIT;
}
@Override
public float nextFloat() {
return (nextInt() >>> 8) * FLOAT_UNIT;
}
@Override
public int nextInt() {
return (int) nextLong();
}
@Override
public int nextInt(int n) {
// Leave superclass's implementation.
return super.nextInt(n);
}
@Override
public double nextGaussian() {
// Leave superclass's implementation.
return super.nextGaussian();
}
@Override
public long nextLong() {
final long s0 = this.s0;
long s1 = this.s1;
final long result = s0 + s1;
s1 ^= s0;
this.s0 = Long.rotateLeft(s0, 55) ^ s1 ^ s1 << 14;
this.s1 = Long.rotateLeft(s1, 36);
return result;
}
@Override
protected int next(int bits) {
return ((int) nextLong()) >>> (32 - bits);
}
}

View File

@ -19,7 +19,11 @@ package com.google.zxing.qrcode;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
@ -33,6 +37,8 @@ import com.google.zxing.qrcode.encoder.QRCode;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.R;
import org.telegram.messenger.SvgHelper;
import org.telegram.ui.Components.RLottieDrawable;
import java.util.Arrays;
import java.util.Map;
@ -243,7 +249,194 @@ public final class QRCodeWriter {
return x >= 0 && y >= 0 && x < input.getWidth() && y < input.getHeight() && input.get(x, y) == 1;
}
public int getImageSize() {
public Bitmap encode(String contents, int width, int height, Map<EncodeHintType, ?> hints, Bitmap bitmap) throws WriterException {
return encode(contents, width, height, hints, bitmap, 1.0f, 0xffffffff, 0xff000000);
}
public Bitmap encode(String contents, int width, int height, Map<EncodeHintType, ?> hints, Bitmap bitmap, float radiusFactor, int backgroundColor, int color) throws WriterException {
if (contents.isEmpty()) {
throw new IllegalArgumentException("Found empty contents");
}
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Requested dimensions are too small: " + width + 'x' + height);
}
ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L;
int quietZone = QUIET_ZONE_SIZE;
if (hints != null) {
if (hints.containsKey(EncodeHintType.ERROR_CORRECTION)) {
errorCorrectionLevel = ErrorCorrectionLevel.valueOf(hints.get(EncodeHintType.ERROR_CORRECTION).toString());
}
if (hints.containsKey(EncodeHintType.MARGIN)) {
quietZone = Integer.parseInt(hints.get(EncodeHintType.MARGIN).toString());
}
}
QRCode code = Encoder.encode(contents, errorCorrectionLevel, hints);
input = code.getMatrix();
if (input == null) {
throw new IllegalStateException();
}
int inputWidth = input.getWidth();
int inputHeight = input.getHeight();
for (int x = 0; x < inputWidth; x++) {
if (has(x, 0)) {
sideQuadSize++;
} else {
break;
}
}
int qrWidth = inputWidth + (quietZone * 2);
int qrHeight = inputHeight + (quietZone * 2);
int outputWidth = Math.max(width, qrWidth);
int outputHeight = Math.max(height, qrHeight);
int multiple = Math.min(outputWidth / qrWidth, outputHeight / qrHeight);
int padding = 16;
int size = multiple * inputWidth + padding * 2;
if (bitmap == null || bitmap.getWidth() != size) {
bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
}
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(backgroundColor);
Paint blackPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
blackPaint.setColor(color);
GradientDrawable rect = new GradientDrawable();
rect.setShape(GradientDrawable.RECTANGLE);
rect.setCornerRadii(radii);
imageBloks = Math.round((size - 32) / 4.65f / multiple);
if (imageBloks % 2 != inputWidth % 2) {
imageBloks++;
}
imageBlockX = (inputWidth - imageBloks) / 2;
imageSize = imageBloks * multiple - 24;
int imageX = (size - imageSize) / 2;
boolean isTransparentBackground = Color.alpha(backgroundColor) == 0;
Path clipPath = new Path();
RectF rectF = new RectF();
for (int a = 0; a < 3; a++) {
int x, y;
if (a == 0) {
x = padding;
y = padding;
} else if (a == 1) {
x = size - sideQuadSize * multiple - padding;
y = padding;
} else {
x = padding;
y = size - sideQuadSize * multiple - padding;
}
float r;
if (isTransparentBackground) {
rectF.set(x + multiple, y + multiple, x + (sideQuadSize - 1) * multiple, y + (sideQuadSize - 1) * multiple);
r = (sideQuadSize * multiple) / 4.0f * radiusFactor;
clipPath.reset();
clipPath.addRoundRect(rectF, r, r, Path.Direction.CW);
clipPath.close();
canvas.save();
canvas.clipPath(clipPath, Region.Op.DIFFERENCE);
}
r = (sideQuadSize * multiple) / 3.0f * radiusFactor;
Arrays.fill(radii, r);
rect.setColor(color);
rect.setBounds(x, y, x + sideQuadSize * multiple, y + sideQuadSize * multiple);
rect.draw(canvas);
canvas.drawRect(x + multiple, y + multiple, x + (sideQuadSize - 1) * multiple, y + (sideQuadSize - 1) * multiple, blackPaint);
if (isTransparentBackground) {
canvas.restore();
}
if (!isTransparentBackground) {
r = (sideQuadSize * multiple) / 4.0f * radiusFactor;
Arrays.fill(radii, r);
rect.setColor(backgroundColor);
rect.setBounds(x + multiple, y + multiple, x + (sideQuadSize - 1) * multiple, y + (sideQuadSize - 1) * multiple);
rect.draw(canvas);
}
r = ((sideQuadSize - 2) * multiple) / 4.0f * radiusFactor;
Arrays.fill(radii, r);
rect.setColor(color);
rect.setBounds(x + multiple * 2, y + multiple * 2, x + (sideQuadSize - 2) * multiple, y + (sideQuadSize - 2) * multiple);
rect.draw(canvas);
}
float r = multiple / 2.0f * radiusFactor;
for (int y = 0, outputY = padding; y < inputHeight; y++, outputY += multiple) {
for (int x = 0, outputX = padding; x < inputWidth; x++, outputX += multiple) {
if (has(x, y)) {
Arrays.fill(radii, r);
if (has(x, y - 1)) {
radii[0] = radii[1] = 0;
radii[2] = radii[3] = 0;
}
if (has(x, y + 1)) {
radii[6] = radii[7] = 0;
radii[4] = radii[5] = 0;
}
if (has(x - 1, y)) {
radii[0] = radii[1] = 0;
radii[6] = radii[7] = 0;
}
if (has(x + 1, y)) {
radii[2] = radii[3] = 0;
radii[4] = radii[5] = 0;
}
rect.setColor(color);
rect.setBounds(outputX, outputY, outputX + multiple, outputY + multiple);
rect.draw(canvas);
} else {
boolean has = false;
Arrays.fill(radii, 0);
if (has(x - 1, y - 1) && has(x - 1, y) && has(x, y - 1)) {
radii[0] = radii[1] = r;
has = true;
}
if (has(x + 1, y - 1) && has(x + 1, y) && has(x, y - 1)) {
radii[2] = radii[3] = r;
has = true;
}
if (has(x - 1, y + 1) && has(x - 1, y) && has(x, y + 1)) {
radii[6] = radii[7] = r;
has = true;
}
if (has(x + 1, y + 1) && has(x + 1, y) && has(x, y + 1)) {
radii[4] = radii[5] = r;
has = true;
}
if (has && !isTransparentBackground) {
canvas.drawRect(outputX, outputY, outputX + multiple, outputY + multiple, blackPaint);
rect.setColor(backgroundColor);
rect.setBounds(outputX, outputY, outputX + multiple, outputY + multiple);
rect.draw(canvas);
}
}
}
}
String svg = RLottieDrawable.readRes(null, R.raw.qr_logo);
Bitmap icon = SvgHelper.getBitmap(svg, imageSize, imageSize, false);
canvas.drawBitmap(icon, imageX, imageX, null);
icon.recycle();
canvas.setBitmap(null);
return bitmap;
}
public int getImageSize() {
return imageSize;
}
}

View File

@ -28,6 +28,7 @@ import android.content.res.AssetFileDescriptor;
import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
@ -1993,6 +1994,29 @@ public class AndroidUtilities {
}
}
public static int charSequenceIndexOf(CharSequence cs, CharSequence needle, int fromIndex) {
for (int i = fromIndex; i < cs.length() - needle.length(); i++) {
boolean eq = true;
for (int j = 0; j < needle.length(); j++) {
if (needle.charAt(j) != cs.charAt(i + j)) {
eq = false;
break;
}
}
if (eq)
return i;
}
return -1;
}
public static int charSequenceIndexOf(CharSequence cs, CharSequence needle) {
return charSequenceIndexOf(cs, needle, 0);
}
public static boolean charSequenceContains(CharSequence cs, CharSequence needle) {
return charSequenceIndexOf(cs, needle) != -1;
}
public static CharSequence getTrimmedString(CharSequence src) {
if (src == null || src.length() == 0) {
return src;
@ -2936,6 +2960,57 @@ public class AndroidUtilities {
return openForView(f, fileName, document.mime_type, activity, null);
}
public static SpannableStringBuilder formatSpannableSimple(String format, CharSequence... cs) {
return formatSpannable(format, i -> "%s", cs);
}
public static SpannableStringBuilder formatSpannable(String format, CharSequence... cs) {
if (format.contains("%s"))
return formatSpannableSimple(format, cs);
return formatSpannable(format, i -> "%" + (i + 1) + "$s", cs);
}
public static SpannableStringBuilder formatSpannable(String format, GenericProvider<Integer, String> keysProvider, CharSequence... cs) {
SpannableStringBuilder stringBuilder = new SpannableStringBuilder(format);
for (int i = 0; i < cs.length; i++) {
String key = keysProvider.provide(i);
int j = format.indexOf(key);
if (j != -1) {
stringBuilder.replace(j, j + key.length(), cs[i]);
format = format.substring(0, j) + cs[i].toString() + format.substring(j + key.length());
}
}
return stringBuilder;
}
public static CharSequence replaceTwoNewLinesToOne(CharSequence original) {
char[] buf = new char[2];
if (original instanceof StringBuilder) {
StringBuilder stringBuilder = (StringBuilder) original;
for (int a = 0, N = original.length(); a < N - 2; a++) {
stringBuilder.getChars(a, a + 2, buf, 0);
if (buf[0] == '\n' && buf[1] == '\n') {
stringBuilder = stringBuilder.replace(a, a + 2, "\n");
a--;
N--;
}
}
return original;
} else if (original instanceof SpannableStringBuilder) {
SpannableStringBuilder stringBuilder = (SpannableStringBuilder) original;
for (int a = 0, N = original.length(); a < N - 2; a++) {
stringBuilder.getChars(a, a + 2, buf, 0);
if (buf[0] == '\n' && buf[1] == '\n') {
stringBuilder = stringBuilder.replace(a, a + 2, "\n");
a--;
N--;
}
}
return original;
}
return original.toString().replace("\n\n", "\n");
}
public static CharSequence replaceNewLines(CharSequence original) {
if (original instanceof StringBuilder) {
StringBuilder stringBuilder = (StringBuilder) original;
@ -2944,6 +3019,7 @@ public class AndroidUtilities {
stringBuilder.setCharAt(a, ' ');
}
}
return original;
} else if (original instanceof SpannableStringBuilder) {
SpannableStringBuilder stringBuilder = (SpannableStringBuilder) original;
for (int a = 0, N = original.length(); a < N; a++) {
@ -2951,6 +3027,7 @@ public class AndroidUtilities {
stringBuilder.replace(a, a + 1, " ");
}
}
return original;
}
return original.toString().replace('\n', ' ');
}
@ -4248,6 +4325,9 @@ public class AndroidUtilities {
}
public static boolean checkHostForPunycode(String url) {
if (url == null) {
return false;
}
boolean hasLatin = false;
boolean hasNonLatin = false;
try {
@ -4381,4 +4461,75 @@ public class AndroidUtilities {
return preferences.getInt(key, (int) defaultValue);
}
}
public static Bitmap getScaledBitmap(float w, float h, String path, String streamPath, int streamOffset) {
FileInputStream stream = null;
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
if (path != null) {
BitmapFactory.decodeFile(path, options);
} else {
stream = new FileInputStream(streamPath);
stream.getChannel().position(streamOffset);
BitmapFactory.decodeStream(stream, null, options);
}
if (options.outWidth > 0 && options.outHeight > 0) {
if (w > h && options.outWidth < options.outHeight) {
float temp = w;
w = h;
h = temp;
}
float scale = Math.min(options.outWidth / w, options.outHeight / h);
options.inSampleSize = 1;
if (scale > 1.0f) {
do {
options.inSampleSize *= 2;
} while (options.inSampleSize < scale);
}
options.inJustDecodeBounds = false;
Bitmap wallpaper;
if (path != null) {
wallpaper = BitmapFactory.decodeFile(path, options);
} else {
stream.getChannel().position(streamOffset);
wallpaper = BitmapFactory.decodeStream(stream, null, options);
}
return wallpaper;
}
} catch (Throwable e) {
FileLog.e(e);
} finally {
try {
if (stream != null) {
stream.close();
}
} catch (Exception e2) {
FileLog.e(e2);
}
}
return null;
}
public static Uri getBitmapShareUri(Bitmap bitmap, String fileName, Bitmap.CompressFormat format) {
File cachePath = AndroidUtilities.getCacheDir();
if (!cachePath.isDirectory()) {
try {
cachePath.mkdirs();
} catch (Exception e) {
FileLog.e(e);
return null;
}
}
File file = new File(cachePath, fileName);
try (FileOutputStream out = new FileOutputStream(file)) {
bitmap.compress(format, 100, out);
out.close();
return FileProvider.getUriForFile(ApplicationLoader.applicationContext, BuildConfig.APPLICATION_ID + ".provider", file);
} catch (IOException e) {
FileLog.e(e);
}
return null;
}
}

View File

@ -21,6 +21,7 @@ public class ForwardingMessagesParams {
public boolean isSecret;
public boolean willSeeSenders;
public boolean multiplyUsers;
public boolean hasSpoilers;
public ArrayList<TLRPC.TL_pollAnswerVoters> pollChoosenAnswers = new ArrayList<>();
@ -29,6 +30,7 @@ public class ForwardingMessagesParams {
hasCaption = false;
hasSenders = false;
isSecret = DialogObject.isEncryptedDialog(newDialogId);
hasSpoilers = false;
ArrayList<String> hiddenSendersName = new ArrayList<>();
for (int i = 0; i < messages.size(); i++) {
MessageObject messageObject = messages.get(i);
@ -46,6 +48,17 @@ public class ForwardingMessagesParams {
message.media = messageObject.messageOwner.media;
message.action = messageObject.messageOwner.action;
message.edit_date = 0;
if (messageObject.messageOwner.entities != null) {
message.entities.addAll(messageObject.messageOwner.entities);
if (!hasSpoilers) {
for (TLRPC.MessageEntity e : message.entities) {
if (e instanceof TLRPC.TL_messageEntitySpoiler) {
hasSpoilers = true;
break;
}
}
}
}
message.out = true;
message.unread = false;

View File

@ -0,0 +1,5 @@
package org.telegram.messenger;
public interface GenericProvider<F, T> {
T provide(F obj);
}

View File

@ -30,7 +30,7 @@ import android.text.TextUtils;
import android.util.SparseArray;
import androidx.exifinterface.media.ExifInterface;
import com.google.android.exoplayer2.util.Log;
import androidx.exifinterface.media.ExifInterface;
import org.json.JSONArray;
import org.json.JSONObject;
@ -362,7 +362,7 @@ public class ImageLoader {
}
}
} catch (Exception e) {
FileLog.e(e);
FileLog.e(e, false);
}
httpConnectionStream = httpConnection.getInputStream();
@ -854,7 +854,7 @@ public class ImageLoader {
float h_filter = Float.parseFloat(args[1]);
w = Math.min(512, (int) (w_filter * AndroidUtilities.density));
h = Math.min(512, (int) (h_filter * AndroidUtilities.density));
if (w_filter <= 90 && h_filter <= 90) {
if (w_filter <= 90 && h_filter <= 90 && !cacheImage.filter.contains("nolimit")) {
w = Math.min(w, 160);
h = Math.min(h, 160);
limitFps = true;

View File

@ -42,10 +42,12 @@ import java.io.IOException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Currency;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import tw.nekomimi.nkmr.NekomuraConfig;
@ -504,6 +506,13 @@ public class LocaleController {
}
return languagesDict.get(key.toLowerCase().replace("-", "_"));
}
public LocaleInfo getLanguageByPlural(String plural) {
Collection<LocaleInfo> values = languagesDict.values();
for (LocaleInfo l : values)
if (l.pluralLangCode != null && l.pluralLangCode.equals(plural))
return l;
return null;
}
private void addRules(String[] languages, PluralRules rules) {
for (String language : languages) {

View File

@ -3694,52 +3694,83 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
new Thread(() -> {
try {
File dir;
if (isMusic) {
dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
if (Build.VERSION.SDK_INT >= 29) {
for (int b = 0, N = messageObjects.size(); b < N; b++) {
MessageObject message = messageObjects.get(b);
String path = message.messageOwner.attachPath;
String name = message.getDocumentName();
if (path != null && path.length() > 0) {
File temp = new File(path);
if (!temp.exists()) {
path = null;
}
}
if (path == null || path.length() == 0) {
path = FileLoader.getPathToMessage(message.messageOwner).toString();
}
File sourceFile = new File(path);
if (!sourceFile.exists()) {
waitingForFile = new CountDownLatch(1);
addMessageToLoad(message);
waitingForFile.await();
}
if (cancelled) {
break;
}
if (sourceFile.exists()) {
saveFileInternal(isMusic ? 3 : 2, sourceFile, name);
}
}
} else {
dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
}
dir.mkdir();
for (int b = 0, N = messageObjects.size(); b < N; b++) {
MessageObject message = messageObjects.get(b);
String name = message.getDocumentName();
File destFile = new File(dir, name);
if (destFile.exists()) {
int idx = name.lastIndexOf('.');
for (int a = 0; a < 10; a++) {
String newName;
if (idx != -1) {
newName = name.substring(0, idx) + "(" + (a + 1) + ")" + name.substring(idx);
} else {
newName = name + "(" + (a + 1) + ")";
}
destFile = new File(dir, newName);
if (!destFile.exists()) {
break;
File dir;
if (isMusic) {
dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
} else {
dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
}
dir.mkdir();
for (int b = 0, N = messageObjects.size(); b < N; b++) {
MessageObject message = messageObjects.get(b);
String name = message.getDocumentName();
File destFile = new File(dir, name);
if (destFile.exists()) {
int idx = name.lastIndexOf('.');
for (int a = 0; a < 10; a++) {
String newName;
if (idx != -1) {
newName = name.substring(0, idx) + "(" + (a + 1) + ")" + name.substring(idx);
} else {
newName = name + "(" + (a + 1) + ")";
}
destFile = new File(dir, newName);
if (!destFile.exists()) {
break;
}
}
}
}
if (!destFile.exists()) {
destFile.createNewFile();
}
String path = message.messageOwner.attachPath;
if (path != null && path.length() > 0) {
File temp = new File(path);
if (!temp.exists()) {
path = null;
if (!destFile.exists()) {
destFile.createNewFile();
}
String path = message.messageOwner.attachPath;
if (path != null && path.length() > 0) {
File temp = new File(path);
if (!temp.exists()) {
path = null;
}
}
if (path == null || path.length() == 0) {
path = FileLoader.getPathToMessage(message.messageOwner).toString();
}
File sourceFile = new File(path);
if (!sourceFile.exists()) {
waitingForFile = new CountDownLatch(1);
addMessageToLoad(message);
waitingForFile.await();
}
if (sourceFile.exists()) {
copyFile(sourceFile, destFile, message.getMimeType());
}
}
if (path == null || path.length() == 0) {
path = FileLoader.getPathToMessage(message.messageOwner).toString();
}
File sourceFile = new File(path);
if (!sourceFile.exists()) {
waitingForFile = new CountDownLatch(1);
addMessageToLoad(message);
waitingForFile.await();
}
copyFile(sourceFile, destFile, message.getMimeType());
}
checkIfFinished();
} catch (Exception e) {
@ -3953,62 +3984,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
boolean result = true;
final String folderName = "NekoX";
if (Build.VERSION.SDK_INT >= 29) {
try {
int selectedType = type;
ContentValues contentValues = new ContentValues();
String extension = MimeTypeMap.getFileExtensionFromUrl(sourceFile.getAbsolutePath());
String mimeType = null;
if (extension != null) {
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
Uri uriToInsert = null;
if ((type == 0 || type == 1) && mimeType != null) {
if (mimeType.startsWith("image")) {
selectedType = 0;
}
if (mimeType.startsWith("video")) {
selectedType = 1;
}
}
if (selectedType == 0) {
uriToInsert = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
File dirDest = new File(Environment.DIRECTORY_PICTURES, folderName);
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, dirDest + File.separator);
contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, AndroidUtilities.generateFileName(0, extension));
contentValues.put(MediaStore.Images.Media.MIME_TYPE, mimeType);
} else if (selectedType == 1) {
File dirDest = new File(Environment.DIRECTORY_MOVIES, folderName);
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, dirDest + File.separator);
uriToInsert = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
contentValues.put(MediaStore.Video.Media.DISPLAY_NAME, AndroidUtilities.generateFileName(1, extension));
contentValues.put(MediaStore.Video.Media.MIME_TYPE, mimeType);
} else if (selectedType == 2) {
File dirDest = new File(Environment.DIRECTORY_DOWNLOADS, folderName);
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, dirDest + File.separator);
uriToInsert = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
contentValues.put(MediaStore.Downloads.DISPLAY_NAME, sourceFile.getName());
contentValues.put(MediaStore.Downloads.MIME_TYPE, mimeType);
} else {
File dirDest = new File(Environment.DIRECTORY_MUSIC, folderName);
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, dirDest + File.separator);
uriToInsert = MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
contentValues.put(MediaStore.Audio.Media.DISPLAY_NAME, sourceFile.getName());
contentValues.put(MediaStore.Audio.Media.MIME_TYPE, mimeType);
}
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
Uri dstUri = context.getContentResolver().insert(uriToInsert, contentValues);
if (dstUri != null) {
FileInputStream fileInputStream = new FileInputStream(sourceFile);
OutputStream outputStream = context.getContentResolver().openOutputStream(dstUri);
AndroidUtilities.copyFile(fileInputStream, outputStream);
fileInputStream.close();
}
} catch (Exception e) {
FileLog.e(e);
result = false;
}
result = saveFileInternal(type, sourceFile, null);
} else {
File destFile;
if (type == 0) {
@ -4128,6 +4104,76 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
}
}
private static boolean saveFileInternal(int type, File sourceFile, String filename) {
try {
int selectedType = type;
ContentValues contentValues = new ContentValues();
String extension = MimeTypeMap.getFileExtensionFromUrl(sourceFile.getAbsolutePath());
String mimeType = null;
if (extension != null) {
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
Uri uriToInsert = null;
if ((type == 0 || type == 1) && mimeType != null) {
if (mimeType.startsWith("image")) {
selectedType = 0;
}
if (mimeType.startsWith("video")) {
selectedType = 1;
}
}
final String folderName = "NekoX";
if (selectedType == 0) {
if (filename == null) {
filename = AndroidUtilities.generateFileName(0, extension);
}
uriToInsert = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
File dirDest = new File(Environment.DIRECTORY_PICTURES, folderName);
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, dirDest + File.separator);
contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, filename);
contentValues.put(MediaStore.Images.Media.MIME_TYPE, mimeType);
} else if (selectedType == 1) {
if (filename == null) {
filename = AndroidUtilities.generateFileName(1, extension);
}
File dirDest = new File(Environment.DIRECTORY_MOVIES, folderName);
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, dirDest + File.separator);
uriToInsert = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
contentValues.put(MediaStore.Video.Media.DISPLAY_NAME, filename);
} else if (selectedType == 2) {
if (filename == null) {
filename = sourceFile.getName();
}
File dirDest = new File(Environment.DIRECTORY_DOWNLOADS, folderName);
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, dirDest + File.separator);
uriToInsert = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
contentValues.put(MediaStore.Downloads.DISPLAY_NAME, filename);
} else {
if (filename == null) {
filename = sourceFile.getName();
}
File dirDest = new File(Environment.DIRECTORY_MUSIC, folderName);
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, dirDest + File.separator);
uriToInsert = MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
contentValues.put(MediaStore.Audio.Media.DISPLAY_NAME, filename);
}
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
Uri dstUri = ApplicationLoader.applicationContext.getContentResolver().insert(uriToInsert, contentValues);
if (dstUri != null) {
FileInputStream fileInputStream = new FileInputStream(sourceFile);
OutputStream outputStream = ApplicationLoader.applicationContext.getContentResolver().openOutputStream(dstUri);
AndroidUtilities.copyFile(fileInputStream, outputStream);
fileInputStream.close();
}
return true;
} catch (Exception e) {
FileLog.e(e);
return false;
}
}
public static String getStickerExt(Uri uri) {
InputStream inputStream = null;
try {

View File

@ -10,6 +10,7 @@ package org.telegram.messenger;
import android.app.Activity;
import android.content.Context;
import android.content.Entity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ShortcutManager;
@ -32,7 +33,6 @@ import android.text.Spanned;
import android.text.SpannedString;
import android.text.TextUtils;
import android.text.style.CharacterStyle;
import android.util.Log;
import android.util.SparseArray;
import android.widget.Toast;
@ -40,6 +40,11 @@ import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.IconCompat;
import androidx.collection.LongSparseArray;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.IconCompat;
import org.telegram.SQLite.SQLiteCursor;
import org.telegram.SQLite.SQLiteDatabase;
import org.telegram.SQLite.SQLiteException;
@ -53,7 +58,6 @@ import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.BaseFragment;
import org.telegram.ui.Components.AvatarDrawable;
import org.telegram.ui.Components.Bulletin;
import org.telegram.ui.Components.SharedMediaLayout;
import org.telegram.ui.Components.StickerSetBulletinLayout;
import org.telegram.ui.Components.StickersArchiveAlert;
import org.telegram.ui.Components.TextStyleSpan;
@ -63,7 +67,6 @@ import org.telegram.ui.LaunchActivity;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@ -74,6 +77,8 @@ import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import androidx.collection.LongSparseArray;
import tw.nekomimi.nkmr.NekomuraConfig;
@ -82,6 +87,10 @@ import tw.nekomimi.nekogram.PinnedStickerHelper;
@SuppressWarnings("unchecked")
public class MediaDataController extends BaseController {
private static Pattern BOLD_PATTERN = Pattern.compile("\\*\\*(.+?)\\*\\*"),
ITALIC_PATTERN = Pattern.compile("__(.+?)__"),
SPOILER_PATTERN = Pattern.compile("\\|\\|(.+?)\\|\\|"),
STRIKE_PATTERN = Pattern.compile("~~(.+?)~~");
public static String SHORTCUT_CATEGORY = "org.telegram.messenger.SHORTCUT_SHARE";
@ -158,6 +167,14 @@ public class MediaDataController extends BaseController {
public static final int TYPE_GREETINGS = 3;
private int reactionsUpdateHash;
private List<TLRPC.TL_availableReaction> reactionsList = new ArrayList<>();
private List<TLRPC.TL_availableReaction> enabledReactionsList = new ArrayList<>();
private HashMap<String, TLRPC.TL_availableReaction> reactionsMap = new HashMap<>();
private String doubleTapReaction;
private boolean isLoadingReactions;
private int reactionsUpdateDate;
private ArrayList<TLRPC.TL_messages_stickerSet>[] stickerSets = new ArrayList[]{new ArrayList<>(), new ArrayList<>(), new ArrayList<>(0), new ArrayList<>(), new ArrayList<>()};
private LongSparseArray<TLRPC.Document>[] stickersByIds = new LongSparseArray[]{new LongSparseArray<>(), new LongSparseArray<>(), new LongSparseArray<>(), new LongSparseArray<>(), new LongSparseArray<>()};
private LongSparseArray<TLRPC.TL_messages_stickerSet> stickerSetsById = new LongSparseArray<>();
@ -269,6 +286,136 @@ public class MediaDataController extends BaseController {
}
}
public void checkReactions() {
if (!isLoadingReactions && Math.abs(System.currentTimeMillis() / 1000 - reactionsUpdateDate) >= 60 * 60) {
loadReactions(true, false);
}
}
public List<TLRPC.TL_availableReaction> getReactionsList() {
return reactionsList;
}
public void loadReactions(boolean cache, boolean force) {
isLoadingReactions = true;
if (cache) {
getMessagesStorage().getStorageQueue().postRunnable(() -> {
SQLiteCursor c = null;
int hash = 0;
int date = 0;
List<TLRPC.TL_availableReaction> reactions = null;
try {
c = getMessagesStorage().getDatabase().queryFinalized("SELECT data, hash, date FROM reactions");
if (c.next()) {
NativeByteBuffer data = c.byteBufferValue(0);
if (data != null) {
int count = data.readInt32(false);
reactions = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
TLRPC.TL_availableReaction react = TLRPC.TL_availableReaction.TLdeserialize(data, data.readInt32(false), true);
reactions.add(react);
}
data.reuse();
}
hash = c.intValue(1);
date = c.intValue(2);
}
} catch (Exception e) {
FileLog.e(e);
} finally {
if (c != null) {
c.dispose();
}
}
processLoadedReactions(reactions, hash, date, true);
});
} else {
TLRPC.TL_messages_getAvailableReactions req = new TLRPC.TL_messages_getAvailableReactions();
req.hash = force ? 0 : reactionsUpdateHash;
getConnectionsManager().sendRequest(req, (response, error) -> {
int date = (int) (System.currentTimeMillis() / 1000);
if (response instanceof TLRPC.TL_messages_availableReactionsNotModified) {
processLoadedReactions(null, 0, date, false);
} else if (response instanceof TLRPC.TL_messages_availableReactions) {
TLRPC.TL_messages_availableReactions r = (TLRPC.TL_messages_availableReactions) response;
processLoadedReactions(r.reactions, r.hash, date, false);
}
});
}
}
private void processLoadedReactions(List<TLRPC.TL_availableReaction> reactions, int hash, int date, boolean cache) {
if (reactions != null && date != 0) {
reactionsList.clear();
reactionsMap.clear();
enabledReactionsList.clear();
reactionsList.addAll(reactions);
for (int i = 0; i < reactionsList.size(); i++) {
reactionsList.get(i).positionInList = i;
reactionsMap.put(reactionsList.get(i).reaction, reactionsList.get(i));
if (!reactionsList.get(i).inactive) {
enabledReactionsList.add(reactionsList.get(i));
}
}
reactionsUpdateHash = hash;
}
reactionsUpdateDate = date;
if (reactions != null) {
AndroidUtilities.runOnUIThread(() -> {
for (int i = 0; i < reactions.size(); i++) {
ImageReceiver imageReceiver = new ImageReceiver();
TLRPC.TL_availableReaction reaction = reactions.get(i);
imageReceiver.setImage(ImageLocation.getForDocument(reaction.activate_animation), null, null, null, 0, 1);
imageReceiver.setImage(ImageLocation.getForDocument(reaction.appear_animation), null, null, null, 0, 1);
imageReceiver = new ImageReceiver();
imageReceiver.setImage(ImageLocation.getForDocument(reaction.static_icon), null, null, null, 0, 1);
}
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.reactionsDidLoad);
});
}
if (!cache) {
putReactionsToCache(reactions, hash, date);
} else if (Math.abs(System.currentTimeMillis() / 1000 - date) >= 60 * 60) {
loadReactions(false, true);
}
}
private void putReactionsToCache(List<TLRPC.TL_availableReaction> reactions, int hash, int date) {
ArrayList<TLRPC.TL_availableReaction> reactionsFinal = reactions != null ? new ArrayList<>(reactions) : null;
getMessagesStorage().getStorageQueue().postRunnable(() -> {
try {
if (reactionsFinal != null) {
SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO reactions VALUES(?, ?, ?)");
state.requery();
int size = 4; // Integer.BYTES
for (int a = 0; a < reactionsFinal.size(); a++) {
size += reactionsFinal.get(a).getObjectSize();
}
NativeByteBuffer data = new NativeByteBuffer(size);
data.writeInt32(reactionsFinal.size());
for (int a = 0; a < reactionsFinal.size(); a++) {
reactionsFinal.get(a).serializeToStream(data);
}
state.bindByteBuffer(1, data);
state.bindInteger(2, hash);
state.bindInteger(3, date);
state.step();
data.reuse();
state.dispose();
} else {
SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("UPDATE reactions SET date = ?");
state.requery();
state.bindLong(1, date);
state.step();
state.dispose();
}
} catch (Exception e) {
FileLog.e(e);
}
});
}
public void checkFeaturedStickers() {
if (!loadingFeaturedStickers && (!featuredStickersLoaded || Math.abs(System.currentTimeMillis() / 1000 - loadFeaturedDate) >= 60 * 60)) {
loadFeaturedStickers(true, false);
@ -4375,7 +4522,7 @@ public class MediaDataController extends BaseController {
Collections.sort(entities, entityComparator);
}
private static boolean checkInclusion(int index, ArrayList<TLRPC.MessageEntity> entities, boolean end) {
private static boolean checkInclusion(int index, List<TLRPC.MessageEntity> entities, boolean end) {
if (entities == null || entities.isEmpty()) {
return false;
}
@ -4389,7 +4536,7 @@ public class MediaDataController extends BaseController {
return false;
}
private static boolean checkIntersection(int start, int end, ArrayList<TLRPC.MessageEntity> entities) {
private static boolean checkIntersection(int start, int end, List<TLRPC.MessageEntity> entities) {
if (entities == null || entities.isEmpty()) {
return false;
}
@ -4403,16 +4550,6 @@ public class MediaDataController extends BaseController {
return false;
}
private static void removeOffsetAfter(int start, int countToRemove, ArrayList<TLRPC.MessageEntity> entities) {
int count = entities.size();
for (int a = 0; a < count; a++) {
TLRPC.MessageEntity entity = entities.get(a);
if (entity.offset > start) {
entity.offset -= countToRemove;
}
}
}
public CharSequence substring(CharSequence source, int start, int end) {
if (source instanceof SpannableStringBuilder) {
return source.subSequence(start, end);
@ -4500,14 +4637,30 @@ public class MediaDataController extends BaseController {
}
}
}
if (span != null && start < end) {
editable.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
if (span != null && start < end && start < editable.length()) {
editable.setSpan(span, start, Math.min(editable.length(), end), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
} catch (Exception e) {
FileLog.e(e);
}
}
public static void addTextStyleRuns(TLRPC.DraftMessage msg, Spannable text) {
addTextStyleRuns(msg.entities, msg.message, text);
}
public static void addTextStyleRuns(MessageObject msg, Spannable text) {
addTextStyleRuns(msg.messageOwner.entities, msg.messageText, text);
}
public static void addTextStyleRuns(ArrayList<TLRPC.MessageEntity> entities, CharSequence messageText, Spannable text) {
for (TextStyleSpan prevSpan : text.getSpans(0, text.length(), TextStyleSpan.class))
text.removeSpan(prevSpan);
for (TextStyleSpan.TextStyleRun run : MediaDataController.getTextStyleRuns(entities, messageText)) {
MediaDataController.addStyleToText(new TextStyleSpan(run), run.start, run.end, text, true);
}
}
public static ArrayList<TextStyleSpan.TextStyleRun> getTextStyleRuns(ArrayList<TLRPC.MessageEntity> entities, CharSequence text) {
ArrayList<TextStyleSpan.TextStyleRun> runs = new ArrayList<>();
ArrayList<TLRPC.MessageEntity> entitiesCopy = new ArrayList<>(entities);
@ -4532,7 +4685,9 @@ public class MediaDataController extends BaseController {
newRun.start = entity.offset;
newRun.end = newRun.start + entity.length;
TLRPC.MessageEntity urlEntity = null;
if (entity instanceof TLRPC.TL_messageEntityStrike) {
if (entity instanceof TLRPC.TL_messageEntitySpoiler) {
newRun.flags = TextStyleSpan.FLAG_STYLE_SPOILER;
} else if (entity instanceof TLRPC.TL_messageEntityStrike) {
newRun.flags = TextStyleSpan.FLAG_STYLE_STRIKE;
} else if (entity instanceof TLRPC.TL_messageEntityUnderline) {
newRun.flags = TextStyleSpan.FLAG_STYLE_UNDERLINE;
@ -4623,42 +4778,26 @@ public class MediaDataController extends BaseController {
}
public void addStyle(int flags, int spanStart, int spanEnd, ArrayList<TLRPC.MessageEntity> entities) {
if ((flags & TextStyleSpan.FLAG_STYLE_BOLD) != 0) {
TLRPC.MessageEntity entity = new TLRPC.TL_messageEntityBold();
entity.offset = spanStart;
entity.length = spanEnd - spanStart;
entities.add(entity);
}
if ((flags & TextStyleSpan.FLAG_STYLE_ITALIC) != 0) {
TLRPC.MessageEntity entity = new TLRPC.TL_messageEntityItalic();
entity.offset = spanStart;
entity.length = spanEnd - spanStart;
entities.add(entity);
}
if ((flags & TextStyleSpan.FLAG_STYLE_MONO) != 0) {
TLRPC.MessageEntity entity = new TLRPC.TL_messageEntityCode();
entity.offset = spanStart;
entity.length = spanEnd - spanStart;
entities.add(entity);
}
if ((flags & TextStyleSpan.FLAG_STYLE_STRIKE) != 0) {
TLRPC.MessageEntity entity = new TLRPC.TL_messageEntityStrike();
entity.offset = spanStart;
entity.length = spanEnd - spanStart;
entities.add(entity);
}
if ((flags & TextStyleSpan.FLAG_STYLE_UNDERLINE) != 0) {
TLRPC.MessageEntity entity = new TLRPC.TL_messageEntityUnderline();
entity.offset = spanStart;
entity.length = spanEnd - spanStart;
entities.add(entity);
}
if ((flags & TextStyleSpan.FLAG_STYLE_QUOTE) != 0) {
TLRPC.MessageEntity entity = new TLRPC.TL_messageEntityBlockquote();
entity.offset = spanStart;
entity.length = spanEnd - spanStart;
entities.add(entity);
}
if ((flags & TextStyleSpan.FLAG_STYLE_SPOILER) != 0)
entities.add(setEntityStartEnd(new TLRPC.TL_messageEntitySpoiler(), spanStart, spanEnd));
if ((flags & TextStyleSpan.FLAG_STYLE_BOLD) != 0)
entities.add(setEntityStartEnd(new TLRPC.TL_messageEntityBold(), spanStart, spanEnd));
if ((flags & TextStyleSpan.FLAG_STYLE_ITALIC) != 0)
entities.add(setEntityStartEnd(new TLRPC.TL_messageEntityItalic(), spanStart, spanEnd));
if ((flags & TextStyleSpan.FLAG_STYLE_MONO) != 0)
entities.add(setEntityStartEnd(new TLRPC.TL_messageEntityCode(), spanStart, spanEnd));
if ((flags & TextStyleSpan.FLAG_STYLE_STRIKE) != 0)
entities.add(setEntityStartEnd(new TLRPC.TL_messageEntityStrike(), spanStart, spanEnd));
if ((flags & TextStyleSpan.FLAG_STYLE_UNDERLINE) != 0)
entities.add(setEntityStartEnd(new TLRPC.TL_messageEntityUnderline(), spanStart, spanEnd));
if ((flags & TextStyleSpan.FLAG_STYLE_QUOTE) != 0)
entities.add(setEntityStartEnd(new TLRPC.TL_messageEntityBlockquote(), spanStart, spanEnd));
}
private TLRPC.MessageEntity setEntityStartEnd(TLRPC.MessageEntity entity, int spanStart, int spanEnd) {
entity.offset = spanStart;
entity.length = spanEnd - spanStart;
return entity;
}
public ArrayList<TLRPC.MessageEntity> getEntities(CharSequence[] message, boolean allowStrike) {
@ -4672,9 +4811,6 @@ public class MediaDataController extends BaseController {
boolean isPre = false;
final String mono = "`";
final String pre = "```";
final String bold = "**";
final String italic = "__";
final String strike = "~~";
while ((index = TextUtils.indexOf(message[0], !isPre ? mono : pre, lastIndex)) != -1) {
if (start == -1) {
isPre = message[0].length() - index > 2 && message[0].charAt(index + 1) == '`' && message[0].charAt(index + 2) == '`';
@ -4797,79 +4933,40 @@ public class MediaDataController extends BaseController {
}
}
int count = allowStrike ? 3 : 2;
for (int c = 0; c < count; c++) {
lastIndex = 0;
start = -1;
String checkString;
char checkChar;
switch (c) {
case 0:
checkString = bold;
checkChar = '*';
break;
case 1:
checkString = italic;
checkChar = '_';
break;
case 2:
default:
checkString = strike;
checkChar = '~';
break;
}
while ((index = TextUtils.indexOf(message[0], checkString, lastIndex)) != -1) {
if (start == -1) {
char prevChar = index == 0 ? ' ' : message[0].charAt(index - 1);
if (!checkInclusion(index, entities, false) && (prevChar == ' ' || prevChar == '\n')) {
start = index;
}
lastIndex = index + 2;
} else {
for (int a = index + 2; a < message[0].length(); a++) {
if (message[0].charAt(a) == checkChar) {
index++;
} else {
break;
}
}
lastIndex = index + 2;
if (checkInclusion(index, entities, false) || checkIntersection(start, index, entities)) {
start = -1;
continue;
}
if (start + 2 != index) {
if (entities == null) {
entities = new ArrayList<>();
}
try {
message[0] = AndroidUtilities.concat(substring(message[0], 0, start), substring(message[0], start + 2, index), substring(message[0], index + 2, message[0].length()));
} catch (Exception e) {
message[0] = substring(message[0], 0, start).toString() + substring(message[0], start + 2, index).toString() + substring(message[0], index + 2, message[0].length()).toString();
}
TLRPC.MessageEntity entity;
if (c == 0) {
entity = new TLRPC.TL_messageEntityBold();
} else if (c == 1) {
entity = new TLRPC.TL_messageEntityItalic();
} else {
entity = new TLRPC.TL_messageEntityStrike();
}
entity.offset = start;
entity.length = index - start - 2;
removeOffsetAfter(entity.offset + entity.length, 4, entities);
entities.add(entity);
lastIndex -= 4;
}
start = -1;
}
}
CharSequence cs = message[0];
if (entities == null) entities = new ArrayList<>();
cs = parsePattern(cs, BOLD_PATTERN, entities, obj -> new TLRPC.TL_messageEntityBold());
cs = parsePattern(cs, ITALIC_PATTERN, entities, obj -> new TLRPC.TL_messageEntityItalic());
cs = parsePattern(cs, SPOILER_PATTERN, entities, obj -> new TLRPC.TL_messageEntitySpoiler());
if (allowStrike) {
cs = parsePattern(cs, STRIKE_PATTERN, entities, obj -> new TLRPC.TL_messageEntityStrike());
}
message[0] = cs;
return entities;
}
private CharSequence parsePattern(CharSequence cs, Pattern pattern, List<TLRPC.MessageEntity> entities, GenericProvider<Void, TLRPC.MessageEntity> entityProvider) {
Matcher m = pattern.matcher(cs);
int offset = 0;
while (m.find()) {
if (checkInclusion(m.start(), entities, false) || checkIntersection(m.start(), m.end(), entities)) {
}
String gr = m.group(1);
cs = cs.subSequence(0, m.start() - offset) + gr + cs.subSequence(m.end() - offset, cs.length());
TLRPC.MessageEntity entity = entityProvider.provide(null);
entity.offset = m.start() + offset;
entity.length = gr.length();
entities.add(entity);
offset += m.end() - m.start() - gr.length();
}
return cs;
}
//---------------- MESSAGES END ----------------
private LongSparseArray<Integer> draftsFolderIds = new LongSparseArray<>();
@ -5392,6 +5489,33 @@ public class MediaDataController extends BaseController {
});
}
public HashMap<String, TLRPC.TL_availableReaction> getReactionsMap() {
return reactionsMap;
}
public String getDoubleTapReaction() {
if (doubleTapReaction != null) {
return doubleTapReaction;
}
if (!getReactionsList().isEmpty()) {
String savedReaction = MessagesController.getEmojiSettings(currentAccount).getString("reaction_on_double_tap", null);
if (savedReaction != null && getReactionsMap().get(savedReaction) != null) {
doubleTapReaction = savedReaction;
return doubleTapReaction;
}
return getReactionsList().get(0).reaction;
}
return null;
}
public void setDoubleTapReaction(String reaction) {
MessagesController.getEmojiSettings(currentAccount).edit().putString("reaction_on_double_tap", reaction).apply();
doubleTapReaction = reaction;
}
public List<TLRPC.TL_availableReaction> getEnabledReactionsList() {
return enabledReactionsList;
}
//---------------- BOT END ----------------
//---------------- EMOJI START ----------------

View File

@ -10,6 +10,7 @@ package org.telegram.messenger;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.text.Layout;
@ -25,6 +26,8 @@ import android.text.style.URLSpan;
import android.text.util.Linkify;
import android.util.Base64;
import androidx.collection.LongSparseArray;
import org.telegram.PhoneFormat.PhoneFormat;
import org.telegram.messenger.browser.Browser;
import org.telegram.tgnet.ConnectionsManager;
@ -33,6 +36,7 @@ import org.telegram.tgnet.TLObject;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Cells.ChatMessageCell;
import org.telegram.ui.Components.spoilers.SpoilerEffect;
import org.telegram.ui.Components.TextStyleSpan;
import org.telegram.ui.Components.TypefaceSpan;
import org.telegram.ui.Components.URLSpanBotCommand;
@ -54,6 +58,9 @@ import java.util.Calendar;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -62,6 +69,7 @@ import tw.nekomimi.nkmr.NekomuraConfig;
import tw.nekomimi.nekogram.NekoXConfig;
import androidx.collection.LongSparseArray;
import androidx.core.math.MathUtils;
public class MessageObject {
@ -94,6 +102,9 @@ public class MessageObject {
public CharSequence caption;
public MessageObject replyMessageObject;
public int type = 1000;
public boolean reactionsVisibleOnScreen;
public long reactionsLastCheckTime;
public String customName;
private int isRoundVideoCached;
public long eventId;
public int contentType;
@ -127,8 +138,12 @@ public class MessageObject {
public boolean isRestrictedMessage;
public long loadedFileSize;
public boolean isSpoilersRevealed;
public byte[] sponsoredId;
public int sponsoredChannelPost;
public TLRPC.ChatInvite sponsoredChatInvite;
public String sponsoredChatInviteHash;
public String botStartParam;
public boolean animateComments;
@ -210,11 +225,16 @@ public class MessageObject {
" & ",
" . "
};
public Drawable customAvatarDrawable;
public int getEmojiOnlyCount() {
return emojiOnlyCount;
}
public boolean shouldDrawReactionsInLayout() {
return getDialogId() < 0;
}
public static class SendAnimationData {
public float x;
public float y;
@ -351,6 +371,9 @@ public class MessageObject {
}
public static class TextLayoutBlock {
public final static int FLAG_RTL = 1, FLAG_NOT_RTL = 2;
public AtomicReference<Layout> spoilersPatchedTextLayout = new AtomicReference<>();
public StaticLayout textLayout;
public float textYOffset;
public int charactersOffset;
@ -358,9 +381,10 @@ public class MessageObject {
public int height;
public int heightByOffset;
public byte directionFlags;
public List<SpoilerEffect> spoilers = new ArrayList<>();
public boolean isRtl() {
return (directionFlags & 1) != 0 && (directionFlags & 2) == 0;
return (directionFlags & FLAG_RTL) != 0 && (directionFlags & FLAG_NOT_RTL) == 0;
}
}
@ -822,13 +846,17 @@ public class MessageObject {
}
public MessageObject findPrimaryMessageObject() {
return findMessageWithFlags(MessageObject.POSITION_FLAG_TOP | MessageObject.POSITION_FLAG_LEFT);
}
public MessageObject findMessageWithFlags(int flags) {
if (!messages.isEmpty() && positions.isEmpty()) {
calculate();
}
for (int i = 0; i < messages.size(); i++) {
MessageObject object = messages.get(i);
MessageObject.GroupedMessagePosition position = positions.get(object);
if (position != null && (position.flags & (MessageObject.POSITION_FLAG_TOP | MessageObject.POSITION_FLAG_LEFT)) != 0) {
if (position != null && (position.flags & (flags)) == flags) {
return object;
}
}
@ -1652,12 +1680,16 @@ public class MessageObject {
message = new TLRPC.TL_message();
message.out = false;
message.unread = false;
message.from_id = new TLRPC.TL_peerUser();
message.from_id.user_id = event.user_id;
message.peer_id = peer_id;
message.date = event.date;
TLRPC.Message newMessage = ((TLRPC.TL_channelAdminLogEventActionEditMessage) event.action).new_message;
TLRPC.Message oldMessage = ((TLRPC.TL_channelAdminLogEventActionEditMessage) event.action).prev_message;
if (newMessage != null && newMessage.from_id != null) {
message.from_id = newMessage.from_id;
} else {
message.from_id = new TLRPC.TL_peerUser();
message.from_id.user_id = event.user_id;
}
if (newMessage.media != null && !(newMessage.media instanceof TLRPC.TL_messageMediaEmpty) && !(newMessage.media instanceof TLRPC.TL_messageMediaWebPage)/* && TextUtils.isEmpty(newMessage.message)*/) {
boolean changedCaption;
boolean changedMedia;
@ -1864,6 +1896,10 @@ public class MessageObject {
} else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionSendMessage) {
message = ((TLRPC.TL_channelAdminLogEventActionSendMessage) event.action).message;
messageText = replaceWithLink(LocaleController.getString("EventLogSendMessages", R.string.EventLogSendMessages), "un1", fromUser);
} else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionChangeAvailableReactions) {
String oldReactions = TextUtils.join(", ", ((TLRPC.TL_channelAdminLogEventActionChangeAvailableReactions) event.action).prev_value);
String newReactions = TextUtils.join(", ", ((TLRPC.TL_channelAdminLogEventActionChangeAvailableReactions) event.action).new_value);
messageText = replaceWithLink(LocaleController.formatString("ActionReactionsChanged", R.string.ActionReactionsChanged, oldReactions, newReactions), "un1", fromUser);
} else {
messageText = "unsupported " + event.action;
}
@ -2125,7 +2161,8 @@ public class MessageObject {
mess = mess.subSequence(0, 20) + "...";
}
mess = Emoji.replaceEmoji(mess, Theme.chat_msgTextPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false);
messageText = replaceWithLink(LocaleController.formatString("ActionPinnedText", R.string.ActionPinnedText, mess), "un1", fromUser != null ? fromUser : chat);
MediaDataController.addTextStyleRuns(replyMessageObject, (Spannable) mess);
messageText = replaceWithLink(AndroidUtilities.formatSpannable(LocaleController.getString("ActionPinnedText", R.string.ActionPinnedText), mess), "un1", fromUser != null ? fromUser : chat);
} else {
messageText = replaceWithLink(LocaleController.getString("ActionPinnedNoText", R.string.ActionPinnedNoText), "un1", fromUser != null ? fromUser : chat);
}
@ -4061,7 +4098,8 @@ public class MessageObject {
entity instanceof TLRPC.TL_messageEntityPre ||
entity instanceof TLRPC.TL_messageEntityMentionName ||
entity instanceof TLRPC.TL_inputMessageEntityMentionName ||
entity instanceof TLRPC.TL_messageEntityTextUrl) {
entity instanceof TLRPC.TL_messageEntityTextUrl ||
entity instanceof TLRPC.TL_messageEntitySpoiler) {
if (spans != null && spans.length > 0) {
for (int b = 0; b < spans.length; b++) {
if (spans[b] == null) {
@ -4081,7 +4119,9 @@ public class MessageObject {
newRun.start = entity.offset;
newRun.end = newRun.start + entity.length;
TLRPC.MessageEntity urlEntity = null;
if (entity instanceof TLRPC.TL_messageEntityStrike) {
if (entity instanceof TLRPC.TL_messageEntitySpoiler) {
newRun.flags = TextStyleSpan.FLAG_STYLE_SPOILER;
} else if (entity instanceof TLRPC.TL_messageEntityStrike) {
newRun.flags = TextStyleSpan.FLAG_STYLE_STRIKE;
} else if (entity instanceof TLRPC.TL_messageEntityUnderline) {
newRun.flags = TextStyleSpan.FLAG_STYLE_UNDERLINE;
@ -4188,6 +4228,7 @@ public class MessageObject {
for (int a = 0; a < count; a++) {
TextStyleSpan.TextStyleRun run = runs.get(a);
boolean setRun = false;
String url = run.urlEntity != null ? TextUtils.substring(text, run.urlEntity.offset, run.urlEntity.offset + run.urlEntity.length) : null;
if (run.urlEntity instanceof TLRPC.TL_messageEntityBotCommand) {
spannable.setSpan(new URLSpanBotCommand(url, t, run), run.start, run.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
@ -4222,6 +4263,10 @@ public class MessageObject {
} else if ((run.flags & TextStyleSpan.FLAG_STYLE_MONO) != 0) {
spannable.setSpan(new URLSpanMono(spannable, run.start, run.end, t, run), run.start, run.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} else {
setRun = true;
spannable.setSpan(new TextStyleSpan(run), run.start, run.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (!setRun && (run.flags & TextStyleSpan.FLAG_STYLE_SPOILER) != 0) {
spannable.setSpan(new TextStyleSpan(run), run.start, run.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
@ -4417,6 +4462,7 @@ public class MessageObject {
block.textYOffset = 0;
block.charactersOffset = 0;
block.charactersEnd = textLayout.getText().length();
if (emojiOnlyCount != 0) {
switch (emojiOnlyCount) {
case 1:
@ -4444,15 +4490,17 @@ public class MessageObject {
block.charactersOffset = startCharacter;
block.charactersEnd = endCharacter;
try {
SpannableStringBuilder sb = SpannableStringBuilder.valueOf(messageText.subSequence(startCharacter, endCharacter));
if (hasUrls && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
block.textLayout = StaticLayout.Builder.obtain(messageText, startCharacter, endCharacter, paint, maxWidth + AndroidUtilities.dp(2))
block.textLayout = StaticLayout.Builder.obtain(sb, 0, sb.length(), paint, maxWidth + AndroidUtilities.dp(2))
.setBreakStrategy(StaticLayout.BREAK_STRATEGY_HIGH_QUALITY)
.setHyphenationFrequency(StaticLayout.HYPHENATION_FREQUENCY_NONE)
.setAlignment(Layout.Alignment.ALIGN_NORMAL)
.build();
} else {
block.textLayout = new StaticLayout(messageText, startCharacter, endCharacter, paint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
block.textLayout = new StaticLayout(sb, 0, sb.length(), paint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
}
block.textYOffset = textLayout.getLineTop(linesOffset);
if (a != 0) {
block.height = (int) (block.textYOffset - prevOffset);
@ -4472,6 +4520,10 @@ public class MessageObject {
}
}
}
block.spoilers.clear();
if (!isSpoilersRevealed) {
SpoilerEffect.addSpoilers(null, block.textLayout, null, block.spoilers);
}
textLayoutBlocks.add(block);
@ -4535,10 +4587,10 @@ public class MessageObject {
if (lineLeft > 0) {
textXOffset = Math.min(textXOffset, lineLeft);
block.directionFlags |= 1;
block.directionFlags |= TextLayoutBlock.FLAG_RTL;
hasRtl = true;
} else {
block.directionFlags |= 2;
block.directionFlags |= TextLayoutBlock.FLAG_NOT_RTL;
}
try {
@ -4570,9 +4622,9 @@ public class MessageObject {
linesMaxWidth += lastLeft;
}
hasRtl = blocksCount != 1;
block.directionFlags |= 1;
block.directionFlags |= TextLayoutBlock.FLAG_RTL;
} else {
block.directionFlags |= 2;
block.directionFlags |= TextLayoutBlock.FLAG_NOT_RTL;
}
textWidth = Math.max(textWidth, Math.min(maxWidth, linesMaxWidth));
@ -4606,10 +4658,16 @@ public class MessageObject {
}
public boolean needDrawAvatar() {
if (customAvatarDrawable != null) {
return true;
}
return !isSponsored() && (isFromUser() || isFromGroup() || eventId != 0 || messageOwner.fwd_from != null && messageOwner.fwd_from.saved_from_peer != null);
}
private boolean needDrawAvatarInternal() {
if (customAvatarDrawable != null) {
return true;
}
return !isSponsored() && (isFromChat() && isFromUser() || isFromGroup() || eventId != 0 || messageOwner.fwd_from != null && messageOwner.fwd_from.saved_from_peer != null);
}
@ -5881,7 +5939,7 @@ public class MessageObject {
if (message.out && message instanceof TLRPC.TL_messageService) {
return message.id != 1 && ChatObject.canUserDoAdminAction(chat, ChatObject.ACTION_DELETE_MESSAGES);
}
return inScheduleMode || message.id != 1 && (chat.creator || chat.admin_rights != null && (chat.admin_rights.delete_messages || message.out && (chat.megagroup || chat.admin_rights.post_messages)) || chat.megagroup && message.out && (message.from_id instanceof TLRPC.TL_peerUser || message.from_id instanceof TLRPC.TL_peerChannel));
return inScheduleMode || message.id != 1 && (chat.creator || chat.admin_rights != null && (chat.admin_rights.delete_messages || message.out && (chat.megagroup || chat.admin_rights.post_messages)) || chat.megagroup && message.out);
}
return inScheduleMode || isOut(message) || !ChatObject.isChannel(chat);
}
@ -6233,4 +6291,75 @@ public class MessageObject {
public boolean equals(MessageObject obj) {
return getId() == obj.getId() && getDialogId() == obj.getDialogId();
}
public boolean isReactionsAvailable() {
return !isEditing() && !isSponsored() && isSent() && messageOwner.action == null;
}
public boolean selectReaction(String reaction, boolean fromDoubleTap) {
if (messageOwner.reactions == null) {
messageOwner.reactions = new TLRPC.TL_messageReactions();
messageOwner.reactions.can_see_list = isFromGroup() || isFromUser();
}
TLRPC.TL_reactionCount choosenReaction = null;
TLRPC.TL_reactionCount newReaction = null;
for (int i = 0; i < messageOwner.reactions.results.size(); i++) {
if (messageOwner.reactions.results.get(i).chosen) {
choosenReaction = messageOwner.reactions.results.get(i);
}
if (messageOwner.reactions.results.get(i).reaction.equals(reaction)) {
newReaction = messageOwner.reactions.results.get(i);
}
}
if (choosenReaction != null && (choosenReaction == newReaction || fromDoubleTap)) {
choosenReaction.chosen = false;
choosenReaction.count--;
if (choosenReaction.count <= 0) {
messageOwner.reactions.results.remove(choosenReaction);
}
if (messageOwner.reactions.can_see_list) {
for (int i = 0; i < messageOwner.reactions.recent_reactons.size(); i++) {
if (messageOwner.reactions.recent_reactons.get(i).user_id == UserConfig.getInstance(currentAccount).getClientUserId()) {
messageOwner.reactions.recent_reactons.remove(i);
i--;
}
}
}
return false;
}
if (choosenReaction != null) {
choosenReaction.chosen = false;
choosenReaction.count--;
if (choosenReaction.count <= 0) {
messageOwner.reactions.results.remove(choosenReaction);
}
if (messageOwner.reactions.can_see_list) {
for (int i = 0; i < messageOwner.reactions.recent_reactons.size(); i++) {
if (messageOwner.reactions.recent_reactons.get(i).user_id == UserConfig.getInstance(currentAccount).getClientUserId()) {
messageOwner.reactions.recent_reactons.remove(i);
i--;
}
}
}
}
if (newReaction == null) {
newReaction = new TLRPC.TL_reactionCount();
newReaction.reaction = reaction;
messageOwner.reactions.results.add(newReaction);
}
newReaction.chosen = true;
newReaction.count++;
if (messageOwner.reactions.can_see_list) {
TLRPC.TL_messageUserReaction action = new TLRPC.TL_messageUserReaction();
messageOwner.reactions.recent_reactons.add(0, action);
action.user_id = UserConfig.getInstance(currentAccount).getClientUserId();
action.reaction = reaction;
}
return true;
}
}

View File

@ -42,27 +42,26 @@ import org.telegram.tgnet.TLObject;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.AlertDialog;
import org.telegram.ui.ActionBar.BaseFragment;
import org.telegram.ui.ActionBar.EmojiThemes;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.ChatActivity;
import org.telegram.ui.Components.AlertsCreator;
import org.telegram.ui.Components.BulletinFactory;
import org.telegram.ui.Components.JoinCallAlert;
import org.telegram.ui.Components.MotionBackgroundDrawable;
import org.telegram.ui.Components.SwipeGestureSettingsView;
import org.telegram.ui.DialogsActivity;
import org.telegram.ui.EditWidgetActivity;
import org.telegram.ui.LaunchActivity;
import org.telegram.ui.ProfileActivity;
import org.telegram.ui.Components.SwipeGestureSettingsView;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@ -149,6 +148,10 @@ public class MessagesController extends BaseController implements NotificationCe
private int pollsToCheckSize;
private long lastViewsCheckTime;
private LongSparseArray<SparseArray<MessageObject>> reactionsToCheck = new LongSparseArray<>();
private long lastReactionsCheckTime;
private LongSparseArray<List<Integer>> reactionsTempDialogs = new LongSparseArray<>();
public ArrayList<DialogFilter> dialogFilters = new ArrayList<>();
public SparseArray<DialogFilter> dialogFiltersById = new SparseArray<>();
private boolean loadingSuggestedFilters;
@ -304,6 +307,8 @@ public class MessagesController extends BaseController implements NotificationCe
public int mapProvider;
public int availableMapProviders;
public int updateCheckDelay;
public int chatReadMarkSizeThreshold;
public int chatReadMarkExpirePeriod;
public String mapKey;
public int maxMessageLength;
public int maxCaptionLength;
@ -333,7 +338,6 @@ public class MessagesController extends BaseController implements NotificationCe
public Set<String> exportPrivateUri;
public boolean autoarchiveAvailable;
public int groipCallVideoMaxParticipants;
public int chatReadMarkSizeThreshold;
public boolean suggestStickersApiOnly;
public ArrayList<String> gifSearchEmojies = new ArrayList<>();
public HashSet<String> diceEmojies;
@ -882,6 +886,7 @@ public class MessagesController extends BaseController implements NotificationCe
autoarchiveAvailable = mainPreferences.getBoolean("autoarchiveAvailable", false);
groipCallVideoMaxParticipants = mainPreferences.getInt("groipCallVideoMaxParticipants", 30);
chatReadMarkSizeThreshold = mainPreferences.getInt("chatReadMarkSizeThreshold", 100);
chatReadMarkExpirePeriod = mainPreferences.getInt("chatReadMarkExpirePeriod", 7 * 86400);
suggestStickersApiOnly = mainPreferences.getBoolean("suggestStickersApiOnly", false);
roundVideoSize = mainPreferences.getInt("roundVideoSize", 384);
roundVideoBitrate = mainPreferences.getInt("roundVideoBitrate", 1000);
@ -1228,7 +1233,6 @@ public class MessagesController extends BaseController implements NotificationCe
getMessagesStorage().putDialogs(pinnedRemoteDialogs, 0);
}
AndroidUtilities.runOnUIThread(() -> {
if (remote != 2) {
dialogFilters = filters;
@ -1752,7 +1756,6 @@ public class MessagesController extends BaseController implements NotificationCe
case "chat_read_mark_size_threshold": {
if (value.value instanceof TLRPC.TL_jsonNumber) {
TLRPC.TL_jsonNumber number = (TLRPC.TL_jsonNumber) value.value;
Log.e("test", chatReadMarkSizeThreshold + "");
if (number.value != chatReadMarkSizeThreshold) {
chatReadMarkSizeThreshold = (int) number.value;
editor.putInt("chatReadMarkSizeThreshold", chatReadMarkSizeThreshold);
@ -1761,6 +1764,17 @@ public class MessagesController extends BaseController implements NotificationCe
}
break;
}
case "chat_read_mark_expire_period": {
if (value.value instanceof TLRPC.TL_jsonNumber) {
TLRPC.TL_jsonNumber number = (TLRPC.TL_jsonNumber) value.value;
if (number.value != chatReadMarkExpirePeriod) {
chatReadMarkExpirePeriod = (int) number.value;
editor.putInt("chatReadMarkExpirePeriod", chatReadMarkExpirePeriod);
changed = true;
}
}
break;
}
case "inapp_update_check_delay": {
if (value.value instanceof TLRPC.TL_jsonNumber) {
TLRPC.TL_jsonNumber number = (TLRPC.TL_jsonNumber) value.value;
@ -2718,6 +2732,7 @@ public class MessagesController extends BaseController implements NotificationCe
channelViewsToSend.clear();
pollsToCheck.clear();
pollsToCheckSize = 0;
reactionsToCheck.clear();
dialogsServerOnly.clear();
dialogsForward.clear();
allDialogs.clear();
@ -2949,6 +2964,12 @@ public class MessagesController extends BaseController implements NotificationCe
object.pollVisibleOnScreen = false;
}
}
array = reactionsToCheck.get(dialogId);
if (array != null) {
for (int i = 0; i < array.size(); i++) {
array.valueAt(i).reactionsVisibleOnScreen = false;
}
}
}
}
Utilities.stageQueue.postRunnable(() -> {
@ -5583,6 +5604,57 @@ public class MessagesController extends BaseController implements NotificationCe
}
}
int currentServerTime = getConnectionsManager().getCurrentTime();
if (Math.abs(System.currentTimeMillis() - lastReactionsCheckTime) >= 15000) {
lastReactionsCheckTime = System.currentTimeMillis();
if (reactionsToCheck.size() > 0) {
AndroidUtilities.runOnUIThread(() -> {
long time = SystemClock.elapsedRealtime();
for (int a = 0, N = reactionsToCheck.size(); a < N; a++) {
SparseArray<MessageObject> array = reactionsToCheck.valueAt(a);
if (array == null) {
continue;
}
reactionsTempDialogs.clear();
for (int b = 0, N2 = array.size(); b < N2; b++) {
MessageObject messageObject = array.valueAt(b);
List<Integer> ids = reactionsTempDialogs.get(messageObject.getDialogId());
if (ids == null) {
reactionsTempDialogs.put(messageObject.getDialogId(), ids = new ArrayList<>());
}
ids.add(messageObject.getId());
int timeout = 15000;
if (Math.abs(time - messageObject.reactionsLastCheckTime) < timeout) {
if (!messageObject.reactionsVisibleOnScreen) {
array.remove(messageObject.getId());
N2--;
b--;
}
} else {
messageObject.reactionsLastCheckTime = time;
}
}
if (array.size() == 0) {
reactionsToCheck.remove(reactionsToCheck.keyAt(a));
N--;
a--;
}
}
for (int i = 0; i < reactionsTempDialogs.size(); i++) {
TLRPC.TL_messages_getMessagesReactions req = new TLRPC.TL_messages_getMessagesReactions();
req.peer = getInputPeer(reactionsTempDialogs.keyAt(i));
req.id.addAll(reactionsTempDialogs.valueAt(i));
getConnectionsManager().sendRequest(req, (response, error) -> {
if (error == null) {
TLRPC.Updates updates = (TLRPC.Updates) response;
processUpdates(updates, false);
}
});
}
});
}
}
if (Math.abs(System.currentTimeMillis() - lastViewsCheckTime) >= 5000) {
lastViewsCheckTime = System.currentTimeMillis();
if (channelViewsToSend.size() != 0) {
@ -8761,6 +8833,28 @@ public class MessagesController extends BaseController implements NotificationCe
});
}
public void addToReactionsQueue(long dialogId, ArrayList<MessageObject> visibleObjects) {
SparseArray<MessageObject> array = reactionsToCheck.get(dialogId);
if (array == null) {
reactionsToCheck.put(dialogId, array = new SparseArray<>());
}
for (int a = 0, N = array.size(); a < N; a++) {
MessageObject object = array.valueAt(a);
object.reactionsVisibleOnScreen = false;
}
int time = getConnectionsManager().getCurrentTime();
for (int a = 0, N = visibleObjects.size(); a < N; a++) {
MessageObject messageObject = visibleObjects.get(a);
int id = messageObject.getId();
MessageObject object = array.get(id);
if (object != null) {
object.reactionsVisibleOnScreen = true;
} else {
array.put(id, messageObject);
}
}
}
public void addToPollsQueue(long dialogId, ArrayList<MessageObject> visibleObjects) {
SparseArray<MessageObject> array = pollsToCheck.get(dialogId);
if (array == null) {
@ -14283,6 +14377,8 @@ public class MessagesController extends BaseController implements NotificationCe
messageObject.sponsoredId = sponsoredMessage.random_id;
messageObject.botStartParam = sponsoredMessage.start_param;
messageObject.sponsoredChannelPost = sponsoredMessage.channel_post;
messageObject.sponsoredChatInvite = sponsoredMessage.chat_invite;
messageObject.sponsoredChatInviteHash = sponsoredMessage.chat_invite_hash;
result.add(messageObject);
}
}
@ -15156,10 +15252,7 @@ public class MessagesController extends BaseController implements NotificationCe
}
public void markSponsoredAsRead(long dialog_id, MessageObject object) {
ArrayList<MessageObject> messages = getSponsoredMessages(dialog_id);
if (messages != null) {
messages.remove(object);
}
sponsoredMessages.remove(dialog_id);
}
public void deleteMessagesRange(long dialogId, long channelId, int minDate, int maxDate, boolean forAll, Runnable callback) {
@ -15191,6 +15284,23 @@ public class MessagesController extends BaseController implements NotificationCe
});
}
public void setChatReactions(long chatId, List<String> reactions) {
TLRPC.TL_messages_setChatAvailableReactions req = new TLRPC.TL_messages_setChatAvailableReactions();
req.peer = getInputPeer(-chatId);
req.available_reactions.addAll(reactions);
getConnectionsManager().sendRequest(req, (response, error) -> {
if (response != null) {
processUpdates((TLRPC.Updates) response, false);
TLRPC.ChatFull full = getChatFull(chatId);
if (full != null) {
full.available_reactions = new ArrayList<>(reactions);
getMessagesStorage().updateChatInfo(full, false);
}
AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.chatAvailableReactionsUpdated, chatId));
}
});
}
public interface MessagesLoadedCallback {
void onMessagesLoaded(boolean fromCache);

View File

@ -21,6 +21,9 @@ import android.util.SparseIntArray;
import androidx.annotation.UiThread;
import androidx.annotation.UiThread;
import androidx.collection.LongSparseArray;
import org.telegram.PhoneFormat.PhoneFormat;
import org.telegram.SQLite.SQLiteCursor;
import org.telegram.SQLite.SQLiteDatabase;
@ -121,7 +124,7 @@ public class MessagesStorage extends BaseController {
private CountDownLatch openSync = new CountDownLatch(1);
private static SparseArray<MessagesStorage> Instance = new SparseArray();
private final static int LAST_DB_VERSION = 86;
private final static int LAST_DB_VERSION = 87;
private boolean databaseMigrationInProgress;
public static MessagesStorage getInstance(int num) {
@ -296,7 +299,7 @@ public class MessagesStorage extends BaseController {
shmCacheFile = new File(filesDir, "cache4.db-shm");
boolean createTable = false;
//cacheFile.delete();
if (!cacheFile.exists()) {
createTable = true;
}
@ -426,6 +429,7 @@ public class MessagesStorage extends BaseController {
database.executeFast("CREATE TABLE polls_v2(mid INTEGER, uid INTEGER, id INTEGER, PRIMARY KEY (mid, uid));").stepThis().dispose();
database.executeFast("CREATE INDEX IF NOT EXISTS polls_id_v2 ON polls_v2(id);").stepThis().dispose();
database.executeFast("CREATE TABLE reactions(data BLOB, hash INTEGER, date INTEGER);").stepThis().dispose();
//version
database.executeFast("PRAGMA user_version = " + LAST_DB_VERSION).stepThis().dispose();
} else {
@ -456,6 +460,9 @@ public class MessagesStorage extends BaseController {
}
cursor.dispose();
} catch (Exception e) {
if (e.getMessage() != null && e.getMessage().contains("malformed")) {
throw new RuntimeException("malformed");
}
FileLog.e(e);
try {
database.executeFast("CREATE TABLE IF NOT EXISTS params(id INTEGER PRIMARY KEY, seq INTEGER, pts INTEGER, date INTEGER, qts INTEGER, lsv INTEGER, sg INTEGER, pbytes BLOB)").stepThis().dispose();
@ -1555,6 +1562,12 @@ public class MessagesStorage extends BaseController {
version = 86;
}
if (version == 86) {
database.executeFast("CREATE TABLE IF NOT EXISTS reactions(data BLOB, hash INTEGER, date INTEGER);").stepThis().dispose();
database.executeFast("PRAGMA user_version = 87").stepThis().dispose();
version = 87;
}
FileLog.d("MessagesStorage db migration finished");
AndroidUtilities.runOnUIThread(() -> {
databaseMigrationInProgress = false;
@ -6041,6 +6054,9 @@ public class MessagesStorage extends BaseController {
participant = TLRPC.ChannelParticipant.TLdeserialize(data, data.readInt32(false), false);
data.reuse();
}
if (participant != null && participant.user_id == getUserConfig().clientUserId) {
user = getUserConfig().getCurrentUser();
}
if (user != null && participant != null) {
if (user.status != null) {
user.status.expires = cursor.intValue(1);
@ -8774,7 +8790,7 @@ public class MessagesStorage extends BaseController {
try {
SQLitePreparedStatement state = database.executeFast("UPDATE messages_v2 SET replies_data = ? WHERE mid = ? AND uid = ?");
TLRPC.MessageReplies currentReplies = null;
SQLiteCursor cursor = database.queryFinalized(String.format("SELECT replies_data FROM messages_v2 WHERE mid = %d AND uid = %d", mid, -chatId));
SQLiteCursor cursor = database.queryFinalized(String.format(Locale.ENGLISH, "SELECT replies_data FROM messages_v2 WHERE mid = %d AND uid = %d", mid, -chatId));
if (cursor.next()) {
NativeByteBuffer data = cursor.byteBufferValue(0);
if (data != null) {

View File

@ -109,6 +109,8 @@ public class NotificationCenter {
public static final int newPeopleNearbyAvailable = totalEvents++;
public static final int stopAllHeavyOperations = totalEvents++;
public static final int startAllHeavyOperations = totalEvents++;
public static final int stopSpoilers = totalEvents++;
public static final int startSpoilers = totalEvents++;
public static final int sendingMessagesChanged = totalEvents++;
public static final int didUpdateReactions = totalEvents++;
public static final int didVerifyMessagesStickers = totalEvents++;
@ -228,6 +230,8 @@ public class NotificationCenter {
public static final int onDatabaseMigration = totalEvents++;
public static final int onEmojiInteractionsReceived = totalEvents++;
public static final int emojiPreviewThemesChanged = totalEvents++;
public static final int reactionsDidLoad = totalEvents++;
public static final int chatAvailableReactionsUpdated = totalEvents++;
// custom

View File

@ -1385,7 +1385,7 @@ public class NotificationsController extends BaseController {
}
}
}
return messageObject.messageOwner.message;
return replaceSpoilers(messageObject);
}
long selfUsedId = getUserConfig().getClientUserId();
if (fromId == 0) {
@ -1834,13 +1834,13 @@ public class NotificationsController extends BaseController {
} else {
if (messageObject.isMediaEmpty()) {
if (!TextUtils.isEmpty(messageObject.messageOwner.message)) {
return messageObject.messageOwner.message;
return replaceSpoilers(messageObject);
} else {
return LocaleController.getString("Message", R.string.Message);
}
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) {
if (Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.message)) {
return "\uD83D\uDDBC " + messageObject.messageOwner.message;
return "\uD83D\uDDBC " + replaceSpoilers(messageObject);
} else if (messageObject.messageOwner.media.ttl_seconds != 0) {
return LocaleController.getString("AttachDestructingPhoto", R.string.AttachDestructingPhoto);
} else {
@ -1848,7 +1848,7 @@ public class NotificationsController extends BaseController {
}
} else if (messageObject.isVideo()) {
if (Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.message)) {
return "\uD83D\uDCF9 " + messageObject.messageOwner.message;
return "\uD83D\uDCF9 " + replaceSpoilers(messageObject);
} else if (messageObject.messageOwner.media.ttl_seconds != 0) {
return LocaleController.getString("AttachDestructingVideo", R.string.AttachDestructingVideo);
} else {
@ -1884,19 +1884,19 @@ public class NotificationsController extends BaseController {
}
} else if (messageObject.isGif()) {
if (Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.message)) {
return "\uD83C\uDFAC " + messageObject.messageOwner.message;
return "\uD83C\uDFAC " + replaceSpoilers(messageObject);
} else {
return LocaleController.getString("AttachGif", R.string.AttachGif);
}
} else {
if (Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.message)) {
return "\uD83D\uDCCE " + messageObject.messageOwner.message;
return "\uD83D\uDCCE " + replaceSpoilers(messageObject);
} else {
return LocaleController.getString("AttachDocument", R.string.AttachDocument);
}
}
} else if (!TextUtils.isEmpty(messageObject.messageText)) {
return messageObject.messageText.toString();
return replaceSpoilers(messageObject);
} else {
return LocaleController.getString("Message", R.string.Message);
}
@ -1911,6 +1911,27 @@ public class NotificationsController extends BaseController {
return null;
}
char[] spoilerChars = new char[] {
'⠌', '⡢', '⢑','⠨',
};
private String replaceSpoilers(MessageObject messageObject) {
String text = messageObject.messageOwner.message;
if (text == null || messageObject == null || messageObject.messageOwner == null || messageObject.messageOwner.entities == null) {
return null;
}
StringBuilder stringBuilder = new StringBuilder(text);
for (int i = 0; i < messageObject.messageOwner.entities.size(); i++) {
if (messageObject.messageOwner.entities.get(i) instanceof TLRPC.TL_messageEntitySpoiler) {
TLRPC.TL_messageEntitySpoiler spoiler = (TLRPC.TL_messageEntitySpoiler) messageObject.messageOwner.entities.get(i);
for (int j = 0; j < spoiler.length; j++) {
stringBuilder.setCharAt(spoiler.offset + j, spoilerChars[j % spoilerChars.length]);
}
}
}
return stringBuilder.toString();
}
private String getStringForMessage(MessageObject messageObject, boolean shortMessage, boolean[] text, boolean[] preview) {
if (AndroidUtilities.needShowPasscode() || SharedConfig.isWaitingForPasscodeEnter) {
return LocaleController.getString("YouHaveNewMessage", R.string.YouHaveNewMessage);

View File

@ -1460,7 +1460,8 @@ public boolean retriedToSend;
entity instanceof TLRPC.TL_messageEntityItalic ||
entity instanceof TLRPC.TL_messageEntityPre ||
entity instanceof TLRPC.TL_messageEntityCode ||
entity instanceof TLRPC.TL_messageEntityTextUrl) {
entity instanceof TLRPC.TL_messageEntityTextUrl ||
entity instanceof TLRPC.TL_messageEntitySpoiler) {
entities.add(entity);
}
}
@ -2655,7 +2656,7 @@ public boolean retriedToSend;
return voteSendTime.get(pollId, 0L);
}
public void sendReaction(MessageObject messageObject, CharSequence reaction, ChatActivity parentFragment) {
public void sendReaction(MessageObject messageObject, CharSequence reaction, ChatActivity parentFragment, Runnable callback) {
if (messageObject == null || parentFragment == null) {
return;
}
@ -2669,16 +2670,10 @@ public boolean retriedToSend;
getConnectionsManager().sendRequest(req, (response, error) -> {
if (response != null) {
getMessagesController().processUpdates((TLRPC.Updates) response, false);
}
/*AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
waitingForVote.remove(key);
if (finishRunnable != null) {
finishRunnable.run();
}
if (callback != null) {
AndroidUtilities.runOnUIThread(callback);
}
});*/
}
});
}

View File

@ -31,17 +31,24 @@ import org.apache.commons.lang3.StringUtils;
import org.dizitart.no2.objects.filters.ObjectFilters;
import org.json.JSONArray;
import org.json.JSONException;
import androidx.annotation.IntDef;
import androidx.core.content.pm.ShortcutManagerCompat;
import org.json.JSONObject;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.SerializedData;
import org.telegram.ui.Components.SwipeGestureSettingsView;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.Components.SwipeGestureSettingsView;
import java.io.File;
import java.io.RandomAccessFile;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.Arrays;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
@ -67,6 +74,7 @@ import static com.v2ray.ang.V2RayConfig.SSR_PROTOCOL;
import static com.v2ray.ang.V2RayConfig.SS_PROTOCOL;
import static com.v2ray.ang.V2RayConfig.WSS_PROTOCOL;
import static com.v2ray.ang.V2RayConfig.WS_PROTOCOL;
import java.util.Locale;
public class SharedConfig {
@ -107,6 +115,7 @@ public class SharedConfig {
public static boolean searchMessagesAsListUsed;
public static boolean stickersReorderingHintUsed;
public static boolean disableVoiceAudioEffects;
public static boolean drawSnowInChat;
private static int lastLocalId = -210000;
public static String storageCacheDir;
@ -1271,6 +1280,7 @@ public class SharedConfig {
mediaColumnsCount = preferences.getInt("mediaColumnsCount", 3);
fastScrollHintCount = preferences.getInt("fastScrollHintCount", 3);
dontAskManageStorage = preferences.getBoolean("dontAskManageStorage", false);
drawSnowInChat = preferences.getBoolean("drawSnowInChat", BuildVars.DEBUG_VERSION);
preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE);
showNotificationsForAllAccounts = preferences.getBoolean("AllAccounts", true);
@ -1608,6 +1618,14 @@ public class SharedConfig {
editor.commit();
}
public static void toggleDrawSnowInChat() {
drawSnowInChat = !drawSnowInChat;
SharedPreferences preferences = MessagesController.getGlobalMainSettings();
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("drawSnowInChat", drawSnowInChat);
editor.commit();
}
public static void toggleNoiseSupression() {
noiseSupression = !noiseSupression;
SharedPreferences preferences = MessagesController.getGlobalMainSettings();
@ -2248,25 +2266,38 @@ public class SharedConfig {
public final static int PERFORMANCE_CLASS_AVERAGE = 1;
public final static int PERFORMANCE_CLASS_HIGH = 2;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
PERFORMANCE_CLASS_LOW,
PERFORMANCE_CLASS_AVERAGE,
PERFORMANCE_CLASS_HIGH
})
public @interface PerformanceClass {}
@PerformanceClass
public static int getDevicePerformanceClass() {
if (devicePerformanceClass == -1) {
int maxCpuFreq = -1;
try {
RandomAccessFile reader = new RandomAccessFile("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", "r");
String line = reader.readLine();
if (line != null) {
maxCpuFreq = Utilities.parseInt(line) / 1000;
}
reader.close();
} catch (Throwable ignore) {
}
int androidVersion = Build.VERSION.SDK_INT;
int cpuCount = ConnectionsManager.CPU_COUNT;
int memoryClass = ((ActivityManager) ApplicationLoader.applicationContext.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
int totalCpuFreq = 0;
int freqResolved = 0;
for (int i = 0; i < cpuCount; i++) {
try {
RandomAccessFile reader = new RandomAccessFile(String.format(Locale.ENGLISH, "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", i), "r");
String line = reader.readLine();
if (line != null) {
totalCpuFreq += Utilities.parseInt(line) / 1000;
freqResolved++;
}
reader.close();
} catch (Throwable ignore) {}
}
int maxCpuFreq = freqResolved == 0 ? -1 : (int) Math.ceil(totalCpuFreq / (float) freqResolved);
if (androidVersion < 21 || cpuCount <= 2 || memoryClass <= 100 || cpuCount <= 4 && maxCpuFreq != -1 && maxCpuFreq <= 1250 || cpuCount <= 4 && maxCpuFreq <= 1600 && memoryClass <= 128 && androidVersion <= 21 || cpuCount <= 4 && maxCpuFreq <= 1300 && memoryClass <= 128 && androidVersion <= 24) {
devicePerformanceClass = PERFORMANCE_CLASS_LOW;
} else if (cpuCount < 8 || memoryClass <= 160 || maxCpuFreq != -1 && maxCpuFreq <= 1650 || maxCpuFreq == -1 && cpuCount == 8 && androidVersion <= 23) {
} else if (cpuCount < 8 || memoryClass <= 160 || maxCpuFreq != -1 && maxCpuFreq <= 2050 || maxCpuFreq == -1 && cpuCount == 8 && androidVersion <= 23) {
devicePerformanceClass = PERFORMANCE_CLASS_AVERAGE;
} else {
devicePerformanceClass = PERFORMANCE_CLASS_HIGH;

View File

@ -14,12 +14,15 @@ import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import com.carrotsearch.randomizedtesting.Xoroshiro128PlusRandom;
import java.io.File;
import java.io.FileInputStream;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -27,6 +30,7 @@ public class Utilities {
public static Pattern pattern = Pattern.compile("[\\-0-9]+");
public static SecureRandom random = new SecureRandom();
public static Random fastRandom = new Xoroshiro128PlusRandom(random.nextLong());
public static volatile DispatchQueue stageQueue = new DispatchQueue("stageQueue");
public static volatile DispatchQueue globalQueue = new DispatchQueue("globalQueue");

View File

@ -166,7 +166,7 @@ public class MediaCodecVideoConvertor {
inputSurface.makeCurrent();
encoder.start();
// outputSurface = new OutputSurface(savedFilterState, videoPath, paintPath, mediaEntities, null, resultWidth, resultHeight, rotationValue, framerate, true);
outputSurface = new OutputSurface(savedFilterState, videoPath, paintPath, mediaEntities, null, resultWidth, resultHeight, rotationValue, framerate, true);
ByteBuffer[] encoderOutputBuffers = null;
ByteBuffer[] encoderInputBuffers = null;

File diff suppressed because it is too large Load Diff

View File

@ -214,7 +214,7 @@ public class ActionBar extends FrameLayout {
manualStart = true;
if (snowflakesEffect == null) {
fireworksEffect = null;
snowflakesEffect = new SnowflakesEffect();
snowflakesEffect = new SnowflakesEffect(0);
titleTextView[0].invalidate();
invalidate();
} else {
@ -270,7 +270,7 @@ public class ActionBar extends FrameLayout {
}
} else if (NekomuraConfig.actionBarDecoration.Int() == 1 || Theme.canStartHolidayAnimation()) {
if (snowflakesEffect == null) {
snowflakesEffect = new SnowflakesEffect();
snowflakesEffect = new SnowflakesEffect(0);
}
} else if (!manualStart) {
if (snowflakesEffect != null) {

View File

@ -24,14 +24,7 @@ import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import androidx.annotation.Keep;
import android.os.SystemClock;
import androidx.core.graphics.ColorUtils;
import androidx.core.math.MathUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
@ -44,14 +37,14 @@ import android.view.ViewOutlineProvider;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import androidx.annotation.Keep;
import com.google.android.exoplayer2.util.Log;
import androidx.annotation.Keep;
import androidx.core.graphics.ColorUtils;
import androidx.core.math.MathUtils;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.MessagesController;
import org.telegram.messenger.R;
@ -1071,6 +1064,7 @@ public class ActionBarLayout extends FrameLayout {
}
public boolean presentFragment(final BaseFragment fragment, final boolean removeLast, boolean forceWithoutAnimation, boolean check, final boolean preview, View menu) {
Log.i("UI", "presenting Fragment " + fragment);
if (fragment == null || checkTransitionAnimation() || delegate != null && check && !delegate.needPresentFragment(fragment, removeLast, forceWithoutAnimation, this) || !fragment.onFragmentCreate()) {
return false;
}
@ -1832,7 +1826,7 @@ public class ActionBarLayout extends FrameLayout {
themeAnimatorSet = null;
}
boolean startAnimation = false;
int fragmentCount = settings.onlyTopFragment ? 1 : 2;
int fragmentCount = settings.onlyTopFragment ? 1 : fragmentsStack.size();
for (int i = 0; i < fragmentCount; i++) {
BaseFragment fragment;
if (i == 0) {
@ -1865,7 +1859,7 @@ public class ActionBarLayout extends FrameLayout {
}
if (i == 0) {
if (settings.applyTheme) {
if (settings.accentId != -1) {
if (settings.accentId != -1 && settings.theme != null) {
settings.theme.setCurrentAccentId(settings.accentId);
Theme.saveThemeAccents(settings.theme, true, false, true, false);
}
@ -1884,11 +1878,13 @@ public class ActionBarLayout extends FrameLayout {
}
}
if (startAnimation) {
int count = fragmentsStack.size() - (inPreviewMode || transitionAnimationPreviewMode ? 2 : 1);
for (int a = 0; a < count; a++) {
BaseFragment fragment = fragmentsStack.get(a);
fragment.clearViews();
fragment.setParentLayout(this);
if (!settings.onlyTopFragment) {
int count = fragmentsStack.size() - (inPreviewMode || transitionAnimationPreviewMode ? 2 : 1);
for (int a = 0; a < count; a++) {
BaseFragment fragment = fragmentsStack.get(a);
fragment.clearViews();
fragment.setParentLayout(this);
}
}
if (settings.instant) {
setThemeAnimationValue(1.0f);

View File

@ -318,6 +318,17 @@ public class ActionBarMenu extends LinearLayout {
return w;
}
public int getVisibleItemsMeasuredWidth() {
int w = 0;
for (int i = 0, count = getChildCount(); i < count; i++) {
View view = getChildAt(i);
if (view instanceof ActionBarMenuItem && view.getVisibility() != View.GONE) {
w += view.getMeasuredWidth();
}
}
return w;
}
public boolean searchFieldVisible() {
int count = getChildCount();
for (int a = 0; a < count; a++) {

View File

@ -1632,6 +1632,7 @@ public class ActionBarMenuItem extends FrameLayout {
popupLayout.getItemAt(a).setVisibility(GONE);
}
measurePopup = true;
checkHideMenuItem();
}
public boolean isSubItemVisible(int id) {

View File

@ -37,12 +37,16 @@ import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.ScrollView;
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.NotificationCenter;
import org.telegram.messenger.R;
import org.telegram.messenger.UserConfig;
import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.PopupSwipeBackLayout;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@ -87,6 +91,7 @@ public class ActionBarPopupWindow extends PopupWindow {
}
public static class ActionBarPopupWindowLayout extends FrameLayout {
public final static int FLAG_USE_SWIPEBACK = 1;
private OnDispatchKeyEventListener mOnDispatchKeyEventListener;
private float backScaleX = 1;
@ -101,6 +106,7 @@ public class ActionBarPopupWindow extends PopupWindow {
private int gapEndY = -1000000;
private Rect bgPaddings = new Rect();
private PopupSwipeBackLayout swipeBackLayout;
private ScrollView scrollView;
protected LinearLayout linearLayout;
@ -111,21 +117,18 @@ public class ActionBarPopupWindow extends PopupWindow {
private final Theme.ResourcesProvider resourcesProvider;
public ActionBarPopupWindowLayout(Context context) {
this(context, false);
this(context, null);
}
public ActionBarPopupWindowLayout(Context context, Theme.ResourcesProvider resourcesProvider) {
this(context, false, R.drawable.popup_fixed_alert2, resourcesProvider);
this(context, R.drawable.popup_fixed_alert2, resourcesProvider);
}
public ActionBarPopupWindowLayout(Context context, boolean verticalScrollBarEnabled) {
this(context, verticalScrollBarEnabled, R.drawable.popup_fixed_alert2, null);
}
public ActionBarPopupWindowLayout(Context context, int resId, Theme.ResourcesProvider resourcesProvider) {
this(context, false, resId, resourcesProvider);
this(context, resId, resourcesProvider, 0);
}
public ActionBarPopupWindowLayout(Context context, boolean verticalScrollBarEnabled, int resId, Theme.ResourcesProvider resourcesProvider) {
public ActionBarPopupWindowLayout(Context context, int resId, Theme.ResourcesProvider resourcesProvider, int flags) {
super(context);
this.resourcesProvider = resourcesProvider;
@ -138,10 +141,17 @@ public class ActionBarPopupWindow extends PopupWindow {
setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8));
setWillNotDraw(false);
if ((flags & FLAG_USE_SWIPEBACK) > 0) {
swipeBackLayout = new PopupSwipeBackLayout(context, resourcesProvider);
addView(swipeBackLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT));
}
try {
scrollView = new ScrollView(context);
scrollView.setVerticalScrollBarEnabled(verticalScrollBarEnabled);
addView(scrollView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT));
// scrollView.setVerticalScrollBarEnabled(verticalScrollBarEnabled);
if (swipeBackLayout != null) {
swipeBackLayout.addView(scrollView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT));
} else addView(scrollView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT));
} catch (Throwable e) {
FileLog.e(e);
}
@ -191,11 +201,23 @@ public class ActionBarPopupWindow extends PopupWindow {
linearLayout.setOrientation(LinearLayout.VERTICAL);
if (scrollView != null) {
scrollView.addView(linearLayout, new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
} else if (swipeBackLayout != null) {
swipeBackLayout.addView(linearLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT));
} else {
addView(linearLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT));
}
}
@Nullable
public PopupSwipeBackLayout getSwipeBack() {
return swipeBackLayout;
}
public int addViewToSwipeBack(View v) {
swipeBackLayout.addView(v);
return swipeBackLayout.getChildCount() - 1;
}
public void setFitItems(boolean value) {
fitItems = value;
}
@ -729,4 +751,4 @@ public class ActionBarPopupWindow extends PopupWindow {
public void setEmptyOutAnimation(long time) {
outEmptyTime = time;
}
}
}

View File

@ -707,7 +707,7 @@ public abstract class BaseFragment {
}
public int getThemedColor(String key) {
return Theme.getColor(key);
return Theme.getColor(key, getResourceProvider());
}
public Drawable getThemedDrawable(String key) {

View File

@ -27,6 +27,14 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.view.NestedScrollingParent;
import androidx.core.view.NestedScrollingParentHelper;
import androidx.core.view.ViewCompat;
import androidx.core.widget.NestedScrollView;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
@ -300,13 +308,15 @@ public class BottomSheet extends Dialog {
}
}
boolean processTouchEvent(MotionEvent ev, boolean intercept) {
private float y = 0f;
public boolean processTouchEvent(MotionEvent ev, boolean intercept) {
if (dismissed) {
return false;
}
if (onContainerTouchEvent(ev)) {
return true;
}
if (canDismissWithTouchOutside() && ev != null && (ev.getAction() == MotionEvent.ACTION_DOWN || ev.getAction() == MotionEvent.ACTION_MOVE) && (!startedTracking && !maybeStartTracking && ev.getPointerCount() == 1)) {
startedTrackingX = (int) ev.getX();
startedTrackingY = (int) ev.getY();
@ -314,6 +324,7 @@ public class BottomSheet extends Dialog {
dismiss();
return true;
}
onScrollUpBegin(y);
startedTrackingPointerId = ev.getPointerId(0);
maybeStartTracking = true;
cancelCurrentAnimation();
@ -326,6 +337,7 @@ public class BottomSheet extends Dialog {
}
float dx = Math.abs((int) (ev.getX() - startedTrackingX));
float dy = (int) ev.getY() - startedTrackingY;
boolean canScrollUp = onScrollUp(y + dy);
velocityTracker.addMovement(ev);
if (!disableScroll && maybeStartTracking && !startedTracking && (dy > 0 && dy / 3.0f > Math.abs(dx) && Math.abs(dy) >= touchSlop)) {
startedTrackingY = (int) ev.getY();
@ -333,12 +345,10 @@ public class BottomSheet extends Dialog {
startedTracking = true;
requestDisallowInterceptTouchEvent(true);
} else if (startedTracking) {
float translationY = containerView.getTranslationY();
translationY += dy;
if (translationY < 0) {
translationY = 0;
}
containerView.setTranslationY(translationY);
y += dy;
if (!canScrollUp)
y = Math.max(y, 0);
containerView.setTranslationY(Math.max(y, 0));
startedTrackingY = (int) ev.getY();
container.invalidate();
}
@ -347,14 +357,13 @@ public class BottomSheet extends Dialog {
velocityTracker = VelocityTracker.obtain();
}
velocityTracker.computeCurrentVelocity(1000);
float translationY = containerView.getTranslationY();
if (startedTracking || translationY != 0) {
onScrollUpEnd(y);
if (startedTracking || y > 0) {
checkDismiss(velocityTracker.getXVelocity(), velocityTracker.getYVelocity());
startedTracking = false;
} else {
maybeStartTracking = false;
startedTracking = false;
}
startedTracking = false;
if (velocityTracker != null) {
velocityTracker.recycle();
velocityTracker = null;
@ -1068,6 +1077,12 @@ public class BottomSheet extends Dialog {
protected boolean onContainerTouchEvent(MotionEvent event) {
return false;
}
protected boolean onScrollUp(float translationY) {
return false;
}
protected void onScrollUpEnd(float translationY) {
}
protected void onScrollUpBegin(float translationY) {}
public void setCustomView(View view) {
customView = view;

View File

@ -29,7 +29,7 @@ import java.util.Map;
public class EmojiThemes {
public boolean showAsDefaultStub;
String emoji;
public String emoji;
int currentIndex = 0;
ArrayList<ThemeItem> items = new ArrayList<>();
@ -186,6 +186,23 @@ public class EmojiThemes {
return themeItem;
}
public static EmojiThemes createHomeQrTheme() {
EmojiThemes themeItem = new EmojiThemes();
themeItem.emoji = "\uD83C\uDFE0";
ThemeItem blue = new ThemeItem();
blue.themeInfo = Theme.getTheme("Blue");
blue.accentId = 99;
themeItem.items.add(blue);
ThemeItem nightBlue = new ThemeItem();
nightBlue.themeInfo = Theme.getTheme("Dark Blue");
nightBlue.accentId = 0;
themeItem.items.add(nightBlue);
return themeItem;
}
public void initColors() {
getPreviewColors(0, 0);
getPreviewColors(0, 1);
@ -202,7 +219,10 @@ public class EmojiThemes {
public TLRPC.WallPaper getWallpaper(int index) {
int settingsIndex = items.get(index).settingsIndex;
if (settingsIndex >= 0) {
return getTlTheme(index).settings.get(settingsIndex).wallpaper;
TLRPC.TL_theme tlTheme = getTlTheme(index);
if (tlTheme != null) {
return tlTheme.settings.get(settingsIndex).wallpaper;
}
}
return null;
}

View File

@ -12,13 +12,17 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.StaticLayout;
import android.text.TextPaint;
@ -34,6 +38,11 @@ import org.telegram.messenger.LocaleController;
import org.telegram.ui.Cells.DialogCell;
import org.telegram.ui.Components.EmptyStubSpan;
import org.telegram.ui.Components.StaticLayoutEx;
import org.telegram.ui.Components.spoilers.SpoilerEffect;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class SimpleTextView extends View implements Drawable.Callback {
@ -91,6 +100,10 @@ public class SimpleTextView extends View implements Drawable.Callback {
private int minusWidth;
private int fullTextMaxLines = 3;
private List<SpoilerEffect> spoilers = new ArrayList<>();
private Stack<SpoilerEffect> spoilersPool = new Stack<>();
private Path path = new Path();
public SimpleTextView(Context context) {
super(context);
textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
@ -298,6 +311,13 @@ public class SimpleTextView extends View implements Drawable.Callback {
}*/
layout = new StaticLayout(string, 0, string.length(), textPaint, scrollNonFitText ? AndroidUtilities.dp(2000) : width + AndroidUtilities.dp(8), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
}
spoilersPool.addAll(spoilers);
spoilers.clear();
if (layout != null && layout.getText() instanceof Spannable) {
SpoilerEffect.addSpoilers(this, layout, spoilersPool, spoilers);
}
calcOffset(width);
} catch (Exception ignore) {
@ -706,13 +726,38 @@ public class SimpleTextView extends View implements Drawable.Callback {
if (fullAlpha > 0 && fullLayoutLeftOffset != 0) {
canvas.save();
canvas.translate(-fullLayoutLeftOffset * fullAlpha + fullLayoutLeftCharactersOffset * fullAlpha, 0);
canvas.save();
clipOutSpoilers(canvas);
layout.draw(canvas);
canvas.restore();
drawSpoilers(canvas);
canvas.restore();
} else {
canvas.save();
clipOutSpoilers(canvas);
layout.draw(canvas);
canvas.restore();
drawSpoilers(canvas);
}
}
private void clipOutSpoilers(Canvas canvas) {
path.rewind();
for (SpoilerEffect eff : spoilers) {
Rect b = eff.getBounds();
path.addRect(b.left, b.top, b.right, b.bottom, Path.Direction.CW);
}
canvas.clipPath(path, Region.Op.DIFFERENCE);
}
private void drawSpoilers(Canvas canvas) {
for (SpoilerEffect eff : spoilers)
eff.draw(canvas);
}
private void updateScrollAnimation() {
if (!scrollNonFitText || !textDoesNotFit && scrollingOffset == 0) {
return;

View File

@ -65,6 +65,11 @@ import android.view.View;
import androidx.annotation.UiThread;
import androidx.core.graphics.ColorUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.core.graphics.ColorUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.telegram.messenger.AndroidUtilities;
@ -89,7 +94,6 @@ import org.telegram.messenger.time.SunDate;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.SerializedData;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.Cells.ThemesHorizontalListCell;
import org.telegram.ui.Components.AudioVisualizerDrawable;
import org.telegram.ui.Components.BackgroundGradientDrawable;
import org.telegram.ui.Components.ChatThemeBottomSheet;
@ -2425,7 +2429,7 @@ public class Theme {
public boolean createBackground(File file, String toPath) {
try {
Bitmap bitmap = ThemesHorizontalListCell.getScaledBitmap(AndroidUtilities.dp(640), AndroidUtilities.dp(360), file.getAbsolutePath(), null, 0);
Bitmap bitmap = AndroidUtilities.getScaledBitmap(AndroidUtilities.dp(640), AndroidUtilities.dp(360), file.getAbsolutePath(), null, 0);
if (bitmap != null && patternBgColor != 0) {
Bitmap finalBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
Canvas canvas = new Canvas(finalBitmap);
@ -3686,6 +3690,14 @@ public class Theme {
public final static String key_statisticChartLine_indigo = "statisticChartLine_indigo";
public final static String key_statisticChartLineEmpty = "statisticChartLineEmpty";
public static final String key_chat_outReactionButtonBackground = "chat_outReactionButtonBackground";
public static final String key_chat_inReactionButtonBackground = "chat_inReactionButtonBackground";
public static final String key_chat_outReactionButtonText = "chat_outReactionButtonText";
public static final String key_chat_inReactionButtonText = "chat_inReactionButtonText";
public static final String key_chat_inReactionButtonTextSelected = "chat_inReactionButtonTextSelected";
public static final String key_chat_outReactionButtonTextSelected = "chat_outReactionButtonTextSelected";
public static final String key_drawable_botInline = "drawableBotInline";
public static final String key_drawable_botLink = "drawableBotLink";
public static final String key_drawable_commentSticker = "drawableCommentSticker";
@ -3732,6 +3744,8 @@ public class Theme {
public static final String key_drawable_lockIconDrawable = "drawableLockIcon";
public static final String key_drawable_chat_pollHintDrawableOut = "drawable_chat_pollHintDrawableOut";
public static final String key_drawable_chat_pollHintDrawableIn = "drawable_chat_pollHintDrawableIn";
private static final HashMap<String, Drawable> defaultChatDrawables = new HashMap<>();
private static final HashMap<String, String> defaultChatDrawableColorKeys = new HashMap<>();
@ -4539,6 +4553,13 @@ public class Theme {
defaultColors.put(key_voipgroup_windowBackgroundWhiteInputField, 0xffdbdbdb);
defaultColors.put(key_voipgroup_windowBackgroundWhiteInputFieldActivated, 0xff37a9f0);
defaultColors.put(key_chat_outReactionButtonBackground, 0xff78c272);
defaultColors.put(key_chat_inReactionButtonBackground, 0xff72b5e8);
defaultColors.put(key_chat_inReactionButtonText, 0xff3a8ccf);
defaultColors.put(key_chat_outReactionButtonText, 0xff55ab4f);
defaultColors.put(key_chat_inReactionButtonTextSelected, 0xffffffff);
defaultColors.put(key_chat_outReactionButtonTextSelected, 0xffffffff);
fallbackKeys.put(key_chat_inAdminText, key_chat_inTimeText);
fallbackKeys.put(key_chat_inAdminSelectedText, key_chat_inTimeSelectedText);
@ -4675,6 +4696,13 @@ public class Theme {
fallbackKeys.put(key_returnToCallMutedBackground, key_windowBackgroundWhite);
fallbackKeys.put(key_dialogSwipeRemove, key_avatar_backgroundRed);
fallbackKeys.put(key_chat_inReactionButtonBackground, key_chat_inLoader);
fallbackKeys.put(key_chat_outReactionButtonBackground, key_chat_outLoader);
fallbackKeys.put(key_chat_inReactionButtonText, key_chat_inPreviewInstantText);
fallbackKeys.put(key_chat_outReactionButtonText, key_chat_outPreviewInstantText);
fallbackKeys.put(key_chat_inReactionButtonTextSelected, key_windowBackgroundWhite);
fallbackKeys.put(key_chat_outReactionButtonTextSelected, key_windowBackgroundWhite);
themeAccentExclusionKeys.addAll(Arrays.asList(keys_avatar_background));
themeAccentExclusionKeys.addAll(Arrays.asList(keys_avatar_nameInMessage));
themeAccentExclusionKeys.add(key_chat_attachFileBackground);
@ -5545,7 +5573,7 @@ public class Theme {
int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
int minutes = calendar.get(Calendar.MINUTE);
int hour = calendar.get(Calendar.HOUR_OF_DAY);
if (monthOfYear == 0 && dayOfMonth == 1 && minutes <= 10 && hour == 0) {
if (monthOfYear == 0 && dayOfMonth == 1 && hour <= 23) {
canStartHolidayAnimation = true;
} else {
canStartHolidayAnimation = false;
@ -9172,7 +9200,7 @@ public class Theme {
return colorInteger;
}
}
return getColor(key);
return getColor(key);
}
public static int getColor(String key) {
return getColor(key, null, false);

View File

@ -10,9 +10,8 @@ package org.telegram.ui;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
@ -26,7 +25,6 @@ import android.transition.TransitionSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
@ -83,11 +81,7 @@ import org.telegram.ui.Components.StroageUsageView;
import org.telegram.ui.Components.UndoView;
import java.io.File;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.stream.Stream;
import cn.hutool.core.thread.ThreadUtil;
import kotlin.Unit;
@ -132,7 +126,6 @@ public class CacheControlActivity extends BaseFragment {
private boolean calculating = true;
private volatile boolean canceled = false;
private boolean hasOldFolder;
private View bottomSheetView;
private BottomSheet bottomSheet;
@ -216,24 +209,6 @@ public class CacheControlActivity extends BaseFragment {
});
fragmentCreateTime = System.currentTimeMillis();
if (Build.VERSION.SDK_INT >= 30) {
File path = Environment.getExternalStorageDirectory();
if (Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(SharedConfig.storageCacheDir)) {
ArrayList<File> dirs = AndroidUtilities.getRootDirs();
if (dirs != null) {
for (int a = 0, N = dirs.size(); a < N; a++) {
File dir = dirs.get(a);
if (dir.getAbsolutePath().startsWith(SharedConfig.storageCacheDir)) {
path = dir;
break;
}
}
}
}
File oldDirectory = new File(path, "Telegram");
hasOldFolder = oldDirectory.exists();
}
updateRows();
return true;
}
@ -936,4 +911,21 @@ public class CacheControlActivity extends BaseFragment {
arrayList.add(new ThemeDescription(bottomSheetView, 0, null, null, null, null, Theme.key_statisticChartLine_indigo));
return arrayList;
}
@Override
public void onRequestPermissionsResultFragment(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == 4) {
boolean allGranted = true;
for (int a = 0; a < grantResults.length; a++) {
if (grantResults[a] != PackageManager.PERMISSION_GRANTED) {
allGranted = false;
break;
}
}
// if (allGranted && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && FilesMigrationService.filesMigrationBottomSheet != null) {
// FilesMigrationService.filesMigrationBottomSheet.migrateOldFolder();
// }
}
}
}

View File

@ -0,0 +1,128 @@
package org.telegram.ui.Cells;
import android.content.Context;
import android.graphics.Canvas;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.DocumentObject;
import org.telegram.messenger.ImageLocation;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.SvgHelper;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Components.BackupImageView;
import org.telegram.ui.Components.CheckBox2;
import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.Switch;
public class AvailableReactionCell extends FrameLayout {
private TextView textView;
private BackupImageView imageView;
private Switch switchView;
private CheckBox2 checkBox;
private View overlaySelectorView;
public TLRPC.TL_availableReaction react;
public AvailableReactionCell(@NonNull Context context, boolean checkbox) {
super(context);
textView = new TextView(context);
textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
textView.setLines(1);
textView.setMaxLines(1);
textView.setSingleLine(true);
textView.setEllipsize(TextUtils.TruncateAt.END);
textView.setGravity(LayoutHelper.getAbsoluteGravityStart() | Gravity.CENTER_VERTICAL);
addView(textView, LayoutHelper.createFrameRelatively(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL, 81, 0, 91, 0));
imageView = new BackupImageView(context);
imageView.setAspectFit(true);
imageView.setLayerNum(1);
addView(imageView, LayoutHelper.createFrameRelatively(32, 32, Gravity.START | Gravity.CENTER_VERTICAL, 23, 0, 0, 0));
if (checkbox) {
checkBox = new CheckBox2(context, 26, null);
checkBox.setDrawUnchecked(false);
checkBox.setColor(null, null, Theme.key_radioBackgroundChecked);
checkBox.setDrawBackgroundAsArc(-1);
addView(checkBox, LayoutHelper.createFrameRelatively(26, 26, Gravity.END | Gravity.CENTER_VERTICAL, 0, 0, 22, 0));
} else {
switchView = new Switch(context);
switchView.setColors(Theme.key_switchTrack, Theme.key_switchTrackChecked, Theme.key_switchTrackBlueThumb, Theme.key_switchTrackBlueThumbChecked);
addView(switchView, LayoutHelper.createFrameRelatively(37, 20, Gravity.END | Gravity.CENTER_VERTICAL, 0, 0, 22, 0));
}
overlaySelectorView = new View(context);
overlaySelectorView.setBackground(Theme.getSelectorDrawable(false));
addView(overlaySelectorView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
setWillNotDraw(false);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((int) (AndroidUtilities.dp(58) + Theme.dividerPaint.getStrokeWidth()), MeasureSpec.EXACTLY));
}
/**
* Binds reaction to the view
* @param react Reaction to bind
* @param checked If view should be checked
*/
public void bind(TLRPC.TL_availableReaction react, boolean checked) {
boolean animated = false;
if (react != null && this.react != null && react.reaction.equals(this.react.reaction)) {
animated = true;
}
this.react = react;
textView.setText(react.title);
SvgHelper.SvgDrawable svgThumb = DocumentObject.getSvgThumb(react.static_icon, Theme.key_windowBackgroundGray, 1.0f);
imageView.setImage(ImageLocation.getForDocument(react.static_icon), "50_50", "webp", svgThumb, react);
setChecked(checked, animated);
}
/**
* Sets view checked
* @param checked If checked or not
*/
public void setChecked(boolean checked) {
setChecked(checked, false);
}
/**
* Sets view checked
* @param checked If checked or not
* @param animated If we should animate change
*/
public void setChecked(boolean checked, boolean animated) {
if (switchView != null) {
switchView.setChecked(checked, animated);
}
if (checkBox != null) {
checkBox.setChecked(checked, animated);
}
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Theme.getColor(Theme.key_windowBackgroundWhite));
float w = Theme.dividerPaint.getStrokeWidth();
int l = 0, r = 0;
int pad = AndroidUtilities.dp(81);
if (LocaleController.isRTL) {
r = pad;
} else {
l = pad;
}
canvas.drawLine(getPaddingLeft() + l, getHeight() - w, getWidth() - getPaddingRight() - r, getHeight() - w, Theme.dividerPaint);
}
}

View File

@ -34,6 +34,7 @@ import org.telegram.messenger.ImageLocation;
import org.telegram.messenger.ImageReceiver;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MessageObject;
import org.telegram.messenger.NotificationCenter;
import org.telegram.messenger.R;
import org.telegram.messenger.SharedConfig;
import org.telegram.messenger.UserConfig;
@ -43,11 +44,28 @@ import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Components.AvatarDrawable;
import org.telegram.ui.Components.URLSpanNoUnderline;
import org.telegram.ui.Components.spoilers.SpoilerEffect;
import org.telegram.ui.PhotoViewer;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class ChatActionCell extends BaseCell implements DownloadController.FileDownloadProgressListener {
public class ChatActionCell extends BaseCell implements DownloadController.FileDownloadProgressListener, NotificationCenter.NotificationCenterDelegate {
@Override
public void didReceivedNotification(int id, int account, Object... args) {
if (id == NotificationCenter.startSpoilers) {
setSpoilersSuppressed(false);
} else if (id == NotificationCenter.stopSpoilers) {
setSpoilersSuppressed(true);
}
}
public void setSpoilersSuppressed(boolean s) {
for (SpoilerEffect eff : spoilers)
eff.setSuppressUpdates(s);
}
private boolean canDrawInParent;
@ -92,6 +110,9 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD
private int previousWidth;
private boolean imagePressed;
public List<SpoilerEffect> spoilers = new ArrayList<>();
private Stack<SpoilerEffect> spoilersPool = new Stack<>();
TextPaint textPaint;
private float viewTop;
@ -407,6 +428,12 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD
int maxWidth = width - AndroidUtilities.dp(30);
invalidatePath = true;
textLayout = new StaticLayout(text, (TextPaint) getThemedPaint(Theme.key_paint_chatActionText), maxWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
spoilersPool.addAll(spoilers);
spoilers.clear();
if (text instanceof Spannable)
SpoilerEffect.addSpoilers(this, textLayout, (Spannable) text, spoilersPool, spoilers);
textHeight = 0;
textWidth = 0;
try {
@ -494,8 +521,17 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD
if (textLayout.getPaint() != textPaint) {
buildLayout();
}
canvas.save();
SpoilerEffect.clipOutCanvas(canvas, spoilers);
textLayout.draw(canvas);
canvas.restore();
for (SpoilerEffect eff : spoilers) {
eff.setColor(textLayout.getPaint().getColor());
eff.draw(canvas);
}
canvas.restore();
}
}

View File

@ -21,6 +21,7 @@ import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.SystemClock;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.StaticLayout;
@ -38,6 +39,8 @@ import org.telegram.messenger.ChatObject;
import org.telegram.messenger.ContactsController;
import org.telegram.messenger.DialogObject;
import org.telegram.messenger.ChatThemeController;
import org.telegram.messenger.ContactsController;
import org.telegram.messenger.DialogObject;
import org.telegram.messenger.DownloadController;
import org.telegram.messenger.Emoji;
import org.telegram.messenger.FileLoader;
@ -66,11 +69,17 @@ import org.telegram.ui.Components.PullForegroundDrawable;
import org.telegram.ui.Components.RLottieDrawable;
import org.telegram.ui.Components.StaticLayoutEx;
import org.telegram.ui.Components.StatusDrawable;
import org.telegram.ui.Components.SwipeGestureSettingsView;
import org.telegram.ui.Components.TypefaceSpan;
import org.telegram.ui.Components.URLSpanNoUnderline;
import org.telegram.ui.Components.URLSpanNoUnderlineBold;
import org.telegram.ui.Components.spoilers.SpoilerEffect;
import org.telegram.ui.DialogsActivity;
import org.telegram.ui.Components.SwipeGestureSettingsView;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import tw.nekomimi.nekogram.MessageHelper;
import tw.nekomimi.nkmr.NekomuraConfig;
@ -234,6 +243,9 @@ public class DialogCell extends BaseCell {
private int messageLeft;
private StaticLayout messageLayout;
private Stack<SpoilerEffect> spoilersPool = new Stack<>();
private List<SpoilerEffect> spoilers = new ArrayList<>();
private int messageNameTop;
private int messageNameLeft;
private StaticLayout messageNameLayout;
@ -650,7 +662,16 @@ public class DialogCell extends BaseCell {
}
}
lastMessageString = message != null ? message.messageText : null;
CharSequence msgText = message != null ? message.messageText : null;
if (msgText instanceof Spannable) {
Spannable sp = new SpannableStringBuilder(msgText);
for (Object span : sp.getSpans(0, sp.length(), URLSpanNoUnderlineBold.class))
sp.removeSpan(span);
for (Object span : sp.getSpans(0, sp.length(), URLSpanNoUnderline.class))
sp.removeSpan(span);
msgText = sp;
}
lastMessageString = msgText;
if (customDialog != null) {
if (customDialog.type == 2) {
@ -938,7 +959,10 @@ public class DialogCell extends BaseCell {
if (mess.length() > 150) {
mess = mess.substring(0, 150);
}
SpannableStringBuilder stringBuilder = SpannableStringBuilder.valueOf(String.format(messageFormat, mess.replace('\n', ' '), messageNameString));
Spannable messSpan = new SpannableStringBuilder(mess);
MediaDataController.addTextStyleRuns(draftMessage, messSpan);
SpannableStringBuilder stringBuilder = AndroidUtilities.formatSpannable(messageFormat, AndroidUtilities.replaceNewLines(messSpan), messageNameString);
if (!useForceThreeLines && !SharedConfig.useThreeLinesLayout) {
stringBuilder.setSpan(new ForegroundColorSpanThemable(Theme.key_chats_draft, resourcesProvider), 0, messageNameString.length() + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
@ -996,7 +1020,7 @@ public class DialogCell extends BaseCell {
messageString = "";
showChecks = false;
} else {
messageString = message.messageText;
messageString = msgText;
}
currentMessagePaint = Theme.dialogs_messagePrintingPaint[paintIndex];
} else {
@ -1052,9 +1076,9 @@ public class DialogCell extends BaseCell {
if (!TextUtils.isEmpty(restrictionReason)) {
stringBuilder = SpannableStringBuilder.valueOf(String.format(messageFormat, restrictionReason, messageNameString));
} else if (message.caption != null) {
String mess = message.caption.toString();
CharSequence mess = message.caption.toString();
if (mess.length() > 150) {
mess = mess.substring(0, 150);
mess = mess.subSequence(0, 150);
}
String emoji;
if (!needEmoji) {
@ -1070,7 +1094,9 @@ public class DialogCell extends BaseCell {
} else {
emoji = "\uD83D\uDCCE ";
}
stringBuilder = SpannableStringBuilder.valueOf(String.format(messageFormat, emoji + mess.replace('\n', ' '), messageNameString));
SpannableStringBuilder msgBuilder = new SpannableStringBuilder(mess);
MediaDataController.addTextStyleRuns(message.messageOwner.entities, message.caption, msgBuilder);
stringBuilder = AndroidUtilities.formatSpannable(messageFormat, new SpannableStringBuilder(emoji).append(AndroidUtilities.replaceNewLines(msgBuilder)), messageNameString);
} else if (message.messageOwner.media != null && !message.isMediaEmpty()) {
currentMessagePaint = Theme.dialogs_messagePrintingPaint[paintIndex];
String innerMessage;
@ -1096,17 +1122,17 @@ public class DialogCell extends BaseCell {
innerMessage = String.format("\uD83C\uDFA7 %s - %s", message.getMusicAuthor(), message.getMusicTitle());
}
} else {
innerMessage = message.messageText.toString();
innerMessage = msgText.toString();
}
innerMessage = innerMessage.replace('\n', ' ');
stringBuilder = SpannableStringBuilder.valueOf(String.format(messageFormat, innerMessage, messageNameString));
stringBuilder = AndroidUtilities.formatSpannable(messageFormat, innerMessage, messageNameString);
try {
stringBuilder.setSpan(new ForegroundColorSpanThemable(Theme.key_chats_attachMessage, resourcesProvider), hasNameInMessage ? messageNameString.length() + 2 : 0, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} catch (Exception e) {
FileLog.e(e);
}
} else if (message.messageOwner.message != null) {
String mess = message.messageOwner.message;
CharSequence mess = message.messageOwner.message;
if (message.hasHighlightedWords()) {
if (message.messageTrimmedToHighlight != null) {
mess = message.messageTrimmedToHighlight;
@ -1123,11 +1149,13 @@ public class DialogCell extends BaseCell {
}
} else {
if (mess.length() > 150) {
mess = mess.substring(0, 150);
mess = mess.subSequence(0, 150);
}
mess = mess.replace('\n', ' ').trim();
mess = AndroidUtilities.replaceNewLines(mess);
}
stringBuilder = SpannableStringBuilder.valueOf(String.format(messageFormat, mess, messageNameString));
mess = new SpannableStringBuilder(mess);
MediaDataController.addTextStyleRuns(message, (Spannable) mess);
stringBuilder = AndroidUtilities.formatSpannable(messageFormat, mess, messageNameString);
} else {
stringBuilder = SpannableStringBuilder.valueOf("");
}
@ -1195,7 +1223,9 @@ public class DialogCell extends BaseCell {
}
messageString = emoji + str;
} else {
messageString = emoji + message.caption;
SpannableStringBuilder msgBuilder = new SpannableStringBuilder(message.caption);
MediaDataController.addTextStyleRuns(message.messageOwner.entities, message.caption, msgBuilder);
messageString = new SpannableStringBuilder(emoji).append(msgBuilder);
}
} else {
if (message.messageOwner.media instanceof TLRPC.TL_messageMediaPoll) {
@ -1216,7 +1246,9 @@ public class DialogCell extends BaseCell {
int w = getMeasuredWidth() - AndroidUtilities.dp(72 + 23 );
messageString = AndroidUtilities.ellipsizeCenterEnd(messageString, message.highlightedWords.get(0), w, currentMessagePaint, 130).toString();
} else {
messageString = message.messageText;
SpannableStringBuilder stringBuilder = new SpannableStringBuilder(msgText);
MediaDataController.addTextStyleRuns(message, stringBuilder);
messageString = stringBuilder;
}
AndroidUtilities.highlightText(messageString, message.highlightedWords, resourcesProvider);
}
@ -1602,14 +1634,14 @@ public class DialogCell extends BaseCell {
if (messageString == null) {
messageString = "";
}
String mess = messageString.toString();
CharSequence mess = messageString;
if (mess.length() > 150) {
mess = mess.substring(0, 150);
mess = mess.subSequence(0, 150);
}
if (!useForceThreeLines && !SharedConfig.useThreeLinesLayout || messageNameString != null) {
mess = mess.replace('\n', ' ');
mess = AndroidUtilities.replaceNewLines(mess);
} else {
mess = mess.replace("\n\n", "\n");
mess = AndroidUtilities.replaceTwoNewLinesToOne(mess);
}
messageString = Emoji.replaceEmoji(mess, Theme.dialogs_messagePaint[paintIndex].getFontMetricsInt(), AndroidUtilities.dp(17), false);
if (message != null) {
@ -1656,6 +1688,7 @@ public class DialogCell extends BaseCell {
} else {
messageStringFinal = messageString;
}
if (useForceThreeLines || SharedConfig.useThreeLinesLayout) {
if (hasMessageThumb && messageNameString != null) {
messageWidth += AndroidUtilities.dp(6);
@ -1672,6 +1705,9 @@ public class DialogCell extends BaseCell {
messageStringFinal = Emoji.replaceEmoji(messageStringFinal,currentMessagePaint.getFontMetricsInt(), AndroidUtilities.dp(12), false);
messageLayout = new StaticLayout(messageStringFinal, currentMessagePaint, messageWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
}
spoilersPool.addAll(spoilers);
spoilers.clear();
SpoilerEffect.addSpoilers(this, messageLayout, spoilersPool, spoilers);
} catch (Exception e) {
messageLayout = null;
FileLog.e(e);
@ -2609,7 +2645,15 @@ public class DialogCell extends BaseCell {
canvas.save();
canvas.translate(messageLeft, messageTop);
try {
canvas.save();
SpoilerEffect.clipOutCanvas(canvas, spoilers);
messageLayout.draw(canvas);
canvas.restore();
for (SpoilerEffect eff : spoilers) {
eff.setColor(messageLayout.getPaint().getColor());
eff.draw(canvas);
}
} catch (Exception e) {
FileLog.e(e);
}

View File

@ -16,6 +16,7 @@ import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.TextView;
@ -97,4 +98,11 @@ public class DrawerActionCell extends FrameLayout {
FileLog.e(e);
}
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
}
}

View File

@ -253,7 +253,7 @@ public class DrawerProfileCell extends FrameLayout {
addView(darkThemeView, LayoutHelper.createFrame(48, 48, Gravity.RIGHT | Gravity.BOTTOM, 0, 10, 6, 90));
if (Theme.getEventType() == 0 || NekomuraConfig.actionBarDecoration.Int() == 1) {
snowflakesEffect = new SnowflakesEffect();
snowflakesEffect = new SnowflakesEffect(0);
snowflakesEffect.setColorKey(Theme.key_chats_menuName);
} else if (NekomuraConfig.actionBarDecoration.Int() == 2) {
fireworksEffect = new FireworksEffect();

View File

@ -14,6 +14,7 @@ import android.graphics.RectF;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.TextView;
@ -124,4 +125,10 @@ public class DrawerUserCell extends FrameLayout {
canvas.drawText(text, rect.left + (rect.width() - textWidth) / 2, countTop + AndroidUtilities.dp(16), Theme.dialogs_countTextPaint);
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}

View File

@ -106,6 +106,16 @@ public class SessionCell extends FrameLayout {
linearLayout.addView(onlineTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.RIGHT | Gravity.TOP, 0, 2, 0, 0));
}
int leftMargin;
int rightMargin;
if (LocaleController.isRTL) {
rightMargin = type == 0 ? 72 : 21;
leftMargin = 21;
} else {
leftMargin = type == 0 ? 72 : 21;
rightMargin = 21;
}
detailTextView = new TextView(context);
detailTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
detailTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
@ -114,7 +124,7 @@ public class SessionCell extends FrameLayout {
//detailTextView.setSingleLine(true);
//detailTextView.setEllipsize(TextUtils.TruncateAt.END);
detailTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP);
addView(detailTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, type == 0 ? 72 : 21, 36, 21, 0));
addView(detailTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, leftMargin, 36, rightMargin, 0));
detailExTextView = new TextView(context);
detailExTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText3));
@ -124,7 +134,7 @@ public class SessionCell extends FrameLayout {
detailExTextView.setSingleLine(true);
detailExTextView.setEllipsize(TextUtils.TruncateAt.END);
detailExTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP);
addView(detailExTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, type == 0 ? 72 : 21, 59, 21, 0));
addView(detailExTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, leftMargin, 59, rightMargin, 0));
}
@Override
@ -137,11 +147,7 @@ public class SessionCell extends FrameLayout {
if (object instanceof TLRPC.TL_authorization) {
TLRPC.TL_authorization session = (TLRPC.TL_authorization) object;
imageView.setImageDrawable(createDrawable(session));
// nameTextView.setText(String.format(Locale.US, "%s %s", session.app_name, session.app_version));
StringBuilder stringBuilder = new StringBuilder();
if (session.device_model.length() != 0) {

View File

@ -30,7 +30,7 @@ public class SettingsSearchCell extends FrameLayout {
private boolean needDivider;
private int left;
public class VerticalImageSpan extends ImageSpan {
public static class VerticalImageSpan extends ImageSpan {
public VerticalImageSpan(Drawable drawable) {
super(drawable);

View File

@ -12,11 +12,18 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.Region;
import android.net.Uri;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
@ -29,6 +36,7 @@ import org.telegram.messenger.Emoji;
import org.telegram.messenger.ImageLocation;
import org.telegram.messenger.ImageReceiver;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MediaDataController;
import org.telegram.messenger.MessageObject;
import org.telegram.messenger.FileLoader;
import org.telegram.messenger.FileLog;
@ -38,14 +46,22 @@ import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.LetterDrawable;
import org.telegram.ui.Components.LinkPath;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Components.TextStyleSpan;
import org.telegram.ui.Components.spoilers.SpoilerEffect;
import org.telegram.ui.FilteredSearchView;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicReference;
import tw.nekomimi.nkmr.NekomuraConfig;
public class SharedLinkCell extends FrameLayout {
private final static int SPOILER_TYPE_LINK = 0,
SPOILER_TYPE_DESCRIPTION = 1,
SPOILER_TYPE_DESCRIPTION2 = 2;
public interface SharedLinkCellDelegate {
void needOpenWebView(TLRPC.WebPage webPage, MessageObject messageObject);
@ -78,7 +94,7 @@ public class SharedLinkCell extends FrameLayout {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
if (pressedLink >= 0) {
delegate.onLinkPress(links.get(pressedLink), true);
delegate.onLinkPress(links.get(pressedLink).toString(), true);
}
MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
onTouchEvent(event);
@ -121,18 +137,27 @@ public class SharedLinkCell extends FrameLayout {
private boolean needDivider;
ArrayList<String> links = new ArrayList<>();
ArrayList<CharSequence> links = new ArrayList<>();
private int linkY;
private ArrayList<StaticLayout> linkLayout = new ArrayList<>();
private SparseArray<List<SpoilerEffect>> linkSpoilers = new SparseArray<>();
private List<SpoilerEffect> descriptionLayoutSpoilers = new ArrayList<>();
private List<SpoilerEffect> descriptionLayout2Spoilers = new ArrayList<>();
private Stack<SpoilerEffect> spoilersPool = new Stack<>();
private Path path = new Path();
private SpoilerEffect spoilerPressed;
private int spoilerTypePressed = -1;
private int titleY = AndroidUtilities.dp(10);
private StaticLayout titleLayout;
private int descriptionY = AndroidUtilities.dp(30);
private StaticLayout descriptionLayout;
private AtomicReference<Layout> patchedDescriptionLayout = new AtomicReference<>();
private int description2Y = AndroidUtilities.dp(30);
private StaticLayout descriptionLayout2;
private AtomicReference<Layout> patchedDescriptionLayout2 = new AtomicReference<>();
private int captionY = AndroidUtilities.dp(30);
private StaticLayout captionLayout;
@ -209,8 +234,8 @@ public class SharedLinkCell extends FrameLayout {
int maxWidth = MeasureSpec.getSize(widthMeasureSpec) - AndroidUtilities.dp(AndroidUtilities.leftBaseline) - AndroidUtilities.dp(8);
String title = null;
String description = null;
String description2 = null;
CharSequence description = null;
CharSequence description2 = null;
String webPageLink = null;
boolean hasPhoto = false;
@ -238,14 +263,18 @@ public class SharedLinkCell extends FrameLayout {
if (a == 0 && webPageLink != null && !(entity.offset == 0 && entity.length == message.messageOwner.message.length())) {
if (message.messageOwner.entities.size() == 1) {
if (description == null) {
description2 = message.messageOwner.message;
SpannableStringBuilder st = SpannableStringBuilder.valueOf(message.messageOwner.message);
MediaDataController.addTextStyleRuns(message, st);
description2 = st;
}
} else {
description2 = message.messageOwner.message;
SpannableStringBuilder st = SpannableStringBuilder.valueOf(message.messageOwner.message);
MediaDataController.addTextStyleRuns(message, st);
description2 = st;
}
}
try {
String link = null;
CharSequence link = null;
if (entity instanceof TLRPC.TL_messageEntityTextUrl || entity instanceof TLRPC.TL_messageEntityUrl) {
if (entity instanceof TLRPC.TL_messageEntityUrl) {
link = message.messageOwner.message.substring(entity.offset, entity.offset + entity.length);
@ -253,11 +282,11 @@ public class SharedLinkCell extends FrameLayout {
link = entity.url;
}
if (title == null || title.length() == 0) {
title = link;
title = link.toString();
Uri uri = Uri.parse(title);
title = uri.getHost();
if (title == null) {
title = link;
title = link.toString();
}
int index;
if (title != null && (index = title.lastIndexOf('.')) >= 0) {
@ -268,7 +297,9 @@ public class SharedLinkCell extends FrameLayout {
title = title.substring(0, 1).toUpperCase() + title.substring(1);
}
if (entity.offset != 0 || entity.length != message.messageOwner.message.length()) {
description = message.messageOwner.message;
SpannableStringBuilder st = SpannableStringBuilder.valueOf(message.messageOwner.message);
MediaDataController.addTextStyleRuns(message, st);
description = st;
}
}
} else if (entity instanceof TLRPC.TL_messageEntityEmail) {
@ -276,16 +307,33 @@ public class SharedLinkCell extends FrameLayout {
link = "mailto:" + message.messageOwner.message.substring(entity.offset, entity.offset + entity.length);
title = message.messageOwner.message.substring(entity.offset, entity.offset + entity.length);
if (entity.offset != 0 || entity.length != message.messageOwner.message.length()) {
description = message.messageOwner.message;
SpannableStringBuilder st = SpannableStringBuilder.valueOf(message.messageOwner.message);
MediaDataController.addTextStyleRuns(message, st);
description = st;
}
}
}
if (link != null) {
if (!link.contains("://") && link.toLowerCase().indexOf("http") != 0 && link.toLowerCase().indexOf("mailto") != 0) {
links.add("http://" + link);
CharSequence lobj;
int offset = 0;
if (!AndroidUtilities.charSequenceContains(link, "://") && link.toString().toLowerCase().indexOf("http") != 0 && link.toString().toLowerCase().indexOf("mailto") != 0) {
String prefix = "http://";
lobj = prefix + link;
offset += prefix.length();
} else {
links.add(link);
lobj = link;
}
SpannableString sb = SpannableString.valueOf(lobj);
int start = entity.offset, end = entity.offset + entity.length;
for (TLRPC.MessageEntity e : message.messageOwner.entities) {
int ss = e.offset, se = e.offset + e.length;
if (e instanceof TLRPC.TL_messageEntitySpoiler && start <= se && end >= ss) {
TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun();
run.flags |= TextStyleSpan.FLAG_STYLE_SPOILER;
sb.setSpan(new TextStyleSpan(run), Math.max(start, ss), Math.min(end, se) + offset, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
links.add(sb);
}
} catch (Exception e) {
FileLog.e(e);
@ -336,6 +384,10 @@ public class SharedLinkCell extends FrameLayout {
if (descriptionLayout.getLineCount() > 0) {
description2Y = descriptionY + descriptionLayout.getLineBottom(descriptionLayout.getLineCount() - 1) + AndroidUtilities.dp(5);
}
spoilersPool.addAll(descriptionLayoutSpoilers);
descriptionLayoutSpoilers.clear();
if (!message.isSpoilersRevealed)
SpoilerEffect.addSpoilers(this, descriptionLayout, spoilersPool, descriptionLayoutSpoilers);
} catch (Exception e) {
FileLog.e(e);
}
@ -347,6 +399,10 @@ public class SharedLinkCell extends FrameLayout {
if (descriptionLayout != null) {
description2Y += AndroidUtilities.dp(10);
}
spoilersPool.addAll(descriptionLayout2Spoilers);
descriptionLayout2Spoilers.clear();
if (!message.isSpoilersRevealed)
SpoilerEffect.addSpoilers(this, descriptionLayout2, spoilersPool, descriptionLayout2Spoilers);
} catch (Exception e) {
FileLog.e(e);
}
@ -368,16 +424,25 @@ public class SharedLinkCell extends FrameLayout {
}
if (!links.isEmpty()) {
for (int i = 0; i < linkSpoilers.size(); i++)
spoilersPool.addAll(linkSpoilers.get(i));
linkSpoilers.clear();
for (int a = 0; a < links.size(); a++) {
try {
String link = links.get(a);
int width = (int) Math.ceil(descriptionTextPaint.measureText(link));
CharSequence linkFinal = TextUtils.ellipsize(link.replace('\n', ' '), descriptionTextPaint, Math.min(width, maxWidth), TextUtils.TruncateAt.MIDDLE);
CharSequence link = links.get(a);
int width = (int) Math.ceil(descriptionTextPaint.measureText(link, 0, link.length()));
CharSequence linkFinal = TextUtils.ellipsize(AndroidUtilities.replaceNewLines(SpannableStringBuilder.valueOf(link)), descriptionTextPaint, Math.min(width, maxWidth), TextUtils.TruncateAt.MIDDLE);
StaticLayout layout = new StaticLayout(linkFinal, descriptionTextPaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
linkY = description2Y;
if (descriptionLayout2 != null && descriptionLayout2.getLineCount() != 0) {
linkY += descriptionLayout2.getLineBottom(descriptionLayout2.getLineCount() - 1) + AndroidUtilities.dp(5);
}
if (!message.isSpoilersRevealed) {
List<SpoilerEffect> l = new ArrayList<>();
if (linkFinal instanceof Spannable)
SpoilerEffect.addSpoilers(this, layout, (Spannable) linkFinal, spoilersPool, l);
linkSpoilers.put(a, l);
}
linkLayout.add(layout);
} catch (Exception e) {
FileLog.e(e);
@ -484,7 +549,7 @@ public class SharedLinkCell extends FrameLayout {
public boolean onTouchEvent(MotionEvent event) {
boolean result = false;
if (message != null && !linkLayout.isEmpty() && delegate != null && delegate.canPerformActions()) {
if (event.getAction() == MotionEvent.ACTION_DOWN || linkPreviewPressed && event.getAction() == MotionEvent.ACTION_UP) {
if (event.getAction() == MotionEvent.ACTION_DOWN || (linkPreviewPressed || spoilerPressed != null) && event.getAction() == MotionEvent.ACTION_UP) {
int x = (int) event.getX();
int y = (int) event.getY();
int offset = 0;
@ -498,35 +563,84 @@ public class SharedLinkCell extends FrameLayout {
ok = true;
if (event.getAction() == MotionEvent.ACTION_DOWN) {
resetPressedLink();
pressedLink = a;
linkPreviewPressed = true;
startCheckLongPress();
try {
urlPath.setCurrentLayout(layout, 0, 0);
layout.getSelectionPath(0, layout.getText().length(), urlPath);
} catch (Exception e) {
FileLog.e(e);
spoilerPressed = null;
if (linkSpoilers.get(a, null) != null) {
for (SpoilerEffect eff : linkSpoilers.get(a)) {
if (eff.getBounds().contains(x - linkPosX, y - linkY - offset)) {
spoilerPressed = eff;
spoilerTypePressed = SPOILER_TYPE_LINK;
break;
}
}
}
if (spoilerPressed != null) {
result = true;
} else {
pressedLink = a;
linkPreviewPressed = true;
startCheckLongPress();
try {
urlPath.setCurrentLayout(layout, 0, 0);
layout.getSelectionPath(0, layout.getText().length(), urlPath);
} catch (Exception e) {
FileLog.e(e);
}
result = true;
}
result = true;
} else if (linkPreviewPressed) {
try {
TLRPC.WebPage webPage = pressedLink == 0 && message.messageOwner.media != null ? message.messageOwner.media.webpage : null;
if (webPage != null && webPage.embed_url != null && webPage.embed_url.length() != 0) {
delegate.needOpenWebView(webPage, message);
} else {
delegate.onLinkPress(links.get(pressedLink), false);
delegate.onLinkPress(links.get(pressedLink).toString(), false);
}
} catch (Exception e) {
FileLog.e(e);
}
resetPressedLink();
result = true;
} else if (spoilerPressed != null) {
startSpoilerRipples(x, y, offset);
result = true;
}
break;
}
offset += height;
}
}
if (event.getAction() == MotionEvent.ACTION_DOWN) {
int offX = AndroidUtilities.dp(LocaleController.isRTL ? 8 : AndroidUtilities.leftBaseline);
if (descriptionLayout != null && x >= offX && x <= offX + descriptionLayout.getWidth() && y >= descriptionY && y <= descriptionY + descriptionLayout.getHeight()) {
for (SpoilerEffect eff : descriptionLayoutSpoilers) {
if (eff.getBounds().contains(x - offX, y - descriptionY)) {
spoilerPressed = eff;
spoilerTypePressed = SPOILER_TYPE_DESCRIPTION;
ok = true;
result = true;
break;
}
}
}
if (descriptionLayout2 != null && x >= offX && x <= offX + descriptionLayout2.getWidth() && y >= description2Y && y <= description2Y + descriptionLayout2.getHeight()) {
for (SpoilerEffect eff : descriptionLayout2Spoilers) {
if (eff.getBounds().contains(x - offX, y - description2Y)) {
spoilerPressed = eff;
spoilerTypePressed = SPOILER_TYPE_DESCRIPTION2;
ok = true;
result = true;
break;
}
}
}
} else if (event.getAction() == MotionEvent.ACTION_UP && spoilerPressed != null) {
startSpoilerRipples(x, y, 0);
ok = true;
result = true;
}
if (!ok) {
resetPressedLink();
}
@ -539,11 +653,85 @@ public class SharedLinkCell extends FrameLayout {
return result || super.onTouchEvent(event);
}
private void startSpoilerRipples(int x, int y, int offset) {
int linkPosX = AndroidUtilities.dp(LocaleController.isRTL ? 8 : AndroidUtilities.leftBaseline);
resetPressedLink();
SpoilerEffect eff = spoilerPressed;
eff.setOnRippleEndCallback(() -> post(() -> {
message.isSpoilersRevealed = true;
linkSpoilers.clear();
descriptionLayoutSpoilers.clear();
descriptionLayout2Spoilers.clear();
invalidate();
}));
int nx = x - linkPosX;
float rad = (float) Math.sqrt(Math.pow(getWidth(), 2) + Math.pow(getHeight(), 2));
float offY = 0;
switch (spoilerTypePressed) {
case SPOILER_TYPE_LINK:
for (int i = 0; i < linkLayout.size(); i++) {
Layout lt = linkLayout.get(i);
offY += lt.getLineBottom(lt.getLineCount() - 1);
for (SpoilerEffect e : linkSpoilers.get(i)) {
e.startRipple(nx, y - getYOffsetForType(SPOILER_TYPE_LINK) - offset + offY, rad);
}
}
break;
case SPOILER_TYPE_DESCRIPTION:
for (SpoilerEffect sp : descriptionLayoutSpoilers)
sp.startRipple(nx, y - getYOffsetForType(SPOILER_TYPE_DESCRIPTION), rad);
break;
case SPOILER_TYPE_DESCRIPTION2:
for (SpoilerEffect sp : descriptionLayout2Spoilers)
sp.startRipple(nx, y - getYOffsetForType(SPOILER_TYPE_DESCRIPTION2), rad);
break;
}
for (int i = SPOILER_TYPE_LINK; i <= SPOILER_TYPE_DESCRIPTION2; i++) {
if (i != spoilerTypePressed) {
switch (i) {
case SPOILER_TYPE_LINK:
for (int j = 0; j < linkLayout.size(); j++) {
Layout lt = linkLayout.get(j);
offY += lt.getLineBottom(lt.getLineCount() - 1);
for (SpoilerEffect e : linkSpoilers.get(j)) {
e.startRipple(e.getBounds().centerX(), e.getBounds().centerY(), rad);
}
}
break;
case SPOILER_TYPE_DESCRIPTION:
for (SpoilerEffect sp : descriptionLayoutSpoilers)
sp.startRipple(sp.getBounds().centerX(), sp.getBounds().centerY(), rad);
break;
case SPOILER_TYPE_DESCRIPTION2:
for (SpoilerEffect sp : descriptionLayout2Spoilers)
sp.startRipple(sp.getBounds().centerX(), sp.getBounds().centerY(), rad);
break;
}
}
}
spoilerTypePressed = -1;
spoilerPressed = null;
}
private int getYOffsetForType(int type) {
switch (type) {
default:
case SPOILER_TYPE_LINK:
return linkY;
case SPOILER_TYPE_DESCRIPTION:
return descriptionY;
case SPOILER_TYPE_DESCRIPTION2:
return description2Y;
}
}
public String getLink(int num) {
if (num < 0 || num >= links.size()) {
return null;
}
return links.get(num);
return links.get(num).toString();
}
protected void resetPressedLink() {
@ -593,7 +781,7 @@ public class SharedLinkCell extends FrameLayout {
descriptionTextPaint.setColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
canvas.save();
canvas.translate(AndroidUtilities.dp(LocaleController.isRTL ? 8 : AndroidUtilities.leftBaseline), descriptionY);
descriptionLayout.draw(canvas);
SpoilerEffect.renderWithRipple(this, false, descriptionTextPaint.getColor(), -AndroidUtilities.dp(2), patchedDescriptionLayout, descriptionLayout, descriptionLayoutSpoilers, canvas);
canvas.restore();
}
@ -601,7 +789,7 @@ public class SharedLinkCell extends FrameLayout {
descriptionTextPaint.setColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
canvas.save();
canvas.translate(AndroidUtilities.dp(LocaleController.isRTL ? 8 : AndroidUtilities.leftBaseline), description2Y);
descriptionLayout2.draw(canvas);
SpoilerEffect.renderWithRipple(this, false, descriptionTextPaint.getColor(), -AndroidUtilities.dp(2), patchedDescriptionLayout2, descriptionLayout2, descriptionLayout2Spoilers, canvas);
canvas.restore();
}
@ -610,14 +798,39 @@ public class SharedLinkCell extends FrameLayout {
int offset = 0;
for (int a = 0; a < linkLayout.size(); a++) {
StaticLayout layout = linkLayout.get(a);
List<SpoilerEffect> spoilers = linkSpoilers.get(a);
if (layout.getLineCount() > 0) {
canvas.save();
canvas.translate(AndroidUtilities.dp(LocaleController.isRTL ? 8 : AndroidUtilities.leftBaseline), linkY + offset);
if (pressedLink == a) {
canvas.drawPath(urlPath, Theme.linkSelectionPaint);
path.rewind();
if (spoilers != null) {
for (SpoilerEffect eff : spoilers) {
Rect b = eff.getBounds();
path.addRect(b.left, b.top, b.right, b.bottom, Path.Direction.CW);
}
}
canvas.save();
canvas.clipPath(path, Region.Op.DIFFERENCE);
if (pressedLink == a) canvas.drawPath(urlPath, Theme.linkSelectionPaint);
layout.draw(canvas);
canvas.restore();
canvas.save();
canvas.clipPath(path);
path.rewind();
if (spoilers != null && !spoilers.isEmpty())
spoilers.get(0).getRipplePath(path);
canvas.clipPath(path);
if (pressedLink == a) canvas.drawPath(urlPath, Theme.linkSelectionPaint);
layout.draw(canvas);
canvas.restore();
if (spoilers != null)
for (SpoilerEffect eff : spoilers) eff.draw(canvas);
canvas.restore();
offset += layout.getLineBottom(layout.getLineCount() - 1);
}
}

View File

@ -252,6 +252,7 @@ public class TextCell extends FrameLayout {
info.setText(text);
}
}
info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
}
public void setNeedDivider(boolean needDivider) {

View File

@ -18,6 +18,7 @@ import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ColorDrawable;
import android.text.TextUtils;
import android.util.Property;
import android.util.TypedValue;
@ -42,6 +43,7 @@ import org.telegram.ui.Components.ViewHelper;
import java.util.ArrayList;
public class TextCheckCell extends FrameLayout {
private boolean isAnimatingToThumbInsteadOfTouch;
private TextView textView;
private TextView valueTextView;
@ -129,6 +131,11 @@ public class TextCheckCell extends FrameLayout {
return super.onTouchEvent(event);
}
public void setDivider(boolean divider) {
needDivider = divider;
setWillNotDraw(!divider);
}
public void setTextAndCheck(String text, boolean checked, boolean divider) {
textView.setText(text);
isMultiline = false;
@ -275,18 +282,50 @@ public class TextCheckCell extends FrameLayout {
private void setAnimationProgress(float value) {
animationProgress = value;
float rad = Math.max(lastTouchX, getMeasuredWidth() - lastTouchX) + AndroidUtilities.dp(40);
float cx = lastTouchX;
float tx = getLastTouchX();
float rad = Math.max(tx, getMeasuredWidth() - tx) + AndroidUtilities.dp(40);
float cx = tx;
int cy = getMeasuredHeight() / 2;
float animatedRad = rad * animationProgress;
checkBox.setOverrideColorProgress(cx, cy, animatedRad);
}
public void setBackgroundColorAnimatedReverse(int color) {
if (animator != null) {
animator.cancel();
animator = null;
}
int from = animatedColorBackground != 0 ? animatedColorBackground : getBackground() instanceof ColorDrawable ? ((ColorDrawable) getBackground()).getColor() : 0;
if (animationPaint == null) animationPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
animationPaint.setColor(from);
setBackgroundColor(color);
checkBox.setOverrideColor(1);
animatedColorBackground = color;
animator = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS, 1, 0).setDuration(240);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
setBackgroundColor(color);
animatedColorBackground = 0;
invalidate();
}
});
animator.setInterpolator(CubicBezierInterpolator.EASE_OUT);
animator.start();
}
private float getLastTouchX() {
return isAnimatingToThumbInsteadOfTouch ? (LocaleController.isRTL ? AndroidUtilities.dp(22) : getMeasuredWidth() - AndroidUtilities.dp(42)) : lastTouchX;
}
@Override
protected void onDraw(Canvas canvas) {
if (animatedColorBackground != 0) {
float rad = Math.max(lastTouchX, getMeasuredWidth() - lastTouchX) + AndroidUtilities.dp(40);
float cx = lastTouchX;
float tx = getLastTouchX();
float rad = Math.max(tx, getMeasuredWidth() - tx) + AndroidUtilities.dp(40);
float cx = tx;
int cy = getMeasuredHeight() / 2;
float animatedRad = rad * animationProgress;
canvas.drawCircle(cx, cy, animatedRad, animationPaint);
@ -296,6 +335,10 @@ public class TextCheckCell extends FrameLayout {
}
}
public void setAnimatingToThumbInsteadOfTouch(boolean animatingToThumbInsteadOfTouch) {
isAnimatingToThumbInsteadOfTouch = animatingToThumbInsteadOfTouch;
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
@ -304,4 +347,4 @@ public class TextCheckCell extends FrameLayout {
info.setChecked(isChecked());
info.setContentDescription(isChecked() ? LocaleController.getString("NotificationsOn", R.string.NotificationsOn) : LocaleController.getString("NotificationsOff", R.string.NotificationsOff));
}
}
}

View File

@ -0,0 +1,300 @@
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.Typeface;
import android.text.TextUtils;
import android.util.Property;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
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.CheckBox2;
import org.telegram.ui.Components.CubicBezierInterpolator;
import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.Switch;
import java.util.ArrayList;
public class TextCheckbox2Cell extends FrameLayout {
private TextView textView;
private TextView valueTextView;
public CheckBox2 checkbox;
private boolean needDivider;
private boolean isMultiline;
private int height = 50;
private int animatedColorBackground;
private float animationProgress;
private Paint animationPaint;
private float lastTouchX;
private ObjectAnimator animator;
private boolean drawCheckRipple;
public static final Property<TextCheckbox2Cell, Float> ANIMATION_PROGRESS = new AnimationProperties.FloatProperty<TextCheckbox2Cell>("animationProgress") {
@Override
public void setValue(TextCheckbox2Cell object, float value) {
object.setAnimationProgress(value);
object.invalidate();
}
@Override
public Float get(TextCheckbox2Cell object) {
return object.animationProgress;
}
};
public TextCheckbox2Cell(Context context) {
this(context, 21);
}
public TextCheckbox2Cell(Context context, int padding) {
this(context, padding, false);
}
public TextCheckbox2Cell(Context context, int padding, boolean dialog) {
super(context);
textView = new TextView(context);
textView.setTextColor(Theme.getColor(dialog ? Theme.key_dialogTextBlack : Theme.key_windowBackgroundWhiteBlackText));
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
textView.setLines(1);
textView.setMaxLines(1);
textView.setSingleLine(true);
textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL);
textView.setEllipsize(TextUtils.TruncateAt.END);
addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? padding : 64, 0, LocaleController.isRTL ? 64 : padding, 0));
valueTextView = new TextView(context);
valueTextView.setTextColor(Theme.getColor(dialog ? Theme.key_dialogIcon : Theme.key_windowBackgroundWhiteGrayText2));
valueTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13);
valueTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT);
valueTextView.setLines(1);
valueTextView.setMaxLines(1);
valueTextView.setSingleLine(true);
valueTextView.setPadding(0, 0, 0, 0);
valueTextView.setEllipsize(TextUtils.TruncateAt.END);
addView(valueTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? padding : 64, 36, LocaleController.isRTL ? 64 : padding, 0));
checkbox = new CheckBox2(context, 21);
checkbox.setDrawUnchecked(true);
checkbox.setDrawBackgroundAsArc(10);
checkbox.setDuration(100);
checkbox.setColor(Theme.key_radioBackgroundChecked, Theme.key_checkboxDisabled, Theme.key_checkboxCheck);
addView(checkbox, LayoutHelper.createFrame(20, 20, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL, 22, 0, 22, 0));
setClipChildren(false);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (isMultiline) {
super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
} else {
super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(valueTextView.getVisibility() == VISIBLE ? 64 : height) + (needDivider ? 1 : 0), 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);
isMultiline = false;
checkbox.setChecked(checked, false);
needDivider = divider;
valueTextView.setVisibility(GONE);
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 setTextAndValue(String text, String value, boolean multiline, boolean divider) {
textView.setText(text);
valueTextView.setText(value);
// checkbox.setChecked(checked, false);
needDivider = divider;
valueTextView.setVisibility(VISIBLE);
isMultiline = multiline;
if (multiline) {
valueTextView.setLines(0);
valueTextView.setMaxLines(0);
valueTextView.setSingleLine(false);
valueTextView.setEllipsize(null);
valueTextView.setPadding(0, 0, 0, AndroidUtilities.dp(11));
} else {
valueTextView.setLines(1);
valueTextView.setMaxLines(1);
valueTextView.setSingleLine(true);
valueTextView.setEllipsize(TextUtils.TruncateAt.END);
valueTextView.setPadding(0, 0, 0, 0);
}
LayoutParams layoutParams = (LayoutParams) textView.getLayoutParams();
layoutParams.height = LayoutParams.WRAP_CONTENT;
layoutParams.topMargin = AndroidUtilities.dp(10);
textView.setLayoutParams(layoutParams);
setWillNotDraw(!divider);
}
public void setTextAndValueAndCheck(String text, String value, boolean checked, boolean multiline, boolean divider) {
textView.setText(text);
valueTextView.setText(value);
checkbox.setChecked(checked, false);
needDivider = divider;
valueTextView.setVisibility(VISIBLE);
isMultiline = multiline;
if (multiline) {
valueTextView.setLines(0);
valueTextView.setMaxLines(0);
valueTextView.setSingleLine(false);
valueTextView.setEllipsize(null);
valueTextView.setPadding(0, 0, 0, AndroidUtilities.dp(11));
} else {
valueTextView.setLines(1);
valueTextView.setMaxLines(1);
valueTextView.setSingleLine(true);
valueTextView.setEllipsize(TextUtils.TruncateAt.END);
valueTextView.setPadding(0, 0, 0, 0);
}
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));
if (valueTextView.getVisibility() == VISIBLE) {
animators.add(ObjectAnimator.ofFloat(valueTextView, "alpha", value ? 1.0f : 0.5f));
}
} else {
textView.setAlpha(value ? 1.0f : 0.5f);
checkbox.setAlpha(value ? 1.0f : 0.5f);
if (valueTextView.getVisibility() == VISIBLE) {
valueTextView.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);
}
if (needDivider) {
canvas.drawLine(LocaleController.isRTL ? 0 : AndroidUtilities.dp(64), getMeasuredHeight() - 1, getMeasuredWidth() - (LocaleController.isRTL ? AndroidUtilities.dp(64) : 0), getMeasuredHeight() - 1, Theme.dividerPaint);
}
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName("android.widget.checkbox");
info.setCheckable(true);
info.setChecked(checkbox.isChecked());
info.setContentDescription(checkbox.isChecked() ? LocaleController.getString("NotificationsOn", R.string.NotificationsOn) : LocaleController.getString("NotificationsOff", R.string.NotificationsOff));
}
}

View File

@ -10,11 +10,15 @@ package org.telegram.ui.Cells;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import org.telegram.messenger.AndroidUtilities;
@ -26,8 +30,9 @@ import org.telegram.ui.Components.LayoutHelper;
public class TextDetailCell extends FrameLayout {
private TextView textView;
private TextView valueTextView;
private final TextView textView;
private final TextView valueTextView;
private final ImageView imageView;
private boolean needDivider;
private boolean contentDescriptionValueFirst;
@ -54,6 +59,10 @@ public class TextDetailCell extends FrameLayout {
valueTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT);
valueTextView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
addView(valueTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 23, 33, 23, 0));
imageView = new ImageView(context);
imageView.setScaleType(ImageView.ScaleType.CENTER);
addView(imageView, LayoutHelper.createFrameRelatively(48, 48, Gravity.END | Gravity.CENTER_VERTICAL, 0, 0, 12, 0));
}
@Override
@ -68,6 +77,26 @@ public class TextDetailCell extends FrameLayout {
setWillNotDraw(!needDivider);
}
public void setImage(Drawable drawable) {
imageView.setImageDrawable(drawable);
if (drawable == null) {
imageView.setBackground(null);
} else {
imageView.setBackground(Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(48), Color.TRANSPARENT, Theme.getColor(Theme.key_listSelector)));
}
int margin = AndroidUtilities.dp(23) + (drawable == null ? 0 : AndroidUtilities.dp(48));
if (LocaleController.isRTL) {
((MarginLayoutParams) textView.getLayoutParams()).leftMargin = margin;
} else {
((MarginLayoutParams) textView.getLayoutParams()).rightMargin = margin;
}
textView.requestLayout();
}
public void setImageClickListener(View.OnClickListener clickListener) {
imageView.setOnClickListener(clickListener);
}
public void setTextWithEmojiAndValue(String text, CharSequence value, boolean divider) {
textView.setText(Emoji.replaceEmoji(text, textView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(14), false));
valueTextView.setText(value);

View File

@ -0,0 +1,272 @@
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.Typeface;
import android.text.TextUtils;
import android.util.Property;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
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.RadioButton;
import org.telegram.ui.Components.Switch;
import java.util.ArrayList;
public class TextRadioCell extends FrameLayout {
private TextView textView;
private TextView valueTextView;
private RadioButton radioButton;
private boolean needDivider;
private boolean isMultiline;
private int height = 50;
private int animatedColorBackground;
private float animationProgress;
private Paint animationPaint;
private float lastTouchX;
private ObjectAnimator animator;
private boolean drawCheckRipple;
public static final Property<TextRadioCell, Float> ANIMATION_PROGRESS = new AnimationProperties.FloatProperty<TextRadioCell>("animationProgress") {
@Override
public void setValue(TextRadioCell object, float value) {
object.setAnimationProgress(value);
object.invalidate();
}
@Override
public Float get(TextRadioCell object) {
return object.animationProgress;
}
};
public TextRadioCell(Context context) {
this(context, 21);
}
public TextRadioCell(Context context, int padding) {
this(context, padding, false);
}
public TextRadioCell(Context context, int padding, boolean dialog) {
super(context);
textView = new TextView(context);
textView.setTextColor(Theme.getColor(dialog ? Theme.key_dialogTextBlack : Theme.key_windowBackgroundWhiteBlackText));
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
textView.setLines(1);
textView.setMaxLines(1);
textView.setSingleLine(true);
textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL);
textView.setEllipsize(TextUtils.TruncateAt.END);
addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? padding : 64, 0, LocaleController.isRTL ? 64 : padding, 0));
valueTextView = new TextView(context);
valueTextView.setTextColor(Theme.getColor(dialog ? Theme.key_dialogIcon : Theme.key_windowBackgroundWhiteGrayText2));
valueTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13);
valueTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT);
valueTextView.setLines(1);
valueTextView.setMaxLines(1);
valueTextView.setSingleLine(true);
valueTextView.setPadding(0, 0, 0, 0);
valueTextView.setEllipsize(TextUtils.TruncateAt.END);
addView(valueTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? padding : 64, 36, LocaleController.isRTL ? 64 : padding, 0));
radioButton = new RadioButton(context);
radioButton.setSize(AndroidUtilities.dp(20));
// radioButton.setColors(Theme.key_switchTrack, Theme.key_switchTrackChecked, Theme.key_windowBackgroundWhite, Theme.key_windowBackgroundWhite);
radioButton.setColor(Theme.getColor(Theme.key_radioBackground), Theme.getColor(Theme.key_radioBackgroundChecked));
addView(radioButton, LayoutHelper.createFrame(20, 20, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL, 22, 0, 22, 0));
setClipChildren(false);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (isMultiline) {
super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
} else {
super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(valueTextView.getVisibility() == VISIBLE ? 64 : height) + (needDivider ? 1 : 0), 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);
isMultiline = false;
radioButton.setChecked(checked, false);
needDivider = divider;
valueTextView.setVisibility(GONE);
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));
// radioButton.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 setTextAndValueAndCheck(String text, String value, boolean checked, boolean multiline, boolean divider) {
textView.setText(text);
valueTextView.setText(value);
radioButton.setChecked(checked, false);
needDivider = divider;
valueTextView.setVisibility(VISIBLE);
isMultiline = multiline;
if (multiline) {
valueTextView.setLines(0);
valueTextView.setMaxLines(0);
valueTextView.setSingleLine(false);
valueTextView.setEllipsize(null);
valueTextView.setPadding(0, 0, 0, AndroidUtilities.dp(11));
} else {
valueTextView.setLines(1);
valueTextView.setMaxLines(1);
valueTextView.setSingleLine(true);
valueTextView.setEllipsize(TextUtils.TruncateAt.END);
valueTextView.setPadding(0, 0, 0, 0);
}
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(radioButton, "alpha", value ? 1.0f : 0.5f));
if (valueTextView.getVisibility() == VISIBLE) {
animators.add(ObjectAnimator.ofFloat(valueTextView, "alpha", value ? 1.0f : 0.5f));
}
} else {
textView.setAlpha(value ? 1.0f : 0.5f);
radioButton.setAlpha(value ? 1.0f : 0.5f);
if (valueTextView.getVisibility() == VISIBLE) {
valueTextView.setAlpha(value ? 1.0f : 0.5f);
}
}
}
public void setChecked(boolean checked) {
radioButton.setChecked(checked, true);
}
public boolean isChecked() {
return radioButton.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);
}
// radioButton.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;
// radioButton.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);
}
if (needDivider) {
canvas.drawLine(LocaleController.isRTL ? 0 : AndroidUtilities.dp(64), getMeasuredHeight() - 1, getMeasuredWidth() - (LocaleController.isRTL ? AndroidUtilities.dp(64) : 0), getMeasuredHeight() - 1, Theme.dividerPaint);
}
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName("android.widget.RadioButton");
info.setCheckable(true);
info.setChecked(radioButton.isChecked());
info.setContentDescription(radioButton.isChecked() ? LocaleController.getString("NotificationsOn", R.string.NotificationsOn) : LocaleController.getString("NotificationsOff", R.string.NotificationsOff));
}
}

View File

@ -12,7 +12,6 @@ import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
@ -28,6 +27,7 @@ import android.widget.TextView;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.LocaleController;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Components.BackupImageView;
import org.telegram.ui.Components.LayoutHelper;
import java.util.ArrayList;
@ -36,6 +36,7 @@ public class TextSettingsCell extends FrameLayout {
private TextView textView;
private TextView valueTextView;
private BackupImageView valueBackupImageView;
private ImageView valueImageView;
private boolean needDivider;
private boolean canDisable;
@ -95,6 +96,10 @@ public class TextSettingsCell extends FrameLayout {
if (valueImageView.getVisibility() == VISIBLE) {
valueImageView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
}
if (valueBackupImageView != null) {
valueBackupImageView.measure(MeasureSpec.makeMeasureSpec(valueBackupImageView.getLayoutParams().height, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(valueBackupImageView.getLayoutParams().width, MeasureSpec.EXACTLY));
}
if (valueTextView.getVisibility() == VISIBLE) {
valueTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
width = availableWidth - valueTextView.getMeasuredWidth() - AndroidUtilities.dp(8);
@ -273,4 +278,12 @@ public class TextSettingsCell extends FrameLayout {
}
invalidate();
}
public BackupImageView getValueBackupImageView() {
if (valueBackupImageView == null) {
valueBackupImageView = new BackupImageView(getContext());
addView(valueBackupImageView, LayoutHelper.createFrame(24, 24, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL, padding, 0, padding, 0));
}
return valueBackupImageView;
}
}

View File

@ -1,5 +1,9 @@
package org.telegram.ui.Cells;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Shader;
@ -7,23 +11,32 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ViewTreeObserver;
import android.widget.LinearLayout;
import androidx.core.content.ContextCompat;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MediaDataController;
import org.telegram.messenger.MessageObject;
import org.telegram.messenger.R;
import org.telegram.messenger.UserConfig;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.ActionBarLayout;
import org.telegram.ui.ActionBar.BaseFragment;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Components.BackgroundGradientDrawable;
import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.MotionBackgroundDrawable;
import org.telegram.ui.Components.Reactions.ReactionsEffectOverlay;
public class ThemePreviewMessagesCell extends LinearLayout {
public final static int TYPE_REACTIONS_DOUBLE_TAP = 2;
private final Runnable invalidateRunnable = this::invalidate;
private BackgroundGradientDrawable.Disposable backgroundGradientDisposable;
@ -34,10 +47,15 @@ public class ThemePreviewMessagesCell extends LinearLayout {
private ChatMessageCell[] cells = new ChatMessageCell[2];
private Drawable shadowDrawable;
private ActionBarLayout parentLayout;
private final int type;
public BaseFragment fragment;
@SuppressLint("ClickableViewAccessibility")
public ThemePreviewMessagesCell(Context context, ActionBarLayout layout, int type) {
super(context);
this.type = type;
int currentAccount = UserConfig.selectedAccount;
parentLayout = layout;
setWillNotDraw(false);
@ -47,92 +65,180 @@ public class ThemePreviewMessagesCell extends LinearLayout {
shadowDrawable = Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow);
int date = (int) (System.currentTimeMillis() / 1000) - 60 * 60;
TLRPC.Message message = new TLRPC.TL_message();
if (type == 0) {
message.message = LocaleController.getString("FontSizePreviewReply", R.string.FontSizePreviewReply);
} else {
message.message = LocaleController.getString("NewThemePreviewReply", R.string.NewThemePreviewReply);
}
message.date = date + 60;
message.dialog_id = 1;
message.flags = 259;
message.from_id = new TLRPC.TL_peerUser();
message.from_id.user_id = UserConfig.getInstance(UserConfig.selectedAccount).getClientUserId();
message.id = 1;
message.media = new TLRPC.TL_messageMediaEmpty();
message.out = true;
message.peer_id = new TLRPC.TL_peerUser();
message.peer_id.user_id = 0;
MessageObject replyMessageObject = new MessageObject(UserConfig.selectedAccount, message, true, false);
message = new TLRPC.TL_message();
if (type == 0) {
message.message = LocaleController.getString("FontSizePreviewLine2", R.string.FontSizePreviewLine2);
MessageObject message1 = null;
MessageObject message2 = null;
if (type == TYPE_REACTIONS_DOUBLE_TAP) {
TLRPC.Message message = new TLRPC.TL_message();
message.message = LocaleController.getString("DoubleTapPreviewMessage", R.string.DoubleTapPreviewMessage);
message.date = date + 60;
message.dialog_id = 1;
message.flags = 259;
message.from_id = new TLRPC.TL_peerUser();
message.from_id.user_id = UserConfig.getInstance(UserConfig.selectedAccount).getClientUserId();
message.id = 1;
message.media = new TLRPC.TL_messageMediaEmpty();
message.out = false;
message.peer_id = new TLRPC.TL_peerUser();
message.peer_id.user_id = 0;
message1 = new MessageObject(UserConfig.selectedAccount, message, true, false);
message1.resetLayout();
message1.eventId = 1;
message1.customName = LocaleController.getString("DoubleTapPreviewSenderName", R.string.DoubleTapPreviewSenderName);
message1.customAvatarDrawable = ContextCompat.getDrawable(context, R.drawable.dino_pic);
} else {
String text = LocaleController.getString("NewThemePreviewLine3", R.string.NewThemePreviewLine3);
StringBuilder builder = new StringBuilder(text);
int index1 = text.indexOf('*');
int index2 = text.lastIndexOf('*');
if (index1 != -1 && index2 != -1) {
builder.replace(index2, index2 + 1, "");
builder.replace(index1, index1 + 1, "");
TLRPC.TL_messageEntityTextUrl entityUrl = new TLRPC.TL_messageEntityTextUrl();
entityUrl.offset = index1;
entityUrl.length = index2 - index1 - 1;
entityUrl.url = "https://telegram.org";
message.entities.add(entityUrl);
TLRPC.Message message = new TLRPC.TL_message();
if (type == 0) {
message.message = LocaleController.getString("FontSizePreviewReply", R.string.FontSizePreviewReply);
} else {
message.message = LocaleController.getString("NewThemePreviewReply", R.string.NewThemePreviewReply);
}
message.message = builder.toString();
}
message.date = date + 960;
message.dialog_id = 1;
message.flags = 259;
message.from_id = new TLRPC.TL_peerUser();
message.from_id.user_id = UserConfig.getInstance(UserConfig.selectedAccount).getClientUserId();
message.id = 1;
message.media = new TLRPC.TL_messageMediaEmpty();
message.out = true;
message.peer_id = new TLRPC.TL_peerUser();
message.peer_id.user_id = 0;
MessageObject message1 = new MessageObject(UserConfig.selectedAccount, message, true, false);
message1.resetLayout();
message1.eventId = 1;
message.date = date + 60;
message.dialog_id = 1;
message.flags = 259;
message.from_id = new TLRPC.TL_peerUser();
message.from_id.user_id = UserConfig.getInstance(UserConfig.selectedAccount).getClientUserId();
message.id = 1;
message.media = new TLRPC.TL_messageMediaEmpty();
message.out = true;
message.peer_id = new TLRPC.TL_peerUser();
message.peer_id.user_id = 0;
MessageObject replyMessageObject = new MessageObject(UserConfig.selectedAccount, message, true, false);
message = new TLRPC.TL_message();
if (type == 0) {
message.message = LocaleController.getString("FontSizePreviewLine1", R.string.FontSizePreviewLine1);
} else {
message.message = LocaleController.getString("NewThemePreviewLine1", R.string.NewThemePreviewLine1);
message = new TLRPC.TL_message();
if (type == 0) {
message.message = LocaleController.getString("FontSizePreviewLine2", R.string.FontSizePreviewLine2);
} else {
String text = LocaleController.getString("NewThemePreviewLine3", R.string.NewThemePreviewLine3);
StringBuilder builder = new StringBuilder(text);
int index1 = text.indexOf('*');
int index2 = text.lastIndexOf('*');
if (index1 != -1 && index2 != -1) {
builder.replace(index2, index2 + 1, "");
builder.replace(index1, index1 + 1, "");
TLRPC.TL_messageEntityTextUrl entityUrl = new TLRPC.TL_messageEntityTextUrl();
entityUrl.offset = index1;
entityUrl.length = index2 - index1 - 1;
entityUrl.url = "https://telegram.org";
message.entities.add(entityUrl);
}
message.message = builder.toString();
}
message.date = date + 960;
message.dialog_id = 1;
message.flags = 259;
message.from_id = new TLRPC.TL_peerUser();
message.from_id.user_id = UserConfig.getInstance(UserConfig.selectedAccount).getClientUserId();
message.id = 1;
message.media = new TLRPC.TL_messageMediaEmpty();
message.out = true;
message.peer_id = new TLRPC.TL_peerUser();
message.peer_id.user_id = 0;
message1 = new MessageObject(UserConfig.selectedAccount, message, true, false);
message1.resetLayout();
message1.eventId = 1;
message = new TLRPC.TL_message();
if (type == 0) {
message.message = LocaleController.getString("FontSizePreviewLine1", R.string.FontSizePreviewLine1);
} else {
message.message = LocaleController.getString("NewThemePreviewLine1", R.string.NewThemePreviewLine1);
}
message.date = date + 60;
message.dialog_id = 1;
message.flags = 257 + 8;
message.from_id = new TLRPC.TL_peerUser();
message.id = 1;
message.reply_to = new TLRPC.TL_messageReplyHeader();
message.reply_to.reply_to_msg_id = 5;
message.media = new TLRPC.TL_messageMediaEmpty();
message.out = false;
message.peer_id = new TLRPC.TL_peerUser();
message.peer_id.user_id = UserConfig.getInstance(UserConfig.selectedAccount).getClientUserId();
message2 = new MessageObject(UserConfig.selectedAccount, message, true, false);
if (type == 0) {
message2.customReplyName = LocaleController.getString("FontSizePreviewName", R.string.FontSizePreviewName);
} else {
message2.customReplyName = LocaleController.getString("NewThemePreviewName", R.string.NewThemePreviewName);
}
message2.eventId = 1;
message2.resetLayout();
message2.replyMessageObject = replyMessageObject;
}
message.date = date + 60;
message.dialog_id = 1;
message.flags = 257 + 8;
message.from_id = new TLRPC.TL_peerUser();
message.id = 1;
message.reply_to = new TLRPC.TL_messageReplyHeader();
message.reply_to.reply_to_msg_id = 5;
message.media = new TLRPC.TL_messageMediaEmpty();
message.out = false;
message.peer_id = new TLRPC.TL_peerUser();
message.peer_id.user_id = UserConfig.getInstance(UserConfig.selectedAccount).getClientUserId();
MessageObject message2 = new MessageObject(UserConfig.selectedAccount, message, true, false);
if (type == 0) {
message2.customReplyName = LocaleController.getString("FontSizePreviewName", R.string.FontSizePreviewName);
} else {
message2.customReplyName = LocaleController.getString("NewThemePreviewName", R.string.NewThemePreviewName);
}
message2.eventId = 1;
message2.resetLayout();
message2.replyMessageObject = replyMessageObject;
for (int a = 0; a < cells.length; a++) {
cells[a] = new ChatMessageCell(context);
cells[a] = new ChatMessageCell(context) {
private GestureDetector gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
boolean added = getMessageObject().selectReaction(MediaDataController.getInstance(currentAccount).getDoubleTapReaction(), false);
setMessageObject(getMessageObject(), null, false, false);
requestLayout();
ReactionsEffectOverlay.removeCurrent(false);
if (added) {
ReactionsEffectOverlay.show(fragment, null, cells[1], e.getX(), e.getY(), MediaDataController.getInstance(currentAccount).getDoubleTapReaction(), currentAccount);
ReactionsEffectOverlay.startAnimation();
}
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
getTransitionParams().resetAnimation();
getTransitionParams().animateChange();
getTransitionParams().animateChange = true;
getTransitionParams().animateChangeProgress = 0f;
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1f);
valueAnimator.addUpdateListener(valueAnimator1 -> {
getTransitionParams().animateChangeProgress = (float) valueAnimator1.getAnimatedValue();
invalidate();
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
getTransitionParams().resetAnimation();
getTransitionParams().animateChange = false;
getTransitionParams().animateChangeProgress = 1f;
}
});
valueAnimator.start();
return false;
}
});
return true;
}
});
@Override
public boolean onTouchEvent(MotionEvent event) {
gestureDetector.onTouchEvent(event);
return true;
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (getAvatarImage() != null && getAvatarImage().getImageHeight() != 0) {
getAvatarImage().setImageCoords(getAvatarImage().getImageX(), getMeasuredHeight() - getAvatarImage().getImageHeight() - AndroidUtilities.dp(4), getAvatarImage().getImageWidth(), getAvatarImage().getImageHeight());
getAvatarImage().setRoundRadius((int) (getAvatarImage().getImageHeight() / 2f));
getAvatarImage().draw(canvas);
} else if (type == TYPE_REACTIONS_DOUBLE_TAP) {
invalidate();
}
super.dispatchDraw(canvas);
}
};
cells[a].setDelegate(new ChatMessageCell.ChatMessageCellDelegate() {
});
cells[a].isChat = false;
cells[a].isChat = type == TYPE_REACTIONS_DOUBLE_TAP;
cells[a].setFullyDraw(true);
cells[a].setMessageObject(a == 0 ? message2 : message1, null, false, false);
MessageObject messageObject = a == 0 ? message2 : message1;
if (messageObject == null) {
continue;
}
cells[a].setMessageObject(messageObject, null, false, false);
addView(cells[a], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
}
}
@ -232,11 +338,17 @@ public class ThemePreviewMessagesCell extends LinearLayout {
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (type == TYPE_REACTIONS_DOUBLE_TAP) {
return super.onInterceptTouchEvent(ev);
}
return false;
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (type == TYPE_REACTIONS_DOUBLE_TAP) {
return super.dispatchTouchEvent(ev);
}
return false;
}
@ -247,6 +359,9 @@ public class ThemePreviewMessagesCell extends LinearLayout {
@Override
public boolean onTouchEvent(MotionEvent event) {
if (type == TYPE_REACTIONS_DOUBLE_TAP) {
return super.onTouchEvent(event);
}
return false;
}
}

View File

@ -393,7 +393,7 @@ public class ThemesHorizontalListCell extends RecyclerListView implements Notifi
backgroundDrawable = drawable;
hsv = AndroidUtilities.rgbToHsv(Color.red(themeInfo.getPreviewBackgroundColor()), Color.green(themeInfo.getPreviewBackgroundColor()), Color.blue(themeInfo.getPreviewBackgroundColor()));
} else if (themeInfo.previewWallpaperOffset > 0 || themeInfo.pathToWallpaper != null) {
Bitmap wallpaper = getScaledBitmap(AndroidUtilities.dp(76), AndroidUtilities.dp(97), themeInfo.pathToWallpaper, themeInfo.pathToFile, themeInfo.previewWallpaperOffset);
Bitmap wallpaper = AndroidUtilities.getScaledBitmap(AndroidUtilities.dp(76), AndroidUtilities.dp(97), themeInfo.pathToWallpaper, themeInfo.pathToFile, themeInfo.previewWallpaperOffset);
if (wallpaper != null) {
backgroundDrawable = new BitmapDrawable(wallpaper);
bitmapShader = new BitmapShader(wallpaper, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
@ -803,56 +803,6 @@ public class ThemesHorizontalListCell extends RecyclerListView implements Notifi
}
}
public static Bitmap getScaledBitmap(float w, float h, String path, String streamPath, int streamOffset) {
FileInputStream stream = null;
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
if (path != null) {
BitmapFactory.decodeFile(path, options);
} else {
stream = new FileInputStream(streamPath);
stream.getChannel().position(streamOffset);
BitmapFactory.decodeStream(stream, null, options);
}
if (options.outWidth > 0 && options.outHeight > 0) {
if (w > h && options.outWidth < options.outHeight) {
float temp = w;
w = h;
h = temp;
}
float scale = Math.min(options.outWidth / w, options.outHeight / h);
options.inSampleSize = 1;
if (scale > 1.0f) {
do {
options.inSampleSize *= 2;
} while (options.inSampleSize < scale);
}
options.inJustDecodeBounds = false;
Bitmap wallpaper;
if (path != null) {
wallpaper = BitmapFactory.decodeFile(path, options);
} else {
stream.getChannel().position(streamOffset);
wallpaper = BitmapFactory.decodeStream(stream, null, options);
}
return wallpaper;
}
} catch (Throwable e) {
FileLog.e(e);
} finally {
try {
if (stream != null) {
stream.close();
}
} catch (Exception e2) {
FileLog.e(e2);
}
}
return null;
}
@Override
public void setBackgroundColor(int color) {
super.setBackgroundColor(color);

File diff suppressed because it is too large Load Diff

View File

@ -72,6 +72,8 @@ import org.telegram.ui.Components.SizeNotifierFrameLayout;
import org.telegram.ui.Components.UndoView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import tw.nekomimi.nekogram.utils.VibrateUtil;
@ -102,6 +104,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image
private TextDetailCell typeCell;
private TextDetailCell linkedCell;
private TextDetailCell historyCell;
private TextCell reactionsCell;
private ShadowSectionCell settingsSectionCell;
private TextCheckCell signCell;
@ -133,6 +136,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image
private boolean isChannel;
private boolean historyHidden;
private List<String> availableReactions = Collections.emptyList();
private boolean createAfterUpload;
private boolean donePressed;
@ -217,6 +221,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image
signMessages = currentChat.signatures;
NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.chatInfoDidLoad);
NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.updateInterfaces);
NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.chatAvailableReactionsUpdated);
if (info != null) {
loadLinksCount();
@ -247,6 +252,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image
}
NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.chatInfoDidLoad);
NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.updateInterfaces);
NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.chatAvailableReactionsUpdated);
if (nameTextView != null) {
nameTextView.onDestroy();
}
@ -794,7 +800,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image
linearLayout1.addView(infoContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
blockCell = new TextCell(context);
blockCell.setBackgroundDrawable(Theme.getSelectorDrawable(false));
blockCell.setBackground(Theme.getSelectorDrawable(false));
blockCell.setVisibility(ChatObject.isChannel(currentChat) || currentChat.creator || ChatObject.hasAdminRights(currentChat) && ChatObject.canChangeChatInfo(currentChat) ? View.VISIBLE : View.GONE);
blockCell.setOnClickListener(v -> {
Bundle args = new Bundle();
@ -806,15 +812,25 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image
});
inviteLinksCell = new TextCell(context);
inviteLinksCell.setBackgroundDrawable(Theme.getSelectorDrawable(false));
inviteLinksCell.setBackground(Theme.getSelectorDrawable(false));
inviteLinksCell.setOnClickListener(v -> {
ManageLinksActivity fragment = new ManageLinksActivity(chatId, 0, 0);
fragment.setInfo(info, info.exported_invite);
presentFragment(fragment);
});
reactionsCell = new TextCell(context);
reactionsCell.setBackground(Theme.getSelectorDrawable(false));
reactionsCell.setOnClickListener(v -> {
Bundle args = new Bundle();
args.putLong(ChatReactionsEditActivity.KEY_CHAT_ID, chatId);
ChatReactionsEditActivity reactionsEditActivity = new ChatReactionsEditActivity(args);
reactionsEditActivity.setInfo(info);
presentFragment(reactionsEditActivity);
});
adminCell = new TextCell(context);
adminCell.setBackgroundDrawable(Theme.getSelectorDrawable(false));
adminCell.setBackground(Theme.getSelectorDrawable(false));
adminCell.setOnClickListener(v -> {
Bundle args = new Bundle();
args.putLong("chat_id", chatId);
@ -851,6 +867,8 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image
logCell.setOnClickListener(v -> presentFragment(new ChannelAdminLogActivity(currentChat)));
}
infoContainer.addView(reactionsCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
if (!isChannel && !currentChat.gigagroup) {
infoContainer.addView(blockCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
}
@ -1017,6 +1035,15 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image
if ((mask & MessagesController.UPDATE_MASK_AVATAR) != 0) {
setAvatar();
}
} else if (id == NotificationCenter.chatAvailableReactionsUpdated) {
long chatId = (long) args[0];
if (chatId == this.chatId) {
info = getMessagesController().getChatFull(chatId);
if (info != null) {
availableReactions = info.available_reactions;
}
updateReactionsCell();
}
}
}
@ -1272,6 +1299,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image
currentChat = getMessagesController().getChat(chatId);
}
historyHidden = !ChatObject.isChannel(currentChat) || info.hidden_prehistory;
availableReactions = info.available_reactions;
}
}
@ -1460,6 +1488,8 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image
}
adminCell.setTextAndIcon(LocaleController.getString("ChannelAdministrators", R.string.ChannelAdministrators), R.drawable.actions_addadmin, true);
}
reactionsCell.setVisibility(ChatObject.canChangeChatInfo(currentChat) ? View.VISIBLE : View.GONE);
updateReactionsCell();
if (info == null || !ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_INVITE) || (!isPrivate && currentChat.creator)) {
inviteLinksCell.setVisibility(View.GONE);
} else {
@ -1480,6 +1510,19 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image
}
}
private void updateReactionsCell() {
int count = 0;
for (int i = 0; i < availableReactions.size(); i++) {
TLRPC.TL_availableReaction reaction = getMediaDataController().getReactionsMap().get(availableReactions.get(i));
if (reaction != null && !reaction.inactive) {
count++;
}
}
int reacts = Math.min(getMediaDataController().getEnabledReactionsList().size(), count);
reactionsCell.setTextAndValueAndIcon(LocaleController.getString("Reactions", R.string.Reactions), reacts == 0 ? LocaleController.getString("ReactionsOff", R.string.ReactionsOff) :
LocaleController.formatString("ReactionsCount", R.string.ReactionsCount, reacts, getMediaDataController().getEnabledReactionsList().size()), R.drawable.actions_reactions, true);
}
@Override
public ArrayList<ThemeDescription> getThemeDescriptions() {
ArrayList<ThemeDescription> themeDescriptions = new ArrayList<>();
@ -1581,6 +1624,10 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image
themeDescriptions.add(new ThemeDescription(undoView, 0, new Class[]{UndoView.class}, new String[]{"progressPaint"}, null, null, null, Theme.key_undo_infoColor));
themeDescriptions.add(new ThemeDescription(undoView, ThemeDescription.FLAG_IMAGECOLOR, new Class[]{UndoView.class}, new String[]{"leftImageView"}, null, null, null, Theme.key_undo_infoColor));
themeDescriptions.add(new ThemeDescription(reactionsCell, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector));
themeDescriptions.add(new ThemeDescription(reactionsCell, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{TextCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
themeDescriptions.add(new ThemeDescription(reactionsCell, 0, new Class[]{TextCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayIcon));
return themeDescriptions;
}
}

View File

@ -144,7 +144,7 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe
}
isPrivate = !isForcePublic && TextUtils.isEmpty(currentChat.username);
isChannel = ChatObject.isChannel(currentChat) && !currentChat.megagroup;
isSaveRestricted = /*isPrivate && */currentChat.noforwards;
isSaveRestricted = currentChat.noforwards;
if (isForcePublic && TextUtils.isEmpty(currentChat.username) || isPrivate && currentChat.creator) {
TLRPC.TL_channels_checkUsername req = new TLRPC.TL_channels_checkUsername();
req.username = "1";
@ -472,7 +472,7 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe
}
private void processDone() {
if (/*isPrivate && */currentChat.noforwards != isSaveRestricted) {
if (currentChat.noforwards != isSaveRestricted) {
getMessagesController().toggleChatNoForwards(chatId, currentChat.noforwards = isSaveRestricted);
}
if (trySetUsername()) {
@ -615,7 +615,7 @@ public class ChatEditTypeActivity extends BaseFragment implements NotificationCe
}
publicContainer.setVisibility(isPrivate ? View.GONE : View.VISIBLE);
privateContainer.setVisibility(isPrivate ? View.VISIBLE : View.GONE);
//saveContainer.setVisibility(isPrivate ? View.VISIBLE : View.GONE);
//saveContainer.setVisibility(View.VISIBLE);
manageLinksTextView.setVisibility(View.VISIBLE);
manageLinksInfoCell.setVisibility(View.VISIBLE);
linkContainer.setPadding(0, 0, 0, isPrivate ? 0 : AndroidUtilities.dp(7));

View File

@ -5,7 +5,6 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
@ -80,7 +79,7 @@ public class ChatPullingDownDrawable implements NotificationCenter.NotificationC
public long nextDialogId;
View parentView;
CounterView.CounterDrawable counterDrawable = new CounterView.CounterDrawable(null, null);
CounterView.CounterDrawable counterDrawable = new CounterView.CounterDrawable(null, true, null);
int params[] = new int[3];
private final int currentAccount;
private final int folderId;
@ -156,10 +155,10 @@ public class ChatPullingDownDrawable implements NotificationCenter.NotificationC
str2 = LocaleController.getString("ReleaseToGoNextArchive", R.string.ReleaseToGoNextArchive);
} else if (drawFolderBackground) {
str1 = LocaleController.getString("SwipeToGoNextFolder", R.string.SwipeToGoNextFolder);
str2 = LocaleController.getString("ReleaseToGoNextFolder", R.string.ReleaseToGoNextFolder);
str2 = LocaleController.getString("ReleaseToGoNextFolder", R.string.ReleaseToGoNextFolder);
} else {
str1 = LocaleController.getString("SwipeToGoNextChannel", R.string.SwipeToGoNextChannel);
str2 = LocaleController.getString("ReleaseToGoNextChannel", R.string.ReleaseToGoNextChannel);
str2 = LocaleController.getString("ReleaseToGoNextChannel", R.string.ReleaseToGoNextChannel);
}
layout1Width = (int) textPaint2.measureText(str1);
layout1Width = Math.min(layout1Width, lastWidth - AndroidUtilities.dp(60));
@ -489,7 +488,7 @@ public class ChatPullingDownDrawable implements NotificationCenter.NotificationC
@Override
public void didReceivedNotification(int id, int account, Object... args) {
if (nextDialogId !=0 ) {
if (nextDialogId != 0) {
TLRPC.Dialog dialog = MessagesController.getInstance(currentAccount).dialogs_dict.get(nextDialogId);
if (dialog != null) {
counterDrawable.setCount(dialog.unread_count, true);

View File

@ -0,0 +1,267 @@
package org.telegram.ui;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ChatObject;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MessagesStorage;
import org.telegram.messenger.NotificationCenter;
import org.telegram.messenger.R;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.ActionBar;
import org.telegram.ui.ActionBar.BaseFragment;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.ActionBar.ThemeDescription;
import org.telegram.ui.Cells.AvailableReactionCell;
import org.telegram.ui.Cells.HeaderCell;
import org.telegram.ui.Cells.TextCheckCell;
import org.telegram.ui.Cells.TextInfoPrivacyCell;
import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.RecyclerListView;
import org.telegram.ui.Components.SimpleThemeDescription;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class ChatReactionsEditActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate {
private final static int TYPE_INFO = 0, TYPE_HEADER = 1, TYPE_REACTION = 2;
public final static String KEY_CHAT_ID = "chat_id";
private TLRPC.Chat currentChat;
private TLRPC.ChatFull info;
private long chatId;
private List<String> chatReactions = new ArrayList<>();
private LinearLayout contentView;
private RecyclerListView listView;
private RecyclerView.Adapter listAdapter;
private TextCheckCell enableReactionsCell;
private ArrayList<TLRPC.TL_availableReaction> availableReactions = new ArrayList();
public ChatReactionsEditActivity(Bundle args) {
super(args);
chatId = args.getLong(KEY_CHAT_ID, 0);
}
@Override
public boolean onFragmentCreate() {
currentChat = getMessagesController().getChat(chatId);
if (currentChat == null) {
currentChat = MessagesStorage.getInstance(currentAccount).getChatSync(chatId);
if (currentChat != null) {
getMessagesController().putChat(currentChat, true);
} else {
return false;
}
if (info == null) {
info = MessagesStorage.getInstance(currentAccount).loadChatInfo(chatId, ChatObject.isChannel(currentChat), new CountDownLatch(1), false, false);
if (info == null) {
return false;
}
}
}
getNotificationCenter().addObserver(this, NotificationCenter.reactionsDidLoad);
return super.onFragmentCreate();
}
@Override
public View createView(Context context) {
actionBar.setTitle(LocaleController.getString("Reactions", R.string.Reactions));
actionBar.setBackButtonImage(R.drawable.ic_ab_back);
actionBar.setAllowOverlayTitle(true);
actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() {
@Override
public void onItemClick(int id) {
if (id == -1) {
finishFragment();
}
}
});
LinearLayout ll = new LinearLayout(context);
ll.setOrientation(LinearLayout.VERTICAL);
availableReactions.addAll(getMediaDataController().getEnabledReactionsList());
enableReactionsCell = new TextCheckCell(context);
enableReactionsCell.setHeight(56);
enableReactionsCell.setTextAndCheck(LocaleController.getString("EnableReactions", R.string.EnableReactions), !chatReactions.isEmpty(), false);
enableReactionsCell.setBackgroundColor(Theme.getColor(enableReactionsCell.isChecked() ? Theme.key_windowBackgroundChecked : Theme.key_windowBackgroundUnchecked));
enableReactionsCell.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
enableReactionsCell.setAnimatingToThumbInsteadOfTouch(true);
enableReactionsCell.setOnClickListener(v -> {
boolean c = !enableReactionsCell.isChecked();
enableReactionsCell.setChecked(c);
int clr = Theme.getColor(c ? Theme.key_windowBackgroundChecked : Theme.key_windowBackgroundUnchecked);
if (c) {
enableReactionsCell.setBackgroundColorAnimated(c, clr);
} else {
enableReactionsCell.setBackgroundColorAnimatedReverse(clr);
}
if (c) {
for (TLRPC.TL_availableReaction a : availableReactions) {
chatReactions.add(a.reaction);
}
listAdapter.notifyItemRangeInserted(1, 1 + availableReactions.size());
} else {
chatReactions.clear();
listAdapter.notifyItemRangeRemoved(1, 1 + availableReactions.size());
}
});
ll.addView(enableReactionsCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
listView = new RecyclerListView(context);
listView.setLayoutManager(new LinearLayoutManager(context));
listView.setAdapter(listAdapter = new RecyclerView.Adapter() {
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
switch (viewType) {
default:
case TYPE_REACTION: {
return new RecyclerListView.Holder(new AvailableReactionCell(context, false));
}
case TYPE_INFO: {
TextInfoPrivacyCell infoCell = new TextInfoPrivacyCell(context);
return new RecyclerListView.Holder(infoCell);
}
case TYPE_HEADER: {
return new RecyclerListView.Holder(new HeaderCell(context, 23));
}
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
switch (getItemViewType(position)) {
case TYPE_INFO:
TextInfoPrivacyCell infoCell = (TextInfoPrivacyCell) holder.itemView;
infoCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText4));
infoCell.setText(ChatObject.isChannelAndNotMegaGroup(currentChat) ? LocaleController.getString("EnableReactionsChannelInfo", R.string.EnableReactionsChannelInfo) :
LocaleController.getString("EnableReactionsGroupInfo", R.string.EnableReactionsGroupInfo));
break;
case TYPE_HEADER:
HeaderCell headerCell = (HeaderCell) holder.itemView;
headerCell.setText(LocaleController.getString("AvailableReactions", R.string.AvailableReactions));
headerCell.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite));
break;
case TYPE_REACTION:
AvailableReactionCell reactionCell = (AvailableReactionCell) holder.itemView;
TLRPC.TL_availableReaction react = availableReactions.get(position - 2);
reactionCell.bind(react, chatReactions.contains(react.reaction));
break;
}
}
@Override
public int getItemCount() {
return 1 + (!chatReactions.isEmpty() ? 1 + availableReactions.size() : 0);
}
@Override
public int getItemViewType(int position) {
return position == 0 ? TYPE_INFO : position == 1 ? TYPE_HEADER : TYPE_REACTION;
}
});
listView.setOnItemClickListener((view, position) -> {
if (position <= 1) return;
AvailableReactionCell cell = (AvailableReactionCell) view;
TLRPC.TL_availableReaction react = availableReactions.get(position - 2);
boolean nc = !chatReactions.contains(react.reaction);
if (nc) chatReactions.add(react.reaction);
else chatReactions.remove(react.reaction);
cell.setChecked(nc, true);
});
ll.addView(listView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 0, 1f));
fragmentView = contentView = ll;
updateColors();
return contentView;
}
@Override
public void onFragmentDestroy() {
super.onFragmentDestroy();
boolean changed = true;
if (info != null) {
changed = !info.available_reactions.equals(chatReactions);
}
if (changed) {
getMessagesController().setChatReactions(chatId, chatReactions);
}
getNotificationCenter().removeObserver(this, NotificationCenter.reactionsDidLoad);
}
/**
* Sets chat full info
* @param info Info to use
*/
public void setInfo(TLRPC.ChatFull info) {
this.info = info;
if (info != null) {
if (currentChat == null) {
currentChat = getMessagesController().getChat(chatId);
}
chatReactions = new ArrayList<>(info.available_reactions);
}
}
@Override
public ArrayList<ThemeDescription> getThemeDescriptions() {
return SimpleThemeDescription.createThemeDescriptions(this::updateColors,
Theme.key_windowBackgroundWhite,
Theme.key_windowBackgroundWhiteBlackText,
Theme.key_windowBackgroundWhiteGrayText2,
Theme.key_listSelector,
Theme.key_windowBackgroundGray,
Theme.key_windowBackgroundWhiteGrayText4,
Theme.key_windowBackgroundWhiteRedText4,
Theme.key_windowBackgroundChecked,
Theme.key_windowBackgroundCheckText,
Theme.key_switchTrackBlue,
Theme.key_switchTrackBlueChecked,
Theme.key_switchTrackBlueThumb,
Theme.key_switchTrackBlueThumbChecked
);
}
@SuppressLint("NotifyDataSetChanged")
private void updateColors() {
contentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray));
enableReactionsCell.setColors(Theme.key_windowBackgroundCheckText, Theme.key_switchTrackBlue, Theme.key_switchTrackBlueChecked, Theme.key_switchTrackBlueThumb, Theme.key_switchTrackBlueThumbChecked);
listAdapter.notifyDataSetChanged();
}
@SuppressLint("NotifyDataSetChanged")
@Override
public void didReceivedNotification(int id, int account, Object... args) {
if (account != currentAccount) return;
if (id == NotificationCenter.reactionsDidLoad) {
availableReactions.clear();
availableReactions.addAll(getMediaDataController().getEnabledReactionsList());
listAdapter.notifyDataSetChanged();
}
}
}

View File

@ -148,7 +148,7 @@ public class CodeNumberField extends EditTextBoldCursor {
if (event.getAction() == MotionEvent.ACTION_UP && pressed) {
if (isFocused() && codeFieldContainer != null) {
ClipboardManager clipboard = ContextCompat.getSystemService(getContext(), ClipboardManager.class);
if (clipboard == null) {
if (clipboard == null || clipboard.getPrimaryClipDescription() == null) {
return false;
}
clipboard.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);

View File

@ -41,6 +41,7 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
@ -901,6 +902,12 @@ public class AudioPlayerAlert extends BottomSheet implements NotificationCenter.
}
return true;
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
}
};
prevButton.setScaleType(ImageView.ScaleType.CENTER);
prevButton.setAnimation(R.raw.player_prev, 20, 20);
@ -1020,6 +1027,12 @@ public class AudioPlayerAlert extends BottomSheet implements NotificationCenter.
return true;
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
}
};
nextButton.setScaleType(ImageView.ScaleType.CENTER);
nextButton.setAnimation(R.raw.player_prev, 20, 20);

View File

@ -0,0 +1,526 @@
package org.telegram.ui.Components;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import android.view.View;
import androidx.core.graphics.ColorUtils;
import org.telegram.messenger.AccountInstance;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.DialogObject;
import org.telegram.messenger.ImageReceiver;
import org.telegram.messenger.MessageObject;
import org.telegram.messenger.MessagesController;
import org.telegram.messenger.UserConfig;
import org.telegram.messenger.voip.VoIPService;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.TLObject;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Cells.GroupCallUserCell;
import java.util.Random;
public class AvatarsDarawable {
public final static int STYLE_GROUP_CALL_TOOLTIP = 10;
public final static int STYLE_MESSAGE_SEEN = 11;
DrawingState[] currentStates = new DrawingState[3];
DrawingState[] animatingStates = new DrawingState[3];
boolean wasDraw;
float transitionProgress = 1f;
ValueAnimator transitionProgressAnimator;
boolean updateAfterTransition;
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint xRefP = new Paint(Paint.ANTI_ALIAS_FLAG);
Runnable updateDelegate;
int currentStyle;
boolean centered;
private boolean isInCall;
public int count;
public int height;
public int width;
View parent;
private int overrideSize;
private float overrideAlpha = 1f;
public void commitTransition(boolean animated) {
if (!wasDraw || !animated) {
transitionProgress = 1f;
swapStates();
return;
}
DrawingState[] removedStates = new DrawingState[3];
boolean changed = false;
for (int i = 0; i < 3; i++) {
removedStates[i] = currentStates[i];
if (currentStates[i].id != animatingStates[i].id) {
changed = true;
} else {
currentStates[i].lastSpeakTime = animatingStates[i].lastSpeakTime;
}
}
if (!changed) {
transitionProgress = 1f;
return;
}
for (int i = 0; i < 3; i++) {
boolean found = false;
for (int j = 0; j < 3; j++) {
if (currentStates[j].id == animatingStates[i].id) {
found = true;
removedStates[j] = null;
if (i == j) {
animatingStates[i].animationType = DrawingState.ANIMATION_TYPE_NONE;
GroupCallUserCell.AvatarWavesDrawable wavesDrawable = animatingStates[i].wavesDrawable;
animatingStates[i].wavesDrawable = currentStates[i].wavesDrawable;
currentStates[i].wavesDrawable = wavesDrawable;
} else {
animatingStates[i].animationType = DrawingState.ANIMATION_TYPE_MOVE;
animatingStates[i].moveFromIndex = j;
}
break;
}
}
if (!found) {
animatingStates[i].animationType = DrawingState.ANIMATION_TYPE_IN;
}
}
for (int i = 0; i < 3; i++) {
if (removedStates[i] != null) {
removedStates[i].animationType = DrawingState.ANIMATION_TYPE_OUT;
}
}
if (transitionProgressAnimator != null) {
transitionProgressAnimator.cancel();
}
transitionProgress = 0;
transitionProgressAnimator = ValueAnimator.ofFloat(0, 1f);
transitionProgressAnimator.addUpdateListener(valueAnimator -> {
transitionProgress = (float) valueAnimator.getAnimatedValue();
invalidate();
});
transitionProgressAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (transitionProgressAnimator != null) {
transitionProgress = 1f;
swapStates();
if (updateAfterTransition) {
updateAfterTransition = false;
if (updateDelegate != null) {
updateDelegate.run();
}
}
invalidate();
}
transitionProgressAnimator = null;
}
});
transitionProgressAnimator.setDuration(220);
transitionProgressAnimator.setInterpolator(CubicBezierInterpolator.DEFAULT);
transitionProgressAnimator.start();
invalidate();
}
private void swapStates() {
for (int i = 0; i < 3; i++) {
DrawingState state = currentStates[i];
currentStates[i] = animatingStates[i];
animatingStates[i] = state;
}
}
public void updateAfterTransitionEnd() {
updateAfterTransition = true;
}
public void setDelegate(Runnable delegate) {
updateDelegate = delegate;
}
public void setStyle(int currentStyle) {
this.currentStyle = currentStyle;
invalidate();
}
private void invalidate() {
if (parent != null) {
parent.invalidate();
}
}
public void setSize(int size) {
overrideSize = size;
}
public void animateFromState(AvatarsDarawable avatarsDarawable, int currentAccount) {
TLObject[] objects = new TLObject[3];
for (int i = 0; i < 3; i++) {
objects[i] = currentStates[i].object;
setObject(i, currentAccount, avatarsDarawable.currentStates[i].object);
}
commitTransition(false);
for (int i = 0; i < 3; i++) {
setObject(i, currentAccount, objects[i]);
}
wasDraw = true;
commitTransition(true);
}
public void setAlpha(float alpha) {
overrideAlpha = alpha;
}
private static class DrawingState {
public static final int ANIMATION_TYPE_NONE = -1;
public static final int ANIMATION_TYPE_IN = 0;
public static final int ANIMATION_TYPE_OUT = 1;
public static final int ANIMATION_TYPE_MOVE = 2;
private AvatarDrawable avatarDrawable;
private GroupCallUserCell.AvatarWavesDrawable wavesDrawable;
private long lastUpdateTime;
private long lastSpeakTime;
private ImageReceiver imageReceiver;
TLRPC.TL_groupCallParticipant participant;
private long id;
private TLObject object;
private int animationType;
private int moveFromIndex;
}
Random random = new Random();
public AvatarsDarawable(View parent, boolean inCall) {
this.parent = parent;
for (int a = 0; a < 3; a++) {
currentStates[a] = new DrawingState();
currentStates[a].imageReceiver = new ImageReceiver();
currentStates[a].imageReceiver.setRoundRadius(AndroidUtilities.dp(12));
currentStates[a].avatarDrawable = new AvatarDrawable();
currentStates[a].avatarDrawable.setTextSize(AndroidUtilities.dp(12));
animatingStates[a] = new DrawingState();
animatingStates[a].imageReceiver = new ImageReceiver();
animatingStates[a].imageReceiver.setRoundRadius(AndroidUtilities.dp(12));
animatingStates[a].avatarDrawable = new AvatarDrawable();
animatingStates[a].avatarDrawable.setTextSize(AndroidUtilities.dp(12));
}
isInCall = inCall;
xRefP.setColor(0);
xRefP.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
public void setObject(int index, int account, TLObject object) {
animatingStates[index].id = 0;
animatingStates[index].participant = null;
if (object == null) {
animatingStates[index].imageReceiver.setImageBitmap((Drawable) null);
invalidate();
return;
}
TLRPC.User currentUser = null;
TLRPC.Chat currentChat = null;
animatingStates[index].lastSpeakTime = -1;
animatingStates[index].object = object;
if (object instanceof TLRPC.TL_groupCallParticipant) {
TLRPC.TL_groupCallParticipant participant = (TLRPC.TL_groupCallParticipant) object;
animatingStates[index].participant = participant;
long id = MessageObject.getPeerId(participant.peer);
if (DialogObject.isUserDialog(id)) {
currentUser = MessagesController.getInstance(account).getUser(id);
animatingStates[index].avatarDrawable.setInfo(currentUser);
} else {
currentChat = MessagesController.getInstance(account).getChat(-id);
animatingStates[index].avatarDrawable.setInfo(currentChat);
}
if (currentStyle == 4) {
if (id == AccountInstance.getInstance(account).getUserConfig().getClientUserId()) {
animatingStates[index].lastSpeakTime = 0;
} else {
if (isInCall) {
animatingStates[index].lastSpeakTime = participant.lastActiveDate;
} else {
animatingStates[index].lastSpeakTime = participant.active_date;
}
}
} else {
animatingStates[index].lastSpeakTime = participant.active_date;
}
animatingStates[index].id = id;
} else if (object instanceof TLRPC.User) {
currentUser = (TLRPC.User) object;
animatingStates[index].avatarDrawable.setInfo(currentUser);
animatingStates[index].id = currentUser.id;
} else {
currentChat = (TLRPC.Chat) object;
animatingStates[index].avatarDrawable.setInfo(currentChat);
animatingStates[index].id = -currentChat.id;
}
if (currentUser != null) {
animatingStates[index].imageReceiver.setForUserOrChat(currentUser, animatingStates[index].avatarDrawable);
} else {
animatingStates[index].imageReceiver.setForUserOrChat(currentChat, animatingStates[index].avatarDrawable);
}
boolean bigAvatars = currentStyle == 4 || currentStyle == STYLE_GROUP_CALL_TOOLTIP;
animatingStates[index].imageReceiver.setRoundRadius(AndroidUtilities.dp(bigAvatars ? 16 : 12));
int size = getSize();
animatingStates[index].imageReceiver.setImageCoords(0, 0, size, size);
invalidate();
}
public void onDraw(Canvas canvas) {
wasDraw = true;
boolean bigAvatars = currentStyle == 4 || currentStyle == STYLE_GROUP_CALL_TOOLTIP;
int size = getSize();
int toAdd;
if (currentStyle == STYLE_MESSAGE_SEEN) {
toAdd = AndroidUtilities.dp(12);
} else if (overrideSize != 0) {
toAdd = (int) (overrideSize * 0.8f);
} else {
toAdd = AndroidUtilities.dp(bigAvatars ? 24 : 20);
}
int drawCount = 0;
for (int i = 0; i < 3; i++) {
if (currentStates[i].id != 0) {
drawCount++;
}
}
int startPadding = (currentStyle == 0 || currentStyle == STYLE_GROUP_CALL_TOOLTIP || currentStyle == STYLE_MESSAGE_SEEN) ? 0 : AndroidUtilities.dp(10);
int ax = centered ? (width - drawCount * toAdd - AndroidUtilities.dp(bigAvatars ? 8 : 4)) / 2 : startPadding;
boolean isMuted = VoIPService.getSharedInstance() != null && VoIPService.getSharedInstance().isMicMute();
if (currentStyle == 4) {
paint.setColor(Theme.getColor(Theme.key_inappPlayerBackground));
} else if (currentStyle != 3) {
paint.setColor(Theme.getColor(isMuted ? Theme.key_returnToCallMutedBackground : Theme.key_returnToCallBackground));
}
int animateToDrawCount = 0;
for (int i = 0; i < 3; i++) {
if (animatingStates[i].id != 0) {
animateToDrawCount++;
}
}
boolean useAlphaLayer = currentStyle == 0 || currentStyle == 1 || currentStyle == 3 || currentStyle == 4 || currentStyle == 5 || currentStyle == STYLE_GROUP_CALL_TOOLTIP || currentStyle == STYLE_MESSAGE_SEEN;
if (useAlphaLayer) {
float padding = currentStyle == STYLE_GROUP_CALL_TOOLTIP ? AndroidUtilities.dp(16) : 0;
canvas.saveLayerAlpha(-padding, -padding, width + padding, height + padding, 255, Canvas.ALL_SAVE_FLAG);
}
for (int a = 2; a >= 0; a--) {
for (int k = 0; k < 2; k++) {
if (k == 0 && transitionProgress == 1f) {
continue;
}
DrawingState[] states = k == 0 ? animatingStates : currentStates;
if (k == 1 && transitionProgress != 1f && states[a].animationType != DrawingState.ANIMATION_TYPE_OUT) {
continue;
}
ImageReceiver imageReceiver = states[a].imageReceiver;
if (!imageReceiver.hasImageSet()) {
continue;
}
if (k == 0) {
int toAx = centered ? (width - animateToDrawCount * toAdd - AndroidUtilities.dp(bigAvatars ? 8 : 4)) / 2 : startPadding;
imageReceiver.setImageX(toAx + toAdd * a);
} else {
imageReceiver.setImageX(ax + toAdd * a);
}
if (currentStyle == 0 || currentStyle == STYLE_GROUP_CALL_TOOLTIP || currentStyle == STYLE_MESSAGE_SEEN) {
imageReceiver.setImageY((height - size) / 2f);
} else {
imageReceiver.setImageY(AndroidUtilities.dp(currentStyle == 4 ? 8 : 6));
}
boolean needRestore = false;
float alpha = 1f;
if (transitionProgress != 1f) {
if (states[a].animationType == DrawingState.ANIMATION_TYPE_OUT) {
canvas.save();
canvas.scale(1f - transitionProgress, 1f - transitionProgress, imageReceiver.getCenterX(), imageReceiver.getCenterY());
needRestore = true;
alpha = 1f - transitionProgress;
} else if (states[a].animationType == DrawingState.ANIMATION_TYPE_IN) {
canvas.save();
canvas.scale(transitionProgress, transitionProgress, imageReceiver.getCenterX(), imageReceiver.getCenterY());
alpha = transitionProgress;
needRestore = true;
} else if (states[a].animationType == DrawingState.ANIMATION_TYPE_MOVE) {
int toAx = centered ? (width - animateToDrawCount * toAdd - AndroidUtilities.dp(bigAvatars ? 8 : 4)) / 2 : startPadding;
int toX = toAx + toAdd * a;
int fromX = ax + toAdd * states[a].moveFromIndex;
imageReceiver.setImageX((int) (toX * transitionProgress + fromX * (1f - transitionProgress)));
} else if (states[a].animationType == DrawingState.ANIMATION_TYPE_NONE && centered) {
int toAx = (width - animateToDrawCount * toAdd - AndroidUtilities.dp(bigAvatars ? 8 : 4)) / 2;
int toX = toAx + toAdd * a;
int fromX = ax + toAdd * a;
imageReceiver.setImageX((int) (toX * transitionProgress + fromX * (1f - transitionProgress)));
}
}
alpha *= overrideAlpha;
float avatarScale = 1f;
if (a != states.length - 1) {
if (currentStyle == 1 || currentStyle == 3 || currentStyle == 5) {
canvas.drawCircle(imageReceiver.getCenterX(), imageReceiver.getCenterY(), AndroidUtilities.dp(13), xRefP);
if (states[a].wavesDrawable == null) {
if (currentStyle == 5) {
states[a].wavesDrawable = new GroupCallUserCell.AvatarWavesDrawable(AndroidUtilities.dp(14), AndroidUtilities.dp(16));
} else {
states[a].wavesDrawable = new GroupCallUserCell.AvatarWavesDrawable(AndroidUtilities.dp(17), AndroidUtilities.dp(21));
}
}
if (currentStyle == 5) {
states[a].wavesDrawable.setColor(ColorUtils.setAlphaComponent(Theme.getColor(Theme.key_voipgroup_speakingText), (int) (255 * 0.3f * alpha)));
}
if (states[a].participant != null && states[a].participant.amplitude > 0) {
states[a].wavesDrawable.setShowWaves(true, parent);
float amplitude = states[a].participant.amplitude * 15f;
states[a].wavesDrawable.setAmplitude(amplitude);
} else {
states[a].wavesDrawable.setShowWaves(false, parent);
}
if (currentStyle == 5 && (SystemClock.uptimeMillis() - states[a].participant.lastSpeakTime) > 500) {
updateDelegate.run();
}
states[a].wavesDrawable.update();
if (currentStyle == 5) {
states[a].wavesDrawable.draw(canvas, imageReceiver.getCenterX(), imageReceiver.getCenterY(), parent);
invalidate();
}
avatarScale = states[a].wavesDrawable.getAvatarScale();
} else if (currentStyle == 4 || currentStyle == STYLE_GROUP_CALL_TOOLTIP) {
canvas.drawCircle(imageReceiver.getCenterX(), imageReceiver.getCenterY(), AndroidUtilities.dp(17), xRefP);
if (states[a].wavesDrawable == null) {
states[a].wavesDrawable = new GroupCallUserCell.AvatarWavesDrawable(AndroidUtilities.dp(17), AndroidUtilities.dp(21));
}
if (currentStyle == STYLE_GROUP_CALL_TOOLTIP) {
states[a].wavesDrawable.setColor(ColorUtils.setAlphaComponent(Theme.getColor(Theme.key_voipgroup_speakingText), (int) (255 * 0.3f * alpha)));
} else {
states[a].wavesDrawable.setColor(ColorUtils.setAlphaComponent(Theme.getColor(Theme.key_voipgroup_listeningText), (int) (255 * 0.3f * alpha)));
}
long currentTime = System.currentTimeMillis();
if (currentTime - states[a].lastUpdateTime > 100) {
states[a].lastUpdateTime = currentTime;
if (currentStyle == STYLE_GROUP_CALL_TOOLTIP) {
if (states[a].participant != null && states[a].participant.amplitude > 0) {
states[a].wavesDrawable.setShowWaves(true, parent);
float amplitude = states[a].participant.amplitude * 15f;
states[a].wavesDrawable.setAmplitude(amplitude);
} else {
states[a].wavesDrawable.setShowWaves(false, parent);
}
} else {
if (ConnectionsManager.getInstance(UserConfig.selectedAccount).getCurrentTime() - states[a].lastSpeakTime <= 5) {
states[a].wavesDrawable.setShowWaves(true, parent);
states[a].wavesDrawable.setAmplitude(random.nextInt() % 100);
} else {
states[a].wavesDrawable.setShowWaves(false, parent);
states[a].wavesDrawable.setAmplitude(0);
}
}
}
states[a].wavesDrawable.update();
states[a].wavesDrawable.draw(canvas, imageReceiver.getCenterX(), imageReceiver.getCenterY(), parent);
avatarScale = states[a].wavesDrawable.getAvatarScale();
} else {
float rad = getSize() / 2f + AndroidUtilities.dp(2);
if (useAlphaLayer) {
canvas.drawCircle(imageReceiver.getCenterX(), imageReceiver.getCenterY(), rad, xRefP);
} else {
int paintAlpha = paint.getAlpha();
if (alpha != 1f) {
paint.setAlpha((int) (paintAlpha * alpha));
}
canvas.drawCircle(imageReceiver.getCenterX(), imageReceiver.getCenterY(), rad, paint);
if (alpha != 1f) {
paint.setAlpha(paintAlpha);
}
}
}
}
imageReceiver.setAlpha(alpha);
if (avatarScale != 1f) {
canvas.save();
canvas.scale(avatarScale, avatarScale, imageReceiver.getCenterX(), imageReceiver.getCenterY());
imageReceiver.draw(canvas);
canvas.restore();
} else {
imageReceiver.draw(canvas);
}
if (needRestore) {
canvas.restore();
}
}
}
if (useAlphaLayer) {
canvas.restore();
}
}
private int getSize() {
if (overrideSize != 0) {
return overrideSize;
}
boolean bigAvatars = currentStyle == 4 || currentStyle == STYLE_GROUP_CALL_TOOLTIP;
return AndroidUtilities.dp(bigAvatars ? 32 : 24);
}
public void onDetachedFromWindow() {
wasDraw = false;
for (int a = 0; a < 3; a++) {
currentStates[a].imageReceiver.onDetachedFromWindow();
animatingStates[a].imageReceiver.onDetachedFromWindow();
}
if (currentStyle == 3) {
Theme.getFragmentContextViewWavesDrawable().setAmplitude(0);
}
}
public void onAttachedToWindow() {
for (int a = 0; a < 3; a++) {
currentStates[a].imageReceiver.onAttachedToWindow();
animatingStates[a].imageReceiver.onAttachedToWindow();
}
}
public void setCentered(boolean centered) {
this.centered = centered;
}
public void setCount(int count) {
this.count = count;
if (parent != null) {
parent.requestLayout();
}
}
public void reset() {
for (int i = 0; i < animatingStates.length; ++i) {
setObject(0, 0, null);
}
}
}

View File

@ -1,486 +1,77 @@
package org.telegram.ui.Components;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import android.widget.FrameLayout;
import android.view.View;
import androidx.core.graphics.ColorUtils;
import androidx.annotation.NonNull;
import org.telegram.messenger.AccountInstance;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.DialogObject;
import org.telegram.messenger.ImageReceiver;
import org.telegram.messenger.MessageObject;
import org.telegram.messenger.MessagesController;
import org.telegram.messenger.UserConfig;
import org.telegram.messenger.voip.VoIPService;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.TLObject;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Cells.GroupCallUserCell;
import java.util.Random;
public class AvatarsImageView extends View {
public class AvatarsImageView extends FrameLayout {
public final AvatarsDarawable avatarsDarawable;
public final static int STYLE_GROUP_CALL_TOOLTIP = 10;
public final static int STYLE_MESSAGE_SEEN = 11;
DrawingState[] currentStates = new DrawingState[3];
DrawingState[] animatingStates = new DrawingState[3];
boolean wasDraw;
float transitionProgress = 1f;
ValueAnimator transitionProgressAnimator;
boolean updateAfterTransition;
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint xRefP = new Paint(Paint.ANTI_ALIAS_FLAG);
Runnable updateDelegate;
int currentStyle;
boolean centered;
private boolean isInCall;
protected int count;
public void commitTransition(boolean animated) {
if (!wasDraw || !animated) {
transitionProgress = 1f;
swapStates();
return;
}
DrawingState[] removedStates = new DrawingState[3];
boolean changed = false;
for (int i = 0; i < 3; i++) {
removedStates[i] = currentStates[i];
if (currentStates[i].id != animatingStates[i].id) {
changed = true;
} else {
currentStates[i].lastSpeakTime = animatingStates[i].lastSpeakTime;
}
}
if (!changed) {
transitionProgress = 1f;
return;
}
for (int i = 0; i < 3; i++) {
boolean found = false;
for (int j = 0; j < 3; j++) {
if (currentStates[j].id == animatingStates[i].id) {
found = true;
removedStates[j] = null;
if (i == j) {
animatingStates[i].animationType = DrawingState.ANIMATION_TYPE_NONE;
GroupCallUserCell.AvatarWavesDrawable wavesDrawable = animatingStates[i].wavesDrawable;
animatingStates[i].wavesDrawable = currentStates[i].wavesDrawable;
currentStates[i].wavesDrawable = wavesDrawable;
} else {
animatingStates[i].animationType = DrawingState.ANIMATION_TYPE_MOVE;
animatingStates[i].moveFromIndex = j;
}
break;
}
}
if (!found) {
animatingStates[i].animationType = DrawingState.ANIMATION_TYPE_IN;
}
}
for (int i = 0; i < 3; i++) {
if (removedStates[i] != null) {
removedStates[i].animationType = DrawingState.ANIMATION_TYPE_OUT;
}
}
if (transitionProgressAnimator != null) {
transitionProgressAnimator.cancel();
}
transitionProgress = 0;
transitionProgressAnimator = ValueAnimator.ofFloat(0, 1f);
transitionProgressAnimator.addUpdateListener(valueAnimator -> {
transitionProgress = (float) valueAnimator.getAnimatedValue();
invalidate();
});
transitionProgressAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (transitionProgressAnimator != null) {
transitionProgress = 1f;
swapStates();
if (updateAfterTransition) {
updateAfterTransition = false;
if (updateDelegate != null) {
updateDelegate.run();
}
}
invalidate();
}
transitionProgressAnimator = null;
}
});
transitionProgressAnimator.setDuration(220);
transitionProgressAnimator.setInterpolator(CubicBezierInterpolator.DEFAULT);
transitionProgressAnimator.start();
invalidate();
}
private void swapStates() {
for (int i = 0; i < 3; i++) {
DrawingState state = currentStates[i];
currentStates[i] = animatingStates[i];
animatingStates[i] = state;
}
}
public void updateAfterTransitionEnd() {
updateAfterTransition = true;
}
public void setDelegate(Runnable delegate) {
updateDelegate = delegate;
}
public void setStyle(int currentStyle) {
this.currentStyle = currentStyle;
invalidate();
}
private static class DrawingState {
public static final int ANIMATION_TYPE_NONE = -1;
public static final int ANIMATION_TYPE_IN = 0;
public static final int ANIMATION_TYPE_OUT = 1;
public static final int ANIMATION_TYPE_MOVE = 2;
private AvatarDrawable avatarDrawable;
private GroupCallUserCell.AvatarWavesDrawable wavesDrawable;
private long lastUpdateTime;
private long lastSpeakTime;
private ImageReceiver imageReceiver;
TLRPC.TL_groupCallParticipant participant;
private long id;
private int animationType;
private int moveFromIndex;
}
Random random = new Random();
public AvatarsImageView(Context context, boolean inCall) {
public AvatarsImageView(@NonNull Context context, boolean inCall) {
super(context);
for (int a = 0; a < 3; a++) {
currentStates[a] = new DrawingState();
currentStates[a].imageReceiver = new ImageReceiver(this);
currentStates[a].imageReceiver.setRoundRadius(AndroidUtilities.dp(12));
currentStates[a].avatarDrawable = new AvatarDrawable();
currentStates[a].avatarDrawable.setTextSize(AndroidUtilities.dp(12));
animatingStates[a] = new DrawingState();
animatingStates[a].imageReceiver = new ImageReceiver(this);
animatingStates[a].imageReceiver.setRoundRadius(AndroidUtilities.dp(12));
animatingStates[a].avatarDrawable = new AvatarDrawable();
animatingStates[a].avatarDrawable.setTextSize(AndroidUtilities.dp(12));
}
isInCall = inCall;
setWillNotDraw(false);
xRefP.setColor(0);
xRefP.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
public void setObject(int index, int account, TLObject object) {
animatingStates[index].id = 0;
animatingStates[index].participant = null;
if (object == null) {
animatingStates[index].imageReceiver.setImageBitmap((Drawable) null);
invalidate();
return;
}
TLRPC.User currentUser = null;
TLRPC.Chat currentChat = null;
animatingStates[index].lastSpeakTime = -1;
if (object instanceof TLRPC.TL_groupCallParticipant) {
TLRPC.TL_groupCallParticipant participant = (TLRPC.TL_groupCallParticipant) object;
animatingStates[index].participant = participant;
long id = MessageObject.getPeerId(participant.peer);
if (DialogObject.isUserDialog(id)) {
currentUser = MessagesController.getInstance(account).getUser(id);
animatingStates[index].avatarDrawable.setInfo(currentUser);
} else {
currentChat = MessagesController.getInstance(account).getChat(-id);
animatingStates[index].avatarDrawable.setInfo(currentChat);
}
if (currentStyle == 4) {
if (id == AccountInstance.getInstance(account).getUserConfig().getClientUserId()) {
animatingStates[index].lastSpeakTime = 0;
} else {
if (isInCall) {
animatingStates[index].lastSpeakTime = participant.lastActiveDate;
} else {
animatingStates[index].lastSpeakTime = participant.active_date;
}
}
} else {
animatingStates[index].lastSpeakTime = participant.active_date;
}
animatingStates[index].id = id;
} else if (object instanceof TLRPC.User) {
currentUser = (TLRPC.User) object;
animatingStates[index].avatarDrawable.setInfo(currentUser);
animatingStates[index].id = currentUser.id;
} else {
currentChat = (TLRPC.Chat) object;
animatingStates[index].avatarDrawable.setInfo(currentChat);
animatingStates[index].id = -currentChat.id;
}
if (currentUser != null) {
animatingStates[index].imageReceiver.setForUserOrChat(currentUser, animatingStates[index].avatarDrawable);
} else {
animatingStates[index].imageReceiver.setForUserOrChat(currentChat, animatingStates[index].avatarDrawable);
}
boolean bigAvatars = currentStyle == 4 || currentStyle == STYLE_GROUP_CALL_TOOLTIP;
animatingStates[index].imageReceiver.setRoundRadius(AndroidUtilities.dp(bigAvatars ? 16 : 12));
int size = AndroidUtilities.dp(bigAvatars ? 32 : 24);
animatingStates[index].imageReceiver.setImageCoords(0, 0, size, size);
invalidate();
}
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
wasDraw = true;
boolean bigAvatars = currentStyle == 4 || currentStyle == STYLE_GROUP_CALL_TOOLTIP;
int size = AndroidUtilities.dp(bigAvatars ? 32 : 24);
int toAdd;
if (currentStyle == STYLE_MESSAGE_SEEN) {
toAdd = AndroidUtilities.dp(12);
} else {
toAdd = AndroidUtilities.dp(bigAvatars ? 24 : 20);
}
int drawCount = 0;
for (int i = 0; i < 3; i++) {
if (currentStates[i].id != 0) {
drawCount++;
}
}
int startPadding = (currentStyle == 0 || currentStyle == STYLE_GROUP_CALL_TOOLTIP || currentStyle == STYLE_MESSAGE_SEEN) ? 0 : AndroidUtilities.dp(10);
int ax = centered ? (getMeasuredWidth() - drawCount * toAdd - AndroidUtilities.dp(bigAvatars ? 8 : 4)) / 2 : startPadding;
boolean isMuted = VoIPService.getSharedInstance() != null && VoIPService.getSharedInstance().isMicMute();
if (currentStyle == 4) {
paint.setColor(Theme.getColor(Theme.key_inappPlayerBackground));
} else if (currentStyle != 3) {
paint.setColor(Theme.getColor(isMuted ? Theme.key_returnToCallMutedBackground : Theme.key_returnToCallBackground));
}
int animateToDrawCount = 0;
for (int i = 0; i < 3; i++) {
if (animatingStates[i].id != 0) {
animateToDrawCount++;
}
}
boolean useAlphaLayer = currentStyle == 0 || currentStyle == 1 || currentStyle == 3 || currentStyle == 4 || currentStyle == 5 || currentStyle == STYLE_GROUP_CALL_TOOLTIP || currentStyle == STYLE_MESSAGE_SEEN;
if (useAlphaLayer) {
float padding = currentStyle == STYLE_GROUP_CALL_TOOLTIP ? AndroidUtilities.dp(16) : 0;
canvas.saveLayerAlpha(-padding, -padding, getMeasuredWidth() + padding, getMeasuredHeight() + padding, 255, Canvas.ALL_SAVE_FLAG);
}
for (int a = 2; a >= 0; a--) {
for (int k = 0; k < 2; k++) {
if (k == 0 && transitionProgress == 1f) {
continue;
}
DrawingState[] states = k == 0 ? animatingStates : currentStates;
if (k == 1 && transitionProgress != 1f && states[a].animationType != DrawingState.ANIMATION_TYPE_OUT) {
continue;
}
ImageReceiver imageReceiver = states[a].imageReceiver;
if (!imageReceiver.hasImageSet()) {
continue;
}
if (k == 0) {
int toAx = centered ? (getMeasuredWidth() - animateToDrawCount * toAdd - AndroidUtilities.dp(bigAvatars ? 8 : 4)) / 2 : startPadding;
imageReceiver.setImageX(toAx + toAdd * a);
} else {
imageReceiver.setImageX(ax + toAdd * a);
}
if (currentStyle == 0 || currentStyle == STYLE_GROUP_CALL_TOOLTIP || currentStyle == STYLE_MESSAGE_SEEN) {
imageReceiver.setImageY((getMeasuredHeight() - size) / 2f);
} else {
imageReceiver.setImageY(AndroidUtilities.dp(currentStyle == 4 ? 8 : 6));
}
boolean needRestore = false;
float alpha = 1f;
if (transitionProgress != 1f) {
if (states[a].animationType == DrawingState.ANIMATION_TYPE_OUT) {
canvas.save();
canvas.scale(1f - transitionProgress, 1f - transitionProgress, imageReceiver.getCenterX(), imageReceiver.getCenterY());
needRestore = true;
alpha = 1f - transitionProgress;
} else if (states[a].animationType == DrawingState.ANIMATION_TYPE_IN) {
canvas.save();
canvas.scale(transitionProgress, transitionProgress, imageReceiver.getCenterX(), imageReceiver.getCenterY());
alpha = transitionProgress;
needRestore = true;
} else if (states[a].animationType == DrawingState.ANIMATION_TYPE_MOVE) {
int toAx = centered ? (getMeasuredWidth() - animateToDrawCount * toAdd - AndroidUtilities.dp(bigAvatars ? 8 : 4)) / 2 : startPadding;
int toX = toAx + toAdd * a;
int fromX = ax + toAdd * states[a].moveFromIndex;
imageReceiver.setImageX((int) (toX * transitionProgress + fromX * (1f - transitionProgress)));
} else if (states[a].animationType == DrawingState.ANIMATION_TYPE_NONE && centered) {
int toAx = (getMeasuredWidth() - animateToDrawCount * toAdd - AndroidUtilities.dp(bigAvatars ? 8 : 4)) / 2;
int toX = toAx + toAdd * a;
int fromX = ax + toAdd * a;
imageReceiver.setImageX((int) (toX * transitionProgress + fromX * (1f - transitionProgress)));
}
}
float avatarScale = 1f;
if (a != states.length - 1) {
if (currentStyle == 1 || currentStyle == 3 || currentStyle == 5) {
canvas.drawCircle(imageReceiver.getCenterX(), imageReceiver.getCenterY(), AndroidUtilities.dp(13), xRefP);
if (states[a].wavesDrawable == null) {
if (currentStyle == 5) {
states[a].wavesDrawable = new GroupCallUserCell.AvatarWavesDrawable(AndroidUtilities.dp(14), AndroidUtilities.dp(16));
} else {
states[a].wavesDrawable = new GroupCallUserCell.AvatarWavesDrawable(AndroidUtilities.dp(17), AndroidUtilities.dp(21));
}
}
if (currentStyle == 5) {
states[a].wavesDrawable.setColor(ColorUtils.setAlphaComponent(Theme.getColor(Theme.key_voipgroup_speakingText), (int) (255 * 0.3f * alpha)));
}
if (states[a].participant != null && states[a].participant.amplitude > 0) {
states[a].wavesDrawable.setShowWaves(true, this);
float amplitude = states[a].participant.amplitude * 15f;
states[a].wavesDrawable.setAmplitude(amplitude);
} else {
states[a].wavesDrawable.setShowWaves(false, this);
}
if (currentStyle == 5 && (SystemClock.uptimeMillis() - states[a].participant.lastSpeakTime) > 500) {
updateDelegate.run();
}
states[a].wavesDrawable.update();
if (currentStyle == 5) {
states[a].wavesDrawable.draw(canvas, imageReceiver.getCenterX(), imageReceiver.getCenterY(), this);
invalidate();
}
avatarScale = states[a].wavesDrawable.getAvatarScale();
} else if (currentStyle == 4 || currentStyle == STYLE_GROUP_CALL_TOOLTIP) {
canvas.drawCircle(imageReceiver.getCenterX(), imageReceiver.getCenterY(), AndroidUtilities.dp(17), xRefP);
if (states[a].wavesDrawable == null) {
states[a].wavesDrawable = new GroupCallUserCell.AvatarWavesDrawable(AndroidUtilities.dp(17), AndroidUtilities.dp(21));
}
if (currentStyle == STYLE_GROUP_CALL_TOOLTIP) {
states[a].wavesDrawable.setColor(ColorUtils.setAlphaComponent(Theme.getColor(Theme.key_voipgroup_speakingText), (int) (255 * 0.3f * alpha)));
} else {
states[a].wavesDrawable.setColor(ColorUtils.setAlphaComponent(Theme.getColor(Theme.key_voipgroup_listeningText), (int) (255 * 0.3f * alpha)));
}
long currentTime = System.currentTimeMillis();
if (currentTime - states[a].lastUpdateTime > 100) {
states[a].lastUpdateTime = currentTime;
if (currentStyle == STYLE_GROUP_CALL_TOOLTIP) {
if (states[a].participant != null && states[a].participant.amplitude > 0) {
states[a].wavesDrawable.setShowWaves(true, this);
float amplitude = states[a].participant.amplitude * 15f;
states[a].wavesDrawable.setAmplitude(amplitude);
} else {
states[a].wavesDrawable.setShowWaves(false, this);
}
} else {
if (ConnectionsManager.getInstance(UserConfig.selectedAccount).getCurrentTime() - states[a].lastSpeakTime <= 5) {
states[a].wavesDrawable.setShowWaves(true, this);
states[a].wavesDrawable.setAmplitude(random.nextInt() % 100);
} else {
states[a].wavesDrawable.setShowWaves(false, this);
states[a].wavesDrawable.setAmplitude(0);
}
}
}
states[a].wavesDrawable.update();
states[a].wavesDrawable.draw(canvas, imageReceiver.getCenterX(), imageReceiver.getCenterY(), this);
avatarScale = states[a].wavesDrawable.getAvatarScale();
} else {
if (useAlphaLayer) {
canvas.drawCircle(imageReceiver.getCenterX(), imageReceiver.getCenterY(), AndroidUtilities.dp(bigAvatars ? 17 : 13), xRefP);
} else {
int paintAlpha = paint.getAlpha();
if (alpha != 1f) {
paint.setAlpha((int) (paintAlpha * alpha));
}
canvas.drawCircle(imageReceiver.getCenterX(), imageReceiver.getCenterY(), AndroidUtilities.dp(bigAvatars ? 17 : 13), paint);
if (alpha != 1f) {
paint.setAlpha(paintAlpha);
}
}
}
}
imageReceiver.setAlpha(alpha);
if (avatarScale != 1f) {
canvas.save();
canvas.scale(avatarScale, avatarScale, imageReceiver.getCenterX(), imageReceiver.getCenterY());
imageReceiver.draw(canvas);
canvas.restore();
} else {
imageReceiver.draw(canvas);
}
if (needRestore) {
canvas.restore();
}
}
}
if (useAlphaLayer) {
canvas.restore();
}
avatarsDarawable = new AvatarsDarawable(this, inCall);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
wasDraw = false;
for (int a = 0; a < 3; a++) {
currentStates[a].imageReceiver.onDetachedFromWindow();
animatingStates[a].imageReceiver.onDetachedFromWindow();
}
if (currentStyle == 3) {
Theme.getFragmentContextViewWavesDrawable().setAmplitude(0);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
avatarsDarawable.width = getMeasuredWidth();
avatarsDarawable.height = getMeasuredHeight();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
for (int a = 0; a < 3; a++) {
currentStates[a].imageReceiver.onAttachedToWindow();
animatingStates[a].imageReceiver.onAttachedToWindow();
}
avatarsDarawable.onAttachedToWindow();
}
public void setCentered(boolean centered) {
this.centered = centered;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
avatarsDarawable.onDraw(canvas);
}
public void setCount(int count) {
this.count = count;
requestLayout();
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
avatarsDarawable.onDetachedFromWindow();
}
public void setStyle(int style) {
avatarsDarawable.setStyle(style);
}
public void setDelegate(Runnable delegate) {
avatarsDarawable.setDelegate(delegate);
}
public void setObject(int a, int currentAccount, TLObject object) {
avatarsDarawable.setObject(a, currentAccount, object);
}
public void reset() {
for (int i = 0; i < animatingStates.length; ++i) {
setObject(0, 0, null);
}
avatarsDarawable.reset();
}
public void setCount(int usersCount) {
avatarsDarawable.setCount(usersCount);
}
public void commitTransition(boolean animated) {
avatarsDarawable.commitTransition(animated);
}
public void updateAfterTransitionEnd() {
avatarsDarawable.updateAfterTransitionEnd();
}
public void setCentered(boolean centered) {
avatarsDarawable.setCentered(centered);
}
}

View File

@ -61,7 +61,6 @@ import android.text.style.ImageSpan;
import android.util.Property;
import android.util.TypedValue;
import android.view.ActionMode;
import android.view.DisplayCutout;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
@ -144,6 +143,7 @@ import org.telegram.ui.DialogsActivity;
import org.telegram.ui.GroupStickersActivity;
import org.telegram.ui.LaunchActivity;
import org.telegram.ui.PhotoViewer;
import org.telegram.ui.ProfileActivity;
import org.telegram.ui.StickersActivity;
import java.io.ByteArrayInputStream;
@ -1879,6 +1879,9 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
@Override
public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
final InputConnection ic = super.onCreateInputConnection(editorInfo);
if (ic == null) {
return null;
}
try {
EditorInfoCompat.setContentMimeTypes(editorInfo, new String[]{"image/gif", "image/*", "image/jpg", "image/png", "image/webp"});
@ -2015,46 +2018,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
ArrayList<Object> entries = new ArrayList<>();
entries.add(photoEntry);
AndroidUtilities.runOnUIThread(() -> {
PhotoViewer.getInstance().setParentActivity(parentActivity, resourcesProvider);
PhotoViewer.getInstance().openPhotoForSelect(entries, 0, 2, false, new PhotoViewer.EmptyPhotoViewerProvider() {
boolean sending;
@Override
public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo, boolean notify, int scheduleDate, boolean forceDocument) {
ArrayList<SendMessagesHelper.SendingMediaInfo> photos = new ArrayList<>();
SendMessagesHelper.SendingMediaInfo info = new SendMessagesHelper.SendingMediaInfo();
if (!photoEntry.isVideo && photoEntry.imagePath != null) {
info.path = photoEntry.imagePath;
} else if (photoEntry.path != null) {
info.path = photoEntry.path;
}
info.thumbPath = photoEntry.thumbPath;
info.isVideo = photoEntry.isVideo;
info.caption = photoEntry.caption != null ? photoEntry.caption.toString() : null;
info.entities = photoEntry.entities;
info.masks = photoEntry.stickers;
info.ttl = photoEntry.ttl;
info.videoEditedInfo = videoEditedInfo;
info.canDeleteAfter = true;
photos.add(info);
photoEntry.reset();
sending = true;
SendMessagesHelper.prepareSendingMedia(accountInstance, photos, dialog_id, replyingMessageObject, getThreadMessage(), null, false, false, editingMessageObject, notify, scheduleDate);
if (delegate != null) {
delegate.onMessageSend(null, true, scheduleDate);
}
}
@Override
public void willHidePhotoViewer() {
if (!sending) {
try {
file.delete();
} catch (Throwable ignore) {
}
}
}
}, parentFragment);
openPhotoViewerForEdit(entries, file);
});
} catch (Throwable e) {
e.printStackTrace();
@ -2062,6 +2026,66 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
});
}
private void openPhotoViewerForEdit(ArrayList<Object> entries, File sourceFile) {
MediaController.PhotoEntry photoEntry = (MediaController.PhotoEntry) entries.get(0);
if (keyboardVisible) {
AndroidUtilities.hideKeyboard(messageEditText);
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
openPhotoViewerForEdit(entries, sourceFile);
}
}, 100);
return;
}
PhotoViewer.getInstance().setParentActivity(parentActivity, resourcesProvider);
PhotoViewer.getInstance().openPhotoForSelect(entries, 0, 2, false, new PhotoViewer.EmptyPhotoViewerProvider() {
boolean sending;
@Override
public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo, boolean notify, int scheduleDate, boolean forceDocument) {
ArrayList<SendMessagesHelper.SendingMediaInfo> photos = new ArrayList<>();
SendMessagesHelper.SendingMediaInfo info = new SendMessagesHelper.SendingMediaInfo();
if (!photoEntry.isVideo && photoEntry.imagePath != null) {
info.path = photoEntry.imagePath;
} else if (photoEntry.path != null) {
info.path = photoEntry.path;
}
info.thumbPath = photoEntry.thumbPath;
info.isVideo = photoEntry.isVideo;
info.caption = photoEntry.caption != null ? photoEntry.caption.toString() : null;
info.entities = photoEntry.entities;
info.masks = photoEntry.stickers;
info.ttl = photoEntry.ttl;
info.videoEditedInfo = videoEditedInfo;
info.canDeleteAfter = true;
photos.add(info);
photoEntry.reset();
sending = true;
SendMessagesHelper.prepareSendingMedia(accountInstance, photos, dialog_id, replyingMessageObject, getThreadMessage(), null, false, false, editingMessageObject, notify, scheduleDate);
if (delegate != null) {
delegate.onMessageSend(null, true, scheduleDate);
}
}
@Override
public void willHidePhotoViewer() {
if (!sending) {
try {
sourceFile.delete();
} catch (Throwable ignore) {
}
}
}
@Override
public boolean canCaptureMorePhotos() {
return false;
}
}, parentFragment);
}
@Override
protected Theme.ResourcesProvider getResourcesProvider() {
return resourcesProvider;
@ -2071,6 +2095,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
@Override
public void onSpansChanged() {
messageEditText.invalidateEffects();
if (delegate != null) {
delegate.onTextSpansChanged(messageEditText.getText());
}
@ -5374,7 +5399,6 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
messageEditText.setAlpha(1f);
messageEditText.setTranslationX(0);
messageEditText.requestFocus();
updateSendAsButton();
}
});
@ -5504,7 +5528,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
CharSequence[] message = new CharSequence[]{AndroidUtilities.getTrimmedString(messageEditText.getText())};
ArrayList<TLRPC.MessageEntity> entities = MediaDataController.getInstance(currentAccount).getEntities(message, supportsSendingNewEntities());
if (!TextUtils.equals(message[0], editingMessageObject.messageText) || entities != null && !entities.isEmpty() || entities == null && !editingMessageObject.messageOwner.entities.isEmpty() || editingMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) {
if (!TextUtils.equals(message[0], editingMessageObject.messageText) || entities != null && !entities.isEmpty() || (entities == null || entities.isEmpty()) && !editingMessageObject.messageOwner.entities.isEmpty() || editingMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) {
editingMessageObject.editingMessage = message[0];
editingMessageObject.editingMessageEntities = entities;
editingMessageObject.editingMessageSearchWebPage = messageWebPageSearch;
@ -7171,6 +7195,10 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
MediaDataController.addStyleToText(new TextStyleSpan(run), entity.offset, entity.offset + entity.length, stringBuilder, true);
} else if (entity instanceof TLRPC.TL_messageEntityTextUrl) {
stringBuilder.setSpan(new URLSpanReplacement(entity.url), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} else if (entity instanceof TLRPC.TL_messageEntitySpoiler) {
TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun();
run.flags |= TextStyleSpan.FLAG_STYLE_SPOILER;
MediaDataController.addStyleToText(new TextStyleSpan(run), entity.offset, entity.offset + entity.length, stringBuilder, true);
}
}
} catch (Exception e) {
@ -7598,7 +7626,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
}
}
boolean wasVisible = senderSelectView.getVisibility() == View.VISIBLE;
boolean isVisible = delegate.getSendAsPeers() != null && defPeer != null && delegate.getSendAsPeers().peers.size() > 1 && !isEditingMessage() && !isRecordingAudioVideo() && (recordedAudioPanel == null || recordedAudioPanel.getVisibility() == View.GONE);
boolean isVisible = delegate.getSendAsPeers() != null && defPeer != null && delegate.getSendAsPeers().peers.size() > 1 && !isEditingMessage() && !isRecordingAudioVideo();
int pad = AndroidUtilities.dp(2);
MarginLayoutParams params = (MarginLayoutParams) senderSelectView.getLayoutParams();
float sA = isVisible ? 0 : 1;
@ -7614,8 +7642,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
}
if (parentFragment.getOtherSameChatsDiff() == 0 && parentFragment.fragmentOpened) {
ValueAnimator anim = ValueAnimator.ofFloat(0, 1).setDuration(220);
anim.setInterpolator(CubicBezierInterpolator.DEFAULT);
ValueAnimator anim = ValueAnimator.ofFloat(0, 1).setDuration(150);
anim.addUpdateListener(animation -> {
float val = (float) animation.getAnimatedValue();
@ -7945,6 +7972,13 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe
});
parentFragment.presentFragment(fragment);
}
} else if (button instanceof TLRPC.TL_keyboardButtonUserProfile) {
if (MessagesController.getInstance(currentAccount).getUser(button.user_id) != null) {
Bundle args = new Bundle();
args.putLong("user_id", button.user_id);
ProfileActivity fragment = new ProfileActivity(args);
parentFragment.presentFragment(fragment);
}
}
return true;
}

View File

@ -28,12 +28,14 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Vibrator;
import android.text.Editable;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.style.ImageSpan;
import android.text.util.Linkify;
import android.util.Property;
import android.util.TypedValue;
import android.view.Gravity;
@ -44,6 +46,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.OvershootInterpolator;
@ -257,7 +260,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N
}
void applyCaption(String text) {
void applyCaption(CharSequence text) {
}
@ -430,6 +433,8 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N
public AttachButton(Context context) {
super(context);
setWillNotDraw(false);
setFocusable(true);
setFocusableInTouchMode(true);
imageView = new RLottieImageView(context) {
@Override
@ -448,6 +453,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N
textView.setTextColor(getThemedColor(Theme.key_dialogTextGray2));
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12);
textView.setLineSpacing(-AndroidUtilities.dp(2), 1.0f);
textView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 0, 62, 0, 0));
}
@ -1847,7 +1853,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N
if (commentTextView.length() <= 0) {
return;
}
currentAttachLayout.applyCaption(commentTextView.getText().toString());
currentAttachLayout.applyCaption(commentTextView.getText());
}
private void sendPressed(boolean notify, int scheduleDate) {

View File

@ -344,39 +344,11 @@ public class ChatAttachAlertDocumentLayout extends ChatAttachAlert.AttachAlertLa
ListItem item = (ListItem) object;
File file = item.file;
boolean isExternalStorageManager = false;
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// isExternalStorageManager = Environment.isExternalStorageManager();
// }
if (!BuildVars.NO_SCOPED_STORAGE && (item.icon == R.drawable.files_storage || item.icon == R.drawable.files_internal)) {
//if (SharedConfig.dontAskManageStorage) {
delegate.startDocumentSelectActivity();
/*} else {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTopImage(R.drawable.doc_big, Theme.getColor(Theme.key_dialogTopBackground));
builder.setMessage(AndroidUtilities.replaceTags(LocaleController.getString("ManageAllFilesRational", R.string.ManageAllFilesRational)));
TextCheckBoxCell textCheckBoxCell = new TextCheckBoxCell(context, true, true);
textCheckBoxCell.setTextAndCheck(LocaleController.getString("DontAskAgain", R.string.DontAskAgain), false, false);
textCheckBoxCell.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
textCheckBoxCell.setChecked(!textCheckBoxCell.isChecked());
}
});
builder.setView(textCheckBoxCell);
builder.setPositiveButton(LocaleController.getString("Allow", R.string.Allow), (i1, i2) -> {
Uri uri = Uri.parse("package:" + BuildConfig.APPLICATION_ID);
context.startActivity(new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, uri));
});
builder.setNegativeButton(LocaleController.getString("UseFileManger", R.string.UseFileManger), (i1, i2) -> {
if (textCheckBoxCell.isChecked()) {
SharedConfig.setDontAskManageStorage(true);
}
delegate.startDocumentSelectActivity();
});
builder.show();
}*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
isExternalStorageManager = Environment.isExternalStorageManager();
}
if (!BuildVars.NO_SCOPED_STORAGE && (item.icon == R.drawable.files_storage || item.icon == R.drawable.files_internal) && !isExternalStorageManager) {
delegate.startDocumentSelectActivity();
} else if (file == null) {
if (item.icon == R.drawable.files_gallery) {
HashMap<Object, Object> selectedPhotos = new HashMap<>();

View File

@ -66,11 +66,13 @@ import org.telegram.messenger.FileLog;
import org.telegram.messenger.ImageReceiver;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MediaController;
import org.telegram.messenger.MediaDataController;
import org.telegram.messenger.MessageObject;
import org.telegram.messenger.NotificationCenter;
import org.telegram.messenger.R;
import org.telegram.messenger.SendMessagesHelper;
import org.telegram.messenger.SharedConfig;
import org.telegram.messenger.UserConfig;
import org.telegram.messenger.Utilities;
import org.telegram.messenger.VideoEditedInfo;
import org.telegram.messenger.camera.CameraController;
@ -2530,15 +2532,17 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou
}
@Override
void applyCaption(String text) {
void applyCaption(CharSequence text) {
int imageId = (Integer) selectedPhotosOrder.get(0);
Object entry = selectedPhotos.get(imageId);
if (entry instanceof MediaController.PhotoEntry) {
MediaController.PhotoEntry photoEntry = (MediaController.PhotoEntry) entry;
photoEntry.caption = text;
photoEntry.entities = MediaDataController.getInstance(UserConfig.selectedAccount).getEntities(new CharSequence[] {text}, false);
} else if (entry instanceof MediaController.SearchImage) {
MediaController.SearchImage searchImage = (MediaController.SearchImage) entry;
searchImage.caption = text;
searchImage.entities = MediaDataController.getInstance(UserConfig.selectedAccount).getEntities(new CharSequence[] {text}, false);
}
}

View File

@ -0,0 +1,30 @@
package org.telegram.ui.Components;
import android.content.Context;
import android.view.View;
import android.widget.LinearLayout;
import org.telegram.ui.ActionBar.ActionBarPopupWindow;
public class ChatScrimPopupContainerLayout extends LinearLayout {
public View reactionsLayout;
public ActionBarPopupWindow.ActionBarPopupWindowLayout popupWindowLayout;
public ChatScrimPopupContainerLayout(Context context) {
super(context);
setOrientation(LinearLayout.VERTICAL);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (reactionsLayout != null && popupWindowLayout != null && popupWindowLayout.getSwipeBack() != null && reactionsLayout.getLayoutParams().width != LayoutHelper.WRAP_CONTENT) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthDiff = popupWindowLayout.getSwipeBack().getMeasuredWidth() - popupWindowLayout.getSwipeBack().getChildAt(0).getMeasuredWidth();
((LayoutParams)reactionsLayout.getLayoutParams()).rightMargin = widthDiff;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}

View File

@ -5,6 +5,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
@ -883,6 +884,7 @@ public class ChatThemeBottomSheet extends BottomSheet implements NotificationCen
public int themeIndex;
public boolean isSelected;
public float animationProgress = 1f;
public Bitmap icon;
public ChatThemeItem(EmojiThemes chatTheme) {
this.chatTheme = chatTheme;

View File

@ -69,6 +69,10 @@ public class CheckBox2 extends View {
checkBoxBase.onAttachedToWindow();
}
public void setDuration(long duration) {
checkBoxBase.animationDuration = duration;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();

View File

@ -176,6 +176,7 @@ public class CheckBoxBase {
}
}
public long animationDuration = 200;
private void animateToCheckedState(boolean newCheckedState) {
checkAnimator = ObjectAnimator.ofFloat(this, "progress", newCheckedState ? 1 : 0);
checkAnimator.addListener(new AnimatorListenerAdapter() {
@ -190,7 +191,7 @@ public class CheckBoxBase {
}
});
checkAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT);
checkAnimator.setDuration(200);
checkAnimator.setDuration(animationDuration);
checkAnimator.start();
}

View File

@ -16,8 +16,6 @@ import android.view.Gravity;
import android.view.View;
import android.view.animation.OvershootInterpolator;
import com.google.android.exoplayer2.util.Log;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.ui.ActionBar.Theme;
@ -30,7 +28,7 @@ public class CounterView extends View {
super(context);
this.resourcesProvider = resourcesProvider;
setVisibility(View.GONE);
counterDrawable = new CounterDrawable(this, resourcesProvider);
counterDrawable = new CounterDrawable(this, true, resourcesProvider);
counterDrawable.updateVisibility = true;
}
@ -77,7 +75,7 @@ public class CounterView extends View {
int animationType = -1;
public Paint circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public Paint circlePaint;
public TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
public RectF rectF = new RectF();
public boolean addServiceGradient;
@ -85,7 +83,7 @@ public class CounterView extends View {
int currentCount;
private boolean countAnimationIncrement;
private ValueAnimator countAnimator;
private float countChangeProgress = 1f;
public float countChangeProgress = 1f;
private StaticLayout countLayout;
private StaticLayout countOldLayout;
private StaticLayout countAnimationStableLayout;
@ -107,21 +105,27 @@ public class CounterView extends View {
private boolean reverseAnimation;
public float horizontalPadding;
private boolean drawBackground = true;
boolean updateVisibility;
public boolean updateVisibility;
private View parent;
public final static int TYPE_DEFAULT = 0;
public final static int TYPE_CHAT_PULLING_DOWN = 1;
public final static int TYPE_CHAT_REACTIONS = 2;
int type = TYPE_DEFAULT;
private final Theme.ResourcesProvider resourcesProvider;
public CounterDrawable(View parent, Theme.ResourcesProvider resourcesProvider) {
public CounterDrawable(View parent, boolean drawBackground, Theme.ResourcesProvider resourcesProvider) {
this.parent = parent;
this.resourcesProvider = resourcesProvider;
circlePaint.setColor(Color.BLACK);
this.drawBackground = drawBackground;
if (drawBackground) {
circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
circlePaint.setColor(Color.BLACK);
}
textPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
textPaint.setTextSize(AndroidUtilities.dp(13));
}
@ -141,9 +145,11 @@ public class CounterView extends View {
float countTop = (lastH - AndroidUtilities.dp(23)) / 2f;
updateX(countWidth);
rectF.set(x, countTop, x + countWidth + AndroidUtilities.dp(11), countTop + AndroidUtilities.dp(23));
canvas.drawRoundRect(rectF, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, circlePaint);
if (addServiceGradient && Theme.hasGradientService()) {
canvas.drawRoundRect(rectF, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, Theme.chat_actionBackgroundGradientDarkenPaint);
if (circlePaint != null && drawBackground) {
canvas.drawRoundRect(rectF, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, circlePaint);
if (addServiceGradient && Theme.hasGradientService()) {
canvas.drawRoundRect(rectF, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, Theme.chat_actionBackgroundGradientDarkenPaint);
}
}
if (countLayout != null) {
canvas.save();
@ -264,14 +270,14 @@ public class CounterView extends View {
}
public void draw(Canvas canvas) {
if (type != TYPE_CHAT_PULLING_DOWN) {
if (type != TYPE_CHAT_PULLING_DOWN && type != TYPE_CHAT_REACTIONS) {
int textColor = getThemedColor(textColorKey);
int circleColor = getThemedColor(circleColorKey);
if (this.textColor != textColor) {
this.textColor = textColor;
textPaint.setColor(textColor);
}
if (this.circleColor != circleColor) {
if (circlePaint != null && this.circleColor != circleColor) {
this.circleColor = circleColor;
circlePaint.setColor(circleColor);
}
@ -313,9 +319,11 @@ public class CounterView extends View {
rectF.set(x, countTop, x + countWidth + AndroidUtilities.dp(11), countTop + AndroidUtilities.dp(23));
canvas.save();
canvas.scale(scale, scale, rectF.centerX(), rectF.centerY());
canvas.drawRoundRect(rectF, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, circlePaint);
if (addServiceGradient && Theme.hasGradientService()) {
canvas.drawRoundRect(rectF, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, Theme.chat_actionBackgroundGradientDarkenPaint);
if (drawBackground && circlePaint != null) {
canvas.drawRoundRect(rectF, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, circlePaint);
if (addServiceGradient && Theme.hasGradientService()) {
canvas.drawRoundRect(rectF, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, Theme.chat_actionBackgroundGradientDarkenPaint);
}
}
canvas.clipRect(rectF);
@ -386,19 +394,20 @@ public class CounterView extends View {
}
private void updateX(float countWidth) {
float padding = drawBackground ? AndroidUtilities.dp(5.5f) : 0f;
if (gravity == Gravity.RIGHT) {
countLeft = width - AndroidUtilities.dp(5.5f);
countLeft = width - padding;
if (horizontalPadding != 0) {
countLeft -= Math.max(horizontalPadding + countWidth / 2f, countWidth);
} else {
countLeft -= countWidth;
}
} else if (gravity == Gravity.LEFT) {
countLeft = AndroidUtilities.dp(5.5f);
countLeft = padding;
} else {
countLeft = (int) ((width - countWidth) / 2f);
}
x = countLeft - AndroidUtilities.dp(5.5f);
x = countLeft - padding;
}
public float getCenterX() {

View File

@ -54,7 +54,7 @@ import org.telegram.ui.ActionBar.Theme;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class EditTextBoldCursor extends EditText {
public class EditTextBoldCursor extends EditTextEffects {
private static Field mEditor;
private static Field mShowCursorField;

View File

@ -136,6 +136,12 @@ public class EditTextCaption extends EditTextBoldCursor {
applyTextStyleToSelection(new TextStyleSpan(run));
}
public void makeSelectedSpoiler() {
TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun();
run.flags |= TextStyleSpan.FLAG_STYLE_SPOILER;
applyTextStyleToSelection(new TextStyleSpan(run));
}
public void makeSelectedItalic() {
TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun();
run.flags |= TextStyleSpan.FLAG_STYLE_ITALIC;
@ -512,7 +518,11 @@ public class EditTextCaption extends EditTextBoldCursor {
} else if (itemId == R.id.menu_underline) {
makeSelectedUnderline();
return true;
} else if (itemId == R.id.menu_spoiler) {
makeSelectedSpoiler();
return true;
} else if (itemId == R.id.menu_translate) {
// NekoX
makeSelectedTranslate();
return true;
}
@ -630,6 +640,7 @@ public class EditTextCaption extends EditTextBoldCursor {
}
}
if (hasSelection()) {
infoCompat.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(R.id.menu_spoiler, LocaleController.getString("Spoiler", R.string.Spoiler)));
infoCompat.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(R.id.menu_bold, LocaleController.getString("Bold", R.string.Bold)));
infoCompat.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(R.id.menu_italic, LocaleController.getString("Italic", R.string.Italic)));
infoCompat.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(R.id.menu_mono, LocaleController.getString("Mono", R.string.Mono)));

View File

@ -0,0 +1,273 @@
package org.telegram.ui.Components;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.Region;
import android.text.Editable;
import android.text.Layout;
import android.text.Spannable;
import android.view.MotionEvent;
import android.widget.EditText;
import org.telegram.ui.Components.spoilers.SpoilerEffect;
import org.telegram.ui.Components.spoilers.SpoilersClickDetector;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class EditTextEffects extends EditText {
private final static int SPOILER_TIMEOUT = 10000;
private List<SpoilerEffect> spoilers = new ArrayList<>();
private Stack<SpoilerEffect> spoilersPool = new Stack<>();
private boolean isSpoilersRevealed;
private boolean shouldRevealSpoilersByTouch = true;
private SpoilersClickDetector clickDetector;
private boolean suppressOnTextChanged;
private Path path = new Path();
private int selStart, selEnd;
private float lastRippleX, lastRippleY;
private boolean postedSpoilerTimeout;
private Runnable spoilerTimeout = () -> {
postedSpoilerTimeout = false;
isSpoilersRevealed = false;
invalidateSpoilers();
if (spoilers.isEmpty())
return;
spoilers.get(0).setOnRippleEndCallback(() -> post(() -> setSpoilersRevealed(false, true)));
float rad = (float) Math.sqrt(Math.pow(getWidth(), 2) + Math.pow(getHeight(), 2));
for (SpoilerEffect eff : spoilers) {
eff.startRipple(lastRippleX, lastRippleY, rad, true);
}
};
private Rect rect = new Rect();
public EditTextEffects(Context context) {
super(context);
clickDetector = new SpoilersClickDetector(this, spoilers, this::onSpoilerClicked);
}
private void onSpoilerClicked(SpoilerEffect eff, float x, float y) {
if (isSpoilersRevealed) return;
lastRippleX = x;
lastRippleY = y;
postedSpoilerTimeout = false;
removeCallbacks(spoilerTimeout);
setSpoilersRevealed(true, false);
eff.setOnRippleEndCallback(() -> post(() -> {
invalidateSpoilers();
checkSpoilerTimeout();
}));
float rad = (float) Math.sqrt(Math.pow(getWidth(), 2) + Math.pow(getHeight(), 2));
for (SpoilerEffect ef : spoilers)
ef.startRipple(x, y, rad);
}
@Override
protected void onSelectionChanged(int selStart, int selEnd) {
super.onSelectionChanged(selStart, selEnd);
if (suppressOnTextChanged)
return;
this.selStart = selStart;
this.selEnd = selEnd;
checkSpoilerTimeout();
}
/**
* Checks for spoiler timeout to be posted
*/
private void checkSpoilerTimeout() {
boolean onSpoiler = false;
CharSequence cs = getLayout() != null ? getLayout().getText() : null;
if (cs instanceof Spannable) {
Spannable e = (Spannable) cs;
TextStyleSpan[] spans = e.getSpans(0, e.length(), TextStyleSpan.class);
for (TextStyleSpan span : spans) {
int ss = e.getSpanStart(span), se = e.getSpanEnd(span);
if (span.isSpoiler()) {
if (ss > selStart && se < selEnd || selStart > ss && selStart < se || selEnd > ss && selEnd < se) {
onSpoiler = true;
removeCallbacks(spoilerTimeout);
postedSpoilerTimeout = false;
break;
}
}
}
}
if (isSpoilersRevealed && !onSpoiler && !postedSpoilerTimeout) {
postedSpoilerTimeout = true;
postDelayed(spoilerTimeout, SPOILER_TIMEOUT);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
removeCallbacks(spoilerTimeout);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
invalidateEffects();
}
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
if (!suppressOnTextChanged) {
invalidateEffects();
Layout layout = getLayout();
if (text instanceof Spannable && layout != null) {
int line = layout.getLineForOffset(start);
int x = (int) layout.getPrimaryHorizontal(start);
int y = (int) ((layout.getLineTop(line) + layout.getLineBottom(line)) / 2f);
for (SpoilerEffect eff : spoilers) {
if (eff.getBounds().contains(x, y)) {
int selOffset = lengthAfter - lengthBefore;
selStart += selOffset;
selEnd += selOffset;
onSpoilerClicked(eff, x, y);
break;
}
}
}
}
}
@Override
public void setText(CharSequence text, BufferType type) {
if (!suppressOnTextChanged) {
isSpoilersRevealed = false;
if (spoilersPool != null) // Constructor check
spoilersPool.clear();
}
super.setText(text, type);
}
/**
* Sets if spoilers should be revealed by touch or not
*/
public void setShouldRevealSpoilersByTouch(boolean shouldRevealSpoilersByTouch) {
this.shouldRevealSpoilersByTouch = shouldRevealSpoilersByTouch;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
boolean detector = false;
if (shouldRevealSpoilersByTouch && clickDetector.onTouchEvent(event)) {
int act = event.getActionMasked();
if (act == MotionEvent.ACTION_UP) {
MotionEvent c = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
super.dispatchTouchEvent(c);
c.recycle();
}
detector = true;
}
return super.dispatchTouchEvent(event) || detector;
}
/**
* Sets if spoiler are already revealed or not
*/
public void setSpoilersRevealed(boolean spoilersRevealed, boolean notifyEffects) {
isSpoilersRevealed = spoilersRevealed;
Spannable text = getText();
if (text != null) {
TextStyleSpan[] spans = text.getSpans(0, text.length(), TextStyleSpan.class);
for (TextStyleSpan span : spans) {
if (span.isSpoiler()) {
span.setSpoilerRevealed(spoilersRevealed);
}
}
}
suppressOnTextChanged = true;
setText(text, BufferType.EDITABLE);
setSelection(selStart, selEnd);
suppressOnTextChanged = false;
if (notifyEffects) {
invalidateSpoilers();
}
}
@Override
protected void onDraw(Canvas canvas) {
canvas.save();
path.rewind();
for (SpoilerEffect eff : spoilers) {
Rect bounds = eff.getBounds();
path.addRect(bounds.left, bounds.top, bounds.right, bounds.bottom, Path.Direction.CW);
}
canvas.clipPath(path, Region.Op.DIFFERENCE);
super.onDraw(canvas);
canvas.restore();
canvas.save();
canvas.clipPath(path);
path.rewind();
if (!spoilers.isEmpty())
spoilers.get(0).getRipplePath(path);
canvas.clipPath(path);
canvas.translate(0, -getPaddingTop());
super.onDraw(canvas);
canvas.restore();
rect.set(0, getScrollY(), getWidth(), getScrollY() + getHeight() - getPaddingBottom());
canvas.save();
canvas.clipRect(rect);
for (SpoilerEffect eff : spoilers) {
Rect b = eff.getBounds();
if (rect.top <= b.bottom && rect.bottom >= b.top || b.top <= rect.bottom && b.bottom >= rect.top) {
eff.setColor(getPaint().getColor());
eff.draw(canvas);
}
}
canvas.restore();
}
public void invalidateEffects() {
Editable text = getText();
if (text != null) {
for (TextStyleSpan span : text.getSpans(0, text.length(), TextStyleSpan.class)) {
if (span.isSpoiler()) {
span.setSpoilerRevealed(isSpoilersRevealed);
}
}
}
invalidateSpoilers();
}
private void invalidateSpoilers() {
if (spoilers == null) return; // A null-check for super constructor, because it calls onTextChanged
spoilersPool.addAll(spoilers);
spoilers.clear();
if (isSpoilersRevealed) {
invalidate();
return;
}
Layout layout = getLayout();
if (layout != null && layout.getText() instanceof Spannable) {
SpoilerEffect.addSpoilers(this, spoilersPool, spoilers);
}
invalidate();
}
}

View File

@ -34,6 +34,8 @@ public class FlickerLoadingView extends View {
public final static int MESSAGE_SEEN_TYPE = 13;
public final static int CHAT_THEMES_TYPE = 14;
public final static int MEMBER_REQUESTS_TYPE = 15;
public final static int REACTED_TYPE = 16;
public final static int QR_TYPE = 17;
private int gradientWidth;
private LinearGradient gradient;
@ -125,7 +127,6 @@ public class FlickerLoadingView extends View {
@Override
protected void onDraw(Canvas canvas) {
Paint paint = this.paint;
if (globalGradientView != null) {
if (getParent() != null) {
@ -441,7 +442,7 @@ public class FlickerLoadingView extends View {
canvas.drawCircle(getMeasuredWidth() - AndroidUtilities.dp(8 + 24 + 12 + 12) + AndroidUtilities.dp(13) + AndroidUtilities.dp(12) * i, cy, AndroidUtilities.dp(13f), backgroundPaint);
canvas.drawCircle(getMeasuredWidth() - AndroidUtilities.dp(8 + 24 + 12 + 12) + AndroidUtilities.dp(13) + AndroidUtilities.dp(12) * i, cy, AndroidUtilities.dp(12f), paint);
}
} else if (getViewType() == CHAT_THEMES_TYPE) {
} else if (getViewType() == CHAT_THEMES_TYPE || getViewType() == QR_TYPE) {
int x = AndroidUtilities.dp(12);
int itemWidth = AndroidUtilities.dp(77);
int INNER_RECT_SPACE = AndroidUtilities.dp(4);
@ -455,17 +456,26 @@ public class FlickerLoadingView extends View {
backgroundPaint.setColor(Theme.getColor(Theme.key_dialogBackground));
}
float bubbleTop = INNER_RECT_SPACE + AndroidUtilities.dp(8);
float bubbleLeft = INNER_RECT_SPACE + AndroidUtilities.dp(22);
AndroidUtilities.rectTmp.set(x + AndroidUtilities.dp(4), AndroidUtilities.dp(4), x + itemWidth - AndroidUtilities.dp(4), getMeasuredHeight() - AndroidUtilities.dp(4));
canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(6), AndroidUtilities.dp(6), paint);
rectF.set(x + bubbleLeft, bubbleTop, x + bubbleLeft + BUBBLE_WIDTH, bubbleTop + BUBBLE_HEIGHT);
canvas.drawRoundRect(rectF, rectF.height() * 0.5f, rectF.height() * 0.5f, backgroundPaint);
bubbleLeft = INNER_RECT_SPACE + AndroidUtilities.dp(5);
bubbleTop += BUBBLE_HEIGHT + AndroidUtilities.dp(4);
rectF.set(x + bubbleLeft, bubbleTop, x + bubbleLeft + BUBBLE_WIDTH, bubbleTop + BUBBLE_HEIGHT);
canvas.drawRoundRect(rectF, rectF.height() * 0.5f, rectF.height() * 0.5f, backgroundPaint);
if (getViewType() == CHAT_THEMES_TYPE) {
float bubbleTop = INNER_RECT_SPACE + AndroidUtilities.dp(8);
float bubbleLeft = INNER_RECT_SPACE + AndroidUtilities.dp(22);
rectF.set(x + bubbleLeft, bubbleTop, x + bubbleLeft + BUBBLE_WIDTH, bubbleTop + BUBBLE_HEIGHT);
canvas.drawRoundRect(rectF, rectF.height() * 0.5f, rectF.height() * 0.5f, backgroundPaint);
bubbleLeft = INNER_RECT_SPACE + AndroidUtilities.dp(5);
bubbleTop += BUBBLE_HEIGHT + AndroidUtilities.dp(4);
rectF.set(x + bubbleLeft, bubbleTop, x + bubbleLeft + BUBBLE_WIDTH, bubbleTop + BUBBLE_HEIGHT);
canvas.drawRoundRect(rectF, rectF.height() * 0.5f, rectF.height() * 0.5f, backgroundPaint);
} else if (getViewType() == QR_TYPE) {
float radius = AndroidUtilities.dp(5);
float squareSize = AndroidUtilities.dp(32);
float left = x + (itemWidth - squareSize) / 2;
int top = AndroidUtilities.dp(21);
AndroidUtilities.rectTmp.set(left, top, left + squareSize, top + AndroidUtilities.dp(32));
canvas.drawRoundRect(AndroidUtilities.rectTmp, radius, radius, backgroundPaint);
}
canvas.drawCircle(x + itemWidth / 2, getMeasuredHeight() - AndroidUtilities.dp(20), AndroidUtilities.dp(8), backgroundPaint);
@ -492,6 +502,27 @@ public class FlickerLoadingView extends View {
break;
}
}
} else if (getViewType() == REACTED_TYPE) {
int k = 0;
while (h <= getMeasuredHeight()) {
int r = AndroidUtilities.dp(16);
canvas.drawCircle(checkRtl(paddingLeft + AndroidUtilities.dp(13) + r), h + AndroidUtilities.dp(24), r, paint);
rectF.set(paddingLeft + AndroidUtilities.dp(53), h + AndroidUtilities.dp(20), getWidth() - AndroidUtilities.dp(53), h + AndroidUtilities.dp(28));
checkRtl(rectF);
canvas.drawRoundRect(rectF, AndroidUtilities.dp(8), AndroidUtilities.dp(8), paint);
if (k < 4) {
r = AndroidUtilities.dp(12);
canvas.drawCircle(checkRtl(getWidth() - AndroidUtilities.dp(12) - r), h + AndroidUtilities.dp(24), r, paint);
}
h += getCellHeight(getMeasuredWidth());
k++;
if (isSingleCell && k >= itemsCount) {
break;
}
}
}
invalidate();
}
@ -518,7 +549,7 @@ public class FlickerLoadingView extends View {
height = getMeasuredHeight();
}
lastUpdateTime = newUpdateTime;
if (isSingleCell || viewType == MESSAGE_SEEN_TYPE || getViewType() == CHAT_THEMES_TYPE) {
if (isSingleCell || viewType == MESSAGE_SEEN_TYPE || getViewType() == CHAT_THEMES_TYPE || getViewType() == QR_TYPE) {
totalTranslation += dt * width / 400.0f;
if (totalTranslation >= width * 2) {
totalTranslation = -gradientWidth * 2;
@ -546,7 +577,7 @@ public class FlickerLoadingView extends View {
if (this.color1 != color1 || this.color0 != color0) {
this.color0 = color0;
this.color1 = color1;
if (isSingleCell || viewType == MESSAGE_SEEN_TYPE || viewType == CHAT_THEMES_TYPE) {
if (isSingleCell || viewType == MESSAGE_SEEN_TYPE || viewType == CHAT_THEMES_TYPE || viewType == QR_TYPE) {
gradient = new LinearGradient(0, 0, gradientWidth = AndroidUtilities.dp(200), 0, new int[]{color1, color0, color0, color1}, new float[]{0.0f, 0.4f, 0.6f, 1f}, Shader.TileMode.CLAMP);
} else {
gradient = new LinearGradient(0, 0, 0, gradientWidth = AndroidUtilities.dp(600), new int[]{color1, color0, color0, color1}, new float[]{0.0f, 0.4f, 0.6f, 1f}, Shader.TileMode.CLAMP);
@ -597,6 +628,8 @@ public class FlickerLoadingView extends View {
return AndroidUtilities.dp(103);
} else if (getViewType() == MEMBER_REQUESTS_TYPE) {
return AndroidUtilities.dp(107);
} else if (getViewType() == REACTED_TYPE) {
return AndroidUtilities.dp(48);
}
return 0;
}

View File

@ -977,6 +977,7 @@ public class ForwardingPreviewView extends FrameLayout {
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
ChatMessageCell cell = (ChatMessageCell) holder.itemView;
cell.setInvalidateSpoilersParent(forwardingMessagesParams.hasSpoilers);
cell.setParentViewSize(chatListView.getMeasuredWidth(), chatListView.getMeasuredHeight());
int id = cell.getMessageObject() != null ? cell.getMessageObject().getId() : 0;
cell.setMessageObject(forwardingMessagesParams.previewMessages.get(position), forwardingMessagesParams.groupedMessagesMap.get(forwardingMessagesParams.previewMessages.get(position).getGroupId()), true, true);

View File

@ -1866,7 +1866,7 @@ public class FragmentContextView extends FrameLayout implements NotificationCent
frameLayout.invalidate();
}
updateAvatars(avatars.wasDraw && updateAnimated);
updateAvatars(avatars.avatarsDarawable.wasDraw && updateAnimated);
} else {
if (voIPService != null && voIPService.groupCall != null) {
updateAvatars(currentStyle == 3);
@ -1925,14 +1925,14 @@ public class FragmentContextView extends FrameLayout implements NotificationCent
private void updateAvatars(boolean animated) {
if (!animated) {
if (avatars.transitionProgressAnimator != null) {
avatars.transitionProgressAnimator.cancel();
avatars.transitionProgressAnimator = null;
if (avatars.avatarsDarawable.transitionProgressAnimator != null) {
avatars.avatarsDarawable.transitionProgressAnimator.cancel();
avatars.avatarsDarawable.transitionProgressAnimator = null;
}
}
ChatObject.Call call;
TLRPC.User userCall;
if (avatars.transitionProgressAnimator == null) {
if (avatars.avatarsDarawable.transitionProgressAnimator == null) {
int currentAccount;
if (currentStyle == 4) {
if (fragment instanceof ChatActivity) {

View File

@ -0,0 +1,530 @@
package org.telegram.ui.Components;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.view.GestureDetector;
import android.view.GestureDetector.OnDoubleTapListener;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
public class GestureDetectorFixDoubleTap {
interface GestureDetectorCompatImpl {
boolean isLongpressEnabled();
boolean onTouchEvent(MotionEvent ev);
void setIsLongpressEnabled(boolean enabled);
void setOnDoubleTapListener(OnDoubleTapListener listener);
}
static class GestureDetectorCompatImplBase implements GestureDetectorCompatImpl {
private int mTouchSlopSquare;
private int mDoubleTapSlopSquare;
private int mMinimumFlingVelocity;
private int mMaximumFlingVelocity;
private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
private static final int DOUBLE_TAP_TIMEOUT = 220;
// constants for Message.what used by GestureHandler below
private static final int SHOW_PRESS = 1;
private static final int LONG_PRESS = 2;
private static final int TAP = 3;
private final Handler mHandler;
final OnGestureListener mListener;
OnDoubleTapListener mDoubleTapListener;
boolean mStillDown;
boolean mDeferConfirmSingleTap;
private boolean mInLongPress;
private boolean mAlwaysInTapRegion;
private boolean mAlwaysInBiggerTapRegion;
MotionEvent mCurrentDownEvent;
private MotionEvent mPreviousUpEvent;
/**
* True when the user is still touching for the second tap (down, move, and
* up events). Can only be true if there is a double tap listener attached.
*/
private boolean mIsDoubleTapping;
private float mLastFocusX;
private float mLastFocusY;
private float mDownFocusX;
private float mDownFocusY;
private boolean mIsLongpressEnabled;
/**
* Determines speed during touch scrolling
*/
private VelocityTracker mVelocityTracker;
private class GestureHandler extends Handler {
@SuppressWarnings("deprecation")
GestureHandler() {
super();
}
GestureHandler(Handler handler) {
super(handler.getLooper());
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_PRESS:
mListener.onShowPress(mCurrentDownEvent);
break;
case LONG_PRESS:
dispatchLongPress();
break;
case TAP:
// If the user's finger is still down, do not count it as a tap
if (mDoubleTapListener != null) {
if (!mStillDown) {
mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
} else {
mDeferConfirmSingleTap = true;
}
}
break;
default:
throw new RuntimeException("Unknown message " + msg); //never
}
}
}
/**
* Creates a GestureDetector with the supplied listener.
* You may only use this constructor from a UI thread (this is the usual situation).
* @see android.os.Handler#Handler()
*
* @param context the application's context
* @param listener the listener invoked for all the callbacks, this must
* not be null.
* @param handler the handler to use
*
* @throws NullPointerException if {@code listener} is null.
*/
GestureDetectorCompatImplBase(Context context, OnGestureListener listener,
Handler handler) {
if (handler != null) {
mHandler = new GestureHandler(handler);
} else {
mHandler = new GestureHandler();
}
mListener = listener;
if (listener instanceof OnDoubleTapListener) {
setOnDoubleTapListener((OnDoubleTapListener) listener);
}
init(context);
}
private void init(Context context) {
if (context == null) {
throw new IllegalArgumentException("Context must not be null");
}
if (mListener == null) {
throw new IllegalArgumentException("OnGestureListener must not be null");
}
mIsLongpressEnabled = true;
final ViewConfiguration configuration = ViewConfiguration.get(context);
final int touchSlop = configuration.getScaledTouchSlop();
final int doubleTapSlop = configuration.getScaledDoubleTapSlop();
mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
mTouchSlopSquare = touchSlop * touchSlop;
mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
}
/**
* Sets the listener which will be called for double-tap and related
* gestures.
*
* @param onDoubleTapListener the listener invoked for all the callbacks, or
* null to stop listening for double-tap gestures.
*/
@Override
public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {
mDoubleTapListener = onDoubleTapListener;
}
/**
* Set whether longpress is enabled, if this is enabled when a user
* presses and holds down you get a longpress event and nothing further.
* If it's disabled the user can press and hold down and then later
* moved their finger and you will get scroll events. By default
* longpress is enabled.
*
* @param isLongpressEnabled whether longpress should be enabled.
*/
@Override
public void setIsLongpressEnabled(boolean isLongpressEnabled) {
mIsLongpressEnabled = isLongpressEnabled;
}
/**
* @return true if longpress is enabled, else false.
*/
@Override
public boolean isLongpressEnabled() {
return mIsLongpressEnabled;
}
/**
* Analyzes the given motion event and if applicable triggers the
* appropriate callbacks on the {@link OnGestureListener} supplied.
*
* @param ev The current motion event.
* @return true if the {@link OnGestureListener} consumed the event,
* else false.
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
final boolean pointerUp =
(action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;
final int skipIndex = pointerUp ? ev.getActionIndex() : -1;
// Determine focal point
float sumX = 0, sumY = 0;
final int count = ev.getPointerCount();
for (int i = 0; i < count; i++) {
if (skipIndex == i) continue;
sumX += ev.getX(i);
sumY += ev.getY(i);
}
final int div = pointerUp ? count - 1 : count;
final float focusX = sumX / div;
final float focusY = sumY / div;
boolean handled = false;
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_POINTER_DOWN:
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
// Cancel long press and taps
cancelTaps();
break;
case MotionEvent.ACTION_POINTER_UP:
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
// Check the dot product of current velocities.
// If the pointer that left was opposing another velocity vector, clear.
mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
final int upIndex = ev.getActionIndex();
final int id1 = ev.getPointerId(upIndex);
final float x1 = mVelocityTracker.getXVelocity(id1);
final float y1 = mVelocityTracker.getYVelocity(id1);
for (int i = 0; i < count; i++) {
if (i == upIndex) continue;
final int id2 = ev.getPointerId(i);
final float x = x1 * mVelocityTracker.getXVelocity(id2);
final float y = y1 * mVelocityTracker.getYVelocity(id2);
final float dot = x + y;
if (dot < 0) {
mVelocityTracker.clear();
break;
}
}
break;
case MotionEvent.ACTION_DOWN:
if (mDoubleTapListener != null) {
boolean hadTapMessage = mHandler.hasMessages(TAP);
if (hadTapMessage) mHandler.removeMessages(TAP);
if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null)
&& hadTapMessage && isConsideredDoubleTap(
mCurrentDownEvent, mPreviousUpEvent, ev)) {
// This is a second tap
mIsDoubleTapping = true;
// Give a callback with the first tap of the double-tap
handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
// Give a callback with down event of the double-tap
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else {
// This is a first tap
mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
}
}
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
if (mCurrentDownEvent != null) {
mCurrentDownEvent.recycle();
}
mCurrentDownEvent = MotionEvent.obtain(ev);
mAlwaysInTapRegion = true;
mAlwaysInBiggerTapRegion = true;
mStillDown = true;
mInLongPress = false;
mDeferConfirmSingleTap = false;
if (mIsLongpressEnabled) {
mHandler.removeMessages(LONG_PRESS);
mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
+ TAP_TIMEOUT + ViewConfiguration.getLongPressTimeout());
}
mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
handled |= mListener.onDown(ev);
break;
case MotionEvent.ACTION_MOVE:
if (mInLongPress) {
break;
}
final float scrollX = mLastFocusX - focusX;
final float scrollY = mLastFocusY - focusY;
if (mIsDoubleTapping) {
// Give the move events of the double-tap
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else if (mAlwaysInTapRegion) {
final int deltaX = (int) (focusX - mDownFocusX);
final int deltaY = (int) (focusY - mDownFocusY);
int distance = (deltaX * deltaX) + (deltaY * deltaY);
if (distance > mTouchSlopSquare) {
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
mAlwaysInTapRegion = false;
mHandler.removeMessages(TAP);
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
}
if (distance > mTouchSlopSquare) {
mAlwaysInBiggerTapRegion = false;
}
} else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
}
break;
case MotionEvent.ACTION_UP:
mStillDown = false;
MotionEvent currentUpEvent = MotionEvent.obtain(ev);
if (mIsDoubleTapping) {
// Finally, give the up event of the double-tap
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else if (mInLongPress) {
mHandler.removeMessages(TAP);
mInLongPress = false;
} else if (mAlwaysInTapRegion) {
handled = mListener.onSingleTapUp(ev);
if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
mDoubleTapListener.onSingleTapConfirmed(ev);
}
} else {
// A fling must travel the minimum tap distance
final VelocityTracker velocityTracker = mVelocityTracker;
final int pointerId = ev.getPointerId(0);
velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
final float velocityY = velocityTracker.getYVelocity(pointerId);
final float velocityX = velocityTracker.getXVelocity(pointerId);
if ((Math.abs(velocityY) > mMinimumFlingVelocity)
|| (Math.abs(velocityX) > mMinimumFlingVelocity)) {
handled = mListener.onFling(
mCurrentDownEvent, ev, velocityX, velocityY);
}
}
if (mPreviousUpEvent != null) {
mPreviousUpEvent.recycle();
}
// Hold the event we obtained above - listeners may have changed the original.
mPreviousUpEvent = currentUpEvent;
if (mVelocityTracker != null) {
// This may have been cleared when we called out to the
// application above.
mVelocityTracker.recycle();
mVelocityTracker = null;
}
mIsDoubleTapping = false;
mDeferConfirmSingleTap = false;
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
break;
case MotionEvent.ACTION_CANCEL:
cancel();
break;
}
return handled;
}
private void cancel() {
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
mHandler.removeMessages(TAP);
mVelocityTracker.recycle();
mVelocityTracker = null;
mIsDoubleTapping = false;
mStillDown = false;
mAlwaysInTapRegion = false;
mAlwaysInBiggerTapRegion = false;
mDeferConfirmSingleTap = false;
if (mInLongPress) {
mInLongPress = false;
}
}
private void cancelTaps() {
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
mHandler.removeMessages(TAP);
mIsDoubleTapping = false;
mAlwaysInTapRegion = false;
mAlwaysInBiggerTapRegion = false;
mDeferConfirmSingleTap = false;
if (mInLongPress) {
mInLongPress = false;
}
}
private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
MotionEvent secondDown) {
if (!mAlwaysInBiggerTapRegion) {
return false;
}
if (secondDown.getEventTime() - firstUp.getEventTime() > DOUBLE_TAP_TIMEOUT) {
return false;
}
int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);
}
void dispatchLongPress() {
mHandler.removeMessages(TAP);
mDeferConfirmSingleTap = false;
mInLongPress = true;
mListener.onLongPress(mCurrentDownEvent);
}
}
static class GestureDetectorCompatImplJellybeanMr2 implements GestureDetectorCompatImpl {
private final GestureDetector mDetector;
GestureDetectorCompatImplJellybeanMr2(Context context, OnGestureListener listener,
Handler handler) {
mDetector = new GestureDetector(context, listener, handler);
}
@Override
public boolean isLongpressEnabled() {
return mDetector.isLongpressEnabled();
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return mDetector.onTouchEvent(ev);
}
@Override
public void setIsLongpressEnabled(boolean enabled) {
mDetector.setIsLongpressEnabled(enabled);
}
@Override
public void setOnDoubleTapListener(OnDoubleTapListener listener) {
mDetector.setOnDoubleTapListener(listener);
}
}
private final GestureDetectorCompatImpl mImpl;
/**
* Creates a GestureDetectorCompat with the supplied listener.
* As usual, you may only use this constructor from a UI thread.
* @see android.os.Handler#Handler()
*
* @param context the application's context
* @param listener the listener invoked for all the callbacks, this must
* not be null.
*/
public GestureDetectorFixDoubleTap(Context context, OnGestureListener listener) {
this(context, listener, null);
}
/**
* Creates a GestureDetectorCompat with the supplied listener.
* As usual, you may only use this constructor from a UI thread.
* @see android.os.Handler#Handler()
*
* @param context the application's context
* @param listener the listener invoked for all the callbacks, this must
* not be null.
* @param handler the handler that will be used for posting deferred messages
*/
public GestureDetectorFixDoubleTap(Context context, OnGestureListener listener, Handler handler) {
mImpl = new GestureDetectorCompatImplBase(context, listener, handler);
}
/**
* @return true if longpress is enabled, else false.
*/
public boolean isLongpressEnabled() {
return mImpl.isLongpressEnabled();
}
/**
* Analyzes the given motion event and if applicable triggers the
* appropriate callbacks on the {@link OnGestureListener} supplied.
*
* @param event The current motion event.
* @return true if the {@link OnGestureListener} consumed the event,
* else false.
*/
public boolean onTouchEvent(MotionEvent event) {
return mImpl.onTouchEvent(event);
}
/**
* Set whether longpress is enabled, if this is enabled when a user
* presses and holds down you get a longpress event and nothing further.
* If it's disabled the user can press and hold down and then later
* moved their finger and you will get scroll events. By default
* longpress is enabled.
*
* @param enabled whether longpress should be enabled.
*/
public void setIsLongpressEnabled(boolean enabled) {
mImpl.setIsLongpressEnabled(enabled);
}
/**
* Sets the listener which will be called for double-tap and related
* gestures.
*
* @param listener the listener invoked for all the callbacks, or
* null to stop listening for double-tap gestures.
*/
public void setOnDoubleTapListener(OnDoubleTapListener listener) {
mImpl.setOnDoubleTapListener(listener);
}
}

View File

@ -676,7 +676,7 @@ public class GroupCallPip implements NotificationCenter.NotificationCenterDelega
}
private void updateAvatars(boolean animated) {
if (avatarsImageView.transitionProgressAnimator == null) {
if (avatarsImageView.avatarsDarawable.transitionProgressAnimator == null) {
ChatObject.Call call;
VoIPService voIPService = VoIPService.getSharedInstance();

View File

@ -0,0 +1,56 @@
package org.telegram.ui.Components;
import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.FrameLayout;
public class LerpedLayoutParams extends ViewGroup.MarginLayoutParams {
private ViewGroup.LayoutParams from;
private ViewGroup.LayoutParams to;
public LerpedLayoutParams(
ViewGroup.LayoutParams from,
ViewGroup.LayoutParams to
) {
super(from == null ? to : from);
this.from = from;
this.to = to;
}
public void apply(float t) {
t = Math.min(Math.max(t, 0), 1);
this.width = lerpSz(from.width, to.width, t);
this.height = lerpSz(from.height, to.height, t);
if (from instanceof ViewGroup.MarginLayoutParams && to instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams marginFrom = (ViewGroup.MarginLayoutParams) from;
ViewGroup.MarginLayoutParams marginTo = (ViewGroup.MarginLayoutParams) to;
this.topMargin = lerp(marginFrom.topMargin, marginTo.topMargin, t);
this.leftMargin = lerp(marginFrom.leftMargin, marginTo.leftMargin, t);
this.rightMargin = lerp(marginFrom.rightMargin, marginTo.rightMargin, t);
this.bottomMargin = lerp(marginFrom.bottomMargin, marginTo.bottomMargin, t);
} else if (from instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams marginFrom = (ViewGroup.MarginLayoutParams) from;
this.topMargin = marginFrom.topMargin;
this.leftMargin = marginFrom.leftMargin;
this.rightMargin = marginFrom.rightMargin;
this.bottomMargin = marginFrom.bottomMargin;
} else if (to instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams marginTo = (ViewGroup.MarginLayoutParams) to;
this.topMargin = marginTo.topMargin;
this.leftMargin = marginTo.leftMargin;
this.rightMargin = marginTo.rightMargin;
this.bottomMargin = marginTo.bottomMargin;
}
}
private int lerp(int from, int to, float t) {
return (int) (from + (to - from) * t);
}
private int lerpSz(int from, int to, float t) {
if (from < 0 || to < 0) // MATCH_PARENT or WRAP_CONTENT
return t < .5f ? from : to;
return lerp(from, to, t);
}
}

View File

@ -104,7 +104,7 @@ public class MediaActivity extends BaseFragment implements SharedMediaLayout.Sha
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (sharedMediaLayout != null && sharedMediaLayout.isInFastScroll() && sharedMediaLayout.getY() == 0) {
if (sharedMediaLayout != null && sharedMediaLayout.isInFastScroll()) {
return sharedMediaLayout.dispatchFastScrollEvent(ev);
}
if (sharedMediaLayout != null && sharedMediaLayout.checkPinchToZoom(ev)) {

View File

@ -57,7 +57,7 @@ public class MotionBackgroundDrawable extends Drawable {
private boolean isPreview;
private float posAnimationProgress = 1.0f;
public float posAnimationProgress = 1.0f;
private int phase;
private RectF rect = new RectF();
@ -103,6 +103,9 @@ public class MotionBackgroundDrawable extends Drawable {
private ColorFilter legacyBitmapColorFilter;
private int legacyBitmapColor;
private boolean isIndeterminateAnimation;
private Paint overrideBitmapPaint;
public MotionBackgroundDrawable() {
super();
init();
@ -440,7 +443,6 @@ public class MotionBackgroundDrawable extends Drawable {
legacyBitmap2 = null;
}
try {
legacyBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
legacyCanvas = new Canvas(legacyBitmap);
invalidateLegacy = true;
@ -588,7 +590,7 @@ public class MotionBackgroundDrawable extends Drawable {
gradientDrawable.draw(canvas);
} else {
rect.set(x, y, x + width, y + height);
canvas.drawBitmap(currentBitmap, null, rect, paint);
canvas.drawBitmap(currentBitmap, null, rect, overrideBitmapPaint != null ? overrideBitmapPaint : paint);
}
}
@ -612,7 +614,7 @@ public class MotionBackgroundDrawable extends Drawable {
updateAnimation();
}
private void updateAnimation() {
public void updateAnimation() {
long newTime = SystemClock.elapsedRealtime();
long dt = newTime - lastUpdateTime;
if (dt > 20) {
@ -623,79 +625,94 @@ public class MotionBackgroundDrawable extends Drawable {
return;
}
if (isIndeterminateAnimation && posAnimationProgress == 1.0f) {
posAnimationProgress = 0f;
}
if (posAnimationProgress < 1.0f) {
float progress;
if (rotatingPreview) {
int stageBefore;
float progressBefore = interpolator.getInterpolation(posAnimationProgress);
if (progressBefore <= 0.25f) {
stageBefore = 0;
} else if (progressBefore <= 0.5f) {
stageBefore = 1;
} else if (progressBefore <= 0.75f) {
stageBefore = 2;
} else {
stageBefore = 3;
}
posAnimationProgress += dt / (rotationBack ? 1000.0f : 2000.0f);
if (posAnimationProgress > 1.0f) {
posAnimationProgress = 1.0f;
}
progress = interpolator.getInterpolation(posAnimationProgress);
if (stageBefore == 0 && progress > 0.25f ||
stageBefore == 1 && progress > 0.5f ||
stageBefore == 2 && progress > 0.75f) {
if (rotationBack) {
phase++;
if (phase > 7) {
phase = 0;
}
} else {
phase--;
if (phase < 0) {
phase = 7;
}
}
}
if (progress <= 0.25f) {
progress /= 0.25f;
} else if (progress <= 0.5f) {
progress = (progress - 0.25f) / 0.25f;
} else if (progress <= 0.75f) {
progress = (progress - 0.5f) / 0.25f;
} else {
progress = (progress - 0.75f) / 0.25f;
}
if (rotationBack) {
float prevProgress = progress;
progress = 1.0f - progress;
if (posAnimationProgress >= 1.0f) {
phase++;
if (phase > 7) {
phase = 0;
}
progress = 1.0f;
}
boolean isNeedGenerateGradient = postInvalidateParent || rotatingPreview;
if (isIndeterminateAnimation) {
posAnimationProgress += dt / 12000f;
if (posAnimationProgress >= 1.0f) {
posAnimationProgress = 0.0f;
}
float progressPerPhase = 1f / 8f;
phase = (int) (posAnimationProgress / progressPerPhase);
progress = 1f - (posAnimationProgress - phase * progressPerPhase) / progressPerPhase;
isNeedGenerateGradient = true;
} else {
posAnimationProgress += dt / (fastAnimation ? 300.0f : 500.0f);
if (posAnimationProgress > 1.0f) {
posAnimationProgress = 1.0f;
}
progress = interpolator.getInterpolation(posAnimationProgress);
if (rotationBack) {
progress = 1.0f - progress;
if (posAnimationProgress >= 1.0f) {
phase++;
if (phase > 7) {
phase = 0;
if (rotatingPreview) {
int stageBefore;
float progressBefore = interpolator.getInterpolation(posAnimationProgress);
if (progressBefore <= 0.25f) {
stageBefore = 0;
} else if (progressBefore <= 0.5f) {
stageBefore = 1;
} else if (progressBefore <= 0.75f) {
stageBefore = 2;
} else {
stageBefore = 3;
}
posAnimationProgress += dt / (rotationBack ? 1000.0f : 2000.0f);
if (posAnimationProgress > 1.0f) {
posAnimationProgress = 1.0f;
}
progress = interpolator.getInterpolation(posAnimationProgress);
if (stageBefore == 0 && progress > 0.25f ||
stageBefore == 1 && progress > 0.5f ||
stageBefore == 2 && progress > 0.75f) {
if (rotationBack) {
phase++;
if (phase > 7) {
phase = 0;
}
} else {
phase--;
if (phase < 0) {
phase = 7;
}
}
}
if (progress <= 0.25f) {
progress /= 0.25f;
} else if (progress <= 0.5f) {
progress = (progress - 0.25f) / 0.25f;
} else if (progress <= 0.75f) {
progress = (progress - 0.5f) / 0.25f;
} else {
progress = (progress - 0.75f) / 0.25f;
}
if (rotationBack) {
float prevProgress = progress;
progress = 1.0f - progress;
if (posAnimationProgress >= 1.0f) {
phase++;
if (phase > 7) {
phase = 0;
}
progress = 1.0f;
}
}
} else {
posAnimationProgress += dt / (fastAnimation ? 300.0f : 500.0f);
if (posAnimationProgress > 1.0f) {
posAnimationProgress = 1.0f;
}
progress = interpolator.getInterpolation(posAnimationProgress);
if (rotationBack) {
progress = 1.0f - progress;
if (posAnimationProgress >= 1.0f) {
phase++;
if (phase > 7) {
phase = 0;
}
progress = 1.0f;
}
progress = 1.0f;
}
}
}
if (postInvalidateParent || rotatingPreview) {
if (isNeedGenerateGradient) {
Utilities.generateGradient(currentBitmap, true, phase, progress, currentBitmap.getWidth(), currentBitmap.getHeight(), currentBitmap.getRowBytes(), colors);
invalidateLegacy = true;
} else {
@ -742,4 +759,12 @@ public class MotionBackgroundDrawable extends Drawable {
public boolean isOneColor() {
return colors[0] == colors[1] && colors[0] == colors[2] && colors[0] == colors[3];
}
public void setIndeterminateAnimation(boolean isIndeterminateAnimation) {
this.isIndeterminateAnimation = isIndeterminateAnimation;
}
public void setOverrideBitmapPaint(Paint overrideBitmapPaint) {
this.overrideBitmapPaint = overrideBitmapPaint;
}
}

View File

@ -916,6 +916,9 @@ public class PhotoPaintView extends FrameLayout implements EntityView.EntityView
int w = view.getMeasuredWidth();
int h = view.getMeasuredHeight();
if (w == 0 || h == 0) {
return;
}
int tr = currentCropState.transformRotation;
int fw = w, rotatedW = w;
int fh = h, rotatedH = h;

View File

@ -42,10 +42,10 @@ import androidx.core.graphics.ColorUtils;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.Emoji;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MessagesController;
import org.telegram.messenger.NotificationCenter;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.R;
import org.telegram.messenger.SharedConfig;
import org.telegram.messenger.UserConfig;
@ -199,6 +199,17 @@ public class PhotoViewerCaptionEnterView extends FrameLayout implements Notifica
};
messageEditText.setDelegate(new EditTextCaption.EditTextCaptionDelegate() {
@Override
public void onSpansChanged() {
messageEditText.invalidateEffects();
}
@Override
public long getCurrentChat() {
return 0;
}
});
messageEditText.setWindowView(windowView);
messageEditText.setHint(LocaleController.getString("AddCaption", R.string.AddCaption));
messageEditText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);

View File

@ -0,0 +1,440 @@
package org.telegram.ui.Components;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.SparseIntArray;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.core.view.GestureDetectorCompat;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.NotificationCenter;
import org.telegram.messenger.UserConfig;
import org.telegram.ui.ActionBar.ActionBarPopupWindow;
import org.telegram.ui.ActionBar.Theme;
public class PopupSwipeBackLayout extends FrameLayout {
private final static int DURATION = 300;
SparseIntArray overrideHeightIndex = new SparseIntArray();
private float transitionProgress;
private float toProgress = -1;
private GestureDetectorCompat detector;
private boolean isProcessingSwipe;
private boolean isAnimationInProgress;
private boolean isSwipeDisallowed;
private Paint overlayPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint foregroundPaint = new Paint();
private Path mPath = new Path();
private RectF mRect = new RectF();
private OnSwipeBackProgressListener onSwipeBackProgressListener;
private boolean isSwipeBackDisallowed;
private float overrideForegroundHeight;
private ValueAnimator foregroundAnimator;
private int currentForegroundIndex = -1;
private int notificationIndex;
Theme.ResourcesProvider resourcesProvider;
private Rect hitRect = new Rect();
public PopupSwipeBackLayout(@NonNull Context context, Theme.ResourcesProvider resourcesProvider) {
super(context);
this.resourcesProvider = resourcesProvider;
int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
detector = new GestureDetectorCompat(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (!isProcessingSwipe && !isSwipeDisallowed) {
if (!isSwipeBackDisallowed && transitionProgress == 1 && distanceX <= -touchSlop && Math.abs(distanceX) >= Math.abs(distanceY * 1.5f) && !isDisallowedView(e2, getChildAt(transitionProgress > 0.5f ? 1 : 0))) {
isProcessingSwipe = true;
MotionEvent c = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
for (int i = 0; i < getChildCount(); i++)
getChildAt(i).dispatchTouchEvent(c);
c.recycle();
} else isSwipeDisallowed = true;
}
if (isProcessingSwipe) {
toProgress = -1;
transitionProgress = 1f - Math.max(0, Math.min(1, (e2.getX() - e1.getX()) / getWidth()));
invalidateTransforms();
}
return isProcessingSwipe;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (isAnimationInProgress || isSwipeDisallowed)
return false;
if (velocityX >= 600) {
clearFlags();
animateToState(0, velocityX / 6000f);
}
return false;
}
});
overlayPaint.setColor(Color.BLACK);
}
/**
* Sets if swipeback action should be disallowed
*
* @param swipeBackDisallowed If swipe should be disallowed
*/
public void setSwipeBackDisallowed(boolean swipeBackDisallowed) {
isSwipeBackDisallowed = swipeBackDisallowed;
}
/**
* Sets new swipeback listener
*
* @param onSwipeBackProgressListener New progress listener
*/
public void setOnSwipeBackProgressListener(OnSwipeBackProgressListener onSwipeBackProgressListener) {
this.onSwipeBackProgressListener = onSwipeBackProgressListener;
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
int i = indexOfChild(child);
int s = canvas.save();
if (i != 0) {
foregroundPaint.setColor(Theme.getColor(Theme.key_actionBarDefaultSubmenuBackground, resourcesProvider));
canvas.drawRect(child.getX(), 0, child.getX() + child.getMeasuredWidth(), getMeasuredHeight(), foregroundPaint);
}
boolean b = super.drawChild(canvas, child, drawingTime);
if (i == 0) {
overlayPaint.setAlpha((int) (transitionProgress * 0x40));
canvas.drawRect(0, 0, getWidth(), getHeight(), overlayPaint);
}
canvas.restoreToCount(s);
return b;
}
/**
* Invalidates transformations
*/
private void invalidateTransforms() {
if (onSwipeBackProgressListener != null) {
onSwipeBackProgressListener.onSwipeBackProgress(this, toProgress, transitionProgress);
}
View bg = getChildAt(0);
View fg = null;
if (currentForegroundIndex >= 0 && currentForegroundIndex < getChildCount()) {
fg = getChildAt(currentForegroundIndex);
}
bg.setTranslationX(-transitionProgress * getWidth() * 0.5f);
float bSc = 0.95f + (1f - transitionProgress) * 0.05f;
bg.setScaleX(bSc);
bg.setScaleY(bSc);
if (fg != null) {
fg.setTranslationX((1f - transitionProgress) * getWidth());
}
invalidateVisibility();
float fW = bg.getMeasuredWidth(), fH = bg.getMeasuredHeight();
float tW = 0;
float tH = 0;
if (fg != null) {
tW = fg.getMeasuredWidth();
tH = overrideForegroundHeight != 0 ? overrideForegroundHeight : fg.getMeasuredHeight();
}
if (bg.getMeasuredWidth() == 0 || bg.getMeasuredHeight() == 0) {
return;
}
ActionBarPopupWindow.ActionBarPopupWindowLayout p = (ActionBarPopupWindow.ActionBarPopupWindowLayout) getParent();
float w = fW + (tW - fW) * transitionProgress;
float h = fH + (tH - fH) * transitionProgress;
w += p.getPaddingLeft() + p.getPaddingRight();
h += p.getPaddingTop() + p.getPaddingBottom();
p.setBackScaleX(w / p.getMeasuredWidth());
p.setBackScaleY(h / p.getMeasuredHeight());
for (int i = 0; i < getChildCount(); i++) {
View ch = getChildAt(i);
ch.setPivotX(0);
ch.setPivotY(0);
}
invalidate();
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (processTouchEvent(ev))
return true;
if (currentForegroundIndex < 0 || currentForegroundIndex >= getChildCount()) {
return super.dispatchTouchEvent(ev);
}
View bv = getChildAt(0);
View fv = getChildAt(currentForegroundIndex);
int act = ev.getActionMasked();
if (act == MotionEvent.ACTION_DOWN && (ev.getX() > (bv.getMeasuredWidth() + (fv.getMeasuredWidth() - bv.getMeasuredWidth()) * transitionProgress) ||
ev.getY() > (bv.getMeasuredHeight() + ((overrideForegroundHeight != 0 ? overrideForegroundHeight : fv.getMeasuredHeight()) - bv.getMeasuredHeight()) * transitionProgress))) {
callOnClick();
return true;
}
boolean b = (transitionProgress > 0.5f ? fv : bv).dispatchTouchEvent(ev);
if (!b && act == MotionEvent.ACTION_DOWN) {
return true;
}
return b || onTouchEvent(ev);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
invalidateTransforms();
}
/**
* Processes touch event and return true if processed
*
* @param ev Event to process
* @return If event is processed
*/
private boolean processTouchEvent(MotionEvent ev) {
int act = ev.getAction() & MotionEvent.ACTION_MASK;
if (isAnimationInProgress)
return true;
if (!detector.onTouchEvent(ev)) {
switch (act) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (isProcessingSwipe) {
clearFlags();
animateToState(transitionProgress >= 0.5f ? 1 : 0, 0);
} else if (isSwipeDisallowed) clearFlags();
return false;
}
}
return isProcessingSwipe;
}
/**
* Animates transition value
*
* @param f End value
* @param flingVal Fling value(If from fling, zero otherwise)
*/
private void animateToState(float f, float flingVal) {
ValueAnimator val = ValueAnimator.ofFloat(transitionProgress, f).setDuration((long) (DURATION * Math.max(0.5f, Math.abs(transitionProgress - f) - Math.min(0.2f, flingVal))));
val.setInterpolator(CubicBezierInterpolator.DEFAULT);
int selectedAccount = UserConfig.selectedAccount;
notificationIndex = NotificationCenter.getInstance(selectedAccount).setAnimationInProgress(notificationIndex, null);
val.addUpdateListener(animation -> {
transitionProgress = (float) animation.getAnimatedValue();
invalidateTransforms();
});
val.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
isAnimationInProgress = true;
toProgress = f;
}
@Override
public void onAnimationEnd(Animator animation) {
NotificationCenter.getInstance(selectedAccount).onAnimationFinish(notificationIndex);
transitionProgress = f;
invalidateTransforms();
isAnimationInProgress = false;
}
});
val.start();
}
/**
* Clears touch flags
*/
private void clearFlags() {
isProcessingSwipe = false;
isSwipeDisallowed = false;
}
/**
* Opens up foreground
*/
public void openForeground(int viewIndex) {
if (isAnimationInProgress) {
return;
}
currentForegroundIndex = viewIndex;
overrideForegroundHeight = overrideHeightIndex.get(viewIndex);
animateToState(1, 0);
}
/**
* Closes foreground view
*/
public void closeForeground() {
if (isAnimationInProgress) return;
animateToState(0, 0);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
for (int i = 0; i < getChildCount(); i++) {
View ch = getChildAt(i);
ch.layout(0, 0, ch.getMeasuredWidth(), ch.getMeasuredHeight());
}
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
super.addView(child, index, params);
invalidateTransforms();
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (getChildCount() == 0) {
return;
}
View backgroundView = getChildAt(0);
float fW = backgroundView.getMeasuredWidth(), fH = backgroundView.getMeasuredHeight();
float w, h;
if (currentForegroundIndex == -1 || currentForegroundIndex >= getChildCount()) {
w = fW;
h = fH;
} else {
View foregroundView = getChildAt(currentForegroundIndex);
float tW = foregroundView.getMeasuredWidth(), tH = overrideForegroundHeight != 0 ? overrideForegroundHeight : foregroundView.getMeasuredHeight();
if (backgroundView.getMeasuredWidth() == 0 || backgroundView.getMeasuredHeight() == 0 || foregroundView.getMeasuredWidth() == 0 || foregroundView.getMeasuredHeight() == 0) {
w = fW;
h = fH;
} else {
w = fW + (tW - fW) * transitionProgress;
h = fH + (tH - fH) * transitionProgress;
}
}
int s = canvas.save();
mPath.rewind();
int rad = AndroidUtilities.dp(6);
mRect.set(0, 0, w, h);
mPath.addRoundRect(mRect, rad, rad, Path.Direction.CW);
canvas.clipPath(mPath);
super.dispatchDraw(canvas);
canvas.restoreToCount(s);
}
/**
* @param e Motion event to check
* @param v View to check
* @return If we should ignore view
*/
private boolean isDisallowedView(MotionEvent e, View v) {
v.getHitRect(hitRect);
if (hitRect.contains((int) e.getX(), (int) e.getY()) && v.canScrollHorizontally(-1))
return true;
if (v instanceof ViewGroup) {
ViewGroup vg = (ViewGroup) v;
for (int i = 0; i < vg.getChildCount(); i++)
if (isDisallowedView(e, vg.getChildAt(i)))
return true;
}
return false;
}
/**
* Invalidates view transforms
*/
private void invalidateVisibility() {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (i == 0) {
if (transitionProgress == 1 && child.getVisibility() != INVISIBLE)
child.setVisibility(INVISIBLE);
if (transitionProgress != 1 && child.getVisibility() != VISIBLE)
child.setVisibility(VISIBLE);
} else if (i == currentForegroundIndex) {
if (transitionProgress == 0 && child.getVisibility() != INVISIBLE)
child.setVisibility(INVISIBLE);
if (transitionProgress != 0 && child.getVisibility() != VISIBLE)
child.setVisibility(VISIBLE);
} else {
child.setVisibility(INVISIBLE);
}
}
}
public void setNewForegroundHeight(int index, int height) {
overrideHeightIndex.put(index, height);
if (index != currentForegroundIndex) {
return;
}
if (currentForegroundIndex < 0 || currentForegroundIndex >= getChildCount()) {
return;
}
if (foregroundAnimator != null) {
foregroundAnimator.cancel();
}
View fg = getChildAt(currentForegroundIndex);
float fromH = overrideForegroundHeight != 0 ? overrideForegroundHeight : fg.getMeasuredHeight();
float toH = height;
ValueAnimator animator = ValueAnimator.ofFloat(fromH, toH).setDuration(240);
animator.setInterpolator(Easings.easeInOutQuad);
animator.addUpdateListener(animation -> {
overrideForegroundHeight = (float) animation.getAnimatedValue();
invalidateTransforms();
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
isAnimationInProgress = false;
}
@Override
public void onAnimationStart(Animator animation) {
isAnimationInProgress = true;
}
});
animator.start();
foregroundAnimator = animator;
}
public interface OnSwipeBackProgressListener {
void onSwipeBackProgress(PopupSwipeBackLayout layout, float toProgress, float progress);
}
}

View File

@ -118,7 +118,7 @@ public class QRCodeBottomSheet extends BottomSheet {
buttonTextView.setText(LocaleController.getString("ShareQrCode", R.string.ShareQrCode));
buttonTextView.setOnClickListener(view -> {
Uri uri = getImageUri(qrCode);
Uri uri = AndroidUtilities.getBitmapShareUri(qrCode, "qr_tmp.png", Bitmap.CompressFormat.PNG);
if (uri != null) {
Intent i = new Intent(Intent.ACTION_SEND);
@ -140,37 +140,13 @@ public class QRCodeBottomSheet extends BottomSheet {
setCustomView(scrollView);
}
public Uri getImageUri(Bitmap inImage) {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
inImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
File cachePath = AndroidUtilities.getCacheDir();
if (!cachePath.isDirectory()) {
try {
cachePath.mkdirs();
} catch (Exception e) {
FileLog.e(e);
return null;
}
}
File file = new File(cachePath, "qr_tmp.png");
try (FileOutputStream out = new FileOutputStream(file)) {
inImage.compress(Bitmap.CompressFormat.PNG, 100, out);
out.close();
return FileProvider.getUriForFile(ApplicationLoader.applicationContext, BuildConfig.APPLICATION_ID + ".provider", file);
} catch (IOException e) {
FileLog.e(e);
}
return null;
}
public Bitmap createQR(Context context, String key, Bitmap oldBitmap) {
try {
HashMap<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
hints.put(EncodeHintType.MARGIN, 0);
QRCodeWriter writer = new QRCodeWriter();
Bitmap bitmap = writer.encode(key, BarcodeFormat.QR_CODE, 768, 768, hints, oldBitmap, context);
Bitmap bitmap = writer.encode(key, 768, 768, hints, oldBitmap);
imageSize = writer.getImageSize();
return bitmap;
} catch (Exception e) {

View File

@ -0,0 +1,301 @@
package org.telegram.ui.Components;
import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.core.util.Consumer;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ChatObject;
import org.telegram.messenger.ImageLocation;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MediaDataController;
import org.telegram.messenger.MessageObject;
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.Theme;
import java.util.ArrayList;
import java.util.List;
public class ReactedHeaderView extends FrameLayout {
private FlickerLoadingView flickerLoadingView;
private TextView titleView;
private AvatarsImageView avatarsImageView;
private ImageView iconView;
private BackupImageView reactView;
private int currentAccount;
private boolean ignoreLayout;
private List<TLRPC.User> seenUsers = new ArrayList<>();
private List<TLRPC.User> users = new ArrayList<>();
private long dialogId;
private MessageObject message;
private boolean isLoaded;
private Consumer<List<TLRPC.User>> seenCallback;
public ReactedHeaderView(@NonNull Context context, int currentAccount, MessageObject message, long dialogId) {
super(context);
this.currentAccount = currentAccount;
this.message = message;
this.dialogId = dialogId;
flickerLoadingView = new FlickerLoadingView(context);
flickerLoadingView.setColors(Theme.key_actionBarDefaultSubmenuBackground, Theme.key_listSelector, null);
flickerLoadingView.setViewType(FlickerLoadingView.MESSAGE_SEEN_TYPE);
flickerLoadingView.setIsSingleCell(false);
addView(flickerLoadingView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT));
titleView = new TextView(context);
titleView.setTextColor(Theme.getColor(Theme.key_actionBarDefaultSubmenuItem));
titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
titleView.setLines(1);
titleView.setEllipsize(TextUtils.TruncateAt.END);
addView(titleView, LayoutHelper.createFrameRelatively(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL, 40, 0, 62, 0));
avatarsImageView = new AvatarsImageView(context, false);
avatarsImageView.setStyle(AvatarsDarawable.STYLE_MESSAGE_SEEN);
addView(avatarsImageView, LayoutHelper.createFrameRelatively(24 + 12 + 12 + 8, LayoutHelper.MATCH_PARENT, Gravity.END | Gravity.CENTER_VERTICAL, 0, 0, 0, 0));
iconView = new ImageView(context);
addView(iconView, LayoutHelper.createFrameRelatively(24, 24, Gravity.START | Gravity.CENTER_VERTICAL, 11, 0, 0, 0));
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.msg_reactions).mutate();
drawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_actionBarDefaultSubmenuItemIcon), PorterDuff.Mode.MULTIPLY));
iconView.setImageDrawable(drawable);
iconView.setVisibility(View.GONE);
reactView = new BackupImageView(context);
addView(reactView, LayoutHelper.createFrameRelatively(24, 24, Gravity.START | Gravity.CENTER_VERTICAL, 11, 0, 0, 0));
titleView.setAlpha(0);
avatarsImageView.setAlpha(0);
setBackground(Theme.getSelectorDrawable(false));
}
public void setSeenCallback(Consumer<List<TLRPC.User>> seenCallback) {
this.seenCallback = seenCallback;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (!isLoaded) {
MessagesController ctrl = MessagesController.getInstance(currentAccount);
TLRPC.Chat chat = ctrl.getChat(message.getChatId());
TLRPC.ChatFull chatInfo = ctrl.getChatFull(message.getChatId());
boolean showSeen = chat != null && message.isOutOwner() && message.isSent() && !message.isEditing() && !message.isSending() && !message.isSendError() && !message.isContentUnread() && !message.isUnread() && (ConnectionsManager.getInstance(currentAccount).getCurrentTime() - message.messageOwner.date < 7 * 86400) && (ChatObject.isMegagroup(chat) || !ChatObject.isChannel(chat)) && chatInfo != null && chatInfo.participants_count < MessagesController.getInstance(currentAccount).chatReadMarkSizeThreshold && !(message.messageOwner.action instanceof TLRPC.TL_messageActionChatJoinedByRequest);
if (showSeen) {
TLRPC.TL_messages_getMessageReadParticipants req = new TLRPC.TL_messages_getMessageReadParticipants();
req.msg_id = message.getId();
req.peer = MessagesController.getInstance(currentAccount).getInputPeer(message.getDialogId());
long fromId = message.messageOwner.from_id != null ? message.messageOwner.from_id.user_id : 0;
ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> {
if (response instanceof TLRPC.Vector) {
List<Long> usersToRequest = new ArrayList<>();
TLRPC.Vector v = (TLRPC.Vector) response;
for (Object obj : v.objects) {
if (obj instanceof Long) {
long l = (long) obj;
if (fromId != l)
usersToRequest.add(l);
}
}
usersToRequest.add(fromId);
List<TLRPC.User> usersRes = new ArrayList<>();
Runnable callback = () -> {
seenUsers.addAll(usersRes);
for (TLRPC.User u : usersRes) {
boolean hasSame = false;
for (int i = 0; i < users.size(); i++) {
if (users.get(i).id == u.id) {
hasSame = true;
break;
}
}
if (!hasSame) {
users.add(u);
}
}
if (seenCallback != null)
seenCallback.accept(usersRes);
loadReactions();
};
if (ChatObject.isChannel(chat)) {
TLRPC.TL_channels_getParticipants usersReq = new TLRPC.TL_channels_getParticipants();
usersReq.limit = MessagesController.getInstance(currentAccount).chatReadMarkSizeThreshold;
usersReq.offset = 0;
usersReq.filter = new TLRPC.TL_channelParticipantsRecent();
usersReq.channel = MessagesController.getInstance(currentAccount).getInputChannel(chat.id);
ConnectionsManager.getInstance(currentAccount).sendRequest(usersReq, (response1, error1) -> AndroidUtilities.runOnUIThread(() -> {
if (response1 != null) {
TLRPC.TL_channels_channelParticipants users = (TLRPC.TL_channels_channelParticipants) response1;
for (int i = 0; i < users.users.size(); i++) {
TLRPC.User user = users.users.get(i);
MessagesController.getInstance(currentAccount).putUser(user, false);
if (!user.self && usersToRequest.contains(user.id))
usersRes.add(user);
}
}
callback.run();
}));
} else {
TLRPC.TL_messages_getFullChat usersReq = new TLRPC.TL_messages_getFullChat();
usersReq.chat_id = chat.id;
ConnectionsManager.getInstance(currentAccount).sendRequest(usersReq, (response1, error1) -> AndroidUtilities.runOnUIThread(() -> {
if (response1 != null) {
TLRPC.TL_messages_chatFull chatFull = (TLRPC.TL_messages_chatFull) response1;
for (int i = 0; i < chatFull.users.size(); i++) {
TLRPC.User user = chatFull.users.get(i);
MessagesController.getInstance(currentAccount).putUser(user, false);
if (!user.self && usersToRequest.contains(user.id))
usersRes.add(user);
}
}
callback.run();
}));
}
}
}, ConnectionsManager.RequestFlagInvokeAfter);
} else loadReactions();
}
}
private void loadReactions() {
MessagesController ctrl = MessagesController.getInstance(currentAccount);
TLRPC.TL_messages_getMessageReactionsList getList = new TLRPC.TL_messages_getMessageReactionsList();
getList.peer = ctrl.getInputPeer(dialogId);
getList.id = message.getId();
getList.limit = 3;
ConnectionsManager.getInstance(currentAccount).sendRequest(getList, (response, error) -> {
if (response instanceof TLRPC.TL_messages_messageReactionsList) {
TLRPC.TL_messages_messageReactionsList list = (TLRPC.TL_messages_messageReactionsList) response;
int c = list.count;
post(() -> {
String str;
if (seenUsers.isEmpty() || seenUsers.size() < c) {
str = LocaleController.formatPluralString("ReactionsCount", c);
} else {
String countStr;
if (c == seenUsers.size()) {
countStr = String.valueOf(c);
} else {
countStr = c + "/" + seenUsers.size();
}
str = String.format(LocaleController.getPluralString("Reacted", c), countStr);
}
titleView.setText(str);
boolean showIcon = true;
if (message.messageOwner.reactions != null && message.messageOwner.reactions.results.size() == 1 && !list.reactions.isEmpty()) {
for (TLRPC.TL_availableReaction r : MediaDataController.getInstance(currentAccount).getReactionsList()) {
if (r.reaction.equals(list.reactions.get(0).reaction)) {
reactView.setImage(ImageLocation.getForDocument(r.static_icon), "50_50", "webp", null, r);
reactView.setVisibility(VISIBLE);
reactView.setAlpha(0);
reactView.animate().alpha(1f).start();
iconView.setVisibility(GONE);
showIcon = false;
break;
}
}
}
if (showIcon) {
iconView.setVisibility(VISIBLE);
iconView.setAlpha(0f);
iconView.animate().alpha(1f).start();
}
for (TLRPC.User u : list.users) {
if (message.messageOwner.from_id != null && u.id != message.messageOwner.from_id.user_id) {
boolean hasSame = false;
for (int i = 0; i < users.size(); i++) {
if (users.get(i).id == u.id) {
hasSame = true;
break;
}
}
if (!hasSame) {
users.add(u);
}
}
}
updateView();
});
}
}, ConnectionsManager.RequestFlagInvokeAfter);
}
public List<TLRPC.User> getSeenUsers() {
return seenUsers;
}
private void updateView() {
setEnabled(users.size() > 0);
for (int i = 0; i < 3; i++) {
if (i < users.size()) {
avatarsImageView.setObject(i, currentAccount, users.get(i));
} else {
avatarsImageView.setObject(i, currentAccount, null);
}
}
float tX;
switch (users.size()) {
case 1:
tX = AndroidUtilities.dp(24);
break;
case 2:
tX = AndroidUtilities.dp(12);
break;
default:
tX = 0;
}
avatarsImageView.setTranslationX(LocaleController.isRTL ? AndroidUtilities.dp(12) : tX);
avatarsImageView.commitTransition(false);
titleView.animate().alpha(1f).setDuration(220).start();
avatarsImageView.animate().alpha(1f).setDuration(220).start();
flickerLoadingView.animate().alpha(0f).setDuration(220).setListener(new HideViewAfterAnimation(flickerLoadingView)).start();
}
@Override
public void requestLayout() {
if (ignoreLayout) {
return;
}
super.requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (flickerLoadingView.getVisibility() == View.VISIBLE) {
// Idk what is happening here, but this class is a clone of MessageSeenView, so this might help with something?
ignoreLayout = true;
flickerLoadingView.setVisibility(View.GONE);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
flickerLoadingView.getLayoutParams().width = getMeasuredWidth();
flickerLoadingView.setVisibility(View.VISIBLE);
ignoreLayout = false;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}

View File

@ -0,0 +1,298 @@
package org.telegram.ui.Components;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.text.TextUtils;
import android.util.LongSparseArray;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.DocumentObject;
import org.telegram.messenger.ImageLocation;
import org.telegram.messenger.MediaDataController;
import org.telegram.messenger.MessageObject;
import org.telegram.messenger.MessagesController;
import org.telegram.messenger.SvgHelper;
import org.telegram.messenger.UserObject;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.Theme;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ReactedUsersListView extends FrameLayout {
public final static int VISIBLE_ITEMS = 6;
public final static int ITEM_HEIGHT_DP = 48;
private int currentAccount;
private MessageObject message;
private String filter;
private RecyclerListView listView;
private RecyclerView.Adapter adapter;
private FlickerLoadingView loadingView;
private List<TLRPC.TL_messageUserReaction> userReactions = new ArrayList<>();
private LongSparseArray<TLRPC.User> users = new LongSparseArray<>();
private String offset;
private boolean isLoading, isLoaded, canLoadMore = true;
private boolean onlySeenNow;
private OnHeightChangedListener onHeightChangedListener;
private OnProfileSelectedListener onProfileSelectedListener;
public ReactedUsersListView(Context context, Theme.ResourcesProvider resourcesProvider, int currentAccount, MessageObject message, TLRPC.TL_reactionCount reactionCount, boolean addPadding) {
super(context);
this.currentAccount = currentAccount;
this.message = message;
this.filter = reactionCount == null ? null : reactionCount.reaction;
listView = new RecyclerListView(context, resourcesProvider) {
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
super.onMeasure(widthSpec, heightSpec);
updateHeight();
}
};
LinearLayoutManager llm = new LinearLayoutManager(context);
listView.setLayoutManager(llm);
if (addPadding) {
listView.setPadding(0, 0, 0, AndroidUtilities.dp(8));
listView.setClipToPadding(false);
}
listView.setAdapter(adapter = new RecyclerView.Adapter() {
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new RecyclerListView.Holder(new ReactedUserHolderView(context));
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
ReactedUserHolderView rhv = (ReactedUserHolderView) holder.itemView;
rhv.setUserReaction(userReactions.get(position));
}
@Override
public int getItemCount() {
return userReactions.size();
}
});
listView.setOnItemClickListener((view, position) -> {
if (onProfileSelectedListener != null)
onProfileSelectedListener.onProfileSelected(this, userReactions.get(position).user_id);
});
listView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
if (isLoaded && canLoadMore && !isLoading && llm.findLastVisibleItemPosition() >= adapter.getItemCount() - 1 - getLoadCount()) {
load();
}
}
});
listView.setVerticalScrollBarEnabled(true);
listView.setAlpha(0);
addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
loadingView = new FlickerLoadingView(context, resourcesProvider);
loadingView.setViewType(FlickerLoadingView.REACTED_TYPE);
loadingView.setIsSingleCell(true);
loadingView.setItemsCount(reactionCount == null ? VISIBLE_ITEMS : reactionCount.count);
addView(loadingView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
}
@SuppressLint("NotifyDataSetChanged")
public ReactedUsersListView setSeenUsers(List<TLRPC.User> users) {
List<TLRPC.TL_messageUserReaction> nr = new ArrayList<>(users.size());
for (TLRPC.User u : users) {
if (this.users.get(u.id) != null) continue;
this.users.put(u.id, u);
TLRPC.TL_messageUserReaction r = new TLRPC.TL_messageUserReaction();
r.reaction = null;
r.user_id = u.id;
nr.add(r);
}
if (userReactions.isEmpty())
onlySeenNow = true;
userReactions.addAll(nr);
adapter.notifyDataSetChanged();
updateHeight();
return this;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (!isLoaded && !isLoading) {
load();
}
}
@SuppressLint("NotifyDataSetChanged")
private void load() {
isLoading = true;
MessagesController ctrl = MessagesController.getInstance(currentAccount);
TLRPC.TL_messages_getMessageReactionsList getList = new TLRPC.TL_messages_getMessageReactionsList();
getList.peer = ctrl.getInputPeer(message.getDialogId());
getList.id = message.getId();
getList.limit = getLoadCount();
getList.reaction = filter;
getList.offset = offset;
if (filter != null)
getList.flags |= 1;
if (offset != null)
getList.flags |= 2;
ConnectionsManager.getInstance(currentAccount).sendRequest(getList, (response, error) -> {
if (response instanceof TLRPC.TL_messages_messageReactionsList) {
TLRPC.TL_messages_messageReactionsList l = (TLRPC.TL_messages_messageReactionsList) response;
for (TLRPC.User u : l.users) {
users.put(u.id, u);
}
// It's safer to create a new list to prevent inconsistency
int prev = userReactions.size();
List<TLRPC.TL_messageUserReaction> newReactions = new ArrayList<>(userReactions.size() + l.reactions.size());
newReactions.addAll(userReactions);
newReactions.addAll(l.reactions);
if (onlySeenNow)
Collections.sort(newReactions, (o1, o2) -> Integer.compare(o1.reaction != null ? 1 : 0, o2.reaction != null ? 1 : 0));
AndroidUtilities.runOnUIThread(()->{
userReactions = newReactions;
if (onlySeenNow) {
onlySeenNow = false;
adapter.notifyDataSetChanged();
} else adapter.notifyItemRangeInserted(prev, l.reactions.size());
if (!isLoaded) {
ValueAnimator anim = ValueAnimator.ofFloat(0, 1).setDuration(150);
anim.setInterpolator(CubicBezierInterpolator.DEFAULT);
anim.addUpdateListener(animation -> {
float val = (float) animation.getAnimatedValue();
listView.setAlpha(val);
loadingView.setAlpha(1f - val);
});
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
loadingView.setVisibility(GONE);
}
});
anim.start();
updateHeight();
isLoaded = true;
}
offset = l.next_offset;
if (offset == null)
canLoadMore = false;
isLoading = false;
});
} else isLoading = false;
}, ConnectionsManager.RequestFlagInvokeAfter);
}
private void updateHeight() {
if (onHeightChangedListener != null) {
int h;
if (listView.getMeasuredHeight() != 0) {
h = Math.min(listView.getMeasuredHeight(), AndroidUtilities.dp(ITEM_HEIGHT_DP * Math.min(userReactions.size(), VISIBLE_ITEMS)));
} else {
h = AndroidUtilities.dp(ITEM_HEIGHT_DP * Math.min(userReactions.size(), VISIBLE_ITEMS));
}
onHeightChangedListener.onHeightChanged(ReactedUsersListView.this, h);
}
}
private int getLoadCount() {
return filter == null ? 100 : 50;
}
private final class ReactedUserHolderView extends FrameLayout {
BackupImageView avatarView;
TextView titleView;
BackupImageView reactView;
AvatarDrawable avatarDrawable = new AvatarDrawable();
View overlaySelectorView;
ReactedUserHolderView(@NonNull Context context) {
super(context);
setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, AndroidUtilities.dp(48)));
avatarView = new BackupImageView(context);
avatarView.setRoundRadius(AndroidUtilities.dp(32));
addView(avatarView, LayoutHelper.createFrameRelatively(36, 36, Gravity.START | Gravity.CENTER_VERTICAL, 8, 0, 0, 0));
titleView = new TextView(context);
titleView.setLines(1);
titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
titleView.setTextColor(Theme.getColor(Theme.key_actionBarDefaultSubmenuItem));
titleView.setEllipsize(TextUtils.TruncateAt.END);
addView(titleView, LayoutHelper.createFrameRelatively(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL, 65, 0, 44, 0));
reactView = new BackupImageView(context);
addView(reactView, LayoutHelper.createFrameRelatively(24, 24, Gravity.END | Gravity.CENTER_VERTICAL, 0, 0, 12, 0));
overlaySelectorView = new View(context);
overlaySelectorView.setBackground(Theme.getSelectorDrawable(false));
addView(overlaySelectorView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
}
void setUserReaction(TLRPC.TL_messageUserReaction reaction) {
TLRPC.User u = users.get(reaction.user_id);
avatarDrawable.setInfo(u);
titleView.setText(UserObject.getUserName(u));
avatarView.setImage(ImageLocation.getForUser(u, ImageLocation.TYPE_SMALL), "50_50", avatarDrawable, u);
if (reaction.reaction != null) {
TLRPC.TL_availableReaction r = MediaDataController.getInstance(currentAccount).getReactionsMap().get(reaction.reaction);
if (r != null) {
SvgHelper.SvgDrawable svgThumb = DocumentObject.getSvgThumb(r.static_icon.thumbs, Theme.key_windowBackgroundGray, 1.0f);
reactView.setImage(ImageLocation.getForDocument(r.static_icon), "50_50", "webp", svgThumb, r);
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(ITEM_HEIGHT_DP), MeasureSpec.EXACTLY));
}
}
public ReactedUsersListView setOnProfileSelectedListener(OnProfileSelectedListener onProfileSelectedListener) {
this.onProfileSelectedListener = onProfileSelectedListener;
return this;
}
public ReactedUsersListView setOnHeightChangedListener(OnHeightChangedListener onHeightChangedListener) {
this.onHeightChangedListener = onHeightChangedListener;
return this;
}
public interface OnHeightChangedListener {
void onHeightChanged(ReactedUsersListView view, int newHeight);
}
public interface OnProfileSelectedListener {
void onProfileSelected(ReactedUsersListView view, long userId);
}
}

View File

@ -0,0 +1,115 @@
package org.telegram.ui.Components;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.DocumentObject;
import org.telegram.messenger.ImageLocation;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MediaDataController;
import org.telegram.messenger.R;
import org.telegram.messenger.SvgHelper;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.Theme;
public class ReactionTabHolderView extends FrameLayout {
private Paint outlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Path path = new Path();
private RectF rect = new RectF();
private float radius = AndroidUtilities.dp(32);
private BackupImageView reactView;
private ImageView iconView;
private TextView counterView;
private float outlineProgress;
public ReactionTabHolderView(@NonNull Context context) {
super(context);
iconView = new ImageView(context);
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.msg_reactions_filled).mutate();
drawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_avatar_nameInMessageBlue), PorterDuff.Mode.MULTIPLY));
iconView.setImageDrawable(drawable);
addView(iconView, LayoutHelper.createFrameRelatively(24, 24, Gravity.START | Gravity.CENTER_VERTICAL, 8, 0, 8, 0));
reactView = new BackupImageView(context);
addView(reactView, LayoutHelper.createFrameRelatively(24, 24, Gravity.START | Gravity.CENTER_VERTICAL, 8, 0, 8, 0));
counterView = new TextView(context);
counterView.setTextColor(Theme.getColor(Theme.key_avatar_nameInMessageBlue));
counterView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
addView(counterView, LayoutHelper.createFrameRelatively(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL, 40, 0, 8, 0));
outlinePaint.setStyle(Paint.Style.STROKE);
outlinePaint.setStrokeWidth(AndroidUtilities.dp(1));
outlinePaint.setColor(Theme.getColor(Theme.key_avatar_nameInMessageBlue));
bgPaint.setColor(Theme.getColor(Theme.key_avatar_nameInMessageBlue));
bgPaint.setAlpha(0x10);
View overlaySelectorView = new View(context);
overlaySelectorView.setBackground(Theme.getSelectorDrawable(bgPaint.getColor(), false));
addView(overlaySelectorView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
setWillNotDraw(false);
}
public void setOutlineProgress(float outlineProgress) {
this.outlineProgress = outlineProgress;
invalidate();
}
public void setCounter(int count) {
counterView.setText(String.format("%s", LocaleController.formatShortNumber(count, null)));
iconView.setVisibility(VISIBLE);
reactView.setVisibility(GONE);
}
public void setCounter(int currentAccount, TLRPC.TL_reactionCount counter) {
counterView.setText(String.format("%s", LocaleController.formatShortNumber(counter.count, null)));
String e = counter.reaction;
for (TLRPC.TL_availableReaction r : MediaDataController.getInstance(currentAccount).getReactionsList()) {
if (r.reaction.equals(e)) {
SvgHelper.SvgDrawable svgThumb = DocumentObject.getSvgThumb(r.static_icon, Theme.key_windowBackgroundGray, 1.0f);
reactView.setImage(ImageLocation.getForDocument(r.static_icon), "50_50", "webp", svgThumb, r);
reactView.setVisibility(VISIBLE);
iconView.setVisibility(GONE);
break;
}
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
int s = canvas.save();
path.rewind();
rect.set(0, 0, getWidth(), getHeight());
path.addRoundRect(rect, radius, radius, Path.Direction.CW);
canvas.clipPath(path);
canvas.drawRoundRect(rect, radius, radius, bgPaint);
super.dispatchDraw(canvas);
outlinePaint.setAlpha((int) (outlineProgress * 0xFF));
float w = outlinePaint.getStrokeWidth();
rect.set(w, w, getWidth() - w, getHeight() - w);
canvas.drawRoundRect(rect, radius, radius, outlinePaint);
canvas.restoreToCount(s);
}
}

View File

@ -0,0 +1,376 @@
package org.telegram.ui.Components.Reactions;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ImageLocation;
import org.telegram.messenger.MediaDataController;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.BaseFragment;
import org.telegram.ui.Cells.ChatMessageCell;
import org.telegram.ui.ChatActivity;
import org.telegram.ui.Components.BackupImageView;
import org.telegram.ui.Components.CubicBezierInterpolator;
import org.telegram.ui.Components.ReactionsContainerLayout;
public class ReactionsEffectOverlay {
public static ReactionsEffectOverlay currentOverlay;
private final AnimationView effectImageView;
private final AnimationView emojiImageView;
private final FrameLayout container;
boolean animateIn;
float animateInProgress;
float animateOutProgress;
FrameLayout windowView;
BackupImageView backupImageView;
private static int unicPrefix;
int[] loc = new int[2];
private WindowManager windowManager;
private boolean dismissed;
private float dismissProgress;
private final int messageId;
private final long groupId;
private final String reaction;
private float lastDrawnToX;
private float lastDrawnToY;
private boolean started;
private ReactionsContainerLayout.ReactionHolderView holderView = null;
private boolean wasScrolled;
private ReactionsEffectOverlay(Context context, BaseFragment fragment, ReactionsContainerLayout reactionsLayout, ChatMessageCell cell, float x, float y, String reaction, int currentAccount) {
this.messageId = cell.getMessageObject().getId();
this.groupId = cell.getMessageObject().getGroupId();
this.reaction = reaction;
ReactionsLayoutInBubble.ReactionButton reactionButton = cell.getReactionButton(reaction);
float fromX, fromY, fromHeight, fromWidth;
if (reactionsLayout != null) {
for (int i = 0; i < reactionsLayout.recyclerListView.getChildCount(); i++) {
if (((ReactionsContainerLayout.ReactionHolderView) reactionsLayout.recyclerListView.getChildAt(i)).currentReaction.reaction.equals(reaction)) {
holderView = ((ReactionsContainerLayout.ReactionHolderView) reactionsLayout.recyclerListView.getChildAt(i));
break;
}
}
}
boolean fromHolder = holderView != null || (x != 0 && y != 0);
if (holderView != null) {
reactionsLayout.getLocationOnScreen(loc);
fromX = loc[0] + holderView.getX() + holderView.backupImageView.getX() + AndroidUtilities.dp(16);
fromY = loc[1] + holderView.getY() + holderView.backupImageView.getY() + AndroidUtilities.dp(16);
fromHeight = holderView.backupImageView.getWidth();
} else if (reactionButton != null) {
cell.getLocationInWindow(loc);
fromX = loc[0] + cell.reactionsLayoutInBubble.x + reactionButton.x + reactionButton.imageReceiver.getImageX();
fromY = loc[1] + cell.reactionsLayoutInBubble.y + reactionButton.y + reactionButton.imageReceiver.getImageY();
fromHeight = reactionButton.imageReceiver.getImageHeight();
fromWidth = reactionButton.imageReceiver.getImageWidth();
} else {
((View) cell.getParent()).getLocationInWindow(loc);
fromX = loc[0] + x;
fromY = loc[1] + y;
fromHeight = 0;
fromWidth = 0;
}
int size = Math.round(Math.min(AndroidUtilities.dp(350), Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y)) * 0.8f);
int sizeForFilter = (int) (2f * size / AndroidUtilities.density);
int emojiSize = size >> 1;
int emojiSizeForFilter = sizeForFilter >> 1;
float fromScale = fromHeight / (float) emojiSize;
animateInProgress = 0f;
animateOutProgress = 0f;
container = new FrameLayout(context);
windowView = new FrameLayout(context) {
@Override
protected void dispatchDraw(Canvas canvas) {
if (dismissed) {
if (dismissProgress != 1f) {
dismissProgress += 16 / 150f;
if (dismissProgress > 1f) {
dismissProgress = 1f;
AndroidUtilities.runOnUIThread(() -> {
try {
windowManager.removeView(windowView);
} catch (Exception e) {
}
});
}
}
if (dismissProgress != 1f) {
setAlpha(1f - dismissProgress);
super.dispatchDraw(canvas);
}
invalidate();
return;
}
if (!started) {
invalidate();
return;
} else {
if (holderView != null) {
holderView.backupImageView.setAlpha(0);
}
}
ChatMessageCell drawingCell;
if (fragment instanceof ChatActivity) {
drawingCell = ((ChatActivity) fragment).findMessageCell(messageId);
} else {
drawingCell = cell;
}
float toX, toY;
if (drawingCell != null) {
cell.getLocationInWindow(loc);
ReactionsLayoutInBubble.ReactionButton reactionButton = cell.getReactionButton(reaction);
toX = loc[0] + cell.reactionsLayoutInBubble.x;
toY = loc[1] + cell.reactionsLayoutInBubble.y;
if (reactionButton != null) {
toX += reactionButton.x + reactionButton.imageReceiver.getImageX();
toY += reactionButton.y + reactionButton.imageReceiver.getImageY();
}
lastDrawnToX = toX;
lastDrawnToY = toY;
} else {
toX = lastDrawnToX;
toY = lastDrawnToY;
}
float previewX = toX - emojiSize / 2f;
float previewY = toY - emojiSize / 2f;
if (fragment.getParentActivity() != null && fragment.getFragmentView().getParent() != null && fragment.getFragmentView().getVisibility() == View.VISIBLE && fragment.getFragmentView() != null) {
fragment.getFragmentView().getLocationOnScreen(loc);
setAlpha(((View) fragment.getFragmentView().getParent()).getAlpha());
} else {
return;
}
if (previewX < loc[0]) {
previewX = loc[0];
}
if (previewX + emojiSize > loc[0] + getMeasuredWidth()) {
previewX = loc[0] + getMeasuredWidth() - emojiSize;
}
float animateInProgressX, animateInProgressY;
if (fromHolder) {
animateInProgressX = CubicBezierInterpolator.EASE_OUT_QUINT.getInterpolation(animateInProgress);
animateInProgressY = CubicBezierInterpolator.DEFAULT.getInterpolation(animateInProgress);
} else {
animateInProgressX = animateInProgressY = animateInProgress;
}
float scale = animateInProgressX + (1f - animateInProgressX) * fromScale;
float toScale;
if (cell.getMessageObject().shouldDrawReactionsInLayout()) {
toScale = AndroidUtilities.dp(20) / (float) emojiSize;
} else {
toScale = AndroidUtilities.dp(14) / (float) emojiSize;
}
float x = fromX * (1f - animateInProgressX) + previewX * animateInProgressX;
float y = fromY * (1f - animateInProgressY) + previewY * animateInProgressY;
effectImageView.setTranslationX(x);
effectImageView.setTranslationY(y);
effectImageView.setAlpha((1f - animateOutProgress));
if (animateOutProgress != 0) {
scale = scale * (1f - animateOutProgress) + toScale * animateOutProgress;
x = x * (1f - animateOutProgress) + toX * animateOutProgress;
y = y * (1f - animateOutProgress) + toY * animateOutProgress;
}
container.setTranslationX(x);
container.setTranslationY(y);
container.setScaleX(scale);
container.setScaleY(scale);
super.dispatchDraw(canvas);
if (emojiImageView.wasPlaying && animateInProgress != 1f) {
if (fromHolder) {
animateInProgress += 16f / 350f;
} else {
animateInProgress += 16f / 220f;
}
if (animateInProgress > 1f) {
animateInProgress = 1f;
}
}
if (wasScrolled || (emojiImageView.wasPlaying && emojiImageView.getImageReceiver().getLottieAnimation() != null && !emojiImageView.getImageReceiver().getLottieAnimation().isRunning())) {
if (animateOutProgress != 1f) {
animateOutProgress += 16f / 220f;
if (animateOutProgress > 1f) {
animateOutProgress = 1f;
currentOverlay = null;
cell.invalidate();
if (cell.getCurrentMessagesGroup() != null && cell.getParent() != null) {
((View) cell.getParent()).invalidate();
}
AndroidUtilities.runOnUIThread(() -> {
try {
windowManager.removeView(windowView);
} catch (Exception e) {
}
});
}
}
}
invalidate();
}
};
effectImageView = new AnimationView(context);
emojiImageView = new AnimationView(context);
TLRPC.TL_availableReaction availableReaction = MediaDataController.getInstance(currentAccount).getReactionsMap().get(reaction);
if (availableReaction != null) {
TLRPC.Document document = availableReaction.effect_animation;
effectImageView.getImageReceiver().setUniqKeyPrefix((unicPrefix++) + "_" + cell.getMessageObject().getId() + "_");
effectImageView.setImage(ImageLocation.getForDocument(document), sizeForFilter + "_" + sizeForFilter + "_pcache", null, null, 0, null);
effectImageView.getImageReceiver().setAutoRepeat(0);
effectImageView.getImageReceiver().setAllowStartAnimation(false);
if (effectImageView.getImageReceiver().getLottieAnimation() != null) {
effectImageView.getImageReceiver().getLottieAnimation().setCurrentFrame(0, false);
effectImageView.getImageReceiver().getLottieAnimation().start();
}
document = availableReaction.activate_animation;
emojiImageView.getImageReceiver().setUniqKeyPrefix((unicPrefix++) + "_" + cell.getMessageObject().getId() + "_");
emojiImageView.setImage(ImageLocation.getForDocument(document), emojiSizeForFilter + "_" + emojiSizeForFilter, null, null, 0, null);
emojiImageView.getImageReceiver().setAutoRepeat(0);
emojiImageView.getImageReceiver().setAllowStartAnimation(false);
if (emojiImageView.getImageReceiver().getLottieAnimation() != null) {
emojiImageView.getImageReceiver().getLottieAnimation().setCurrentFrame(0, false);
emojiImageView.getImageReceiver().getLottieAnimation().start();
}
int topOffset = (size - emojiSize) >> 1;
int leftOffset = size - emojiSize;
container.addView(emojiImageView);
emojiImageView.getLayoutParams().width = emojiSize;
emojiImageView.getLayoutParams().height = emojiSize;
((FrameLayout.LayoutParams) emojiImageView.getLayoutParams()).topMargin = topOffset;
((FrameLayout.LayoutParams) emojiImageView.getLayoutParams()).leftMargin = leftOffset;
windowView.addView(container);
container.getLayoutParams().width = size;
container.getLayoutParams().height = size;
((FrameLayout.LayoutParams) container.getLayoutParams()).topMargin = -topOffset;
((FrameLayout.LayoutParams) container.getLayoutParams()).leftMargin = -leftOffset;
windowView.addView(effectImageView);
effectImageView.getLayoutParams().width = size;
effectImageView.getLayoutParams().height = size;
effectImageView.getLayoutParams().width = size;
effectImageView.getLayoutParams().height = size;
((FrameLayout.LayoutParams) effectImageView.getLayoutParams()).topMargin = -topOffset;
((FrameLayout.LayoutParams) effectImageView.getLayoutParams()).leftMargin = -leftOffset;
container.setPivotX(leftOffset);
container.setPivotY(topOffset);
}
}
public static void show(BaseFragment baseFragment, ReactionsContainerLayout reactionsLayout, ChatMessageCell cell, float x, float y, String reaction, int currentAccount) {
if (cell == null) {
return;
}
ReactionsEffectOverlay reactionsEffectOverlay = new ReactionsEffectOverlay(baseFragment.getParentActivity(), baseFragment, reactionsLayout, cell, x, y, reaction, currentAccount);
currentOverlay = reactionsEffectOverlay;
WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
lp.width = lp.height = WindowManager.LayoutParams.MATCH_PARENT;
lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
lp.format = PixelFormat.TRANSLUCENT;
reactionsEffectOverlay.windowManager = baseFragment.getParentActivity().getWindowManager();
reactionsEffectOverlay.windowManager.addView(reactionsEffectOverlay.windowView, lp);
cell.invalidate();
if (cell.getCurrentMessagesGroup() != null && cell.getParent() != null) {
((View) cell.getParent()).invalidate();
}
}
public static void startAnimation() {
if (currentOverlay != null) {
currentOverlay.started = true;
}
}
public static void removeCurrent(boolean instant) {
if (currentOverlay != null) {
if (instant) {
try {
currentOverlay.windowManager.removeView(currentOverlay.windowView);
} catch (Exception e) {
}
} else {
currentOverlay.dismissed = true;
}
}
currentOverlay = null;
}
public static boolean isPlaying(int messageId, long groupId, String reaction) {
if (currentOverlay != null) {
return ((currentOverlay.groupId != 0 && groupId == currentOverlay.groupId) || messageId == currentOverlay.messageId) && currentOverlay.reaction.equals(reaction);
}
return false;
}
private class AnimationView extends BackupImageView {
public AnimationView(Context context) {
super(context);
}
boolean wasPlaying;
@Override
protected void onDraw(Canvas canvas) {
if (getImageReceiver().getLottieAnimation() != null && getImageReceiver().getLottieAnimation().isRunning()) {
wasPlaying = true;
}
if (!wasPlaying && getImageReceiver().getLottieAnimation() != null && !getImageReceiver().getLottieAnimation().isRunning()) {
getImageReceiver().getLottieAnimation().setCurrentFrame(0, false);
getImageReceiver().getLottieAnimation().start();
}
super.onDraw(canvas);
}
}
public static void onScrolled(int dy) {
if (currentOverlay != null) {
currentOverlay.lastDrawnToY -= dy;
if (dy != 0) {
currentOverlay.wasScrolled = true;
}
}
}
}

View File

@ -0,0 +1,625 @@
package org.telegram.ui.Components.Reactions;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.text.TextPaint;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import androidx.core.graphics.ColorUtils;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.DocumentObject;
import org.telegram.messenger.ImageLocation;
import org.telegram.messenger.ImageReceiver;
import org.telegram.messenger.MediaDataController;
import org.telegram.messenger.MessageObject;
import org.telegram.messenger.MessagesController;
import org.telegram.messenger.SvgHelper;
import org.telegram.messenger.UserConfig;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Cells.ChatMessageCell;
import org.telegram.ui.Components.AvatarsDarawable;
import org.telegram.ui.Components.CounterView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
public class ReactionsLayoutInBubble {
private final static int ANIMATION_TYPE_IN = 1;
private final static int ANIMATION_TYPE_OUT = 2;
private final static int ANIMATION_TYPE_MOVE = 3;
public boolean drawServiceShaderBackground;
private static Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private static TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
public boolean isSmall;
public int x;
public int y;
private float fromX;
private float fromY;
private float lastDrawnX;
private float lastDrawnY;
private boolean wasDrawn;
private boolean animateMove;
private boolean animateWidth;
public int positionOffsetY;
int currentAccount;
public int height;
public int totalHeight;
public int width;
public int fromWidth;
public boolean isEmpty;
private float touchSlop;
public int lastLineX;
ArrayList<ReactionButton> reactionButtons = new ArrayList<>();
ArrayList<ReactionButton> outButtons = new ArrayList<>();
HashMap<String, ReactionButton> lastDrawingReactionButtons = new HashMap<>();
HashMap<String, ReactionButton> lastDrawingReactionButtonsTmp = new HashMap<>();
ChatMessageCell parentView;
MessageObject messageObject;
Theme.ResourcesProvider resourcesProvider;
int availableWidth;
private int lastDrawnWidth;
boolean attached;
private final static ButtonsComparator comparator = new ButtonsComparator();
public ReactionsLayoutInBubble(ChatMessageCell parentView) {
this.parentView = parentView;
currentAccount = UserConfig.selectedAccount;
paint.setColor(Theme.getColor(Theme.key_chat_inLoader));
textPaint.setColor(Theme.getColor(Theme.key_featuredStickers_buttonText));
textPaint.setTextSize(AndroidUtilities.dp(12));
textPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
touchSlop = ViewConfiguration.get(ApplicationLoader.applicationContext).getScaledTouchSlop();
}
public void setMessage(MessageObject messageObject, boolean isSmall, Theme.ResourcesProvider resourcesProvider) {
this.resourcesProvider = resourcesProvider;
this.isSmall = isSmall;
this.messageObject = messageObject;
for (int i = 0; i < reactionButtons.size(); i++) {
reactionButtons.get(i).detach();
}
reactionButtons.clear();
if (messageObject != null) {
if (messageObject.messageOwner.reactions != null && messageObject.messageOwner.reactions.results != null) {
int totalCount = 0;
for (int i = 0; i < messageObject.messageOwner.reactions.results.size(); i++) {
totalCount += messageObject.messageOwner.reactions.results.get(i).count;
}
for (int i = 0; i < messageObject.messageOwner.reactions.results.size(); i++) {
TLRPC.TL_reactionCount reactionCount = messageObject.messageOwner.reactions.results.get(i);
ReactionButton button = new ReactionButton(reactionCount);
reactionButtons.add(button);
if (!isSmall && messageObject.messageOwner.reactions.recent_reactons != null) {
ArrayList<TLRPC.User> users = null;
if (reactionCount.count <= 3 && totalCount <= 3) {
for (int j = 0; j < messageObject.messageOwner.reactions.recent_reactons.size(); j++) {
TLRPC.TL_messageUserReaction reccent = messageObject.messageOwner.reactions.recent_reactons.get(j);
if (reccent.reaction.equals(reactionCount.reaction) && MessagesController.getInstance(currentAccount).getUser(reccent.user_id) != null) {
if (users == null) {
users = new ArrayList<>();
}
users.add(MessagesController.getInstance(currentAccount).getUser(reccent.user_id));
}
}
button.setUsers(users);
if (users != null && !users.isEmpty()) {
button.count = 0;
button.counterDrawable.setCount(0, false);
}
}
}
if (isSmall && reactionCount.count > 1 && reactionCount.chosen) {
reactionButtons.add(new ReactionButton(reactionCount));
reactionButtons.get(0).isSelected = false;
reactionButtons.get(1).isSelected = true;
break;
}
if (isSmall && i == 2) {
break;
}
if (attached) {
button.attach();
}
}
}
comparator.currentAccount = currentAccount;
Collections.sort(reactionButtons, comparator);
}
isEmpty = reactionButtons.isEmpty();
}
public void measure(int availableWidth) {
height = 0;
width = 0;
positionOffsetY = 0;
if (isEmpty) {
return;
}
this.availableWidth = availableWidth;
int maxWidth = 0;
int currentX = 0;
int currentY = 0;
for (int i = 0; i < reactionButtons.size(); i++) {
ReactionButton button = reactionButtons.get(i);
if (isSmall) {
button.width = AndroidUtilities.dp(14);
button.height = AndroidUtilities.dp(14);
} else {
button.width = (int) (AndroidUtilities.dp(8) + AndroidUtilities.dp(20) + AndroidUtilities.dp(4));
if (button.avatarsDarawable != null && button.users.size() > 0) {
button.users.size();
int c1 = 1;
int c2 = button.users.size() > 1 ? button.users.size() - 1 : 0;
button.width += AndroidUtilities.dp(2) + c1 * AndroidUtilities.dp(20) + c2 * AndroidUtilities.dp(20) * 0.8f + AndroidUtilities.dp(1);
button.avatarsDarawable.height = AndroidUtilities.dp(26);
} else {
button.width += button.counterDrawable.textPaint.measureText(button.countText) + AndroidUtilities.dp(8);
}
button.height = AndroidUtilities.dp(26);
}
if (currentX + button.width > availableWidth) {
currentX = 0;
currentY += button.height + AndroidUtilities.dp(4);
}
button.x = currentX;
button.y = currentY;
currentX += button.width + AndroidUtilities.dp(4);
if (currentX > maxWidth) {
maxWidth = currentX;
}
}
lastLineX = currentX;
width = maxWidth;
height = currentY + (reactionButtons.size() == 0 ? 0 : AndroidUtilities.dp(26));
drawServiceShaderBackground = false;
}
public void draw(Canvas canvas, float animationProgress) {
if (isEmpty && outButtons.isEmpty()) {
return;
}
float totalX = this.x;
float totalY = this.y;
if (isEmpty) {
totalX = lastDrawnX;
totalY = lastDrawnY;
} else if (animateMove) {
totalX = totalX * (animationProgress) + fromX * (1f - animationProgress);
totalY = totalY * (animationProgress) + fromY * (1f - animationProgress);
}
canvas.save();
canvas.translate(totalX, totalY);
for (int i = 0; i < reactionButtons.size(); i++) {
ReactionButton reactionButton = reactionButtons.get(i);
canvas.save();
float x = reactionButton.x;
float y = reactionButton.y;
if (animationProgress != 1f && reactionButton.animationType == ANIMATION_TYPE_MOVE) {
x = reactionButton.x * animationProgress + reactionButton.animateFromX * (1f - animationProgress);
y = reactionButton.y * animationProgress + reactionButton.animateFromY * (1f - animationProgress);
}
canvas.translate(x, y);
float alpha = 1f;
if (animationProgress != 1f && reactionButton.animationType == ANIMATION_TYPE_IN) {
float s = 0.5f + 0.5f * animationProgress;
alpha = animationProgress;
canvas.scale(s, s, reactionButton.width / 2f, reactionButton.height / 2f);
}
reactionButton.draw(canvas, reactionButton.animationType == ANIMATION_TYPE_IN ? 1f : animationProgress, alpha);
canvas.restore();
}
for (int i = 0; i < outButtons.size(); i++) {
ReactionButton reactionButton = outButtons.get(i);
canvas.save();
canvas.translate(reactionButton.x, reactionButton.y);
float s = 0.5f + 0.5f * (1f - animationProgress);
canvas.scale(s, s, reactionButton.width / 2f, reactionButton.height / 2f);
outButtons.get(i).draw(canvas, 1f, (1f - animationProgress));
canvas.restore();
}
canvas.restore();
}
public void recordDrawingState() {
lastDrawingReactionButtons.clear();
for (int i = 0; i < reactionButtons.size(); i++) {
lastDrawingReactionButtons.put(reactionButtons.get(i).reaction, reactionButtons.get(i));
}
wasDrawn = !isEmpty;
lastDrawnX = x;
lastDrawnY = y;
lastDrawnWidth = width;
}
public boolean animateChange() {
boolean changed = false;
lastDrawingReactionButtonsTmp.clear();
for (int i = 0; i < outButtons.size(); i++) {
outButtons.get(i).detach();
}
outButtons.clear();
lastDrawingReactionButtonsTmp.putAll(lastDrawingReactionButtons);
for (int i = 0; i < reactionButtons.size(); i++) {
ReactionButton button = reactionButtons.get(i);
ReactionButton lastButton = lastDrawingReactionButtonsTmp.remove(button.reaction);
if (lastButton != null) {
if (button.animateFromX != lastButton.x || button.animateFromY != lastButton.y || button.animateFromWidth != lastButton.width || button.count != lastButton.count || button.backgroundColor != lastButton.backgroundColor) {
button.animateFromX = lastButton.x;
button.animateFromY = lastButton.y;
button.animateFromWidth = lastButton.width;
button.fromTextColor = lastButton.lastDrawnTextColor;
button.fromBackgroundColor = lastButton.lastDrawnBackgroundColor;
button.animationType = ANIMATION_TYPE_MOVE;
if (button.count != lastButton.count) {
button.counterDrawable.setCount(lastButton.count, false);
button.counterDrawable.setCount(button.count, true);
}
if (button.avatarsDarawable != null || lastButton.avatarsDarawable != null) {
if (button.avatarsDarawable == null) {
button.setUsers(new ArrayList<>());
}
if (lastButton.avatarsDarawable == null) {
lastButton.setUsers(new ArrayList<>());
}
button.avatarsDarawable.animateFromState(lastButton.avatarsDarawable, currentAccount);
}
changed = true;
} else {
button.animationType = 0;
}
} else {
changed = true;
button.animationType = ANIMATION_TYPE_IN;
}
}
if (!lastDrawingReactionButtonsTmp.isEmpty()) {
changed = true;
outButtons.addAll(lastDrawingReactionButtonsTmp.values());
for (int i = 0; i < outButtons.size(); i++) {
outButtons.get(i).drawImage = outButtons.get(i).lastImageDrawn;
outButtons.get(i).attach();
}
}
if (wasDrawn && (lastDrawnX != x || lastDrawnY != y)) {
animateMove = true;
fromX = lastDrawnX;
fromY = lastDrawnY;
changed = true;
}
if (lastDrawnWidth != width) {
animateWidth = true;
fromWidth = lastDrawnWidth;
changed = true;
}
return changed;
}
public void resetAnimation() {
for (int i = 0; i < outButtons.size(); i++) {
outButtons.get(i).detach();
}
outButtons.clear();
animateMove = false;
animateWidth = false;
for (int i = 0; i < reactionButtons.size(); i++) {
reactionButtons.get(i).animationType = 0;
}
}
public ReactionButton getReactionButton(String reaction) {
return lastDrawingReactionButtons.get(reaction);
}
public class ReactionButton {
private final TLRPC.TL_reactionCount reactionCount;
public int animationType;
public int animateFromX;
public int animateFromY;
public int animateFromWidth;
public int fromBackgroundColor;
public int fromTextColor;
public int realCount;
public boolean drawImage = true;
public boolean lastImageDrawn;
String countText;
String reaction;
int count;
public int x;
public int y;
int width;
int height;
ImageReceiver imageReceiver = new ImageReceiver();
CounterView.CounterDrawable counterDrawable = new CounterView.CounterDrawable(parentView, false, null);
int backgroundColor;
int textColor;
int serviceBackgroundColor;
int serviceTextColor;
int lastDrawnTextColor;
int lastDrawnBackgroundColor;
boolean isSelected;
AvatarsDarawable avatarsDarawable;
ArrayList<TLRPC.User> users;
public ReactionButton(TLRPC.TL_reactionCount reactionCount) {
this.reactionCount = reactionCount;
this.reaction = reactionCount.reaction;
this.count = reactionCount.count;
this.realCount = reactionCount.count;
countText = Integer.toString(reactionCount.count);
imageReceiver.setParentView(parentView);
isSelected = reactionCount.chosen;
counterDrawable.updateVisibility = false;
if (reactionCount.chosen) {
backgroundColor = Theme.getColor(messageObject.isOutOwner() ? Theme.key_chat_outReactionButtonBackground : Theme.key_chat_inReactionButtonBackground, resourcesProvider);
textColor = Theme.getColor(messageObject.isOutOwner() ? Theme.key_chat_outReactionButtonTextSelected : Theme.key_chat_inReactionButtonTextSelected, resourcesProvider);
serviceTextColor = Theme.getColor(messageObject.isOutOwner() ? Theme.key_chat_outReactionButtonBackground : Theme.key_chat_inReactionButtonBackground, resourcesProvider);
serviceBackgroundColor = Theme.getColor(messageObject.isOutOwner() ? Theme.key_chat_outBubble : Theme.key_chat_inBubble);
} else {
textColor = Theme.getColor(messageObject.isOutOwner() ? Theme.key_chat_outReactionButtonText : Theme.key_chat_inReactionButtonText, resourcesProvider);
backgroundColor = Theme.getColor(messageObject.isOutOwner() ? Theme.key_chat_outReactionButtonBackground : Theme.key_chat_inReactionButtonBackground, resourcesProvider);
backgroundColor = ColorUtils.setAlphaComponent(backgroundColor, (int) (Color.alpha(backgroundColor) * 0.156f));
serviceTextColor = Theme.getColor(Theme.key_chat_serviceText, resourcesProvider);
serviceBackgroundColor = Color.TRANSPARENT;
}
if (reaction != null) {
TLRPC.TL_availableReaction r = MediaDataController.getInstance(currentAccount).getReactionsMap().get(reaction);
if (r != null) {
SvgHelper.SvgDrawable svgThumb = DocumentObject.getSvgThumb(r.static_icon, Theme.key_windowBackgroundGray, 1.0f);
imageReceiver.setImage(ImageLocation.getForDocument(r.static_icon), "40_40", svgThumb, "webp", r, 1);
}
}
counterDrawable.setSize(AndroidUtilities.dp(26), AndroidUtilities.dp(100));
counterDrawable.textPaint = textPaint;
counterDrawable.setCount(count, false);
counterDrawable.setType(CounterView.CounterDrawable.TYPE_CHAT_REACTIONS);
counterDrawable.gravity = Gravity.LEFT;
}
public void draw(Canvas canvas, float progress, float alpha) {
if (isSmall) {
imageReceiver.setAlpha(alpha);
imageReceiver.setImageCoords(0, 0, AndroidUtilities.dp(14), AndroidUtilities.dp(14));
drawImage(canvas);
return;
}
if (drawServiceShaderBackground) {
textPaint.setColor(lastDrawnTextColor = ColorUtils.blendARGB(fromTextColor, serviceTextColor, progress));
paint.setColor(lastDrawnBackgroundColor = ColorUtils.blendARGB(fromBackgroundColor, serviceBackgroundColor, progress));
} else {
textPaint.setColor(lastDrawnTextColor = ColorUtils.blendARGB(fromTextColor, textColor, progress));
paint.setColor(lastDrawnBackgroundColor = ColorUtils.blendARGB(fromBackgroundColor, backgroundColor, progress));
}
if (alpha != 1f) {
textPaint.setAlpha((int) (textPaint.getAlpha() * alpha));
paint.setAlpha((int) (paint.getAlpha() * alpha));
}
imageReceiver.setAlpha(alpha);
int w = width;
if (progress != 1f && animationType == ANIMATION_TYPE_MOVE) {
w = (int) (width * progress + animateFromWidth * (1f - progress));
}
AndroidUtilities.rectTmp.set(0, 0, w, height);
float rad = height / 2f;
if (drawServiceShaderBackground) {
Paint paint1 = getThemedPaint(Theme.key_paint_chatActionBackground);
Paint paint2 = Theme.chat_actionBackgroundGradientDarkenPaint;
int oldAlpha = paint1.getAlpha();
int oldAlpha2 = paint2.getAlpha();
paint1.setAlpha((int) (oldAlpha * alpha));
paint2.setAlpha((int) (oldAlpha2 * alpha));
canvas.drawRoundRect(AndroidUtilities.rectTmp, rad, rad, paint1);
if (hasGradientService()) {
canvas.drawRoundRect(AndroidUtilities.rectTmp, rad, rad, paint2);
}
paint1.setAlpha(oldAlpha);
paint2.setAlpha(oldAlpha2);
}
canvas.drawRoundRect(AndroidUtilities.rectTmp, rad, rad, paint);
imageReceiver.setImageCoords(AndroidUtilities.dp(8), (height - AndroidUtilities.dp(20)) / 2f, AndroidUtilities.dp(20), AndroidUtilities.dp(20));
drawImage(canvas);
if (count != 0 || counterDrawable.countChangeProgress != 1f) {
canvas.save();
canvas.translate(AndroidUtilities.dp(8) + AndroidUtilities.dp(20) + AndroidUtilities.dp(2), 0);
counterDrawable.draw(canvas);
canvas.restore();
}
if (avatarsDarawable != null) {
canvas.save();
canvas.translate(AndroidUtilities.dp(10) + AndroidUtilities.dp(20) + AndroidUtilities.dp(2), 0);
avatarsDarawable.setAlpha(alpha);
avatarsDarawable.onDraw(canvas);
canvas.restore();
}
}
private void drawImage(Canvas canvas) {
if (drawImage && ((realCount > 1 || !ReactionsEffectOverlay.isPlaying(messageObject.getId(), messageObject.getGroupId(), reaction)) || !isSelected)) {
imageReceiver.draw(canvas);
lastImageDrawn = true;
} else {
imageReceiver.setAlpha(0);
imageReceiver.draw(canvas);
lastImageDrawn = false;
}
}
public void setUsers(ArrayList<TLRPC.User> users) {
this.users = users;
if (users != null) {
if (avatarsDarawable == null) {
avatarsDarawable = new AvatarsDarawable(parentView, false);
avatarsDarawable.setSize(AndroidUtilities.dp(20));
avatarsDarawable.width = AndroidUtilities.dp(100);
avatarsDarawable.height = height;
if (attached) {
avatarsDarawable.onAttachedToWindow();
}
}
for (int i = 0; i < users.size(); i++) {
if (i == 3) {
break;
}
avatarsDarawable.setObject(i, currentAccount, users.get(i));
}
avatarsDarawable.commitTransition(false);
}
}
public void attach() {
if (imageReceiver != null) {
imageReceiver.onAttachedToWindow();
}
if (avatarsDarawable != null) {
avatarsDarawable.onAttachedToWindow();
}
}
public void detach() {
if (imageReceiver != null) {
imageReceiver.onDetachedFromWindow();
}
if (avatarsDarawable != null) {
avatarsDarawable.onDetachedFromWindow();
}
}
}
static int attachedCount;
float lastX;
float lastY;
ReactionButton lastSelectedButton;
boolean pressed;
Runnable longPressRunnable;
public boolean chekTouchEvent(MotionEvent event) {
if (isEmpty || messageObject == null || messageObject.messageOwner == null || messageObject.messageOwner.reactions == null) {
return false;
}
float x = event.getX() - this.x;
float y = event.getY() - this.y;
if (event.getAction() == MotionEvent.ACTION_DOWN) {
for (int i = 0, n = reactionButtons.size(); i < n; i++) {
if (x > reactionButtons.get(i).x && x < reactionButtons.get(i).x + reactionButtons.get(i).width &&
y > reactionButtons.get(i).y && y < reactionButtons.get(i).y + reactionButtons.get(i).height) {
lastX = event.getX();
lastY = event.getY();
lastSelectedButton = reactionButtons.get(i);
if (longPressRunnable != null && messageObject.messageOwner.reactions.can_see_list) {
AndroidUtilities.cancelRunOnUIThread(longPressRunnable);
longPressRunnable = null;
}
final ReactionButton selectedButtonFinal = lastSelectedButton;
AndroidUtilities.runOnUIThread(longPressRunnable = () -> {
parentView.getDelegate().didPressReaction(parentView, selectedButtonFinal.reactionCount, true);
longPressRunnable = null;
}, ViewConfiguration.getLongPressTimeout());
pressed = true;
break;
}
}
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (pressed && Math.abs(event.getX() - lastX) > touchSlop || Math.abs(event.getY() - lastY) > touchSlop) {
pressed = false;
lastSelectedButton = null;
if (longPressRunnable != null) {
AndroidUtilities.cancelRunOnUIThread(longPressRunnable);
longPressRunnable = null;
}
}
} else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
if (longPressRunnable != null) {
AndroidUtilities.cancelRunOnUIThread(longPressRunnable);
longPressRunnable = null;
}
if (pressed && lastSelectedButton != null && event.getAction() == MotionEvent.ACTION_UP) {
if (parentView.getDelegate() != null) {
parentView.getDelegate().didPressReaction(parentView, lastSelectedButton.reactionCount, false);
}
}
pressed = false;
lastSelectedButton = null;
}
return pressed;
}
private boolean hasGradientService() {
return resourcesProvider != null ? resourcesProvider.hasGradientService() : Theme.hasGradientService();
}
private Paint getThemedPaint(String paintKey) {
Paint paint = resourcesProvider != null ? resourcesProvider.getPaint(paintKey) : null;
return paint != null ? paint : Theme.getThemePaint(paintKey);
}
public float getCurrentWidth(float transitionProgress) {
if (animateWidth) {
return fromWidth * (1f - transitionProgress) + width * transitionProgress;
}
return width;
}
private static class ButtonsComparator implements Comparator<ReactionButton> {
int currentAccount;
@Override
public int compare(ReactionButton o1, ReactionButton o2) {
if (o1.realCount != o2.realCount) {
return o2.realCount - o1.realCount;
}
TLRPC.TL_availableReaction availableReaction1 = MediaDataController.getInstance(currentAccount).getReactionsMap().get(o1.reaction);
TLRPC.TL_availableReaction availableReaction2 = MediaDataController.getInstance(currentAccount).getReactionsMap().get(o2.reaction);
if (availableReaction1 != null && availableReaction2 != null) {
return availableReaction1.positionInList - availableReaction2.positionInList;
}
return 0;
}
}
public void onAttachToWindow() {
for (int i = 0; i < reactionButtons.size(); i++) {
reactionButtons.get(i).attach();
}
}
public void onDetachFromWindow() {
for (int i = 0; i < reactionButtons.size(); i++) {
reactionButtons.get(i).detach();
}
}
}

View File

@ -0,0 +1,432 @@
package org.telegram.ui.Components;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.core.util.Consumer;
import androidx.dynamicanimation.animation.FloatPropertyCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.DocumentObject;
import org.telegram.messenger.ImageLocation;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.R;
import org.telegram.messenger.SvgHelper;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.Theme;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
public class ReactionsContainerLayout extends FrameLayout {
public final static FloatPropertyCompat<ReactionsContainerLayout> TRANSITION_PROGRESS_VALUE = new FloatPropertyCompat<ReactionsContainerLayout>("transitionProgress") {
@Override
public float getValue(ReactionsContainerLayout object) {
return object.transitionProgress * 100f;
}
@Override
public void setValue(ReactionsContainerLayout object, float value) {
object.setTransitionProgress(value / 100f);
}
};
private final static Random RANDOM = new Random();
private final static int ALPHA_DURATION = 150;
private final static float SIDE_SCALE = 0.6f;
private final static float SCALE_PROGRESS = 0.75f;
private final static float CLIP_PROGRESS = 0.25f;
public final RecyclerListView recyclerListView;
private Paint bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint leftShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG),
rightShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private float leftAlpha, rightAlpha;
private float transitionProgress = 1f;
private RectF rect = new RectF();
private Path mPath = new Path();
private float radius = AndroidUtilities.dp(72);
private float bigCircleRadius = AndroidUtilities.dp(8);
private float smallCircleRadius = bigCircleRadius / 2;
private int bigCircleOffset = AndroidUtilities.dp(36);
private List<TLRPC.TL_availableReaction> reactionsList = Collections.emptyList();
private LinearLayoutManager linearLayoutManager;
private RecyclerView.Adapter listAdapter;
private int[] location = new int[2];
private ReactionsContainerDelegate delegate;
private Rect shadowPad = new Rect();
private Drawable shadow;
private List<String> triggeredReactions = new ArrayList<>();
Theme.ResourcesProvider resourcesProvider;
public ReactionsContainerLayout(@NonNull Context context, Theme.ResourcesProvider resourcesProvider) {
super(context);
this.resourcesProvider = resourcesProvider;
shadow = ContextCompat.getDrawable(context, R.drawable.reactions_bubble_shadow).mutate();
shadowPad.left = shadowPad.top = shadowPad.right = shadowPad.bottom = AndroidUtilities.dp(7);
shadow.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_messagePanelShadow), PorterDuff.Mode.MULTIPLY));
recyclerListView = new RecyclerListView(context);
linearLayoutManager = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
recyclerListView.addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int position = parent.getChildAdapterPosition(view);
if (position == 0) {
outRect.left = AndroidUtilities.dp(6);
}
outRect.right = AndroidUtilities.dp(4);
if (position == listAdapter.getItemCount() - 1) {
outRect.right = AndroidUtilities.dp(6);
}
}
});
recyclerListView.setLayoutManager(linearLayoutManager);
recyclerListView.setOverScrollMode(View.OVER_SCROLL_NEVER);
recyclerListView.setAdapter(listAdapter = new RecyclerView.Adapter() {
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ReactionHolderView hv = new ReactionHolderView(context);
int size = getLayoutParams().height - getPaddingTop() - getPaddingBottom();
hv.setLayoutParams(new RecyclerView.LayoutParams(size - AndroidUtilities.dp(12), size));
return new RecyclerListView.Holder(hv);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
ReactionHolderView h = (ReactionHolderView) holder.itemView;
h.setScaleX(1);
h.setScaleY(1);
h.setReaction(reactionsList.get(position));
}
@Override
public int getItemCount() {
return reactionsList.size();
}
});
recyclerListView.setOnItemClickListener((view, position) -> {
ReactionHolderView h = (ReactionHolderView) view;
if (delegate != null)
delegate.onReactionClicked(h, h.currentReaction);
});
recyclerListView.addOnScrollListener(new LeftRightShadowsListener());
recyclerListView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
if (recyclerView.getChildCount() > 2) {
float sideDiff = 1f - SIDE_SCALE;
recyclerView.getLocationInWindow(location);
int rX = location[0];
View ch1 = recyclerView.getChildAt(0);
ch1.getLocationInWindow(location);
int ch1X = location[0];
int dX1 = ch1X - rX;
float s1 = SIDE_SCALE + (1f - Math.min(1, -Math.min(dX1, 0f) / ch1.getWidth())) * sideDiff;
if (Float.isNaN(s1)) s1 = 1f;
ch1.setScaleX(s1);
ch1.setScaleY(s1);
View ch2 = recyclerView.getChildAt(recyclerView.getChildCount() - 1);
ch2.getLocationInWindow(location);
int ch2X = location[0];
int dX2 = rX + recyclerView.getWidth() - (ch2X + ch2.getWidth());
float s2 = SIDE_SCALE + (1f - Math.min(1, -Math.min(dX2, 0f) / ch2.getWidth())) * sideDiff;
if (Float.isNaN(s2)) s2 = 1f;
ch2.setScaleX(s2);
ch2.setScaleY(s2);
}
for (int i = 1; i < recyclerListView.getChildCount() - 1; i++) {
View ch = recyclerListView.getChildAt(i);
float sc = 1f;
ch.setScaleX(sc);
ch.setScaleY(sc);
}
invalidate();
}
});
recyclerListView.addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
int i = parent.getChildAdapterPosition(view);
if (i == 0)
outRect.left = AndroidUtilities.dp(8);
if (i == listAdapter.getItemCount() - 1)
outRect.right = AndroidUtilities.dp(8);
}
});
addView(recyclerListView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
invalidateShaders();
bgPaint.setColor(Theme.getColor(Theme.key_actionBarDefaultSubmenuBackground, resourcesProvider));
}
public void setDelegate(ReactionsContainerDelegate delegate) {
this.delegate = delegate;
}
@SuppressLint("NotifyDataSetChanged")
public void setReactionsList(List<TLRPC.TL_availableReaction> reactionsList) {
this.reactionsList = reactionsList;
listAdapter.notifyDataSetChanged();
}
HashSet<View> lastVisibleViews = new HashSet<>();
HashSet<View> lastVisibleViewsTmp = new HashSet<>();
@Override
protected void dispatchDraw(Canvas canvas) {
lastVisibleViewsTmp.clear();
lastVisibleViewsTmp.addAll(lastVisibleViews);
lastVisibleViews.clear();
if (transitionProgress != 0) {
int delay = 0;
for (int i = 0; i < recyclerListView.getChildCount(); i++) {
View view = recyclerListView.getChildAt(i);
if (view.getX() + view.getMeasuredWidth() > 0 && view.getX() < getWidth()) {
if (!lastVisibleViewsTmp.contains(view)) {
((ReactionHolderView) view).play(delay);
delay += 50;
}
lastVisibleViews.add(view);
}
}
}
float cPr = (Math.max(CLIP_PROGRESS, Math.min(transitionProgress, 1f)) - CLIP_PROGRESS) / (1f - CLIP_PROGRESS);
float br = bigCircleRadius * cPr, sr = smallCircleRadius * cPr;
float cx = LocaleController.isRTL ? bigCircleOffset : getWidth() - bigCircleOffset, cy = getHeight() - getPaddingBottom();
int sPad = AndroidUtilities.dp(3);
shadow.setBounds((int) (cx - br - sPad * cPr), (int) (cy - br - sPad * cPr), (int) (cx + br + sPad * cPr), (int) (cy + br + sPad * cPr));
shadow.draw(canvas);
canvas.drawCircle(cx, cy, br, bgPaint);
cx = LocaleController.isRTL ? bigCircleOffset - bigCircleRadius : getWidth() - bigCircleOffset + bigCircleRadius;
cy = getHeight() - smallCircleRadius - sPad;
sPad = -AndroidUtilities.dp(1);
shadow.setBounds((int) (cx - br - sPad * cPr), (int) (cy - br - sPad * cPr), (int) (cx + br + sPad * cPr), (int) (cy + br + sPad * cPr));
shadow.draw(canvas);
canvas.drawCircle(cx, cy, sr, bgPaint);
int s = canvas.save();
mPath.rewind();
mPath.addCircle(LocaleController.isRTL ? bigCircleOffset : getWidth() - bigCircleOffset, getHeight() - getPaddingBottom(), br, Path.Direction.CW);
canvas.clipPath(mPath, Region.Op.DIFFERENCE);
float pivotX = LocaleController.isRTL ? getWidth() * 0.125f : getWidth() * 0.875f;
if (transitionProgress <= SCALE_PROGRESS) {
float sc = transitionProgress / SCALE_PROGRESS;
canvas.scale(sc, sc, pivotX, getHeight() / 2f);
}
float lt = 0, rt = 1;
if (LocaleController.isRTL) {
rt = Math.max(CLIP_PROGRESS, transitionProgress);
} else {
lt = (1f - Math.max(CLIP_PROGRESS, transitionProgress));
}
rect.set(getPaddingLeft() + (getWidth() - getPaddingRight()) * lt, getPaddingTop(), (getWidth() - getPaddingRight()) * rt, getHeight() - getPaddingBottom());
shadow.setBounds((int) (getPaddingLeft() + (getWidth() - getPaddingRight() + shadowPad.right) * lt - shadowPad.left), getPaddingTop() - shadowPad.top, (int) ((getWidth() - getPaddingRight() + shadowPad.right) * rt), getHeight() - getPaddingBottom() + shadowPad.bottom);
shadow.draw(canvas);
canvas.restoreToCount(s);
s = canvas.save();
if (transitionProgress <= SCALE_PROGRESS) {
float sc = transitionProgress / SCALE_PROGRESS;
canvas.scale(sc, sc, pivotX, getHeight() / 2f);
}
canvas.drawRoundRect(rect, radius, radius, bgPaint);
canvas.restoreToCount(s);
mPath.rewind();
mPath.addRoundRect(rect, radius, radius, Path.Direction.CW);
s = canvas.save();
if (transitionProgress <= SCALE_PROGRESS) {
float sc = transitionProgress / SCALE_PROGRESS;
canvas.scale(sc, sc, pivotX, getHeight() / 2f);
}
canvas.clipPath(mPath);
canvas.translate((LocaleController.isRTL ? -1 : 1) * getWidth() * (1f - transitionProgress), 0);
super.dispatchDraw(canvas);
canvas.restoreToCount(s);
s = canvas.save();
if (LocaleController.isRTL) rt = Math.max(CLIP_PROGRESS, Math.min(1, transitionProgress));
else lt = 1f - Math.max(CLIP_PROGRESS, Math.min(1f, transitionProgress));
rect.set(getPaddingLeft() + (getWidth() - getPaddingRight()) * lt, getPaddingTop(), (getWidth() - getPaddingRight()) * rt, getHeight() - getPaddingBottom());
mPath.rewind();
mPath.addRoundRect(rect, radius, radius, Path.Direction.CW);
canvas.clipPath(mPath);
if (leftShadowPaint != null) {
leftShadowPaint.setAlpha((int) (leftAlpha * transitionProgress * 0xFF));
canvas.drawRect(rect, leftShadowPaint);
}
if (rightShadowPaint != null) {
rightShadowPaint.setAlpha((int) (rightAlpha * transitionProgress * 0xFF));
canvas.drawRect(rect, rightShadowPaint);
}
canvas.restoreToCount(s);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
invalidateShaders();
}
/**
* Invalidates shaders
*/
private void invalidateShaders() {
int dp = AndroidUtilities.dp(24);
float cy = getHeight() / 2f;
int clr = Theme.getColor(Theme.key_actionBarDefaultSubmenuBackground);
leftShadowPaint.setShader(new LinearGradient(0, cy, dp, cy, clr, Color.TRANSPARENT, Shader.TileMode.CLAMP));
rightShadowPaint.setShader(new LinearGradient(getWidth(), cy, getWidth() - dp, cy, clr, Color.TRANSPARENT, Shader.TileMode.CLAMP));
invalidate();
}
public void setTransitionProgress(float transitionProgress) {
this.transitionProgress = transitionProgress;
invalidate();
}
private final class LeftRightShadowsListener extends RecyclerView.OnScrollListener {
private boolean leftVisible, rightVisible;
private ValueAnimator leftAnimator, rightAnimator;
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
boolean l = linearLayoutManager.findFirstVisibleItemPosition() != 0;
if (l != leftVisible) {
if (leftAnimator != null)
leftAnimator.cancel();
leftAnimator = startAnimator(leftAlpha, l ? 1 : 0, aFloat -> {
leftShadowPaint.setAlpha((int) ((leftAlpha = aFloat) * 0xFF));
invalidate();
}, () -> leftAnimator = null);
leftVisible = l;
}
boolean r = linearLayoutManager.findLastVisibleItemPosition() != listAdapter.getItemCount() - 1;
if (r != rightVisible) {
if (rightAnimator != null)
rightAnimator.cancel();
rightAnimator = startAnimator(rightAlpha, r ? 1 : 0, aFloat -> {
rightShadowPaint.setAlpha((int) ((rightAlpha = aFloat) * 0xFF));
invalidate();
}, () -> rightAnimator = null);
rightVisible = r;
}
}
private ValueAnimator startAnimator(float fromAlpha, float toAlpha, Consumer<Float> callback, Runnable onEnd) {
ValueAnimator a = ValueAnimator.ofFloat(fromAlpha, toAlpha).setDuration((long) (Math.abs(toAlpha - fromAlpha) * ALPHA_DURATION));
a.addUpdateListener(animation -> callback.accept((Float) animation.getAnimatedValue()));
a.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
onEnd.run();
}
});
a.start();
return a;
}
}
public final class ReactionHolderView extends FrameLayout {
public BackupImageView backupImageView;
public TLRPC.TL_availableReaction currentReaction;
Runnable playRunnable = new Runnable() {
@Override
public void run() {
if (backupImageView.getImageReceiver().getLottieAnimation() != null && !backupImageView.getImageReceiver().getLottieAnimation().isRunning()) {
backupImageView.getImageReceiver().getLottieAnimation().start();
}
}
};
ReactionHolderView(Context context) {
super(context);
backupImageView = new BackupImageView(context);
backupImageView.getImageReceiver().setAutoRepeat(0);
addView(backupImageView, LayoutHelper.createFrame(34, 34, Gravity.CENTER));
}
private void setReaction(TLRPC.TL_availableReaction react) {
currentReaction = react;
SvgHelper.SvgDrawable svgThumb = DocumentObject.getSvgThumb(currentReaction.appear_animation, Theme.key_windowBackgroundGray, 1.0f);
backupImageView.getImageReceiver().setImage(ImageLocation.getForDocument(currentReaction.appear_animation), "80_80_nolimit", null, null, svgThumb, 0, "tgs", react, 0);
}
public void play(int delay) {
AndroidUtilities.cancelRunOnUIThread(playRunnable);
if (backupImageView.getImageReceiver().getLottieAnimation() != null) {
backupImageView.getImageReceiver().getLottieAnimation().setCurrentFrame(0, false);
if (delay == 0) {
playRunnable.run();
} else {
backupImageView.getImageReceiver().getLottieAnimation().stop();
backupImageView.getImageReceiver().getLottieAnimation().setCurrentFrame(0, false);
AndroidUtilities.runOnUIThread(playRunnable, delay);
}
}
}
}
public interface ReactionsContainerDelegate {
void onReactionClicked(View v, TLRPC.TL_availableReaction reaction);
}
}

View File

@ -25,6 +25,7 @@ import android.graphics.drawable.TransitionDrawable;
import android.os.Build;
import android.os.SystemClock;
import android.text.Layout;
import android.text.SpannableStringBuilder;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.SparseIntArray;
@ -39,11 +40,17 @@ import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewPropertyAnimator;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.R;
import org.telegram.ui.ActionBar.Theme;
@ -108,7 +115,7 @@ public class RecyclerListView extends RecyclerView {
public boolean scrollingByUser;
private GestureDetector gestureDetector;
private GestureDetectorFixDoubleTap gestureDetector;
private View currentChildView;
private int currentChildPosition;
private boolean interceptedByChild;
@ -150,6 +157,18 @@ public class RecyclerListView extends RecyclerView {
protected final Theme.ResourcesProvider resourcesProvider;
private boolean accessibilityEnabled = true;
private AccessibilityDelegate accessibilityDelegate = new AccessibilityDelegate() {
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
if (host.isEnabled()) {
info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
};
public FastScroll getFastScroll() {
return fastScroll;
}
@ -159,6 +178,14 @@ public class RecyclerListView extends RecyclerView {
}
public interface OnItemClickListenerExtended {
default boolean hasDoubleTap(View view, int position) {
return false;
}
default void onDoubleTap(View view, int position, float x, float y) {
}
void onItemClick(View view, int position, float x, float y);
}
@ -168,8 +195,12 @@ public class RecyclerListView extends RecyclerView {
public interface OnItemLongClickListenerExtended {
boolean onItemClick(View view, int position, float x, float y);
void onMove(float dx, float dy);
void onLongClickRelease();
default void onMove(float dx, float dy) {
}
default void onLongClickRelease() {
}
}
public interface OnInterceptTouchListener {
@ -186,10 +217,13 @@ public class RecyclerListView extends RecyclerView {
public abstract static class FastScrollAdapter extends SelectionAdapter {
public abstract String getLetter(int position);
public abstract void getPositionForScrollProgress(RecyclerListView listView, float progress, int[] position);
public void onStartFastScroll() {
}
public void onFinishFastScroll(RecyclerListView listView) {
}
@ -205,6 +239,7 @@ public class RecyclerListView extends RecyclerView {
public boolean fastScrollIsVisible(RecyclerListView listView) {
return true;
}
public void onFastScrollSingleTap() {
}
@ -338,11 +373,17 @@ public class RecyclerListView extends RecyclerView {
}
public abstract int getSectionCount();
public abstract int getCountForSection(int section);
public abstract boolean isEnabled(ViewHolder holder, int section, int row);
public abstract int getItemViewType(int section, int position);
public abstract Object getItem(int section, int position);
public abstract void onBindViewHolder(int section, int position, ViewHolder holder);
public abstract View getSectionHeaderView(int section, View view);
}
@ -367,6 +408,13 @@ public class RecyclerListView extends RecyclerView {
private boolean pressed;
private StaticLayout letterLayout;
private StaticLayout oldLetterLayout;
private StaticLayout outLetterLayout;
private StaticLayout inLetterLayout;
private StaticLayout stableLetterLayout;
private float replaceLayoutProgress = 1f;
private boolean fromTop;
private float lastLetterY;
private float fromWidth;
private TextPaint letterPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
private String currentLetter;
private Path path = new Path();
@ -387,6 +435,7 @@ public class RecyclerListView extends RecyclerView {
float touchSlop;
Drawable fastScrollShadowDrawable;
Drawable fastScrollBackgroundDrawable;
boolean isRtl;
Runnable hideFloatingDateRunnable = new Runnable() {
@Override
@ -406,7 +455,9 @@ public class RecyclerListView extends RecyclerView {
this.type = type;
if (type == LETTER_TYPE) {
letterPaint.setTextSize(AndroidUtilities.dp(45));
isRtl = LocaleController.isRTL;
} else {
isRtl = false;
letterPaint.setTextSize(AndroidUtilities.dp(13));
letterPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
paint2.setColor(Theme.getColor(Theme.key_windowBackgroundWhite));
@ -417,7 +468,7 @@ public class RecyclerListView extends RecyclerView {
radii[a] = AndroidUtilities.dp(44);
}
scrollX = LocaleController.isRTL ? AndroidUtilities.dp(10) : AndroidUtilities.dp((type == LETTER_TYPE ? 132 : 240) - 15);
scrollX = isRtl ? AndroidUtilities.dp(10) : AndroidUtilities.dp((type == LETTER_TYPE ? 132 : 240) - 15);
updateColors();
setFocusableInTouchMode(true);
ViewConfiguration vc = ViewConfiguration.get(context);
@ -455,11 +506,11 @@ public class RecyclerListView extends RecyclerView {
float x = event.getX();
startY = lastY = event.getY();
float currentY = (float) Math.ceil((getMeasuredHeight() - AndroidUtilities.dp(24 + 30)) * progress) + AndroidUtilities.dp(12);
if (LocaleController.isRTL && x > AndroidUtilities.dp(25) || !LocaleController.isRTL && x < AndroidUtilities.dp(107) || lastY < currentY || lastY > currentY + AndroidUtilities.dp(30)) {
if (isRtl && x > AndroidUtilities.dp(25) || !isRtl && x < AndroidUtilities.dp(107) || lastY < currentY || lastY > currentY + AndroidUtilities.dp(30)) {
return false;
}
if (type == DATE_TYPE && !floatingDateVisible) {
if (LocaleController.isRTL && x > AndroidUtilities.dp(25) || !LocaleController.isRTL && x < (getMeasuredWidth() - AndroidUtilities.dp(25)) || lastY < currentY || lastY > currentY + AndroidUtilities.dp(30)) {
if (isRtl && x > AndroidUtilities.dp(25) || !isRtl && x < (getMeasuredWidth() - AndroidUtilities.dp(25)) || lastY < currentY || lastY > currentY + AndroidUtilities.dp(30)) {
return false;
}
}
@ -544,17 +595,47 @@ public class RecyclerListView extends RecyclerView {
}
letterLayout = null;
} else if (!newLetter.equals(currentLetter)) {
currentLetter = newLetter;
if (type == LETTER_TYPE) {
letterLayout = new StaticLayout(newLetter, letterPaint, 1000, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
} else {
outLetterLayout = letterLayout;
int w = ((int) letterPaint.measureText(newLetter)) + 1;
letterLayout = new StaticLayout(newLetter, letterPaint, w, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
if (outLetterLayout != null) {
String[] newSplits = newLetter.split(" ");
String[] oldSplits = outLetterLayout.getText().toString().split(" ");
if (newSplits != null && oldSplits != null && newSplits.length == 2 && oldSplits.length == 2 && newSplits[1].equals(oldSplits[1])) {
String oldText = outLetterLayout.getText().toString();
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(oldText);
spannableStringBuilder.setSpan(new EmptyStubSpan(), oldSplits[0].length(), oldText.length(), 0);
int oldW = ((int) letterPaint.measureText(oldText)) + 1;
outLetterLayout = new StaticLayout(spannableStringBuilder, letterPaint, oldW, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
spannableStringBuilder = new SpannableStringBuilder(newLetter);
spannableStringBuilder.setSpan(new EmptyStubSpan(), newSplits[0].length(), newLetter.length(), 0);
inLetterLayout = new StaticLayout(spannableStringBuilder, letterPaint, w, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
spannableStringBuilder = new SpannableStringBuilder(newLetter);
spannableStringBuilder.setSpan(new EmptyStubSpan(), 0, newSplits[0].length(), 0);
stableLetterLayout = new StaticLayout(spannableStringBuilder, letterPaint, w, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
} else {
inLetterLayout = letterLayout;
stableLetterLayout = null;
}
fromWidth = outLetterLayout.getWidth();
replaceLayoutProgress = 0f;
fromTop = getProgress() > lastLetterY;
}
lastLetterY = getProgress();
}
oldLetterLayout = null;
if (letterLayout.getLineCount() > 0) {
float lWidth = letterLayout.getLineWidth(0);
float lleft = letterLayout.getLineLeft(0);
if (LocaleController.isRTL) {
if (isRtl) {
textX = AndroidUtilities.dp(10) + (AndroidUtilities.dp(88) - letterLayout.getLineWidth(0)) / 2 - letterLayout.getLineLeft(0);
} else {
textX = (AndroidUtilities.dp(88) - letterLayout.getLineWidth(0)) / 2 - letterLayout.getLineLeft(0);
@ -627,8 +708,8 @@ public class RecyclerListView extends RecyclerView {
raduisBottom = AndroidUtilities.dp(44);
raduisTop = AndroidUtilities.dp(4) + (1.0f - diff / AndroidUtilities.dp(29)) * AndroidUtilities.dp(40);
}
if (LocaleController.isRTL && (radii[0] != raduisTop || radii[6] != raduisBottom) || !LocaleController.isRTL && (radii[2] != raduisTop || radii[4] != raduisBottom)) {
if (LocaleController.isRTL) {
if (isRtl && (radii[0] != raduisTop || radii[6] != raduisBottom) || !isRtl && (radii[2] != raduisTop || radii[4] != raduisBottom)) {
if (isRtl) {
radii[0] = radii[1] = raduisTop;
radii[6] = radii[7] = raduisBottom;
} else {
@ -636,7 +717,7 @@ public class RecyclerListView extends RecyclerView {
radii[4] = radii[5] = raduisBottom;
}
path.reset();
rect.set(LocaleController.isRTL ? AndroidUtilities.dp(10) : 0, 0, AndroidUtilities.dp(LocaleController.isRTL ? 98 : 88), AndroidUtilities.dp(88));
rect.set(isRtl ? AndroidUtilities.dp(10) : 0, 0, AndroidUtilities.dp(isRtl ? 98 : 88), AndroidUtilities.dp(88));
path.addRoundRect(rect, radii, Path.Direction.CW);
path.close();
}
@ -659,19 +740,61 @@ public class RecyclerListView extends RecyclerView {
float cy = rect.centerY();
float x = rect.left - AndroidUtilities.dp(30) * bubbleProgress - AndroidUtilities.dp(8);
float r = letterLayout.getHeight() / 2f + AndroidUtilities.dp(6);
rect.set(x - letterLayout.getWidth() - AndroidUtilities.dp(36), cy - letterLayout.getHeight() / 2f - AndroidUtilities.dp(8), x - AndroidUtilities.dp(12), cy + letterLayout.getHeight() / 2f + AndroidUtilities.dp(8));
float width = replaceLayoutProgress * letterLayout.getWidth() + fromWidth * (1f - replaceLayoutProgress);
rect.set(x - width - AndroidUtilities.dp(36), cy - letterLayout.getHeight() / 2f - AndroidUtilities.dp(8), x - AndroidUtilities.dp(12), cy + letterLayout.getHeight() / 2f + AndroidUtilities.dp(8));
int oldAlpha1 = paint2.getAlpha();
int oldAlpha2 = letterPaint.getAlpha();
paint2.setAlpha((int) (oldAlpha1 * floatingDateProgress));
letterPaint.setAlpha((int) (oldAlpha2 * floatingDateProgress));
fastScrollBackgroundDrawable.setBounds((int) rect.left, (int) rect.top, (int) rect.right, (int) rect.bottom);
fastScrollBackgroundDrawable.setAlpha((int) (255 * floatingDateProgress));
fastScrollBackgroundDrawable.draw(canvas);
canvas.save();
canvas.translate(x - letterLayout.getWidth() - AndroidUtilities.dp(24), cy - letterLayout.getHeight() / 2f);
letterLayout.draw(canvas);
canvas.restore();
if (replaceLayoutProgress != 1f) {
replaceLayoutProgress += 16f / 150f;
if (replaceLayoutProgress > 1f) {
replaceLayoutProgress = 1f;
} else {
invalidate();
}
}
if (replaceLayoutProgress != 1f) {
canvas.save();
rect.inset(AndroidUtilities.dp(4), AndroidUtilities.dp(2));
canvas.clipRect(rect);
if (outLetterLayout != null) {
letterPaint.setAlpha((int) (oldAlpha2 * floatingDateProgress * (1f - replaceLayoutProgress)));
canvas.save();
canvas.translate(x - outLetterLayout.getWidth() - AndroidUtilities.dp(24), cy - outLetterLayout.getHeight() / 2f + (fromTop ? -1 : 1) * AndroidUtilities.dp(15) * replaceLayoutProgress);
outLetterLayout.draw(canvas);
canvas.restore();
}
if (inLetterLayout != null) {
letterPaint.setAlpha((int) (oldAlpha2 * floatingDateProgress * replaceLayoutProgress));
canvas.save();
canvas.translate(x - inLetterLayout.getWidth() - AndroidUtilities.dp(24), cy - inLetterLayout.getHeight() / 2f + (fromTop ? 1 : -1) * AndroidUtilities.dp(15) * (1f - replaceLayoutProgress));
inLetterLayout.draw(canvas);
canvas.restore();
}
if (stableLetterLayout != null) {
letterPaint.setAlpha((int) (oldAlpha2 * floatingDateProgress));
canvas.save();
canvas.translate(x - stableLetterLayout.getWidth() - AndroidUtilities.dp(24), cy - stableLetterLayout.getHeight() / 2f);
stableLetterLayout.draw(canvas);
canvas.restore();
}
canvas.restore();
} else {
letterPaint.setAlpha((int) (oldAlpha2 * floatingDateProgress));
canvas.save();
canvas.translate(x - letterLayout.getWidth() - AndroidUtilities.dp(24), cy - letterLayout.getHeight() / 2f + AndroidUtilities.dp(15) * (1f - replaceLayoutProgress));
letterLayout.draw(canvas);
canvas.restore();
}
paint2.setAlpha(oldAlpha1);
letterPaint.setAlpha(oldAlpha2);
@ -787,14 +910,50 @@ public class RecyclerListView extends RecyclerView {
private class RecyclerListViewItemClickListener implements OnItemTouchListener {
public RecyclerListViewItemClickListener(Context context) {
gestureDetector = new GestureDetector(context, new GestureDetector.OnGestureListener() {
gestureDetector = new GestureDetectorFixDoubleTap(context, new GestureDetector.SimpleOnGestureListener() {
private View doubleTapView;
@Override
public boolean onSingleTapUp(MotionEvent e) {
if (currentChildView != null && (onItemClickListener != null || onItemClickListenerExtended != null)) {
if (currentChildView != null) {
if (onItemClickListenerExtended != null && onItemClickListenerExtended.hasDoubleTap(currentChildView, currentChildPosition)) {
doubleTapView = currentChildView;
} else {
onPressItem(currentChildView, e);
return true;
}
}
return false;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (doubleTapView != null && onItemClickListenerExtended != null) {
if (onItemClickListenerExtended.hasDoubleTap(doubleTapView, currentChildPosition)) {
onPressItem(doubleTapView, e);
doubleTapView = null;
return true;
}
}
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
if (doubleTapView != null && onItemClickListenerExtended != null && onItemClickListenerExtended.hasDoubleTap(doubleTapView, currentChildPosition)) {
onItemClickListenerExtended.onDoubleTap(doubleTapView, currentChildPosition, e.getX(), e.getY());
doubleTapView = null;
return true;
}
return false;
}
private void onPressItem(View cv, MotionEvent e) {
if (cv != null && (onItemClickListener != null || onItemClickListenerExtended != null)) {
final float x = e.getX();
final float y = e.getY();
onChildPressed(currentChildView, x, y, true);
final View view = currentChildView;
onChildPressed(cv, x, y, true);
final View view = cv;
final int position = currentChildPosition;
if (instantClick && position != -1) {
view.playSoundEffect(SoundEffectConstants.CLICK);
@ -829,15 +988,13 @@ public class RecyclerListView extends RecyclerView {
}, ViewConfiguration.getPressedStateDuration());
if (selectChildRunnable != null) {
View pressedChild = currentChildView;
AndroidUtilities.cancelRunOnUIThread(selectChildRunnable);
selectChildRunnable = null;
currentChildView = null;
interceptedByChild = false;
removeSelection(pressedChild, e);
removeSelection(cv, e);
}
}
return true;
}
@Override
@ -868,21 +1025,6 @@ public class RecyclerListView extends RecyclerView {
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
});
gestureDetector.setIsLongpressEnabled(false);
}
@ -1231,7 +1373,7 @@ public class RecyclerListView extends RecyclerView {
if (fastScroll != null) {
selfOnLayout = true;
t += getPaddingTop();
if (LocaleController.isRTL) {
if (fastScroll.isRtl) {
fastScroll.layout(0, t, fastScroll.getMeasuredWidth(), t + fastScroll.getMeasuredHeight());
} else {
int x = getMeasuredWidth() - fastScroll.getMeasuredWidth();
@ -1880,9 +2022,13 @@ public class RecyclerListView extends RecyclerView {
ViewHolder holder = findContainingViewHolder(child);
if (holder != null) {
child.setEnabled(((SelectionAdapter) getAdapter()).isEnabled(holder));
if (accessibilityEnabled) {
child.setAccessibilityDelegate(accessibilityDelegate);
}
}
} else {
child.setEnabled(false);
child.setAccessibilityDelegate(null);
}
super.onChildAttachedToWindow(child);
}
@ -2174,11 +2320,12 @@ public class RecyclerListView extends RecyclerView {
y = event.getY();
onFocus = true;
parent.requestDisallowInterceptTouchEvent(true);
} if (event.getAction() == MotionEvent.ACTION_MOVE) {
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
float dx = (x - event.getX());
float dy = (y - event.getY());
float touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
if (onFocus && Math.sqrt(dx * dx + dy * dy) >touchSlop) {
if (onFocus && Math.sqrt(dx * dx + dy * dy) > touchSlop) {
onFocus = false;
parent.requestDisallowInterceptTouchEvent(false);
}
@ -2218,7 +2365,7 @@ public class RecyclerListView extends RecyclerView {
if (fastScroll != null && fastScroll.pressed) {
return false;
}
if (multiSelectionGesture && e.getAction() != MotionEvent.ACTION_DOWN &&e.getAction() != MotionEvent.ACTION_UP && e.getAction() != MotionEvent.ACTION_CANCEL) {
if (multiSelectionGesture && e.getAction() != MotionEvent.ACTION_DOWN && e.getAction() != MotionEvent.ACTION_UP && e.getAction() != MotionEvent.ACTION_CANCEL) {
if (lastX == Float.MAX_VALUE && lastY == Float.MAX_VALUE) {
lastX = e.getX();
lastY = e.getY();
@ -2388,4 +2535,8 @@ public class RecyclerListView extends RecyclerView {
public void setItemsEnterAnimator(RecyclerItemsEnterAnimator itemsEnterAnimator) {
this.itemsEnterAnimator = itemsEnterAnimator;
}
}
public void setAccessibilityEnabled(boolean accessibilityEnabled) {
this.accessibilityEnabled = accessibilityEnabled;
}
}

View File

@ -54,7 +54,6 @@ public class SeekBarWaveform {
paintOuter.setStrokeWidth(AndroidUtilities.dpf2(2));
paintInner.setStrokeCap(Paint.Cap.ROUND);
paintOuter.setStrokeCap(Paint.Cap.ROUND);
}
}

View File

@ -0,0 +1,17 @@
package org.telegram.ui.Components;
import org.telegram.ui.ActionBar.ThemeDescription;
import java.util.ArrayList;
public class SimpleThemeDescription {
private SimpleThemeDescription() {}
public static ArrayList<ThemeDescription> createThemeDescriptions(ThemeDescription.ThemeDescriptionDelegate del, String... keys) {
ArrayList<ThemeDescription> l = new ArrayList<>(keys.length);
for (String k : keys) {
l.add(new ThemeDescription(null, 0, null, null, null, del, k));
}
return l;
}
}

View File

@ -18,15 +18,10 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.view.View;
import android.view.WindowInsets;
import android.widget.FrameLayout;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.google.android.exoplayer2.util.Log;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.SharedConfig;
import org.telegram.ui.ActionBar.ActionBar;
import org.telegram.ui.ActionBar.ActionBarLayout;
import org.telegram.ui.ActionBar.AdjustPanLayoutHelper;
@ -54,6 +49,7 @@ public class SizeNotifierFrameLayout extends FrameLayout {
private float emojiOffset;
private boolean animationInProgress;
private boolean skipBackgroundDrawing;
SnowflakesEffect snowflakesEffect;
public interface SizeNotifierFrameLayoutDelegate {
void onSizeChanged(int keyboardHeight, boolean isWidthGreater);
@ -263,6 +259,7 @@ public class SizeNotifierFrameLayout extends FrameLayout {
canvas.clipRect(0, actionBarHeight, width, getMeasuredHeight() - bottomClip);
drawable.setBounds(x, y, x + width, y + height);
drawable.draw(canvas);
checkSnowflake(canvas);
canvas.restore();
} else {
if (bottomClip != 0) {
@ -289,6 +286,7 @@ public class SizeNotifierFrameLayout extends FrameLayout {
}
drawable.setBounds(0, 0, getMeasuredWidth(), getRootView().getMeasuredHeight());
drawable.draw(canvas);
checkSnowflake(canvas);
if (bottomClip != 0) {
canvas.restore();
}
@ -299,6 +297,7 @@ public class SizeNotifierFrameLayout extends FrameLayout {
}
drawable.setBounds(0, backgroundTranslationY, getMeasuredWidth(), backgroundTranslationY + getRootView().getMeasuredHeight());
drawable.draw(canvas);
checkSnowflake(canvas);
if (bottomClip != 0) {
canvas.restore();
}
@ -310,6 +309,7 @@ public class SizeNotifierFrameLayout extends FrameLayout {
canvas.scale(scale, scale);
drawable.setBounds(0, 0, (int) Math.ceil(getMeasuredWidth() / scale), (int) Math.ceil(getRootView().getMeasuredHeight() / scale));
drawable.draw(canvas);
checkSnowflake(canvas);
canvas.restore();
} else {
int actionBarHeight = (isActionBarVisible() ? ActionBar.getCurrentActionBarHeight() : 0) + (Build.VERSION.SDK_INT >= 21 && occupyStatusBar ? AndroidUtilities.statusBarHeight : 0);
@ -325,6 +325,7 @@ public class SizeNotifierFrameLayout extends FrameLayout {
canvas.clipRect(0, actionBarHeight, width, getMeasuredHeight() - bottomClip);
drawable.setBounds(x, y, x + width, y + height);
drawable.draw(canvas);
checkSnowflake(canvas);
canvas.restore();
}
}
@ -335,6 +336,15 @@ public class SizeNotifierFrameLayout extends FrameLayout {
}
}
private void checkSnowflake(Canvas canvas) {
if (SharedConfig.drawSnowInChat || Theme.canStartHolidayAnimation()) {
if (snowflakesEffect == null) {
snowflakesEffect = new SnowflakesEffect(1);
}
snowflakesEffect.onDraw(this, canvas);
}
}
protected boolean isActionBarVisible() {
return true;
}

View File

@ -8,6 +8,7 @@
package org.telegram.ui.Components;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Build;
@ -23,7 +24,11 @@ public class SnowflakesEffect {
private Paint particlePaint;
private Paint particleThinPaint;
private Paint bitmapPaint = new Paint();
private String colorKey = Theme.key_actionBarDefaultTitle;
private int viewType;
Bitmap particleBitmap;
private long lastAnimationTime;
@ -50,31 +55,41 @@ public class SnowflakesEffect {
}
case 1:
default: {
particleThinPaint.setAlpha((int) (255 * alpha));
float angle = (float) -Math.PI / 2;
float px = AndroidUtilities.dpf2(2.0f) * 2 * scale;
float px1 = -AndroidUtilities.dpf2(0.57f) * 2 * scale;
float py1 = AndroidUtilities.dpf2(1.55f) * 2 * scale;
for (int a = 0; a < 6; a++) {
float x1 = (float) Math.cos(angle) * px;
float y1 = (float) Math.sin(angle) * px;
float cx = x1 * 0.66f;
float cy = y1 * 0.66f;
canvas.drawLine(x, y, x + x1, y + y1, particleThinPaint);
if (particleBitmap == null) {
particleThinPaint.setAlpha(255);
particleBitmap = Bitmap.createBitmap(AndroidUtilities.dp(16), AndroidUtilities.dp(16), Bitmap.Config.ARGB_8888);
Canvas bitmapCanvas = new Canvas(particleBitmap);
float px = AndroidUtilities.dpf2(2.0f) * 2;
float px1 = -AndroidUtilities.dpf2(0.57f) * 2;
float py1 = AndroidUtilities.dpf2(1.55f) * 2;
for (int a = 0; a < 6; a++) {
float x = AndroidUtilities.dp(8);
float y = AndroidUtilities.dp(8);
float x1 = (float) Math.cos(angle) * px;
float y1 = (float) Math.sin(angle) * px;
float cx = x1 * 0.66f;
float cy = y1 * 0.66f;
bitmapCanvas.drawLine(x, y, x + x1, y + y1, particleThinPaint);
float angle2 = (float) (angle - Math.PI / 2);
x1 = (float) (Math.cos(angle2) * px1 - Math.sin(angle2) * py1);
y1 = (float) (Math.sin(angle2) * px1 + Math.cos(angle2) * py1);
canvas.drawLine(x + cx, y + cy, x + x1, y + y1, particleThinPaint);
float angle2 = (float) (angle - Math.PI / 2);
x1 = (float) (Math.cos(angle2) * px1 - Math.sin(angle2) * py1);
y1 = (float) (Math.sin(angle2) * px1 + Math.cos(angle2) * py1);
bitmapCanvas.drawLine(x + cx, y + cy, x + x1, y + y1, particleThinPaint);
x1 = (float) (-Math.cos(angle2) * px1 - Math.sin(angle2) * py1);
y1 = (float) (-Math.sin(angle2) * px1 + Math.cos(angle2) * py1);
canvas.drawLine(x + cx, y + cy, x + x1, y + y1, particleThinPaint);
x1 = (float) (-Math.cos(angle2) * px1 - Math.sin(angle2) * py1);
y1 = (float) (-Math.sin(angle2) * px1 + Math.cos(angle2) * py1);
bitmapCanvas.drawLine(x + cx, y + cy, x + x1, y + y1, particleThinPaint);
angle += angleDiff;
angle += angleDiff;
}
}
bitmapPaint.setAlpha((int) (255 * alpha));
canvas.save();
canvas.scale(scale, scale, x, y);
canvas.drawBitmap(particleBitmap, x, y, bitmapPaint);
canvas.restore();
break;
}
}
@ -87,7 +102,8 @@ public class SnowflakesEffect {
private int color;
public SnowflakesEffect() {
public SnowflakesEffect(int viewType) {
this.viewType = viewType;
particlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
particlePaint.setStrokeWidth(AndroidUtilities.dp(1.5f));
particlePaint.setStrokeCap(Paint.Cap.ROUND);
@ -132,10 +148,18 @@ public class SnowflakesEffect {
count--;
continue;
}
if (particle.currentTime < 200.0f) {
particle.alpha = AndroidUtilities.accelerateInterpolator.getInterpolation(particle.currentTime / 200.0f);
if (viewType == 0) {
if (particle.currentTime < 200.0f) {
particle.alpha = AndroidUtilities.accelerateInterpolator.getInterpolation(particle.currentTime / 200.0f);
} else {
particle.alpha = 1.0f - AndroidUtilities.decelerateInterpolator.getInterpolation((particle.currentTime - 200.0f) / (particle.lifeTime - 200.0f));
}
} else {
particle.alpha = 1.0f - AndroidUtilities.decelerateInterpolator.getInterpolation((particle.currentTime - 200.0f) / (particle.lifeTime - 200.0f));
if (particle.currentTime < 200.0f) {
particle.alpha = AndroidUtilities.accelerateInterpolator.getInterpolation(particle.currentTime / 200.0f);
} else if (particle.lifeTime - particle.currentTime < 2000) {
particle.alpha = AndroidUtilities.decelerateInterpolator.getInterpolation((particle.lifeTime - particle.currentTime) / 2000);
}
}
particle.x += particle.vx * particle.velocity * dt / 500.0f;
particle.y += particle.vy * particle.velocity * dt / 500.0f;
@ -153,38 +177,47 @@ public class SnowflakesEffect {
Particle particle = particles.get(a);
particle.draw(canvas);
}
int maxCount = viewType == 0 ? 100 : 300;
int createPerFrame = viewType == 0 ? 1 : 10;
if (particles.size() < maxCount) {
for (int i = 0; i < createPerFrame; i++) {
if (particles.size() < maxCount && Utilities.random.nextFloat() > 0.7f) {
int statusBarHeight = (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0);
float cx = Utilities.random.nextFloat() * parent.getMeasuredWidth();
float cy = statusBarHeight + Utilities.random.nextFloat() * (parent.getMeasuredHeight() - AndroidUtilities.dp(20) - statusBarHeight);
if (Utilities.random.nextFloat() > 0.7f && particles.size() < 100) {
int statusBarHeight = (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0);
float cx = Utilities.random.nextFloat() * parent.getMeasuredWidth();
float cy = statusBarHeight + Utilities.random.nextFloat() * (parent.getMeasuredHeight() - AndroidUtilities.dp(20) - statusBarHeight);
int angle = Utilities.random.nextInt(40) - 20 + 90;
float vx = (float) Math.cos(Math.PI / 180.0 * angle);
float vy = (float) Math.sin(Math.PI / 180.0 * angle);
int angle = Utilities.random.nextInt(40) - 20 + 90;
float vx = (float) Math.cos(Math.PI / 180.0 * angle);
float vy = (float) Math.sin(Math.PI / 180.0 * angle);
Particle newParticle;
if (!freeParticles.isEmpty()) {
newParticle = freeParticles.get(0);
freeParticles.remove(0);
} else {
newParticle = new Particle();
}
newParticle.x = cx;
newParticle.y = cy;
Particle newParticle;
if (!freeParticles.isEmpty()) {
newParticle = freeParticles.get(0);
freeParticles.remove(0);
} else {
newParticle = new Particle();
newParticle.vx = vx;
newParticle.vy = vy;
newParticle.alpha = 0.0f;
newParticle.currentTime = 0;
newParticle.scale = Utilities.random.nextFloat() * 1.2f;
newParticle.type = Utilities.random.nextInt(2);
if (viewType == 0) {
newParticle.lifeTime = 2000 + Utilities.random.nextInt(100);
} else {
newParticle.lifeTime = 3000 + Utilities.random.nextInt(2000);
}
newParticle.velocity = 20.0f + Utilities.random.nextFloat() * 4.0f;
particles.add(newParticle);
}
}
newParticle.x = cx;
newParticle.y = cy;
newParticle.vx = vx;
newParticle.vy = vy;
newParticle.alpha = 0.0f;
newParticle.currentTime = 0;
newParticle.scale = Utilities.random.nextFloat() * 1.2f;
newParticle.type = Utilities.random.nextInt(2);
newParticle.lifeTime = 2000 + Utilities.random.nextInt(100);
newParticle.velocity = 20.0f + Utilities.random.nextFloat() * 4.0f;
particles.add(newParticle);
}
long newTime = System.currentTimeMillis();

View File

@ -172,7 +172,6 @@ public class StaticLayoutEx {
.setAlignment(align)
.setLineSpacing(spacingAdd, spacingMult)
.setIncludePad(includePad)
.setEllipsize(TextUtils.TruncateAt.END)
.setEllipsizedWidth(ellipsisWidth)
.setMaxLines(maxLines)
.setBreakStrategy(canContainUrl ? StaticLayout.BREAK_STRATEGY_HIGH_QUALITY : StaticLayout.BREAK_STRATEGY_SIMPLE)

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