From d2997260ea37b1df888d3a6a05371ae99442502c Mon Sep 17 00:00:00 2001 From: xtaodada Date: Thu, 15 Feb 2024 15:50:56 +0800 Subject: [PATCH 1/4] fix: NPE in ChatActivity ActionBarMenuItem --- .../java/org/telegram/ui/ChatActivity.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java index 679ab48779..9fe6c1bba6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java @@ -16958,10 +16958,16 @@ private void addToSelectedMessages(MessageObject messageObject, boolean outside, ActionBarMenuItem selectItem = actionBar.createActionMode().getItem(nkactionbarbtn_selectBetween); ActionBarMenuItem combineMessageItem = actionBar.createActionMode().getItem(combine_message); - ActionBarMenuSubItem saveToDownloadsItem = actionModeOtherItem.getSubItem(save_to); - ActionBarMenuSubItem saveMessageItem = actionModeOtherItem.getSubItem(nkbtn_savemessage); - ActionBarMenuSubItem forwardNoQuoteItem = actionModeOtherItem.getSubItem(nkbtn_forward_noquote); - ActionBarMenuSubItem starItem = actionModeOtherItem.getSubItem(star); + ActionBarMenuSubItem saveToDownloadsItem = null; + ActionBarMenuSubItem saveMessageItem = null; + ActionBarMenuSubItem forwardNoQuoteItem = null; + ActionBarMenuSubItem starItem = null; + if (actionModeOtherItem != null) { + saveToDownloadsItem = actionModeOtherItem.getSubItem(save_to); + saveMessageItem = actionModeOtherItem.getSubItem(nkbtn_savemessage); + forwardNoQuoteItem = actionModeOtherItem.getSubItem(nkbtn_forward_noquote); + starItem = actionModeOtherItem.getSubItem(star); + } boolean noforwards = getMessagesController().isChatNoForwards(currentChat) || hasSelectedNoforwardsMessage(); boolean canForward = chatMode != MODE_SCHEDULED && cantForwardMessagesCount == 0 && !noforwards; @@ -17249,7 +17255,9 @@ public void onAnimationCancel(Animator animation) { if (translateItem != null) translateItem.setVisibility(selectedMessagesCanCopyIds[0].size() + selectedMessagesCanCopyIds[1].size() > 0); - actionModeOtherItem.setSubItemVisibility(nkbtn_sharemessage, selectedMessagesCanCopyIds[0].size() + selectedMessagesCanCopyIds[1].size() > 0); + if (actionModeOtherItem != null) { + actionModeOtherItem.setSubItemVisibility(nkbtn_sharemessage, selectedMessagesCanCopyIds[0].size() + selectedMessagesCanCopyIds[1].size() > 0); + } boolean allowPin = false; if (currentChat != null) { @@ -17267,7 +17275,9 @@ public void onAnimationCancel(Animator animation) { } } } - actionModeOtherItem.setSubItemVisibility(nkbtn_unpin, allowPin); + if (actionModeOtherItem != null) { + actionModeOtherItem.setSubItemVisibility(nkbtn_unpin, allowPin); + } } } updateSelectedMessageReactions(); From fd247b02cf536f213bcb9665f716a41a32e8c3d6 Mon Sep 17 00:00:00 2001 From: xtaodada Date: Thu, 15 Feb 2024 17:00:16 +0800 Subject: [PATCH 2/4] feat: search config --- .../java/org/telegram/ui/ProfileActivity.java | 25 ++++++++++-- .../nekogram/helpers/SettingsHelper.java | 40 +++++++++++++++++++ .../helpers/SettingsSearchResult.java | 20 ++++++++++ .../settings/BaseNekoXSettingsActivity.java | 20 +++++++++- .../settings/NekoChatSettingsActivity.java | 17 +++++++- .../NekoExperimentalSettingsActivity.java | 22 +++++++++- .../settings/NekoGeneralSettingsActivity.java | 22 +++++++++- 7 files changed, 156 insertions(+), 10 deletions(-) create mode 100644 TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/SettingsSearchResult.java diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java index 39eccdf1e5..dc388e0a6e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java @@ -285,6 +285,8 @@ import kotlin.Unit; import libv2ray.Libv2ray; import tw.nekomimi.nekogram.BackButtonMenuRecent; +import tw.nekomimi.nekogram.helpers.SettingsHelper; +import tw.nekomimi.nekogram.helpers.SettingsSearchResult; import tw.nekomimi.nekogram.transtale.popupwrapper.AutoTranslatePopupWrapper; import tw.nekomimi.nekogram.ui.BottomBuilder; import tw.nekomimi.nekogram.InternalUpdater; @@ -11401,7 +11403,7 @@ private void updateSearchArray() { } private SearchResult[] onCreateSearchArray() { - return new SearchResult[]{ + SearchResult[] arr = new SearchResult[]{ new SearchResult(500, LocaleController.getString("EditName", R.string.EditName), 0, () -> presentFragment(new ChangeNameActivity(resourcesProvider))), new SearchResult(501, LocaleController.getString("ChangePhoneNumber", R.string.ChangePhoneNumber), 0, () -> presentFragment(new ActionIntroActivity(ActionIntroActivity.ACTION_TYPE_CHANGE_PHONE_NUMBER))), new SearchResult(502, LocaleController.getString("AddAnotherAccount", R.string.AddAnotherAccount), 0, () -> { @@ -11664,6 +11666,22 @@ private SearchResult[] onCreateSearchArray() { new SearchResult(403, LocaleController.getString("TelegramFAQ", R.string.TelegramFAQ), LocaleController.getString("SettingsHelp", R.string.SettingsHelp), R.drawable.msg2_help, () -> Browser.openUrl(getParentActivity(), LocaleController.getString("TelegramFaqUrl", R.string.TelegramFaqUrl))), new SearchResult(404, LocaleController.getString("PrivacyPolicy", R.string.PrivacyPolicy), LocaleController.getString("SettingsHelp", R.string.SettingsHelp), R.drawable.msg2_help, () -> Browser.openUrl(getParentActivity(), LocaleController.getString("PrivacyPolicyUrl", R.string.PrivacyPolicyUrl))), }; + ArrayList nagramSettings = SettingsHelper.onCreateSearchArray( + fragment -> AndroidUtilities.runOnUIThread(() -> presentFragment(fragment, false, false)) + ); + ArrayList list = new ArrayList<>(); + for (SettingsSearchResult oldResult: nagramSettings) { + SearchResult result = new SearchResult( + oldResult.guid, oldResult.searchTitle, null, oldResult.path1, oldResult.path2, oldResult.iconResId, oldResult.openRunnable + ); + list.add(result); + } + // combine + SearchResult[] result = Arrays.copyOf(arr, arr.length + list.size()); + for (int i = 0; i < list.size(); i++) { + result[arr.length + i] = list.get(i); + } + return result; } private boolean isPremiumFeatureAvailable(int feature) { @@ -11937,12 +11955,13 @@ public void search(String text) { for (int i = 0; i < searchArgs.length; i++) { if (searchArgs[i].length() != 0) { String searchString = searchArgs[i]; - int index = title.indexOf(" " + searchString); + int index = title.indexOf(searchString); if (index < 0 && translitArgs[i] != null) { searchString = translitArgs[i]; - index = title.indexOf(" " + searchString); + index = title.indexOf(searchString); } if (index >= 0) { + index -= 1; if (stringBuilder == null) { stringBuilder = new SpannableStringBuilder(result.searchTitle); } diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/SettingsHelper.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/SettingsHelper.java index e2d8f3a78d..93bd9cba3a 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/SettingsHelper.java +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/SettingsHelper.java @@ -4,8 +4,13 @@ import android.text.TextUtils; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.R; import org.telegram.ui.ActionBar.BaseFragment; +import java.util.ArrayList; +import java.util.Map; + import tw.nekomimi.nekogram.settings.BaseNekoSettingsActivity; import tw.nekomimi.nekogram.settings.BaseNekoXSettingsActivity; import tw.nekomimi.nekogram.settings.NekoChatSettingsActivity; @@ -91,4 +96,39 @@ public static void processDeepLink(Uri uri, Callback callback, Runnable unknown) public interface Callback { void presentFragment(BaseFragment fragment); } + + public static ArrayList onCreateSearchArray(Callback callback) { + ArrayList items = new ArrayList<>(); + ArrayList fragments = new ArrayList<>(); + fragments.add(new NekoGeneralSettingsActivity()); + fragments.add(new NekoChatSettingsActivity()); + fragments.add(new NekoExperimentalSettingsActivity()); + String n_title = LocaleController.getString("NekoSettings", R.string.NekoSettings); + for (BaseNekoXSettingsActivity fragment: fragments) { + int uid = fragment.getBaseGuid(); + int drawable = fragment.getDrawable(); + String f_title = fragment.getTitle(); + for (Map.Entry entry : fragment.getRowMapReverse().entrySet()) { + Integer i = entry.getKey(); + String key = entry.getValue(); + if (key.equals(String.valueOf(i))) { + continue; + } + int guid = uid + i; + String title = LocaleController.getString(key); + if (title == null || title.isEmpty()) { + continue; + } + Runnable open = () -> { + callback.presentFragment(fragment); + AndroidUtilities.runOnUIThread(() -> fragment.scrollToRow(key, null)); + }; + SettingsSearchResult result = new SettingsSearchResult( + guid, title, n_title, f_title, drawable, open + ); + items.add(result); + } + } + return items; + } } diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/SettingsSearchResult.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/SettingsSearchResult.java new file mode 100644 index 0000000000..60fdc903b8 --- /dev/null +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/SettingsSearchResult.java @@ -0,0 +1,20 @@ +package tw.nekomimi.nekogram.helpers; + +public class SettingsSearchResult { + + public String searchTitle; + public Runnable openRunnable; + public String path1; + public String path2; + public int iconResId; + public int guid; + + public SettingsSearchResult(int guid, String searchTitle,String path1, String path2, int iconResId, Runnable open) { + this.guid = guid; + this.searchTitle = searchTitle; + this.path1 = path1; + this.path2 = path2; + this.iconResId = iconResId; + this.openRunnable = open; + } +} diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/BaseNekoXSettingsActivity.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/BaseNekoXSettingsActivity.java index cc117130c7..84822f6ba4 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/BaseNekoXSettingsActivity.java +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/BaseNekoXSettingsActivity.java @@ -30,6 +30,18 @@ public class BaseNekoXSettingsActivity extends BaseFragment { protected void updateRows() { } + public int getBaseGuid() { + return 10000; + } + + public int getDrawable() { + return 0; + } + + public String getTitle() { + return ""; + } + protected void addRowsToMap(CellGroup cellGroup) { rowMap.clear(); rowMapReverse.clear(); @@ -68,6 +80,8 @@ protected ConfigItem getBindConfig(AbstractConfigCell row) { return ((ConfigCellTextDetail) row).getBindConfig(); } else if (row instanceof ConfigCellTextInput) { return ((ConfigCellTextInput) row).getBindConfig(); + } else if (row instanceof ConfigCellAutoTextCheck) { + return ((ConfigCellAutoTextCheck) row).getBindConfig(); } return null; } @@ -164,8 +178,12 @@ public void scrollToRow(String key, Runnable unknown) { layoutManager.scrollToPositionWithOffset(finalPosition, AndroidUtilities.dp(60)); return finalPosition; }); - } else { + } else if (unknown != null) { unknown.run(); } } + + public HashMap getRowMapReverse() { + return rowMapReverse; + } } diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoChatSettingsActivity.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoChatSettingsActivity.java index 1620fb82cf..cdd0e0f65e 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoChatSettingsActivity.java +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoChatSettingsActivity.java @@ -205,6 +205,10 @@ public class NekoChatSettingsActivity extends BaseNekoXSettingsActivity implemen private EmojiSetCell emojiSetCell; private UndoView tooltip; + public NekoChatSettingsActivity() { + addRowsToMap(cellGroup); + } + @Override public boolean onFragmentCreate() { EmojiHelper.getInstance().loadEmojisInfo(this); @@ -222,7 +226,7 @@ public boolean onFragmentCreate() { @Override public View createView(Context context) { actionBar.setBackButtonImage(R.drawable.ic_ab_back); - actionBar.setTitle(LocaleController.getString("Chat", R.string.Chat)); + actionBar.setTitle(getTitle()); if (AndroidUtilities.isTablet()) { actionBar.setOccupyStatusBar(false); @@ -339,7 +343,6 @@ public void onItemClick(int id) { } } }); - addRowsToMap(cellGroup); listView.setOnItemLongClickListener((view, position, x, y) -> { var holder = listView.findViewHolderForAdapterPosition(position); if (holder != null && listAdapter.isEnabled(holder)) { @@ -386,6 +389,16 @@ protected void updateRows() { } } + @Override + public int getDrawable() { + return R.drawable.menu_chats; + } + + @Override + public String getTitle() { + return LocaleController.getString("Chat", R.string.Chat); + } + @Override public ArrayList getThemeDescriptions() { ArrayList themeDescriptions = new ArrayList<>(); diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoExperimentalSettingsActivity.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoExperimentalSettingsActivity.java index c34065ef2c..ab77a33f66 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoExperimentalSettingsActivity.java +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoExperimentalSettingsActivity.java @@ -116,6 +116,10 @@ public class NekoExperimentalSettingsActivity extends BaseNekoXSettingsActivity private static final int INTENT_PICK_CUSTOM_EMOJI_PACK = 114; private static final int INTENT_PICK_EXTERNAL_STICKER_DIRECTORY = 514; + public NekoExperimentalSettingsActivity() { + addRowsToMap(cellGroup); + } + private void setExternalStickerCacheCellsEnabled(boolean enabled) { ((ConfigCellText) externalStickerCacheSyncAllRow).setEnabled(enabled); ((ConfigCellText) externalStickerCacheDeleteAllRow).setEnabled(enabled); @@ -154,7 +158,7 @@ public boolean onFragmentCreate() { @Override public View createView(Context context) { actionBar.setBackButtonImage(R.drawable.ic_ab_back); - actionBar.setTitle(LocaleController.getString("Experiment", R.string.Experiment)); + actionBar.setTitle(getTitle()); if (AndroidUtilities.isTablet()) { actionBar.setOccupyStatusBar(false); @@ -257,7 +261,6 @@ public void onItemClick(int id) { } } }); - addRowsToMap(cellGroup); listView.setOnItemLongClickListener((view, position, x, y) -> { var holder = listView.findViewHolderForAdapterPosition(position); if (holder != null && listAdapter.isEnabled(holder)) { @@ -368,6 +371,21 @@ protected void updateRows() { } } + @Override + public int getBaseGuid() { + return 11000; + } + + @Override + public int getDrawable() { + return R.drawable.msg_fave; + } + + @Override + public String getTitle() { + return LocaleController.getString("Experiment", R.string.Experiment); + } + @Override public ArrayList getThemeDescriptions() { ArrayList themeDescriptions = new ArrayList<>(); diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoGeneralSettingsActivity.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoGeneralSettingsActivity.java index 92b583cac0..f59f5fcc82 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoGeneralSettingsActivity.java +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoGeneralSettingsActivity.java @@ -244,6 +244,10 @@ public class NekoGeneralSettingsActivity extends BaseNekoXSettingsActivity { private ChatBlurAlphaSeekBar chatBlurAlphaSeekbar; private UndoView restartTooltip; + public NekoGeneralSettingsActivity() { + addRowsToMap(cellGroup); + } + @Override public boolean onFragmentCreate() { super.onFragmentCreate(); @@ -257,7 +261,7 @@ public boolean onFragmentCreate() { @Override public View createView(Context context) { actionBar.setBackButtonImage(R.drawable.ic_ab_back); - actionBar.setTitle(LocaleController.getString("General", R.string.General)); + actionBar.setTitle(getTitle()); if (AndroidUtilities.isTablet()) { actionBar.setOccupyStatusBar(false); @@ -382,7 +386,6 @@ public void onItemClick(int id) { } } }); - addRowsToMap(cellGroup); listView.setOnItemLongClickListener((view, position, x, y) -> { var holder = listView.findViewHolderForAdapterPosition(position); if (holder != null && listAdapter.isEnabled(holder)) { @@ -659,6 +662,21 @@ protected void updateRows() { } } + @Override + public int getBaseGuid() { + return 12000; + } + + @Override + public int getDrawable() { + return R.drawable.msg_theme; + } + + @Override + public String getTitle() { + return LocaleController.getString("General", R.string.General); + } + @Override public ArrayList getThemeDescriptions() { ArrayList themeDescriptions = new ArrayList<>(); From 08ae1692a8fc3c4cce9b76d14f2bb137f0528a10 Mon Sep 17 00:00:00 2001 From: xtaodada Date: Thu, 15 Feb 2024 17:01:04 +0800 Subject: [PATCH 3/4] release: bump version --- TMessagesProj/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TMessagesProj/build.gradle b/TMessagesProj/build.gradle index c4b447c490..862ffdc8e6 100644 --- a/TMessagesProj/build.gradle +++ b/TMessagesProj/build.gradle @@ -4,7 +4,7 @@ apply plugin: "com.android.application" apply plugin: "kotlin-android" def verName = "10.6.4" -def verCode = 1158 +def verCode = 1159 def officialVer = "10.6.4" From 8b14fd85e657d939086e8fca521d2d1332d01e70 Mon Sep 17 00:00:00 2001 From: xtaodada Date: Thu, 15 Feb 2024 17:05:32 +0800 Subject: [PATCH 4/4] fix: Update App Alert Dialog cannot show Emoji Co-authored-by: tehcneko --- .../ui/Components/BlockingUpdateView.java | 3 +- .../ui/Components/UpdateAppAlertDialog.java | 4 +- .../tw/nekomimi/nekogram/TextViewEffects.java | 219 ++++++++++++++++++ 3 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 TMessagesProj/src/main/java/tw/nekomimi/nekogram/TextViewEffects.java diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BlockingUpdateView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BlockingUpdateView.java index 3fe57fff92..ef128c272b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BlockingUpdateView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BlockingUpdateView.java @@ -38,6 +38,7 @@ import java.util.Locale; +import tw.nekomimi.nekogram.TextViewEffects; import tw.nekomimi.nekogram.helpers.remote.UpdateHelper; public class BlockingUpdateView extends FrameLayout implements NotificationCenter.NotificationCenterDelegate { @@ -97,7 +98,7 @@ public BlockingUpdateView(final Context context) { titleTextView.setText(LocaleController.getString("UpdateTelegram", R.string.UpdateTelegram).replace("Telegram", LocaleController.getString("NekoX", R.string.NekoX))); container.addView(titleTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL | Gravity.TOP)); - textView = new TextView(context); + textView = new TextViewEffects(context); textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); textView.setLinkTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/UpdateAppAlertDialog.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/UpdateAppAlertDialog.java index c947eb8281..0ecba4e0aa 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/UpdateAppAlertDialog.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/UpdateAppAlertDialog.java @@ -35,6 +35,8 @@ import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Stories.recorder.ButtonWithCounterView; +import tw.nekomimi.nekogram.TextViewEffects; + public class UpdateAppAlertDialog extends BottomSheet { private TLRPC.TL_help_appUpdate appUpdate; @@ -266,7 +268,7 @@ protected void onScrollChanged(int l, int t, int oldl, int oldt) { messageTextView.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP); linearLayout.addView(messageTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 23, 0, 23, 5)); - TextView changelogTextView = new TextView(getContext()); + TextView changelogTextView = new TextViewEffects(getContext()); changelogTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); changelogTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); changelogTextView.setLinkTextColor(Theme.getColor(Theme.key_dialogTextLink)); diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/TextViewEffects.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/TextViewEffects.java new file mode 100644 index 0000000000..e2482776f1 --- /dev/null +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/TextViewEffects.java @@ -0,0 +1,219 @@ +package tw.nekomimi.nekogram; + +import android.content.Context; +import android.graphics.Canvas; +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.text.Layout; +import android.text.Spanned; +import android.view.MotionEvent; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.AnimatedEmojiDrawable; +import org.telegram.ui.Components.AnimatedEmojiSpan; +import org.telegram.ui.Components.LinkSpanDrawable; +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 TextViewEffects extends LinkSpanDrawable.LinksTextView { + + private final SpoilersClickDetector clickDetector; + private final List spoilers = new ArrayList<>(); + private final Stack spoilersPool = new Stack<>(); + private boolean isSpoilersRevealed; + private final Path path = new Path(); + private Paint xRefPaint; + + private int emojiOnlyCount; + private AnimatedEmojiSpan.EmojiGroupedSpans animatedEmojiDrawables; + private Layout lastLayout = null; + private int lastTextLength; + + public TextViewEffects(Context context) { + this(context, null); + } + + public TextViewEffects(Context context, Theme.ResourcesProvider resourcesProvider) { + super(context, resourcesProvider); + + clickDetector = new SpoilersClickDetector(this, spoilers, (eff, x, y) -> { + if (isSpoilersRevealed) return; + + eff.setOnRippleEndCallback(() -> post(() -> { + isSpoilersRevealed = true; + invalidateSpoilers(); + })); + + float rad = (float) Math.sqrt(Math.pow(getWidth(), 2) + Math.pow(getHeight(), 2)); + for (SpoilerEffect ef : spoilers) + ef.startRipple(x, y, rad); + }); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + if (clickDetector.onTouchEvent(event)) + return true; + return super.dispatchTouchEvent(event); + } + + public void setText(CharSequence text, int emojiOnlyCount) { + this.emojiOnlyCount = emojiOnlyCount; + super.setText(text); + } + + @Override + public void setText(CharSequence text, BufferType type) { + isSpoilersRevealed = false; + super.setText(text, type); + } + + @Override + protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { + super.onTextChanged(text, start, lengthBefore, lengthAfter); + invalidateSpoilers(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + invalidateSpoilers(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + AnimatedEmojiSpan.release(this, animatedEmojiDrawables); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + updateAnimatedEmoji(false); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + updateAnimatedEmoji(false); + } + + @Override + protected void onDraw(Canvas canvas) { + int pl = getPaddingLeft(), pt = getPaddingTop(); + + canvas.save(); + path.rewind(); + for (SpoilerEffect eff : spoilers) { + Rect bounds = eff.getBounds(); + path.addRect(bounds.left + pl, bounds.top + pt, bounds.right + pl, bounds.bottom + pt, Path.Direction.CW); + } + canvas.clipPath(path, Region.Op.DIFFERENCE); + updateAnimatedEmoji(false); + super.onDraw(canvas); + if (animatedEmojiDrawables != null) { + AnimatedEmojiSpan.drawAnimatedEmojis(canvas, getLayout(), animatedEmojiDrawables, 0, spoilers, computeVerticalScrollOffset() - AndroidUtilities.dp(6), computeVerticalScrollOffset() + computeVerticalScrollExtent(), 0, 1f); + } + canvas.restore(); + + canvas.save(); + canvas.clipPath(path); + path.rewind(); + if (!spoilers.isEmpty()) { + spoilers.get(0).getRipplePath(path); + } + canvas.clipPath(path); + super.onDraw(canvas); + canvas.restore(); + + if (!spoilers.isEmpty()) { + boolean useAlphaLayer = spoilers.get(0).getRippleProgress() != -1; + if (useAlphaLayer) { + canvas.saveLayer(0, 0, getMeasuredWidth(), getMeasuredHeight(), null, Canvas.ALL_SAVE_FLAG); + } else { + canvas.save(); + } + canvas.translate(getPaddingLeft(), getPaddingTop() + AndroidUtilities.dp(2)); + for (SpoilerEffect eff : spoilers) { + eff.setColor(getPaint().getColor()); + eff.draw(canvas); + } + + if (useAlphaLayer) { + path.rewind(); + spoilers.get(0).getRipplePath(path); + if (xRefPaint == null) { + xRefPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + xRefPaint.setColor(0xff000000); + xRefPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + } + canvas.drawPath(path, xRefPaint); + } + canvas.restore(); + } + } + + public void updateAnimatedEmoji(boolean force) { + int newTextLength = (getLayout() == null || getLayout().getText() == null) ? 0 : getLayout().getText().length(); + if (force || lastLayout != getLayout() || lastTextLength != newTextLength) { + int cacheType = -1; + switch (emojiOnlyCount) { + case 0: + case 1: + case 2: + case 3: + case 4: + cacheType = AnimatedEmojiDrawable.CACHE_TYPE_MESSAGES_LARGE; + break; + case 5: + case 6: + cacheType = AnimatedEmojiDrawable.CACHE_TYPE_KEYBOARD; + break; + case 7: + case 8: + case 9: + default: + if (emojiOnlyCount > 9) { + cacheType = AnimatedEmojiDrawable.CACHE_TYPE_MESSAGES; + } + break; + } + animatedEmojiDrawables = AnimatedEmojiSpan.update(cacheType, this, animatedEmojiDrawables, getLayout()); + lastLayout = getLayout(); + lastTextLength = newTextLength; + } + } + + private void invalidateSpoilers() { + if (spoilers == null) return; + spoilersPool.addAll(spoilers); + spoilers.clear(); + + if (isSpoilersRevealed) { + invalidate(); + return; + } + + Layout layout = getLayout(); + if (layout != null && layout.getText() instanceof Spanned) { + if (animatedEmojiDrawables != null) { + animatedEmojiDrawables.recordPositions(false); + } + SpoilerEffect.addSpoilers(this, spoilersPool, spoilers); + if (animatedEmojiDrawables != null) { + animatedEmojiDrawables.recordPositions(true); + } + } + invalidate(); + } +}