diff --git a/TMessagesProj/jni/jni.c b/TMessagesProj/jni/jni.c index 1b0f168bd..88d776cf6 100644 --- a/TMessagesProj/jni/jni.c +++ b/TMessagesProj/jni/jni.c @@ -186,6 +186,14 @@ JNIEXPORT void Java_org_telegram_messenger_Utilities_aesCbcEncryption(JNIEnv *en (*env)->ReleaseByteArrayElements(env, iv, ivBuff, JNI_ABORT); } +#define LISTDIR_DOCTYPE_ALL 0 +#define LISTDIR_DOCTYPE_OTHER_THAN_MUSIC 1 +#define LISTDIR_DOCTYPE_MUSIC 2 + +#define LISTDIR_DOCTYPE2_EMOJI 3 +#define LISTDIR_DOCTYPE2_TEMP 4 +#define LISTDIR_DOCTYPE2_OTHER 5 + int64_t listdir(const char *fileName, int32_t mode, int32_t docType, int64_t time, uint8_t subdirs) { int64_t value = 0; DIR *dir; @@ -199,15 +207,29 @@ int64_t listdir(const char *fileName, int32_t mode, int32_t docType, int64_t tim if (name[0] == '.') { continue; } - if ((docType == 1 || docType == 2) && len > 4) { - if (name[len - 4] == '.' && ( - ((name[len - 3] == 'm' || name[len - 3] == 'M') && (name[len - 2] == 'p' || name[len - 2] == 'P') && (name[len - 1] == '3')) || - ((name[len - 3] == 'm' || name[len - 3] == 'M') && (name[len - 2] == '4') && (name[len - 1] == 'a' || name[len - 1] == 'A')) - )) { - if (docType == 1) { - continue; - } - } else if (docType == 2) { + if (docType > 0 && len > 4) { + int isMusic = ( + name[len - 4] == '.' && ( + ((name[len - 3] == 'm' || name[len - 3] == 'M') && (name[len - 2] == 'p' || name[len - 2] == 'P') && (name[len - 1] == '3')) || // mp3 + ((name[len - 3] == 'm' || name[len - 3] == 'M') && (name[len - 2] == '4') && (name[len - 1] == 'a' || name[len - 1] == 'A')) // m4a + )); + int isEmoji = ( + name[len - 4] == '.' && (name[len - 3] == 't' || name[len - 3] == 'T') && (name[len - 2] == 'g' || name[len - 2] == 'G') && (name[len - 1] == 's' || name[len - 1] == 'S') || // tgs + len > 5 && name[len - 5] == '.' && (name[len - 4] == 'w' || name[len - 4] == 'W') && (name[len - 3] == 'e' || name[len - 3] == 'E') && (name[len - 2] == 'b' || name[len - 2] == 'B') && (name[len - 1] == 'm' || name[len - 1] == 'M') // webm + ); + int isTemp = ( + name[len - 4] == '.' && (name[len - 3] == 't' || name[len - 3] == 'T') && (name[len - 2] == 'm' || name[len - 2] == 'M') && (name[len - 1] == 'p' || name[len - 1] == 'P') || // tmp + len > 5 && name[len - 5] == '.' && (name[len - 4] == 't' || name[len - 4] == 'T') && (name[len - 3] == 'e' || name[len - 3] == 'E') && (name[len - 2] == 'm' || name[len - 2] == 'M') && (name[len - 1] == 'p' || name[len - 1] == 'P') || // temp + len > 8 && name[len - 8] == '.' && (name[len - 7] == 'p' || name[len - 7] == 'P') && (name[len - 6] == 'r' || name[len - 6] == 'R') && (name[len - 5] == 'e' || name[len - 5] == 'E') && (name[len - 4] == 'l' || name[len - 4] == 'L') && (name[len - 3] == 'o' || name[len - 3] == 'O') && (name[len - 2] == 'a' || name[len - 2] == 'A') && (name[len - 1] == 'd' || name[len - 1] == 'D') // preload + ); + if ( + isMusic && docType == LISTDIR_DOCTYPE_OTHER_THAN_MUSIC || + !isMusic && docType == LISTDIR_DOCTYPE_MUSIC || + isEmoji && docType == LISTDIR_DOCTYPE2_OTHER || + !isEmoji && docType == LISTDIR_DOCTYPE2_EMOJI || + isTemp && docType == LISTDIR_DOCTYPE2_OTHER || + !isTemp && docType == LISTDIR_DOCTYPE2_TEMP + ) { continue; } } @@ -247,6 +269,20 @@ JNIEXPORT jlong Java_org_telegram_messenger_Utilities_getDirSize(JNIEnv *env, jc return value; } +JNIEXPORT jlong Java_org_telegram_messenger_Utilities_getLastUsageFileTime(JNIEnv *env, jclass class, jstring path) { + const char *fileName = (*env)->GetStringUTFChars(env, path, NULL); + struct stat attrib; + stat(fileName, &attrib); + jlong value; + if (attrib.st_atim.tv_sec != 0) { + value = attrib.st_atim.tv_sec; + } else { + value = attrib.st_mtim.tv_sec; + } + (*env)->ReleaseStringUTFChars(env, path, fileName); + return value; +} + JNIEXPORT void Java_org_telegram_messenger_Utilities_clearDir(JNIEnv *env, jclass class, jstring path, jint docType, jlong time, jboolean subdirs) { const char *fileName = (*env)->GetStringUTFChars(env, path, NULL); listdir(fileName, 1, docType, time, subdirs); diff --git a/TMessagesProj/jni/tgnet/ApiScheme.cpp b/TMessagesProj/jni/tgnet/ApiScheme.cpp index 5e7483978..5ea31be64 100644 --- a/TMessagesProj/jni/tgnet/ApiScheme.cpp +++ b/TMessagesProj/jni/tgnet/ApiScheme.cpp @@ -118,7 +118,7 @@ void TL_cdnConfig::readParams(NativeByteBuffer *stream, int32_t instanceNum, boo int magic = stream->readInt32(&error); if (magic != 0x1cb5c415) { error = true; - if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic, got %x", magic); + if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic in TL_cdnConfig, got %x", magic); return; } int count = stream->readInt32(&error); @@ -173,7 +173,7 @@ void TL_config::readParams(NativeByteBuffer *stream, int32_t instanceNum, bool & uint32_t magic = stream->readUint32(&error); if (magic != 0x1cb5c415) { error = true; - if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic, got %x", magic); + if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic in TL_config, got %x", magic); return; } int32_t count = stream->readInt32(&error); @@ -456,7 +456,7 @@ void TL_user::readParams(NativeByteBuffer *stream, int32_t instanceNum, bool &er uint32_t magic = stream->readUint32(&error); if (magic != 0x1cb5c415) { error = true; - if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic, got %x", magic); + if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic in TL_user, got %x", magic); return; } int32_t count = stream->readInt32(&error); @@ -474,11 +474,28 @@ void TL_user::readParams(NativeByteBuffer *stream, int32_t instanceNum, bool &er if ((flags & 4194304) != 0) { lang_code = stream->readString(&error); } + if ((flags & 1073741824) != 0) { + uint32_t magic = stream->readUint32(&error); + if (magic == 0x2de11aae) { + // emojiStatusEmpty + } else if (magic == 0x929b619d) { + // emojiStatus + int64_t document_id = stream->readInt64(&error); + } else if (magic == 0xfa30a8c7) { + // emojiStatusUntil + int64_t document_id = stream->readInt64(&error); + int until = stream->readInt32(&error); + } else { + error = true; + if (LOGS_ENABLED) DEBUG_FATAL("wrong EmojiStatus magic, got %x", magic); + return; + } + } if ((flags2 & 1) != 0) { uint32_t magic = stream->readUint32(&error); if (magic != 0x1cb5c415) { error = true; - if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic, got %x", magic); + if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic in TL_user (2), got %x", magic); return; } int32_t count = stream->readInt32(&error); @@ -1001,7 +1018,7 @@ void TL_help_termsOfService::readParams(NativeByteBuffer *stream, int32_t instan int magic = stream->readInt32(&error); if (magic != 0x1cb5c415) { error = true; - if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic, got %x", magic); + if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic in TL_help_termsOfService, got %x", magic); return; } int count = stream->readInt32(&error); diff --git a/TMessagesProj/jni/tgnet/MTProtoScheme.cpp b/TMessagesProj/jni/tgnet/MTProtoScheme.cpp index 99b43c84b..10746ce38 100644 --- a/TMessagesProj/jni/tgnet/MTProtoScheme.cpp +++ b/TMessagesProj/jni/tgnet/MTProtoScheme.cpp @@ -195,7 +195,7 @@ void TL_resPQ::readParams(NativeByteBuffer *stream, int32_t instanceNum, bool &e uint32_t magic = stream->readUint32(&error); if (magic != 0x1cb5c415) { error = true; - if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic, got %x", magic); + if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic in TL_resPQ, got %x", magic); return; } uint32_t count = stream->readUint32(&error); @@ -455,7 +455,7 @@ void TL_msgs_state_req::readParams(NativeByteBuffer *stream, int32_t instanceNum uint32_t magic = stream->readUint32(&error); if (magic != 0x1cb5c415) { error = true; - if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic, got %x", magic); + if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic in TL_msgs_state_req, got %x", magic); return; } uint32_t count = stream->readUint32(&error); @@ -544,7 +544,7 @@ void TL_msgs_all_info::readParams(NativeByteBuffer *stream, int32_t instanceNum, uint32_t magic = stream->readUint32(&error); if (magic != 0x1cb5c415) { error = true; - if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic, got %x", magic); + if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic in TL_msgs_all_info, got %x", magic); return; } uint32_t count = stream->readUint32(&error); @@ -616,7 +616,7 @@ void TL_msgs_ack::readParams(NativeByteBuffer *stream, int32_t instanceNum, bool uint32_t magic = stream->readUint32(&error); if (magic != 0x1cb5c415) { error = true; - if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic, got %x", magic); + if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic in TL_msgs_ack, got %x", magic); return; } uint32_t count = stream->readUint32(&error); @@ -721,7 +721,7 @@ void TL_msg_resend_req::readParams(NativeByteBuffer *stream, int32_t instanceNum uint32_t magic = stream->readUint32(&error); if (magic != 0x1cb5c415) { error = true; - if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic, got %x", magic); + if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic in TL_msg_resend_req, got %x", magic); return; } uint32_t count = stream->readUint32(&error); @@ -1028,7 +1028,7 @@ void TL_jsonArray::readParams(NativeByteBuffer *stream, int32_t instanceNum, boo int magic = stream->readInt32(&error); if (magic != 0x1cb5c415) { error = true; - if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic, got %x", magic); + if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic in TL_jsonArray, got %x", magic); return; } int count = stream->readInt32(&error); @@ -1055,7 +1055,7 @@ void TL_jsonObject::readParams(NativeByteBuffer *stream, int32_t instanceNum, bo int magic = stream->readInt32(&error); if (magic != 0x1cb5c415) { error = true; - if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic, got %x", magic); + if (LOGS_ENABLED) DEBUG_FATAL("wrong Vector magic in TL_jsonObject, got %x", magic); return; } int count = stream->readInt32(&error); diff --git a/TMessagesProj/src/main/assets/PhoneFormats.dat b/TMessagesProj/src/main/assets/PhoneFormats.dat index 23aa358dc..52332a09d 100644 Binary files a/TMessagesProj/src/main/assets/PhoneFormats.dat and b/TMessagesProj/src/main/assets/PhoneFormats.dat differ diff --git a/TMessagesProj/src/main/assets/arctic.attheme b/TMessagesProj/src/main/assets/arctic.attheme index 5465ce92e..994ffe657 100644 --- a/TMessagesProj/src/main/assets/arctic.attheme +++ b/TMessagesProj/src/main/assets/arctic.attheme @@ -175,7 +175,7 @@ picker_badge=-13261833 chat_outFileInfoSelectedText=-1056964609 chat_outAudioDurationSelectedText=-1056964609 avatar_actionBarSelectorPink=-8684677 -statisticChartLine_orange=-1853657 +statisticChartLine_orange=-881607 chat_topPanelTitle=-281900315 chat_inAudioCacheSeekbar=1071179248 chat_outContactIcon=-13332255 diff --git a/TMessagesProj/src/main/assets/bluebubbles.attheme b/TMessagesProj/src/main/assets/bluebubbles.attheme index de2fda7cc..e38120888 100644 --- a/TMessagesProj/src/main/assets/bluebubbles.attheme +++ b/TMessagesProj/src/main/assets/bluebubbles.attheme @@ -95,7 +95,7 @@ avatar_backgroundArchived=-4998207 chat_outFileInfoSelectedText=-8674358 chat_outAudioDurationSelectedText=-8674358 avatar_backgroundArchivedHidden=-10832396 -statisticChartLine_orange=-1853657 +statisticChartLine_orange=-881607 chat_topPanelTitle=-13199648 chat_outContactIcon=-1 chat_inPreviewLine=-348675089 diff --git a/TMessagesProj/src/main/assets/bot_clipboard_wrapper.js b/TMessagesProj/src/main/assets/bot_clipboard_wrapper.js deleted file mode 100644 index 89d74144f..000000000 --- a/TMessagesProj/src/main/assets/bot_clipboard_wrapper.js +++ /dev/null @@ -1,5 +0,0 @@ -navigator.clipboard.__proto__.readText = function() { - return new Promise(function(resolve, reject) { - resolve(TelegramWebviewProxy.getClipboardText()); - }); -}; \ No newline at end of file diff --git a/TMessagesProj/src/main/assets/darkblue.attheme b/TMessagesProj/src/main/assets/darkblue.attheme index 8938468fa..7e766787b 100644 --- a/TMessagesProj/src/main/assets/darkblue.attheme +++ b/TMessagesProj/src/main/assets/darkblue.attheme @@ -260,7 +260,7 @@ premiumGradientBackground2=-15394271 premiumStarGradient1=-15436801 premiumStarGradient2=-4167942 dialogTextHint=-8549479 -statisticChartLine_orange=-1720817 +statisticChartLine_orange=-881607 chat_topPanelTitle=-9719066 chat_inAudioCacheSeekbar=-11443856 chat_outContactIcon=-1 diff --git a/TMessagesProj/src/main/assets/day.attheme b/TMessagesProj/src/main/assets/day.attheme index 43822a88d..d1b4e80db 100644 --- a/TMessagesProj/src/main/assets/day.attheme +++ b/TMessagesProj/src/main/assets/day.attheme @@ -189,7 +189,7 @@ chat_outFileInfoSelectedText=-3676417 chat_outAudioDurationSelectedText=-3676417 avatar_backgroundArchivedHidden=-12208385 avatar_actionBarSelectorPink=-8684677 -statisticChartLine_orange=-1853657 +statisticChartLine_orange=-881607 chat_topPanelTitle=-12478747 chat_inAudioCacheSeekbar=1071179248 chat_outContactIcon=-14707997 diff --git a/TMessagesProj/src/main/assets/night.attheme b/TMessagesProj/src/main/assets/night.attheme index 2adf980b2..01aabc917 100644 --- a/TMessagesProj/src/main/assets/night.attheme +++ b/TMessagesProj/src/main/assets/night.attheme @@ -278,7 +278,7 @@ premiumGradientBackground2=-16645625 premiumStarGradient1=-2342678 premiumStarGradient2=-9992961 dialogTextHint=-8553091 -statisticChartLine_orange=-1457126 +statisticChartLine_orange=-881607 chat_topPanelTitle=-10440716 chat_inAudioCacheSeekbar=-10461088 chat_outContactIcon=-1 diff --git a/TMessagesProj/src/main/assets/shapes.dat b/TMessagesProj/src/main/assets/shapes.dat new file mode 100644 index 000000000..d04ff6144 Binary files /dev/null and b/TMessagesProj/src/main/assets/shapes.dat differ diff --git a/TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerView.java b/TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerView.java index dc2a4deef..42ca8a991 100644 --- a/TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerView.java +++ b/TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerView.java @@ -71,6 +71,7 @@ import androidx.annotation.Px; import androidx.annotation.RestrictTo; import androidx.annotation.VisibleForTesting; import androidx.core.os.TraceCompat; +import androidx.core.util.Consumer; import androidx.core.util.Preconditions; import androidx.core.view.AccessibilityDelegateCompat; import androidx.core.view.InputDeviceCompat; @@ -630,6 +631,18 @@ public class RecyclerView extends ViewGroup implements ScrollingView, return mChildHelper.getHiddenChildAt(index); } + public void forAllChild(Consumer callback) { + for (int i = 0; i < getChildCount(); i++) { + callback.accept(getChildAt(i)); + } + for (int i = 0; i < getHiddenChildCount(); i++) { + callback.accept(getHiddenChildAt(i)); + } + for (int i = 0; i < getAttachedScrapChildCount(); i++) { + callback.accept(getAttachedScrapChildAt(i)); + } + } + void applyEdgeEffectColor(EdgeEffect edgeEffect) { if (edgeEffect != null && Build.VERSION.SDK_INT >= 21 && glowColor != null) { edgeEffect.setColor(glowColor); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/AccountInstance.java b/TMessagesProj/src/main/java/org/telegram/messenger/AccountInstance.java index 50cc574fa..b36d750d0 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/AccountInstance.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/AccountInstance.java @@ -3,6 +3,7 @@ package org.telegram.messenger; import android.content.SharedPreferences; import org.telegram.tgnet.ConnectionsManager; +import org.telegram.ui.Components.Paint.PersistColorPalette; public class AccountInstance { @@ -37,6 +38,10 @@ public class AccountInstance { return ContactsController.getInstance(currentAccount); } + public PersistColorPalette getColorPalette() { + return PersistColorPalette.getInstance(currentAccount); + } + public MediaDataController getMediaDataController() { return MediaDataController.getInstance(currentAccount); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java b/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java index 9eca21043..a5391ed58 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java @@ -81,6 +81,7 @@ import android.view.TextureView; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager; @@ -91,6 +92,7 @@ import android.view.animation.DecelerateInterpolator; import android.view.animation.OvershootInterpolator; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; +import android.view.inspector.WindowInspector; import android.webkit.MimeTypeMap; import android.widget.EdgeEffect; import android.widget.HorizontalScrollView; @@ -129,8 +131,10 @@ import org.telegram.ui.ActionBar.BottomSheet; import org.telegram.ui.ActionBar.INavigationLayout; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Cells.TextDetailSettingsCell; +import org.telegram.ui.ChatActivity; import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.BackgroundGradientDrawable; +import org.telegram.ui.Components.Bulletin; import org.telegram.ui.Components.CubicBezierInterpolator; import org.telegram.ui.Components.ForegroundColorSpanThemable; import org.telegram.ui.Components.ForegroundDetector; @@ -142,6 +146,8 @@ import org.telegram.ui.Components.RecyclerListView; import org.telegram.ui.Components.ShareAlert; import org.telegram.ui.Components.TypefaceSpan; import org.telegram.ui.Components.URLSpanReplacement; +import org.telegram.ui.Components.UndoView; +import org.telegram.ui.LaunchActivity; import org.telegram.ui.ThemePreviewActivity; import org.telegram.ui.WallpapersListActivity; @@ -164,6 +170,7 @@ import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; @@ -185,6 +192,10 @@ public class AndroidUtilities { public final static int REPLACING_TAG_TYPE_BOLD = 1; public final static String TYPEFACE_ROBOTO_MEDIUM = "fonts/rmedium.ttf"; + public final static String TYPEFACE_ROBOTO_MEDIUM_ITALIC = "fonts/rmediumitalic.ttf"; + public final static String TYPEFACE_ROBOTO_MONO = "fonts/rmono.ttf"; + public final static String TYPEFACE_MERRIWEATHER_BOLD = "fonts/mw_bold.ttf"; + public final static String TYPEFACE_COURIER_NEW_BOLD = "fonts/courier_new_bold.ttf"; private static final Hashtable typefaceCache = new Hashtable<>(); public static float touchSlop; @@ -3698,6 +3709,17 @@ public class AndroidUtilities { ConnectionsManager.setProxySettings(true, address, p, user, password, secret); NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxySettingsChanged); + if (activity instanceof LaunchActivity) { + INavigationLayout layout = ((LaunchActivity) activity).getActionBarLayout(); + BaseFragment fragment = layout.getLastFragment(); + if (fragment instanceof ChatActivity) { + ((ChatActivity) fragment).getUndoView().showWithAction(0, UndoView.ACTION_PROXY_ADDED, null); + } else { + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.showBulletin, Bulletin.TYPE_SUCCESS, LocaleController.getString(R.string.ProxyAddedSuccess)); + } + } else { + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.showBulletin, Bulletin.TYPE_SUCCESS, LocaleController.getString(R.string.ProxyAddedSuccess)); + } dismissRunnable.run(); }); builder.show(); @@ -3981,10 +4003,19 @@ public class AndroidUtilities { return (int) (a + f * (b - a)); } + public static float lerpAngle(float a, float b, float f) { + float delta = ((b - a + 360 + 180) % 360) - 180; + return (a + delta * f + 360) % 360; + } + public static float lerp(float a, float b, float f) { return a + f * (b - a); } + public static double lerp(double a, double b, float f) { + return a + f * (b - a); + } + public static float lerp(float[] ab, float f) { return lerp(ab[0], ab[1], f); } @@ -4443,10 +4474,14 @@ public class AndroidUtilities { } public static void updateViewShow(View view, boolean show, boolean scale, boolean animated) { - updateViewShow(view, show, scale, animated, null); + updateViewShow(view, show, scale, 0, animated, null); } public static void updateViewShow(View view, boolean show, boolean scale, boolean animated, Runnable onDone) { + updateViewShow(view, show, scale, 0, animated, onDone); + } + + public static void updateViewShow(View view, boolean show, boolean scale, float translate, boolean animated, Runnable onDone) { if (view == null) { return; } @@ -4461,6 +4496,9 @@ public class AndroidUtilities { view.setAlpha(1f); view.setScaleX(scale && !show ? 0f : 1f); view.setScaleY(scale && !show ? 0f : 1f); + if (translate != 0) { + view.setTranslationY(show ? 0 : AndroidUtilities.dp(-16) * translate); + } if (onDone != null) { onDone.run(); } @@ -4470,10 +4508,23 @@ public class AndroidUtilities { view.setAlpha(0f); view.setScaleX(scale ? 0 : 1); view.setScaleY(scale ? 0 : 1); + if (translate != 0) { + view.setTranslationY(AndroidUtilities.dp(-16) * translate); + } } - view.animate().alpha(1f).scaleY(1f).scaleX(1f).setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT).setDuration(340).withEndAction(onDone).start(); + ViewPropertyAnimator animate = view.animate(); + animate = animate.alpha(1f).scaleY(1f).scaleX(1f).setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT).setDuration(340).withEndAction(onDone); + if (translate != 0) { + animate.translationY(0); + } + animate.start(); } else { - view.animate().alpha(0).scaleY(scale ? 0 : 1).scaleX(scale ? 0 : 1).setListener(new HideViewAfterAnimation(view)).setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT).setDuration(340).withEndAction(onDone).start(); + ViewPropertyAnimator animate = view.animate(); + animate = animate.alpha(0).scaleY(scale ? 0 : 1).scaleX(scale ? 0 : 1).setListener(new HideViewAfterAnimation(view)).setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT).setDuration(340).withEndAction(onDone); + if (translate != 0) { + animate.translationY(AndroidUtilities.dp(-16) * translate); + } + animate.start(); } } @@ -4618,4 +4669,149 @@ public class AndroidUtilities { } return spannableStringBuilder; } + + public static Bitmap makeBlurBitmap(View view) { + if (view == null) { + return null; + } + int w = (int) (view.getWidth() / 6.0f); + int h = (int) (view.getHeight() / 6.0f); + Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + canvas.scale(1.0f / 6.0f, 1.0f / 6.0f); + canvas.drawColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + view.draw(canvas); + Utilities.stackBlurBitmap(bitmap, Math.max(7, Math.max(w, h) / 180)); + return bitmap; + } + + public static void makeGlobalBlurBitmap(Utilities.Callback onBitmapDone, float amount) { + if (onBitmapDone == null) { + return; + } + + List views = null; + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + views = WindowInspector.getGlobalWindowViews(); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + Class wmgClass = Class.forName("android.view.WindowManagerGlobal"); + Object wmgInstance = wmgClass.getMethod("getInstance").invoke(null, (Object[]) null); + + Method getViewRootNames = wmgClass.getMethod("getViewRootNames"); + Method getRootView = wmgClass.getMethod("getRootView", String.class); + String[] rootViewNames = (String[])getViewRootNames.invoke(wmgInstance, (Object[])null); + + views = new ArrayList<>(); + for (String viewName : rootViewNames) { + views.add((View) getRootView.invoke(wmgInstance, viewName)); + } + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { + Class wmiClass = Class.forName("android.view.WindowManagerImpl"); + Object wmiInstance = wmiClass.getMethod("getDefault").invoke(null); + + Field viewsField = wmiClass.getDeclaredField("mViews"); + viewsField.setAccessible(true); + Object viewsObject = viewsField.get(wmiInstance); + + if (viewsObject instanceof List) { + views = (List) viewsField.get(wmiInstance); + } else if (viewsObject instanceof View[]) { + views = Arrays.asList((View[]) viewsField.get(wmiInstance)); + } + } + } catch (Exception e) { + FileLog.e("makeGlobalBlurBitmap()", e); + } + + if (views == null) { + onBitmapDone.run(null); + return; + } + + final List finalViews = views; + //Utilities.themeQueue.postRunnable(() -> { + try { + int w = (int) (AndroidUtilities.displaySize.x / amount); + int h = (int) ((AndroidUtilities.displaySize.y + AndroidUtilities.statusBarHeight) / amount); + Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + canvas.scale(1.0f / amount, 1.0f / amount); + canvas.drawColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + int[] location = new int[2]; + for (int i = 0; i < finalViews.size(); ++i) { + View view = finalViews.get(i); + + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + if (layoutParams instanceof WindowManager.LayoutParams) { + WindowManager.LayoutParams params = (WindowManager.LayoutParams) layoutParams; + if ((params.flags & WindowManager.LayoutParams.FLAG_DIM_BEHIND) != 0) { + canvas.drawColor(ColorUtils.setAlphaComponent(0xFF000000, (int) (0xFF * params.dimAmount))); + } + } + + canvas.save(); + view.getLocationOnScreen(location); + canvas.translate(location[0] / amount, location[1] / amount); + try { + view.draw(canvas); + } catch (Exception e) { + + } + canvas.restore(); + } + Utilities.stackBlurBitmap(bitmap, Math.max((int) amount, Math.max(w, h) / 180)); + AndroidUtilities.runOnUIThread(() -> { + onBitmapDone.run(bitmap); + }); + } catch (Exception e) { + FileLog.e(e); + AndroidUtilities.runOnUIThread(() -> { + onBitmapDone.run(null); + }); + } + // }); + } + + // rounds percents to be exact 100% in sum + public static int[] roundPercents(float[] percents, int[] output) { + if (percents == null) { + throw new NullPointerException("percents or output is null"); + } + if (output == null) { + output = new int[percents.length]; + } + if (percents.length != output.length) { + throw new IndexOutOfBoundsException("percents.length != output.length"); + } + + float sum = 0; + for (int i = 0; i < percents.length; ++i) { + sum += percents[i]; + } + + int roundedSum = 0; + for (int i = 0; i < percents.length; ++i) { + roundedSum += (output[i] = (int) Math.floor(percents[i] / sum * 100)); + } + + while (roundedSum < 100) { + float maxError = 0; + int maxErrorIndex = -1; + for (int i = 0; i < percents.length; ++i) { + float error = (percents[i] / sum) - (output[i] / 100f); + if (percents[i] > 0 && error >= maxError) { + maxErrorIndex = i; + maxError = error; + } + } + if (maxErrorIndex < 0) { + break; + } + output[maxErrorIndex]++; + roundedSum++; + } + + return output; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/BaseController.java b/TMessagesProj/src/main/java/org/telegram/messenger/BaseController.java index 1affa94f7..e30886fb9 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/BaseController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/BaseController.java @@ -1,6 +1,7 @@ package org.telegram.messenger; import org.telegram.tgnet.ConnectionsManager; +import org.telegram.ui.Components.Paint.PersistColorPalette; public class BaseController { @@ -24,6 +25,10 @@ public class BaseController { return parentAccountInstance.getContactsController(); } + protected final PersistColorPalette getColorPalette() { + return parentAccountInstance.getColorPalette(); + } + protected final MediaDataController getMediaDataController() { return parentAccountInstance.getMediaDataController(); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java b/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java index 1493777ad..5a7afc476 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java @@ -24,8 +24,8 @@ public class BuildVars { public static boolean USE_CLOUD_STRINGS = true; public static boolean CHECK_UPDATES = true; public static boolean NO_SCOPED_STORAGE = Build.VERSION.SDK_INT <= 29; - public static int BUILD_VERSION = 2965; - public static String BUILD_VERSION_STRING = "9.2.2"; + public static int BUILD_VERSION = 3021; + public static String BUILD_VERSION_STRING = "9.3.0"; public static int APP_ID = 4; public static String APP_HASH = "014b35b6184100b085b0d0572f9b5103"; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/CacheByChatsController.java b/TMessagesProj/src/main/java/org/telegram/messenger/CacheByChatsController.java new file mode 100644 index 000000000..537e1456a --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/CacheByChatsController.java @@ -0,0 +1,195 @@ +package org.telegram.messenger; + +import android.text.TextUtils; +import android.util.LongSparseArray; + +import org.telegram.tgnet.TLRPC; + +import java.io.File; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashSet; + +public class CacheByChatsController { + + public static int KEEP_MEDIA_DELETE = 4; + public static int KEEP_MEDIA_FOREVER = 2; + public static int KEEP_MEDIA_ONE_DAY = 3; + public static int KEEP_MEDIA_ONE_WEEK = 0; + public static int KEEP_MEDIA_ONE_MONTH = 1; + //TEST VALUE + public static int KEEP_MEDIA_ONE_MINUTE = 5; + + public static final int KEEP_MEDIA_TYPE_USER = 0; + public static final int KEEP_MEDIA_TYPE_GROUP = 1; + public static final int KEEP_MEDIA_TYPE_CHANNEL = 2; + + private final int currentAccount; + + int[] keepMediaByTypes = {-1, -1, -1}; + + public CacheByChatsController(int currentAccount) { + this.currentAccount = currentAccount; + for (int i = 0; i < 3; i++) { + keepMediaByTypes[i] = SharedConfig.getPreferences().getInt("keep_media_type_" + i, getDefault(i)); + } + } + + public static int getDefault(int type) { + if (type == KEEP_MEDIA_TYPE_USER) { + return KEEP_MEDIA_FOREVER; + } else if (type == KEEP_MEDIA_TYPE_GROUP) { + return KEEP_MEDIA_ONE_MONTH; + } else if (type == KEEP_MEDIA_TYPE_CHANNEL) { + return KEEP_MEDIA_ONE_MONTH; + } + return SharedConfig.keepMedia; + } + + public static String getKeepMediaString(int keepMedia) { + if (keepMedia == KEEP_MEDIA_ONE_MINUTE) { + return LocaleController.formatPluralString("Minutes", 1); + } else if (keepMedia == KEEP_MEDIA_ONE_DAY) { + return LocaleController.formatPluralString("Days", 1); + } else if (keepMedia == KEEP_MEDIA_ONE_WEEK) { + return LocaleController.formatPluralString("Weeks", 1); + } else if (keepMedia == KEEP_MEDIA_ONE_MONTH) { + return LocaleController.formatPluralString("Months", 1); + } + return LocaleController.getString("AutoDeleteMediaNever", R.string.AutoDeleteMediaNever); + } + + public static long getDaysInSeconds(int keepMedia) { + long seconds; + if (keepMedia == CacheByChatsController.KEEP_MEDIA_FOREVER) { + seconds = Long.MAX_VALUE; + } else if (keepMedia == CacheByChatsController.KEEP_MEDIA_ONE_WEEK) { + seconds = 60L * 60L * 24L * 7L; + } else if (keepMedia == CacheByChatsController.KEEP_MEDIA_ONE_MONTH) { + seconds = 60L * 60L * 24L * 30L; + } else if (keepMedia == CacheByChatsController.KEEP_MEDIA_ONE_DAY) { + seconds = 60L * 60L * 24L; + } else { //one min + seconds = 60L; + } + return seconds; + } + + public ArrayList getKeepMediaExceptions(int type) { + ArrayList exceptions = new ArrayList<>(); + HashSet idsSet = new HashSet<>(); + String exceptionsHash = UserConfig.getInstance(currentAccount).getPreferences().getString("keep_media_exceptions_" + type, ""); + if (TextUtils.isEmpty(exceptionsHash)) { + return exceptions; + } else { + ByteBuffer byteBuffer = ByteBuffer.wrap(Utilities.hexToBytes(exceptionsHash)); + int n = byteBuffer.getInt(); + for (int i = 0; i < n; i++) { + KeepMediaException exception = new KeepMediaException(byteBuffer.getLong(), byteBuffer.getInt()); + if (!idsSet.contains(exception.dialogId)) { + idsSet.add(exception.dialogId); + exceptions.add(exception); + } + } + byteBuffer.clear(); + } + return exceptions; + } + + public void saveKeepMediaExceptions(int type, ArrayList exceptions) { + String key = "keep_media_exceptions_" + type; + if (exceptions.isEmpty()) { + UserConfig.getInstance(currentAccount).getPreferences().edit().remove(key).apply(); + } else { + int n = exceptions.size(); + ByteBuffer byteBuffer = ByteBuffer.allocate(4 + (8 + 4) * n); + byteBuffer.putInt(n); + for (int i = 0; i < n; i++) { + byteBuffer.putLong(exceptions.get(i).dialogId); + byteBuffer.putInt(exceptions.get(i).keepMedia); + } + UserConfig.getInstance(currentAccount).getPreferences().edit().putString(key, Utilities.bytesToHex(byteBuffer.array())).apply(); + byteBuffer.clear(); + } + } + + public int getKeepMedia(int type) { + if (keepMediaByTypes[type] == -1) { + return SharedConfig.keepMedia; + } + return keepMediaByTypes[type]; + } + + public void setKeepMedia(int type, int keepMedia) { + keepMediaByTypes[type] = keepMedia; + SharedConfig.getPreferences().edit().putInt("keep_media_type_" + type, keepMedia).apply(); + } + + public void lookupFiles(ArrayList keepMediaFiles) { + LongSparseArray> filesByDialogId = FileLoader.getInstance(currentAccount).getFileDatabase().lookupFiles(keepMediaFiles); + LongSparseArray exceptionsByType = getKeepMediaExceptionsByDialogs(); + for (int i = 0; i < filesByDialogId.size(); i++) { + long dialogId = filesByDialogId.keyAt(i); + ArrayList files = filesByDialogId.valueAt(i); + int type; + if (dialogId >= 0) { + type = KEEP_MEDIA_TYPE_USER; + } else { + TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-dialogId); + if (chat == null) { + chat = MessagesStorage.getInstance(currentAccount).getChatSync(-dialogId); + } + if (chat == null) { + type = -1; + } else if (ChatObject.isChannel(chat)) { + type = KEEP_MEDIA_TYPE_CHANNEL; + } else { + type = KEEP_MEDIA_TYPE_GROUP; + } + } + KeepMediaException exception = exceptionsByType.get(dialogId); + for (int k = 0; k < files.size(); k++) { + KeepMediaFile file = files.get(k); + if (type >= 0) { + file.dialogType = type; + } + if (exception != null) { + file.keepMedia = exception.keepMedia; + } + } + } + } + + public LongSparseArray getKeepMediaExceptionsByDialogs() { + LongSparseArray sparseArray = new LongSparseArray<>(); + for (int i = 0; i < 3; i++) { + ArrayList exceptions = getKeepMediaExceptions(i); + if (exceptions != null) { + for (int k = 0; k < exceptions.size(); k++) { + sparseArray.put(exceptions.get(k).dialogId, exceptions.get(k)); + } + } + } + return sparseArray; + } + + public static class KeepMediaException { + public final long dialogId; + public int keepMedia; + + public KeepMediaException(long dialogId, int keepMedia) { + this.dialogId = dialogId; + this.keepMedia = keepMedia; + } + } + + public static class KeepMediaFile { + final File file; + int keepMedia = -1; + int dialogType = KEEP_MEDIA_TYPE_CHANNEL; + + public KeepMediaFile(File file) { + this.file = file; + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ContactsController.java b/TMessagesProj/src/main/java/org/telegram/messenger/ContactsController.java index 15708edca..7f0d15677 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ContactsController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ContactsController.java @@ -23,7 +23,9 @@ import android.net.Uri; import android.os.Build; import android.provider.BaseColumns; import android.provider.ContactsContract; +import android.telephony.PhoneNumberUtils; import android.text.TextUtils; +import android.util.Log; import android.util.SparseArray; import androidx.collection.LongSparseArray; @@ -38,6 +40,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class ContactsController extends BaseController { @@ -556,7 +560,7 @@ public class ContactsController extends BaseController { return count > 3; } - private HashMap readContactsFromPhoneBook() { + public HashMap readContactsFromPhoneBook() { if (!getUserConfig().syncContacts) { if (BuildVars.LOGS_ENABLED) { FileLog.d("contacts sync disabled"); @@ -748,6 +752,66 @@ public class ContactsController extends BaseController { } pCur = null; } + + //TODO optimize + Cursor cur = cr.query(ContactsContract.Contacts.CONTENT_URI, null,ContactsContract.Contacts.HAS_PHONE_NUMBER + " = ?", new String[]{"0"}, null); + if (cur != null) { + String[] metadata = new String[5]; + Pattern phonePattern = Pattern.compile(".*(\\+[0-9 \\-]+).*"); + while (cur.moveToNext()) { + String id = cur.getString(cur.getColumnIndex(ContactsContract.Contacts._ID)); + String lookup_key = cur.getString(cur.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY)); + String name = cur.getString(cur.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)); + String phone = null; + if (contactsMap.get(lookup_key) != null || TextUtils.isEmpty(name)) { + continue; + } + pCur = cr.query( + ContactsContract.Data.CONTENT_URI, + null, + ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", + new String[]{id}, null); + loop : while (pCur.moveToNext()) { + metadata[0] = pCur.getString(pCur.getColumnIndex(ContactsContract.Data.DATA1)); + metadata[1] = pCur.getString(pCur.getColumnIndex(ContactsContract.Data.DATA2)); + metadata[2] = pCur.getString(pCur.getColumnIndex(ContactsContract.Data.DATA3)); + metadata[3] = pCur.getString(pCur.getColumnIndex(ContactsContract.Data.DATA4)); + metadata[4] = pCur.getString(pCur.getColumnIndex(ContactsContract.Data.DATA5)); + for (int i = 0; i < metadata.length; i++) { + if (metadata[i] == null) { + continue; + } + Matcher matcher = phonePattern.matcher(metadata[i]); + if (matcher.matches()) { + phone = matcher.group(1).replace(" ", "").replace("-", ""); + break loop; + } + } + } + + if (phone != null) { + + String shortNumber = phone; + if (phone.startsWith("+")) { + shortNumber = phone.substring(1); + } + + Contact contact = new Contact(); + contact.first_name = name; + contact.last_name = ""; + contact.contact_id = lastContactId++; + contact.key = lookup_key; + contact.phones.add(phone); + contact.shortPhones.add(shortNumber); + contact.phoneDeleted.add(0); + contact.phoneTypes.add(LocaleController.getString("PhoneOther", R.string.PhoneOther)); + +// contact.provider = accountType; +// contact.isGoodProvider = isGoodAccountType; + contactsMap.put(lookup_key, contact); + } + } + } } catch (Throwable e) { FileLog.e(e); if (contactsMap != null) { @@ -2169,13 +2233,14 @@ public class ContactsController extends BaseController { return; } final TLRPC.Updates res = (TLRPC.Updates) response; - getMessagesController().processUpdates(res, false); - - /*if (BuildVars.DEBUG_VERSION) { - for (TLRPC.User user : res.users) { - FileLog.e("received user " + user.first_name + " " + user.last_name + " " + user.phone); + if (user.photo != null && user.photo.personal) { + for (int i = 0; i < res.users.size(); i++) { + if (res.users.get(i).id == user.id) { + res.users.get(i).photo = user.photo; + } } - }*/ + } + getMessagesController().processUpdates(res, false); for (int a = 0; a < res.users.size(); a++) { final TLRPC.User u = res.users.get(a); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/DispatchQueuePoolBackground.java b/TMessagesProj/src/main/java/org/telegram/messenger/DispatchQueuePoolBackground.java index 8b628ac81..355a358e8 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/DispatchQueuePoolBackground.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/DispatchQueuePoolBackground.java @@ -109,8 +109,11 @@ public class DispatchQueuePoolBackground { } @UiThread public static void execute(Runnable runnable, boolean now) { - if (BuildVars.DEBUG_PRIVATE_VERSION && Thread.currentThread() != ApplicationLoader.applicationHandler.getLooper().getThread()) { - throw new RuntimeException("wrong thread"); + if (Thread.currentThread() != ApplicationLoader.applicationHandler.getLooper().getThread()) { + if (BuildVars.DEBUG_PRIVATE_VERSION) { + FileLog.e(new RuntimeException("wrong thread")); + } + return; } if (updateTaskCollection == null) { if (!freeCollections.isEmpty()) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/DownloadController.java b/TMessagesProj/src/main/java/org/telegram/messenger/DownloadController.java index b943fd284..a2f766fbc 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/DownloadController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/DownloadController.java @@ -1255,8 +1255,10 @@ public class DownloadController extends BaseController implements NotificationCe AndroidUtilities.runOnUIThread(() -> { boolean removed = false; + TLRPC.Document parentDocument = parentObject.getDocument(); for (int i = 0; i < downloadingFiles.size(); i++) { - if (downloadingFiles.get(i).getDocument().id == parentObject.getDocument().id) { + TLRPC.Document downloadingDocument = downloadingFiles.get(i).getDocument(); + if (downloadingDocument == null || parentDocument != null && downloadingDocument.id == parentDocument.id) { downloadingFiles.remove(i); removed = true; break; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/Emoji.java b/TMessagesProj/src/main/java/org/telegram/messenger/Emoji.java index 6e385e255..7b168cfd8 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/Emoji.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/Emoji.java @@ -485,6 +485,10 @@ public class Emoji { return emojis; } + public static CharSequence replaceEmoji(CharSequence cs, Paint.FontMetricsInt fontMetrics, boolean createNew) { + return replaceEmoji(cs, fontMetrics, AndroidUtilities.dp(16), createNew, null); + } + public static CharSequence replaceEmoji(CharSequence cs, Paint.FontMetricsInt fontMetrics, int size, boolean createNew) { return replaceEmoji(cs, fontMetrics, size, createNew, null); } @@ -565,6 +569,16 @@ public class Emoji { size = newSize; } + public void replaceFontMetrics(Paint.FontMetricsInt newMetrics) { + fontMetrics = newMetrics; + if (fontMetrics != null) { + size = Math.abs(fontMetrics.descent) + Math.abs(fontMetrics.ascent); + if (size == 0) { + size = AndroidUtilities.dp(20); + } + } + } + @Override public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { if (fm == null) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java index 6cb108a95..d96cc3339 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java @@ -13,6 +13,7 @@ import org.telegram.tgnet.NativeByteBuffer; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.LaunchActivity; +import org.telegram.ui.Storage.CacheModel; import java.io.File; import java.io.FileInputStream; @@ -189,7 +190,7 @@ public class FileLoadOperation { private File tempPath; private boolean isForceRequest; private int priority; - private long fileDialogId; + private FilePathDatabase.FileMeta fileMetadata; private boolean ungzip; @@ -222,7 +223,7 @@ public class FileLoadOperation { public FileLoadOperation(ImageLocation imageLocation, Object parent, String extension, long size) { updateParams(); parentObject = parent; - fileDialogId = FileLoader.getDialogIdFromParent(currentAccount, parentObject); + fileMetadata = FileLoader.getFileMetadataFromParent(currentAccount, parentObject); isStream = imageLocation.imageType == FileLoader.IMAGE_TYPE_ANIMATION; if (imageLocation.isEncrypted()) { location = new TLRPC.TL_inputEncryptedFileLocation(); @@ -329,7 +330,7 @@ public class FileLoadOperation { updateParams(); try { parentObject = parent; - fileDialogId = FileLoader.getDialogIdFromParent(currentAccount, parentObject); + fileMetadata = FileLoader.getFileMetadataFromParent(currentAccount, parentObject); if (documentLocation instanceof TLRPC.TL_documentEncrypted) { location = new TLRPC.TL_inputEncryptedFileLocation(); location.id = documentLocation.id; @@ -971,9 +972,9 @@ public class FileLoadOperation { } } - if (fileDialogId != 0) { - FileLoader.getInstance(currentAccount).getFileDatabase().saveFileDialogId(cacheFileParts, fileDialogId); - FileLoader.getInstance(currentAccount).getFileDatabase().saveFileDialogId(cacheFileTemp, fileDialogId); + if (fileMetadata != null) { + FileLoader.getInstance(currentAccount).getFileDatabase().saveFileDialogId(cacheFileParts, fileMetadata); + FileLoader.getInstance(currentAccount).getFileDatabase().saveFileDialogId(cacheFileTemp, fileMetadata); } if (cacheFileTemp.exists()) { @@ -1293,12 +1294,12 @@ public class FileLoadOperation { if (BuildVars.DEBUG_VERSION) { FileLog.d("finished preloading file to " + cacheFileTemp + " loaded " + totalPreloadedBytes + " of " + totalBytesCount); } - if (fileDialogId != 0) { + if (fileMetadata != null) { if (cacheFileTemp != null) { - FileLoader.getInstance(currentAccount).getFileDatabase().removeFiles(Collections.singletonList(cacheFileTemp)); + FileLoader.getInstance(currentAccount).getFileDatabase().removeFiles(Collections.singletonList(new CacheModel.FileInfo(cacheFileTemp))); } if (cacheFileParts != null) { - FileLoader.getInstance(currentAccount).getFileDatabase().removeFiles(Collections.singletonList(cacheFileParts)); + FileLoader.getInstance(currentAccount).getFileDatabase().removeFiles(Collections.singletonList(new CacheModel.FileInfo(cacheFileParts))); } } delegate.didFinishLoadingFile(FileLoadOperation.this, cacheFileFinal); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java index c3a075f51..9abfea23a 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java @@ -40,26 +40,35 @@ public class FileLoader extends BaseController { private static Pattern sentPattern; - public static long getDialogIdFromParent(int currentAccount, Object parentObject) { + public static FilePathDatabase.FileMeta getFileMetadataFromParent(int currentAccount, Object parentObject) { if (parentObject instanceof String) { String str = (String) parentObject; if (str.startsWith("sent_")) { if (sentPattern == null) { - sentPattern = Pattern.compile("sent_.*_.*_([0-9]+)"); + sentPattern = Pattern.compile("sent_.*_([0-9]+)_([0-9]+)_([0-9]+)"); } try { Matcher matcher = sentPattern.matcher(str); if (matcher.matches()) { - return Long.parseLong(matcher.group(1)); + FilePathDatabase.FileMeta fileMeta = new FilePathDatabase.FileMeta(); + fileMeta.messageId = Integer.parseInt(matcher.group(1)); + fileMeta.dialogId = Long.parseLong(matcher.group(2)); + fileMeta.messageType = Integer.parseInt(matcher.group(3)); + return fileMeta; } } catch (Exception e) { FileLog.e(e); } } } else if (parentObject instanceof MessageObject) { - return ((MessageObject) parentObject).getDialogId(); + MessageObject messageObject = (MessageObject) parentObject; + FilePathDatabase.FileMeta fileMeta = new FilePathDatabase.FileMeta(); + fileMeta.messageId = messageObject.getId(); + fileMeta.dialogId = messageObject.getDialogId(); + fileMeta.messageType = messageObject.type; + return fileMeta; } - return 0; + return null; } private int getPriorityValue(int priorityType) { @@ -767,9 +776,9 @@ public class FileLoader extends BaseController { if (!operation.isPreloadVideoOperation() && operation.isPreloadFinished()) { return; } - long dialogId = getDialogIdFromParent(currentAccount, parentObject); - if (dialogId != 0) { - getFileLoader().getFileDatabase().saveFileDialogId(finalFile, dialogId); + FilePathDatabase.FileMeta fileMeta = getFileMetadataFromParent(currentAccount, parentObject); + if (fileMeta != null) { + getFileLoader().getFileDatabase().saveFileDialogId(finalFile, fileMeta); } if (parentObject instanceof MessageObject) { MessageObject messageObject = (MessageObject) parentObject; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FilePathDatabase.java b/TMessagesProj/src/main/java/org/telegram/messenger/FilePathDatabase.java index 8b0bbbc7e..ad1e909f5 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FilePathDatabase.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FilePathDatabase.java @@ -1,11 +1,13 @@ package org.telegram.messenger; import android.os.Looper; +import android.util.LongSparseArray; import org.telegram.SQLite.SQLiteCursor; import org.telegram.SQLite.SQLiteDatabase; import org.telegram.SQLite.SQLiteException; import org.telegram.SQLite.SQLitePreparedStatement; +import org.telegram.ui.Storage.CacheModel; import java.io.File; import java.io.IOException; @@ -22,11 +24,15 @@ public class FilePathDatabase { private File cacheFile; private File shmCacheFile; - private final static int LAST_DB_VERSION = 3; + private final static int LAST_DB_VERSION = 4; private final static String DATABASE_NAME = "file_to_path"; private final static String DATABASE_BACKUP_NAME = "file_to_path_backup"; + public final static int MESSAGE_TYPE_VIDEO_MESSAGE = 0; + + private final FileMeta metaTmp = new FileMeta(); + public FilePathDatabase(int currentAccount) { this.currentAccount = currentAccount; dispatchQueue = new DispatchQueue("files_database_queue_" + currentAccount); @@ -58,7 +64,7 @@ public class FilePathDatabase { database.executeFast("CREATE TABLE paths(document_id INTEGER, dc_id INTEGER, type INTEGER, path TEXT, PRIMARY KEY(document_id, dc_id, type));").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS path_in_paths ON paths(path);").stepThis().dispose(); - database.executeFast("CREATE TABLE paths_by_dialog_id(path TEXT PRIMARY KEY, dialog_id INTEGER);").stepThis().dispose(); + database.executeFast("CREATE TABLE paths_by_dialog_id(path TEXT PRIMARY KEY, dialog_id INTEGER, message_id INTEGER, message_type INTEGER);").stepThis().dispose(); database.executeFast("PRAGMA user_version = " + LAST_DB_VERSION).stepThis().dispose(); } else { @@ -104,6 +110,11 @@ public class FilePathDatabase { database.executeFast("PRAGMA user_version = " + 3).stepThis().dispose(); version = 3; } + if (version == 3) { + database.executeFast("ALTER TABLE paths_by_dialog_id ADD COLUMN message_id INTEGER default 0").stepThis().dispose(); + database.executeFast("ALTER TABLE paths_by_dialog_id ADD COLUMN message_type INTEGER default 0").stepThis().dispose(); + database.executeFast("PRAGMA user_version = " + 4).stepThis().dispose(); + } } private void createBackup() { @@ -310,17 +321,19 @@ public class FilePathDatabase { return res[0]; } - public void saveFileDialogId(File file, long dialogId) { - if (file == null) { + public void saveFileDialogId(File file,FileMeta fileMeta) { + if (file == null || fileMeta == null) { return; } dispatchQueue.postRunnable(() -> { SQLitePreparedStatement state = null; try { - state = database.executeFast("REPLACE INTO paths_by_dialog_id VALUES(?, ?)"); + state = database.executeFast("REPLACE INTO paths_by_dialog_id VALUES(?, ?, ?, ?)"); state.requery(); - state.bindString(1, file.getPath()); - state.bindLong(2, dialogId); + state.bindString(1, shield(file.getPath())); + state.bindLong(2, fileMeta.dialogId); + state.bindInteger(3, fileMeta.messageId); + state.bindInteger(4, fileMeta.messageType); state.step(); } catch (Exception e) { FileLog.e(e); @@ -332,16 +345,23 @@ public class FilePathDatabase { }); } - public long getFileDialogId(File file) { + public FileMeta getFileDialogId(File file, FileMeta metaTmp) { if (file == null) { - return 0; + return null; + } + if (metaTmp == null) { + metaTmp = this.metaTmp; } long dialogId = 0; + int messageId = 0; + int messageType = 0; SQLiteCursor cursor = null; try { - cursor = database.queryFinalized("SELECT dialog_id FROM paths_by_dialog_id WHERE path = '" + file.getPath() + "'"); + cursor = database.queryFinalized("SELECT dialog_id, message_id, message_type FROM paths_by_dialog_id WHERE path = '" + shield(file.getPath()) + "'"); if (cursor.next()) { dialogId = cursor.longValue(0); + messageId = cursor.intValue(1); + messageType = cursor.intValue(2); } } catch (Exception e) { FileLog.e(e); @@ -350,19 +370,26 @@ public class FilePathDatabase { cursor.dispose(); } } - return dialogId; + metaTmp.dialogId = dialogId; + metaTmp.messageId = messageId; + metaTmp.messageType = messageType; + return metaTmp; + } + + private String shield(String path) { + return path.replace("'","").replace("\"",""); } public DispatchQueue getQueue() { return dispatchQueue; } - public void removeFiles(List filesToRemove) { + public void removeFiles(List filesToRemove) { dispatchQueue.postRunnable(() -> { try { database.beginTransaction(); for (int i = 0; i < filesToRemove.size(); i++) { - database.executeFast("DELETE FROM paths_by_dialog_id WHERE path = '" + filesToRemove.get(i).getPath() + "'").stepThis().dispose(); + database.executeFast("DELETE FROM paths_by_dialog_id WHERE path = '" + shield(filesToRemove.get(i).file.getPath()) + "'").stepThis().dispose(); } } catch (Exception e) { FileLog.e(e); @@ -372,6 +399,36 @@ public class FilePathDatabase { }); } + public LongSparseArray> lookupFiles(ArrayList keepMediaFiles) { + CountDownLatch syncLatch = new CountDownLatch(1); + LongSparseArray> filesByDialogId = new LongSparseArray<>(); + dispatchQueue.postRunnable(() -> { + try { + FileMeta fileMetaTmp = new FileMeta(); + for (int i = 0; i < keepMediaFiles.size(); i++) { + FileMeta fileMeta = getFileDialogId(keepMediaFiles.get(i).file, fileMetaTmp); + if (fileMeta != null && fileMeta.dialogId != 0) { + ArrayList list = filesByDialogId.get(fileMeta.dialogId); + if (list == null) { + list = new ArrayList<>(); + filesByDialogId.put(fileMeta.dialogId, list); + } + list.add(keepMediaFiles.get(i)); + } + } + } catch (Exception e) { + FileLog.e(e); + } + syncLatch.countDown(); + }); + try { + syncLatch.await(); + } catch (InterruptedException e) { + FileLog.e(e); + } + return filesByDialogId; + } + public static class PathData { public final long id; public final int dc; @@ -383,4 +440,10 @@ public class FilePathDatabase { this.type = type; } } + + public static class FileMeta { + public long dialogId; + public int messageId; + public int messageType; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileRefController.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileRefController.java index aa9a3bb02..d4b1ff985 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FileRefController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileRefController.java @@ -752,7 +752,7 @@ public class FileRefController extends BaseController { } else if (message.media.webpage != null) { result = getFileReference(message.media.webpage, requester.location, needReplacement, locationReplacement); } - } else if (message.action instanceof TLRPC.TL_messageActionChatEditPhoto) { + } else if (message.action instanceof TLRPC.TL_messageActionChatEditPhoto || message.action instanceof TLRPC.TL_messageActionSuggestProfilePhoto) { result = getFileReference(message.action.photo, requester.location, needReplacement, locationReplacement); } if (result != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java index e3af7cb76..b8954434a 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java @@ -1020,7 +1020,8 @@ public class ImageLoader { cacheOptions.compressQuality = BitmapsCache.COMPRESS_QUALITY_DEFAULT; } } - fileDrawable = new AnimatedFileDrawable(cacheImage.finalFilePath, false, size, document, document == null ? cacheImage.imageLocation : null, cacheImage.parentObject, seekTo, cacheImage.currentAccount, false, cacheOptions); + boolean notCreateStream = cacheImage.filter != null && cacheImage.filter.contains("nostream"); + fileDrawable = new AnimatedFileDrawable(cacheImage.finalFilePath, false, notCreateStream ? 0 : size, notCreateStream ? null : document, document == null && !notCreateStream ? cacheImage.imageLocation : null, cacheImage.parentObject, seekTo, cacheImage.currentAccount, false, cacheOptions); fileDrawable.setIsWebmSticker(MessageObject.isWebM(document) || MessageObject.isVideoSticker(document) || isAnimatedAvatar(cacheImage.filter)); } else { @@ -1042,7 +1043,9 @@ public class ImageLoader { cacheOptions.compressQuality = BitmapsCache.COMPRESS_QUALITY_DEFAULT; } } - fileDrawable = new AnimatedFileDrawable(cacheImage.finalFilePath, "d".equals(cacheImage.filter), 0, cacheImage.imageLocation.document, null, null, seekTo, cacheImage.currentAccount, false, w, h, cacheOptions); + boolean createDecoder = cacheImage.filter != null && ("d".equals(cacheImage.filter) || cacheImage.filter.contains("_d")); + boolean notCreateStream = cacheImage.filter != null && cacheImage.filter.contains("nostream"); + fileDrawable = new AnimatedFileDrawable(cacheImage.finalFilePath, createDecoder, 0, notCreateStream ? null : cacheImage.imageLocation.document, null, null, seekTo, cacheImage.currentAccount, false, w, h, cacheOptions); fileDrawable.setIsWebmSticker(MessageObject.isWebM(cacheImage.imageLocation.document) || MessageObject.isVideoSticker(cacheImage.imageLocation.document) || isAnimatedAvatar(cacheImage.filter)); } fileDrawable.setLimitFps(limitFps); @@ -1380,7 +1383,13 @@ public class ImageLoader { opts.inDither = false; if (mediaId != null && mediaThumbPath == null) { if (mediaIsVideo) { - image = MediaStore.Video.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Video.Thumbnails.MINI_KIND, opts); + if (mediaId == 0) { + AnimatedFileDrawable fileDrawable = new AnimatedFileDrawable(cacheFileFinal, true, 0, null, null, null, 0, 0, true, null); + image = fileDrawable.getFrameAtTime(0, true); + fileDrawable.recycle(); + } else { + image = MediaStore.Video.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Video.Thumbnails.MINI_KIND, opts); + } } else { image = MediaStore.Images.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Images.Thumbnails.MINI_KIND, opts); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ImageLocation.java b/TMessagesProj/src/main/java/org/telegram/messenger/ImageLocation.java index 34f1b113c..47a415bcd 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ImageLocation.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ImageLocation.java @@ -137,7 +137,7 @@ public class ImageLocation { int currentAccount = UserConfig.selectedAccount; if (MessagesController.getInstance(currentAccount).isPremiumUser(user) && user.photo.has_video) { final TLRPC.UserFull userFull = MessagesController.getInstance(currentAccount).getUserFull(user.id); - if (userFull != null && userFull.profile_photo !=null && userFull.profile_photo.video_sizes != null && !userFull.profile_photo.video_sizes.isEmpty()) { + if (userFull != null && userFull.profile_photo != null && userFull.profile_photo.video_sizes != null && !userFull.profile_photo.video_sizes.isEmpty()) { TLRPC.VideoSize videoSize = userFull.profile_photo.video_sizes.get(0); for (int i = 0; i < userFull.profile_photo.video_sizes.size(); i++) { if ("p".equals(userFull.profile_photo.video_sizes.get(i).type)) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java index ef5264ba6..3ca9ae14e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java @@ -48,7 +48,8 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg if (currentThumbDrawable != null && thumbShader != null) { drawDrawable(null, currentThumbDrawable, 255, thumbShader, 0, 0, null); return true; - } if (staticThumbDrawable != null && thumbShader != null) { + } + if (staticThumbDrawable != null && thumbShader != null) { drawDrawable(null, staticThumbDrawable, 255, thumbShader, 0, 0, null); return true; } @@ -190,6 +191,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg private int fileLoadingPriority = FileLoader.PRIORITY_NORMAL; private int currentLayerNum; + public boolean ignoreNotifications; private int currentOpenedLayerFlags; private int isLastFrame; @@ -384,7 +386,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg if (user.photo != null) { strippedBitmap = user.photo.strippedBitmap; hasStripped = user.photo.stripped_thumb != null; - if (animationEnabled && MessagesController.getInstance(currentAccount).isPremiumUser(user) && user.photo.has_video) { + if (animationEnabled && MessagesController.getInstance(currentAccount).isPremiumUser(user) && user.photo.has_video && !SharedConfig.getLiteMode().enabled()) { final TLRPC.UserFull userFull = MessagesController.getInstance(currentAccount).getUserFull(user.id); if (userFull == null) { MessagesController.getInstance(currentAccount).loadFullUser(user, currentGuid, false); @@ -417,7 +419,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg ImageLocation location = ImageLocation.getForUserOrChat(object, ImageLocation.TYPE_SMALL); String filter = "50_50"; if (videoLocation != null) { - setImage(videoLocation, "avatar", location, filter, null, null,strippedBitmap, 0, null, parentObject, 0); + setImage(videoLocation, "avatar", location, filter, null, null, strippedBitmap, 0, null, parentObject, 0); animatedFileDrawableRepeatMaxCount = 3; } else { if (strippedBitmap != null) { @@ -958,9 +960,11 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg setImageBackup.cacheType = currentCacheType; setImageBackup.parentObject = currentParentObject; } - NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.didReplacedPhotoInMemCache); - NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.stopAllHeavyOperations); - NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.startAllHeavyOperations); + if (!ignoreNotifications) { + NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.didReplacedPhotoInMemCache); + NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.stopAllHeavyOperations); + NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.startAllHeavyOperations); + } if (staticThumbDrawable != null) { staticThumbDrawable = null; @@ -1001,13 +1005,34 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg return false; } + private int bufferedFrame; + public void incrementFrames(int inc) { + if (currentMediaDrawable instanceof RLottieDrawable) { +// RLottieDrawable rlottie = (RLottieDrawable) currentMediaDrawable; +// inc = (int) Math.round((float) rlottie.getFramesCount() / rlottie.getDuration() * (1f / 30f)); +// rlottie.setCurrentFrame( +// (rlottie.getCurrentFrame() + inc) % (int) rlottie.getFramesCount() +// ); + } else if (currentMediaDrawable instanceof AnimatedFileDrawable) { + int lastFrame = (int) bufferedFrame; + bufferedFrame += inc; + int currentFrame = (int) bufferedFrame; + while (lastFrame != currentFrame) { + ((AnimatedFileDrawable) currentMediaDrawable).getNextFrame(); + currentFrame--; + } + } + } + public boolean onAttachedToWindow() { attachedToWindow = true; currentOpenedLayerFlags = NotificationCenter.getGlobalInstance().getCurrentHeavyOperationFlags(); currentOpenedLayerFlags &= ~currentLayerNum; - NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.didReplacedPhotoInMemCache); - NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.stopAllHeavyOperations); - NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.startAllHeavyOperations); + if (!ignoreNotifications) { + NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.didReplacedPhotoInMemCache); + NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.stopAllHeavyOperations); + NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.startAllHeavyOperations); + } if (setBackupImage()) { return true; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java index c90cf95eb..cacf807c9 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java @@ -348,6 +348,10 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, public boolean isVideo; public boolean isMuted; public boolean canDeleteAfter; + public boolean hasSpoiler; + + public boolean isChatPreviewSpoilerRevealed; + public boolean isAttachSpoilerRevealed; public PhotoEntry(int bucketId, int imageId, long dateTaken, String path, int orientation, boolean isVideo, int width, int height, long size) { this.bucketId = bucketId; @@ -365,6 +369,12 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, this.isVideo = isVideo; } + @Override + public void copyFrom(MediaEditState state) { + super.copyFrom(state); + this.hasSpoiler = state instanceof PhotoEntry && ((PhotoEntry) state).hasSpoiler; + } + @Override public String getPath() { return path; @@ -378,6 +388,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, filterPath = null; } } + hasSpoiler = false; super.reset(); } } @@ -3762,7 +3773,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, currentAccount.getNotificationCenter().addObserver(this, NotificationCenter.fileLoaded); currentAccount.getNotificationCenter().addObserver(this, NotificationCenter.fileLoadProgressChanged); currentAccount.getNotificationCenter().addObserver(this, NotificationCenter.fileLoadFailed); - progressDialog = new AlertDialog(context, 2); + progressDialog = new AlertDialog(context, AlertDialog.ALERT_TYPE_LOADING); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setCancelable(true); @@ -4045,7 +4056,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, final boolean[] finished = new boolean[1]; if (context != null && type != 0) { try { - final AlertDialog dialog = new AlertDialog(context, 2); + final AlertDialog dialog = new AlertDialog(context, AlertDialog.ALERT_TYPE_LOADING); dialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); dialog.setCanceledOnTouchOutside(false); dialog.setCancelable(true); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java index 8d878c024..5ae8c1f1f 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java @@ -65,7 +65,6 @@ import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.Bulletin; import org.telegram.ui.Components.ChatThemeBottomSheet; import org.telegram.ui.Components.RLottieDrawable; -import org.telegram.ui.Components.Reactions.ReactionsEffectOverlay; import org.telegram.ui.Components.StickerSetBulletinLayout; import org.telegram.ui.Components.StickersArchiveAlert; import org.telegram.ui.Components.TextStyleSpan; @@ -652,7 +651,7 @@ public class MediaDataController extends BaseController { } public void preloadDefaultReactions() { - if (reactionsList == null || reactionsCacheGenerated) { + if (reactionsList == null || reactionsCacheGenerated || SharedConfig.getLiteMode().enabled()) { return; } reactionsCacheGenerated = true; @@ -665,8 +664,6 @@ public class MediaDataController extends BaseController { for (int i = 0; i < arrayList.size(); i++) { TLRPC.TL_availableReaction reaction = arrayList.get(i); - int size = ReactionsEffectOverlay.sizeForBigReaction(); - preloadImage(ImageLocation.getForDocument(reaction.around_animation), ReactionsEffectOverlay.getFilterForAroundAnimation(), true); preloadImage(ImageLocation.getForDocument(reaction.effect_animation), null); } } @@ -699,6 +696,14 @@ public class MediaDataController extends BaseController { imageReceiver.setImage(location, filter, null, null, 0, FileLoader.PRELOAD_CACHE_TYPE); } + public void preloadImage(ImageReceiver imageReceiver, ImageLocation location, String filter) { + if (SharedConfig.getLiteMode().enabled()) { + return; + } + imageReceiver.setUniqKeyPrefix("preload"); + imageReceiver.setImage(location, filter, null, null, 0, FileLoader.PRELOAD_CACHE_TYPE); + } + private void putReactionsToCache(List reactions, int hash, int date) { ArrayList reactionsFinal = reactions != null ? new ArrayList<>(reactions) : null; getMessagesStorage().getStorageQueue().postRunnable(() -> { @@ -1067,6 +1072,16 @@ public class MediaDataController extends BaseController { groupStickerSets.put(stickerSet.set.id, stickerSet); } + public static TLRPC.InputStickerSet getInputStickerSet(TLRPC.StickerSet set) { + if (set != null) { + TLRPC.TL_inputStickerSetID inputStickerSetID = new TLRPC.TL_inputStickerSetID(); + inputStickerSetID.id = set.id; + inputStickerSetID.access_hash = set.access_hash; + return inputStickerSetID; + } + return null; + } + public TLRPC.TL_messages_stickerSet getStickerSet(TLRPC.InputStickerSet inputStickerSet, boolean cacheOnly) { return getStickerSet(inputStickerSet, cacheOnly, null); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java index ef1a90b0a..c3cbaf844 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java @@ -98,6 +98,7 @@ public class MessageObject { public static final int TYPE_GIFT_PREMIUM = 18; public static final int TYPE_EMOJIS = 19; public static final int TYPE_EXTENDED_MEDIA_PREVIEW = 20; + public static final int TYPE_SUGGEST_PHOTO = 21; public int localType; public String localName; @@ -112,6 +113,7 @@ public class MessageObject { public TLRPC.Document emojiAnimatedSticker; public Long emojiAnimatedStickerId; public boolean isTopicMainMessage; + public boolean settingAvatar; private boolean emojiAnimatedStickerLoading; public String emojiAnimatedStickerColor; public CharSequence messageText; @@ -164,6 +166,9 @@ public class MessageObject { public long loadedFileSize; public boolean isSpoilersRevealed; + public boolean isMediaSpoilersRevealed; + public boolean isMediaSpoilersRevealedInSharedMedia; + public boolean revealingMediaSpoilers; public byte[] sponsoredId; public int sponsoredChannelPost; public TLRPC.ChatInvite sponsoredChatInvite; @@ -340,6 +345,10 @@ public class MessageObject { return emojiOnlyCount; } + public boolean hasMediaSpoilers() { + return messageOwner.media != null && messageOwner.media.spoiler; + } + public boolean shouldDrawReactionsInLayout() { if (getDialogId() < 0 || UserConfig.getInstance(currentAccount).isPremium()) { return true; @@ -3299,6 +3308,12 @@ public class MessageObject { SpannableStringBuilder sb = SpannableStringBuilder.valueOf(messageText); messageText = sb.replace(i, i + 3, BillingController.getInstance().formatCurrency(messageOwner.action.amount, messageOwner.action.currency)); } + } else if (messageOwner.action instanceof TLRPC.TL_messageActionSuggestProfilePhoto) { + if (messageOwner.action.photo != null && messageOwner.action.photo.video_sizes != null && !messageOwner.action.photo.video_sizes.isEmpty()) { + messageText = LocaleController.getString(R.string.ActionSuggestVideoShort); + } else { + messageText = LocaleController.getString(R.string.ActionSuggestPhotoShort); + } } else if (messageOwner.action instanceof TLRPC.TL_messageActionChatEditPhoto) { TLRPC.Chat chat = messageOwner.peer_id != null && messageOwner.peer_id.channel_id != 0 ? getChat(chats, sChats, messageOwner.peer_id.channel_id) : null; if (ChatObject.isChannel(chat) && !chat.megagroup) { @@ -3358,6 +3373,8 @@ public class MessageObject { messageText = LocaleController.formatString("MessageLifetimeRemoved", R.string.MessageLifetimeRemoved, UserObject.getFirstName(fromUser)); } } + } else if (messageOwner.action instanceof TLRPC.TL_messageActionAttachMenuBotAllowed) { + messageText = LocaleController.getString(R.string.ActionAttachMenuBotAllowed); } else if (messageOwner.action instanceof TLRPC.TL_messageActionSetMessagesTTL) { TLRPC.TL_messageActionSetMessagesTTL action = (TLRPC.TL_messageActionSetMessagesTTL) messageOwner.action; TLRPC.Chat chat = messageOwner.peer_id != null && messageOwner.peer_id.channel_id != 0 ? getChat(chats, sChats, messageOwner.peer_id.channel_id) : null; @@ -3872,7 +3889,13 @@ public class MessageObject { type = TYPE_TEXT; } } else if (messageOwner instanceof TLRPC.TL_messageService) { - if (messageOwner.action instanceof TLRPC.TL_messageActionLoginUnknownLocation) { + if (messageOwner.action instanceof TLRPC.TL_messageActionSuggestProfilePhoto) { + contentType = 1; + type = TYPE_SUGGEST_PHOTO; + photoThumbs = new ArrayList<>(); + photoThumbs.addAll(messageOwner.action.photo.sizes); + photoThumbsObject = messageOwner.action.photo; + } else if (messageOwner.action instanceof TLRPC.TL_messageActionLoginUnknownLocation) { type = TYPE_TEXT; } else if (messageOwner.action instanceof TLRPC.TL_messageActionGiftPremium) { contentType = 1; @@ -5505,7 +5528,7 @@ public class MessageObject { return isOutOwnerCached = false; } if (messageOwner.fwd_from == null) { - return isOutOwnerCached= true; + return isOutOwnerCached = true; } long selfUserId = UserConfig.getInstance(currentAccount).getClientUserId(); if (getDialogId() == selfUserId) { @@ -5971,7 +5994,7 @@ public class MessageObject { } public static boolean canAutoplayAnimatedSticker(TLRPC.Document document) { - return (isAnimatedStickerDocument(document, true) || isVideoStickerDocument(document)) && SharedConfig.getDevicePerformanceClass() != SharedConfig.PERFORMANCE_CLASS_LOW; + return (isAnimatedStickerDocument(document, true) || isVideoStickerDocument(document)) && SharedConfig.getDevicePerformanceClass() != SharedConfig.PERFORMANCE_CLASS_LOW && !SharedConfig.getLiteMode().enabled(); } public static boolean isMaskDocument(TLRPC.Document document) { @@ -6135,6 +6158,9 @@ public class MessageObject { if (getMedia(message) instanceof TLRPC.TL_messageMediaWebPage) { return getMedia(message).webpage.photo instanceof TLRPC.TL_photo && !(getMedia(message).webpage.document instanceof TLRPC.TL_document); } + if (message != null && message.action != null && message.action.photo != null) { + return message.action.photo instanceof TLRPC.TL_photo; + } return getMedia(message) instanceof TLRPC.TL_messageMediaPhoto; } @@ -6355,7 +6381,7 @@ public class MessageObject { return AndroidUtilities.dp(82); } else if (type == 10) { return AndroidUtilities.dp(30); - } else if (type == TYPE_ACTION_PHOTO || type == TYPE_GIFT_PREMIUM) { + } else if (type == TYPE_ACTION_PHOTO || type == TYPE_GIFT_PREMIUM || type == TYPE_SUGGEST_PHOTO) { return AndroidUtilities.dp(50); } else if (type == TYPE_ROUND_VIDEO) { return AndroidUtilities.roundMessageSize; @@ -7026,6 +7052,10 @@ public class MessageObject { return messageOwner.reply_to != null ? messageOwner.reply_to.reply_to_top_id : 0; } + public int getReplyTopMsgId(boolean sureIsForum) { + return messageOwner.reply_to != null ? (sureIsForum && (messageOwner.reply_to.flags & 2) > 0 && messageOwner.reply_to.reply_to_top_id == 0 ? 1 : messageOwner.reply_to.reply_to_top_id) : 0; + } + public static long getReplyToDialogId(TLRPC.Message message) { if (message.reply_to == null) { return 0; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java index ee5b76908..ef107555d 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java @@ -38,6 +38,7 @@ import androidx.core.util.Consumer; import org.telegram.SQLite.SQLiteCursor; import org.telegram.SQLite.SQLiteException; import org.telegram.SQLite.SQLitePreparedStatement; +import org.telegram.messenger.browser.Browser; import org.telegram.messenger.support.LongSparseIntArray; import org.telegram.messenger.support.LongSparseLongArray; import org.telegram.messenger.voip.VoIPService; @@ -56,6 +57,7 @@ import org.telegram.ui.ChatRightsEditActivity; import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.AnimatedEmojiDrawable; import org.telegram.ui.Components.BulletinFactory; +import org.telegram.ui.Components.ImageUpdater; import org.telegram.ui.Components.JoinCallAlert; import org.telegram.ui.Components.MotionBackgroundDrawable; import org.telegram.ui.Components.SwipeGestureSettingsView; @@ -84,6 +86,7 @@ import java.util.concurrent.CountDownLatch; public class MessagesController extends BaseController implements NotificationCenter.NotificationCenterDelegate { + public int lastKnownSessionsCount; private ConcurrentHashMap chats = new ConcurrentHashMap<>(100, 1.0f, 2); private ConcurrentHashMap encryptedChats = new ConcurrentHashMap<>(10, 1.0f, 2); private ConcurrentHashMap users = new ConcurrentHashMap<>(100, 1.0f, 2); @@ -373,6 +376,7 @@ public class MessagesController extends BaseController implements NotificationCe public int topicsPinnedLimit; public long telegramAntispamUserId; public int telegramAntispamGroupSizeMin; + public int hiddenMembersGroupSizeMin; public int uploadMaxFileParts; public int uploadMaxFilePartsPremium; @@ -396,6 +400,7 @@ public class MessagesController extends BaseController implements NotificationCe private Runnable recentEmojiStatusUpdateRunnable; private LongSparseArray emojiStatusUntilValues = new LongSparseArray<>(); private TopicsController topicsController; + private CacheByChatsController cacheByChatsController; public void getNextReactionMention(long dialogId, int topicId, int count, Consumer callback) { final MessagesStorage messagesStorage = getMessagesStorage(); @@ -619,6 +624,23 @@ public class MessagesController extends BaseController implements NotificationCe }); } + public SparseArray photoSuggestion = new SparseArray<>(); + + public String getFullName(long dialogId) { + if (dialogId > 0) { + TLRPC.User user = getUser(dialogId); + if (user != null) { + return ContactsController.formatName(user.first_name, user.last_name); + } + } else { + TLRPC.Chat chat = getChat(-dialogId); + if (chat != null) { + return chat.title; + } + } + return null; + } + public class SponsoredMessagesInfo { public ArrayList messages; public Integer posts_between; @@ -1129,6 +1151,7 @@ public class MessagesController extends BaseController implements NotificationCe topicsPinnedLimit = mainPreferences.getInt("topicsPinnedLimit", 3); telegramAntispamUserId = mainPreferences.getLong("telegramAntispamUserId", -1); telegramAntispamGroupSizeMin = mainPreferences.getInt("telegramAntispamGroupSizeMin", 100); + hiddenMembersGroupSizeMin = mainPreferences.getInt("hiddenMembersGroupSizeMin", 100); BuildVars.GOOGLE_AUTH_CLIENT_ID = mainPreferences.getString("googleAuthClientId", BuildVars.GOOGLE_AUTH_CLIENT_ID); Set currencySet = mainPreferences.getStringSet("directPaymentsCurrency", null); @@ -1266,6 +1289,7 @@ public class MessagesController extends BaseController implements NotificationCe } topicsController = new TopicsController(num); + cacheByChatsController = new CacheByChatsController(num); } @@ -2654,6 +2678,7 @@ public class MessagesController extends BaseController implements NotificationCe changed = true; } } + break; } case "telegram_antispam_user_id": { if (value.value instanceof TLRPC.TL_jsonString) { @@ -2669,6 +2694,7 @@ public class MessagesController extends BaseController implements NotificationCe FileLog.e(e); } } + break; } case "telegram_antispam_group_size_min": { if (value.value instanceof TLRPC.TL_jsonNumber) { @@ -2679,6 +2705,18 @@ public class MessagesController extends BaseController implements NotificationCe changed = true; } } + break; + } + case "hidden_members_group_size_min": { + if (value.value instanceof TLRPC.TL_jsonNumber) { + TLRPC.TL_jsonNumber number = (TLRPC.TL_jsonNumber) value.value; + if (number.value != hiddenMembersGroupSizeMin) { + hiddenMembersGroupSizeMin = (int) number.value; + editor.putInt("hiddenMembersGroupSizeMin", hiddenMembersGroupSizeMin); + changed = true; + } + } + break; } } } @@ -3411,6 +3449,7 @@ public class MessagesController extends BaseController implements NotificationCe getSecretChatHelper().cleanup(); getLocationController().cleanup(); getMediaDataController().cleanup(); + getColorPalette().cleanup(); showFiltersTooltip = false; @@ -10309,7 +10348,7 @@ public class MessagesController extends BaseController implements NotificationCe public void convertToMegaGroup(Context context, long chatId, BaseFragment fragment, MessagesStorage.LongCallback convertRunnable, Runnable errorRunnable) { TLRPC.TL_messages_migrateChat req = new TLRPC.TL_messages_migrateChat(); req.chat_id = chatId; - AlertDialog progressDialog = context != null ? new AlertDialog(context, 3) : null; + AlertDialog progressDialog = context != null ? new AlertDialog(context, AlertDialog.ALERT_TYPE_SPINNER) : null; int reqId = getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { if (context != null) { @@ -10370,7 +10409,7 @@ public class MessagesController extends BaseController implements NotificationCe public void convertToGigaGroup(final Context context, TLRPC.Chat chat, BaseFragment fragment, MessagesStorage.BooleanCallback convertRunnable) { TLRPC.TL_channels_convertToGigagroup req = new TLRPC.TL_channels_convertToGigagroup(); req.channel = getInputChannel(chat); - AlertDialog progressDialog = context != null ? new AlertDialog(context, 3) : null; + AlertDialog progressDialog = context != null ? new AlertDialog(context, AlertDialog.ALERT_TYPE_SPINNER) : null; int reqId = getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { if (context != null) { @@ -13793,7 +13832,15 @@ public class MessagesController extends BaseController implements NotificationCe updatesOnMainThread = new ArrayList<>(); } updatesOnMainThread.add(baseUpdate); - } else if (baseUpdate instanceof TLRPC.TL_updateUserPhone) { + } else if (baseUpdate instanceof TLRPC.TL_updateUser) { + TLRPC.TL_updateUser update = (TLRPC.TL_updateUser) baseUpdate; + interfaceUpdateMask |= UPDATE_MASK_AVATAR; + getMessagesStorage().clearUserPhotos(update.user_id); + if (updatesOnMainThread == null) { + updatesOnMainThread = new ArrayList<>(); + } + updatesOnMainThread.add(baseUpdate); + }else if (baseUpdate instanceof TLRPC.TL_updateUserPhone) { interfaceUpdateMask |= UPDATE_MASK_PHONE; if (updatesOnMainThread == null) { updatesOnMainThread = new ArrayList<>(); @@ -14741,6 +14788,31 @@ public class MessagesController extends BaseController implements NotificationCe if (UserObject.isUserSelf(currentUser)) { getNotificationCenter().postNotificationName(NotificationCenter.mainUserInfoChanged); } + } else if (baseUpdate instanceof TLRPC.TL_updateUser) { + TLRPC.TL_updateUser update = (TLRPC.TL_updateUser) baseUpdate; + TLRPC.User currentUser = getUser(update.user_id); + TLRPC.User updated = null; + if (usersArr != null) { + for (int i = 0; i < usersArr.size(); ++i) { + TLRPC.User user = usersArr.get(i); + if (user != null && user.id == update.user_id) { + updated = user; + break; + } + } + } + if (updated != null && updated.photo != null) { + if (currentUser != null) { + currentUser.photo = updated.photo; + } + TLRPC.User toDbUser = new TLRPC.TL_user(); + toDbUser.id = updated.id; + toDbUser.photo = updated.photo; + dbUsers.add(toDbUser); + } + if (UserObject.isUserSelf(currentUser)) { + getNotificationCenter().postNotificationName(NotificationCenter.mainUserInfoChanged); + } } else if (baseUpdate instanceof TLRPC.TL_updateUserPhone) { TLRPC.TL_updateUserPhone update = (TLRPC.TL_updateUserPhone) baseUpdate; TLRPC.User currentUser = getUser(update.user_id); @@ -16058,7 +16130,7 @@ public class MessagesController extends BaseController implements NotificationCe return false; } - protected boolean updateInterfaceWithMessages(long dialogId, ArrayList messages, boolean scheduled) { + public boolean updateInterfaceWithMessages(long dialogId, ArrayList messages, boolean scheduled) { if (messages == null || messages.isEmpty()) { return false; } @@ -16573,7 +16645,7 @@ public class MessagesController extends BaseController implements NotificationCe if (messageId != 0 && originalMessage != null && chat != null && chat.access_hash == 0) { long did = originalMessage.getDialogId(); if (!DialogObject.isEncryptedDialog(did)) { - AlertDialog progressDialog = new AlertDialog(fragment.getParentActivity(), 3); + AlertDialog progressDialog = new AlertDialog(fragment.getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER); TLObject req; if (did < 0) { chat = getChat(-did); @@ -16659,6 +16731,10 @@ public class MessagesController extends BaseController implements NotificationCe } public void openByUserName(String username, BaseFragment fragment, int type) { + openByUserName(username, fragment, type, null); + } + + public void openByUserName(String username, BaseFragment fragment, int type, Browser.Progress progress) { if (username == null || fragment == null) { return; } @@ -16684,16 +16760,20 @@ public class MessagesController extends BaseController implements NotificationCe if (fragment.getParentActivity() == null) { return; } - AlertDialog[] progressDialog = new AlertDialog[]{new AlertDialog(fragment.getParentActivity(), 3)}; + AlertDialog[] progressDialog = new AlertDialog[] { + new AlertDialog(fragment.getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER) + }; TLRPC.TL_contacts_resolveUsername req = new TLRPC.TL_contacts_resolveUsername(); req.username = username; int reqId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { try { - progressDialog[0].dismiss(); - } catch (Exception ignored) { - - } + if (progress != null) { + progress.end(); + } else { + progressDialog[0].dismiss(); + } + } catch (Exception ignored) {} progressDialog[0] = null; fragment.setVisibleDialog(null); if (error == null) { @@ -16719,13 +16799,18 @@ public class MessagesController extends BaseController implements NotificationCe } } })); - AndroidUtilities.runOnUIThread(() -> { - if (progressDialog[0] == null) { - return; - } - progressDialog[0].setOnCancelListener(dialog -> getConnectionsManager().cancelRequest(reqId, true)); - fragment.showDialog(progressDialog[0]); - }, 500); + if (progress != null) { + progress.onCancel(() -> getConnectionsManager().cancelRequest(reqId, true)); + progress.init(); + } else { + AndroidUtilities.runOnUIThread(() -> { + if (progressDialog[0] == null) { + return; + } + progressDialog[0].setOnCancelListener(dialog -> getConnectionsManager().cancelRequest(reqId, true)); + fragment.showDialog(progressDialog[0]); + }, 500); + } } } @@ -17077,4 +17162,7 @@ public class MessagesController extends BaseController implements NotificationCe }); } + public CacheByChatsController getCacheByChatsController() { + return cacheByChatsController; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java index 60533c794..a016d55cb 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java @@ -3924,6 +3924,9 @@ public class MessagesStorage extends BaseController { if (photo instanceof TLRPC.TL_photoEmpty) { continue; } + if (photo.file_reference == null) { + photo.file_reference = new byte[0]; + } state.requery(); int size = photo.getObjectSize(); if (messages != null) { @@ -3952,6 +3955,33 @@ public class MessagesStorage extends BaseController { }); } + public void addDialogPhoto(long did, TLRPC.Photo photo) { + storageQueue.postRunnable(() -> { + SQLitePreparedStatement state = null; + try { + state = database.executeFast("REPLACE INTO user_photos VALUES(?, ?, ?)"); + + state.requery(); + int size = photo.getObjectSize(); + NativeByteBuffer data = new NativeByteBuffer(size); + photo.serializeToStream(data); + state.bindLong(1, did); + state.bindLong(2, photo.id); + state.bindByteBuffer(3, data); + state.step(); + data.reuse(); + state.dispose(); + state = null; + } catch (Exception e) { + FileLog.e(e); + } finally { + if (state != null) { + state.dispose(); + } + } + }); + } + public void emptyMessagesMedia(long dialogId, ArrayList mids) { storageQueue.postRunnable(() -> { SQLiteCursor cursor = null; @@ -10650,6 +10680,7 @@ public class MessagesStorage extends BaseController { object.ttl_seconds = message.media.ttl_seconds; object.flags |= 4; } + MessageObject messageObject = new MessageObject(currentAccount, message, false, false); downloadMediaMask |= type; state_download.requery(); data = new NativeByteBuffer(object.getObjectSize()); @@ -10658,7 +10689,7 @@ public class MessagesStorage extends BaseController { state_download.bindInteger(2, type); state_download.bindInteger(3, message.date); state_download.bindByteBuffer(4, data); - state_download.bindString(5, "sent_" + (message.peer_id != null ? message.peer_id.channel_id : 0) + "_" + message.id + "_" + DialogObject.getPeerDialogId(message.peer_id)); + state_download.bindString(5, "sent_" + (message.peer_id != null ? message.peer_id.channel_id : 0) + "_" + message.id + "_" + DialogObject.getPeerDialogId(message.peer_id) + "_" + messageObject.type); state_download.step(); data.reuse(); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java index 2ebb767ed..3772ef046 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java @@ -3853,7 +3853,7 @@ public class NotificationsController extends BaseController { } if (silent != 1 && !notifyDisabled) { - if (!isInApp || preferences.getBoolean("EnableInAppPreview", true)) { + if (!isInApp || preferences.getBoolean("EnableInAppPreview", true) && lastMessage != null) { if (lastMessage.length() > 100) { lastMessage = lastMessage.substring(0, 100).replace('\n', ' ').trim() + "..."; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java index ee550d4cc..052babcac 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java @@ -66,7 +66,7 @@ public class SecretChatHelper extends BaseController { } } - public static int CURRENT_SECRET_CHAT_LAYER = 101; + public static int CURRENT_SECRET_CHAT_LAYER = 151; private ArrayList sendingNotifyLayer = new ArrayList<>(); private SparseArray> secretHolesQueue = new SparseArray<>(); @@ -1926,7 +1926,7 @@ public class SecretChatHelper extends BaseController { return; } startingSecretChat = true; - AlertDialog progressDialog = new AlertDialog(context, 3); + AlertDialog progressDialog = new AlertDialog(context, AlertDialog.ALERT_TYPE_SPINNER); TLRPC.TL_messages_getDhConfig req = new TLRPC.TL_messages_getDhConfig(); req.random_length = 256; req.version = getMessagesStorage().getLastSecretVersion(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java index 75e8db83c..c70ee541c 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java @@ -8,6 +8,7 @@ package org.telegram.messenger; +import android.annotation.SuppressLint; import android.content.ClipDescription; import android.content.Context; import android.content.Intent; @@ -517,8 +518,10 @@ public class SendMessagesHelper extends BaseController implements NotificationCe public boolean canDeleteAfter; public boolean forceImage; public boolean updateStickersOrder; + public boolean hasMediaSpoilers; } + @SuppressLint("MissingPermission") public static class LocationProvider { public interface LocationProviderDelegate { @@ -1379,7 +1382,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe public boolean retrySendMessage(MessageObject messageObject, boolean unsent) { if (messageObject.getId() >= 0) { if (messageObject.isEditing()) { - editMessage(messageObject, null, null, null, null, null, true, messageObject); + editMessage(messageObject, null, null, null, null, null, true, messageObject.hasMediaSpoilers(), messageObject); } return false; } @@ -1451,7 +1454,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe HashMap params = null; if (DialogObject.isEncryptedDialog(did) && messageObject.messageOwner.peer_id != null && (messageObject.messageOwner.media.photo instanceof TLRPC.TL_photo || messageObject.messageOwner.media.document instanceof TLRPC.TL_document)) { params = new HashMap<>(); - params.put("parentObject", "sent_" + messageObject.messageOwner.peer_id.channel_id + "_" + messageObject.getId() + "_" + messageObject.getDialogId()); + params.put("parentObject", "sent_" + messageObject.messageOwner.peer_id.channel_id + "_" + messageObject.getId() + "_" + messageObject.getDialogId() + "_" + messageObject.type); } if (messageObject.messageOwner.media.photo instanceof TLRPC.TL_photo) { sendMessage((TLRPC.TL_photo) messageObject.messageOwner.media.photo, null, did, messageObject.replyMessageObject, null, messageObject.messageOwner.message, messageObject.messageOwner.entities, null, params, true, 0, messageObject.messageOwner.media.ttl_seconds, messageObject, false); @@ -2233,7 +2236,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe } } - public void editMessage(MessageObject messageObject, TLRPC.TL_photo photo, VideoEditedInfo videoEditedInfo, TLRPC.TL_document document, String path, HashMap params, boolean retry, Object parentObject) { + public void editMessage(MessageObject messageObject, TLRPC.TL_photo photo, VideoEditedInfo videoEditedInfo, TLRPC.TL_document document, String path, HashMap params, boolean retry, boolean hasMediaSpoilers, Object parentObject) { if (messageObject == null) { return; } @@ -2303,6 +2306,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe newMsg.media = new TLRPC.TL_messageMediaPhoto(); newMsg.media.flags |= 3; newMsg.media.photo = photo; + newMsg.media.spoiler = hasMediaSpoilers; type = 2; if (path != null && path.length() > 0 && path.startsWith("http")) { newMsg.attachPath = path; @@ -2314,6 +2318,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe newMsg.media = new TLRPC.TL_messageMediaDocument(); newMsg.media.flags |= 3; newMsg.media.document = document; + newMsg.media.spoiler = hasMediaSpoilers; if (MessageObject.isVideoDocument(document) || videoEditedInfo != null) { type = 3; } else { @@ -2406,6 +2411,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe } else if (type == 2) { TLRPC.TL_inputMediaUploadedPhoto uploadedPhoto = new TLRPC.TL_inputMediaUploadedPhoto(); + uploadedPhoto.spoiler = hasMediaSpoilers; if (params != null) { String masks = params.get("masks"); if (masks != null) { @@ -2431,6 +2437,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe if (media.id.file_reference == null) { media.id.file_reference = new byte[0]; } + media.spoiler = hasMediaSpoilers; inputMedia = media; } @@ -2449,6 +2456,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe } } else if (type == 3) { TLRPC.TL_inputMediaUploadedDocument uploadedDocument = new TLRPC.TL_inputMediaUploadedDocument(); + uploadedDocument.spoiler = hasMediaSpoilers; if (params != null) { String masks = params.get("masks"); if (masks != null) { @@ -2481,6 +2489,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe if (media.id.file_reference == null) { media.id.file_reference = new byte[0]; } + media.spoiler = hasMediaSpoilers; inputMedia = media; } @@ -2504,6 +2513,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe TLRPC.InputMedia uploadedDocument = new TLRPC.TL_inputMediaUploadedDocument(); uploadedDocument.mime_type = document.mime_type; uploadedDocument.attributes = document.attributes; + uploadedDocument.spoiler = hasMediaSpoilers; if (document.access_hash == 0) { inputMedia = uploadedDocument; @@ -2517,6 +2527,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe if (media.id.file_reference == null) { media.id.file_reference = new byte[0]; } + media.spoiler = hasMediaSpoilers; inputMedia = media; } if (!http) { @@ -3212,6 +3223,10 @@ public class SendMessagesHelper extends BaseController implements NotificationCe sendMessage(null, caption, null, null, videoEditedInfo, null, document, null, null, null, peer, path, replyToMsg, replyToTopMsg, null, true, null, entities, replyMarkup, params, notify, scheduleDate, ttl, parentObject, sendAnimationData, updateStickersOrder); } + public void sendMessage(TLRPC.TL_document document, VideoEditedInfo videoEditedInfo, String path, long peer, MessageObject replyToMsg, MessageObject replyToTopMsg, String caption, ArrayList entities, TLRPC.ReplyMarkup replyMarkup, HashMap params, boolean notify, int scheduleDate, int ttl, Object parentObject, MessageObject.SendAnimationData sendAnimationData, boolean updateStickersOrder, boolean hasMediaSpoilers) { + sendMessage(null, caption, null, null, videoEditedInfo, null, document, null, null, null, peer, path, replyToMsg, replyToTopMsg, null, true, null, entities, replyMarkup, params, notify, scheduleDate, ttl, parentObject, sendAnimationData, updateStickersOrder, hasMediaSpoilers); + } + public void sendMessage(String message, long peer, MessageObject replyToMsg, MessageObject replyToTopMsg, TLRPC.WebPage webPage, boolean searchLinks, ArrayList entities, TLRPC.ReplyMarkup replyMarkup, HashMap params, boolean notify, int scheduleDate, MessageObject.SendAnimationData sendAnimationData, boolean updateStickersOrder) { sendMessage(message, null, null, null, null, null, null, null, null, null, peer, null, replyToMsg, replyToTopMsg, webPage, searchLinks, null, entities, replyMarkup, params, notify, scheduleDate, 0, null, sendAnimationData, updateStickersOrder); } @@ -3224,8 +3239,12 @@ public class SendMessagesHelper extends BaseController implements NotificationCe sendMessage(null, null, null, null, null, null, null, null, poll, null, peer, null, replyToMsg, replyToTopMsg, null, true, null, null, replyMarkup, params, notify, scheduleDate, 0, null, null, false); } - public void sendMessage(TLRPC.TL_game game, long peer, TLRPC.ReplyMarkup replyMarkup, HashMap params, boolean notify, int scheduleDate) { - sendMessage(null, null, null, null, null, null, null, game, null, null, peer, null, null, null, null, true, null, null, replyMarkup, params, notify, scheduleDate, 0, null, null, false); + public void sendMessage(TLRPC.TL_game game, long peer, MessageObject replyToMsg, MessageObject replyToTopMsg, TLRPC.ReplyMarkup replyMarkup, HashMap params, boolean notify, int scheduleDate) { + sendMessage(null, null, null, null, null, null, null, game, null, null, peer, null, replyToMsg, replyToTopMsg, null, true, null, null, replyMarkup, params, notify, scheduleDate, 0, null, null, false); + } + + public void sendMessage(TLRPC.TL_photo photo, String path, long peer, MessageObject replyToMsg, MessageObject replyToTopMsg, String caption, ArrayList entities, TLRPC.ReplyMarkup replyMarkup, HashMap params, boolean notify, int scheduleDate, int ttl, Object parentObject, boolean updateStickersOrder, boolean hasMediaSpoilers) { + sendMessage(null, caption, null, photo, null, null, null, null, null, null, peer, path, replyToMsg, replyToTopMsg, null, true, null, entities, replyMarkup, params, notify, scheduleDate, ttl, parentObject, null, updateStickersOrder, hasMediaSpoilers); } public void sendMessage(TLRPC.TL_photo photo, String path, long peer, MessageObject replyToMsg, MessageObject replyToTopMsg, String caption, ArrayList entities, TLRPC.ReplyMarkup replyMarkup, HashMap params, boolean notify, int scheduleDate, int ttl, Object parentObject, boolean updateStickersOrder) { @@ -3233,6 +3252,10 @@ public class SendMessagesHelper extends BaseController implements NotificationCe } private void sendMessage(String message, String caption, TLRPC.MessageMedia location, TLRPC.TL_photo photo, VideoEditedInfo videoEditedInfo, TLRPC.User user, TLRPC.TL_document document, TLRPC.TL_game game, TLRPC.TL_messageMediaPoll poll, TLRPC.TL_messageMediaInvoice invoice, long peer, String path, MessageObject replyToMsg, MessageObject replyToTopMsg, TLRPC.WebPage webPage, boolean searchLinks, MessageObject retryMessageObject, ArrayList entities, TLRPC.ReplyMarkup replyMarkup, HashMap params, boolean notify, int scheduleDate, int ttl, Object parentObject, MessageObject.SendAnimationData sendAnimationData, boolean updateStickersOreder) { + sendMessage(message, caption, location, photo, videoEditedInfo, user, document, game, poll, invoice, peer, path, replyToMsg, replyToTopMsg, webPage, searchLinks, retryMessageObject, entities, replyMarkup, params, notify, scheduleDate, ttl, parentObject, sendAnimationData, updateStickersOreder, false); + } + + private void sendMessage(String message, String caption, TLRPC.MessageMedia location, TLRPC.TL_photo photo, VideoEditedInfo videoEditedInfo, TLRPC.User user, TLRPC.TL_document document, TLRPC.TL_game game, TLRPC.TL_messageMediaPoll poll, TLRPC.TL_messageMediaInvoice invoice, long peer, String path, MessageObject replyToMsg, MessageObject replyToTopMsg, TLRPC.WebPage webPage, boolean searchLinks, MessageObject retryMessageObject, ArrayList entities, TLRPC.ReplyMarkup replyMarkup, HashMap params, boolean notify, int scheduleDate, int ttl, Object parentObject, MessageObject.SendAnimationData sendAnimationData, boolean updateStickersOreder, boolean hasMediaSpoilers) { if (user != null && user.phone == null) { return; } @@ -3431,6 +3454,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe } newMsg.media = new TLRPC.TL_messageMediaPhoto(); newMsg.media.flags |= 3; + newMsg.media.spoiler = hasMediaSpoilers; if (entities != null) { newMsg.entities = entities; } @@ -3509,6 +3533,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe } newMsg.media = new TLRPC.TL_messageMediaDocument(); newMsg.media.flags |= 3; + newMsg.media.spoiler = hasMediaSpoilers; if (ttl != 0) { newMsg.ttl = newMsg.media.ttl_seconds = ttl; newMsg.media.flags |= 4; @@ -3642,7 +3667,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe } newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_MEDIA; newMsg.dialog_id = peer; - if (replyToMsg != null) { + if (replyToMsg != null && (replyToTopMsg == null || replyToMsg != replyToTopMsg || replyToTopMsg.getId() != 1)) { newMsg.reply_to = new TLRPC.TL_messageReplyHeader(); if (encryptedChat != null && replyToMsg.messageOwner.random_id != 0) { newMsg.reply_to.reply_to_random_id = replyToMsg.messageOwner.random_id; @@ -3651,7 +3676,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe newMsg.flags |= TLRPC.MESSAGE_FLAG_REPLY; } newMsg.reply_to.reply_to_msg_id = replyToMsg.getId(); - if (replyToTopMsg != null && replyToTopMsg != replyToMsg) { + if (replyToTopMsg != null && replyToTopMsg != replyToMsg && replyToTopMsg.getId() != 1) { newMsg.reply_to.reply_to_top_id = replyToTopMsg.getId(); newMsg.reply_to.flags |= 2; if (replyToTopMsg.isTopicMainMessage) { @@ -3754,7 +3779,11 @@ public class SendMessagesHelper extends BaseController implements NotificationCe isFinalGroupMedia = params.get("final") != null; } - newMsgObj = new MessageObject(currentAccount, newMsg, replyToMsg, true, true); + MessageObject reply = replyToMsg; + if (replyToTopMsg != null && replyToTopMsg == reply && replyToTopMsg.getId() == 1) { + reply = null; + } + newMsgObj = new MessageObject(currentAccount, newMsg, reply, true, true); newMsgObj.sendAnimationData = sendAnimationData; newMsgObj.wasJustSent = true; newMsgObj.scheduled = scheduleDate != 0; @@ -3903,6 +3932,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe inputMedia.geo_point._long = location.geo._long; } else if (type == 2 || type == 9 && photo != null) { TLRPC.TL_inputMediaUploadedPhoto uploadedPhoto = new TLRPC.TL_inputMediaUploadedPhoto(); + uploadedPhoto.spoiler = hasMediaSpoilers; if (ttl != 0) { newMsg.ttl = uploadedPhoto.ttl_seconds = ttl; uploadedPhoto.flags |= 2; @@ -3931,6 +3961,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe if (media.id.file_reference == null) { media.id.file_reference = new byte[0]; } + media.spoiler = hasMediaSpoilers; inputMedia = media; } if (delayedMessage == null) { @@ -3952,6 +3983,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe TLRPC.TL_inputMediaUploadedDocument uploadedDocument = new TLRPC.TL_inputMediaUploadedDocument(); uploadedDocument.mime_type = document.mime_type; uploadedDocument.attributes = document.attributes; + uploadedDocument.spoiler = hasMediaSpoilers; if (forceNoSoundVideo || !MessageObject.isRoundVideoDocument(document) && (videoEditedInfo == null || !videoEditedInfo.muted && !videoEditedInfo.roundVideo)) { uploadedDocument.nosound_video = true; if (BuildVars.DEBUG_VERSION) { @@ -3990,6 +4022,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe media.query = params.get("query"); media.flags |= 2; } + media.spoiler = hasMediaSpoilers; inputMedia = media; } if (delayedMessage == null) { @@ -4025,6 +4058,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe TLRPC.InputMedia uploadedMedia; if (originalPath != null || path != null || document.access_hash == 0) { uploadedMedia = new TLRPC.TL_inputMediaUploadedDocument(); + uploadedMedia.spoiler = hasMediaSpoilers; if (ttl != 0) { newMsg.ttl = uploadedMedia.ttl_seconds = ttl; uploadedMedia.flags |= 2; @@ -4055,6 +4089,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe media.query = params.get("query"); media.flags |= 2; } + media.spoiler = hasMediaSpoilers; inputMedia = media; } if (!http && uploadedMedia != null) { @@ -4080,6 +4115,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe TLRPC.TL_inputMediaUploadedDocument uploadedDocument = new TLRPC.TL_inputMediaUploadedDocument(); uploadedDocument.mime_type = document.mime_type; uploadedDocument.attributes = document.attributes; + uploadedDocument.spoiler = hasMediaSpoilers; if (ttl != 0) { newMsg.ttl = uploadedDocument.ttl_seconds = ttl; uploadedDocument.flags |= 2; @@ -4101,6 +4137,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe media.query = params.get("query"); media.flags |= 2; } + media.spoiler = hasMediaSpoilers; inputMedia = media; } delayedMessage = new DelayedMessage(peer); @@ -4965,6 +5002,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe inputMediaPhoto.id.id = messageMedia.photo.id; inputMediaPhoto.id.access_hash = messageMedia.photo.access_hash; inputMediaPhoto.id.file_reference = messageMedia.photo.file_reference; + inputMediaPhoto.spoiler = inputMedia.spoiler; newInputMedia = inputMediaPhoto; if (BuildVars.DEBUG_VERSION) { FileLog.d("set uploaded photo"); @@ -4975,6 +5013,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe inputMediaDocument.id.id = messageMedia.document.id; inputMediaDocument.id.access_hash = messageMedia.document.access_hash; inputMediaDocument.id.file_reference = messageMedia.document.file_reference; + inputMediaDocument.spoiler = inputMedia.spoiler; newInputMedia = inputMediaDocument; if (BuildVars.DEBUG_VERSION) { FileLog.d("set uploaded document"); @@ -5783,7 +5822,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe } if (sentMessage.media instanceof TLRPC.TL_messageMediaPhoto && sentMessage.media.photo != null && newMsg.media instanceof TLRPC.TL_messageMediaPhoto && newMsg.media.photo != null) { if (sentMessage.media.ttl_seconds == 0 && !newMsgObj.scheduled) { - getMessagesStorage().putSentFile(originalPath, sentMessage.media.photo, 0, "sent_" + sentMessage.peer_id.channel_id + "_" + sentMessage.id + "_" + DialogObject.getPeerDialogId(sentMessage.peer_id)); + getMessagesStorage().putSentFile(originalPath, sentMessage.media.photo, 0, "sent_" + sentMessage.peer_id.channel_id + "_" + sentMessage.id + "_" + DialogObject.getPeerDialogId(sentMessage.peer_id) + "_" + MessageObject.TYPE_PHOTO); } if (newMsg.media.photo.sizes.size() == 1 && newMsg.media.photo.sizes.get(0).location instanceof TLRPC.TL_fileLocationUnavailable) { @@ -5843,13 +5882,15 @@ public class SendMessagesHelper extends BaseController implements NotificationCe boolean isVideo = MessageObject.isVideoMessage(sentMessage); if ((isVideo || MessageObject.isGifMessage(sentMessage)) && MessageObject.isGifDocument(sentMessage.media.document) == MessageObject.isGifDocument(newMsg.media.document)) { if (!newMsgObj.scheduled) { - getMessagesStorage().putSentFile(originalPath, sentMessage.media.document, 2, "sent_" + sentMessage.peer_id.channel_id + "_" + sentMessage.id + "_" + DialogObject.getPeerDialogId(sentMessage.peer_id)); + MessageObject messageObject = new MessageObject(currentAccount, sentMessage, false, false); + getMessagesStorage().putSentFile(originalPath, sentMessage.media.document, 2, "sent_" + sentMessage.peer_id.channel_id + "_" + sentMessage.id + "_" + DialogObject.getPeerDialogId(sentMessage.peer_id) + "_" + messageObject.type); } if (isVideo) { sentMessage.attachPath = newMsg.attachPath; } } else if (!MessageObject.isVoiceMessage(sentMessage) && !MessageObject.isRoundVideoMessage(sentMessage) && !newMsgObj.scheduled) { - getMessagesStorage().putSentFile(originalPath, sentMessage.media.document, 1, "sent_" + sentMessage.peer_id.channel_id + "_" + sentMessage.id + "_" + DialogObject.getPeerDialogId(sentMessage.peer_id)); + MessageObject messageObject = new MessageObject(currentAccount, sentMessage, false, false); + getMessagesStorage().putSentFile(originalPath, sentMessage.media.document, 1, "sent_" + sentMessage.peer_id.channel_id + "_" + sentMessage.id + "_" + DialogObject.getPeerDialogId(sentMessage.peer_id) + "_" + messageObject.type); } } @@ -5974,7 +6015,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe return delayedMessages.get(location); } - protected long getNextRandomId() { + public long getNextRandomId() { long val = 0; while (val == 0) { val = Utilities.random.nextLong(); @@ -6477,7 +6518,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe AndroidUtilities.runOnUIThread(() -> { if (editingMessageObject != null) { - accountInstance.getSendMessagesHelper().editMessage(editingMessageObject, null, null, documentFinal, pathFinal, params, false, parentFinal); + accountInstance.getSendMessagesHelper().editMessage(editingMessageObject, null, null, documentFinal, pathFinal, params, false, false, parentFinal); } else { accountInstance.getSendMessagesHelper().sendMessage(documentFinal, null, pathFinal, dialogId, replyToMsg, replyToTopMsg, captionFinal, entities, null, params, notify, scheduleDate, 0, parentFinal, null, false); } @@ -6590,9 +6631,9 @@ public class SendMessagesHelper extends BaseController implements NotificationCe } AndroidUtilities.runOnUIThread(() -> { if (editingMessageObject != null) { - accountInstance.getSendMessagesHelper().editMessage(editingMessageObject, null, null, documentFinal, messageObject.messageOwner.attachPath, params, false, parentFinal); + accountInstance.getSendMessagesHelper().editMessage(editingMessageObject, null, null, documentFinal, messageObject.messageOwner.attachPath, params, false, false, parentFinal); } else { - accountInstance.getSendMessagesHelper().sendMessage(documentFinal, null, messageObject.messageOwner.attachPath, dialogId, replyToMsg, replyToTopMsg, captionFinal, entities, null, params, notify, scheduleDate, 0, parentFinal, null, false); + accountInstance.getSendMessagesHelper().sendMessage(documentFinal, null, messageObject.messageOwner.attachPath, dialogId, replyToMsg, replyToTopMsg, captionFinal, entities, null, params, notify, scheduleDate, 0, parentFinal, null, false, false); } }); } @@ -6998,7 +7039,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe } else if (finalPhoto != null) { accountInstance.getSendMessagesHelper().sendMessage(finalPhoto, result.content != null ? result.content.url : null, dialogId, replyToMsg, replyToTopMsg, result.send_message.message, result.send_message.entities, result.send_message.reply_markup, params, notify, scheduleDate, 0, result, false); } else if (finalGame != null) { - accountInstance.getSendMessagesHelper().sendMessage(finalGame, dialogId, result.send_message.reply_markup, params, notify, scheduleDate); + accountInstance.getSendMessagesHelper().sendMessage(finalGame, dialogId, replyToMsg, replyToTopMsg, result.send_message.reply_markup, params, notify, scheduleDate); } }); }).run(); @@ -7430,9 +7471,9 @@ public class SendMessagesHelper extends BaseController implements NotificationCe } AndroidUtilities.runOnUIThread(() -> { if (editingMessageObject != null) { - accountInstance.getSendMessagesHelper().editMessage(editingMessageObject, null, null, documentFinal, pathFinal, params, false, parentFinal); + accountInstance.getSendMessagesHelper().editMessage(editingMessageObject, null, null, documentFinal, pathFinal, params, false, info.hasMediaSpoilers, parentFinal); } else { - accountInstance.getSendMessagesHelper().sendMessage(documentFinal, null, pathFinal, dialogId, replyToMsg, replyToTopMsg, info.caption, info.entities, null, params, notify, scheduleDate, 0, parentFinal, null, false); + accountInstance.getSendMessagesHelper().sendMessage(documentFinal, null, pathFinal, dialogId, replyToMsg, replyToTopMsg, info.caption, info.entities, null, params, notify, scheduleDate, 0, parentFinal, null, false, info.hasMediaSpoilers); } }); } else { @@ -7501,9 +7542,9 @@ public class SendMessagesHelper extends BaseController implements NotificationCe } AndroidUtilities.runOnUIThread(() -> { if (editingMessageObject != null) { - accountInstance.getSendMessagesHelper().editMessage(editingMessageObject, photoFinal, null, null, needDownloadHttpFinal ? info.searchImage.imageUrl : null, params, false, parentFinal); + accountInstance.getSendMessagesHelper().editMessage(editingMessageObject, photoFinal, null, null, needDownloadHttpFinal ? info.searchImage.imageUrl : null, params, false, info.hasMediaSpoilers, parentFinal); } else { - accountInstance.getSendMessagesHelper().sendMessage(photoFinal, needDownloadHttpFinal ? info.searchImage.imageUrl : null, dialogId, replyToMsg, replyToTopMsg, info.caption, info.entities, null, params, notify, scheduleDate, info.ttl, parentFinal, false); + accountInstance.getSendMessagesHelper().sendMessage(photoFinal, needDownloadHttpFinal ? info.searchImage.imageUrl : null, dialogId, replyToMsg, replyToTopMsg, info.caption, info.entities, null, params, notify, scheduleDate, info.ttl, parentFinal, false, info.hasMediaSpoilers); } }); } @@ -7681,9 +7722,9 @@ public class SendMessagesHelper extends BaseController implements NotificationCe ImageLoader.getInstance().putImageToCache(new BitmapDrawable(thumbFinal), thumbKeyFinal, false); } if (editingMessageObject != null) { - accountInstance.getSendMessagesHelper().editMessage(editingMessageObject, null, videoEditedInfo, videoFinal, finalPath, params, false, parentFinal); + accountInstance.getSendMessagesHelper().editMessage(editingMessageObject, null, videoEditedInfo, videoFinal, finalPath, params, false, info.hasMediaSpoilers, parentFinal); } else { - accountInstance.getSendMessagesHelper().sendMessage(videoFinal, videoEditedInfo, finalPath, dialogId, replyToMsg, replyToTopMsg, info.caption, info.entities, null, params, notify, scheduleDate, info.ttl, parentFinal, null, false); + accountInstance.getSendMessagesHelper().sendMessage(videoFinal, videoEditedInfo, finalPath, dialogId, replyToMsg, replyToTopMsg, info.caption, info.entities, null, params, notify, scheduleDate, info.ttl, parentFinal, null, false, info.hasMediaSpoilers); } }); } else { @@ -7878,9 +7919,9 @@ public class SendMessagesHelper extends BaseController implements NotificationCe ImageLoader.getInstance().putImageToCache(new BitmapDrawable(bitmapFinal[0]), keyFinal[0], false); } if (editingMessageObject != null) { - accountInstance.getSendMessagesHelper().editMessage(editingMessageObject, photoFinal, null, null, null, params, false, parentFinal); + accountInstance.getSendMessagesHelper().editMessage(editingMessageObject, photoFinal, null, null, null, params, false, info.hasMediaSpoilers, parentFinal); } else { - accountInstance.getSendMessagesHelper().sendMessage(photoFinal, null, dialogId, replyToMsg, replyToTopMsg, info.caption, info.entities, null, params, notify, scheduleDate, info.ttl, parentFinal, updateStikcersOrder); + accountInstance.getSendMessagesHelper().sendMessage(photoFinal, null, dialogId, replyToMsg, replyToTopMsg, info.caption, info.entities, null, params, notify, scheduleDate, info.ttl, parentFinal, updateStikcersOrder, info.hasMediaSpoilers); } }); } else { @@ -8178,7 +8219,7 @@ public class SendMessagesHelper extends BaseController implements NotificationCe } @UiThread - public static void prepareSendingVideo(AccountInstance accountInstance, String videoPath, VideoEditedInfo info, long dialogId, MessageObject replyToMsg, MessageObject replyToTopMsg, CharSequence caption, ArrayList entities, int ttl, MessageObject editingMessageObject, boolean notify, int scheduleDate, boolean forceDocument) { + public static void prepareSendingVideo(AccountInstance accountInstance, String videoPath, VideoEditedInfo info, long dialogId, MessageObject replyToMsg, MessageObject replyToTopMsg, CharSequence caption, ArrayList entities, int ttl, MessageObject editingMessageObject, boolean notify, int scheduleDate, boolean forceDocument, boolean hasMediaSpoilers) { if (videoPath == null || videoPath.length() == 0) { return; } @@ -8323,9 +8364,9 @@ public class SendMessagesHelper extends BaseController implements NotificationCe ImageLoader.getInstance().putImageToCache(new BitmapDrawable(thumbFinal), thumbKeyFinal, false); } if (editingMessageObject != null) { - accountInstance.getSendMessagesHelper().editMessage(editingMessageObject, null, videoEditedInfo, videoFinal, finalPath, params, false, parentFinal); + accountInstance.getSendMessagesHelper().editMessage(editingMessageObject, null, videoEditedInfo, videoFinal, finalPath, params, false, hasMediaSpoilers, parentFinal); } else { - accountInstance.getSendMessagesHelper().sendMessage(videoFinal, videoEditedInfo, finalPath, dialogId, replyToMsg, replyToTopMsg, captionFinal, entities, null, params, notify, scheduleDate, ttl, parentFinal, null, false); + accountInstance.getSendMessagesHelper().sendMessage(videoFinal, videoEditedInfo, finalPath, dialogId, replyToMsg, replyToTopMsg, captionFinal, entities, null, params, notify, scheduleDate, ttl, parentFinal, null, false, hasMediaSpoilers); } }); } else { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java b/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java index 9398ccaff..172354075 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java @@ -28,13 +28,17 @@ import org.json.JSONObject; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.SerializedData; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.Components.AnimatedEmojiDrawable; import org.telegram.ui.Components.SwipeGestureSettingsView; import java.io.File; import java.io.RandomAccessFile; +import java.io.UnsupportedEncodingException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.net.URLEncoder; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Locale; @@ -43,6 +47,17 @@ public class SharedConfig { public final static int PASSCODE_TYPE_PIN = 0, PASSCODE_TYPE_PASSWORD = 1; + public static LiteMode getLiteMode() { + if (liteMode == null) { + liteMode = new LiteMode(); + } + return liteMode; + } + + public static boolean loopStickers() { + return loopStickers && !getLiteMode().enabled; + } + @Retention(RetentionPolicy.SOURCE) @IntDef({ PASSCODE_TYPE_PIN, @@ -85,7 +100,7 @@ public class SharedConfig { public static int suggestStickers; public static boolean suggestAnimatedEmoji; public static boolean loopStickers; - public static int keepMedia = 2; + public static int keepMedia = CacheByChatsController.KEEP_MEDIA_ONE_MONTH; //deprecated public static int lastKeepMediaCheckTime; public static int lastLogsCheckTime; public static int searchMessagesAsListHintShows; @@ -170,6 +185,7 @@ public class SharedConfig { public static boolean dontAskManageStorage; public static boolean isFloatingDebugActive; + public static LiteMode liteMode; static { loadConfig(); @@ -208,6 +224,23 @@ public class SharedConfig { secret = ""; } } + + public String getLink() { + StringBuilder url = new StringBuilder(!TextUtils.isEmpty(secret) ? "https://t.me/proxy?" : "https://t.me/socks?"); + try { + url.append("server=").append(URLEncoder.encode(address, "UTF-8")).append("&").append("port=").append(port); + if (!TextUtils.isEmpty(username)) { + url.append("&user=").append(URLEncoder.encode(username, "UTF-8")); + } + if (!TextUtils.isEmpty(password)) { + url.append("&pass=").append(URLEncoder.encode(password, "UTF-8")); + } + if (!TextUtils.isEmpty(secret)) { + url.append("&secret=").append(URLEncoder.encode(secret, "UTF-8")); + } + } catch (UnsupportedEncodingException ignored) {} + return url.toString(); + } } public static ArrayList proxyList = new ArrayList<>(); @@ -411,7 +444,7 @@ public class SharedConfig { distanceSystemType = preferences.getInt("distanceSystemType", 0); devicePerformanceClass = preferences.getInt("devicePerformanceClass", -1); loopStickers = preferences.getBoolean("loopStickers", true); - keepMedia = preferences.getInt("keep_media", 2); + keepMedia = preferences.getInt("keep_media", CacheByChatsController.KEEP_MEDIA_ONE_MONTH); noStatusBar = preferences.getBoolean("noStatusBar", true); forceRtmpStream = preferences.getBoolean("forceRtmpStream", false); debugWebView = preferences.getBoolean("debugWebView", false); @@ -733,34 +766,143 @@ public class SharedConfig { public static void checkKeepMedia() { int time = (int) (System.currentTimeMillis() / 1000); - if (Math.abs(time - lastKeepMediaCheckTime) < 60 * 60) { + if (!BuildVars.DEBUG_PRIVATE_VERSION && Math.abs(time - lastKeepMediaCheckTime) < 60 * 60) { return; } lastKeepMediaCheckTime = time; File cacheDir = FileLoader.checkDirectory(FileLoader.MEDIA_DIR_CACHE); + Utilities.cacheClearQueue.postRunnable(() -> { - if (keepMedia != 2) { - int days; - if (keepMedia == 0) { - days = 7; - } else if (keepMedia == 1) { - days = 30; - } else { - days = 3; + boolean hasExceptions = false; + ArrayList cacheByChatsControllers = new ArrayList<>(); + for (int account = 0; account < UserConfig.MAX_ACCOUNT_COUNT; account++) { + if (UserConfig.getInstance(account).isClientActivated()) { + CacheByChatsController cacheByChatsController = UserConfig.getInstance(account).getMessagesController().getCacheByChatsController(); + cacheByChatsControllers.add(cacheByChatsController); + if (cacheByChatsController.getKeepMediaExceptionsByDialogs().size() > 0) { + hasExceptions = true; + } } - long currentTime = time - 60 * 60 * 24 * days; + } + + int[] keepMediaByTypes = new int[3]; + boolean allKeepMediaTypesForever = true; + long keepMediaMinSeconds = Long.MAX_VALUE; + for (int i = 0; i < 3; i++) { + keepMediaByTypes[i] = SharedConfig.getPreferences().getInt("keep_media_type_" + i, CacheByChatsController.getDefault(i)); + if (keepMediaByTypes[i] != CacheByChatsController.KEEP_MEDIA_FOREVER) { + allKeepMediaTypesForever = false; + } + long days = CacheByChatsController.getDaysInSeconds(keepMediaByTypes[i]); + if (days < keepMediaMinSeconds) { + keepMediaMinSeconds = days; + } + } + if (hasExceptions) { + allKeepMediaTypesForever = false; + } + if (!allKeepMediaTypesForever) { + //long currentTime = time - 60 * 60 * 24 * days; final SparseArray paths = ImageLoader.getInstance().createMediaPaths(); for (int a = 0; a < paths.size(); a++) { + boolean isCacheDir = false; if (paths.keyAt(a) == FileLoader.MEDIA_DIR_CACHE) { - continue; + isCacheDir = true; } + File dir = paths.valueAt(a); try { - Utilities.clearDir(paths.valueAt(a).getAbsolutePath(), 0, currentTime, false); + File[] files = dir.listFiles(); + ArrayList keepMediaFiles = new ArrayList<>(); + for (int i = 0; i < files.length; i++) { + keepMediaFiles.add(new CacheByChatsController.KeepMediaFile(files[i])); + } + for (int i = 0; i < cacheByChatsControllers.size(); i++) { + cacheByChatsControllers.get(i).lookupFiles(keepMediaFiles); + } + for (int i = 0; i < keepMediaFiles.size(); i++) { + CacheByChatsController.KeepMediaFile file = keepMediaFiles.get(i); + if (file.keepMedia == CacheByChatsController.KEEP_MEDIA_FOREVER) { + continue; + } + long seconds; + boolean isException = false; + if (file.keepMedia >= 0) { + isException = true; + seconds = CacheByChatsController.getDaysInSeconds(file.keepMedia); + } else if (file.dialogType >= 0) { + seconds = CacheByChatsController.getDaysInSeconds(keepMediaByTypes[file.dialogType]); + } else if (isCacheDir) { + continue; + } else { + seconds = keepMediaMinSeconds; + } + if (seconds == Long.MAX_VALUE) { + continue; + } + long lastUsageTime = Utilities.getLastUsageFileTime(file.file.getAbsolutePath()); + long timeLocal = time - seconds; + boolean needDelete = lastUsageTime < timeLocal; + if (needDelete) { + try { + file.file.delete(); + } catch (Exception exception) { + FileLog.e(exception); + } + } + } } catch (Throwable e) { FileLog.e(e); } } } + + int maxCacheGb = SharedConfig.getPreferences().getInt("cache_limit", Integer.MAX_VALUE); + if (maxCacheGb != Integer.MAX_VALUE) { + long maxCacheSize; + if (maxCacheGb == 1) { + maxCacheSize = 1024L * 1024L * 300L; + } else { + maxCacheSize = maxCacheGb * 1024L * 1024L * 1000L; + } + final SparseArray paths = ImageLoader.getInstance().createMediaPaths(); + long totalSize = 0; + for (int a = 0; a < paths.size(); a++) { + totalSize += Utilities.getDirSize(paths.valueAt(a).getAbsolutePath(), 0, true); + } + if (totalSize > maxCacheSize) { + ArrayList allFiles = new ArrayList<>(); + for (int a = 0; a < paths.size(); a++) { + File dir = paths.valueAt(a); + fillFilesRecursive(dir, allFiles); + } + Collections.sort(allFiles, (o1, o2) -> { + if (o2.lastUsageDate > o1.lastUsageDate) { + return -1; + } else if (o2.lastUsageDate < o1.lastUsageDate) { + return 1; + } + return 0; + }); + for (int i = 0; i < allFiles.size(); i++) { + long size = allFiles.get(i).file.length(); + totalSize -= size; + try { + allFiles.get(i).file.delete(); + } catch (Exception e) { + + } + + if (totalSize < maxCacheSize) { + break; + } + } + } + + } + + + //TODO now every day generating cache for reactions and cleared it after one day -\_(-_-)_/- + //need fix File stickersPath = new File(cacheDir, "acache"); if (stickersPath.exists()) { long currentTime = time - 60 * 60 * 24; @@ -770,13 +912,32 @@ public class SharedConfig { FileLog.e(e); } } - SharedPreferences preferences = MessagesController.getGlobalMainSettings(); - SharedPreferences.Editor editor = preferences.edit(); - editor.putInt("lastKeepMediaCheckTime", lastKeepMediaCheckTime); - editor.commit(); + MessagesController.getGlobalMainSettings().edit() + .putInt("lastKeepMediaCheckTime", lastKeepMediaCheckTime) + .apply(); }); } + private static void fillFilesRecursive(final File fromFolder, ArrayList fileInfoList) { + if (fromFolder == null) { + return; + } + File[] files = fromFolder.listFiles(); + if (files == null) { + return; + } + for (final File fileEntry : files) { + if (fileEntry.isDirectory()) { + fillFilesRecursive(fileEntry, fileInfoList); + } else { + if (fileEntry.getName().equals(".nomedia")) { + continue; + } + fileInfoList.add(new FileInfoInternal(fileEntry)); + } + } + } + public static void toggleDisableVoiceAudioEffects() { disableVoiceAudioEffects = !disableVoiceAudioEffects; SharedPreferences preferences = MessagesController.getGlobalMainSettings(); @@ -1337,4 +1498,55 @@ public class SharedConfig { } return animationsEnabled; } + + public static SharedPreferences getPreferences() { + return ApplicationLoader.applicationContext.getSharedPreferences("userconfing", Context.MODE_PRIVATE); + } + + private static class FileInfoInternal { + final File file; + final long lastUsageDate; + + private FileInfoInternal(File file) { + this.file = file; + this.lastUsageDate = Utilities.getLastUsageFileTime(file.getAbsolutePath()); + } + } + + + public static class LiteMode { + + private boolean enabled; + + LiteMode() { + loadPreference(); + } + + public boolean enabled() { + return enabled; + } + + public void toggleMode() { + enabled = !enabled; + savePreference(); + AnimatedEmojiDrawable.lightModeChanged(); + } + + private void loadPreference() { + int flags = MessagesController.getGlobalMainSettings().getInt("light_mode", getDevicePerformanceClass() == PERFORMANCE_CLASS_LOW ? 1 : 0) ; + enabled = (flags & 1) != 0; + } + + public void savePreference() { + int flags = 0; + if (enabled) { + flags |= 1; + } + MessagesController.getGlobalMainSettings().edit().putInt("light_mode", flags).apply(); + } + + public boolean animatedEmojiEnabled() { + return !enabled; + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SvgHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/SvgHelper.java index 5682fb5de..ebae29326 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SvgHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SvgHelper.java @@ -41,6 +41,8 @@ import android.graphics.drawable.Drawable; import android.os.Build; import android.util.SparseArray; +import androidx.core.graphics.ColorUtils; + import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.DrawingInBackgroundThreadDrawable; import org.xml.sax.Attributes; @@ -164,7 +166,7 @@ public class SvgHelper { } float scale = getScale((int) w, (int) h); - if (placeholderGradient[threadIndex] != null && gradientWidth > 0) { + if (placeholderGradient[threadIndex] != null && gradientWidth > 0 && !SharedConfig.getLiteMode().enabled()) { if (drawInBackground) { long dt = time - lastUpdateTime; if (dt > 64) { @@ -324,6 +326,22 @@ public class SvgHelper { currentColorKey = colorKey; currentColor[index] = color; gradientWidth = AndroidUtilities.displaySize.x * 2; + if (SharedConfig.getLiteMode().enabled()) { + int color2 = ColorUtils.setAlphaComponent(currentColor[index], 70); + if (drawInBackground) { + if (backgroundPaint == null) { + backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + } + backgroundPaint.setShader(null); + backgroundPaint.setColor(color2); + } else { + for (Paint paint : paints.values()) { + paint.setShader(null); + paint.setColor(color2); + } + } + return; + } float w = AndroidUtilities.dp(180) / gradientWidth; color = Color.argb((int) (Color.alpha(color) / 2 * colorAlpha), Color.red(color), Color.green(color), Color.blue(color)); float centerX = (1.0f - w) / 2; @@ -452,7 +470,7 @@ public class SvgHelper { } } - public static SvgDrawable getDrawable(int resId, int color) { + public static SvgDrawable getDrawable(int resId, Integer color) { try { SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser sp = spf.newSAXParser(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/UserObject.java b/TMessagesProj/src/main/java/org/telegram/messenger/UserObject.java index 04bb0c2c7..af7af8a8f 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/UserObject.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/UserObject.java @@ -65,6 +65,24 @@ public class UserObject { return getPublicUsername(user, false); } + public static boolean hasPublicUsername(TLRPC.User user, String username) { + if (user == null || username == null) { + return false; + } + if (username.equalsIgnoreCase(user.username)) { + return true; + } + if (user.usernames != null) { + for (int i = 0; i < user.usernames.size(); ++i) { + TLRPC.TL_username u = user.usernames.get(i); + if (u != null && u.active && username.equalsIgnoreCase(u.username)) { + return true; + } + } + } + return false; + } + public static String getFirstName(TLRPC.User user) { return getFirstName(user, true); } @@ -89,4 +107,8 @@ public class UserObject { public static TLRPC.UserProfilePhoto getPhoto(TLRPC.User user) { return hasPhoto(user) ? user.photo : null; } + + public static boolean hasFallbackPhoto(TLRPC.UserFull userInfo) { + return userInfo != null && userInfo.fallback_photo != null && !(userInfo.fallback_photo instanceof TLRPC.TL_photoEmpty); + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/Utilities.java b/TMessagesProj/src/main/java/org/telegram/messenger/Utilities.java index a95f6f4c1..8b140259f 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/Utilities.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/Utilities.java @@ -73,6 +73,7 @@ public class Utilities { public native static String readlink(String path); public native static String readlinkFd(int fd); public native static long getDirSize(String path, int docType, boolean subdirs); + public native static long getLastUsageFileTime(String path); public native static void clearDir(String path, int docType, long time, boolean subdirs); private native static int pbkdf2(byte[] password, byte[] salt, byte[] dst, int iterations); public static native void stackBlurBitmap(Bitmap bitmap, int radius); @@ -80,6 +81,32 @@ public class Utilities { public static native int saveProgressiveJpeg(Bitmap bitmap, int width, int height, int stride, int quality, String path); public static native void generateGradient(Bitmap bitmap, boolean unpin, int phase, float progress, int width, int height, int stride, int[] colors); + public static Bitmap stackBlurBitmapMax(Bitmap bitmap) { + int w = AndroidUtilities.dp(20); + int h = (int) (AndroidUtilities.dp(20) * (float) bitmap.getHeight() / bitmap.getWidth()); + Bitmap scaledBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(scaledBitmap); + canvas.save(); + canvas.scale((float) scaledBitmap.getWidth() / bitmap.getWidth(), (float) scaledBitmap.getHeight() / bitmap.getHeight()); + canvas.drawBitmap(bitmap, 0, 0, null); + canvas.restore(); + Utilities.stackBlurBitmap(scaledBitmap, Math.max(10, Math.max(w, h) / 150)); + return scaledBitmap; + } + + public static Bitmap stackBlurBitmapWithScaleFactor(Bitmap bitmap, float scaleFactor) { + int w = (int) Math.max(AndroidUtilities.dp(20), bitmap.getWidth() / scaleFactor); + int h = (int) Math.max(AndroidUtilities.dp(20) * (float) bitmap.getHeight() / bitmap.getWidth(), bitmap.getHeight() / scaleFactor); + Bitmap scaledBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(scaledBitmap); + canvas.save(); + canvas.scale((float) scaledBitmap.getWidth() / bitmap.getWidth(), (float) scaledBitmap.getHeight() / bitmap.getHeight()); + canvas.drawBitmap(bitmap, 0, 0, null); + canvas.restore(); + Utilities.stackBlurBitmap(scaledBitmap, Math.max(10, Math.max(w, h) / 150)); + return scaledBitmap; + } + public static Bitmap blurWallpaper(Bitmap src) { if (src == null) { return null; @@ -466,10 +493,27 @@ public class Utilities { return sb.toString(); } + public static String getExtension(String fileName) { + int idx = fileName.lastIndexOf('.'); + String ext = null; + if (idx != -1) { + ext = fileName.substring(idx + 1); + } + if (ext == null) { + return null; + } + ext = ext.toUpperCase(); + return ext; + } + public static interface Callback { public void run(T arg); } + public static interface Callback2 { + public void run(T arg, T2 arg2); + } + public static Value getOrDefault(HashMap map, Key key, Value defaultValue) { Value v = map.get(key); if (v == null) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/VideoEditedInfo.java b/TMessagesProj/src/main/java/org/telegram/messenger/VideoEditedInfo.java index 076d12bad..d44203a04 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/VideoEditedInfo.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/VideoEditedInfo.java @@ -9,11 +9,16 @@ package org.telegram.messenger; import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.text.TextUtils; +import android.util.Log; import android.view.View; +import org.telegram.tgnet.AbstractSerializedData; import org.telegram.tgnet.SerializedData; import org.telegram.tgnet.TLRPC; import org.telegram.ui.Components.AnimatedFileDrawable; +import org.telegram.ui.Components.Paint.PaintTypeface; import org.telegram.ui.Components.PhotoFilterView; import org.telegram.ui.Components.Point; @@ -57,6 +62,32 @@ public class VideoEditedInfo { public boolean needUpdateProgress = false; public boolean shouldLimitFps = true; + public static class EmojiEntity extends TLRPC.TL_messageEntityCustomEmoji { + + public String documentAbsolutePath; + + @Override + public void readParams(AbstractSerializedData stream, boolean exception) { + super.readParams(stream, exception); + boolean hasPath = stream.readBool(exception); + if (hasPath) { + documentAbsolutePath = stream.readString(exception); + } + if (TextUtils.isEmpty(documentAbsolutePath)) { + documentAbsolutePath = null; + } + } + + @Override + public void serializeToStream(AbstractSerializedData stream) { + super.serializeToStream(stream); + stream.writeBool(!TextUtils.isEmpty(documentAbsolutePath)); + if (!TextUtils.isEmpty(documentAbsolutePath)) { + stream.writeString(documentAbsolutePath); + } + } + } + public static class MediaEntity { public byte type; public byte subType; @@ -66,8 +97,11 @@ public class VideoEditedInfo { public float width; public float height; public String text; + public ArrayList entities = new ArrayList<>(); public int color; public int fontSize; + public PaintTypeface textTypeface; + public int textAlign; public int viewWidth; public int viewHeight; @@ -87,6 +121,7 @@ public class VideoEditedInfo { public Bitmap bitmap; public View view; + public Canvas canvas; public AnimatedFileDrawable animatedFileDrawable; public MediaEntity() { @@ -102,10 +137,18 @@ public class VideoEditedInfo { width = data.readFloat(false); height = data.readFloat(false); text = data.readString(false); + int count = data.readInt32(false); + for (int i = 0; i < count; ++i) { + EmojiEntity entity = new EmojiEntity(); + data.readInt32(false); + entity.readParams(data, false); + entities.add(entity); + } color = data.readInt32(false); fontSize = data.readInt32(false); viewWidth = data.readInt32(false); viewHeight = data.readInt32(false); + textAlign = data.readInt32(false); } private void serializeTo(SerializedData data) { @@ -117,10 +160,15 @@ public class VideoEditedInfo { data.writeFloat(width); data.writeFloat(height); data.writeString(text); + data.writeInt32(entities.size()); + for (int i = 0; i < entities.size(); ++i) { + entities.get(i).serializeToStream(data); + } data.writeInt32(color); data.writeInt32(fontSize); data.writeInt32(viewWidth); data.writeInt32(viewHeight); + data.writeInt32(textAlign); } public MediaEntity copy() { @@ -133,6 +181,7 @@ public class VideoEditedInfo { entity.width = width; entity.height = height; entity.text = text; + entity.entities.addAll(entities); entity.color = color; entity.fontSize = fontSize; entity.viewWidth = viewWidth; @@ -142,6 +191,7 @@ public class VideoEditedInfo { entity.textViewHeight = textViewHeight; entity.textViewX = textViewX; entity.textViewY = textViewY; + entity.textAlign = textAlign; return entity; } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/browser/Browser.java b/TMessagesProj/src/main/java/org/telegram/messenger/browser/Browser.java index 84a6a8311..c53c09743 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/browser/Browser.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/browser/Browser.java @@ -213,7 +213,33 @@ public class Browser { ); } + public static class Progress { + public void init() {} + public void end() { + end(false); + } + public void end(boolean replaced) {} + + private Runnable onCancelListener; + public void cancel() { + cancel(false); + } + public void cancel(boolean replaced) { + if (onCancelListener != null) { + onCancelListener.run(); + } + end(replaced); + } + public void onCancel(Runnable onCancelListener) { + this.onCancelListener = onCancelListener; + } + } + public static void openUrl(final Context context, Uri uri, final boolean allowCustom, boolean tryTelegraph) { + openUrl(context, uri, allowCustom, tryTelegraph, null); + } + + public static void openUrl(final Context context, Uri uri, final boolean allowCustom, boolean tryTelegraph, Progress inCaseLoading) { if (context == null || uri == null) { return; } @@ -224,18 +250,22 @@ public class Browser { try { String host = uri.getHost().toLowerCase(); if (isTelegraphUrl(host, true) || uri.toString().toLowerCase().contains("telegram.org/faq") || uri.toString().toLowerCase().contains("telegram.org/privacy")) { - final AlertDialog[] progressDialog = new AlertDialog[]{new AlertDialog(context, 3)}; + final AlertDialog[] progressDialog = new AlertDialog[] { + new AlertDialog(context, AlertDialog.ALERT_TYPE_SPINNER) + }; Uri finalUri = uri; TLRPC.TL_messages_getWebPagePreview req = new TLRPC.TL_messages_getWebPagePreview(); req.message = uri.toString(); final int reqId = ConnectionsManager.getInstance(UserConfig.selectedAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { - try { - progressDialog[0].dismiss(); - } catch (Throwable ignore) { - + if (inCaseLoading != null) { + inCaseLoading.end(); + } else { + try { + progressDialog[0].dismiss(); + } catch (Throwable ignore) {} + progressDialog[0] = null; } - progressDialog[0] = null; boolean ok = false; if (response instanceof TLRPC.TL_messageMediaWebPage) { @@ -249,17 +279,19 @@ public class Browser { openUrl(context, finalUri, allowCustom, false); } })); - AndroidUtilities.runOnUIThread(() -> { - if (progressDialog[0] == null) { - return; - } - try { - progressDialog[0].setOnCancelListener(dialog -> ConnectionsManager.getInstance(UserConfig.selectedAccount).cancelRequest(reqId, true)); - progressDialog[0].show(); - } catch (Exception ignore) { - - } - }, 1000); + if (inCaseLoading != null) { + inCaseLoading.init(); + } else { + AndroidUtilities.runOnUIThread(() -> { + if (progressDialog[0] == null) { + return; + } + try { + progressDialog[0].setOnCancelListener(dialog -> ConnectionsManager.getInstance(UserConfig.selectedAccount).cancelRequest(reqId, true)); + progressDialog[0].show(); + } catch (Exception ignore) {} + }, 1000); + } return; } } catch (Exception ignore) { @@ -372,7 +404,7 @@ public class Browser { intent.putExtra(android.provider.Browser.EXTRA_CREATE_NEW_TAB, true); intent.putExtra(android.provider.Browser.EXTRA_APPLICATION_ID, context.getPackageName()); if (internalUri && context instanceof LaunchActivity) { - ((LaunchActivity) context).onNewIntent(intent); + ((LaunchActivity) context).onNewIntent(intent, inCaseLoading); } else { context.startActivity(intent); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/utils/BitmapsCache.java b/TMessagesProj/src/main/java/org/telegram/messenger/utils/BitmapsCache.java index 3aafb812e..1078cb10e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/utils/BitmapsCache.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/utils/BitmapsCache.java @@ -471,6 +471,7 @@ public class BitmapsCache { } options.inBitmap = bitmap; BitmapFactory.decodeByteArray(bufferTmp, 0, selectedFrame.frameSize, options); + options.inBitmap = null; return FRAME_RESULT_OK; } catch (FileNotFoundException e) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/utils/PhotoUtilities.java b/TMessagesProj/src/main/java/org/telegram/messenger/utils/PhotoUtilities.java new file mode 100644 index 000000000..8636c4b4e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/utils/PhotoUtilities.java @@ -0,0 +1,148 @@ +package org.telegram.messenger.utils; + +import android.os.Bundle; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.FileLoader; +import org.telegram.messenger.ImageLoader; +import org.telegram.messenger.ImageLocation; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MediaController; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.R; +import org.telegram.messenger.UserConfig; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.INavigationLayout; +import org.telegram.ui.Components.BulletinFactory; +import org.telegram.ui.Components.ImageUpdater; +import org.telegram.ui.ProfileActivity; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; + +public class PhotoUtilities { + + public static void applyPhotoToUser(TLRPC.Photo photo, TLRPC.User user, boolean personal) { + ArrayList sizes = photo.sizes; + TLRPC.PhotoSize smallSize2 = FileLoader.getClosestPhotoSizeWithSize(sizes, 100); + TLRPC.PhotoSize bigSize2 = FileLoader.getClosestPhotoSizeWithSize(sizes, 1000); + + user.flags |= 32; + user.photo = new TLRPC.TL_userProfilePhoto(); + user.photo.personal = personal; + user.photo.photo_id = photo.id; + user.photo.has_video = photo.video_sizes != null && photo.video_sizes.size() > 0; + if (smallSize2 != null) { + user.photo.photo_small = smallSize2.location; + } + if (bigSize2 != null) { + user.photo.photo_big = bigSize2.location; + } + } + + public static void setImageAsAvatar(MediaController.PhotoEntry entry, BaseFragment baseFragment, Runnable onDone) { + INavigationLayout layout = baseFragment.getParentLayout(); + int currentAccount = baseFragment.getCurrentAccount(); + + ImageUpdater imageUpdater = new ImageUpdater(true); + imageUpdater.parentFragment = baseFragment; + imageUpdater.processEntry(entry); + imageUpdater.setDelegate((photo, video, videoStartTimestamp, videoPath, bigSize, smallSize, isVideo) -> AndroidUtilities.runOnUIThread(() -> { + TLRPC.TL_photos_uploadProfilePhoto req = new TLRPC.TL_photos_uploadProfilePhoto(); + if (photo != null) { + req.file = photo; + req.flags |= 1; + } + if (video != null) { + req.video = video; + req.flags |= 2; + req.video_start_ts = videoStartTimestamp; + req.flags |= 4; + } + ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + if (response instanceof TLRPC.TL_photos_photo) { + TLRPC.TL_photos_photo photos_photo = (TLRPC.TL_photos_photo) response; + MessagesController.getInstance(currentAccount).putUsers(photos_photo.users, false); + TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(UserConfig.getInstance(currentAccount).clientUserId); + if (photos_photo.photo instanceof TLRPC.TL_photo) { + if (user != null) { + TLRPC.PhotoSize smallSize2 = FileLoader.getClosestPhotoSizeWithSize(photos_photo.photo.sizes, 100); + TLRPC.PhotoSize bigSize2 = FileLoader.getClosestPhotoSizeWithSize(photos_photo.photo.sizes, 1000); + if (smallSize2 != null && smallSize != null && smallSize.location != null) { + File destFile = FileLoader.getInstance(currentAccount).getPathToAttach(smallSize2, true); + File src = FileLoader.getInstance(currentAccount).getPathToAttach(smallSize.location, true); + src.renameTo(destFile); + String oldKey = smallSize.location.volume_id + "_" + smallSize.location.local_id + "@50_50"; + String newKey = smallSize2.location.volume_id + "_" + smallSize2.location.local_id + "@50_50"; + ImageLoader.getInstance().replaceImageInCache(oldKey, newKey, ImageLocation.getForUser(user, ImageLocation.TYPE_SMALL), false); + } + + if (bigSize2 != null && smallSize != null && smallSize.location != null) { + File destFile = FileLoader.getInstance(currentAccount).getPathToAttach(bigSize2, true); + File src = FileLoader.getInstance(currentAccount).getPathToAttach(smallSize.location, true); + src.renameTo(destFile); + } + + PhotoUtilities.applyPhotoToUser(photos_photo.photo, user, false); + UserConfig.getInstance(currentAccount).setCurrentUser(user); + UserConfig.getInstance(currentAccount).saveConfig(true); + if (onDone != null) { + onDone.run(); + } + CharSequence title = AndroidUtilities.replaceTags(LocaleController.getString("ApplyAvatarHint", R.string.ApplyAvatarHintTitle)); + CharSequence subtitle = AndroidUtilities.replaceSingleTag(LocaleController.getString("ApplyAvatarHint", R.string.ApplyAvatarHint), () -> { + Bundle args = new Bundle(); + args.putLong("user_id", UserConfig.getInstance(currentAccount).clientUserId); + layout.getLastFragment().presentFragment(new ProfileActivity(args)); + }); + BulletinFactory.of(layout.getLastFragment()).createUsersBulletin(Collections.singletonList(user), title, subtitle).show(); + } + } + } + })); + imageUpdater.onPause(); + })); + } + + public static void replacePhotoImagesInCache(int currentAccount, TLRPC.Photo photo, TLRPC.Photo photoToReplace) { + TLRPC.PhotoSize smallSize = FileLoader.getClosestPhotoSizeWithSize(photo.sizes, 100); + TLRPC.PhotoSize bigSize = FileLoader.getClosestPhotoSizeWithSize(photo.sizes, 1000); + + TLRPC.PhotoSize smallSize2 = FileLoader.getClosestPhotoSizeWithSize(photoToReplace.sizes, 100); + TLRPC.PhotoSize bigSize2 = FileLoader.getClosestPhotoSizeWithSize(photoToReplace.sizes, 1000); + if (smallSize2 != null && smallSize != null) { + File destFile = FileLoader.getInstance(currentAccount).getPathToAttach(smallSize2, true); + File src = FileLoader.getInstance(currentAccount).getPathToAttach(smallSize, true); + src.renameTo(destFile); + String oldKey = smallSize.location.volume_id + "_" + smallSize.location.local_id + "@50_50"; + String newKey = smallSize2.location.volume_id + "_" + smallSize2.location.local_id + "@50_50"; + ImageLoader.getInstance().replaceImageInCache(oldKey, newKey, ImageLocation.getForPhoto(smallSize, photo), false); + } + + if (bigSize2 != null && bigSize != null) { + File destFile = FileLoader.getInstance(currentAccount).getPathToAttach(bigSize2, true); + File src = FileLoader.getInstance(currentAccount).getPathToAttach(bigSize, true); + src.renameTo(destFile); + String oldKey = bigSize.location.volume_id + "_" + bigSize.location.local_id + "@150_150"; + String newKey = bigSize2.location.volume_id + "_" + bigSize2.location.local_id + "@150_150"; + ImageLoader.getInstance().replaceImageInCache(oldKey, newKey, ImageLocation.getForPhoto(bigSize, photo), false); + } + } + + public static void applyPhotoToUser(TLRPC.PhotoSize smallSize, TLRPC.PhotoSize bigSize, boolean hasVideo, TLRPC.User user, boolean personal) { + user.flags |= 32; + user.photo = new TLRPC.TL_userProfilePhoto(); + user.photo.personal = personal; + user.photo.photo_id = 0; + user.photo.has_video = hasVideo; + if (smallSize != null) { + user.photo.photo_small = smallSize.location; + } + if (bigSize != null) { + user.photo.photo_big = bigSize.location; + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/video/MediaCodecVideoConvertor.java b/TMessagesProj/src/main/java/org/telegram/messenger/video/MediaCodecVideoConvertor.java index 703711757..fea4f5c05 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/video/MediaCodecVideoConvertor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/video/MediaCodecVideoConvertor.java @@ -3,12 +3,14 @@ package org.telegram.messenger.video; import android.annotation.TargetApi; import android.media.MediaCodec; import android.media.MediaCodecInfo; +import android.media.MediaCodecList; import android.media.MediaExtractor; import android.media.MediaFormat; import android.os.Build; import com.google.android.exoplayer2.util.Log; +import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.BuildVars; import org.telegram.messenger.FileLog; import org.telegram.messenger.MediaController; @@ -341,7 +343,7 @@ public class MediaCodecVideoConvertor { extractor.selectTrack(videoIndex); MediaFormat videoFormat = extractor.getTrackFormat(videoIndex); - + String encoderName = null; if (avatarStartTime >= 0) { if (durationS <= 2000) { bitrate = 2600000; @@ -351,6 +353,8 @@ public class MediaCodecVideoConvertor { bitrate = 1560000; } avatarStartTime = 0; + //this encoder work with bitrate better, prevent case when result video max 2MB + encoderName = "OMX.google.h264.encoder"; } else if (bitrate <= 0) { bitrate = 921600; } @@ -386,7 +390,7 @@ public class MediaCodecVideoConvertor { h = resultHeight; } if (BuildVars.LOGS_ENABLED) { - FileLog.d("create encoder with w = " + w + " h = " + h); + FileLog.d("create encoder with w = " + w + " h = " + h + " bitrate = " + bitrate); } MediaFormat outputFormat = MediaFormat.createVideoFormat(MediaController.VIDEO_MIME_TYPE, w, h); outputFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); @@ -401,7 +405,16 @@ public class MediaCodecVideoConvertor { outputFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); } - encoder = MediaCodec.createEncoderByType(MediaController.VIDEO_MIME_TYPE); + if (encoderName != null) { + try { + encoder = MediaCodec.createByCodecName(encoderName); + } catch (Exception e) { + + } + } + if (encoder == null) { + encoder = MediaCodec.createEncoderByType(MediaController.VIDEO_MIME_TYPE); + } encoder.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); inputSurface = new InputSurface(encoder.createInputSurface()); inputSurface.makeCurrent(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/video/TextureRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/video/TextureRenderer.java index c371c8aef..57edfb8c9 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/video/TextureRenderer.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/video/TextureRenderer.java @@ -22,6 +22,9 @@ import android.opengl.GLUtils; import android.opengl.Matrix; import android.os.Build; import android.text.Layout; +import android.text.SpannableString; +import android.text.Spanned; +import android.util.Log; import android.util.TypedValue; import android.view.Gravity; import android.view.View; @@ -34,14 +37,20 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.Bitmaps; import org.telegram.messenger.BuildVars; +import org.telegram.messenger.Emoji; import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaController; import org.telegram.messenger.UserConfig; import org.telegram.messenger.Utilities; import org.telegram.messenger.VideoEditedInfo; +import org.telegram.ui.Components.AnimatedEmojiDrawable; +import org.telegram.ui.Components.AnimatedEmojiSpan; import org.telegram.ui.Components.AnimatedFileDrawable; +import org.telegram.ui.Components.EditTextEffects; import org.telegram.ui.Components.FilterShaders; import org.telegram.ui.Components.Paint.Views.EditTextOutline; +import org.telegram.ui.Components.Paint.Views.PaintTextOptionsView; import org.telegram.ui.Components.RLottieDrawable; import java.io.File; @@ -72,6 +81,7 @@ public class TextureRenderer { private String paintPath; private String imagePath; private ArrayList mediaEntities; + private ArrayList emojiDrawables; private int originalWidth; private int originalHeight; private int transformedWidth; @@ -420,6 +430,17 @@ public class TextureRenderer { GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, stickerBitmap, 0); drawTexture(false, stickerTexture[0], entity.x, entity.y, entity.width, entity.height, entity.rotation, (entity.subType & 2) != 0); } + } else if (entity.view != null && entity.canvas != null && entity.bitmap != null) { + entity.bitmap.eraseColor(Color.TRANSPARENT); + int lastFrame = (int) entity.currentFrame; + entity.currentFrame += entity.framesPerDraw; + int currentFrame = (int) entity.currentFrame; + EditTextEffects editTextEffects = (EditTextEffects) entity.view; + editTextEffects.incrementFrames(currentFrame - lastFrame); + entity.view.draw(entity.canvas); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, stickerTexture[0]); + GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, entity.bitmap, 0); + drawTexture(false, stickerTexture[0], entity.x, entity.y, entity.width, entity.height, entity.rotation, (entity.subType & 2) != 0); } else { if (entity.bitmap != null) { GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, stickerTexture[0]); @@ -664,14 +685,69 @@ public class TextureRenderer { } } } else if (entity.type == 1) { - EditTextOutline editText = new EditTextOutline(ApplicationLoader.applicationContext); + EditTextOutline editText = new EditTextOutline(ApplicationLoader.applicationContext) { + { + animatedEmojiOffsetX = AndroidUtilities.dp(8); + animatedEmojiRawDraw = true; + animatedEmojiRawDrawFps = (int) videoFps; + } + }; editText.setBackgroundColor(Color.TRANSPARENT); editText.setPadding(AndroidUtilities.dp(7), AndroidUtilities.dp(7), AndroidUtilities.dp(7), AndroidUtilities.dp(7)); + Typeface typeface; + if (entity.textTypeface != null && (typeface = entity.textTypeface.getTypeface()) != null) { + editText.setTypeface(typeface); + } editText.setTextSize(TypedValue.COMPLEX_UNIT_PX, entity.fontSize); - editText.setText(entity.text); + SpannableString text = new SpannableString(entity.text); + boolean containsAnimated = false; + for (VideoEditedInfo.EmojiEntity e : entity.entities) { + containsAnimated = true; + AnimatedEmojiSpan span; + if (e.document != null) { + span = new AnimatedEmojiSpan(e.document, editText.getPaint().getFontMetricsInt()); + } else { + span = new AnimatedEmojiSpan(e.document_id, editText.getPaint().getFontMetricsInt()); + } + span.cacheType = AnimatedEmojiDrawable.CACHE_TYPE_RENDERING_VIDEO; + span.documentAbsolutePath = e.documentAbsolutePath; + text.setSpan(span, e.offset, e.offset + e.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + editText.setText(Emoji.replaceEmoji(text, editText.getPaint().getFontMetricsInt(), (int) (editText.getTextSize() * .8f), false)); editText.setTextColor(entity.color); - editText.setTypeface(null, Typeface.BOLD); - editText.setGravity(Gravity.CENTER); + + int gravity; + switch (entity.textAlign) { + default: + case PaintTextOptionsView.ALIGN_LEFT: + gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL; + break; + case PaintTextOptionsView.ALIGN_CENTER: + gravity = Gravity.CENTER; + break; + case PaintTextOptionsView.ALIGN_RIGHT: + gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL; + break; + } + + editText.setGravity(gravity); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + int textAlign; + switch (entity.textAlign) { + default: + case PaintTextOptionsView.ALIGN_LEFT: + textAlign = LocaleController.isRTL ? View.TEXT_ALIGNMENT_TEXT_END : View.TEXT_ALIGNMENT_TEXT_START; + break; + case PaintTextOptionsView.ALIGN_CENTER: + textAlign = View.TEXT_ALIGNMENT_CENTER; + break; + case PaintTextOptionsView.ALIGN_RIGHT: + textAlign = LocaleController.isRTL ? View.TEXT_ALIGNMENT_TEXT_START : View.TEXT_ALIGNMENT_TEXT_END; + break; + } + editText.setTextAlignment(textAlign); + } + editText.setHorizontallyScrolling(false); editText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); editText.setFocusableInTouchMode(true); @@ -701,6 +777,12 @@ public class TextureRenderer { entity.bitmap = Bitmap.createBitmap(entity.viewWidth, entity.viewHeight, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(entity.bitmap); editText.draw(canvas); + if (containsAnimated) { + entity.view = editText; + entity.canvas = canvas; + entity.framesPerDraw = videoFps / 30f; + entity.currentFrame = 0; + } } } } catch (Throwable e) { @@ -744,6 +826,9 @@ public class TextureRenderer { if (entity.animatedFileDrawable != null) { entity.animatedFileDrawable.recycle(); } + if (entity.view instanceof EditTextEffects) { + ((EditTextEffects) entity.view).recycleEmojis(); + } } } } diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java b/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java index e8bc858b2..59e61c453 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java @@ -68,7 +68,7 @@ public class TLRPC { public static final int MESSAGE_FLAG_HAS_BOT_ID = 0x00000800; public static final int MESSAGE_FLAG_EDITED = 0x00008000; - public static final int LAYER = 150; + public static final int LAYER = 151; public static class TL_stats_megagroupStats extends TLObject { public static int constructor = 0xef7ff916; @@ -8054,6 +8054,7 @@ public class TLRPC { public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); nopremium = (flags & 8) != 0; + spoiler = (flags & 16) != 0; if ((flags & 1) != 0) { document = Document.TLdeserialize(stream, stream.readInt32(exception), exception); } else { @@ -8067,6 +8068,7 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); flags = nopremium ? (flags | 8) : (flags &~ 8); + flags = spoiler ? (flags | 16) : (flags &~ 16); flags = document != null ? (flags | 1) : (flags &~ 1); stream.writeInt32(flags); if ((flags & 1) != 0) { @@ -8147,6 +8149,7 @@ public class TLRPC { public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); + spoiler = (flags & 8) != 0; if ((flags & 1) != 0) { photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); } else { @@ -8159,6 +8162,7 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = spoiler ? (flags | 8) : (flags &~ 8); stream.writeInt32(flags); if ((flags & 1) != 0) { photo.serializeToStream(stream); @@ -12084,6 +12088,7 @@ public class TLRPC { public int flags2; public boolean can_delete_channel; public boolean antispam; + public boolean participants_hidden; public ChatReactions available_reactions; public long inviterId; //custom @@ -14558,6 +14563,7 @@ public class TLRPC { flags2 = stream.readInt32(exception); can_delete_channel = (flags2 & 1) != 0; antispam = (flags2 & 2) != 0; + participants_hidden = (flags2 & 4) != 0; id = stream.readInt64(exception); about = stream.readString(exception); if ((flags & 1) != 0) { @@ -14694,6 +14700,7 @@ public class TLRPC { stream.writeInt32(flags); flags2 = can_delete_channel ? (flags2 | 1) : (flags2 &~ 1); flags2 = antispam ? (flags2 | 2) : (flags2 &~ 2); + flags2 = participants_hidden ? (flags2 | 4) : (flags2 &~ 4); stream.writeInt32(flags2); stream.writeInt64(id); stream.writeString(about); @@ -24095,6 +24102,12 @@ public class TLRPC { case 0x98e0d697: result = new TL_messageActionGeoProximityReached(); break; + case 0x57de635e: + result = new TL_messageActionSuggestProfilePhoto(); + break; + case 0xe7e75f97: + result = new TL_messageActionAttachMenuBotAllowed(); + break; case 0x95e3fbef: result = new TL_messageActionChatDeletePhoto(); break; @@ -29247,6 +29260,7 @@ public class TLRPC { public String mime_type; public ArrayList attributes = new ArrayList<>(); public int proximity_notification_radius; + public boolean spoiler; public static InputMedia TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { InputMedia result = null; @@ -29331,6 +29345,7 @@ public class TLRPC { public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); + spoiler = (flags & 4) != 0; id = InputDocument.TLdeserialize(stream, stream.readInt32(exception), exception); if ((flags & 1) != 0) { ttl_seconds = stream.readInt32(exception); @@ -29342,6 +29357,7 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = spoiler ? (flags | 4) : (flags &~ 4); stream.writeInt32(flags); id.serializeToStream(stream); if ((flags & 1) != 0) { @@ -29404,6 +29420,7 @@ public class TLRPC { public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); + spoiler = (flags & 2) != 0; url = stream.readString(exception); if ((flags & 1) != 0) { ttl_seconds = stream.readInt32(exception); @@ -29412,6 +29429,7 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = spoiler ? (flags | 2) : (flags &~ 2); stream.writeInt32(flags); stream.writeString(url); if ((flags & 1) != 0) { @@ -29507,6 +29525,7 @@ public class TLRPC { public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); + spoiler = (flags & 4) != 0; file = InputFile.TLdeserialize(stream, stream.readInt32(exception), exception); if ((flags & 1) != 0) { int magic = stream.readInt32(exception); @@ -29532,6 +29551,7 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = spoiler ? (flags | 4) : (flags &~ 4); stream.writeInt32(flags); file.serializeToStream(stream); if ((flags & 1) != 0) { @@ -29616,6 +29636,7 @@ public class TLRPC { flags = stream.readInt32(exception); nosound_video = (flags & 8) != 0; force_file = (flags & 16) != 0; + spoiler = (flags & 32) != 0; file = InputFile.TLdeserialize(stream, stream.readInt32(exception), exception); if ((flags & 4) != 0) { thumb = InputFile.TLdeserialize(stream, stream.readInt32(exception), exception); @@ -29662,6 +29683,7 @@ public class TLRPC { stream.writeInt32(constructor); flags = nosound_video ? (flags | 8) : (flags &~ 8); flags = force_file ? (flags | 16) : (flags &~ 16); + flags = spoiler ? (flags | 32) : (flags &~ 32); stream.writeInt32(flags); file.serializeToStream(stream); if ((flags & 4) != 0) { @@ -29695,6 +29717,7 @@ public class TLRPC { public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); + spoiler = (flags & 2) != 0; url = stream.readString(exception); if ((flags & 1) != 0) { ttl_seconds = stream.readInt32(exception); @@ -29703,6 +29726,7 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = spoiler ? (flags | 2) : (flags &~ 2); stream.writeInt32(flags); stream.writeString(url); if ((flags & 1) != 0) { @@ -29718,6 +29742,7 @@ public class TLRPC { public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); + spoiler = (flags & 2) != 0; id = InputPhoto.TLdeserialize(stream, stream.readInt32(exception), exception); if ((flags & 1) != 0) { ttl_seconds = stream.readInt32(exception); @@ -29726,6 +29751,7 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = spoiler ? (flags | 2) : (flags &~ 2); stream.writeInt32(flags); id.serializeToStream(stream); if ((flags & 1) != 0) { @@ -29751,6 +29777,9 @@ public class TLRPC { case 0x40d13c0e: result = new TL_stickerSetFullCovered(); break; + case 0x77b15d1c: + result = new TL_stickerSetNoCovered(); + break; case 0x6410a5d2: result = new TL_stickerSetCovered(); break; @@ -29901,6 +29930,19 @@ public class TLRPC { } } + public static class TL_stickerSetNoCovered extends StickerSetCovered { + public static int constructor = 0x77b15d1c; + + public void readParams(AbstractSerializedData stream, boolean exception) { + set = StickerSet.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + set.serializeToStream(stream); + } + } + public static class TL_stickerSetFullCovered extends StickerSetCovered { public static int constructor = 0x40d13c0e; @@ -31028,6 +31070,9 @@ public class TLRPC { case 0xf227868c: result = new TL_updateUserPhoto(); break; + case 0x20529438: + result = new TL_updateUser(); + break; case 0x17b7a20b: result = new TL_updateAttachMenuBots(); break; @@ -32963,6 +33008,21 @@ public class TLRPC { } } + public static class TL_updateUser extends Update { + public static int constructor = 0x20529438; + + public long user_id; + + public void readParams(AbstractSerializedData stream, boolean exception) { + user_id = stream.readInt64(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt64(user_id); + } + } + public static class TL_updateDialogFilters extends Update { public static int constructor = 0x3504914f; @@ -34232,6 +34292,7 @@ public class TLRPC { public FileLocation photo_big; public byte[] stripped_thumb; public int dc_id; + public boolean personal; public BitmapDrawable strippedBitmap; public static UserProfilePhoto TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { @@ -34400,6 +34461,7 @@ public class TLRPC { public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); has_video = (flags & 1) != 0; + personal = (flags & 4) != 0; photo_id = stream.readInt64(exception); if ((flags & 2) != 0) { stripped_thumb = stream.readByteArray(exception); @@ -34424,6 +34486,7 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); flags = has_video ? (flags | 1) : (flags &~ 1); + flags = personal ? (flags | 4) : (flags &~ 4); stream.writeInt32(flags); stream.writeInt64(photo_id); if ((flags & 2) != 0) { @@ -46232,6 +46295,7 @@ public class TLRPC { public User user; public String about; public TL_contacts_link_layer101 link; + public Photo personal_photo; public Photo profile_photo; public PeerNotifySettings notify_settings; public BotInfo bot_info; @@ -46246,13 +46310,20 @@ public class TLRPC { public TL_chatAdminRights bot_group_admin_rights; public TL_chatAdminRights bot_broadcast_admin_rights; public ArrayList premium_gifts = new ArrayList<>(); + public Photo fallback_photo; public static UserFull TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { UserFull result = null; switch (constructor) { - case 0xc4b1fc3f: + case 0xf8d32aed: result = new TL_userFull(); break; + case 0xec6d41e3: + result = new TL_userFull_layer150_rev2(); + break; + case 0xc4b1fc3f: + result = new TL_userFull_layer150(); + break; case 0x8c72ea81: result = new TL_userFull_layer143(); break; @@ -46286,6 +46357,264 @@ public class TLRPC { } public static class TL_userFull extends UserFull { + public static int constructor = 0xf8d32aed; + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + blocked = (flags & 1) != 0; + phone_calls_available = (flags & 16) != 0; + phone_calls_private = (flags & 32) != 0; + can_pin_message = (flags & 128) != 0; + has_scheduled = (flags & 4096) != 0; + video_calls_available = (flags & 8192) != 0; + voice_messages_forbidden = (flags & 1048576) != 0; + id = stream.readInt64(exception); + if ((flags & 2) != 0) { + about = stream.readString(exception); + } + settings = TL_peerSettings.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 2097152) != 0) { + personal_photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 4) != 0) { + profile_photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 4194304) != 0) { + fallback_photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + notify_settings = PeerNotifySettings.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 8) != 0) { + bot_info = BotInfo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 64) != 0) { + pinned_msg_id = stream.readInt32(exception); + } + common_chats_count = stream.readInt32(exception); + if ((flags & 2048) != 0) { + folder_id = stream.readInt32(exception); + } + if ((flags & 16384) != 0) { + ttl_period = stream.readInt32(exception); + } + if ((flags & 32768) != 0) { + theme_emoticon = stream.readString(exception); + } + if ((flags & 65536) != 0) { + private_forward_name = stream.readString(exception); + } + if ((flags & 131072) != 0) { + bot_group_admin_rights = TL_chatAdminRights.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 262144) != 0) { + bot_broadcast_admin_rights = TL_chatAdminRights.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 524288) != 0) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TL_premiumGiftOption object = TL_premiumGiftOption.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + premium_gifts.add(object); + } + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = blocked ? (flags | 1) : (flags &~ 1); + flags = phone_calls_available ? (flags | 16) : (flags &~ 16); + flags = phone_calls_private ? (flags | 32) : (flags &~ 32); + flags = can_pin_message ? (flags | 128) : (flags &~ 128); + flags = has_scheduled ? (flags | 4096) : (flags &~ 4096); + flags = video_calls_available ? (flags | 8192) : (flags &~ 8192); + flags = voice_messages_forbidden ? (flags | 1048576) : (flags &~ 1048576); + stream.writeInt32(flags); + stream.writeInt64(id); + if ((flags & 2) != 0) { + stream.writeString(about); + } + settings.serializeToStream(stream); + if ((flags & 2097152) != 0) { + personal_photo.serializeToStream(stream); + } + if ((flags & 4) != 0) { + profile_photo.serializeToStream(stream); + } + if ((flags & 4194304) != 0) { + fallback_photo.serializeToStream(stream); + } + notify_settings.serializeToStream(stream); + if ((flags & 8) != 0) { + bot_info.serializeToStream(stream); + } + if ((flags & 64) != 0) { + stream.writeInt32(pinned_msg_id); + } + stream.writeInt32(common_chats_count); + if ((flags & 2048) != 0) { + stream.writeInt32(folder_id); + } + if ((flags & 16384) != 0) { + stream.writeInt32(ttl_period); + } + if ((flags & 32768) != 0) { + stream.writeString(theme_emoticon); + } + if ((flags & 65536) != 0) { + stream.writeString(private_forward_name); + } + if ((flags & 131072) != 0) { + bot_group_admin_rights.serializeToStream(stream); + } + if ((flags & 262144) != 0) { + bot_broadcast_admin_rights.serializeToStream(stream); + } + if ((flags & 524288) != 0) { + stream.writeInt32(0x1cb5c415); + int count = premium_gifts.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + premium_gifts.get(a).serializeToStream(stream); + } + } + } + } + + public static class TL_userFull_layer150_rev2 extends UserFull { + public static int constructor = 0xec6d41e3; + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + blocked = (flags & 1) != 0; + phone_calls_available = (flags & 16) != 0; + phone_calls_private = (flags & 32) != 0; + can_pin_message = (flags & 128) != 0; + has_scheduled = (flags & 4096) != 0; + video_calls_available = (flags & 8192) != 0; + voice_messages_forbidden = (flags & 1048576) != 0; + id = stream.readInt64(exception); + if ((flags & 2) != 0) { + about = stream.readString(exception); + } + settings = TL_peerSettings.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 2097152) != 0) { + personal_photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 4) != 0) { + profile_photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + notify_settings = PeerNotifySettings.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 8) != 0) { + bot_info = BotInfo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 64) != 0) { + pinned_msg_id = stream.readInt32(exception); + } + common_chats_count = stream.readInt32(exception); + if ((flags & 2048) != 0) { + folder_id = stream.readInt32(exception); + } + if ((flags & 16384) != 0) { + ttl_period = stream.readInt32(exception); + } + if ((flags & 32768) != 0) { + theme_emoticon = stream.readString(exception); + } + if ((flags & 65536) != 0) { + private_forward_name = stream.readString(exception); + } + if ((flags & 131072) != 0) { + bot_group_admin_rights = TL_chatAdminRights.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 262144) != 0) { + bot_broadcast_admin_rights = TL_chatAdminRights.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 524288) != 0) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TL_premiumGiftOption object = TL_premiumGiftOption.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + premium_gifts.add(object); + } + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = blocked ? (flags | 1) : (flags &~ 1); + flags = phone_calls_available ? (flags | 16) : (flags &~ 16); + flags = phone_calls_private ? (flags | 32) : (flags &~ 32); + flags = can_pin_message ? (flags | 128) : (flags &~ 128); + flags = has_scheduled ? (flags | 4096) : (flags &~ 4096); + flags = video_calls_available ? (flags | 8192) : (flags &~ 8192); + flags = voice_messages_forbidden ? (flags | 1048576) : (flags &~ 1048576); + stream.writeInt32(flags); + stream.writeInt64(id); + if ((flags & 2) != 0) { + stream.writeString(about); + } + settings.serializeToStream(stream); + if ((flags & 2097152) != 0) { + personal_photo.serializeToStream(stream); + } + if ((flags & 4) != 0) { + profile_photo.serializeToStream(stream); + } + notify_settings.serializeToStream(stream); + if ((flags & 8) != 0) { + bot_info.serializeToStream(stream); + } + if ((flags & 64) != 0) { + stream.writeInt32(pinned_msg_id); + } + stream.writeInt32(common_chats_count); + if ((flags & 2048) != 0) { + stream.writeInt32(folder_id); + } + if ((flags & 16384) != 0) { + stream.writeInt32(ttl_period); + } + if ((flags & 32768) != 0) { + stream.writeString(theme_emoticon); + } + if ((flags & 65536) != 0) { + stream.writeString(private_forward_name); + } + if ((flags & 131072) != 0) { + bot_group_admin_rights.serializeToStream(stream); + } + if ((flags & 262144) != 0) { + bot_broadcast_admin_rights.serializeToStream(stream); + } + if ((flags & 524288) != 0) { + stream.writeInt32(0x1cb5c415); + int count = premium_gifts.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + premium_gifts.get(a).serializeToStream(stream); + } + } + } + } + + public static class TL_userFull_layer150 extends UserFull { public static int constructor = 0xc4b1fc3f; public void readParams(AbstractSerializedData stream, boolean exception) { @@ -51512,6 +51841,7 @@ public class TLRPC { public static int constructor = 0x89f30f69; public int flags; + public boolean fallback; public InputFile file; public InputFile video; public double video_start_ts; @@ -51522,6 +51852,7 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = fallback ? (flags | 8) : (flags &~ 8); stream.writeInt32(flags); if ((flags & 1) != 0) { file.serializeToStream(stream); @@ -58121,6 +58452,7 @@ public class TLRPC { public int proximity_notification_radius; public boolean nopremium; public MessageExtendedMedia extended_media; + public boolean spoiler; public static MessageMedia TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { MessageMedia result = null; @@ -62694,6 +63026,7 @@ public class TLRPC { public int flags; public boolean inactive; public boolean has_settings; + public boolean request_write_access; public long bot_id; public String short_name; public ArrayList peer_types = new ArrayList<>(); @@ -62703,6 +63036,7 @@ public class TLRPC { flags = stream.readInt32(exception); inactive = (flags & 1) != 0; has_settings = (flags & 2) != 0; + request_write_access = (flags & 4) != 0; bot_id = stream.readInt64(exception); short_name = stream.readString(exception); int magic = stream.readInt32(exception); @@ -62741,6 +63075,7 @@ public class TLRPC { stream.writeInt32(constructor); flags = inactive ? (flags | 1) : (flags &~ 1); flags = has_settings ? (flags | 2) : (flags &~ 2); + flags = request_write_access ? (flags | 4) : (flags &~ 4); stream.writeInt32(flags); stream.writeInt64(bot_id); stream.writeString(short_name); @@ -62961,8 +63296,10 @@ public class TLRPC { } public static class TL_messages_toggleBotInAttachMenu extends TLObject { - public static int constructor = 0x1aee33af; + public static int constructor = 0x69f59d69; + public int flags; + public boolean write_allowed; public InputUser bot; public boolean enabled; @@ -62972,6 +63309,8 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = write_allowed ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); bot.serializeToStream(stream); stream.writeBool(enabled); } @@ -64980,6 +65319,23 @@ public class TLRPC { } } + public static class TL_channels_toggleParticipantsHidden extends TLObject { + public static int constructor = 0x6a6e7854; + + public InputChannel channel; + public boolean enabled; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + stream.writeBool(enabled); + } + } + public static class TL_messages_setDefaultHistoryTTL extends TLObject { public static int constructor = 0x9eb51445; @@ -65063,6 +65419,64 @@ public class TLRPC { } } + public static class TL_messageActionSuggestProfilePhoto extends MessageAction { + public static int constructor = 0x57de635e; + + public void readParams(AbstractSerializedData stream, boolean exception) { + photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + photo.serializeToStream(stream); + } + } + + public static class TL_messageActionAttachMenuBotAllowed extends MessageAction { + public static int constructor = 0xe7e75f97; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_photos_uploadContactProfilePhoto extends TLObject { + public static int constructor = 0xb91a83bf; + + public int flags; + public boolean suggest; + public boolean save; + public InputUser user_id; + public InputFile file; + public InputFile video; + public double video_start_ts; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_photos_photo.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = suggest ? (flags | 8) : (flags &~ 8); + flags = save ? (flags | 16) : (flags &~ 16); + stream.writeInt32(flags); + user_id.serializeToStream(stream); + if ((flags & 1) != 0) { + file.serializeToStream(stream); + } + if ((flags & 2) != 0) { + video.serializeToStream(stream); + } + if ((flags & 4) != 0) { + stream.writeDouble(video_start_ts); + } + } + } + //functions public static class Vector extends TLObject { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java index 1ce7f3974..518a31c57 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java @@ -415,6 +415,10 @@ public class ActionBarMenuItem extends FrameLayout { popupLayout.setShownFromBottom(value); } + public void setFitSubItems(boolean fit) { + popupLayout.setFitItems(fit); + } + public void addSubItem(View view, int width, int height) { createPopupLayout(); popupLayout.addView(view, new LinearLayout.LayoutParams(width, height)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuSubItem.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuSubItem.java index 778b0ef77..043acef1b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuSubItem.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuSubItem.java @@ -4,12 +4,9 @@ import android.content.Context; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; -import android.graphics.drawable.RippleDrawable; -import android.os.Build; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; -import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; @@ -18,15 +15,15 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; -import org.telegram.messenger.R; import org.telegram.ui.Components.CheckBox2; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RLottieImageView; public class ActionBarMenuSubItem extends FrameLayout { private TextView textView; private TextView subtextView; - private ImageView imageView; + private RLottieImageView imageView; private CheckBox2 checkView; private ImageView rightIcon; @@ -67,7 +64,7 @@ public class ActionBarMenuSubItem extends FrameLayout { updateBackground(); setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); - imageView = new ImageView(context); + imageView = new RLottieImageView(context); imageView.setScaleType(ImageView.ScaleType.CENTER); imageView.setColorFilter(new PorterDuffColorFilter(iconColor, PorterDuff.Mode.MULTIPLY)); addView(imageView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 40, Gravity.CENTER_VERTICAL | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT))); @@ -184,7 +181,17 @@ public class ActionBarMenuSubItem extends FrameLayout { imageView.setImageResource(resId); } - public void setText(String text) { + public void setAnimatedIcon(int resId) { + imageView.setAnimation(resId, 24, 24); + } + + public void onItemShown() { + if (imageView.getAnimatedDrawable() != null) { + imageView.getAnimatedDrawable().start(); + } + } + + public void setText(CharSequence text) { textView.setText(text); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarPopupWindow.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarPopupWindow.java index e7222fc4d..04ec758a2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarPopupWindow.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarPopupWindow.java @@ -16,8 +16,6 @@ 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.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; @@ -45,12 +43,14 @@ 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.Paint.Views.LPhotoPaintView; import org.telegram.ui.Components.PopupSwipeBackLayout; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; +import java.util.Locale; public class ActionBarPopupWindow extends PopupWindow { @@ -125,6 +125,7 @@ public class ActionBarPopupWindow extends PopupWindow { private boolean fitItems; private final Theme.ResourcesProvider resourcesProvider; private View topView; + protected ActionBarPopupWindow window; public int subtractBackgroundHeight; @@ -311,7 +312,7 @@ public class ActionBarPopupWindow extends PopupWindow { if (shownFromBottom) { for (int a = lastStartedChild; a >= 0; a--) { View child = getItemAt(a); - if (child.getVisibility() != VISIBLE || child instanceof GapView) { + if (child == null || child.getVisibility() != VISIBLE || child instanceof GapView) { continue; } Integer position = positions.get(child); @@ -368,6 +369,10 @@ public class ActionBarPopupWindow extends PopupWindow { @Override public void onAnimationEnd(Animator animation) { itemAnimators.remove(animatorSet); + + if (child instanceof ActionBarMenuSubItem) { + ((ActionBarMenuSubItem) child).onItemShown(); + } } }); animatorSet.setInterpolator(decelerateInterpolator); @@ -609,6 +614,10 @@ public class ActionBarPopupWindow extends PopupWindow { swipeBackLayout.invalidateTransforms(!startAnimationPending); } } + + public void setParentWindow(ActionBarPopupWindow popupWindow) { + window = popupWindow; + } } public ActionBarPopupWindow() { @@ -763,6 +772,9 @@ public class ActionBarPopupWindow extends PopupWindow { int visibleCount = 0; for (int a = 0; a < count; a++) { View child = content.getItemAt(a); + if (child instanceof GapView) { + continue; + } child.setAlpha(0.0f); if (child.getVisibility() != View.VISIBLE) { continue; @@ -790,9 +802,9 @@ public class ActionBarPopupWindow extends PopupWindow { if (child instanceof GapView) { continue; } - float at = AndroidUtilities.cascade(t, a, count2, 2); - child.setTranslationY((1f - at) * AndroidUtilities.dp(-12)); - child.setAlpha(at); + float at = AndroidUtilities.cascade(t, content.shownFromBottom ? count2 - 1 - a : a, count2, 4); + child.setTranslationY((1f - at) * AndroidUtilities.dp(-6)); +// child.setAlpha(at * (child.isEnabled() ? 1f : 0.5f)); } }); content.updateAnimation = true; @@ -801,7 +813,7 @@ public class ActionBarPopupWindow extends PopupWindow { ObjectAnimator.ofInt(content, "backAlpha", 0, 255), childtranslations ); - windowAnimatorSet.setDuration(150 + 16 * visibleCount + 1000); + windowAnimatorSet.setDuration(150 + 16 * visibleCount); windowAnimatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AdjustPanLayoutHelper.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AdjustPanLayoutHelper.java index 50c01e342..bdc223030 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AdjustPanLayoutHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AdjustPanLayoutHelper.java @@ -34,6 +34,7 @@ import java.util.List; public class AdjustPanLayoutHelper { public static boolean USE_ANDROID11_INSET_ANIMATOR = false; + private boolean useInsetsAnimator; public final static Interpolator keyboardInterpolator = ChatListItemAnimator.DEFAULT_INTERPOLATOR; public final static long keyboardDuration = 250; @@ -178,7 +179,7 @@ public class AdjustPanLayoutHelper { setViewHeight(Math.max(previousHeight, contentHeight + additionalContentHeight)); resizableView.requestLayout(); - onTransitionStart(isKeyboardVisible, contentHeight); + onTransitionStart(isKeyboardVisible, previousHeight, contentHeight); float dy = contentHeight - previousHeight; keyboardSize = Math.abs(dy); @@ -269,12 +270,11 @@ public class AdjustPanLayoutHelper { } public AdjustPanLayoutHelper(View parent) { - this.parent = parent; - onAttach(); + this(parent, USE_ANDROID11_INSET_ANIMATOR); } public AdjustPanLayoutHelper(View parent, boolean useInsetsAnimator) { - USE_ANDROID11_INSET_ANIMATOR = USE_ANDROID11_INSET_ANIMATOR && useInsetsAnimator; + this.useInsetsAnimator = useInsetsAnimator; this.parent = parent; onAttach(); } @@ -295,7 +295,7 @@ public class AdjustPanLayoutHelper { parentForListener = resizableView; resizableView.getViewTreeObserver().addOnPreDrawListener(onPreDrawListener); } - if (USE_ANDROID11_INSET_ANIMATOR && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (useInsetsAnimator && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { setupNewCallback(); } } @@ -335,7 +335,7 @@ public class AdjustPanLayoutHelper { parentForListener.getViewTreeObserver().removeOnPreDrawListener(onPreDrawListener); parentForListener = null; } - if (parent != null && USE_ANDROID11_INSET_ANIMATOR && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (parent != null && useInsetsAnimator && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { parent.setWindowInsetsAnimationCallback(null); } } @@ -368,6 +368,10 @@ public class AdjustPanLayoutHelper { } + protected void onTransitionStart(boolean keyboardVisible, int previousHeight, int contentHeight) { + onTransitionStart(keyboardVisible, contentHeight); + } + protected void onTransitionStart(boolean keyboardVisible, int contentHeight) { } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AlertDialog.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AlertDialog.java index 4afb008ad..28b057374 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AlertDialog.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AlertDialog.java @@ -14,14 +14,22 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.app.Dialog; import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.ColorFilter; +import android.graphics.Matrix; +import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; +import android.graphics.Shader; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.RoundRectShape; import android.os.Build; import android.os.Bundle; import android.text.TextPaint; @@ -34,6 +42,7 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowManager; +import android.view.animation.OvershootInterpolator; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; @@ -42,6 +51,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.graphics.ColorUtils; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.Emoji; @@ -49,18 +59,26 @@ import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; +import org.telegram.messenger.SharedConfig; +import org.telegram.ui.Components.AnimatedFloat; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.LineProgressView; import org.telegram.ui.Components.RLottieDrawable; import org.telegram.ui.Components.RLottieImageView; import org.telegram.ui.Components.RadialProgressView; import org.telegram.ui.Components.spoilers.SpoilersTextView; +import org.telegram.ui.LaunchActivity; import java.util.ArrayList; import java.util.Map; public class AlertDialog extends Dialog implements Drawable.Callback, NotificationCenter.NotificationCenterDelegate { + public static final int ALERT_TYPE_MESSAGE = 0; + public static final int ALERT_TYPE_SPINNER_DETAIL = 1; // not used? + public static final int ALERT_TYPE_LOADING = 2; + public static final int ALERT_TYPE_SPINNER = 3; + private View customView; private int customViewHeight = LayoutHelper.WRAP_CONTENT; private TextView titleTextView; @@ -104,7 +122,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati private int topHeight = 132; private Drawable topDrawable; private int topBackgroundColor; - private int progressViewStyle; // TODO: Use constants here + private int progressViewStyle; private int currentProgress; private boolean messageTextViewClickable = true; @@ -125,12 +143,20 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati private LineProgressView lineProgressView; private TextView lineProgressViewPercent; private OnClickListener onBackButtonListener; + private int[] containerViewLocation = new int[2]; private boolean checkFocusable = true; private Drawable shadowDrawable; private Rect backgroundPaddings; + private float blurOpacity; + private Bitmap blurBitmap; + private Matrix blurMatrix; + private BitmapShader blurShader; + private Paint blurPaint; + private Paint dimBlurPaint; + private boolean focusable; private boolean verticalButtons; @@ -154,6 +180,29 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati private boolean dimCustom = false; private final Theme.ResourcesProvider resourcesProvider; private boolean topAnimationAutoRepeat = true; + private boolean blurredBackground; + private boolean blurredNativeBackground; + private int backgroundColor; + float blurAlpha = 0.8f; + private boolean blurBehind; + private int additioanalHorizontalPadding; + + public void setBlurParams(float blurAlpha, boolean blurBehind, boolean blurBackground) { + this.blurAlpha = blurAlpha; + this.blurBehind = blurBehind; + this.blurredBackground = blurBackground; + } + + private boolean supportsNativeBlur() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && LaunchActivity.systemBlurEnabled; + } + + public void redPositive() { + TextView button = (TextView) getButton(DialogInterface.BUTTON_POSITIVE); + if (button != null) { + button.setTextColor(getThemedColor(Theme.key_dialogTextRed2)); + } + } public static class AlertDialogCell extends FrameLayout { @@ -220,18 +269,36 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati public AlertDialog(Context context, int progressStyle, Theme.ResourcesProvider resourcesProvider) { super(context, R.style.TransparentDialog); + blurredNativeBackground = supportsNativeBlur() && progressViewStyle == ALERT_TYPE_MESSAGE; + blurredBackground = blurredNativeBackground || !supportsNativeBlur() && SharedConfig.getDevicePerformanceClass() >= SharedConfig.PERFORMANCE_CLASS_HIGH; this.resourcesProvider = resourcesProvider; backgroundPaddings = new Rect(); - if (progressStyle != 3) { + if (progressStyle != ALERT_TYPE_SPINNER || blurredBackground) { shadowDrawable = context.getResources().getDrawable(R.drawable.popup_fixed_alert3).mutate(); - shadowDrawable.setColorFilter(new PorterDuffColorFilter(getThemedColor(Theme.key_dialogBackground), PorterDuff.Mode.MULTIPLY)); + backgroundColor = getThemedColor(Theme.key_dialogBackground); + blurOpacity = progressStyle == ALERT_TYPE_SPINNER ? 0.55f : (AndroidUtilities.computePerceivedBrightness(backgroundColor) < 0.721f ? 0.80f : 0.92f); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(backgroundColor, PorterDuff.Mode.MULTIPLY)); shadowDrawable.getPadding(backgroundPaddings); } progressViewStyle = progressStyle; } + @Override + public void show() { + super.show(); + if (progressViewContainer != null && progressViewStyle == ALERT_TYPE_SPINNER) { + progressViewContainer.setScaleX(0); + progressViewContainer.setScaleY(0); + progressViewContainer.animate() + .scaleX(1f).scaleY(1f) + .setInterpolator(new OvershootInterpolator(1.3f)) + .setDuration(190) + .start(); + } + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -242,7 +309,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati @Override public boolean onTouchEvent(MotionEvent event) { - if (progressViewStyle == 3) { + if (progressViewStyle == ALERT_TYPE_SPINNER) { showCancelAlert(); return false; } @@ -251,7 +318,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - if (progressViewStyle == 3) { + if (progressViewStyle == ALERT_TYPE_SPINNER) { showCancelAlert(); return false; } @@ -260,7 +327,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (progressViewStyle == 3) { + if (progressViewStyle == ALERT_TYPE_SPINNER) { progressViewContainer.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(86), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(86), MeasureSpec.EXACTLY)); setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec)); } else { @@ -309,8 +376,8 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati availableHeight -= subtitleTextView.getMeasuredHeight() + layoutParams.bottomMargin + layoutParams.topMargin; } if (topImageView != null) { - topImageView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(topHeight), MeasureSpec.EXACTLY)); - availableHeight -= topImageView.getMeasuredHeight() - AndroidUtilities.dp(8); + topImageView.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(topHeight), MeasureSpec.EXACTLY)); + availableHeight -= topImageView.getMeasuredHeight(); } if (topView != null) { int w = width - AndroidUtilities.dp(16); @@ -325,7 +392,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati topView.getLayoutParams().height = h; availableHeight -= topView.getMeasuredHeight(); } - if (progressViewStyle == 0) { + if (progressViewStyle == ALERT_TYPE_MESSAGE) { layoutParams = (LayoutParams) contentScrollView.getLayoutParams(); if (customView != null) { @@ -400,7 +467,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); - if (progressViewStyle == 3) { + if (progressViewStyle == ALERT_TYPE_SPINNER) { int x = (r - l - progressViewContainer.getMeasuredWidth()) / 2; int y = (b - t - progressViewContainer.getMeasuredHeight()) / 2; progressViewContainer.layout(x, y, x + progressViewContainer.getMeasuredWidth(), y + progressViewContainer.getMeasuredHeight()); @@ -415,6 +482,14 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati } onScrollChangedListener.onScrollChanged(); } + + getLocationOnScreen(containerViewLocation); + if (blurMatrix != null && blurShader != null) { + blurMatrix.reset(); + blurMatrix.postScale(8f, 8f); + blurMatrix.postTranslate(-containerViewLocation[0], -containerViewLocation[1]); + blurShader.setLocalMatrix(blurMatrix); + } } @Override @@ -430,9 +505,53 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati return false; } + private AnimatedFloat blurPaintAlpha = new AnimatedFloat(0, this); + private Paint backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + @Override + public void draw(Canvas canvas) { + if (blurredBackground && !blurredNativeBackground) { + float r; + if (progressViewStyle == ALERT_TYPE_SPINNER && progressViewContainer != null) { + r = AndroidUtilities.dp(18); + float w = progressViewContainer.getWidth() * progressViewContainer.getScaleX(); + float h = progressViewContainer.getHeight() * progressViewContainer.getScaleY(); + AndroidUtilities.rectTmp.set( + (getWidth() - w) / 2f, + (getHeight() - h) / 2f, + (getWidth() + w) / 2f, + (getHeight() + h) / 2f + ); + } else { + r = AndroidUtilities.dp(10); + AndroidUtilities.rectTmp.set(getPaddingLeft(), getPaddingTop(), getMeasuredWidth() - getPaddingRight(), getMeasuredHeight() - getPaddingBottom()); + } + + // draw blur of background + float blurAlpha = blurPaintAlpha.set(blurPaint != null ? 1f : 0f); + if (blurPaint != null) { + blurPaint.setAlpha((int) (0xFF * blurAlpha)); + canvas.drawRoundRect(AndroidUtilities.rectTmp, r, r, blurPaint); + } + + // draw dim above blur + if (dimBlurPaint == null) { + dimBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + dimBlurPaint.setColor(ColorUtils.setAlphaComponent(0xff000000, (int) (0xFF * dimAlpha))); + } + canvas.drawRoundRect(AndroidUtilities.rectTmp, r, r, dimBlurPaint); + + // draw background + backgroundPaint.setColor(backgroundColor); + backgroundPaint.setAlpha((int) (backgroundPaint.getAlpha() * (blurAlpha * (blurOpacity - 1f) + 1f))); + canvas.drawRoundRect(AndroidUtilities.rectTmp, r, r, backgroundPaint); + } + super.draw(canvas); + } + @Override protected void dispatchDraw(Canvas canvas) { - if (drawBackground) { + if (drawBackground && !blurredBackground) { shadowDrawable.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); if (topView != null && notDrawBackgroundOnTopView) { int clipTop = topView.getBottom(); @@ -448,9 +567,12 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati } }; containerView.setOrientation(LinearLayout.VERTICAL); - if (progressViewStyle == 3) { + if (blurredBackground || progressViewStyle == ALERT_TYPE_SPINNER) { containerView.setBackgroundDrawable(null); containerView.setPadding(0, 0, 0, 0); + if (blurredBackground && !blurredNativeBackground) { + containerView.setWillNotDraw(false); + } drawBackground = false; } else { if (notDrawBackgroundOnTopView) { @@ -518,8 +640,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati }); topHeight = 92; } else { - topImageView.setBackgroundDrawable(getContext().getResources().getDrawable(R.drawable.popup_fixed_top)); - topImageView.getBackground().setColorFilter(new PorterDuffColorFilter(topBackgroundColor, PorterDuff.Mode.MULTIPLY)); + topImageView.setBackground(Theme.createRoundRectDrawable(AndroidUtilities.dp(10), 0, topBackgroundColor)); } if (topAnimationIsNew) { topImageView.setTranslationY(AndroidUtilities.dp(16)); @@ -527,7 +648,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati topImageView.setTranslationY(0); } topImageView.setPadding(0, 0, 0, 0); - containerView.addView(topImageView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, topHeight, Gravity.LEFT | Gravity.TOP, -8, -8, 0, 0)); + containerView.addView(topImageView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, topHeight, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 0)); } else if (topView != null) { topView.setPadding(0, 0, 0, 0); containerView.addView(topView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, topHeight, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 0)); @@ -564,7 +685,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati containerView.addView(subtitleTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 24, 0, 24, items != null ? 14 : 10)); } - if (progressViewStyle == 0) { + if (progressViewStyle == ALERT_TYPE_MESSAGE) { shadow[0] = (BitmapDrawable) getContext().getResources().getDrawable(R.drawable.header_shadow).mutate(); shadow[1] = (BitmapDrawable) getContext().getResources().getDrawable(R.drawable.header_shadow_reverse).mutate(); shadow[0].setAlpha(0); @@ -613,7 +734,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati messageTextView.setEnabled(false); } messageTextView.setGravity((topAnimationIsNew ? Gravity.CENTER_HORIZONTAL : LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); - if (progressViewStyle == 1) { + if (progressViewStyle == ALERT_TYPE_SPINNER_DETAIL) { progressViewContainer = new FrameLayout(getContext()); containerView.addView(progressViewContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 44, Gravity.LEFT | Gravity.TOP, 23, title == null ? 24 : 0, 23, 24)); @@ -624,7 +745,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati messageTextView.setLines(1); messageTextView.setEllipsize(TextUtils.TruncateAt.END); progressViewContainer.addView(messageTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL, (LocaleController.isRTL ? 0 : 62), 0, (LocaleController.isRTL ? 62 : 0), 0)); - } else if (progressViewStyle == 2) { + } else if (progressViewStyle == ALERT_TYPE_LOADING) { containerView.addView(messageTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 24, title == null ? 19 : 0, 24, 20)); lineProgressView = new LineProgressView(getContext()); @@ -640,30 +761,36 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati lineProgressViewPercent.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); containerView.addView(lineProgressViewPercent, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 23, 4, 23, 24)); updateLineProgressTextView(); - } else if (progressViewStyle == 3) { + } else if (progressViewStyle == ALERT_TYPE_SPINNER) { setCanceledOnTouchOutside(false); setCancelable(false); progressViewContainer = new FrameLayout(getContext()); - progressViewContainer.setBackgroundDrawable(Theme.createRoundRectDrawable(AndroidUtilities.dp(18), getThemedColor(Theme.key_dialog_inlineProgressBackground))); + backgroundColor = getThemedColor(Theme.key_dialog_inlineProgressBackground); + if (!(blurredBackground && !blurredNativeBackground)) { + progressViewContainer.setBackgroundDrawable(Theme.createRoundRectDrawable(AndroidUtilities.dp(18), backgroundColor)); + } containerView.addView(progressViewContainer, LayoutHelper.createLinear(86, 86, Gravity.CENTER)); RadialProgressView progressView = new RadialProgressView(getContext(), resourcesProvider); + progressView.setSize(AndroidUtilities.dp(32)); progressView.setProgressColor(getThemedColor(Theme.key_dialog_inlineProgress)); - progressViewContainer.addView(progressView, LayoutHelper.createLinear(86, 86)); + progressViewContainer.addView(progressView, LayoutHelper.createFrame(86, 86, Gravity.CENTER)); } else { scrollContainer.addView(messageTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (topAnimationIsNew ? Gravity.CENTER_HORIZONTAL : LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 24, 0, 24, customView != null || items != null ? customViewOffset : 0)); } if (!TextUtils.isEmpty(message)) { messageTextView.setText(message); + if (customView != null) { + ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) messageTextView.getLayoutParams(); + params.topMargin = AndroidUtilities.dp(16); + } messageTextView.setVisibility(View.VISIBLE); } else { messageTextView.setVisibility(View.GONE); } if (items != null) { - FrameLayout rowLayout = null; - int lastRowLayoutNum = 0; for (int a = 0; a < items.length; a++) { if (items[a] == null) { continue; @@ -928,7 +1055,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati Window window = getWindow(); WindowManager.LayoutParams params = new WindowManager.LayoutParams(); params.copyFrom(window.getAttributes()); - if (progressViewStyle == 3) { + if (progressViewStyle == ALERT_TYPE_SPINNER) { params.width = WindowManager.LayoutParams.MATCH_PARENT; } else { if (dimEnabled && !dimCustom) { @@ -940,7 +1067,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati } lastScreenWidth = AndroidUtilities.displaySize.x; - final int calculatedWidth = AndroidUtilities.displaySize.x - AndroidUtilities.dp(48); + final int calculatedWidth = AndroidUtilities.displaySize.x - AndroidUtilities.dp(48) - additioanalHorizontalPadding * 2; int maxWidth; if (AndroidUtilities.isTablet()) { if (AndroidUtilities.isSmallTablet()) { @@ -963,6 +1090,40 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; } + if (blurredBackground) { + if (supportsNativeBlur()) { + if (progressViewStyle == ALERT_TYPE_MESSAGE) { + blurredNativeBackground = true; + window.setBackgroundBlurRadius(50); + float rad = AndroidUtilities.dp(12); + ShapeDrawable shapeDrawable = new ShapeDrawable(new RoundRectShape(new float[]{rad, rad, rad, rad, rad, rad, rad, rad}, null, null)); + shapeDrawable.getPaint().setColor(ColorUtils.setAlphaComponent(backgroundColor, (int) (blurAlpha * 255))); + window.setBackgroundDrawable(shapeDrawable); + if (blurBehind) { + params.flags |= WindowManager.LayoutParams.FLAG_BLUR_BEHIND; + params.setBlurBehindRadius(20); + } + } + } else { + AndroidUtilities.makeGlobalBlurBitmap(bitmap -> { + if (bitmap == null) { + return; + } + if (blurPaint == null) { + blurPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + } + blurBitmap = bitmap; + blurShader = new BitmapShader(blurBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + blurPaint.setShader(blurShader); + blurMatrix = new Matrix(); + blurMatrix.postScale(8f, 8f); + blurMatrix.postTranslate(-containerViewLocation[0], -containerViewLocation[1]); + blurShader.setLocalMatrix(blurMatrix); + containerView.invalidate(); + }, 8); + } + } + window.setAttributes(params); NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.emojiLoaded); @@ -994,7 +1155,10 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati } public void setBackgroundColor(int color) { - shadowDrawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + backgroundColor = color; + if (shadowDrawable != null) { + shadowDrawable.setColorFilter(new PorterDuffColorFilter(backgroundColor, PorterDuff.Mode.MULTIPLY)); + } } public void setTextColor(int color) { @@ -1011,7 +1175,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati return; } Builder builder = new Builder(getContext()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setTitle(LocaleController.getString("StopLoadingTitle", R.string.StopLoadingTitle)); builder.setMessage(LocaleController.getString("StopLoading", R.string.StopLoading)); builder.setPositiveButton(LocaleController.getString("WaitMore", R.string.WaitMore), null); builder.setNegativeButton(LocaleController.getString("Stop", R.string.Stop), (dialogInterface, i) -> { @@ -1130,6 +1294,13 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati } AndroidUtilities.cancelRunOnUIThread(showRunnable); + + if (blurShader != null && blurBitmap != null) { + blurBitmap.recycle(); + blurShader = null; + blurPaint = null; + blurBitmap = null; + } } @Override @@ -1475,6 +1646,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati public void notDrawBackgroundOnTopView(boolean b) { alertDialog.notDrawBackgroundOnTopView = b; + alertDialog.blurredBackground = false; } public void setButtonsVertical(boolean vertical) { @@ -1485,5 +1657,15 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati alertDialog.onDismissListener = onDismissListener; return this; } + + public Builder setBlurredBackground(boolean b) { + alertDialog.blurredBackground = b; + return this; + } + + public Builder setAdditionalHorizontalPadding(int padding) { + alertDialog.additioanalHorizontalPadding = padding; + return this; + } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BaseFragment.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BaseFragment.java index ddbb39f47..72e688f68 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BaseFragment.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BaseFragment.java @@ -298,6 +298,10 @@ public abstract class BaseFragment { } } + public void setFinishing(boolean finishing) { + this.finishing = finishing; + } + public void finishFragment(boolean animated) { if (isFinished || parentLayout == null) { return; @@ -864,6 +868,15 @@ public abstract class BaseFragment { this.previewDelegate = previewDelegate; } + public void resetFragment() { + if (isFinished) { + clearViews(); + isFinished = false; + finishing = false; + } + } + + public interface PreviewDelegate { void finishFragment(); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java index 33c586fa1..f8c8507b1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java @@ -180,7 +180,7 @@ public class BottomSheet extends Dialog { public boolean useBackgroundTopPadding = true; protected int customViewGravity = Gravity.LEFT | Gravity.TOP; - protected class ContainerView extends FrameLayout implements NestedScrollingParent { + public class ContainerView extends FrameLayout implements NestedScrollingParent { private VelocityTracker velocityTracker = null; private int startedTrackingX; @@ -1386,7 +1386,7 @@ public class BottomSheet extends Dialog { delegate = bottomSheetDelegate; } - public FrameLayout getContainer() { + public ContainerView getContainer() { return container; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java index 6f9024213..43a7a8d57 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java @@ -1849,6 +1849,44 @@ public class Theme { ); } + public static int adaptHue(int color, int hueFromColor) { + float hue, sat; + float[] tempHSV = getTempHsv(5); + Color.colorToHSV(hueFromColor, tempHSV); + hue = tempHSV[0]; + sat = tempHSV[1]; + Color.colorToHSV(color, tempHSV); + tempHSV[0] = hue; + tempHSV[1] = AndroidUtilities.lerp(tempHSV[1], sat, .25f); + return Color.HSVToColor(Color.alpha(color), tempHSV); + } + + public static int adaptHSV(int color, float sat, float val) { + float[] tempHSV = getTempHsv(5); + Color.colorToHSV(color, tempHSV); + tempHSV[1] = MathUtils.clamp(tempHSV[1] + sat, 0, 1); + tempHSV[2] = MathUtils.clamp(tempHSV[2] + val, 0, 1); + return Color.HSVToColor(Color.alpha(color), tempHSV); + } + + public static int percentSV(int color1, int color2, float satT, float valT) { + float[] tempHSV = getTempHsv(5); + Color.colorToHSV(color2, tempHSV); + float sat2 = tempHSV[1]; + float val2 = tempHSV[2]; + Color.colorToHSV(color1, tempHSV); + tempHSV[1] = MathUtils.clamp(AndroidUtilities.lerp(tempHSV[1], sat2, satT), 0, 1); + tempHSV[2] = MathUtils.clamp(AndroidUtilities.lerp(tempHSV[2], val2, valT), 0, 1); + return Color.HSVToColor( + AndroidUtilities.lerp(Color.alpha(color1), Color.alpha(color2), .85f), + tempHSV + ); + } + + public static int multAlpha(int color, float multiply) { + return ColorUtils.setAlphaComponent(color, MathUtils.clamp((int) (Color.alpha(color) * multiply), 0, 0xFF)); + } + public static int reverseBlendOver(float ax, int y, int z) { float ay = Color.alpha(y) / 255f, az = Color.alpha(z) / 255f; @@ -3489,6 +3527,7 @@ public class Theme { public static final String key_chat_serviceIcon = "chat_serviceIcon"; public static final String key_chat_serviceBackground = "chat_serviceBackground"; public static final String key_chat_serviceBackgroundSelected = "chat_serviceBackgroundSelected"; + public static final String key_chat_serviceBackgroundSelector = "chat_serviceBackgroundSelector"; public static final String key_chat_muteIcon = "chat_muteIcon"; public static final String key_chat_lockIcon = "chat_lockIcon"; public static final String key_chat_outSentCheck = "chat_outSentCheck"; @@ -3964,6 +4003,8 @@ public class Theme { public final static String key_statisticChartLine_lightgreen = "statisticChartLine_lightgreen"; public final static String key_statisticChartLine_orange = "statisticChartLine_orange"; public final static String key_statisticChartLine_indigo = "statisticChartLine_indigo"; + public final static String key_statisticChartLine_purple = "statisticChartLine_purple"; + public final static String key_statisticChartLine_cyan = "statisticChartLine_cyan"; public final static String key_statisticChartLineEmpty = "statisticChartLineEmpty"; public static final String key_chat_outReactionButtonBackground = "chat_outReactionButtonBackground"; @@ -4106,7 +4147,7 @@ public class Theme { defaultColors.put(key_dialogCheckboxSquareDisabled, 0xffb0b0b0); defaultColors.put(key_dialogRadioBackground, 0xffb3b3b3); defaultColors.put(key_dialogRadioBackgroundChecked, 0xff37a9f0); - defaultColors.put(key_dialogProgressCircle, 0xff289deb); + defaultColors.put(key_dialogProgressCircle, 0xff0A0D0F); defaultColors.put(key_dialogLineProgress, 0xff527da3); defaultColors.put(key_dialogLineProgressBackground, 0xffdbdbdb); defaultColors.put(key_dialogButton, 0xff4991cc); @@ -4626,6 +4667,7 @@ public class Theme { defaultColors.put(key_chat_mediaLoaderPhotoIconSelected, 0xffd9d9d9); defaultColors.put(key_chat_secretTimerBackground, 0xcc3e648e); defaultColors.put(key_chat_secretTimerText, 0xffffffff); + defaultColors.put(key_chat_serviceBackgroundSelector, 0x20ffffff); defaultColors.put(key_profile_creatorIcon, 0xff3a95d5); defaultColors.put(key_profile_actionIcon, 0xff81868a); @@ -4796,11 +4838,13 @@ public class Theme { defaultColors.put(key_statisticChartLine_blue, 0xff327FE5); defaultColors.put(key_statisticChartLine_green, 0xff61C752); defaultColors.put(key_statisticChartLine_red, 0xffE05356); - defaultColors.put(key_statisticChartLine_golden, 0xffDEBA08); + defaultColors.put(key_statisticChartLine_golden, 0xffEBA52D); defaultColors.put(key_statisticChartLine_lightblue, 0xff58A8ED); defaultColors.put(key_statisticChartLine_lightgreen, 0xff8FCF39); - defaultColors.put(key_statisticChartLine_orange, 0xffE3B727); + defaultColors.put(key_statisticChartLine_orange, 0xffF28C39); defaultColors.put(key_statisticChartLine_indigo, 0xff7F79F3); + defaultColors.put(key_statisticChartLine_purple, 0xff9F79E8); + defaultColors.put(key_statisticChartLine_cyan, 0xff40D0CA); defaultColors.put(key_statisticChartLineEmpty, 0xFFEEEEEE); defaultColors.put(key_actionBarTipBackground, 0xFF446F94); @@ -5849,13 +5893,13 @@ public class Theme { if (monthOfYear == 0 && dayOfMonth == 1 && hour <= 23) { canStartHolidayAnimation = true; } else { - canStartHolidayAnimation = false; + canStartHolidayAnimation = BuildVars.DEBUG_VERSION;//false; } if (dialogs_holidayDrawable == null) { if (monthOfYear == 11 && dayOfMonth >= (BuildVars.DEBUG_PRIVATE_VERSION ? 29 : 31) && dayOfMonth <= 31 || monthOfYear == 0 && dayOfMonth == 1) { dialogs_holidayDrawable = ApplicationLoader.applicationContext.getResources().getDrawable(R.drawable.newyear); dialogs_holidayDrawableOffsetX = -AndroidUtilities.dp(3); - dialogs_holidayDrawableOffsetY = -AndroidUtilities.dp(1); + dialogs_holidayDrawableOffsetY = -AndroidUtilities.dp(-7); } } } @@ -6680,7 +6724,7 @@ public class Theme { } } - public static void setMaskDrawableRad(Drawable rippleDrawable, int topLeftRad, int topRightRad, int bottomRightRad, int bottomLeftRad) { + public static void setMaskDrawableRad(Drawable rippleDrawable, float topLeftRad, float topRightRad, float bottomRightRad, float bottomLeftRad) { if (Build.VERSION.SDK_INT < 21) { return; } @@ -9821,7 +9865,7 @@ public class Theme { } Drawable drawable = wallpaperOverride != null ? wallpaperOverride : currentWallpaper; - boolean drawServiceGradient = drawable instanceof MotionBackgroundDrawable && SharedConfig.getDevicePerformanceClass() != SharedConfig.PERFORMANCE_CLASS_LOW; + boolean drawServiceGradient = drawable instanceof MotionBackgroundDrawable && SharedConfig.getDevicePerformanceClass() != SharedConfig.PERFORMANCE_CLASS_LOW && !SharedConfig.getLiteMode().enabled(); if (drawServiceGradient) { Bitmap newBitmap = ((MotionBackgroundDrawable) drawable).getBitmap(); if (serviceBitmap != newBitmap) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java index 59af7a2ed..620e73e6a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java @@ -232,7 +232,12 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter implements super(viewType, true); this.dialog = dialog; if (dialog != null) { - pinned = dialog.pinned; + if (dialogsType == 7 || dialogsType == 8) { + MessagesController.DialogFilter filter = MessagesController.getInstance(currentAccount).selectedDialogFilter[dialogsType == 8 ? 1 : 0]; + pinned = filter != null && filter.pinnedDialogs.indexOfKey(dialog.id) >= 0; + } else { + pinned = dialog.pinned; + } isFolder = dialog.isFolder; isForumCell = MessagesController.getInstance(currentAccount).isForum(dialog.id); } @@ -366,12 +371,12 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter implements hasHints = folderId == 0 && dialogsType == 0 && !isOnlySelect && !MessagesController.getInstance(currentAccount).hintDialogs.isEmpty(); } - public void updateList(RecyclerListView recyclerListView, boolean hasHiddenArchive) { + public void updateList(RecyclerListView recyclerListView, boolean hasHiddenArchive, float tabsTranslation) { oldItems.clear(); oldItems.addAll(itemInternals); updateItemList(); - if (recyclerListView != null && recyclerListView.getChildCount() > 0 && recyclerListView.getLayoutManager() != null) { + if (recyclerListView != null && recyclerListView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE && recyclerListView.getChildCount() > 0 && recyclerListView.getLayoutManager() != null) { LinearLayoutManager layoutManager = ((LinearLayoutManager) recyclerListView.getLayoutManager()); View view = null; int position = -1; @@ -379,19 +384,19 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter implements for (int i = 0; i < recyclerListView.getChildCount(); i++) { int childPosition = recyclerListView.getChildAdapterPosition(recyclerListView.getChildAt(i)); View child = recyclerListView.getChildAt(i); - if (childPosition != RecyclerListView.NO_POSITION && child.getTop() < top) { + if (childPosition != RecyclerListView.NO_POSITION && child != null && child.getTop() < top) { view = child; position = childPosition; top = child.getTop(); } } if (view != null) { - int offset = view.getTop() - recyclerListView.getPaddingTop(); - if (hasHiddenArchive && position == 0 && view.getTop() - recyclerListView.getPaddingTop() < AndroidUtilities.dp(SharedConfig.useThreeLinesLayout ? 78 : 72)) { + float offset = view.getTop() - recyclerListView.getPaddingTop() + tabsTranslation; + if (hasHiddenArchive && position == 0 && view.getTop() - recyclerListView.getPaddingTop() + tabsTranslation < AndroidUtilities.dp(SharedConfig.useThreeLinesLayout ? 78 : 72)) { position = 1; - offset = 0; + offset = tabsTranslation; } - layoutManager.scrollToPositionWithOffset(position, offset); + layoutManager.scrollToPositionWithOffset(position, (int) offset); } } @@ -825,7 +830,7 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter implements toDialog.pinnedNum = oldNum; } Collections.swap(dialogs, fromIndex, toIndex); - updateList(recyclerView, false); + updateList(recyclerView, false, 0); } @Override public void notifyItemMoved(int fromPosition, int toPosition) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java index 7a2cadc89..caccbdc37 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java @@ -70,11 +70,12 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { private final int VIEW_TYPE_HASHTAG_CELL = 5; private final int VIEW_TYPE_CATEGORY_LIST = 6; private final int VIEW_TYPE_ADD_BY_PHONE = 7; - + private final int VIEW_TYPE_INVITE_CONTACT_CELL = 8; private Context mContext; private Runnable searchRunnable; private Runnable searchRunnable2; private ArrayList searchResult = new ArrayList<>(); + private ArrayList searchContacts = new ArrayList<>(); private ArrayList searchTopics = new ArrayList<>(); private ArrayList searchResultNames = new ArrayList<>(); private ArrayList searchForumResultMessages = new ArrayList<>(); @@ -121,6 +122,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { private FilteredSearchView.Delegate filtersDelegate; private int currentItemCount; private int folderId; + private ArrayList allContacts; public boolean isSearching() { return waitingResponseCount > 0; @@ -212,7 +214,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { } } - public DialogsSearchAdapter(Context context, int messagesSearch, int type, DefaultItemAnimator itemAnimator) { + public DialogsSearchAdapter(Context context, int messagesSearch, int type, DefaultItemAnimator itemAnimator, boolean allowGlobalSearch) { this.itemAnimator = itemAnimator; searchAdapterHelper = new SearchAdapterHelper(false); searchAdapterHelper.setDelegate(new SearchAdapterHelper.SearchAdapterHelperDelegate() { @@ -252,6 +254,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { return searchId == lastSearchId; } }); + searchAdapterHelper.setAllowGlobalResults(allowGlobalSearch); mContext = context; needMessagesSearch = messagesSearch; dialogsType = type; @@ -540,7 +543,11 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { } public boolean hasRecentSearch() { - return dialogsType != 2 && dialogsType != 4 && dialogsType != 5 && dialogsType != 6 && dialogsType != 11 && getRecentItemsCount() > 0; + return resentSearchAvailable() && getRecentItemsCount() > 0; + } + + private boolean resentSearchAvailable() { + return dialogsType != 2 && dialogsType != 4 && dialogsType != 5 && dialogsType != 6 && dialogsType != 11; } public boolean isSearchWas() { @@ -777,15 +784,31 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { String q = query.trim().toLowerCase(); if (q.length() == 0) { lastSearchId = 0; - updateSearchResults(new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), lastSearchId); + updateSearchResults(new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), lastSearchId); return; } MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> { ArrayList resultArray = new ArrayList<>(); ArrayList resultArrayNames = new ArrayList<>(); ArrayList encUsers = new ArrayList<>(); + ArrayList contacts = new ArrayList<>(); MessagesStorage.getInstance(currentAccount).localSearch(dialogsType, q, resultArray, resultArrayNames, encUsers, -1); - updateSearchResults(resultArray, resultArrayNames, encUsers, searchId); +// if (allContacts == null) { +// allContacts = new ArrayList<>(); +// for (ContactsController.Contact contact : ContactsController.getInstance(currentAccount).phoneBookContacts) { +// ContactEntry contactEntry = new ContactEntry(); +// contactEntry.contact = contact; +// contactEntry.q1 = (contact.first_name + " " + contact.last_name).toLowerCase(); +// contactEntry.q2 = (contact.last_name + " " + contact.first_name).toLowerCase(); +// allContacts.add(contactEntry); +// } +// } +// for (int i = 0; i < allContacts.size(); i++) { +// if (allContacts.get(i).q1.toLowerCase().contains(q) || allContacts.get(i).q1.toLowerCase().contains(q)) { +// contacts.add(allContacts.get(i).contact); +// } +// } + updateSearchResults(resultArray, resultArrayNames, encUsers, contacts, searchId); FiltersView.fillTipDates(q, localTipDates); localTipArchive = false; if (q.length() >= 3 && (LocaleController.getString("ArchiveSearchFilter", R.string.ArchiveSearchFilter).toLowerCase().startsWith(q) || "archive".startsWith(query))) { @@ -800,7 +823,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { } - private void updateSearchResults(final ArrayList result, final ArrayList names, final ArrayList encUsers, final int searchId) { + private void updateSearchResults(final ArrayList result, final ArrayList names, final ArrayList encUsers, final ArrayList contacts, final int searchId) { AndroidUtilities.runOnUIThread(() -> { waitingResponseCount--; if (searchId != lastSearchId) { @@ -853,25 +876,28 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { } } - boolean foundInRecent = false; - if (delegate != null && delegate.getSearchForumDialogId() == dialogId) { - foundInRecent = true; - } - for (int j = 0; !foundInRecent && j < recentCount; ++j) { - RecentSearchObject o = filtered2RecentSearchObjects.get(j); - if (o != null && o.did == dialogId) { + if (resentSearchAvailable()) { + boolean foundInRecent = false; + if (delegate != null && delegate.getSearchForumDialogId() == dialogId) { foundInRecent = true; } - } - if (foundInRecent) { - result.remove(a); - names.remove(a); - a--; + for (int j = 0; !foundInRecent && j < recentCount; ++j) { + RecentSearchObject o = filtered2RecentSearchObjects.get(j); + if (o != null && o.did == dialogId) { + foundInRecent = true; + } + } + if (foundInRecent) { + result.remove(a); + names.remove(a); + a--; + } } } MessagesController.getInstance(currentAccount).putUsers(encUsers, true); searchResult = result; searchResultNames = names; + // searchContacts = contacts; searchAdapterHelper.mergeResults(searchResult, filtered2RecentSearchObjects); notifyDataSetChanged(); if (delegate != null) { @@ -1024,8 +1050,13 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { } if (!searchTopics.isEmpty()) { count++; + count += searchTopics.size(); } - count += searchTopics.size(); + if (!searchContacts.isEmpty()) { + int contactsCount = searchContacts.size(); + count += contactsCount + 1; + } + int resultsCount = searchResult.size(); count += resultsCount; int localServerCount = searchAdapterHelper.getLocalServerSearch().size(); @@ -1102,6 +1133,12 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { } i -= 1 + searchTopics.size(); } + if (!searchContacts.isEmpty()) { + if (i > 0 && i <= searchContacts.size()) { + return searchContacts.get(i - 1); + } + i -= 1 + searchContacts.size(); + } ArrayList globalSearch = searchAdapterHelper.getGlobalSearch(); ArrayList localServerSearch = searchAdapterHelper.getLocalServerSearch(); ArrayList phoneSearch = searchAdapterHelper.getPhoneSearch(); @@ -1178,7 +1215,11 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { if (globalCount > 4 && globalSearchCollapsed) { globalCount = 4; } - + int contactsCount = searchContacts.size(); + if (i >= 0 && i < contactsCount) { + return false; + } + i -= contactsCount + 1; if (i >= 0 && i < localCount) { return false; } @@ -1286,6 +1327,9 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { view = horizontalListView; innerListView = horizontalListView; break; + case VIEW_TYPE_INVITE_CONTACT_CELL: + view = new ProfileSearchCell(mContext); + break; case VIEW_TYPE_ADD_BY_PHONE: default: view = new TextCell(mContext, 16, false); @@ -1449,7 +1493,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { }); } else { int rawPosition = position; - if (isRecentSearchDisplayed() || !searchTopics.isEmpty()) { + if (isRecentSearchDisplayed() || !searchTopics.isEmpty() || !searchContacts.isEmpty()) { int offset = (!searchWas && !MediaDataController.getInstance(currentAccount).hints.isEmpty() ? 1 : 0); if (position < offset) { cell.setText(LocaleController.getString("ChatHints", R.string.ChatHints)); @@ -1469,7 +1513,7 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { }); } return; - } else if (position == getRecentItemsCount() + (searchTopics.isEmpty() ? 0 : searchTopics.size() + 1)) { + } else if (position == getRecentItemsCount() + (searchTopics.isEmpty() ? 0 : searchTopics.size() + 1) + (searchContacts.isEmpty() ? 0 : searchContacts.size() + 1)) { cell.setText(LocaleController.getString("SearchAllChatsShort", R.string.SearchAllChatsShort)); return; } else { @@ -1498,6 +1542,12 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { } position -= 1 + searchTopics.size(); } + if (!searchContacts.isEmpty()) { + if (position == 0) { + title = LocaleController.getString("InviteToTelegramShort", R.string.InviteToTelegramShort); + } + position -= 1 + searchContacts.size(); + } if (title == null) { position -= localCount + localServerCount; if (position >= 0 && position < phoneCount) { @@ -1630,6 +1680,12 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { cell.setText(LocaleController.formatString("AddContactByPhone", R.string.AddContactByPhone, PhoneFormat.getInstance().format("+" + str)), false); break; } + case VIEW_TYPE_INVITE_CONTACT_CELL: { + ProfileSearchCell profileSearchCell = (ProfileSearchCell) holder.itemView; + ContactsController.Contact contact = (ContactsController.Contact) getItem(position); + profileSearchCell.setData(contact, null, ContactsController.formatName(contact.first_name, contact.last_name), PhoneFormat.getInstance().format("+" + contact.shortPhones.get(0)), false, false); + break; + } } } @@ -1662,6 +1718,15 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { } i -= 1 + searchTopics.size(); } + + if (!searchContacts.isEmpty()) { + if (i == 0) { + return VIEW_TYPE_GRAY_SECTION; + } else if (i <= searchContacts.size()) { + return VIEW_TYPE_INVITE_CONTACT_CELL; + } + i -= 1 + searchContacts.size(); + } ArrayList globalSearch = searchAdapterHelper.getGlobalSearch(); int localCount = searchResult.size(); int localServerCount = searchAdapterHelper.getLocalServerSearch().size(); @@ -1809,4 +1874,10 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { public interface OnRecentSearchLoaded { void setRecentSearch(ArrayList arrayList, LongSparseArray hashMap); } + + private static class ContactEntry { + String q1; + String q2; + ContactsController.Contact contact; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java index 560a90829..f87e0bd06 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java @@ -63,6 +63,8 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { private boolean searchInProgress; private int searchReqId; private int searchPointer; + private ArrayList allUnregistredContacts; + private ArrayList unregistredContacts = new ArrayList<>(); public SearchAdapter(Context context, LongSparseArray arg1, boolean usernameSearch, boolean mutual, boolean chats, boolean bots, boolean self, boolean phones, int searchChannelId) { @@ -109,6 +111,7 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { FileLog.e(e); } searchResult.clear(); + unregistredContacts.clear(); searchResultNames.clear(); if (allowUsernameSearch) { searchAdapterHelper.queryServerSearch(null, true, allowChats, allowBots, allowSelf, false, channelId, allowPhoneNumbers, 0, 0); @@ -144,7 +147,7 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { Utilities.searchQueue.postRunnable(() -> { String search1 = query.trim().toLowerCase(); if (search1.length() == 0) { - updateSearchResults(searchReqIdFinal, new ArrayList<>(), new ArrayList<>()); + updateSearchResults(searchReqIdFinal, new ArrayList<>(), new ArrayList<>(), unregistredContacts); return; } String search2 = LocaleController.getInstance().getTranslitString(search1); @@ -159,6 +162,7 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { ArrayList resultArray = new ArrayList<>(); ArrayList resultArrayNames = new ArrayList<>(); + ArrayList unregistredContacts = new ArrayList<>(); for (int a = 0; a < contactsCopy.size(); a++) { TLRPC.TL_contact contact = contactsCopy.get(a); @@ -203,16 +207,35 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { } } } - updateSearchResults(searchReqIdFinal, resultArray, resultArrayNames); + + if (allUnregistredContacts == null) { + allUnregistredContacts = new ArrayList<>(); + for (ContactsController.Contact contact : ContactsController.getInstance(currentAccount).phoneBookContacts) { + ContactEntry contactEntry = new ContactEntry(); + contactEntry.contact = contact; + contactEntry.q1 = (contact.first_name + " " + contact.last_name).toLowerCase(); + contactEntry.q2 = (contact.last_name + " " + contact.first_name).toLowerCase(); + allUnregistredContacts.add(contactEntry); + } + } + for (int i = 0; i < allUnregistredContacts.size(); i++) { + ContactEntry contact = allUnregistredContacts.get(i); + if ((search2 != null && (contact.q1.toLowerCase().contains(search2) || contact.q1.toLowerCase().contains(search2))) || contact.q1.toLowerCase().contains(search1) || contact.q1.toLowerCase().contains(search1)) { + unregistredContacts.add(contact.contact); + } + } + + updateSearchResults(searchReqIdFinal, resultArray, resultArrayNames, unregistredContacts); }); }); } - private void updateSearchResults(int searchReqIdFinal, final ArrayList users, final ArrayList names) { + private void updateSearchResults(int searchReqIdFinal, final ArrayList users, final ArrayList names, ArrayList unregistredContacts) { AndroidUtilities.runOnUIThread(() -> { if (searchReqIdFinal == searchReqId) { searchResult = users; searchResultNames = names; + this.unregistredContacts = unregistredContacts; searchAdapterHelper.mergeResults(users); searchInProgress = false; notifyDataSetChanged(); @@ -232,12 +255,19 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { @Override public boolean isEnabled(RecyclerView.ViewHolder holder) { int type = holder.getItemViewType(); - return type == 0 || type == 2; + return type == 0 || type == 2 || type == 3; } + int unregistredContactsHeaderRow; @Override public int getItemCount() { + unregistredContactsHeaderRow = -1; int count = searchResult.size(); + unregistredContactsHeaderRow = count; + if (!unregistredContacts.isEmpty()) { + count += unregistredContacts.size() + 1; + } + int globalCount = searchAdapterHelper.getGlobalSearch().size(); if (globalCount != 0) { count += globalCount + 1; @@ -251,13 +281,16 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { public boolean isGlobalSearch(int i) { int localCount = searchResult.size(); + int unregistredCount = unregistredContacts.size(); int globalCount = searchAdapterHelper.getGlobalSearch().size(); int phoneCount = searchAdapterHelper.getPhoneSearch().size(); if (i >= 0 && i < localCount) { return false; - } else if (i > localCount && i < localCount + phoneCount) { + } else if (i > localCount && i < localCount + unregistredCount + 1) { return false; - } else if (i > localCount + phoneCount && i <= globalCount + phoneCount + localCount) { + } else if (i > localCount + unregistredCount + 1 && i < localCount + phoneCount + unregistredCount + 1) { + return false; + } else if (i > localCount + phoneCount + unregistredCount + 1 && i <= globalCount + phoneCount + localCount + unregistredCount + 1) { return true; } return false; @@ -265,12 +298,23 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { public Object getItem(int i) { int localCount = searchResult.size(); + int unregistredCount = unregistredContacts.size(); int globalCount = searchAdapterHelper.getGlobalSearch().size(); int phoneCount = searchAdapterHelper.getPhoneSearch().size(); if (i >= 0 && i < localCount) { return searchResult.get(i); } else { i -= localCount; + if (unregistredCount > 0) { + if (i == 0) { + return null; + } + if (i > 0 && i <= unregistredCount) { + return unregistredContacts.get(i - 1); + } else { + i -= unregistredCount + 1; + } + } if (i >= 0 && i < phoneCount) { return searchAdapterHelper.getPhoneSearch().get(i); } else { @@ -304,6 +348,9 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { default: view = new TextCell(mContext, 16, false); break; + case 3: + view = new ProfileSearchCell(mContext); + break; } return new RecyclerListView.Holder(view); } @@ -388,7 +435,9 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { } case 1: { GraySectionCell cell = (GraySectionCell) holder.itemView; - if (getItem(position) == null) { + if (position == unregistredContactsHeaderRow) { + cell.setText(LocaleController.getString("InviteToTelegramShort", R.string.InviteToTelegramShort)); + } else if (getItem(position) == null) { cell.setText(LocaleController.getString("GlobalSearch", R.string.GlobalSearch)); } else { cell.setText(LocaleController.getString("PhoneNumberSearch", R.string.PhoneNumberSearch)); @@ -402,6 +451,13 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { cell.setText(LocaleController.formatString("AddContactByPhone", R.string.AddContactByPhone, PhoneFormat.getInstance().format("+" + str)), false); break; } + case 3: { + ProfileSearchCell profileSearchCell = (ProfileSearchCell) holder.itemView; + ContactsController.Contact contact = (ContactsController.Contact) getItem(position); + profileSearchCell.useSeparator = getItem(position + 1) instanceof ContactsController.Contact ; + profileSearchCell.setData(contact, null, ContactsController.formatName(contact.first_name, contact.last_name), PhoneFormat.getInstance().format("+" + contact.shortPhones.get(0)), false, false); + break; + } } } @@ -417,7 +473,15 @@ public class SearchAdapter extends RecyclerListView.SelectionAdapter { } else { return 2; } + } else if (item instanceof ContactsController.Contact) { + return 3; } return 0; } + + private static class ContactEntry { + String q1; + String q2; + ContactsController.Contact contact; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java index 83c4b1441..94e63299e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java @@ -3687,7 +3687,7 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg textSelectionHelper.setParentView(listView[0]); if (MessagesController.getGlobalMainSettings().getBoolean("translate_button", false)) { textSelectionHelper.setOnTranslate((text, fromLang, toLang, onAlertDismiss) -> { - TranslateAlert.showAlert(parentActivity, parentFragment, fromLang, toLang, text, false, null, onAlertDismiss); + TranslateAlert.showAlert(parentActivity, parentFragment, currentAccount, fromLang, toLang, text, false, null, onAlertDismiss); }); } textSelectionHelper.layoutManager = layoutManager[0]; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CacheChatsExceptionsFragment.java b/TMessagesProj/src/main/java/org/telegram/ui/CacheChatsExceptionsFragment.java new file mode 100644 index 000000000..2290b737f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/CacheChatsExceptionsFragment.java @@ -0,0 +1,327 @@ +package org.telegram.ui; + +import android.content.Context; +import android.os.Bundle; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.CacheByChatsController; +import org.telegram.messenger.ContactsController; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.R; +import org.telegram.tgnet.TLObject; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.ActionBarPopupWindow; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.BackDrawable; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Cells.ShadowSectionCell; +import org.telegram.ui.Cells.TextCell; +import org.telegram.ui.Cells.UserCell; +import org.telegram.ui.Components.AlertsCreator; +import org.telegram.ui.Components.ListView.AdapterWithDiffUtils; +import org.telegram.ui.Components.RecyclerListView; + +import java.util.ArrayList; + +public class CacheChatsExceptionsFragment extends BaseFragment { + + private final int VIEW_TYPE_ADD_EXCEPTION = 1; + private final int VIEW_TYPE_CHAT= 2; + private final int VIEW_TYPE_DIVIDER = 3; + private final int VIEW_TYPE_DELETE_ALL = 4; + + Adapter adapter; + + RecyclerListView recyclerListView; + + ArrayList items = new ArrayList<>(); + ArrayList exceptionsDialogs = new ArrayList<>(); + int currentType; + + public CacheChatsExceptionsFragment(Bundle bundle) { + super(bundle); + } + + @Override + public View createView(Context context) { + FrameLayout frameLayout = new FrameLayout(context); + fragmentView = frameLayout; + + actionBar.setBackButtonDrawable(new BackDrawable(false)); + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + return; + } + } + }); + actionBar.setTitle(LocaleController.getString(R.string.NotificationsExceptions)); + recyclerListView = new RecyclerListView(context); + DefaultItemAnimator defaultItemAnimator = new DefaultItemAnimator(); + defaultItemAnimator.setDelayAnimations(false); + defaultItemAnimator.setSupportsChangeAnimations(false); + recyclerListView.setItemAnimator(defaultItemAnimator); + recyclerListView.setLayoutManager(new LinearLayoutManager(context)); + recyclerListView.setAdapter(adapter = new Adapter()); + recyclerListView.setOnItemClickListener((view, position, x, y) -> { + if (items.get(position).viewType == VIEW_TYPE_ADD_EXCEPTION) { + Bundle args = new Bundle(); + args.putBoolean("onlySelect", true); + args.putBoolean("checkCanWrite", false); + if (currentType == CacheControlActivity.KEEP_MEDIA_TYPE_GROUP) { + args.putInt("dialogsType", 6); + } else if (currentType == CacheControlActivity.KEEP_MEDIA_TYPE_CHANNEL) { + args.putInt("dialogsType", 5); + } else { + args.putInt("dialogsType", 4); + } + args.putBoolean("allowGlobalSearch", false); + DialogsActivity activity = new DialogsActivity(args); + activity.setDelegate((fragment, dids, message, param) -> { + activity.finishFragment(); + CacheByChatsController.KeepMediaException newException = null; + for (int i = 0; i < dids.size(); i++) { + boolean contains = false; + for (int k = 0; k < exceptionsDialogs.size(); k++) { + if (exceptionsDialogs.get(k).dialogId == dids.get(i).dialogId) { + newException = exceptionsDialogs.get(k); + contains = true; + break; + } + } + if (!contains) { + int startFrom = CacheByChatsController.KEEP_MEDIA_FOREVER; + if (getMessagesController().getCacheByChatsController().getKeepMedia(currentType) == CacheByChatsController.KEEP_MEDIA_FOREVER) { + startFrom = CacheByChatsController.KEEP_MEDIA_ONE_DAY; + } + exceptionsDialogs.add(newException = new CacheByChatsController.KeepMediaException(dids.get(i).dialogId, startFrom)); + } + } + getMessagesController().getCacheByChatsController().saveKeepMediaExceptions(currentType, exceptionsDialogs); + updateRows(); + if (newException != null) { + int p = 0; + for (int i = 0; i < items.size(); i++) { + if (items.get(i).exception != null && items.get(i).exception.dialogId == newException.dialogId) { + p = i; + break; + } + } + recyclerListView.scrollToPosition(p); + int finalP = p; + showPopupFor(newException); + } + }); + presentFragment(activity); + } else if (items.get(position).viewType == VIEW_TYPE_CHAT) { + CacheByChatsController.KeepMediaException keepMediaException = items.get(position).exception; + KeepMediaPopupView windowLayout = new KeepMediaPopupView(CacheChatsExceptionsFragment.this, view.getContext()); + windowLayout.updateForDialog(false); + ActionBarPopupWindow popupWindow = AlertsCreator.createSimplePopup(CacheChatsExceptionsFragment.this, windowLayout, view, x, y); + windowLayout.setParentWindow(popupWindow); + windowLayout.setCallback((type, keepMedia) -> { + if (keepMedia == CacheByChatsController.KEEP_MEDIA_DELETE) { + exceptionsDialogs.remove(keepMediaException); + updateRows(); + } else { + keepMediaException.keepMedia = keepMedia; + AndroidUtilities.updateVisibleRows(recyclerListView); + } + + getMessagesController().getCacheByChatsController().saveKeepMediaExceptions(currentType, exceptionsDialogs); + + }); + } else if (items.get(position).viewType == VIEW_TYPE_DELETE_ALL) { + AlertDialog alertDialog = AlertsCreator.createSimpleAlert(getContext(), + LocaleController.getString("NotificationsDeleteAllExceptionTitle", R.string.NotificationsDeleteAllExceptionTitle), + LocaleController.getString("NotificationsDeleteAllExceptionAlert", R.string.NotificationsDeleteAllExceptionAlert), + LocaleController.getString("Delete", R.string.Delete), + () -> { + exceptionsDialogs.clear(); + getMessagesController().getCacheByChatsController().saveKeepMediaExceptions(currentType, exceptionsDialogs); + updateRows(); + finishFragment(); + }, null).create(); + alertDialog.show(); + alertDialog.redPositive(); + } + }); + frameLayout.addView(recyclerListView); + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); + updateRows(); + return fragmentView; + } + + public void showPopupFor(CacheByChatsController.KeepMediaException newException) { + AndroidUtilities.runOnUIThread(() -> { + int p = 0; + for (int i = 0; i < items.size(); i++) { + if (items.get(i).exception != null && items.get(i).exception.dialogId == newException.dialogId) { + p = i; + break; + } + } + RecyclerView.ViewHolder viewHolder = recyclerListView.findViewHolderForAdapterPosition(p); + if (viewHolder != null) { + KeepMediaPopupView windowLayout = new KeepMediaPopupView(CacheChatsExceptionsFragment.this, getContext()); + windowLayout.updateForDialog(true); + ActionBarPopupWindow popupWindow = AlertsCreator.createSimplePopup(CacheChatsExceptionsFragment.this, windowLayout, viewHolder.itemView, viewHolder.itemView.getMeasuredWidth() / 2f, viewHolder.itemView.getMeasuredHeight() / 2f); + windowLayout.setParentWindow(popupWindow); + windowLayout.setCallback((type, keepMedia) -> { + newException.keepMedia = keepMedia; + getMessagesController().getCacheByChatsController().saveKeepMediaExceptions(currentType, exceptionsDialogs); + AndroidUtilities.updateVisibleRows(recyclerListView); + }); + } + }, 150); + } + + @Override + public boolean onFragmentCreate() { + currentType = getArguments().getInt("type"); + updateRows(); + return super.onFragmentCreate(); + } + + private void updateRows() { + boolean animated = !isPaused && adapter != null; + ArrayList oldItems = null; + if (animated) { + oldItems = new ArrayList(); + oldItems.addAll(items); + } + + items.clear(); + items.add(new Item(VIEW_TYPE_ADD_EXCEPTION, null)); + boolean added = false; + for (CacheByChatsController.KeepMediaException exception : exceptionsDialogs) { + items.add(new Item(VIEW_TYPE_CHAT, exception)); + added = true; + } + + if (added) { + items.add(new Item(VIEW_TYPE_DIVIDER, null)); + items.add(new Item(VIEW_TYPE_DELETE_ALL, null)); + } + items.add(new Item(VIEW_TYPE_DIVIDER, null)); + + if (adapter != null) { + if (oldItems != null) { + adapter.setItems(oldItems, items); + } else { + adapter.notifyDataSetChanged(); + } + } + } + + public void setExceptions(ArrayList notificationsExceptionTopics) { + exceptionsDialogs = notificationsExceptionTopics; + updateRows(); + } + + private class Adapter extends AdapterWithDiffUtils { + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = null; + switch (viewType) { + case VIEW_TYPE_CHAT: + view = new UserCell(parent.getContext(), 4, 0, false, false); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case VIEW_TYPE_ADD_EXCEPTION: + TextCell textCell = new TextCell(parent.getContext()); + textCell.setTextAndIcon(LocaleController.getString("NotificationsAddAnException", R.string.NotificationsAddAnException), R.drawable.msg_contact_add, true); + textCell.setColors(Theme.key_windowBackgroundWhiteBlueIcon, Theme.key_windowBackgroundWhiteBlueButton); + view = textCell; + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case VIEW_TYPE_DIVIDER: + view = new ShadowSectionCell(parent.getContext()); + break; + case VIEW_TYPE_DELETE_ALL: + textCell = new TextCell(parent.getContext()); + textCell.setText(LocaleController.getString("NotificationsDeleteAllException", R.string.NotificationsDeleteAllException), false); + textCell.setColors(null, Theme.key_windowBackgroundWhiteRedText5); + view = textCell; + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + } + view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + if (items.get(position).viewType == VIEW_TYPE_CHAT) { + UserCell cell = (UserCell) holder.itemView; + CacheByChatsController.KeepMediaException exception = items.get(position).exception; + TLObject object = getMessagesController().getUserOrChat(exception.dialogId); + String title = null; + if (object instanceof TLRPC.User) { + TLRPC.User user = (TLRPC.User) object; + if (user.self) { + title = LocaleController.getString("SavedMessages", R.string.SavedMessages); + } else { + title = ContactsController.formatName(user.first_name, user.last_name); + } + } else if (object instanceof TLRPC.Chat) { + TLRPC.Chat chat = (TLRPC.Chat) object; + title = chat.title; + } + cell.setSelfAsSavedMessages(true); + cell.setData(object, title, CacheByChatsController.getKeepMediaString(exception.keepMedia), 0, !(position != items.size() - 1 && items.get(position + 1).viewType != VIEW_TYPE_CHAT)); + } + } + + @Override + public int getItemCount() { + return items.size(); + } + + @Override + public int getItemViewType(int position) { + return items.get(position).viewType; + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return holder.getItemViewType() == VIEW_TYPE_ADD_EXCEPTION || holder.getItemViewType() == VIEW_TYPE_CHAT || holder.getItemViewType() == VIEW_TYPE_DELETE_ALL; + } + } + + private class Item extends AdapterWithDiffUtils.Item { + final CacheByChatsController.KeepMediaException exception; + + private Item(int viewType, CacheByChatsController.KeepMediaException exception) { + super(viewType, false); + this.exception = exception; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Item item = (Item) o; + if (viewType != item.viewType) { + return false; + } + if (exception != null && item.exception != null) { + return exception.dialogId == item.exception.dialogId; + } + return true; + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java index e115c0f44..02655dfef 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java @@ -10,44 +10,51 @@ package org.telegram.ui; import android.animation.Animator; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.content.Context; import android.content.DialogInterface; import android.content.pm.PackageManager; import android.graphics.Canvas; -import android.graphics.Color; +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.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.StatFs; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.Spanned; import android.text.TextUtils; -import android.transition.ChangeBounds; -import android.transition.Fade; -import android.transition.TransitionManager; -import android.transition.TransitionSet; +import android.text.style.RelativeSizeSpan; import android.util.LongSparseArray; import android.util.SparseArray; import android.util.TypedValue; import android.view.Gravity; -import android.view.HapticFeedbackConstants; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; -import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; -import androidx.core.widget.NestedScrollView; +import androidx.core.graphics.ColorUtils; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.BotWebViewVibrationEffect; -import org.telegram.messenger.DialogObject; +import org.telegram.messenger.CacheByChatsController; import org.telegram.messenger.Emoji; import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; +import org.telegram.messenger.FilePathDatabase; import org.telegram.messenger.FilesMigrationService; import org.telegram.messenger.ImageLoader; import org.telegram.messenger.LocaleController; @@ -58,11 +65,12 @@ import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; import org.telegram.messenger.SharedConfig; import org.telegram.messenger.Utilities; -import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.ActionBarMenuSubItem; +import org.telegram.ui.ActionBar.ActionBarPopupWindow; import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BackDrawable; import org.telegram.ui.ActionBar.BaseFragment; @@ -71,27 +79,33 @@ import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.CheckBoxCell; import org.telegram.ui.Cells.HeaderCell; +import org.telegram.ui.Cells.TextCell; import org.telegram.ui.Cells.TextCheckBoxCell; import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Cells.TextSettingsCell; +import org.telegram.ui.Components.AlertsCreator; +import org.telegram.ui.Components.AnimatedFloat; import org.telegram.ui.Components.AnimatedTextView; -import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.CacheChart; import org.telegram.ui.Components.CheckBox2; import org.telegram.ui.Components.CubicBezierInterpolator; import org.telegram.ui.Components.FlickerLoadingView; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.ListView.AdapterWithDiffUtils; -import org.telegram.ui.Components.MediaActivity; -import org.telegram.ui.Components.NumberTextView; +import org.telegram.ui.Components.LoadingDrawable; +import org.telegram.ui.Components.NestedSizeNotifierLayout; import org.telegram.ui.Components.RecyclerListView; import org.telegram.ui.Components.SlideChooseView; import org.telegram.ui.Components.StorageDiagramView; import org.telegram.ui.Components.StorageUsageView; +import org.telegram.ui.Components.TypefaceSpan; import org.telegram.ui.Components.UndoView; +import org.telegram.ui.Storage.CacheModel; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Objects; @@ -104,44 +118,56 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe private static final int VIEW_TYPE_CHOOSER = 4; private static final int VIEW_TYPE_CHAT = 5; private static final int VIEW_FLICKER_LOADING_DIALOG = 6; + private static final int VIEW_TYPE_KEEP_MEDIA_CELL = 7; private static final int VIEW_TYPE_TEXT_SETTINGS = 0; + private static final int VIEW_TYPE_CACHE_VIEW_PAGER = 8; + + private static final int VIEW_TYPE_CHART = 9; + private static final int VIEW_TYPE_CHART_HEADER = 10; + public static final int VIEW_TYPE_SECTION = 11; + private static final int VIEW_TYPE_SECTION_LOADING = 12; + private static final int VIEW_TYPE_CLEAR_CACHE_BUTTON = 13; + private static final int VIEW_TYPE_MAX_CACHE_SIZE = 14; + + public static final int KEEP_MEDIA_TYPE_USER = 0; + public static final int KEEP_MEDIA_TYPE_GROUP = 1; + public static final int KEEP_MEDIA_TYPE_CHANNEL = 2; public static final long UNKNOWN_CHATS_DIALOG_ID = Long.MAX_VALUE; + private ListAdapter listAdapter; private RecyclerListView listView; @SuppressWarnings("FieldCanBeLocal") private LinearLayoutManager layoutManager; AlertDialog progressDialog; - private int databaseRow; - private int databaseInfoRow; - private int keepMediaHeaderRow; - private int keepMediaInfoRow; - private int cacheInfoRow; - private int deviseStorageHeaderRow; - private int storageUsageRow; - private int keepMediaChooserRow; - private int chatsHeaderRow; - private int chatsStartRow; - private int chatsEndRow; - private int rowCount; - private int dialogsStartRow, dialogsEndRow; - + private boolean[] selected = new boolean[] { true, true, true, true, true, true, true, true, true }; private long databaseSize = -1; - private long cacheSize = -1; + private long cacheSize = -1, cacheEmojiSize = -1, cacheTempSize = -1; private long documentsSize = -1; private long audioSize = -1; private long musicSize = -1; private long photoSize = -1; private long videoSize = -1; - private long stickersSize = -1; + private long stickersCacheSize = -1; private long totalSize = -1; private long totalDeviceSize = -1; private long totalDeviceFreeSize = -1; private long migrateOldFolderRow = -1; - private StorageDiagramView.ClearViewData[] clearViewData = new StorageDiagramView.ClearViewData[7]; private boolean calculating = true; + private boolean collapsed = true; + private CachedMediaLayout cachedMediaLayout; + + private int[] percents; + private float[] tempSizes; + + private int sectionsStartRow = -1; + private int sectionsEndRow = -1; + + private CacheChart cacheChart; + private CacheChartHeader cacheChartHeader; + private ClearCacheButtonInternal clearCacheButton; private volatile boolean canceled = false; @@ -154,17 +180,32 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe long fragmentCreateTime; private boolean updateDatabaseSize; - private final int TYPE_PHOTOS = 0; - private final int TYPE_VIDEOS = 1; - private final int TYPE_DOCUMENTS = 2; - private final int TYPE_MUSIC = 3; - private final int TYPE_VOICE = 4; - private final int TYPE_ANIMATED_STICKERS_CACHE = 5; - private final int TYPE_OTHER = 6; + public final static int TYPE_PHOTOS = 0; + public final static int TYPE_VIDEOS = 1; + public final static int TYPE_DOCUMENTS = 2; + public final static int TYPE_MUSIC = 3; + public final static int TYPE_VOICE = 4; + public final static int TYPE_ANIMATED_STICKERS_CACHE = 5; + public final static int TYPE_OTHER = 6; - HashSet selectedDialogs = new HashSet(); private static final int delete_id = 1; + private static final int other_id = 2; + private static final int clear_database_id = 3; private boolean loadingDialogs; + private NestedSizeNotifierLayout nestedSizeNotifierLayout; + + private ActionBarMenuSubItem clearDatabaseItem; + private void updateDatabaseItemSize() { + if (clearDatabaseItem != null) { + SpannableStringBuilder string = new SpannableStringBuilder(); + string.append(LocaleController.getString("ClearLocalDatabase", R.string.ClearLocalDatabase)); +// string.append("\t"); +// SpannableString databaseSizeString = new SpannableString(AndroidUtilities.formatFileSize(databaseSize)); +// databaseSizeString.setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText)), 0, databaseSizeString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); +// string.append(databaseSizeString); + clearDatabaseItem.setText(string); + } + } @Override public boolean onFragmentCreate() { @@ -174,7 +215,12 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe loadingDialogs = true; Utilities.globalQueue.postRunnable(() -> { - cacheSize = getDirectorySize(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_CACHE), 0); + cacheSize = getDirectorySize(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_CACHE), 5); + if (canceled) { + return; + } + + cacheTempSize = getDirectorySize(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_CACHE), 4); if (canceled) { return; } @@ -199,12 +245,17 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe if (canceled) { return; } - stickersSize = getDirectorySize(new File(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_CACHE), "acache"), 0); + stickersCacheSize = getDirectorySize(new File(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_CACHE), "acache"), 0); if (canceled) { return; } + cacheEmojiSize = getDirectorySize(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_CACHE), 3); + if (canceled) { + return; + } + stickersCacheSize += cacheEmojiSize; audioSize = getDirectorySize(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_AUDIO), 0); - totalSize = cacheSize + videoSize + audioSize + photoSize + documentsSize + musicSize + stickersSize; + totalSize = cacheSize + cacheTempSize + videoSize + audioSize + photoSize + documentsSize + musicSize + stickersCacheSize; File path; if (Build.VERSION.SDK_INT >= 19) { @@ -250,31 +301,71 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe FileLog.e(e); } + long minDuration = System.currentTimeMillis() - fragmentCreateTime > 25 || true ? 800 : 0; AndroidUtilities.runOnUIThread(() -> { resumeDelayedFragmentAnimation(); calculating = false; - updateStorageUsageRow(); - }); + + updateRows(true); + updateChart(); + }, Math.max(1, minDuration - (System.currentTimeMillis() - fragmentCreateTime))); + loadDialogEntities(); }); fragmentCreateTime = System.currentTimeMillis(); - updateRows(); + updateRows(false); + updateChart(); return true; } + private void updateChart() { + if (cacheChart != null) { + if (!calculating && totalSize > 0) { + CacheChart.SegmentSize[] segments = new CacheChart.SegmentSize[9]; + for (int i = 0; i < itemInners.size(); ++i) { + ItemInner item = itemInners.get(i); + if (item.viewType == VIEW_TYPE_SECTION) { + if (item.index < 0) { + if (collapsed) { + segments[8] = CacheChart.SegmentSize.of(item.size, selected[8]); + } + } else { + segments[item.index] = CacheChart.SegmentSize.of(item.size, selected[item.index]); + } + } + } + if (System.currentTimeMillis() - fragmentCreateTime < 80) { + cacheChart.loadingFloat.set(0, true); + } + cacheChart.setSegments(totalSize, segments); + } else if (calculating) { + cacheChart.setSegments(-1); + } else { + cacheChart.setSegments(0); + } + } + if (clearCacheButton != null && !calculating) { + clearCacheButton.updateSize(); + } + } + private void loadDialogEntities() { getFileLoader().getFileDatabase().getQueue().postRunnable(() -> { + CacheModel cacheModel = new CacheModel(false); LongSparseArray dilogsFilesEntities = new LongSparseArray<>(); - fillDialogsEntitiesRecursive(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_CACHE), TYPE_OTHER, dilogsFilesEntities); - fillDialogsEntitiesRecursive(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_IMAGE), TYPE_PHOTOS, dilogsFilesEntities); - fillDialogsEntitiesRecursive(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_AUDIO), TYPE_VOICE, dilogsFilesEntities); - fillDialogsEntitiesRecursive(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_IMAGE_PUBLIC), TYPE_PHOTOS, dilogsFilesEntities); - fillDialogsEntitiesRecursive(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_VIDEO), TYPE_VIDEOS, dilogsFilesEntities); - fillDialogsEntitiesRecursive(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_VIDEO_PUBLIC), TYPE_VIDEOS, dilogsFilesEntities); - fillDialogsEntitiesRecursive(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_DOCUMENT), TYPE_DOCUMENTS, dilogsFilesEntities); - fillDialogsEntitiesRecursive(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_FILES), TYPE_DOCUMENTS, dilogsFilesEntities); + fillDialogsEntitiesRecursive(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_CACHE), TYPE_OTHER, dilogsFilesEntities, null); + + fillDialogsEntitiesRecursive(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_IMAGE), TYPE_PHOTOS, dilogsFilesEntities, cacheModel); + fillDialogsEntitiesRecursive(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_IMAGE_PUBLIC), TYPE_PHOTOS, dilogsFilesEntities, cacheModel); + + fillDialogsEntitiesRecursive(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_VIDEO), TYPE_VIDEOS, dilogsFilesEntities, cacheModel); + fillDialogsEntitiesRecursive(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_VIDEO_PUBLIC), TYPE_VIDEOS, dilogsFilesEntities, cacheModel); + + fillDialogsEntitiesRecursive(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_AUDIO), TYPE_VOICE, dilogsFilesEntities, cacheModel); + fillDialogsEntitiesRecursive(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_DOCUMENT), TYPE_DOCUMENTS, dilogsFilesEntities, cacheModel); + fillDialogsEntitiesRecursive(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_FILES), TYPE_DOCUMENTS, dilogsFilesEntities, cacheModel); ArrayList entities = new ArrayList<>(); ArrayList unknownUsers = new ArrayList<>(); @@ -290,6 +381,7 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe } } } + cacheModel.sortBySize(); getMessagesStorage().getStorageQueue().postRunnable(() -> { ArrayList users = new ArrayList<>(); ArrayList chats = new ArrayList<>(); @@ -337,9 +429,12 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe } } } + cacheModel.setEntities(entities); + if (!canceled) { - this.dialogsFilesEntities = entities; + setCacheModel(cacheModel); updateRows(); + updateChart(); } }); }); @@ -357,10 +452,16 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe }); } + CacheModel cacheModel; - ArrayList dialogsFilesEntities = null; + public void setCacheModel(CacheModel cacheModel) { + this.cacheModel = cacheModel; + if (cachedMediaLayout != null) { + cachedMediaLayout.setCacheModel(cacheModel); + } + } - public void fillDialogsEntitiesRecursive(final File fromFolder, int type, LongSparseArray dilogsFilesEntities) { + public void fillDialogsEntitiesRecursive(final File fromFolder, int type, LongSparseArray dilogsFilesEntities, CacheModel cacheModel) { if (fromFolder == null) { return; } @@ -373,22 +474,42 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe return; } if (fileEntry.isDirectory()) { - fillDialogsEntitiesRecursive(fileEntry, type, dilogsFilesEntities); + fillDialogsEntitiesRecursive(fileEntry, type, dilogsFilesEntities, cacheModel); } else { - long dialogId = getFileLoader().getFileDatabase().getFileDialogId(fileEntry); - if (dialogId != 0) { - DialogFileEntities dilogEntites = dilogsFilesEntities.get(dialogId, null); - if (dilogEntites == null) { - dilogEntites = new DialogFileEntities(dialogId); - dilogsFilesEntities.put(dialogId, dilogEntites); - } - int addToType = type; - String fileName = fileEntry.getName().toLowerCase(); - if (fileName.endsWith(".mp3") || fileName.endsWith(".m4a") ) { - addToType = TYPE_MUSIC; - } - dilogEntites.addFile(fileEntry, addToType); + if (fileEntry.getName().equals(".nomedia")) { + continue; } + FilePathDatabase.FileMeta fileMetadata = getFileLoader().getFileDatabase().getFileDialogId(fileEntry, null); + int addToType = type; + String fileName = fileEntry.getName().toLowerCase(); + if (fileName.endsWith(".mp3") || fileName.endsWith(".m4a") ) { + addToType = TYPE_MUSIC; + } + CacheModel.FileInfo fileInfo = new CacheModel.FileInfo(fileEntry); + fileInfo.type = addToType; + if (fileMetadata != null) { + fileInfo.dialogId = fileMetadata.dialogId; + fileInfo.messageId = fileMetadata.messageId; + fileInfo.messageType = fileMetadata.messageType; + } + fileInfo.size = fileEntry.length(); + if (fileInfo.dialogId != 0) { + DialogFileEntities dilogEntites = dilogsFilesEntities.get(fileInfo.dialogId, null); + if (dilogEntites == null) { + dilogEntites = new DialogFileEntities(fileInfo.dialogId); + dilogsFilesEntities.put(fileInfo.dialogId, dilogEntites); + } + dilogEntites.addFile(fileInfo, addToType); + } + if (cacheModel != null) { + cacheModel.add(addToType, fileInfo); + } + //TODO measure for other accounts +// for (int i = 0; i < UserConfig.MAX_ACCOUNT_COUNT; i++) { +// if (i != currentAccount && UserConfig.getInstance(currentAccount).isClientActivated()) { +// FileLoader.getInstance(currentAccount).getFileDatabase().getFileDialogId(fileEntry); +// } +// } } } } @@ -396,70 +517,157 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe private ArrayList oldItems = new ArrayList<>(); private ArrayList itemInners = new ArrayList<>(); + private String formatPercent(float k) { + return formatPercent(k, true); + } + + private String formatPercent(float k, boolean minimize) { + float p = Math.round(k * 100f); + if (minimize && p < 0.1f) { + return String.format("<%.1f%%", 0.1f); + } + if (p % 1 == 0) { + return ((int) p) + "%"; + } + return String.format("%.1f%%", p); + } + + private CharSequence getCheckBoxTitle(CharSequence header, int percent) { + return getCheckBoxTitle(header, percent, false); + } + + private CharSequence getCheckBoxTitle(CharSequence header, int percent, boolean addArrow) { + String percentString = percent <= 0 ? String.format("<%.1f%%", 0.1f) : String.format("%d%%", percent); + SpannableString percentStr = new SpannableString(percentString); + percentStr.setSpan(new RelativeSizeSpan(.834f), 0, percentStr.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + percentStr.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf")), 0, percentStr.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + SpannableStringBuilder string = new SpannableStringBuilder(header); + string.append(" "); + string.append(percentStr); + return string; + } + private void updateRows() { - rowCount = 0; + updateRows(true); + } + + private void updateRows(boolean animated) { + if (animated && System.currentTimeMillis() - fragmentCreateTime < 80) { + animated = false; + } + oldItems.clear(); oldItems.addAll(itemInners); itemInners.clear(); - itemInners.add(new ItemInner(VIEW_TYPE_HEADER, LocaleController.getString("KeepMedia", R.string.KeepMedia), null)); - itemInners.add(new ItemInner(VIEW_TYPE_CHOOSER, null, null)); - keepMediaInfoRow = itemInners.size(); - itemInners.add(new ItemInner(VIEW_TYPE_INFO, "keep media", null)); - itemInners.add(new ItemInner(VIEW_TYPE_HEADER, LocaleController.getString("DeviceStorage", R.string.DeviceStorage), null)); - storageUsageRow = itemInners.size(); - itemInners.add(new ItemInner(VIEW_TYPE_STORAGE, null, null)); -// cacheInfoRow = itemInners.size(); -// itemInners.add(new ItemInner(VIEW_TYPE_INFO, "cache", null)); - databaseRow = itemInners.size(); - itemInners.add(new ItemInner(VIEW_TYPE_TEXT_SETTINGS, null, null)); - databaseInfoRow = itemInners.size(); - itemInners.add(new ItemInner(VIEW_TYPE_INFO, "database", null)); + itemInners.add(new ItemInner(VIEW_TYPE_CHART, null, null)); + itemInners.add(new ItemInner(VIEW_TYPE_CHART_HEADER, null, null)); - if (loadingDialogs) { - itemInners.add(new ItemInner(VIEW_TYPE_HEADER, LocaleController.getString("DataUsageByChats", R.string.DataUsageByChats), 15, 4, null)); - itemInners.add(new ItemInner(VIEW_FLICKER_LOADING_DIALOG, null, null)); - } else if (dialogsFilesEntities != null && dialogsFilesEntities.size() > 0) { - itemInners.add(new ItemInner(VIEW_TYPE_HEADER, LocaleController.getString("DataUsageByChats", R.string.DataUsageByChats), 15, 4, null)); - dialogsStartRow = itemInners.size(); - for (int i = 0; i < dialogsFilesEntities.size(); i++) { - itemInners.add(new ItemInner(VIEW_TYPE_CHAT, null, dialogsFilesEntities.get(i))); - } - dialogsEndRow = itemInners.size() - 1; - itemInners.add(new ItemInner(VIEW_TYPE_INFO, null, null)); - } - if (listAdapter != null) { - listAdapter.setItems(oldItems, itemInners); - } - } - - private void updateStorageUsageRow() { - View view = layoutManager.findViewByPosition(storageUsageRow); - if (view instanceof StorageUsageView) { - StorageUsageView storageUsageView = ((StorageUsageView) view); - long currentTime = System.currentTimeMillis(); - if (currentTime - fragmentCreateTime > 150) { - TransitionSet transition = new TransitionSet(); - ChangeBounds changeBounds = new ChangeBounds(); - changeBounds.setDuration(250); - changeBounds.excludeTarget(storageUsageView.legendLayout, true); - Fade in = new Fade(Fade.IN); - in.setDuration(290); - transition - .addTransition(new Fade(Fade.OUT).setDuration(250)) - .addTransition(changeBounds) - .addTransition(in); - transition.setOrdering(TransitionSet.ORDERING_TOGETHER); - transition.setInterpolator(CubicBezierInterpolator.EASE_OUT); - TransitionManager.beginDelayedTransition(listView, transition); - } - storageUsageView.setStorageUsage(calculating, databaseSize, totalSize, totalDeviceFreeSize, totalDeviceSize); - RecyclerView.ViewHolder holder = listView.findViewHolderForAdapterPosition(storageUsageRow); - if (holder != null) { - storageUsageView.setEnabled(listAdapter.isEnabled(holder)); - } + sectionsStartRow = itemInners.size(); + boolean hasCache = false; + if (calculating) { + itemInners.add(new ItemInner(VIEW_TYPE_SECTION_LOADING, null, null)); + itemInners.add(new ItemInner(VIEW_TYPE_SECTION_LOADING, null, null)); + itemInners.add(new ItemInner(VIEW_TYPE_SECTION_LOADING, null, null)); + itemInners.add(new ItemInner(VIEW_TYPE_SECTION_LOADING, null, null)); + itemInners.add(new ItemInner(VIEW_TYPE_SECTION_LOADING, null, null)); + hasCache = true; } else { - updateRows(); + ArrayList sections = new ArrayList<>(); + if (photoSize > 0) { + sections.add(ItemInner.asCheckBox(LocaleController.getString(R.string.LocalPhotoCache), 0, photoSize, Theme.key_statisticChartLine_lightblue)); + } + if (videoSize > 0) { + sections.add(ItemInner.asCheckBox(LocaleController.getString(R.string.LocalVideoCache), 1, videoSize, Theme.key_statisticChartLine_blue)); + } + if (documentsSize > 0) { + sections.add(ItemInner.asCheckBox(LocaleController.getString(R.string.LocalDocumentCache), 2, documentsSize, Theme.key_statisticChartLine_green)); + } + if (musicSize > 0) { + sections.add(ItemInner.asCheckBox(LocaleController.getString(R.string.LocalMusicCache), 3, musicSize, Theme.key_statisticChartLine_red)); + } + if (audioSize > 0) { + sections.add(ItemInner.asCheckBox(LocaleController.getString(R.string.LocalAudioCache), 4, audioSize, Theme.key_statisticChartLine_lightgreen)); + } + if (stickersCacheSize > 0) { + sections.add(ItemInner.asCheckBox(LocaleController.getString(R.string.LocalStickersCache), 5, stickersCacheSize, Theme.key_statisticChartLine_orange)); + } + if (cacheSize > 0) { + sections.add(ItemInner.asCheckBox(LocaleController.getString(R.string.LocalProfilePhotosCache), 6, cacheSize, Theme.key_statisticChartLine_cyan)); + } + if (cacheTempSize > 0) { + sections.add(ItemInner.asCheckBox(LocaleController.getString(R.string.LocalMiscellaneousCache), 7, cacheTempSize, Theme.key_statisticChartLine_purple)); + } + if (!sections.isEmpty()) { + Collections.sort(sections, (a, b) -> Long.compare(b.size, a.size)); + sections.get(sections.size() - 1).last = true; + hasCache = true; + + if (tempSizes == null) { + tempSizes = new float[9]; + } + for (int i = 0; i < tempSizes.length; ++i) { + tempSizes[i] = (float) size(i); + } + if (percents == null) { + percents = new int[9]; + } + AndroidUtilities.roundPercents(tempSizes, percents); + + final int MAX_NOT_COLLAPSED = 4; + if (sections.size() > MAX_NOT_COLLAPSED + 1) { + itemInners.addAll(sections.subList(0, MAX_NOT_COLLAPSED)); + int sumPercents = 0; + long sum = 0; + for (int i = MAX_NOT_COLLAPSED; i < sections.size(); ++i) { + sections.get(i).pad = true; + sum += sections.get(i).size; + sumPercents += percents[sections.get(i).index]; + } + percents[8] = sumPercents; + itemInners.add(ItemInner.asCheckBox(LocaleController.getString(R.string.LocalOther), -1, sum, Theme.key_statisticChartLine_golden)); + if (!collapsed) { + itemInners.addAll(sections.subList(MAX_NOT_COLLAPSED, sections.size())); + } + } else { + itemInners.addAll(sections); + } + } + } + + if (hasCache) { + sectionsEndRow = itemInners.size(); + itemInners.add(new ItemInner(VIEW_TYPE_CLEAR_CACHE_BUTTON, null, null)); + itemInners.add(ItemInner.asInfo(LocaleController.getString("StorageUsageInfo", R.string.StorageUsageInfo))); + } else { + sectionsEndRow = -1; + } + + itemInners.add(new ItemInner(VIEW_TYPE_HEADER, LocaleController.getString("AutoDeleteCachedMedia", R.string.AutoDeleteCachedMedia), null)); + itemInners.add(new ItemInner(VIEW_TYPE_KEEP_MEDIA_CELL, KEEP_MEDIA_TYPE_USER)); + itemInners.add(new ItemInner(VIEW_TYPE_KEEP_MEDIA_CELL, KEEP_MEDIA_TYPE_GROUP)); + itemInners.add(new ItemInner(VIEW_TYPE_KEEP_MEDIA_CELL, KEEP_MEDIA_TYPE_CHANNEL)); + itemInners.add(ItemInner.asInfo(LocaleController.getString("KeepMediaInfoPart", R.string.KeepMediaInfoPart))); + + if (totalDeviceSize > 0) { + itemInners.add(new ItemInner(VIEW_TYPE_HEADER, LocaleController.getString("MaxCacheSize", R.string.MaxCacheSize), null)); + itemInners.add(new ItemInner(VIEW_TYPE_MAX_CACHE_SIZE)); + itemInners.add(ItemInner.asInfo(LocaleController.getString("MaxCacheSizeInfo", R.string.MaxCacheSizeInfo))); + } + + if (hasCache && cacheModel != null && !cacheModel.isEmpty()) { + itemInners.add(new ItemInner(VIEW_TYPE_CACHE_VIEW_PAGER, null, null)); + } + + if (listAdapter != null) { + if (animated) { + listAdapter.setItems(oldItems, itemInners); + } else { + listAdapter.notifyDataSetChanged(); + } + } + if (cachedMediaLayout != null) { + cachedMediaLayout.update(); } } @@ -498,32 +706,33 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe } private void cleanupFolders() { - if (selectedDialogs.size() > 0) { - actionBar.hideActionMode(); - selectedDialogs.clear(); + if (cacheModel != null) { + cacheModel.clearSelection(); } - progressDialog = new AlertDialog(getParentActivity(), 3); + if (cachedMediaLayout != null) { + cachedMediaLayout.updateVisibleRows(); + cachedMediaLayout.showActionMode(false); + } + + progressDialog = new AlertDialog(getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER); progressDialog.setCanCancel(false); progressDialog.showDelayed(500); getFileLoader().cancelLoadAllFiles(); getFileLoader().getFileLoaderQueue().postRunnable(() -> Utilities.globalQueue.postRunnable(() -> { cleanupFoldersInternal(); })); - dialogsFilesEntities = null; + setCacheModel(null); loadingDialogs = true; - updateRows(); - +// updateRows(); } private void cleanupFoldersInternal() { boolean imagesCleared = false; long clearedSize = 0; boolean allItemsClear = true; - for (int a = 0; a < 7; a++) { - if (clearViewData[a] == null || !clearViewData[a].clear) { - if (clearViewData[a] != null) { - allItemsClear = false; - } + for (int a = 0; a < 8; a++) { + if (!selected[a]) { + allItemsClear = false; continue; } int type = -1; @@ -547,9 +756,14 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe clearedSize += audioSize; } else if (a == 5) { type = 100; - clearedSize += stickersSize; + clearedSize += stickersCacheSize + cacheEmojiSize; } else if (a == 6) { clearedSize += cacheSize; + documentsMusicType = 5; + type = FileLoader.MEDIA_DIR_CACHE; + } else if (a == 7) { + clearedSize += cacheTempSize; + documentsMusicType = 4; type = FileLoader.MEDIA_DIR_CACHE; } if (type == -1) { @@ -564,6 +778,12 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe if (file != null) { Utilities.clearDir(file.getAbsolutePath(), documentsMusicType, Long.MAX_VALUE, false); } + if (type == 100) { + file = FileLoader.checkDirectory(FileLoader.MEDIA_DIR_CACHE); + if (file != null) { + Utilities.clearDir(file.getAbsolutePath(), 3, Long.MAX_VALUE, false); + } + } if (type == FileLoader.MEDIA_DIR_IMAGE || type == FileLoader.MEDIA_DIR_VIDEO) { int publicDirectoryType; if (type == FileLoader.MEDIA_DIR_IMAGE) { @@ -585,7 +805,8 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe } if (type == FileLoader.MEDIA_DIR_CACHE) { - cacheSize = getDirectorySize(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_CACHE), documentsMusicType); + cacheSize = getDirectorySize(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_CACHE), 5); + cacheTempSize = getDirectorySize(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_CACHE), 4); imagesCleared = true; } else if (type == FileLoader.MEDIA_DIR_AUDIO) { audioSize = getDirectorySize(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_AUDIO), documentsMusicType); @@ -606,11 +827,14 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe videoSize += getDirectorySize(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_VIDEO_PUBLIC), documentsMusicType); } else if (type == 100) { imagesCleared = true; - stickersSize = getDirectorySize(new File(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_CACHE), "acache"), documentsMusicType); + stickersCacheSize = getDirectorySize(new File(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_CACHE), "acache"), documentsMusicType); + cacheEmojiSize = getDirectorySize(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_CACHE), 3); + stickersCacheSize += cacheEmojiSize; } } final boolean imagesClearedFinal = imagesCleared; - totalSize = cacheSize + videoSize + audioSize + photoSize + documentsSize + musicSize + stickersSize; + totalSize = cacheSize + cacheTempSize + videoSize + audioSize + photoSize + documentsSize + musicSize + stickersCacheSize; + Arrays.fill(selected, true); File path = Environment.getDataDirectory(); StatFs stat = new StatFs(path.getPath()); @@ -646,9 +870,6 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe if (imagesClearedFinal) { ImageLoader.getInstance().clearMemory(); } - if (listAdapter != null) { - updateStorageUsageRow(); - } try { if (progressDialog != null) { progressDialog.dismiss(); @@ -664,129 +885,295 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe MediaDataController.getInstance(currentAccount).chekAllMedia(true); loadDialogEntities(); + + updateChart(); + if (cacheChartHeader != null && !calculating) { + cacheChartHeader.setData( + totalSize > 0, + totalDeviceSize <= 0 ? 0 : (float) totalSize / totalDeviceSize, + totalDeviceFreeSize <= 0 || totalDeviceSize <= 0 ? 0 : (float) (totalDeviceSize - totalDeviceFreeSize) / totalDeviceSize + ); + } }); } + private boolean changeStatusBar; + + @Override + public void onTransitionAnimationProgress(boolean isOpen, float progress) { + if (progress > .5f && !changeStatusBar) { + changeStatusBar = true; + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.needCheckSystemBarColors); + } + super.onTransitionAnimationProgress(isOpen, progress); + } + + @Override + public boolean isLightStatusBar() { + if (!changeStatusBar) { + return super.isLightStatusBar(); + } + return AndroidUtilities.computePerceivedBrightness(Theme.getColor(Theme.key_windowBackgroundGray)) > 0.721f; + } + + private long size(int type) { + switch (type) { + case 0: return photoSize; + case 1: return videoSize; + case 2: return documentsSize; + case 3: return musicSize; + case 4: return audioSize; + case 5: return stickersCacheSize; + case 6: return cacheSize; + case 7: return cacheTempSize; + default: return 0; + } + } + + private int sectionsSelected() { + int count = 0; + for (int i = 0; i < 8; ++i) { + if (selected[i] && size(i) > 0) { + count++; + } + } + return count; + } + + private ActionBarMenu actionMode; + private AnimatedTextView actionModeTitle; + private AnimatedTextView actionModeSubtitle; + private TextView actionModeClearButton; + @Override public View createView(Context context) { + actionBar.setBackgroundDrawable(null); + actionBar.setCastShadows(false); + actionBar.setAddToContainer(false); + actionBar.setOccupyStatusBar(true); + actionBar.setTitleColor(ColorUtils.setAlphaComponent(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText), 0)); + actionBar.setItemsColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText), false); + actionBar.setItemsBackgroundColor(Theme.getColor(Theme.key_listSelector), false); actionBar.setBackButtonDrawable(new BackDrawable(false)); - actionBar.setAllowOverlayTitle(true); + actionBar.setAllowOverlayTitle(false); actionBar.setTitle(LocaleController.getString("StorageUsage", R.string.StorageUsage)); actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @Override public void onItemClick(int id) { if (id == -1) { - if (selectedDialogs.size() > 0) { - selectedDialogs.clear(); - actionBar.hideActionMode(); - listAdapter.notifyItemRangeChanged(0, listAdapter.getItemCount()); + if (actionBar.isActionModeShowed()) { + if (cacheModel != null) { + cacheModel.clearSelection(); + } + if (cachedMediaLayout != null) { + cachedMediaLayout.showActionMode(false); + cachedMediaLayout.updateVisibleRows(); + } return; } finishFragment(); } else if (id == delete_id) { - if (selectedDialogs.isEmpty() || getParentActivity() == null) { - return; - } - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(LocaleController.getString("ClearCache", R.string.ClearCache)); - builder.setMessage(LocaleController.getString("ClearCacheForChats", R.string.ClearCacheForChats)); - builder.setPositiveButton(LocaleController.getString("ClearButton", R.string.ClearButton), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - DialogFileEntities mergedEntities = new DialogFileEntities(0); - for (int i = 0; i < dialogsFilesEntities.size(); i++) { - if (selectedDialogs.contains(dialogsFilesEntities.get(i).dialogId)) { - mergedEntities.merge(dialogsFilesEntities.get(i)); - dialogsFilesEntities.remove(i); - i--; - } - } - if (mergedEntities.totalSize > 0) { - cleanupDialogFiles(mergedEntities, null); - } - selectedDialogs.clear(); - actionBar.hideActionMode(); - updateRows(); - } - }); - builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); - AlertDialog dialog = builder.create(); - showDialog(dialog); - TextView button = (TextView) dialog.getButton(DialogInterface.BUTTON_POSITIVE); - if (button != null) { - button.setTextColor(Theme.getColor(Theme.key_dialogTextRed2)); - } + clearSelectedFiles(); + } else if (id == clear_database_id) { + clearDatabase(); } } }); + actionMode = actionBar.createActionMode(); + FrameLayout actionModeLayout = new FrameLayout(context); + actionMode.addView(actionModeLayout, LayoutHelper.createLinear(0, LayoutHelper.MATCH_PARENT, 1.0f, 72, 0, 0, 0)); + + actionModeTitle = new AnimatedTextView(context, true, true, true); + actionModeTitle.setAnimationProperties(.35f, 0, 350, CubicBezierInterpolator.EASE_OUT_QUINT); + actionModeTitle.setTextSize(AndroidUtilities.dp(18)); + actionModeTitle.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + actionModeTitle.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + actionModeLayout.addView(actionModeTitle, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 18, Gravity.LEFT | Gravity.CENTER_VERTICAL, 0, -11, 0, 0)); + + actionModeSubtitle = new AnimatedTextView(context, true, true, true); + actionModeSubtitle.setAnimationProperties(.35f, 0, 350, CubicBezierInterpolator.EASE_OUT_QUINT); + actionModeSubtitle.setTextSize(AndroidUtilities.dp(14)); + actionModeSubtitle.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText)); + actionModeLayout.addView(actionModeSubtitle, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 18, Gravity.LEFT | Gravity.CENTER_VERTICAL, 0, 10, 0, 0)); + + actionModeClearButton = new TextView(context); + actionModeClearButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + actionModeClearButton.setPadding(AndroidUtilities.dp(14), 0, AndroidUtilities.dp(14), 0); + actionModeClearButton.setTextColor(Theme.getColor(Theme.key_featuredStickers_buttonText)); + actionModeClearButton.setBackground(Theme.AdaptiveRipple.filledRect(Theme.key_featuredStickers_addButton, 6)); + actionModeClearButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + actionModeClearButton.setGravity(Gravity.CENTER); + actionModeClearButton.setText(LocaleController.getString("CacheClear", R.string.CacheClear)); + actionModeClearButton.setOnClickListener(e -> clearSelectedFiles()); + actionModeLayout.addView(actionModeClearButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 28, Gravity.RIGHT | Gravity.CENTER_VERTICAL, 0, 0, 14, 0)); + + ActionBarMenuItem otherItem = actionBar.createMenu().addItem(other_id, R.drawable.ic_ab_other); + clearDatabaseItem = otherItem.addSubItem(clear_database_id, R.drawable.msg_delete, LocaleController.getString("ClearLocalDatabase", R.string.ClearLocalDatabase)); + clearDatabaseItem.setIconColor(Theme.getColor(Theme.key_dialogRedIcon)); + clearDatabaseItem.setTextColor(Theme.getColor(Theme.key_dialogTextRed2)); + updateDatabaseItemSize(); + listAdapter = new ListAdapter(context); - fragmentView = new FrameLayout(context); - FrameLayout frameLayout = (FrameLayout) fragmentView; + nestedSizeNotifierLayout = new NestedSizeNotifierLayout(context) { + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + boolean show = !isPinnedToTop(); + if (!show && actionBarShadowAlpha != 0) { + actionBarShadowAlpha -= 16f / 100f; + invalidate(); + } else if (show && actionBarShadowAlpha != 1f) { + actionBarShadowAlpha += 16f / 100f; + invalidate(); + } + actionBarShadowAlpha = Utilities.clamp(actionBarShadowAlpha, 1f, 0); + if (parentLayout != null) { + parentLayout.drawHeaderShadow(canvas, (int) (0xFF * actionBarShownT * actionBarShadowAlpha), AndroidUtilities.statusBarHeight + ActionBar.getCurrentActionBarHeight()); + } + } + }; + fragmentView = nestedSizeNotifierLayout; + FrameLayout frameLayout = nestedSizeNotifierLayout; frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); - listView = new RecyclerListView(context); + listView = new RecyclerListView(context) { + @Override + protected void dispatchDraw(Canvas canvas) { + if (sectionsStartRow >= 0 && sectionsEndRow >= 0) { + drawSectionBackgroundExclusive(canvas, sectionsStartRow - 1, sectionsEndRow, Theme.getColor(Theme.key_windowBackgroundWhite)); + } + super.dispatchDraw(canvas); + } + + @Override + protected boolean allowSelectChildAtPosition(View child) { + return child != cacheChart; + } + }; + listView.setVerticalScrollBarEnabled(false); + listView.setClipToPadding(false); + listView.setPadding(0, AndroidUtilities.statusBarHeight + ActionBar.getCurrentActionBarHeight() / 2, 0, 0); listView.setLayoutManager(layoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); listView.setAdapter(listAdapter); - DefaultItemAnimator itemAnimator = new DefaultItemAnimator(); + DefaultItemAnimator itemAnimator = new DefaultItemAnimator() { + @Override + protected void onMoveAnimationUpdate(RecyclerView.ViewHolder holder) { + listView.invalidate(); + } + }; + itemAnimator.setDurations(350); + itemAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT); itemAnimator.setDelayAnimations(false); itemAnimator.setSupportsChangeAnimations(false); listView.setItemAnimator(itemAnimator); - listView.setOnItemClickListener((view, position) -> { + listView.setOnItemClickListener((view, position, x, y) -> { if (getParentActivity() == null) { return; } - if (position == databaseRow) { - clearDatabase(); - } else if (position == storageUsageRow) { - showClearCacheDialog(null); - } else if (itemInners.get(position).entities != null) { - if (view instanceof UserCell && selectedDialogs.size() > 0) { - selectDialog((UserCell) view, itemInners.get(position).entities.dialogId); + ItemInner item = itemInners.get(position); +// if (position == databaseRow) { +// clearDatabase(); +// } else + if (item.viewType == VIEW_TYPE_SECTION && view instanceof CheckBoxCell) { + if (item.index < 0) { + collapsed = !collapsed; + updateRows(); + updateChart(); return; } - showClearCacheDialog(itemInners.get(position).entities); + toggleSection(item, view); + } else if (item.entities != null) { +// if (view instanceof UserCell && selectedDialogs.size() > 0) { +// selectDialog((UserCell) view, itemInners.get(position).entities.dialogId); +// return; +// } + showClearCacheDialog(item.entities); + } else if (item.keepMediaType >= 0) { + KeepMediaPopupView windowLayout = new KeepMediaPopupView(this, view.getContext()); + ActionBarPopupWindow popupWindow = AlertsCreator.createSimplePopup(CacheControlActivity.this, windowLayout, view, x, y); + windowLayout.update(itemInners.get(position).keepMediaType); + windowLayout.setParentWindow(popupWindow); + windowLayout.setCallback((type, keepMedia) -> { + AndroidUtilities.updateVisibleRows(listView); + }); } }); - listView.setOnItemLongClickListener(new RecyclerListView.OnItemLongClickListener() { + listView.addOnScrollListener(new RecyclerView.OnScrollListener() { + + boolean pinned; @Override - public boolean onItemClick(View view, int position) { - if (view instanceof UserCell && itemInners.get(position).entities != null) { - view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - selectDialog((UserCell) view, itemInners.get(position).entities.dialogId); + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + updateActionBar(layoutManager.findFirstVisibleItemPosition() > 0 || actionBar.isActionModeShowed()); + if (pinned != nestedSizeNotifierLayout.isPinnedToTop()) { + pinned = nestedSizeNotifierLayout.isPinnedToTop(); + nestedSizeNotifierLayout.invalidate(); } - return false; } }); + frameLayout.addView(actionBar, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + cacheRemovedTooltip = new UndoView(context); frameLayout.addView(cacheRemovedTooltip, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.LEFT, 8, 0, 8, 8)); - + nestedSizeNotifierLayout.setTargetListView(listView); return fragmentView; } - private void selectDialog(UserCell userCell, long dialogId) { - boolean selected = false; - if (selectedDialogs.contains(dialogId)) { - selectedDialogs.remove(dialogId); - } else { - selectedDialogs.add(dialogId); - selected = true; - } - userCell.setChecked(selected, true); - - if (selectedDialogs.size() > 0) { - checkActionMode(); - actionBar.showActionMode(true); - selectedDialogsCountTextView.setNumber(selectedDialogs.size(), true); - } else { - actionBar.hideActionMode(); + private void clearSelectedFiles() { + if (cacheModel.getSelectedFiles() == 0 || getParentActivity() == null) { return; } + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + builder.setTitle(LocaleController.getString("ClearCache", R.string.ClearCache)); + builder.setMessage(LocaleController.getString("ClearCacheForChats", R.string.ClearCacheForChats)); + builder.setPositiveButton(LocaleController.getString("Clear", R.string.Clear), (di, which) -> { + DialogFileEntities mergedEntities = cacheModel.removeSelectedFiles(); + if (mergedEntities.totalSize > 0) { + cleanupDialogFiles(mergedEntities, null, null); + } + cacheModel.clearSelection(); + if (cachedMediaLayout != null) { + cachedMediaLayout.update(); + cachedMediaLayout.showActionMode(false); + } + updateRows(); + updateChart(); + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + AlertDialog dialog = builder.create(); + showDialog(dialog); + TextView button = (TextView) dialog.getButton(DialogInterface.BUTTON_POSITIVE); + if (button != null) { + button.setTextColor(Theme.getColor(Theme.key_dialogTextRed2)); + } + } + private ValueAnimator actionBarAnimator; + private float actionBarShownT; + private boolean actionBarShown; + private float actionBarShadowAlpha = 1f; + private void updateActionBar(boolean show) { + if (show != actionBarShown) { + if (actionBarAnimator != null) { + actionBarAnimator.cancel(); + } + + actionBarAnimator = ValueAnimator.ofFloat(actionBarShownT, (actionBarShown = show) ? 1f : 0f); + actionBarAnimator.addUpdateListener(anm -> { + actionBarShownT = (float) anm.getAnimatedValue(); + actionBar.setTitleColor(ColorUtils.setAlphaComponent(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText), (int) (255 * actionBarShownT))); + actionBar.setBackgroundColor(ColorUtils.setAlphaComponent(Theme.getColor(Theme.key_windowBackgroundWhite), (int) (255 * actionBarShownT))); + fragmentView.invalidate(); + }); + actionBarAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT); + actionBarAnimator.setDuration(380); + actionBarAnimator.start(); + } } private void showClearCacheDialog(DialogFileEntities entities) { @@ -794,156 +1181,33 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe return; } - bottomSheet = new BottomSheet(getParentActivity(), false) { + bottomSheet = new DilogCacheBottomSheet(CacheControlActivity.this, entities, entities.createCacheModel(), new DilogCacheBottomSheet.Delegate() { @Override - protected boolean canDismissWithSwipe() { - return false; - } - }; - bottomSheet.fixNavigationBar(); - bottomSheet.setAllowNestedScroll(true); - bottomSheet.setApplyBottomPadding(false); - LinearLayout linearLayout = new LinearLayout(getParentActivity()); - bottomSheetView = linearLayout; - linearLayout.setOrientation(LinearLayout.VERTICAL); - StorageDiagramView circleDiagramView; - if (entities != null) { - circleDiagramView = new StorageDiagramView(getContext(), entities.dialogId) { - @Override - protected void onAvatarClick() { - bottomSheet.dismiss(); - Bundle args = new Bundle(); -// if (UserConfig.getInstance(currentAccount).getClientUserId() == entities.dialogId) { - args.putLong("dialog_id", entities.dialogId); - MediaActivity fragment = new MediaActivity(args, null); - fragment.setChatInfo(null); - presentFragment(fragment); -// } else { -// if (entities.dialogId < 0) { -// args.putLong("chat_id", -entities.dialogId); -// } else { -// args.putLong("user_id", entities.dialogId); -// } -// presentFragment(new ProfileActivity(args)); -// } - } - }; - } else { - circleDiagramView = new StorageDiagramView(getContext()); - } - linearLayout.addView(circleDiagramView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL, 0, 16, 0, 16)); - CheckBoxCell lastCreatedCheckbox = null; - for (int a = 0; a < 7; a++) { - long size; - String name; - String color; - - if (a == TYPE_PHOTOS) { - size = photoSize; - name = LocaleController.getString("LocalPhotoCache", R.string.LocalPhotoCache); - color = Theme.key_statisticChartLine_blue; - } else if (a == TYPE_VIDEOS) { - size = videoSize; - name = LocaleController.getString("LocalVideoCache", R.string.LocalVideoCache); - color = Theme.key_statisticChartLine_golden; - } else if (a == TYPE_DOCUMENTS) { - size = documentsSize; - name = LocaleController.getString("LocalDocumentCache", R.string.LocalDocumentCache); - color = Theme.key_statisticChartLine_green; - } else if (a == TYPE_MUSIC) { - size = musicSize; - name = LocaleController.getString("LocalMusicCache", R.string.LocalMusicCache); - color = Theme.key_statisticChartLine_indigo; - } else if (a == TYPE_VOICE) { - size = audioSize; - name = LocaleController.getString("LocalAudioCache", R.string.LocalAudioCache); - color = Theme.key_statisticChartLine_red; - } else if (a == TYPE_ANIMATED_STICKERS_CACHE) { - size = stickersSize; - name = LocaleController.getString("AnimatedStickers", R.string.AnimatedStickers); - color = Theme.key_statisticChartLine_lightgreen; - } else { - size = cacheSize; - name = LocaleController.getString("LocalCache", R.string.LocalCache); - color = Theme.key_statisticChartLine_lightblue; - } - if (entities != null) { - FileEntities fileEntities = entities.entitiesByType.get(a); - if (fileEntities != null) { - size = fileEntities.totalSize; + public void onAvatarClick() { + bottomSheet.dismiss(); + Bundle args = new Bundle(); + if (entities.dialogId > 0) { + args.putLong("user_id", entities.dialogId); } else { - size = 0; + args.putLong("chat_id", -entities.dialogId); } + presentFragment(new ProfileActivity(args, null)); } - if (size > 0) { - clearViewData[a] = new StorageDiagramView.ClearViewData(circleDiagramView); - clearViewData[a].size = size; - clearViewData[a].color = color; - CheckBoxCell checkBoxCell = new CheckBoxCell(getParentActivity(), 4, 21, null); - lastCreatedCheckbox = checkBoxCell; - checkBoxCell.setTag(a); - checkBoxCell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); - linearLayout.addView(checkBoxCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 50)); - checkBoxCell.setText(name, AndroidUtilities.formatFileSize(size), true, true); - checkBoxCell.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); - checkBoxCell.setCheckBoxColor(color, Theme.key_windowBackgroundWhiteGrayIcon, Theme.key_checkboxCheck); - checkBoxCell.setOnClickListener(v -> { - int enabledCount = 0; - for (int i = 0; i < clearViewData.length; i++) { - if (clearViewData[i] != null && clearViewData[i].clear) { - enabledCount++; - } - } - CheckBoxCell cell = (CheckBoxCell) v; - int num = (Integer) cell.getTag(); - if (enabledCount == 1 && clearViewData[num].clear) { - BotWebViewVibrationEffect.APP_ERROR.vibrate(); - AndroidUtilities.shakeViewSpring(((CheckBoxCell) v).getCheckBoxView(), -3); - return; - } - clearViewData[num].setClear(!clearViewData[num].clear); - cell.setChecked(clearViewData[num].clear, true); - }); - } else { - clearViewData[a] = null; - } - } - if (lastCreatedCheckbox != null) { - lastCreatedCheckbox.setNeedDivider(false); - } - circleDiagramView.setData(clearViewData); - BottomSheet.BottomSheetCell cell = new BottomSheet.BottomSheetCell(getParentActivity(), 2); - cell.setTextAndIcon(LocaleController.getString("ClearMediaCache", R.string.ClearMediaCache), 0); - actionTextView = cell.getTextView(); - cell.getTextView().setOnClickListener(v -> { - try { - if (visibleDialog != null) { - visibleDialog.dismiss(); - } - } catch (Exception e) { - FileLog.e(e); - } - if (entities == null) { - cleanupFolders(); - } else { - cleanupDialogFiles(entities, clearViewData); + @Override + public void cleanupDialogFiles(DialogFileEntities entities, StorageDiagramView.ClearViewData[] clearViewData, CacheModel cacheModel) { + CacheControlActivity.this.cleanupDialogFiles(entities, clearViewData, cacheModel); } }); - linearLayout.addView(cell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 50)); - NestedScrollView scrollView = new NestedScrollView(getContext()); - scrollView.setVerticalScrollBarEnabled(false); - scrollView.addView(linearLayout); - bottomSheet.setCustomView(scrollView); showDialog(bottomSheet); } - private void cleanupDialogFiles(DialogFileEntities dialogEntities, StorageDiagramView.ClearViewData[] clearViewData) { - final AlertDialog progressDialog = new AlertDialog(getParentActivity(), 3); + private void cleanupDialogFiles(DialogFileEntities dialogEntities, StorageDiagramView.ClearViewData[] clearViewData, CacheModel dialogCacheModel) { + final AlertDialog progressDialog = new AlertDialog(getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER); progressDialog.setCanCancel(false); progressDialog.showDelayed(500); - ArrayList filesToRemove = new ArrayList<>(); + HashSet filesToRemove = new HashSet<>(); long totalSizeBefore = totalSize; for (int a = 0; a < 7; a++) { if (clearViewData != null) { @@ -971,24 +1235,49 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe } else if (a == TYPE_VOICE) { audioSize -= entitiesToDelete.totalSize; } else if (a == TYPE_ANIMATED_STICKERS_CACHE) { - stickersSize -= entitiesToDelete.totalSize; + stickersCacheSize -= entitiesToDelete.totalSize; } else { cacheSize -= entitiesToDelete.totalSize; } } if (dialogEntities.entitiesByType.size() == 0) { - dialogsFilesEntities.remove(dialogEntities); + cacheModel.remove(dialogEntities); } updateRows(); + if (dialogCacheModel != null) { + for (CacheModel.FileInfo fileInfo : dialogCacheModel.selectedFiles) { + if (!filesToRemove.contains(fileInfo)) { + totalSize -= fileInfo.size; + totalDeviceFreeSize += fileInfo.size; + filesToRemove.add(fileInfo); + dialogEntities.removeFile(fileInfo); + if (fileInfo.type == TYPE_PHOTOS) { + photoSize -= fileInfo.size; + } else if (fileInfo.type == TYPE_VIDEOS) { + videoSize -= fileInfo.size; + } else if (fileInfo.size == TYPE_DOCUMENTS) { + documentsSize -= fileInfo.size; + } else if (fileInfo.size == TYPE_MUSIC) { + musicSize -= fileInfo.size; + } else if (fileInfo.size == TYPE_VOICE) { + audioSize -= fileInfo.size; + } + } + } + } + for (CacheModel.FileInfo fileInfo : filesToRemove) { + this.cacheModel.onFileDeleted(fileInfo); + } cacheRemovedTooltip.setInfoText(LocaleController.formatString("CacheWasCleared", R.string.CacheWasCleared, AndroidUtilities.formatFileSize(totalSizeBefore - totalSize))); cacheRemovedTooltip.showWithAction(0, UndoView.ACTION_CACHE_WAS_CLEARED, null, null); - getFileLoader().getFileDatabase().removeFiles(filesToRemove); + ArrayList fileInfos = new ArrayList<>(filesToRemove); + getFileLoader().getFileDatabase().removeFiles(fileInfos); getFileLoader().cancelLoadAllFiles(); getFileLoader().getFileLoaderQueue().postRunnable(() -> { - for (int i = 0; i < filesToRemove.size(); i++) { - filesToRemove.get(i).delete(); + for (int i = 0; i < fileInfos.size(); i++) { + fileInfos.get(i).file.delete(); } AndroidUtilities.runOnUIThread(() -> { @@ -1010,13 +1299,17 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe private void clearDatabase() { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("LocalDatabaseClearTextTitle", R.string.LocalDatabaseClearTextTitle)); - builder.setMessage(LocaleController.getString("LocalDatabaseClearText", R.string.LocalDatabaseClearText)); + SpannableStringBuilder message = new SpannableStringBuilder(); + message.append(LocaleController.getString("LocalDatabaseClearText", R.string.LocalDatabaseClearText)); + message.append("\n\n"); + message.append(AndroidUtilities.replaceTags(LocaleController.formatString("LocalDatabaseClearText2", R.string.LocalDatabaseClearText2, AndroidUtilities.formatFileSize(databaseSize)))); + builder.setMessage(message); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); builder.setPositiveButton(LocaleController.getString("CacheClear", R.string.CacheClear), (dialogInterface, i) -> { if (getParentActivity() == null) { return; } - progressDialog = new AlertDialog(getParentActivity(), 3); + progressDialog = new AlertDialog(getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER); progressDialog.setCanCancel(false); progressDialog.showDelayed(500); MessagesController.getInstance(currentAccount).clearQueryTime(); @@ -1033,7 +1326,10 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe @Override public void onResume() { super.onResume(); - loadDialogEntities(); + listAdapter.notifyDataSetChanged(); + if (!calculating) { +// loadDialogEntities(); + } } @Override @@ -1050,11 +1346,468 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe if (listAdapter != null) { databaseSize = MessagesStorage.getInstance(currentAccount).getDatabaseSize(); updateDatabaseSize = true; + updateDatabaseItemSize(); updateRows(); } } } + class CacheChartHeader extends FrameLayout { + + AnimatedTextView title; + TextView[] subtitle = new TextView[3]; + View bottomImage; + + RectF progressRect = new RectF(); + LoadingDrawable loadingDrawable = new LoadingDrawable(); + + Float percent, usedPercent; + AnimatedFloat percentAnimated = new AnimatedFloat(this, 450, CubicBezierInterpolator.EASE_OUT_QUINT); + AnimatedFloat usedPercentAnimated = new AnimatedFloat(this, 450, CubicBezierInterpolator.EASE_OUT_QUINT); + AnimatedFloat loadingFloat = new AnimatedFloat(this, 450, CubicBezierInterpolator.EASE_OUT_QUINT); + + Paint loadingBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + Paint percentPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + Paint usedPercentPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + boolean firstSet = true; + + public CacheChartHeader(Context context) { + super(context); + + title = new AnimatedTextView(context); + title.setAnimationProperties(.35f, 0, 350, CubicBezierInterpolator.EASE_OUT_QUINT); + title.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + title.setTextSize(AndroidUtilities.dp(20)); + title.setText(LocaleController.getString("StorageUsage", R.string.StorageUsage)); + title.setGravity(Gravity.CENTER); + title.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + addView(title, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 26, Gravity.TOP | Gravity.CENTER_HORIZONTAL)); + + for (int i = 0; i < 3; ++i) { + subtitle[i] = new TextView(context); + subtitle[i].setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); + subtitle[i].setGravity(Gravity.CENTER); + subtitle[i].setPadding(AndroidUtilities.dp(24), 0, AndroidUtilities.dp(24), 0); + if (i == 0) { + subtitle[i].setText(LocaleController.getString("StorageUsageCalculating", R.string.StorageUsageCalculating)); + } else if (i == 1) { + subtitle[i].setAlpha(0); + subtitle[i].setText(LocaleController.getString("StorageUsageTelegram", R.string.StorageUsageTelegram)); + subtitle[i].setVisibility(View.GONE); + } else if (i == 2) { + subtitle[i].setText(LocaleController.getString("StorageCleared2", R.string.StorageCleared2)); + subtitle[i].setAlpha(0); + subtitle[i].setVisibility(View.GONE); + } + subtitle[i].setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText4)); + addView(subtitle[i], LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, i == 2 ? 38 : 32, 0, 0)); + } + + bottomImage = new View(context) { + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec) + getPaddingLeft() + getPaddingRight(), MeasureSpec.EXACTLY), heightMeasureSpec); + } + }; + Drawable bottomImageDrawable = getContext().getResources().getDrawable(R.drawable.popup_fixed_alert2).mutate(); + bottomImageDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_windowBackgroundWhite), PorterDuff.Mode.MULTIPLY)); + bottomImage.setBackground(bottomImageDrawable); + MarginLayoutParams bottomImageParams = LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 24, Gravity.BOTTOM | Gravity.FILL_HORIZONTAL); + bottomImageParams.leftMargin = -bottomImage.getPaddingLeft(); + bottomImageParams.bottomMargin = -AndroidUtilities.dp(11); + bottomImageParams.rightMargin = -bottomImage.getPaddingRight(); + addView(bottomImage, bottomImageParams); + + int color = Theme.getColor(Theme.key_windowBackgroundWhiteGrayText4); + loadingDrawable.setColors( + Theme.getColor(Theme.key_actionBarActionModeDefaultSelector), + Theme.multAlpha(color, .2f) + ); + loadingDrawable.setRadiiDp(4); + loadingDrawable.setCallback(this); + } + + public void setData(boolean hasCache, float percent, float usedPercent) { + title.setText( + hasCache ? + LocaleController.getString("StorageUsage", R.string.StorageUsage) : + LocaleController.getString("StorageCleared", R.string.StorageCleared) + ); + if (hasCache) { + if (percent < 0.1f) { + subtitle[1].setText(LocaleController.formatString("StorageUsageTelegramLess", R.string.StorageUsageTelegramLess, formatPercent(percent))); + } else { + subtitle[1].setText(LocaleController.formatString("StorageUsageTelegram", R.string.StorageUsageTelegram, formatPercent(percent))); + } + switchSubtitle(1); + } else { + switchSubtitle(2); + } + bottomImage.animate().cancel(); + if (firstSet) { + bottomImage.setAlpha(hasCache ? 1 : 0); + } else { + bottomImage.animate().alpha(hasCache ? 1 : 0).setDuration(365).setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT).start(); + } + firstSet = false; + this.percent = percent; + this.usedPercent = usedPercent; + invalidate(); + } + + private void switchSubtitle(int type) { + boolean animated = System.currentTimeMillis() - fragmentCreateTime > 40; + AndroidUtilities.updateViewShow(subtitle[0], type == 0, false, -.5f, animated, null); + AndroidUtilities.updateViewShow(subtitle[1], type == 1, false, -.5f, animated, null); + AndroidUtilities.updateViewShow(subtitle[2], type == 2, false, -.5f, animated, null); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int fullWidth = MeasureSpec.getSize(widthMeasureSpec); + int width = (int) Math.min(AndroidUtilities.dp(174), fullWidth * .8); + + super.measureChildren(MeasureSpec.makeMeasureSpec(fullWidth, MeasureSpec.EXACTLY), heightMeasureSpec); + int height = AndroidUtilities.dp(90 - 18); + int maxSubtitleHeight = 0; + for (int i = 0; i < subtitle.length; ++i) { + maxSubtitleHeight = Math.max(maxSubtitleHeight, subtitle[i].getMeasuredHeight()); + } + height += maxSubtitleHeight; + setMeasuredDimension(fullWidth, height); + + progressRect.set( + (fullWidth - width) / 2f, + height - AndroidUtilities.dp(30), + (fullWidth + width) / 2f, + height - AndroidUtilities.dp(30 - 4) + ); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + float barAlpha = Math.max(subtitle[0].getAlpha(), subtitle[1].getAlpha()); + + float loading = this.loadingFloat.set(this.percent == null ? 1f : 0f); + float percent = this.percentAnimated.set(this.percent == null ? 0 : this.percent); + float usedPercent = this.usedPercentAnimated.set(this.usedPercent == null ? 0 : this.usedPercent); + + loadingBackgroundPaint.setColor(Theme.getColor(Theme.key_actionBarActionModeDefaultSelector)); + loadingBackgroundPaint.setAlpha((int) (loadingBackgroundPaint.getAlpha() * barAlpha)); + AndroidUtilities.rectTmp.set( + Math.max( + progressRect.left + (1f - loading) * Math.max(AndroidUtilities.dp(4), usedPercent * progressRect.width()), + progressRect.left + (1f - loading) * Math.max(AndroidUtilities.dp(4), percent * progressRect.width()) + ) + AndroidUtilities.dp(1), progressRect.top, + progressRect.right, progressRect.bottom + ); + if (AndroidUtilities.rectTmp.left < AndroidUtilities.rectTmp.right && AndroidUtilities.rectTmp.width() > AndroidUtilities.dp(3)) { + drawRoundRect(canvas, AndroidUtilities.rectTmp, AndroidUtilities.dp(AndroidUtilities.lerp(1, 2, loading)), AndroidUtilities.dp(2), loadingBackgroundPaint); + } + + loadingDrawable.setBounds(progressRect); + loadingDrawable.setAlpha((int) (0xFF * barAlpha * loading)); + loadingDrawable.draw(canvas); + + usedPercentPaint.setColor(Theme.percentSV(Theme.getColor(Theme.key_radioBackgroundChecked), Theme.getColor(Theme.key_actionBarActionModeDefaultSelector), .922f, 1.8f)); + usedPercentPaint.setAlpha((int) (usedPercentPaint.getAlpha() * barAlpha)); + AndroidUtilities.rectTmp.set( + progressRect.left + (1f - loading) * Math.max(AndroidUtilities.dp(4), percent * progressRect.width()) + AndroidUtilities.dp(1), + progressRect.top, + progressRect.left + (1f - loading) * Math.max(AndroidUtilities.dp(4), usedPercent * progressRect.width()), + progressRect.bottom + ); + if (AndroidUtilities.rectTmp.width() > AndroidUtilities.dp(3)) { + drawRoundRect(canvas, AndroidUtilities.rectTmp, AndroidUtilities.dp(1), AndroidUtilities.dp(usedPercent > .97f ? 2 : 1), usedPercentPaint); + } + + percentPaint.setColor(Theme.getColor(Theme.key_radioBackgroundChecked)); + percentPaint.setAlpha((int) (percentPaint.getAlpha() * barAlpha)); + AndroidUtilities.rectTmp.set(progressRect.left, progressRect.top, progressRect.left + (1f - loading) * Math.max(AndroidUtilities.dp(4), percent * progressRect.width()), progressRect.bottom); + drawRoundRect(canvas, AndroidUtilities.rectTmp, AndroidUtilities.dp(2), AndroidUtilities.dp(percent > .97f ? 2 : 1), percentPaint); + + if (loading > 0 || this.percentAnimated.isInProgress()) { + invalidate(); + } + + super.dispatchDraw(canvas); + } + + private Path roundPath; + private float[] radii; + + private void drawRoundRect(Canvas canvas, RectF rect, float left, float right, Paint paint) { + if (roundPath == null) { + roundPath = new Path(); + } else { + roundPath.rewind(); + } + if (radii == null) { + radii = new float[8]; + } + radii[0] = radii[1] = radii[6] = radii[7] = left; + radii[2] = radii[3] = radii[4] = radii[5] = right; + roundPath.addRoundRect(rect, radii, Path.Direction.CW); + canvas.drawPath(roundPath, paint); + } + } + + private class ClearCacheButtonInternal extends ClearCacheButton { + + public ClearCacheButtonInternal(Context context) { + super(context); + ((MarginLayoutParams) button.getLayoutParams()).topMargin = AndroidUtilities.dp(5); + button.setOnClickListener(e -> { + cleanupFolders(); + }); + } + + public void updateSize() { + long size = ( + (selected[0] ? photoSize : 0) + + (selected[1] ? videoSize : 0) + + (selected[2] ? documentsSize : 0) + + (selected[3] ? musicSize : 0) + + (selected[4] ? audioSize : 0) + + (selected[5] ? stickersCacheSize : 0) + + (selected[6] ? cacheSize : 0) + + (selected[7] ? cacheTempSize : 0) + ); + setSize( + isAllSectionsSelected(), + size + ); + } + } + + private boolean isAllSectionsSelected() { + for (int i = 0; i < itemInners.size(); ++i) { + ItemInner item = itemInners.get(i); + if (item.viewType != VIEW_TYPE_SECTION) { + continue; + } + int index = item.index; + if (index < 0) { + index = selected.length - 1; + } + if (!selected[index]) { + return false; + } + } + return true; + } + + public static class ClearCacheButton extends FrameLayout { + FrameLayout button; + AnimatedTextView.AnimatedTextDrawable textView; + AnimatedTextView.AnimatedTextDrawable valueTextView; + + TextView rtlTextView; + + public ClearCacheButton(Context context) { + super(context); + + button = new FrameLayout(context) { + @Override + protected void dispatchDraw(Canvas canvas) { + final int margin = AndroidUtilities.dp(8); + int x = (getMeasuredWidth() - margin - valueTextView.getCurrentWidth() + textView.getCurrentWidth()) / 2; + + if (LocaleController.isRTL) { + super.dispatchDraw(canvas); + } else { + textView.setBounds(0, 0, x, getHeight()); + textView.draw(canvas); + + valueTextView.setBounds(x + AndroidUtilities.dp(8), 0, getWidth(), getHeight()); + valueTextView.draw(canvas); + } + } + + @Override + protected boolean verifyDrawable(@NonNull Drawable who) { + return who == valueTextView || who == textView || super.verifyDrawable(who); + } + }; + button.setBackground(Theme.AdaptiveRipple.filledRect(Theme.key_featuredStickers_addButton, 8)); + + if (LocaleController.isRTL) { + rtlTextView = new TextView(context); + rtlTextView.setText(LocaleController.getString("ClearCache", R.string.ClearCache)); + rtlTextView.setGravity(Gravity.CENTER); + rtlTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + rtlTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + rtlTextView.setTextColor(Theme.getColor(Theme.key_featuredStickers_buttonText)); + button.addView(rtlTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); + } + + textView = new AnimatedTextView.AnimatedTextDrawable(true, true, true); + textView.setAnimationProperties(.25f, 0, 300, CubicBezierInterpolator.EASE_OUT_QUINT); + textView.setCallback(button); + textView.setTextSize(AndroidUtilities.dp(14)); + textView.setText(LocaleController.getString("ClearCache", R.string.ClearCache)); + textView.setGravity(Gravity.RIGHT); + textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + textView.setTextColor(Theme.getColor(Theme.key_featuredStickers_buttonText)); + + valueTextView = new AnimatedTextView.AnimatedTextDrawable(true, true, true); + valueTextView.setAnimationProperties(.25f, 0, 300, CubicBezierInterpolator.EASE_OUT_QUINT); + valueTextView.setCallback(button); + valueTextView.setTextSize(AndroidUtilities.dp(14)); + valueTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + valueTextView.setTextColor(Theme.adaptHSV(Theme.getColor(Theme.key_featuredStickers_addButton), -.46f, +.08f)); + valueTextView.setText(""); + + setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + addView(button, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.FILL, 16, 16, 16, 16)); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure( + MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), + heightMeasureSpec + ); + } + + public void setSize(boolean allSelected, long size) { + textView.setText(( + allSelected ? + LocaleController.getString("ClearCache", R.string.ClearCache) : + LocaleController.getString("ClearSelectedCache", R.string.ClearSelectedCache) + )); + valueTextView.setText(size <= 0 ? "" : AndroidUtilities.formatFileSize(size)); + setDisabled(size <= 0); + button.invalidate(); + } + + public void setDisabled(boolean disabled) { + button.animate().cancel(); + button.animate().alpha(disabled ? .65f : 1f).start(); + button.setClickable(!disabled); + } + } + + private boolean isOtherSelected() { + boolean[] indexes = new boolean[CacheControlActivity.this.selected.length]; + for (int i = 0; i < itemInners.size(); ++i) { + ItemInner item2 = itemInners.get(i); + if (item2.viewType == VIEW_TYPE_SECTION && !item2.pad && item2.index >= 0) { + indexes[item2.index] = true; + } + } + for (int i = 0; i < indexes.length; ++i) { + if (!indexes[i] && !CacheControlActivity.this.selected[i]) { + return false; + } + } + return true; + } + + private void toggleSection(ItemInner item, View cell) { + if (item.index < 0) { + toggleOtherSelected(cell); + return; + } + if (selected[item.index] && sectionsSelected() <= 1) { + BotWebViewVibrationEffect.APP_ERROR.vibrate(); + if (cell != null) { + AndroidUtilities.shakeViewSpring(cell, -3); + } + return; + } + if (cell instanceof CheckBoxCell) { + ((CheckBoxCell) cell).setChecked(selected[item.index] = !selected[item.index], true); + } else { + selected[item.index] = !selected[item.index]; + int position = itemInners.indexOf(item); + if (position >= 0) { + for (int i = 0; i < listView.getChildCount(); ++i) { + View child = listView.getChildAt(i); + if (child instanceof CheckBoxCell && position == listView.getChildAdapterPosition(child)) { + ((CheckBoxCell) child).setChecked(selected[item.index], true); + } + } + } + } + if (item.pad) { + for (int i = 0; i < listView.getChildCount(); ++i) { + View child = listView.getChildAt(i); + if (child instanceof CheckBoxCell) { + int pos = listView.getChildAdapterPosition(child); + if (pos >= 0 && pos < itemInners.size() && itemInners.get(pos).index < 0) { + ((CheckBoxCell) child).setChecked(isOtherSelected(), true); + break; + } + } + } + } + updateChart(); + } + + private void toggleOtherSelected(View cell) { + boolean selected = isOtherSelected(); + + if (selected) { + boolean hasNonOtherSelected = false; + for (int i = 0; i < itemInners.size(); ++i) { + ItemInner item2 = itemInners.get(i); + if (item2.viewType == VIEW_TYPE_SECTION && !item2.pad && item2.index >= 0 && CacheControlActivity.this.selected[item2.index]) { + hasNonOtherSelected = true; + break; + } + } + + if (!hasNonOtherSelected) { + BotWebViewVibrationEffect.APP_ERROR.vibrate(); + if (cell != null) { + AndroidUtilities.shakeViewSpring(cell, -3); + } + return; + } + } + + if (collapsed) { + boolean[] indexes = new boolean[CacheControlActivity.this.selected.length]; + for (int i = 0; i < itemInners.size(); ++i) { + ItemInner item2 = itemInners.get(i); + if (item2.viewType == VIEW_TYPE_SECTION && !item2.pad && item2.index >= 0) { + indexes[item2.index] = true; + } + } + for (int i = 0; i < indexes.length; ++i) { + if (!indexes[i]) { + CacheControlActivity.this.selected[i] = !selected; + } + } + } else { + for (int i = 0; i < itemInners.size(); ++i) { + ItemInner item2 = itemInners.get(i); + if (item2.viewType == VIEW_TYPE_SECTION && item2.pad && item2.index >= 0) { + CacheControlActivity.this.selected[item2.index] = !selected; + } + } + } + + for (int i = 0; i < listView.getChildCount(); ++i) { + View child = listView.getChildAt(i); + if (child instanceof CheckBoxCell) { + int pos = listView.getChildAdapterPosition(child); + if (pos >= 0) { + ItemInner item2 = itemInners.get(pos); + if (item2.viewType == VIEW_TYPE_SECTION) { + if (item2.index < 0) { + ((CheckBoxCell) child).setChecked(!selected, true); + } else { + ((CheckBoxCell) child).setChecked(CacheControlActivity.this.selected[item2.index], true); + } + } + } + } + } + updateChart(); + } + private class ListAdapter extends AdapterWithDiffUtils { private Context mContext; @@ -1066,7 +1819,7 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe @Override public boolean isEnabled(RecyclerView.ViewHolder holder) { int position = holder.getAdapterPosition(); - return position == migrateOldFolderRow || position == databaseRow || (holder.getItemViewType() == VIEW_TYPE_STORAGE && (totalSize > 0) && !calculating) || holder.getItemViewType() == VIEW_TYPE_CHAT; + return position == migrateOldFolderRow || (holder.getItemViewType() == VIEW_TYPE_STORAGE && (totalSize > 0) && !calculating) || holder.getItemViewType() == VIEW_TYPE_CHAT || holder.getItemViewType() == VIEW_TYPE_KEEP_MEDIA_CELL || holder.getItemViewType() == VIEW_TYPE_SECTION; } @Override @@ -1128,38 +1881,269 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe flickerLoadingView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); view = flickerLoadingView; break; + case VIEW_TYPE_KEEP_MEDIA_CELL: + view = new TextCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case VIEW_TYPE_CHART: + view = cacheChart = new CacheChart(mContext) { + @Override + protected void onSectionClick(int index) { +// if (index == 8) { +// index = -1; +// } +// for (int i = 0; i < itemInners.size(); ++i) { +// ItemInner item = itemInners.get(i); +// if (item != null && item.index == index) { +// toggleSection(item, null); +// return; +// } +// } + } + + @Override + protected void onSectionDown(int index, boolean down) { + if (!down) { + listView.removeHighlightRow(); + return; + } + if (index == 8) { + index = -1; + } + int position = -1; + for (int i = 0; i < itemInners.size(); ++i) { + ItemInner item2 = itemInners.get(i); + if (item2 != null && item2.viewType == VIEW_TYPE_SECTION && item2.index == index) { + position = i; + break; + } + } + + if (position >= 0) { + final int finalPosition = position; + listView.highlightRow(() -> finalPosition, 0); + } else { + listView.removeHighlightRow(); + } + } + }; + break; + case VIEW_TYPE_CHART_HEADER: + view = cacheChartHeader = new CacheChartHeader(mContext); + break; + case VIEW_TYPE_SECTION_LOADING: + FlickerLoadingView flickerLoadingView2 = new FlickerLoadingView(getContext()); + flickerLoadingView2.setIsSingleCell(true); + flickerLoadingView2.setItemsCount(1); + flickerLoadingView2.setIgnoreHeightCheck(true); + flickerLoadingView2.setViewType(FlickerLoadingView.CHECKBOX_TYPE); + flickerLoadingView2.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + view = flickerLoadingView2; + break; + case VIEW_TYPE_SECTION: + view = new CheckBoxCell(mContext, 4, 21, getResourceProvider()); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; case VIEW_TYPE_INFO: default: view = new TextInfoPrivacyCell(mContext); break; + case VIEW_TYPE_CACHE_VIEW_PAGER: + view = cachedMediaLayout = new CachedMediaLayout(mContext, CacheControlActivity.this) { + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec) - (ActionBar.getCurrentActionBarHeight() / 2), MeasureSpec.EXACTLY)); + } + + @Override + protected void showActionMode(boolean show) { + if (show) { + updateActionBar(true); + actionBar.showActionMode(); + } else { + actionBar.hideActionMode(); + } + } + + @Override + protected boolean actionModeIsVisible() { + return actionBar.isActionModeShowed(); + } + }; + cachedMediaLayout.setDelegate(new CachedMediaLayout.Delegate() { + @Override + public void onItemSelected(DialogFileEntities entities, CacheModel.FileInfo fileInfo, boolean longPress) { + if (entities != null) { + if ((cacheModel.getSelectedFiles() > 0 || longPress)) { + cacheModel.toggleSelect(entities); + cachedMediaLayout.updateVisibleRows(); + updateActionMode(); + } else { + showClearCacheDialog(entities); + } + return; + } + if (fileInfo != null) { + cacheModel.toggleSelect(fileInfo); + cachedMediaLayout.updateVisibleRows(); + updateActionMode(); + } + } + + @Override + public void clear() { + clearSelectedFiles(); + } + + @Override + public void clearSelection() { + if (cacheModel != null && cacheModel.getSelectedFiles() > 0) { + cacheModel.clearSelection(); + if (cachedMediaLayout != null) { + cachedMediaLayout.showActionMode(false); + cachedMediaLayout.updateVisibleRows(); + } + return; + } + } + }); + cachedMediaLayout.setCacheModel(cacheModel); + nestedSizeNotifierLayout.setChildLayout(cachedMediaLayout); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + break; + case VIEW_TYPE_CLEAR_CACHE_BUTTON: + view = clearCacheButton = new ClearCacheButtonInternal(mContext); + break; + case VIEW_TYPE_MAX_CACHE_SIZE: + SlideChooseView slideChooseView2 = new SlideChooseView(mContext); + view = slideChooseView2; + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + + float totalSizeInGb = (int) (totalDeviceSize / 1024L / 1024L) / 1000.0f; + ArrayList options = new ArrayList<>(); +// if (BuildVars.DEBUG_PRIVATE_VERSION) { +// options.add(1); +// } + if (totalSizeInGb <= 17) { + options.add(2); + } + if (totalSizeInGb > 5) { + options.add(5); + } + if (totalSizeInGb > 16) { + options.add(16); + } + if (totalSizeInGb > 32) { + options.add(32); + } + options.add(Integer.MAX_VALUE); + String[] values = new String[options.size()]; + for (int i = 0; i < options.size(); i++) { + if (options.get(i) == 1) { + values[i] = String.format("300 MB"); + } else if (options.get(i) == Integer.MAX_VALUE) { + values[i] = LocaleController.getString("NoLimit", R.string.NoLimit); + } else { + values[i] = String.format("%d GB", options.get(i)); + } + } + slideChooseView2.setCallback(i -> { + SharedConfig.getPreferences().edit().putInt("cache_limit", options.get(i)).apply(); + }); + int currentLimit = SharedConfig.getPreferences().getInt("cache_limit", Integer.MAX_VALUE); + int i = options.indexOf(currentLimit); + if (i < 0) { + i = options.size() - 1; + } + slideChooseView2.setOptions(i, values); + break; } return new RecyclerListView.Holder(view); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + final ItemInner item = itemInners.get(position); switch (holder.getItemViewType()) { + case VIEW_TYPE_CHART: +// updateChart(); + break; + case VIEW_TYPE_CHART_HEADER: + if (cacheChartHeader != null && !calculating) { + cacheChartHeader.setData( + totalSize > 0, + totalDeviceSize <= 0 ? 0 : (float) totalSize / totalDeviceSize, + totalDeviceFreeSize <= 0 || totalDeviceSize <= 0 ? 0 : (float) (totalDeviceSize - totalDeviceFreeSize) / totalDeviceSize + ); + } + break; + case VIEW_TYPE_SECTION: + CheckBoxCell cell = (CheckBoxCell) holder.itemView; + final boolean selected; + if (item.index < 0) { + selected = isOtherSelected(); + } else { + selected = CacheControlActivity.this.selected[item.index]; + } + cell.setText(getCheckBoxTitle(item.headerName, percents[item.index < 0 ? 8 : item.index], item.index < 0), AndroidUtilities.formatFileSize(item.size), selected, item.index < 0 ? !collapsed : !item.last); + cell.setCheckBoxColor(item.colorKey, Theme.key_windowBackgroundWhiteGrayIcon, Theme.key_checkboxCheck); + cell.setCollapsed(item.index < 0 ? collapsed : null); + if (item.index == -1) { + cell.setOnSectionsClickListener(e -> { + collapsed = !collapsed; + updateRows(); + updateChart(); + }, e -> toggleOtherSelected(cell)); + } else { + cell.setOnSectionsClickListener(null, null); + } + cell.setPad(item.pad ? 1 : 0); + break; + case VIEW_TYPE_KEEP_MEDIA_CELL: + TextCell textCell2 = (TextCell) holder.itemView; + CacheByChatsController cacheByChatsController = getMessagesController().getCacheByChatsController(); + int keepMediaType = item.keepMediaType; + int exceptionsCount = cacheByChatsController.getKeepMediaExceptions(itemInners.get(position).keepMediaType).size(); + String subtitle = null; + if (exceptionsCount > 0) { + subtitle = LocaleController.formatPluralString("ExceptionShort", exceptionsCount, exceptionsCount); + } + String value = CacheByChatsController.getKeepMediaString(cacheByChatsController.getKeepMedia(keepMediaType)); + if (itemInners.get(position).keepMediaType == KEEP_MEDIA_TYPE_USER) { + textCell2.setTextAndValue(LocaleController.getString("PrivateChats", R.string.PrivateChats), value, true, true); + textCell2.setColorfulIcon(getThemedColor(Theme.key_statisticChartLine_lightblue), R.drawable.msg_filled_menu_users); + } else if (itemInners.get(position).keepMediaType == KEEP_MEDIA_TYPE_GROUP) { + textCell2.setTextAndValue(LocaleController.getString("GroupChats", R.string.GroupChats), value, true, true); + textCell2.setColorfulIcon(getThemedColor(Theme.key_statisticChartLine_green), R.drawable.msg_filled_menu_groups); + } else if (itemInners.get(position).keepMediaType == KEEP_MEDIA_TYPE_CHANNEL) { + textCell2.setTextAndValue(LocaleController.getString("CacheChannels", R.string.CacheChannels), value, true, false); + textCell2.setColorfulIcon(getThemedColor(Theme.key_statisticChartLine_golden), R.drawable.msg_filled_menu_channels); + } + textCell2.setSubtitle(subtitle); + break; case VIEW_TYPE_TEXT_SETTINGS: TextSettingsCell textCell = (TextSettingsCell) holder.itemView; - if (position == databaseRow) { - textCell.setTextAndValue(LocaleController.getString("ClearLocalDatabase", R.string.ClearLocalDatabase), AndroidUtilities.formatFileSize(databaseSize), updateDatabaseSize, false); - updateDatabaseSize = false; - } else if (position == migrateOldFolderRow) { +// if (position == databaseRow) { +// textCell.setTextAndValue(LocaleController.getString("ClearLocalDatabase", R.string.ClearLocalDatabase), AndroidUtilities.formatFileSize(databaseSize), updateDatabaseSize, false); +// updateDatabaseSize = false; +// } else + if (position == migrateOldFolderRow) { textCell.setTextAndValue(LocaleController.getString("MigrateOldFolder", R.string.MigrateOldFolder), null, false); } break; case VIEW_TYPE_INFO: TextInfoPrivacyCell privacyCell = (TextInfoPrivacyCell) holder.itemView; - if (position == databaseInfoRow) { - privacyCell.setText(LocaleController.getString("LocalDatabaseInfo", R.string.LocalDatabaseInfo)); - privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); - } else if (position == keepMediaInfoRow) { - privacyCell.setText(AndroidUtilities.replaceTags(LocaleController.getString("KeepMediaInfo", R.string.KeepMediaInfo))); +// if (position == databaseInfoRow) { +// privacyCell.setText(LocaleController.getString("LocalDatabaseInfo", R.string.LocalDatabaseInfo)); +// privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); +// } else if (position == keepMediaInfoRow) { +// privacyCell.setText(AndroidUtilities.replaceTags(LocaleController.getString("KeepMediaInfo", R.string.KeepMediaInfo))); +// privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); +// } else { + privacyCell.setText(AndroidUtilities.replaceTags(item.text)); privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); - } else { - privacyCell.setText(""); - privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); - } +// } break; case VIEW_TYPE_STORAGE: StorageUsageView storageUsageView = (StorageUsageView) holder.itemView; @@ -1171,24 +2155,7 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe headerCell.setTopMargin(itemInners.get(position).headerTopMargin); headerCell.setBottomMargin(itemInners.get(position).headerBottomMargin); break; - case VIEW_TYPE_CHAT: - UserCell userCell = (UserCell) holder.itemView; - DialogFileEntities dialogFileEntities = itemInners.get(position).entities; - TLObject object = getMessagesController().getUserOrChat(dialogFileEntities.dialogId); - String title; - boolean animated = userCell.dialogFileEntities != null && userCell.dialogFileEntities.dialogId == dialogFileEntities.dialogId; - if (dialogFileEntities.dialogId == UNKNOWN_CHATS_DIALOG_ID) { - title = LocaleController.getString("CacheOtherChats", R.string.CacheOtherChats); - userCell.getImageView().getAvatarDrawable().setAvatarType(AvatarDrawable.AVATAR_TYPE_OTHER_CHATS); - userCell.getImageView().setForUserOrChat(null, userCell.getImageView().getAvatarDrawable()); - } else { - title = DialogObject.setDialogPhotoTitle(userCell.getImageView(), object); - } - userCell.dialogFileEntities = dialogFileEntities; - userCell.getImageView().setRoundRadius(AndroidUtilities.dp(object instanceof TLRPC.Chat && ((TLRPC.Chat) object).forum ? 12 : 19)); - userCell.setTextAndValue(title, AndroidUtilities.formatFileSize(dialogFileEntities.totalSize), position < getItemCount() - 2); - userCell.setChecked(selectedDialogs.contains(dialogFileEntities.dialogId), animated); - break; + } } @@ -1198,6 +2165,40 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe } } + private void updateActionMode() { + if (cacheModel.getSelectedFiles() > 0) { + if (cachedMediaLayout != null) { + String filesString; + if (!cacheModel.selectedDialogs.isEmpty()) { + int filesInChats = 0; + for (CacheControlActivity.DialogFileEntities entity : cacheModel.entities) { + if (cacheModel.selectedDialogs.contains(entity.dialogId)) { + filesInChats += entity.filesCount; + } + } + int filesNotInChat = cacheModel.getSelectedFiles() - filesInChats; + if (filesNotInChat > 0) { + filesString = String.format("%s, %s", + LocaleController.formatPluralString("Chats", cacheModel.selectedDialogs.size(), cacheModel.selectedDialogs.size()), + LocaleController.formatPluralString("Files", filesNotInChat, filesNotInChat) + ); + } else { + filesString = LocaleController.formatPluralString("Chats", cacheModel.selectedDialogs.size(), cacheModel.selectedDialogs.size()); + } + } else { + filesString = LocaleController.formatPluralString("Files", cacheModel.getSelectedFiles(), cacheModel.getSelectedFiles()); + } + String sizeString = AndroidUtilities.formatFileSize(cacheModel.getSelectedFilesSize()); + actionModeTitle.setText(sizeString); + actionModeSubtitle.setText(filesString); + cachedMediaLayout.showActionMode(true); + } + } else { + cachedMediaLayout.showActionMode(false); + return; + } + } + @Override public ArrayList getThemeDescriptions() { ThemeDescription.ThemeDescriptionDelegate deldegagte = () -> { @@ -1259,7 +2260,7 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe return arrayList; } - public class UserCell extends FrameLayout implements NotificationCenter.NotificationCenterDelegate { + public static class UserCell extends FrameLayout implements NotificationCenter.NotificationCenterDelegate { public DialogFileEntities dialogFileEntities; private Theme.ResourcesProvider resourcesProvider; @@ -1473,26 +2474,28 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe } } - private class DialogFileEntities { + public static class DialogFileEntities { - long dialogId; + public long dialogId; + int filesCount; long totalSize; - SparseArray entitiesByType = new SparseArray<>(); + public final SparseArray entitiesByType = new SparseArray<>(); public DialogFileEntities(long dialogId) { this.dialogId = dialogId; } - public void addFile(File file, int type) { + public void addFile(CacheModel.FileInfo file, int type) { FileEntities entities = entitiesByType.get(type, null); if (entities == null) { entities = new FileEntities(); entitiesByType.put(type, entities); } entities.count++; - long fileSize = file.length(); + long fileSize = file.size; entities.totalSize += fileSize; totalSize += fileSize; + filesCount++; entities.files.add(file); } @@ -1510,28 +2513,81 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe totalSize += entitesToMerge.totalSize; entities.files.addAll(entitesToMerge.files); } + filesCount += dialogEntities.filesCount; + } + + public void removeFile(CacheModel.FileInfo fileInfo) { + FileEntities entities = entitiesByType.get(fileInfo.type, null); + if (entities == null) { + return; + } + if (entities.files.remove(fileInfo)) { + entities.count--; + entities.totalSize -= fileInfo.size; + totalSize -= fileInfo.size; + filesCount--; + } + } + + public boolean isEmpty() { + return totalSize <= 0; + } + + public CacheModel createCacheModel() { + CacheModel cacheModel = new CacheModel(true); + if (entitiesByType.get(TYPE_PHOTOS) != null) { + cacheModel.media.addAll(entitiesByType.get(TYPE_PHOTOS).files); + } + if (entitiesByType.get(TYPE_VIDEOS) != null) { + cacheModel.media.addAll(entitiesByType.get(TYPE_VIDEOS).files); + } + if (entitiesByType.get(TYPE_DOCUMENTS) != null) { + cacheModel.documents.addAll(entitiesByType.get(TYPE_DOCUMENTS).files); + } + if (entitiesByType.get(TYPE_MUSIC) != null) { + cacheModel.music.addAll(entitiesByType.get(TYPE_MUSIC).files); + } + if (entitiesByType.get(TYPE_VOICE) != null) { + cacheModel.voice.addAll(entitiesByType.get(TYPE_VOICE).files); + } + cacheModel.selectAllFiles(); + cacheModel.sortBySize(); + return cacheModel; } } - private class FileEntities { - long totalSize; - int count; - ArrayList files = new ArrayList<>(); + public static class FileEntities { + public long totalSize; + public int count; + public ArrayList files = new ArrayList<>(); } - private class ItemInner extends AdapterWithDiffUtils.Item { + public static class ItemInner extends AdapterWithDiffUtils.Item { int headerTopMargin = 15; int headerBottomMargin = 0; - String headerName; + int keepMediaType = -1; + CharSequence headerName; + String text; DialogFileEntities entities; + public int index; + public long size; + String colorKey; + public boolean pad; + boolean last; + public ItemInner(int viewType, String headerName, DialogFileEntities dialogFileEntities) { super(viewType, true); this.headerName = headerName; this.entities = dialogFileEntities; } + public ItemInner(int viewType, int keepMediaType) { + super(viewType, true); + this.keepMediaType = keepMediaType; + } + public ItemInner(int viewType, String headerName, int headerTopMargin, int headerBottomMargin, DialogFileEntities dialogFileEntities) { super(viewType, true); this.headerName = headerName; @@ -1540,46 +2596,88 @@ public class CacheControlActivity extends BaseFragment implements NotificationCe this.entities = dialogFileEntities; } + private ItemInner(int viewType) { + super(viewType, true); + } + + public static ItemInner asCheckBox(CharSequence text, int index, long size, String colorKey) { + return asCheckBox(text, index, size, colorKey, false); + } + + public static ItemInner asCheckBox(CharSequence text, int index, long size, String colorKey, boolean last) { + ItemInner item = new ItemInner(VIEW_TYPE_SECTION); + item.index = index; + item.headerName = text; + item.size = size; + item.colorKey = colorKey; + item.last = last; + return item; + } + + public static ItemInner asInfo(String text) { + ItemInner item = new ItemInner(VIEW_TYPE_INFO); + item.text = text; + return item; + } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ItemInner itemInner = (ItemInner) o; if (viewType == itemInner.viewType) { + if (viewType == VIEW_TYPE_CHART || viewType == VIEW_TYPE_CHART_HEADER) { + return true; + } if (viewType == VIEW_TYPE_CHAT && entities != null && itemInner.entities != null) { return entities.dialogId == itemInner.entities.dialogId; } - if (viewType == VIEW_TYPE_CHOOSER || viewType == VIEW_TYPE_STORAGE || viewType == VIEW_TYPE_TEXT_SETTINGS) { + if (viewType == VIEW_TYPE_CACHE_VIEW_PAGER || viewType == VIEW_TYPE_CHOOSER || viewType == VIEW_TYPE_STORAGE || viewType == VIEW_TYPE_TEXT_SETTINGS || viewType == VIEW_TYPE_CLEAR_CACHE_BUTTON) { return true; } - if (viewType == VIEW_TYPE_HEADER || viewType == VIEW_TYPE_INFO) { + if (viewType == VIEW_TYPE_HEADER) { return Objects.equals(headerName, itemInner.headerName); } + if (viewType == VIEW_TYPE_INFO) { + return Objects.equals(text, itemInner.text); + } + if (viewType == VIEW_TYPE_SECTION) { + return index == itemInner.index && size == itemInner.size; + } + if (viewType == VIEW_TYPE_KEEP_MEDIA_CELL) { + return keepMediaType == itemInner.keepMediaType; + } return false; } return false; } } - NumberTextView selectedDialogsCountTextView; - private void checkActionMode() { - if (actionBar.actionModeIsExist(null)) { - return; + AnimatedTextView selectedDialogsCountTextView; + + @Override + public boolean isSwipeBackEnabled(MotionEvent event) { + if (cachedMediaLayout != null) { + cachedMediaLayout.getHitRect(AndroidUtilities.rectTmp2); + if (!AndroidUtilities.rectTmp2.contains((int) event.getX(), (int) event.getY() - actionBar.getMeasuredHeight())) { + return true; + } else { + return cachedMediaLayout.viewPagerFixed.isCurrentTabFirst(); + } } - final ActionBarMenu actionMode = actionBar.createActionMode(false, null); + return true; + } - if (inPreviewMode) { - actionMode.setBackgroundColor(Color.TRANSPARENT); - actionMode.drawBlur = false; + @Override + public boolean onBackPressed() { + if (cacheModel != null && !cacheModel.selectedFiles.isEmpty()) { + cacheModel.clearSelection(); + if (cachedMediaLayout != null) { + cachedMediaLayout.showActionMode(false); + cachedMediaLayout.updateVisibleRows(); + } + return false; } - selectedDialogsCountTextView = new NumberTextView(actionMode.getContext()); - selectedDialogsCountTextView.setTextSize(18); - selectedDialogsCountTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - selectedDialogsCountTextView.setTextColor(Theme.getColor(Theme.key_actionBarActionModeDefaultIcon)); - actionMode.addView(selectedDialogsCountTextView, LayoutHelper.createLinear(0, LayoutHelper.MATCH_PARENT, 1.0f, 72, 0, 0, 0)); - selectedDialogsCountTextView.setOnTouchListener((v, event) -> true); - - - ActionBarMenuItem deleteItem = actionMode.addItemWithWidth(delete_id, R.drawable.msg_clear, AndroidUtilities.dp(54), LocaleController.getString("ClearCache", R.string.ClearCache)); + return super.onBackPressed(); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CachedMediaLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/CachedMediaLayout.java new file mode 100644 index 000000000..e6639052e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/CachedMediaLayout.java @@ -0,0 +1,1030 @@ +package org.telegram.ui; + +import static org.telegram.ui.CacheControlActivity.TYPE_DOCUMENTS; +import static org.telegram.ui.CacheControlActivity.TYPE_MUSIC; +import static org.telegram.ui.CacheControlActivity.TYPE_VIDEOS; +import static org.telegram.ui.CacheControlActivity.UNKNOWN_CHATS_DIALOG_ID; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.drawable.ColorDrawable; +import android.media.MediaMetadataRetriever; +import android.net.Uri; +import android.os.Bundle; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.DialogObject; +import org.telegram.messenger.FileLoader; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.ImageLocation; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MediaController; +import org.telegram.messenger.MessageObject; +import org.telegram.messenger.R; +import org.telegram.messenger.UserConfig; +import org.telegram.messenger.Utilities; +import org.telegram.tgnet.TLObject; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.ActionBarPopupWindow; +import org.telegram.ui.ActionBar.BackDrawable; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Cells.SharedAudioCell; +import org.telegram.ui.Cells.SharedDocumentCell; +import org.telegram.ui.Cells.SharedPhotoVideoCell2; +import org.telegram.ui.Components.AlertsCreator; +import org.telegram.ui.Components.AnimatedTextView; +import org.telegram.ui.Components.AvatarDrawable; +import org.telegram.ui.Components.CheckBox2; +import org.telegram.ui.Components.CombinedDrawable; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.ListView.AdapterWithDiffUtils; +import org.telegram.ui.Components.NestedSizeNotifierLayout; +import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.Components.ViewPagerFixed; +import org.telegram.ui.Storage.CacheModel; + +import java.io.File; +import java.util.ArrayList; +import java.util.Objects; + +public class CachedMediaLayout extends FrameLayout implements NestedSizeNotifierLayout.ChildLayout { + + private static final int PAGE_TYPE_CHATS = 0; + private static final int PAGE_TYPE_MEDIA = 1; + private static final int PAGE_TYPE_DOCUMENTS = 2; + private static final int PAGE_TYPE_MUSIC = 3; + private static final int PAGE_TYPE_VOICE = 4; + + private static final int VIEW_TYPE_CHAT = 1; + private static final int VIEW_TYPE_FILE_ENTRY = 2; + private final LinearLayout actionModeLayout; + private final ImageView closeButton; + private final BackDrawable backDrawable; + private final ArrayList actionModeViews = new ArrayList<>(); + public final AnimatedTextView selectedMessagesCountTextView; + private final ActionBarMenuItem clearItem; + private final ViewPagerFixed.TabsView tabs; + private final View divider; + + BaseFragment parentFragment; + + ArrayList pages = new ArrayList<>(); + CacheModel cacheModel; + ViewPagerFixed viewPagerFixed; + + Page[] allPages = new Page[5]; + + BasePlaceProvider placeProvider; + private int bottomPadding; + + public CachedMediaLayout(@NonNull Context context, BaseFragment parentFragment) { + super(context); + this.parentFragment = parentFragment; + + int CacheTabChats; + allPages[PAGE_TYPE_CHATS] = new Page(LocaleController.getString("Chats", R.string.Chats), PAGE_TYPE_CHATS, new DialogsAdapter()); + allPages[PAGE_TYPE_MEDIA] = new Page(LocaleController.getString("Media", R.string.Media), PAGE_TYPE_MEDIA, new MediaAdapter()); + allPages[PAGE_TYPE_DOCUMENTS] = new Page(LocaleController.getString("Files", R.string.Files), PAGE_TYPE_DOCUMENTS, new DocumentsAdapter()); + allPages[PAGE_TYPE_MUSIC] = new Page(LocaleController.getString("Music", R.string.Music), PAGE_TYPE_MUSIC, new MusicAdapter()); + // allPages[PAGE_TYPE_VOICE] = new Page(LocaleController.getString("Voice", R.string.Voice), PAGE_TYPE_VOICE, new VoiceAdapter()); + + for (int i = 0; i < allPages.length; i++) { + if (allPages[i] == null) { + continue; + } + pages.add(i, allPages[i]); + } + + viewPagerFixed = new ViewPagerFixed(getContext()); + viewPagerFixed.setAllowDisallowInterceptTouch(false); + addView(viewPagerFixed, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 0, 0, 48, 0, 0)); + addView(tabs = viewPagerFixed.createTabsView(true, 3), LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48)); + divider = new View(getContext()); + divider.setBackgroundColor(Theme.getColor(Theme.key_divider)); + addView(divider, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 1, 0, 0, 48, 0, 0)); + divider.getLayoutParams().height = 1; + viewPagerFixed.setAdapter(new ViewPagerFixed.Adapter() { + + private ActionBarPopupWindow popupWindow; + + @Override + public String getItemTitle(int position) { + return pages.get(position).title; + } + + @Override + public int getItemCount() { + return pages.size(); + } + + @Override + public int getItemId(int position) { + return pages.get(position).type; + } + + @Override + public View createView(int viewType) { + RecyclerListView recyclerListView = new RecyclerListView(context); + + DefaultItemAnimator itemAnimator = (DefaultItemAnimator) recyclerListView.getItemAnimator(); + itemAnimator.setDelayAnimations(false); + itemAnimator.setSupportsChangeAnimations(false); + recyclerListView.setClipToPadding(false); + recyclerListView.setPadding(0, 0, 0, bottomPadding); + recyclerListView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + BaseAdapter adapter = (BaseAdapter) recyclerListView.getAdapter(); + ItemInner itemInner = adapter.itemInners.get(position); + //if (cacheModel.getSelectedFiles() == 0) { + if (view instanceof SharedPhotoVideoCell2) { + openPhoto(itemInner, (MediaAdapter) adapter, recyclerListView, (SharedPhotoVideoCell2)view); + return; + } + + //} + if (delegate != null) { + delegate.onItemSelected(itemInner.entities, itemInner.file, false); + } + } + }); + recyclerListView.setOnItemLongClickListener((view, position, x, y) -> { + BaseAdapter adapter = (BaseAdapter) recyclerListView.getAdapter(); + ItemInner itemInner = adapter.itemInners.get(position); + if (view instanceof CacheCell || view instanceof SharedPhotoVideoCell2) { + ActionBarPopupWindow.ActionBarPopupWindowLayout popupWindowLayout = new ActionBarPopupWindow.ActionBarPopupWindowLayout(getContext()); + if (view instanceof SharedPhotoVideoCell2) { + ActionBarMenuItem.addItem(popupWindowLayout, R.drawable.msg_view_file, LocaleController.getString("CacheOpenFile", R.string.CacheOpenFile), false, null).setOnClickListener(v -> { + openPhoto(itemInner, (MediaAdapter) adapter, recyclerListView, (SharedPhotoVideoCell2) view); + if (popupWindow != null) { + popupWindow.dismiss(); + } + }); + } else if (((CacheCell) view).container.getChildAt(0) instanceof SharedAudioCell) { + ActionBarMenuItem.addItem(popupWindowLayout, R.drawable.msg_played, LocaleController.getString("PlayFile", R.string.PlayFile), false, null).setOnClickListener(v -> { + openItem(itemInner.file, (CacheCell) view); + if (popupWindow != null) { + popupWindow.dismiss(); + } + }); + } else { + ActionBarMenuItem.addItem(popupWindowLayout, R.drawable.msg_view_file, LocaleController.getString("CacheOpenFile", R.string.CacheOpenFile), false, null).setOnClickListener(v -> { + openItem(itemInner.file, (CacheCell) view); + if (popupWindow != null) { + popupWindow.dismiss(); + } + }); + } + if (itemInner.file.dialogId != 0 && itemInner.file.messageId != 0) { + ActionBarMenuItem.addItem(popupWindowLayout, R.drawable.msg_viewintopic, LocaleController.getString("ViewInChat", R.string.ViewInChat), false, null).setOnClickListener(v -> { + Bundle args = new Bundle(); + if (itemInner.file.dialogId > 0) { + args.putLong("user_id", itemInner.file.dialogId); + } else { + args.putLong("chat_id", -itemInner.file.dialogId); + } + args.putInt("message_id", itemInner.file.messageId); + parentFragment.presentFragment(new ChatActivity(args)); + delegate.dismiss(); + if (popupWindow != null) { + popupWindow.dismiss(); + } + }); + } + ActionBarMenuItem.addItem(popupWindowLayout, R.drawable.msg_select, + !cacheModel.selectedFiles.contains(itemInner.file) ? LocaleController.getString("Select", R.string.Select) : LocaleController.getString("Deselect", R.string.Deselect), + false, null).setOnClickListener(v -> { + if (delegate != null) { + delegate.onItemSelected(itemInner.entities, itemInner.file, true); + } + if (popupWindow != null) { + popupWindow.dismiss(); + } + }); + popupWindow = AlertsCreator.createSimplePopup(parentFragment, popupWindowLayout, view, (int) x, (int) y); + getRootView().dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0)); + return true; + } else { + if (delegate != null) { + delegate.onItemSelected(itemInner.entities, itemInner.file, true); + } + } + return true; + }); + return recyclerListView; + } + + @Override + public void bindView(View view, int position, int viewType) { + RecyclerListView recyclerListView = (RecyclerListView) view; + recyclerListView.setAdapter(pages.get(position).adapter); + if (pages.get(position).type == PAGE_TYPE_MEDIA) { + recyclerListView.setLayoutManager(new GridLayoutManager(view.getContext(), 3)); + } else { + recyclerListView.setLayoutManager(new LinearLayoutManager(view.getContext())); + } + recyclerListView.setTag(pages.get(position).type); + } + + @Override + public boolean hasStableId() { + return true; + } + + }); + + actionModeLayout = new LinearLayout(context); + actionModeLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + actionModeLayout.setAlpha(0.0f); + actionModeLayout.setClickable(true); + addView(actionModeLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48)); + AndroidUtilities.updateViewVisibilityAnimated(actionModeLayout, false, 1f, false); + + closeButton = new ImageView(context); + closeButton.setScaleType(ImageView.ScaleType.CENTER); + closeButton.setImageDrawable(backDrawable = new BackDrawable(true)); + backDrawable.setColor(Theme.getColor(Theme.key_actionBarActionModeDefaultIcon)); + closeButton.setBackground(Theme.createSelectorDrawable(Theme.getColor(Theme.key_actionBarActionModeDefaultSelector), 1)); + closeButton.setContentDescription(LocaleController.getString("Close", R.string.Close)); + actionModeLayout.addView(closeButton, new LinearLayout.LayoutParams(AndroidUtilities.dp(54), ViewGroup.LayoutParams.MATCH_PARENT)); + actionModeViews.add(closeButton); + closeButton.setOnClickListener(v -> { + delegate.clearSelection(); + }); + + selectedMessagesCountTextView = new AnimatedTextView(context, true, true, true); + selectedMessagesCountTextView.setTextSize(AndroidUtilities.dp(18)); + selectedMessagesCountTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + selectedMessagesCountTextView.setTextColor(Theme.getColor(Theme.key_actionBarActionModeDefaultIcon)); + actionModeLayout.addView(selectedMessagesCountTextView, LayoutHelper.createLinear(0, LayoutHelper.MATCH_PARENT, 1.0f, 18, 0, 0, 0)); + actionModeViews.add(selectedMessagesCountTextView); + + clearItem = new ActionBarMenuItem(context, null, Theme.getColor(Theme.key_actionBarActionModeDefaultSelector), Theme.getColor(Theme.key_actionBarActionModeDefaultIcon), false); + clearItem.setIcon(R.drawable.msg_clear); + clearItem.setContentDescription(LocaleController.getString("Delete", R.string.Delete)); + clearItem.setDuplicateParentStateEnabled(false); + actionModeLayout.addView(clearItem, new LinearLayout.LayoutParams(AndroidUtilities.dp(54), ViewGroup.LayoutParams.MATCH_PARENT)); + actionModeViews.add(clearItem); + clearItem.setOnClickListener(v -> { + delegate.clear(); + }); + } + + private void openPhoto(ItemInner itemInner, MediaAdapter adapter, RecyclerListView recyclerListView, SharedPhotoVideoCell2 view) { + MediaAdapter mediaAdapter = (MediaAdapter) adapter; + PhotoViewer.getInstance().setParentActivity(parentFragment); + if (placeProvider == null) { + placeProvider = new BasePlaceProvider(); + } + placeProvider.setRecyclerListView(recyclerListView); + int p = adapter.itemInners.indexOf(itemInner); + if (p >= 0) { + PhotoViewer.getInstance().openPhotoForSelect(mediaAdapter.getPhotos(), adapter.itemInners.indexOf(itemInner), PhotoViewer.SELECT_TYPE_NO_SELECT, false, placeProvider, null); + } + } + + private void openItem(CacheModel.FileInfo fileInfo, CacheCell cacheCell) { + RecyclerListView recyclerListView = (RecyclerListView) viewPagerFixed.getCurrentView(); + if (cacheCell.type == TYPE_DOCUMENTS) { + if (!(recyclerListView.getAdapter() instanceof DocumentsAdapter)) { + return; + } + DocumentsAdapter documentsAdapter = (DocumentsAdapter) recyclerListView.getAdapter(); + PhotoViewer.getInstance().setParentActivity(parentFragment); + if (placeProvider == null) { + placeProvider = new BasePlaceProvider(); + } + placeProvider.setRecyclerListView(recyclerListView); + + if (fileIsMedia(fileInfo.file)) { + ArrayList photoEntries = new ArrayList<>(); + photoEntries.add(new MediaController.PhotoEntry(0, 0, 0, fileInfo.file.getPath(), 0, fileInfo.type == TYPE_VIDEOS, 0, 0, 0)); + ; + PhotoViewer.getInstance().openPhotoForSelect(photoEntries, 0, PhotoViewer.SELECT_TYPE_NO_SELECT, false, placeProvider, null); + } else { + AndroidUtilities.openForView(fileInfo.file, fileInfo.file.getName(), null, parentFragment.getParentActivity(), null); + } + } + if (cacheCell.type == TYPE_MUSIC) { + if (MediaController.getInstance().isPlayingMessage(fileInfo.messageObject)) { + if (!MediaController.getInstance().isMessagePaused()) { + MediaController.getInstance().pauseMessage(fileInfo.messageObject); + } else { + MediaController.getInstance().playMessage(fileInfo.messageObject); + } + } else { + // MediaController.getInstance().setPlaylist(documentsAdapter.createPlaylist(), fileInfo.messageObject, 0); + MediaController.getInstance().playMessage(fileInfo.messageObject); + } + } + return; + } + + private SharedPhotoVideoCell2 getCellForIndex(int index) { + RecyclerListView listView = getListView(); + for (int i = 0; i < listView.getChildCount(); i++) { + View child = listView.getChildAt(i); + if (listView.getChildAdapterPosition(child) == index && child instanceof SharedPhotoVideoCell2) { + return (SharedPhotoVideoCell2) child; + } + } + return null; + } + + public void setCacheModel(CacheModel cacheModel) { + this.cacheModel = cacheModel; + update(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + //itemSize = ((MeasureSpec.getSize(widthMeasureSpec) - AndroidUtilities.dp(6 * 2) - AndroidUtilities.dp(5 * 2)) / 3); + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY)); + } + + public void update() { + ArrayList oldPages = new ArrayList<>(); + oldPages.addAll(pages); + pages.clear(); + if (cacheModel != null) { + for (int i = 0; i < allPages.length; i++) { + if (allPages[i] == null) { + continue; + } + if (allPages[i].type == PAGE_TYPE_CHATS && !cacheModel.entities.isEmpty()) { + pages.add(allPages[i]); + } else if (allPages[i].type == PAGE_TYPE_MEDIA && !cacheModel.media.isEmpty()) { + pages.add(allPages[i]); + } else if (allPages[i].type == PAGE_TYPE_DOCUMENTS && !cacheModel.documents.isEmpty()) { + pages.add(allPages[i]); + } else if (allPages[i].type == PAGE_TYPE_MUSIC && !cacheModel.music.isEmpty()) { + pages.add(allPages[i]); + } else if (allPages[i].type == PAGE_TYPE_VOICE && !cacheModel.voice.isEmpty()) { + pages.add(allPages[i]); + } + } + } + if (pages.size() == 1 && cacheModel.isDialog) { + tabs.setVisibility(View.GONE); + ((MarginLayoutParams) viewPagerFixed.getLayoutParams()).topMargin = 0; + ((MarginLayoutParams) divider.getLayoutParams()).topMargin = 0; + } + boolean rebuildPager = false; + if (oldPages.size() == pages.size()) { + for (int i = 0; i < oldPages.size(); i++) { + if (oldPages.get(i).type != pages.get(i).type) { + rebuildPager = true; + break; + } + } + } else { + rebuildPager = true; + } + if (rebuildPager) { + viewPagerFixed.rebuild(true); + } + for (int i = 0; i < pages.size(); i++) { + if (pages.get(i).adapter != null) { + pages.get(i).adapter.update(); + } + } + } + + @Override + public RecyclerListView getListView() { + if (viewPagerFixed.getCurrentView() == null) { + return null; + } + return (RecyclerListView) viewPagerFixed.getCurrentView(); + } + + @Override + public boolean isAttached() { + return true; + } + + public void updateVisibleRows() { + for (int i = 0; i < viewPagerFixed.getViewPages().length; i++) { + RecyclerListView recyclerListView = (RecyclerListView) viewPagerFixed.getViewPages()[i]; + AndroidUtilities.updateVisibleRows(recyclerListView); + } + } + + public void setBottomPadding(int padding) { + this.bottomPadding = padding; + for (int i = 0; i < viewPagerFixed.getViewPages().length; i++) { + RecyclerListView recyclerListView = (RecyclerListView) viewPagerFixed.getViewPages()[i]; + if (recyclerListView != null) { + recyclerListView.setPadding(0, 0, 0, padding); + } + } + } + + protected void showActionMode(boolean show) { + + } + + protected boolean actionModeIsVisible() { + return false; + } + +// public void showActionMode(boolean show) { +// AndroidUtilities.updateViewVisibilityAnimated(actionModeLayout, show); +// } + +// public boolean actionModeIsVisible() { +// return actionModeLayout.getVisibility() == View.VISIBLE; +// } + + private class Page { + final public String title; + final public int type; + final public BaseAdapter adapter; + + private Page(String title, int type, BaseAdapter adapter) { + this.title = title; + this.type = type; + this.adapter = adapter; + } + } + + private abstract class BaseAdapter extends AdapterWithDiffUtils { + + + final int type; + + ArrayList itemInners = new ArrayList<>(); + + protected BaseAdapter(int type) { + this.type = type; + } + + @Override + public int getItemViewType(int position) { + return itemInners.get(position).viewType; + } + + @Override + public int getItemCount() { + return itemInners.size(); + } + + abstract void update(); + + public ArrayList getPhotos() { + return null; + } + } + + private class DialogsAdapter extends BaseAdapter { + + ArrayList old = new ArrayList<>(); + + private DialogsAdapter() { + super(PAGE_TYPE_CHATS); + } + + @Override + void update() { + old.clear(); + old.addAll(itemInners); + itemInners.clear(); + if (cacheModel != null) { + for (int i = 0; i < cacheModel.entities.size(); i++) { + itemInners.add(new ItemInner(VIEW_TYPE_CHAT, cacheModel.entities.get(i))); + } + } + setItems(old, itemInners); +// if (loadingDialogs) { +// itemInners.add(new ItemInner(VIEW_FLICKER_LOADING_DIALOG, null, null)); +// } else if (dialogsFilesEntities != null && dialogsFilesEntities.size() > 0) { +// +// itemInners.add(new ItemInner(VIEW_TYPE_INFO, null, null)); +// } + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = null; + switch (viewType) { + case VIEW_TYPE_CHAT: + CacheControlActivity.UserCell userCell = new CacheControlActivity.UserCell(getContext(), null); + userCell.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + view = userCell; + break; + } + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case VIEW_TYPE_CHAT: + CacheControlActivity.UserCell userCell = (CacheControlActivity.UserCell) holder.itemView; + CacheControlActivity.DialogFileEntities dialogFileEntities = itemInners.get(position).entities; + TLObject object = parentFragment.getMessagesController().getUserOrChat(dialogFileEntities.dialogId); + String title; + boolean animated = userCell.dialogFileEntities != null && userCell.dialogFileEntities.dialogId == dialogFileEntities.dialogId; + if (dialogFileEntities.dialogId == UNKNOWN_CHATS_DIALOG_ID) { + title = LocaleController.getString("CacheOtherChats", R.string.CacheOtherChats); + userCell.getImageView().getAvatarDrawable().setAvatarType(AvatarDrawable.AVATAR_TYPE_OTHER_CHATS); + userCell.getImageView().setForUserOrChat(null, userCell.getImageView().getAvatarDrawable()); + } else { + title = DialogObject.setDialogPhotoTitle(userCell.getImageView(), object); + } + userCell.dialogFileEntities = dialogFileEntities; + userCell.getImageView().setRoundRadius(AndroidUtilities.dp(object instanceof TLRPC.Chat && ((TLRPC.Chat) object).forum ? 12 : 19)); + userCell.setTextAndValue(title, AndroidUtilities.formatFileSize(dialogFileEntities.totalSize), position < getItemCount() - 1); + userCell.setChecked(cacheModel.isSelected(dialogFileEntities.dialogId), animated); + break; + } + } + + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return true; + } + } + + private abstract class BaseFilesAdapter extends BaseAdapter { + + ArrayList oldItems = new ArrayList<>(); + + protected BaseFilesAdapter(int type) { + super(type); + } + + @Override + void update() { + oldItems.clear(); + oldItems.addAll(itemInners); + itemInners.clear(); + if (cacheModel != null) { + ArrayList files = null; + if (type == PAGE_TYPE_MEDIA) { + files = cacheModel.media; + } else if (type == PAGE_TYPE_DOCUMENTS) { + files = cacheModel.documents; + } else if (type == PAGE_TYPE_MUSIC) { + files = cacheModel.music; + } else if (type == PAGE_TYPE_VOICE) { + files = cacheModel.voice; + } + if (files != null) { + for (int i = 0; i < files.size(); i++) { + itemInners.add(new ItemInner(VIEW_TYPE_FILE_ENTRY, files.get(i))); + } + } + } + setItems(oldItems, itemInners); + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return true; + } + } + + private class ItemInner extends AdapterWithDiffUtils.Item { + + CacheControlActivity.DialogFileEntities entities; + CacheModel.FileInfo file; + + public ItemInner(int viewType, CacheControlActivity.DialogFileEntities entities) { + super(viewType, true); + this.entities = entities; + } + + public ItemInner(int viewType, CacheModel.FileInfo file) { + super(viewType, true); + this.file = file; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ItemInner itemInner = (ItemInner) o; + if (viewType == itemInner.viewType) { + if (viewType == VIEW_TYPE_CHAT && entities != null && itemInner.entities != null) { + return entities.dialogId == itemInner.entities.dialogId; + } + if (viewType == VIEW_TYPE_FILE_ENTRY && file != null && itemInner.file != null) { + return Objects.equals(file.file, itemInner.file.file); + } + return false; + } + return false; + } + } + + private class MediaAdapter extends BaseFilesAdapter { + + private SharedPhotoVideoCell2.SharedResources sharedResources; + + private MediaAdapter() { + super(PAGE_TYPE_MEDIA); + } + + ArrayList photoEntries = new ArrayList<>(); + + @Override + void update() { + super.update(); + photoEntries.clear(); + for (int i = 0; i < itemInners.size(); i++) { + photoEntries.add(new MediaController.PhotoEntry(0, 0, 0, itemInners.get(i).file.file.getPath(), 0, itemInners.get(i).file.type == TYPE_VIDEOS, 0, 0, 0)); + } + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + if (sharedResources == null) { + sharedResources = new SharedPhotoVideoCell2.SharedResources(parent.getContext(), null); + } + SharedPhotoVideoCell2 view = new SharedPhotoVideoCell2(parent.getContext(), sharedResources, parentFragment.getCurrentAccount()) { + @Override + public void onCheckBoxPressed() { + CacheModel.FileInfo file = (CacheModel.FileInfo) getTag(); + delegate.onItemSelected(null, file, true); + } + }; + view.setStyle(SharedPhotoVideoCell2.STYLE_CACHE); + return new RecyclerListView.Holder(view); + } + + CombinedDrawable thumb; + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + if (thumb == null) { + thumb = new CombinedDrawable(new ColorDrawable(Theme.getColor(Theme.key_chat_attachPhotoBackground)), Theme.chat_attachEmptyDrawable); + thumb.setFullsize(true); + } + SharedPhotoVideoCell2 cell = (SharedPhotoVideoCell2) holder.itemView; + CacheModel.FileInfo file = itemInners.get(position).file; + boolean animated = file == cell.getTag(); + cell.setTag(file); + if (file.type == TYPE_VIDEOS) { + cell.imageReceiver.setImage(ImageLocation.getForPath("vthumb://" + 0 + ":" + file.file.getAbsolutePath()), null, thumb, null, null, 0); + cell.setVideoText(AndroidUtilities.formatFileSize(file.size), true); + } else { + cell.imageReceiver.setImage(ImageLocation.getForPath("thumb://" + 0 + ":" + file.file.getAbsolutePath()), null, thumb, null, null, 0); + cell.setVideoText(AndroidUtilities.formatFileSize(file.size), false); + } + cell.setChecked(cacheModel.isSelected(file), animated); + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return false; + } + + @Override + public ArrayList getPhotos() { + return photoEntries; + } + } + + private class DocumentsAdapter extends BaseFilesAdapter { + + private DocumentsAdapter() { + super(PAGE_TYPE_DOCUMENTS); + } + + ArrayList photoEntries = new ArrayList<>(); + + @Override + void update() { + super.update(); + photoEntries.clear(); + for (int i = 0; i < itemInners.size(); i++) { + photoEntries.add(new MediaController.PhotoEntry(0, 0, 0, itemInners.get(i).file.file.getPath(), 0, itemInners.get(i).file.type == TYPE_VIDEOS, 0, 0, 0)); + } + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + CacheCell cacheCell = new CacheCell(parent.getContext()) { + @Override + public void onCheckBoxPressed() { + CacheModel.FileInfo file = (CacheModel.FileInfo) getTag(); + delegate.onItemSelected(null, file, true); + } + }; + cacheCell.type = TYPE_DOCUMENTS; + SharedDocumentCell cell = new SharedDocumentCell(parent.getContext(), SharedDocumentCell.VIEW_TYPE_CACHE, null); + cacheCell.container.addView(cell); + return new RecyclerListView.Holder(cacheCell); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + CacheCell cacheCell = (CacheCell) holder.itemView; + SharedDocumentCell cell = (SharedDocumentCell) cacheCell.container.getChildAt(0); + CacheModel.FileInfo file = itemInners.get(position).file; + boolean animated = file == holder.itemView.getTag(); + boolean divider = position != itemInners.size() - 1; + holder.itemView.setTag(file); + long date = file.file.lastModified(); + + cell.setTextAndValueAndTypeAndThumb(file.messageType == MessageObject.TYPE_ROUND_VIDEO ? LocaleController.getString("AttachRound", R.string.AttachRound) : file.file.getName(), LocaleController.formatDateAudio(date, true), Utilities.getExtension(file.file.getName()), null, 0, divider); + if (!animated) { + cell.setPhoto(file.file.getPath()); + } + cell.getImageView().setRoundRadius(file.messageType == MessageObject.TYPE_ROUND_VIDEO ? AndroidUtilities.dp(20) : AndroidUtilities.dp(4)); + cacheCell.drawDivider = divider; + cacheCell.sizeTextView.setText(AndroidUtilities.formatFileSize(file.size)); + cacheCell.checkBox.setChecked(cacheModel.isSelected(file), animated); + } + + @Override + public ArrayList getPhotos() { + return photoEntries; + } + + } + + private class MusicAdapter extends BaseFilesAdapter { + + private MusicAdapter() { + super(PAGE_TYPE_MUSIC); + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + CacheCell cacheCell = new CacheCell(parent.getContext()) { + @Override + public void onCheckBoxPressed() { + CacheModel.FileInfo file = (CacheModel.FileInfo) getTag(); + delegate.onItemSelected(null, file, true); + } + }; + cacheCell.type = TYPE_MUSIC; + SharedAudioCell cell = new SharedAudioCell(parent.getContext(), SharedDocumentCell.VIEW_TYPE_DEFAULT, null) { + @Override + public void didPressedButton() { + openItem((CacheModel.FileInfo) cacheCell.getTag(), cacheCell); + } + }; + cell.setCheckForButtonPress(true); + cacheCell.container.addView(cell); + return new RecyclerListView.Holder(cacheCell); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + CacheCell cacheCell = (CacheCell) holder.itemView; + SharedAudioCell cell = (SharedAudioCell) cacheCell.container.getChildAt(0); + CacheModel.FileInfo fileInfo = itemInners.get(position).file; + boolean animated = fileInfo == cacheCell.getTag(); + boolean divider = position != itemInners.size() - 1; + cacheCell.setTag(fileInfo); + + checkMessageObjectForAudio(fileInfo, position); + cell.setMessageObject(fileInfo.messageObject, divider); + cell.showName(!fileInfo.metadata.loading, animated); + cacheCell.drawDivider = divider; + cacheCell.sizeTextView.setText(AndroidUtilities.formatFileSize(fileInfo.size)); + cacheCell.checkBox.setChecked(cacheModel.isSelected(fileInfo), animated); + } + + public ArrayList createPlaylist() { + ArrayList playlist = new ArrayList<>(); + for (int i = 0; i < itemInners.size(); i++) { + checkMessageObjectForAudio(itemInners.get(i).file, i); + playlist.add(itemInners.get(i).file.messageObject); + } + return playlist; + } + } + + private void checkMessageObjectForAudio(CacheModel.FileInfo fileInfo, int position) { + if (fileInfo.messageObject == null) { + TLRPC.TL_message message = new TLRPC.TL_message(); + message.out = true; + message.id = position; + message.peer_id = new TLRPC.TL_peerUser(); + message.from_id = new TLRPC.TL_peerUser(); + message.peer_id.user_id = message.from_id.user_id = UserConfig.getInstance(parentFragment.getCurrentAccount()).getClientUserId(); + message.date = (int) (System.currentTimeMillis() / 1000); + message.message = ""; + message.attachPath = fileInfo.file.getPath(); + message.media = new TLRPC.TL_messageMediaDocument(); + message.media.flags |= 3; + message.media.document = new TLRPC.TL_document(); + message.flags |= TLRPC.MESSAGE_FLAG_HAS_MEDIA | TLRPC.MESSAGE_FLAG_HAS_FROM_ID; + + String ext = FileLoader.getFileExtension(fileInfo.file); + + message.media.document.id = 0; + message.media.document.access_hash = 0; + message.media.document.file_reference = new byte[0]; + message.media.document.date = message.date; + message.media.document.mime_type = "audio/" + (ext.length() > 0 ? ext : "mp3"); + message.media.document.size = fileInfo.size; + message.media.document.dc_id = 0; + + TLRPC.TL_documentAttributeAudio attributeAudio = new TLRPC.TL_documentAttributeAudio(); + + if (fileInfo.metadata == null) { + fileInfo.metadata = new CacheModel.FileInfo.FileMetadata(); + fileInfo.metadata.loading = true; + Utilities.globalQueue.postRunnable(() -> { + MediaMetadataRetriever mediaMetadataRetriever = null; + String title = ""; + String author = ""; + try { + mediaMetadataRetriever = new MediaMetadataRetriever(); + Uri uri = Uri.fromFile(fileInfo.file); + mediaMetadataRetriever.setDataSource(getContext(), uri); + + title = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE); + author = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST); + } catch (Exception e) { + FileLog.e(e); + } finally { + if (mediaMetadataRetriever != null) { + mediaMetadataRetriever.release(); + } + } + String finalTitle = title; + String finalAuthor = author; + AndroidUtilities.runOnUIThread(() -> { + fileInfo.metadata.loading = false; + attributeAudio.title = fileInfo.metadata.title = finalTitle; + attributeAudio.performer = fileInfo.metadata.author = finalAuthor; + updateRow(fileInfo, PAGE_TYPE_MUSIC); + }); + }); + + } + attributeAudio.flags |= 3; + message.media.document.attributes.add(attributeAudio); + + + TLRPC.TL_documentAttributeFilename fileName = new TLRPC.TL_documentAttributeFilename(); + fileName.file_name = fileInfo.file.getName(); + message.media.document.attributes.add(fileName); + + fileInfo.messageObject = new MessageObject(parentFragment.getCurrentAccount(), message, false, false); + fileInfo.messageObject.mediaExists = true; + } + } + + private void updateRow(CacheModel.FileInfo fileInfo, int pageType) { + for (int i = 0; i < viewPagerFixed.getViewPages().length; i++) { + RecyclerListView recyclerListView = (RecyclerListView) viewPagerFixed.getViewPages()[i]; + if (recyclerListView != null && ((BaseAdapter)recyclerListView.getAdapter()).type == pageType) { + BaseAdapter adapter = (BaseAdapter) recyclerListView.getAdapter(); + for (int k = 0; k < adapter.itemInners.size(); k++) { + if (adapter.itemInners.get(k).file == fileInfo) { + adapter.notifyItemChanged(k); + break; + } + } + } + } + } + + Delegate delegate; + + public void setDelegate(Delegate delegate) { + this.delegate = delegate; + } + + public interface Delegate { + void onItemSelected(CacheControlActivity.DialogFileEntities entities, CacheModel.FileInfo fileInfo, boolean longPress); + + void clear(); + + void clearSelection(); + + default void dismiss() { + + } + } + + private class BasePlaceProvider extends PhotoViewer.EmptyPhotoViewerProvider { + + RecyclerListView recyclerListView; + + public void setRecyclerListView(RecyclerListView recyclerListView) { + this.recyclerListView = recyclerListView; + } + + @Override + public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index, boolean needPreview) { + SharedPhotoVideoCell2 cell = getCellForIndex(index); + if (cell != null) { + int[] coords = new int[2]; + cell.getLocationInWindow(coords); + PhotoViewer.PlaceProviderObject object = new PhotoViewer.PlaceProviderObject(); + object.viewX = coords[0]; + object.viewY = coords[1]; + object.parentView = recyclerListView; + object.imageReceiver = cell.imageReceiver; + object.thumb = object.imageReceiver.getBitmapSafe(); + object.scale = cell.getScaleX(); + return object; + } + return null; + } + } + + private class VoiceAdapter extends BaseFilesAdapter { + + private VoiceAdapter() { + super(PAGE_TYPE_VOICE); + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + SharedDocumentCell cell = new SharedDocumentCell(parent.getContext(), SharedDocumentCell.VIEW_TYPE_DEFAULT, null); + return new RecyclerListView.Holder(cell); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + SharedDocumentCell cell = (SharedDocumentCell) holder.itemView; + CacheModel.FileInfo file = itemInners.get(position).file; + boolean animated = file == cell.getTag(); + cell.setTag(file); + cell.setTextAndValueAndTypeAndThumb(file.file.getName(), AndroidUtilities.formatFileSize(file.size), Utilities.getExtension(file.file.getName()), null, 0, true); + cell.setPhoto(file.file.getPath()); + cell.setChecked(cacheModel.isSelected(file), animated); + } + } + + private class CacheCell extends FrameLayout { + + CheckBox2 checkBox; + FrameLayout container; + TextView sizeTextView; + boolean drawDivider; + int type; + + public CacheCell(@NonNull Context context) { + super(context); + checkBox = new CheckBox2(context, 21); + checkBox.setDrawBackgroundAsArc(14); + checkBox.setColor(Theme.key_radioBackground, Theme.key_radioBackground, Theme.key_checkboxCheck); + addView(checkBox, LayoutHelper.createFrame(24, 24, Gravity.LEFT | Gravity.CENTER_VERTICAL, 18, 0, 0, 0)); + View checkBoxClickableView = new View(getContext()); + checkBoxClickableView.setOnClickListener(v -> { + onCheckBoxPressed(); + }); + addView(checkBoxClickableView, LayoutHelper.createFrame(40, 40, Gravity.LEFT | Gravity.CENTER_VERTICAL, 0, 0, 0, 0)); + container = new FrameLayout(context); + + addView(container, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 48, 0, 90, 0)); + sizeTextView = new TextView(context); + sizeTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + sizeTextView.setGravity(Gravity.RIGHT); + sizeTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText)); + + addView(sizeTextView, LayoutHelper.createFrame(69, LayoutHelper.WRAP_CONTENT, Gravity.RIGHT | Gravity.CENTER_VERTICAL, 0, 0, 21, 0)); + + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + if (drawDivider) { + canvas.drawLine(getMeasuredWidth() - AndroidUtilities.dp(90), getMeasuredHeight() - 1, getMeasuredWidth(), getMeasuredHeight() - 1, Theme.dividerPaint); + } + } + + public void onCheckBoxPressed() { + + } + } + + public static boolean fileIsMedia(File file) { + String name = file.getName().toLowerCase(); + return file.getName().endsWith("mp4") || file.getName().endsWith(".jpg") || name.endsWith(".jpeg") || name.endsWith(".png") || name.endsWith(".gif"); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CameraScanActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CameraScanActivity.java index 0b1566f39..5b85fb68c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CameraScanActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CameraScanActivity.java @@ -35,7 +35,6 @@ import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; import android.text.style.ClickableSpan; -import android.util.Log; import android.util.SparseArray; import android.util.TypedValue; import android.view.Gravity; @@ -142,6 +141,7 @@ public class CameraScanActivity extends BaseFragment { public static final int TYPE_MRZ = 0; public static final int TYPE_QR = 1; public static final int TYPE_QR_LOGIN = 2; + public static final int TYPE_QR_WEB_BOT = 3; public interface CameraScanActivityDelegate { default void didFindMrzInfo(MrzRecognizer.Result result) { @@ -155,20 +155,31 @@ public class CameraScanActivity extends BaseFragment { default boolean processQr(String text, Runnable onLoadEnd) { return false; } - } - public static INavigationLayout[] showAsSheet(BaseFragment parentFragment, boolean gallery, int type, CameraScanActivityDelegate cameraDelegate) { - if (parentFragment == null || parentFragment.getParentActivity() == null) { + default String getSubtitleText() { return null; } - INavigationLayout[] actionBarLayout = new INavigationLayout[]{INavigationLayout.newLayout(parentFragment.getParentActivity())}; - BottomSheet bottomSheet = new BottomSheet(parentFragment.getParentActivity(), false) { + + default void onDismiss() {} + } + + public static BottomSheet showAsSheet(BaseFragment parentFragment, boolean gallery, int type, CameraScanActivityDelegate cameraDelegate) { + return showAsSheet(parentFragment.getParentActivity(), gallery, type, cameraDelegate); + } + + public static BottomSheet showAsSheet(Activity parentActivity, boolean gallery, int type, CameraScanActivityDelegate cameraDelegate) { + if (parentActivity == null) { + return null; + } + INavigationLayout[] actionBarLayout = new INavigationLayout[]{INavigationLayout.newLayout(parentActivity)}; + BottomSheet bottomSheet = new BottomSheet(parentActivity, false) { CameraScanActivity fragment; { actionBarLayout[0].setFragmentStack(new ArrayList<>()); fragment = new CameraScanActivity(type) { @Override public void finishFragment() { + setFinishing(true); dismiss(); } @@ -183,6 +194,9 @@ public class CameraScanActivity extends BaseFragment { actionBarLayout[0].showLastFragment(); actionBarLayout[0].getView().setPadding(backgroundPaddingLeft, 0, backgroundPaddingLeft, 0); fragment.setDelegate(cameraDelegate); + if (cameraDelegate.getSubtitleText() != null) { + fragment.descriptionText.setText(cameraDelegate.getSubtitleText()); + } containerView = actionBarLayout[0].getView(); setApplyBottomPadding(false); setApplyBottomPadding(false); @@ -207,6 +221,7 @@ public class CameraScanActivity extends BaseFragment { public void dismiss() { super.dismiss(); actionBarLayout[0] = null; + cameraDelegate.onDismiss(); } }; bottomSheet.setUseLightStatusBar(false); @@ -215,7 +230,7 @@ public class CameraScanActivity extends BaseFragment { bottomSheet.setUseLightStatusBar(false); bottomSheet.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); bottomSheet.show(); - return actionBarLayout; + return bottomSheet; } public CameraScanActivity(int type) { @@ -304,7 +319,11 @@ public class CameraScanActivity extends BaseFragment { flashButton.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(60), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(60), MeasureSpec.EXACTLY)); } titleTextView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(72), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); - descriptionText.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.9f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); + if (currentType == TYPE_QR_WEB_BOT) { + descriptionText.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(72), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); + } else { + descriptionText.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.9f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); + } setMeasuredDimension(width, height); } @@ -335,6 +354,10 @@ public class CameraScanActivity extends BaseFragment { y = (height - size) / 2 - titleTextView.getMeasuredHeight() - AndroidUtilities.dp(64); } titleTextView.layout(AndroidUtilities.dp(36), y, AndroidUtilities.dp(36) + titleTextView.getMeasuredWidth(), y + titleTextView.getMeasuredHeight()); + if (currentType == TYPE_QR_WEB_BOT) { + y += titleTextView.getMeasuredHeight() + AndroidUtilities.dp(8); + descriptionText.layout(AndroidUtilities.dp(36), y, AndroidUtilities.dp(36) + descriptionText.getMeasuredWidth(), y + descriptionText.getMeasuredHeight()); + } recognizedMrzView.layout(0, getMeasuredHeight() - recognizedMrzView.getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight()); int x; @@ -352,9 +375,11 @@ public class CameraScanActivity extends BaseFragment { } } - y = (int) (height * 0.74f); - int x = (int) (width * 0.05f); - descriptionText.layout(x, y, x + descriptionText.getMeasuredWidth(), y + descriptionText.getMeasuredHeight()); + if (currentType != TYPE_QR_WEB_BOT) { + y = (int) (height * 0.74f); + int x = (int) (width * 0.05f); + descriptionText.layout(x, y, x + descriptionText.getMeasuredWidth(), y + descriptionText.getMeasuredHeight()); + } updateNormalBounds(); } @@ -433,7 +458,7 @@ public class CameraScanActivity extends BaseFragment { viewGroup.setOnTouchListener((v, event) -> true); fragmentView = viewGroup; - if (currentType == TYPE_QR || currentType == TYPE_QR_LOGIN) { + if (isQr()) { fragmentView.postDelayed(this::initCameraView, 450); } else { initCameraView(); @@ -452,7 +477,7 @@ public class CameraScanActivity extends BaseFragment { viewGroup.addView(actionBar); } - if (currentType == TYPE_QR_LOGIN) { + if (currentType == TYPE_QR_LOGIN || currentType == TYPE_QR_WEB_BOT) { actionBar.setTitle(LocaleController.getString("AuthAnotherClientScan", R.string.AuthAnotherClientScan)); } @@ -564,7 +589,7 @@ public class CameraScanActivity extends BaseFragment { if (needGalleryButton) { //titleTextView.setText(LocaleController.getString("WalletScanCode", R.string.WalletScanCode)); } else { - if (currentType == TYPE_QR) { + if (currentType == TYPE_QR || currentType == TYPE_QR_WEB_BOT) { titleTextView.setText(LocaleController.getString("AuthAnotherClientScan", R.string.AuthAnotherClientScan)); } else { String text = LocaleController.getString("AuthAnotherClientInfo5", R.string.AuthAnotherClientInfo5); @@ -601,6 +626,9 @@ public class CameraScanActivity extends BaseFragment { } } titleTextView.setTextColor(0xffffffff); + if (currentType == TYPE_QR_WEB_BOT) { + descriptionText.setTextColor(0x99ffffff); + } recognizedMrzView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); recognizedMrzView.setPadding(AndroidUtilities.dp(10), 0, AndroidUtilities.dp(10), AndroidUtilities.dp(10)); if (needGalleryButton) { @@ -717,17 +745,27 @@ public class CameraScanActivity extends BaseFragment { private ValueAnimator recognizedAnimator; private float recognizedT = 0; + private float newRecognizedT = 0; private SpringAnimation useRecognizedBoundsAnimator; private float useRecognizedBounds = 0; private void updateRecognized() { - if (recognizedAnimator != null) { - recognizedAnimator.cancel(); + float wasNewRecognizedT = recognizedT; + newRecognizedT = recognized ? 1f : 0f; + if (wasNewRecognizedT != newRecognizedT) { + if (recognizedAnimator != null) { + recognizedAnimator.cancel(); + } + } else { + return; } - float newRecognizedT = recognized ? 1f : 0f; + recognizedAnimator = ValueAnimator.ofFloat(recognizedT, newRecognizedT); recognizedAnimator.addUpdateListener(a -> { recognizedT = (float) a.getAnimatedValue(); titleTextView.setAlpha(1f - recognizedT); + if (currentType == TYPE_QR_WEB_BOT) { + descriptionText.setAlpha(1f - recognizedT); + } flashButton.setAlpha(1f - recognizedT); backShadowAlpha = .5f + recognizedT * .25f; fragmentView.invalidate(); @@ -988,15 +1026,32 @@ public class CameraScanActivity extends BaseFragment { (recognizeIndex == 0 && res != null && res.bounds == null && !qrLoading) || // first recognition doesn't have bounds (SystemClock.elapsedRealtime() - recognizedStart > 1000 && !qrLoading) // got more than 1 second and nothing is loading ) && recognizedText != null) { - if (cameraView != null && cameraView.getCameraSession() != null) { + if (cameraView != null && cameraView.getCameraSession() != null && currentType != TYPE_QR_WEB_BOT) { CameraController.getInstance().stopPreview(cameraView.getCameraSession()); } + String text = recognizedText; AndroidUtilities.runOnUIThread(() -> { if (delegate != null) { - delegate.didFindQr(recognizedText); + delegate.didFindQr(text); + } + if (currentType != TYPE_QR_WEB_BOT) { + finishFragment(); } - finishFragment(); }); + if (currentType == TYPE_QR_WEB_BOT) { + AndroidUtilities.runOnUIThread(()->{ + if (isFinishing()) { + return; + } + + recognizedText = null; + recognized = false; + requestShot.run(); + if (!recognized) { + AndroidUtilities.runOnUIThread(this::updateRecognized, 500); + } + }); + } } else if (recognized) { long delay = Math.max(16, 1000 / sps - (long) averageProcessTime); handler.postDelayed(() -> { @@ -1206,14 +1261,10 @@ public class CameraScanActivity extends BaseFragment { return null; } if (needGalleryButton) { - if (!text.startsWith("ton://transfer/")) { - //onNoWalletFound(bitmap != null); - return null; - } Uri uri = Uri.parse(text); String path = uri.getPath().replace("/", ""); } else { - if (!text.startsWith("tg://login?token=")) { + if (!text.startsWith("tg://login?token=") && currentType != TYPE_QR_WEB_BOT) { onNoQrFound(); return null; } @@ -1239,7 +1290,7 @@ public class CameraScanActivity extends BaseFragment { private boolean isQr() { - return currentType == TYPE_QR || currentType == TYPE_QR_LOGIN; + return currentType == TYPE_QR || currentType == TYPE_QR_LOGIN || currentType == TYPE_QR_WEB_BOT; } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/AboutLinkCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/AboutLinkCell.java index c504048ce..131cecae5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/AboutLinkCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/AboutLinkCell.java @@ -19,6 +19,7 @@ import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.Build; import android.text.Layout; import android.text.Spannable; @@ -27,7 +28,6 @@ import android.text.StaticLayout; import android.text.TextUtils; import android.text.style.ClickableSpan; import android.text.style.URLSpan; -import android.text.util.Linkify; import android.util.TypedValue; import android.view.Gravity; import android.view.HapticFeedbackConstants; @@ -39,7 +39,6 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.TextView; -import androidx.annotation.Nullable; import androidx.core.view.GestureDetectorCompat; import androidx.recyclerview.widget.RecyclerView; @@ -58,6 +57,7 @@ import org.telegram.ui.Components.BulletinFactory; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.LinkPath; import org.telegram.ui.Components.LinkSpanDrawable; +import org.telegram.ui.Components.LoadingDrawable; import org.telegram.ui.Components.StaticLayoutEx; import org.telegram.ui.Components.URLSpanNoUnderline; @@ -77,9 +77,13 @@ public class AboutLinkCell extends FrameLayout { private Drawable showMoreBackgroundDrawable; private LinkSpanDrawable pressedLink; + private float pressedLinkYOffset; + private Layout pressedLinkLayout; private LinkSpanDrawable.LinkCollector links; private Point urlPathOffset = new Point(); private LinkPath urlPath = new LinkPath(true); + private Browser.Progress currentProgress; + private LoadingDrawable currentLoading; private BaseFragment parentFragment; private Theme.ResourcesProvider resourcesProvider; @@ -204,12 +208,13 @@ public class AboutLinkCell extends FrameLayout { LinkSpanDrawable link = hitLink(x, y); if (link != null) { result = true; + pressedLinkLayout = textLayout; links.addLink(pressedLink = link); AndroidUtilities.runOnUIThread(longPressedRunnable, ViewConfiguration.getLongPressTimeout()); } } else if (pressedLink != null) { try { - onLinkClick((ClickableSpan) pressedLink.getSpan()); + onLinkClick((ClickableSpan) pressedLink.getSpan(), textLayout, pressedLinkYOffset); } catch (Exception e) { FileLog.e(e); } @@ -318,7 +323,7 @@ public class AboutLinkCell extends FrameLayout { canvas.restore(); } - protected void didPressUrl(String url) { + protected void didPressUrl(String url, Browser.Progress progress) { } protected void didResizeStart() { @@ -387,12 +392,15 @@ public class AboutLinkCell extends FrameLayout { performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } catch (Exception ignore) {} + final Layout layout = pressedLinkLayout; + final float yOffset = pressedLinkYOffset; + ClickableSpan pressedLinkFinal = (ClickableSpan) pressedLink.getSpan(); BottomSheet.Builder builder = new BottomSheet.Builder(parentFragment.getParentActivity()); builder.setTitle(url); builder.setItems(new CharSequence[]{LocaleController.getString("Open", R.string.Open), LocaleController.getString("Copy", R.string.Copy)}, (dialog, which) -> { if (which == 0) { - onLinkClick(pressedLinkFinal); + onLinkClick(pressedLinkFinal, layout, yOffset); } else if (which == 1) { AndroidUtilities.addToClipboard(url); if (AndroidUtilities.shouldShowClipboardToast()) { @@ -457,7 +465,7 @@ public class AboutLinkCell extends FrameLayout { int start = buffer.getSpanStart(link[0]); int end = buffer.getSpanEnd(link[0]); LinkPath path = linkDrawable.obtainNewPath(); - path.setCurrentLayout(textLayout, start, textY); + path.setCurrentLayout(textLayout, start, pressedLinkYOffset = textY); textLayout.getSelectionPath(start, end, path); return linkDrawable; } @@ -468,19 +476,49 @@ public class AboutLinkCell extends FrameLayout { return null; } - private void onLinkClick(ClickableSpan pressedLink) { + private void onLinkClick(ClickableSpan pressedLink, Layout layout, float yOffset) { + if (currentProgress != null) { + currentProgress.cancel(); + currentProgress = null; + } + currentProgress = layout != null && pressedLink != null ? new Browser.Progress() { + LoadingDrawable thisLoading; + + @Override + public void init() { + if (currentLoading != null) { + links.removeLoading(currentLoading, true); + } + currentLoading = thisLoading = LinkSpanDrawable.LinkCollector.makeLoading(layout, pressedLink, yOffset); + thisLoading.setColors( + Theme.multAlpha(Theme.getColor(Theme.key_chat_linkSelectBackground, resourcesProvider), .8f), + Theme.multAlpha(Theme.getColor(Theme.key_chat_linkSelectBackground, resourcesProvider), 1.3f), + Theme.multAlpha(Theme.getColor(Theme.key_chat_linkSelectBackground, resourcesProvider), 1f), + Theme.multAlpha(Theme.getColor(Theme.key_chat_linkSelectBackground, resourcesProvider), 4f) + ); + thisLoading.strokePaint.setStrokeWidth(AndroidUtilities.dpf2(1.25f)); + links.addLoading(thisLoading); + } + + @Override + public void end(boolean replacing) { + if (thisLoading != null) { + links.removeLoading(thisLoading, true); + } + } + } : null; if (pressedLink instanceof URLSpanNoUnderline) { String url = ((URLSpanNoUnderline) pressedLink).getURL(); if (url.startsWith("@") || url.startsWith("#") || url.startsWith("/")) { - didPressUrl(url); + didPressUrl(url, currentProgress); } } else { if (pressedLink instanceof URLSpan) { String url = ((URLSpan) pressedLink).getURL(); if (AndroidUtilities.shouldShowUrlInAlert(url)) { - AlertsCreator.showOpenUrlAlert(parentFragment, url, true, true); + AlertsCreator.showOpenUrlAlert(parentFragment, url, true, true, true, currentProgress, null); } else { - Browser.openUrl(getContext(), url); + Browser.openUrl(getContext(), Uri.parse(url), true, true, currentProgress); } } else { pressedLink.onClick(this); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java index 62ff1b31c..df9360881 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java @@ -53,6 +53,7 @@ import org.telegram.messenger.R; import org.telegram.messenger.SharedConfig; import org.telegram.messenger.SvgHelper; import org.telegram.messenger.UserConfig; +import org.telegram.messenger.Utilities; import org.telegram.messenger.browser.Browser; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; @@ -63,8 +64,12 @@ import org.telegram.ui.Components.AnimatedEmojiDrawable; import org.telegram.ui.Components.AnimatedEmojiSpan; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.Forum.ForumUtilities; +import org.telegram.ui.Components.ImageUpdater; +import org.telegram.ui.Components.MediaActionDrawable; import org.telegram.ui.Components.Premium.StarParticlesView; import org.telegram.ui.Components.RLottieDrawable; +import org.telegram.ui.Components.RadialProgress2; +import org.telegram.ui.Components.RadialProgressView; import org.telegram.ui.Components.TypefaceSpan; import org.telegram.ui.Components.URLSpanNoUnderline; import org.telegram.ui.Components.spoilers.SpoilerEffect; @@ -81,7 +86,7 @@ import java.util.Stack; public class ChatActionCell extends BaseCell implements DownloadController.FileDownloadProgressListener, NotificationCenter.NotificationCenterDelegate { private final static boolean USE_PREMIUM_GIFT_LOCAL_STICKER = false; private final static boolean USE_PREMIUM_GIFT_MONTHS_AS_EMOJI_NUMBERS = false; - + private static Map monthsToEmoticon = new HashMap<>(); static { @@ -155,7 +160,7 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD return 0; } - default int getTopicId() { + default int getTopicId() { return 0; } @@ -184,6 +189,8 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD private int previousWidth; private boolean imagePressed; private boolean giftButtonPressed; + RadialProgressView progressView; + float progressToProgress; private RectF giftButtonRect = new RectF(); @@ -237,9 +244,10 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD private TLRPC.Document giftSticker; private TLRPC.VideoSize giftEffectAnimation; - + private RadialProgress2 radialProgress = new RadialProgress2(this); + private int giftPremiumAdditionalHeight; private boolean forceWasUnread; - + private RectF backroundRect; private ImageReceiver.ImageReceiverDelegate giftStickerDelegate = (imageReceiver1, set, thumb, memCache) -> { if (set) { RLottieDrawable drawable = imageReceiver.getLottieAnimation(); @@ -377,18 +385,61 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD } public void setMessageObject(MessageObject messageObject, boolean force) { - if (currentMessageObject == messageObject && (textLayout == null || TextUtils.equals(textLayout.getText(), messageObject.messageText)) && (hasReplyMessage || messageObject.replyMessageObject == null) && !force) { + if (currentMessageObject == messageObject && (textLayout == null || TextUtils.equals(textLayout.getText(), messageObject.messageText)) && (hasReplyMessage || messageObject.replyMessageObject == null) && !force && messageObject.type != MessageObject.TYPE_SUGGEST_PHOTO) { return; } if (BuildVars.DEBUG_PRIVATE_VERSION && Thread.currentThread() != ApplicationLoader.applicationHandler.getLooper().getThread()) { FileLog.e(new IllegalStateException("Wrong thread!!!")); } accessibilityText = null; + boolean messageIdChanged = currentMessageObject == null || currentMessageObject.stableId != messageObject.stableId; currentMessageObject = messageObject; hasReplyMessage = messageObject.replyMessageObject != null; DownloadController.getInstance(currentAccount).removeLoadingFileObserver(this); previousWidth = 0; - if (messageObject.type == MessageObject.TYPE_GIFT_PREMIUM) { + if (messageObject.type == MessageObject.TYPE_SUGGEST_PHOTO) { + imageReceiver.setRoundRadius((int) (stickerSize / 2f)); + imageReceiver.setAllowStartLottieAnimation(true); + imageReceiver.setDelegate(null); + TLRPC.TL_messageActionSuggestProfilePhoto action = (TLRPC.TL_messageActionSuggestProfilePhoto) messageObject.messageOwner.action; + + ImageLocation videoLocation; + if (action.photo.video_sizes != null && !action.photo.video_sizes.isEmpty()) { + videoLocation = ImageLocation.getForPhoto(action.photo.video_sizes.get(0), action.photo); + } else { + videoLocation = null; + } + TLRPC.Photo photo = messageObject.messageOwner.action.photo; + TLRPC.VideoSize videoSize = null; + TLRPC.PhotoSize strippedPhotoSize = null; + for (int a = 0, N = messageObject.photoThumbs.size(); a < N; a++) { + TLRPC.PhotoSize photoSize = messageObject.photoThumbs.get(a); + if (photoSize instanceof TLRPC.TL_photoStrippedSize) { + strippedPhotoSize = photoSize; + break; + } + } + TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 1000); + if (photoSize != null) { + if (!photo.video_sizes.isEmpty()) { + videoSize = photo.video_sizes.get(0); + } + if (videoSize != null) { + imageReceiver.setImage(videoLocation, ImageLoader.AUTOPLAY_FILTER, ImageLocation.getForPhoto(photoSize, photo), "150_150", ImageLocation.getForObject(strippedPhotoSize, messageObject.photoThumbsObject), "50_50_b", null, 0, null, messageObject, 0); + } else { + imageReceiver.setImage(ImageLocation.getForPhoto(photoSize, photo), "150_150", ImageLocation.getForObject(strippedPhotoSize, messageObject.photoThumbsObject), "50_50_b", null, 0, null, messageObject, 0); + } + } + + imageReceiver.setAllowStartLottieAnimation(false); + ImageUpdater imageUpdater = MessagesController.getInstance(currentAccount).photoSuggestion.get(messageObject.messageOwner.local_id); + if (imageUpdater == null || imageUpdater.getCurrentImageProgress() == 1f) { + radialProgress.setProgress(1f, !messageIdChanged); + radialProgress.setIcon(MediaActionDrawable.ICON_NONE, !messageIdChanged, !messageIdChanged); + } else { + radialProgress.setIcon(MediaActionDrawable.ICON_CANCEL, !messageIdChanged, !messageIdChanged); + } + } else if (messageObject.type == MessageObject.TYPE_GIFT_PREMIUM) { imageReceiver.setRoundRadius(0); if (USE_PREMIUM_GIFT_LOCAL_STICKER) { @@ -517,7 +568,7 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD imageReceiver.setDelegate(null); imageReceiver.setImageBitmap((Bitmap) null); } - rippleView.setVisibility(messageObject.type == MessageObject.TYPE_GIFT_PREMIUM ? VISIBLE : GONE); + rippleView.setVisibility(isButtonLayout(messageObject) ? VISIBLE : GONE); ForumUtilities.applyTopicToMessage(messageObject); requestLayout(); } @@ -571,6 +622,10 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD animatedEmojiStack = AnimatedEmojiSpan.update(AnimatedEmojiDrawable.CACHE_TYPE_MESSAGES, this, canDrawInParent && (delegate != null && !delegate.canDrawOutboundsContent()), animatedEmojiStack, textLayout); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.didUpdatePremiumGiftStickers); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.diceStickersDidLoad); + + if (currentMessageObject != null && currentMessageObject.type == MessageObject.TYPE_SUGGEST_PHOTO) { + setMessageObject(currentMessageObject, true); + } } private void setStarsPaused(boolean paused) { @@ -601,11 +656,15 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD boolean result = false; if (event.getAction() == MotionEvent.ACTION_DOWN) { if (delegate != null) { - if ((messageObject.type == MessageObject.TYPE_ACTION_PHOTO || messageObject.type == MessageObject.TYPE_GIFT_PREMIUM) && imageReceiver.isInsideImage(x, y)) { + if ((messageObject.type == MessageObject.TYPE_ACTION_PHOTO || isButtonLayout(messageObject)) && imageReceiver.isInsideImage(x, y)) { imagePressed = true; result = true; } - if (messageObject.type == MessageObject.TYPE_GIFT_PREMIUM && giftButtonRect.contains(x, y)) { + if (messageObject.type == MessageObject.TYPE_SUGGEST_PHOTO && backroundRect.contains(x, y)) { + imagePressed = true; + result = true; + } + if (isButtonLayout(messageObject) && giftButtonRect.contains(x, y)) { rippleView.setPressed(giftButtonPressed = true); result = true; } @@ -624,16 +683,27 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD if (messageObject.type == MessageObject.TYPE_GIFT_PREMIUM) { openPremiumGiftPreview(); } else if (delegate != null) { - delegate.didClickImage(this); - playSoundEffect(SoundEffectConstants.CLICK); + ImageUpdater imageUpdater = MessagesController.getInstance(currentAccount).photoSuggestion.get(messageObject.messageOwner.local_id); + if (imageUpdater != null) { + imageUpdater.cancel(); + } else { + delegate.didClickImage(this); + playSoundEffect(SoundEffectConstants.CLICK); + } } break; case MotionEvent.ACTION_CANCEL: imagePressed = false; break; case MotionEvent.ACTION_MOVE: - if (!imageReceiver.isInsideImage(x, y)) { - imagePressed = false; + if (messageObject.type == MessageObject.TYPE_SUGGEST_PHOTO) { + if (!backroundRect.contains(x, y)) { + imagePressed = false; + } + } else { + if (!imageReceiver.isInsideImage(x, y)) { + imagePressed = false; + } } break; } @@ -642,8 +712,15 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD case MotionEvent.ACTION_UP: rippleView.setPressed(giftButtonPressed = false); if (delegate != null) { - playSoundEffect(SoundEffectConstants.CLICK); - openPremiumGiftPreview(); + if (messageObject.type == MessageObject.TYPE_GIFT_PREMIUM) { + playSoundEffect(SoundEffectConstants.CLICK); + openPremiumGiftPreview(); + } else { + ImageUpdater imageUpdater = MessagesController.getInstance(currentAccount).photoSuggestion.get(messageObject.messageOwner.local_id); + if (imageUpdater == null) { + delegate.didClickImage(this); + } + } } break; case MotionEvent.ACTION_CANCEL: @@ -810,9 +887,14 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), textHeight + AndroidUtilities.dp(14)); return; } - if (messageObject != null && messageObject.type == MessageObject.TYPE_GIFT_PREMIUM) { + if (isButtonLayout(messageObject)) { giftRectSize = Math.min((int) (AndroidUtilities.isTablet() ? AndroidUtilities.getMinTabletSide() * 0.6f : AndroidUtilities.displaySize.x * 0.6f), AndroidUtilities.displaySize.y - ActionBar.getCurrentActionBarHeight() - AndroidUtilities.statusBarHeight - AndroidUtilities.dp(64)); stickerSize = giftRectSize - AndroidUtilities.dp(106); + if (messageObject.type == MessageObject.TYPE_SUGGEST_PHOTO) { + imageReceiver.setRoundRadius(stickerSize / 2); + } else { + imageReceiver.setRoundRadius(0); + } } int width = Math.max(AndroidUtilities.dp(30), MeasureSpec.getSize(widthMeasureSpec)); if (previousWidth != width) { @@ -824,15 +906,36 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD if (messageObject != null) { if (messageObject.type == MessageObject.TYPE_ACTION_PHOTO) { additionalHeight = AndroidUtilities.roundMessageSize + AndroidUtilities.dp(10); - } else if (messageObject.type == MessageObject.TYPE_GIFT_PREMIUM) { + } else if (isButtonLayout(messageObject)) { additionalHeight = giftRectSize + AndroidUtilities.dp(12); } } - setMeasuredDimension(width, textHeight + additionalHeight + AndroidUtilities.dp(14)); - if (messageObject != null && messageObject.type == MessageObject.TYPE_GIFT_PREMIUM) { - float y = textY + textHeight + giftRectSize * 0.075f + stickerSize + AndroidUtilities.dp(4) + giftPremiumTitleLayout.getHeight() + AndroidUtilities.dp(4) + giftPremiumSubtitleLayout.getHeight(); - y += (getMeasuredHeight() - y - giftPremiumButtonLayout.getHeight() - AndroidUtilities.dp(8)) / 2f; + if (isButtonLayout(messageObject)) { + int imageSize = stickerSize; + if (messageObject.type == MessageObject.TYPE_SUGGEST_PHOTO) { + imageSize = (int) (stickerSize * 0.7f); + } + float y = textY + textHeight + giftRectSize * 0.075f + imageSize + AndroidUtilities.dp(4) + AndroidUtilities.dp(4) + giftPremiumSubtitleLayout.getHeight(); + giftPremiumAdditionalHeight = 0; + if (giftPremiumTitleLayout != null) { + y += giftPremiumTitleLayout.getHeight(); + } else { + y -= AndroidUtilities.dp(12); + giftPremiumAdditionalHeight -= AndroidUtilities.dp(30); + } + if (messageObject.type == MessageObject.TYPE_SUGGEST_PHOTO) { + y += AndroidUtilities.dp(16); + } + + if (giftPremiumSubtitleLayout.getLineCount() > 2) { + giftPremiumAdditionalHeight += (giftPremiumSubtitleLayout.getLineBottom(0) - giftPremiumSubtitleLayout.getLineTop(0)) * giftPremiumSubtitleLayout.getLineCount() - 2; + } + additionalHeight += giftPremiumAdditionalHeight; + + int h = textHeight + additionalHeight + AndroidUtilities.dp(14); + + y += (h - y - giftPremiumButtonLayout.getHeight() - AndroidUtilities.dp(8)) / 2f; float rectX = (previousWidth - giftPremiumButtonWidth) / 2f; giftButtonRect.set(rectX - AndroidUtilities.dp(18), y - AndroidUtilities.dp(8), rectX + giftPremiumButtonWidth + AndroidUtilities.dp(18), y + giftPremiumButtonLayout.getHeight() + AndroidUtilities.dp(8)); @@ -843,7 +946,12 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD starsSize = sizeInternal; starParticlesDrawable.resetPositions(); } + + } + + setMeasuredDimension(width, textHeight + additionalHeight + AndroidUtilities.dp(14)); + } private void buildLayout() { @@ -876,14 +984,53 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD imageReceiver.setImageCoords((previousWidth - AndroidUtilities.roundMessageSize) / 2f, textHeight + AndroidUtilities.dp(19), AndroidUtilities.roundMessageSize, AndroidUtilities.roundMessageSize); } else if (messageObject.type == MessageObject.TYPE_GIFT_PREMIUM) { createGiftPremiumLayouts(LocaleController.getString(R.string.ActionGiftPremiumTitle), LocaleController.formatString(R.string.ActionGiftPremiumSubtitle, LocaleController.formatPluralString("Months", messageObject.messageOwner.action.months)), LocaleController.getString(R.string.ActionGiftPremiumView), giftRectSize); + } else if (messageObject.type == MessageObject.TYPE_SUGGEST_PHOTO) { + TLRPC.TL_messageActionSuggestProfilePhoto actionSuggestProfilePhoto = (TLRPC.TL_messageActionSuggestProfilePhoto) messageObject.messageOwner.action; + String description; + TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(messageObject.isOutOwner() ? 0 : messageObject.getDialogId()); + boolean isVideo = actionSuggestProfilePhoto.video || (actionSuggestProfilePhoto.photo != null && actionSuggestProfilePhoto.photo.video_sizes != null && !actionSuggestProfilePhoto.photo.video_sizes.isEmpty()); + if (user.id == UserConfig.getInstance(currentAccount).clientUserId) { + TLRPC.User user2 = MessagesController.getInstance(currentAccount).getUser(messageObject.getDialogId()); + if (isVideo) { + description = LocaleController.formatString("ActionSuggestVideoFromYouDescription", R.string.ActionSuggestVideoFromYouDescription, user2.first_name); + } else { + description = LocaleController.formatString("ActionSuggestPhotoFromYouDescription", R.string.ActionSuggestPhotoFromYouDescription, user2.first_name); + } + } else { + if (isVideo) { + description = LocaleController.formatString("ActionSuggestVideoToYouDescription", R.string.ActionSuggestVideoToYouDescription, user.first_name); + } else { + description = LocaleController.formatString("ActionSuggestPhotoToYouDescription", R.string.ActionSuggestPhotoToYouDescription, user.first_name); + } + } + String action; + if (actionSuggestProfilePhoto.video || (actionSuggestProfilePhoto.photo.video_sizes != null && !actionSuggestProfilePhoto.photo.video_sizes.isEmpty())) { + action = LocaleController.getString("ViewVideoAction", R.string.ViewVideoAction); + } else { + action = LocaleController.getString("ViewPhotoAction", R.string.ViewPhotoAction); + } + createGiftPremiumLayouts(null, description, action, giftRectSize); + textLayout = null; + textHeight = 0; + textY = 0; } } } private void createGiftPremiumLayouts(CharSequence title, CharSequence subtitle, CharSequence button, int width) { - SpannableStringBuilder titleBuilder = SpannableStringBuilder.valueOf(title); - titleBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_ROBOTO_MEDIUM)), 0, titleBuilder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - giftPremiumTitleLayout = new StaticLayout(titleBuilder, giftTitlePaint, width, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); + width -= AndroidUtilities.dp(16); + if (title != null) { + SpannableStringBuilder titleBuilder = SpannableStringBuilder.valueOf(title); + titleBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_ROBOTO_MEDIUM)), 0, titleBuilder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + giftPremiumTitleLayout = new StaticLayout(titleBuilder, giftTitlePaint, width, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); + } else { + giftPremiumTitleLayout = null; + } + if (currentMessageObject != null && currentMessageObject.type == MessageObject.TYPE_SUGGEST_PHOTO) { + giftSubtitlePaint.setTextSize(AndroidUtilities.dp(13)); + } else { + giftSubtitlePaint.setTextSize(AndroidUtilities.dp(15)); + } giftPremiumSubtitleLayout = new StaticLayout(subtitle, giftSubtitlePaint, width, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); SpannableStringBuilder buttonBuilder = SpannableStringBuilder.valueOf(button); buttonBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_ROBOTO_MEDIUM)), 0, buttonBuilder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); @@ -909,10 +1056,15 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD @Override protected void onDraw(Canvas canvas) { MessageObject messageObject = currentMessageObject; - if (messageObject != null && messageObject.type == MessageObject.TYPE_GIFT_PREMIUM) { + int imageSize = stickerSize; + if (isButtonLayout(messageObject)) { stickerSize = giftRectSize - AndroidUtilities.dp(106); - imageReceiver.setImageCoords((previousWidth - stickerSize) / 2f, textY + textHeight + giftRectSize * 0.075f, stickerSize, stickerSize); - + if (messageObject.type == MessageObject.TYPE_ACTION_PHOTO) { + imageReceiver.setImageCoords((previousWidth - stickerSize) / 2f, textY + textHeight + giftRectSize * 0.075f, stickerSize, stickerSize); + } else { + imageSize = (int) (stickerSize * 0.7f); + imageReceiver.setImageCoords((previousWidth - imageSize) / 2f, textY + textHeight + giftRectSize * 0.075f + AndroidUtilities.dp(8), imageSize, imageSize); + } if (textPaint != null && giftTitlePaint != null && giftSubtitlePaint != null) { if (giftTitlePaint.getColor() != textPaint.getColor()) { giftTitlePaint.setColor(textPaint.getColor()); @@ -922,17 +1074,35 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD } } } - if (messageObject != null && (messageObject.type == MessageObject.TYPE_ACTION_PHOTO || messageObject.type == MessageObject.TYPE_GIFT_PREMIUM)) { - imageReceiver.draw(canvas); - } - - if (textLayout == null) { - return; - } drawBackground(canvas, false); - if (textPaint != null) { + if (isButtonLayout(messageObject) || (messageObject != null && messageObject.type == MessageObject.TYPE_ACTION_PHOTO)) { + imageReceiver.draw(canvas); + radialProgress.setProgressRect( + imageReceiver.getImageX(), + imageReceiver.getImageY(), + imageReceiver.getImageX() + imageReceiver.getImageWidth(), + imageReceiver.getImageY() + imageReceiver.getImageHeight() + ); + if (messageObject.type == MessageObject.TYPE_SUGGEST_PHOTO) { + ImageUpdater imageUpdater = MessagesController.getInstance(currentAccount).photoSuggestion.get(messageObject.messageOwner.local_id); + if (imageUpdater != null) { + radialProgress.setProgress(imageUpdater.getCurrentImageProgress(), true); + radialProgress.setCircleRadius((int) (imageReceiver.getImageWidth() * 0.5f) + 1); + radialProgress.setMaxIconSize(AndroidUtilities.dp(24)); + radialProgress.setColors(Theme.key_chat_mediaLoaderPhoto, Theme.key_chat_mediaLoaderPhotoSelected, Theme.key_chat_mediaLoaderPhotoIcon, Theme.key_chat_mediaLoaderPhotoIconSelected); + if (imageUpdater.getCurrentImageProgress() == 1f) { + radialProgress.setIcon(MediaActionDrawable.ICON_NONE, true, true); + } else { + radialProgress.setIcon(MediaActionDrawable.ICON_CANCEL, true, true); + } + } + radialProgress.draw(canvas); + } + } + + if (textPaint != null && textLayout != null) { canvas.save(); canvas.translate(textXLeft, textY); if (textLayout.getPaint() != textPaint) { @@ -954,23 +1124,39 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD canvas.restore(); } - if (messageObject != null && messageObject.type == MessageObject.TYPE_GIFT_PREMIUM) { + if (isButtonLayout(messageObject)) { canvas.save(); - float x = (previousWidth - giftRectSize) / 2f, y = textY + textHeight + giftRectSize * 0.075f + stickerSize + AndroidUtilities.dp(4); + float x = (previousWidth - giftRectSize) / 2f + AndroidUtilities.dp(8), y = textY + textHeight + giftRectSize * 0.075f + imageSize + AndroidUtilities.dp(4); + if (messageObject.type == MessageObject.TYPE_SUGGEST_PHOTO) { + y += +AndroidUtilities.dp(16); + } canvas.translate(x, y); - giftPremiumTitleLayout.draw(canvas); + if (giftPremiumTitleLayout != null) { + giftPremiumTitleLayout.draw(canvas); + y += giftPremiumTitleLayout.getHeight(); + } else { + y -= AndroidUtilities.dp(4); + } canvas.restore(); - y += giftPremiumTitleLayout.getHeight(); y += AndroidUtilities.dp(4); canvas.save(); canvas.translate(x, y); giftPremiumSubtitleLayout.draw(canvas); canvas.restore(); + if (giftPremiumTitleLayout == null) { + y -= AndroidUtilities.dp(8); + } + y += giftPremiumSubtitleLayout.getHeight(); y += (getHeight() - y - giftPremiumButtonLayout.getHeight() - AndroidUtilities.dp(8)) / 2f; + if (themeDelegate != null) { + themeDelegate.applyServiceShaderMatrix(getMeasuredWidth(), backgroundHeight, 0, viewTop + AndroidUtilities.dp(4)); + } else { + Theme.applyServiceShaderMatrix(getMeasuredWidth(), backgroundHeight, 0, viewTop + AndroidUtilities.dp(4)); + } Paint backgroundPaint = getThemedPaint(Theme.key_paint_chatActionBackground); canvas.drawRoundRect(giftButtonRect, AndroidUtilities.dp(16), AndroidUtilities.dp(16), backgroundPaint); @@ -982,16 +1168,43 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD starsPath.addRoundRect(giftButtonRect, AndroidUtilities.dp(16), AndroidUtilities.dp(16), Path.Direction.CW); canvas.save(); canvas.clipPath(starsPath); - starParticlesDrawable.onDraw(canvas); - if (!starParticlesDrawable.paused) { + if (getMessageObject().type != MessageObject.TYPE_SUGGEST_PHOTO) { + starParticlesDrawable.onDraw(canvas); + if (!starParticlesDrawable.paused) { + invalidate(); + } + } else { + //TODO optimize invalidate(); } canvas.restore(); - canvas.save(); - canvas.translate(x, y); - giftPremiumButtonLayout.draw(canvas); - canvas.restore(); + if (messageObject.settingAvatar && progressToProgress != 1f) { + progressToProgress += 16 / 150f; + } else if (!messageObject.settingAvatar && progressToProgress != 0) { + progressToProgress -= 16 / 150f; + } + progressToProgress = Utilities.clamp(progressToProgress, 1f, 0f); + if (progressToProgress != 0) { + if (progressView == null) { + progressView = new RadialProgressView(getContext()); + } + int rad = AndroidUtilities.dp(16); + canvas.save(); + canvas.scale(progressToProgress, progressToProgress, giftButtonRect.centerX(), giftButtonRect.centerY()); + progressView.setSize(rad); + progressView.setProgressColor(Theme.getColor(Theme.key_chat_serviceText)); + progressView.draw(canvas, giftButtonRect.centerX(), giftButtonRect.centerY()); + canvas.restore(); + } + if (progressToProgress != 1f){ + canvas.save(); + float s = 1f - progressToProgress; + canvas.scale(s, s, giftButtonRect.centerX(), giftButtonRect.centerY()); + canvas.translate(x, y); + giftPremiumButtonLayout.draw(canvas); + canvas.restore(); + } } } @@ -1022,7 +1235,7 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD if (invalidatePath) { invalidatePath = false; lineWidths.clear(); - final int count = textLayout.getLineCount(); + final int count = textLayout == null ? 0 : textLayout.getLineCount(); final int corner = AndroidUtilities.dp(11); final int cornerIn = AndroidUtilities.dp(8); @@ -1171,13 +1384,17 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD } MessageObject messageObject = currentMessageObject; - if (messageObject != null && messageObject.type == MessageObject.TYPE_GIFT_PREMIUM) { + if (isButtonLayout(messageObject)) { float x = (getWidth() - giftRectSize) / 2f, y = textY + textHeight + AndroidUtilities.dp(12); - AndroidUtilities.rectTmp.set(x, y, x + giftRectSize, y + giftRectSize); - canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(16), AndroidUtilities.dp(16), backgroundPaint); + AndroidUtilities.rectTmp.set(x, y, x + giftRectSize, y + giftRectSize + giftPremiumAdditionalHeight); + if (backroundRect == null) { + backroundRect = new RectF(); + } + backroundRect.set(AndroidUtilities.rectTmp); + canvas.drawRoundRect(backroundRect, AndroidUtilities.dp(16), AndroidUtilities.dp(16), backgroundPaint); if (hasGradientService()) { - canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(16), AndroidUtilities.dp(16), Theme.chat_actionBackgroundGradientDarkenPaint); + canvas.drawRoundRect(backroundRect, AndroidUtilities.dp(16), AndroidUtilities.dp(16), Theme.chat_actionBackgroundGradientDarkenPaint); } } @@ -1290,4 +1507,8 @@ public class ChatActionCell extends BaseCell implements DownloadController.FileD AnimatedEmojiSpan.drawAnimatedEmojis(canvas, textLayout, animatedEmojiStack, 0, spoilers, 0, 0, 0, 1f); canvas.restore(); } + + private boolean isButtonLayout(MessageObject messageObject) { + return messageObject != null && (messageObject.type == MessageObject.TYPE_GIFT_PREMIUM || messageObject.type == MessageObject.TYPE_SUGGEST_PHOTO); + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java index 3ac18c180..eaa09d07e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java @@ -69,11 +69,13 @@ import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeProvider; import android.view.animation.Interpolator; +import android.view.animation.OvershootInterpolator; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import androidx.core.graphics.ColorUtils; +import androidx.core.math.MathUtils; import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.AccountInstance; @@ -129,6 +131,7 @@ import org.telegram.ui.Components.Forum.MessageTopicButton; import org.telegram.ui.Components.InfiniteProgress; import org.telegram.ui.Components.LinkPath; import org.telegram.ui.Components.LinkSpanDrawable; +import org.telegram.ui.Components.LoadingDrawable; import org.telegram.ui.Components.MediaActionDrawable; import org.telegram.ui.Components.MessageBackgroundDrawable; import org.telegram.ui.Components.MotionBackgroundDrawable; @@ -270,13 +273,19 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } public void setSpoilersSuppressed(boolean s) { - for (SpoilerEffect eff : captionSpoilers) + for (int i = 0; i < captionSpoilers.size(); i++) { + SpoilerEffect eff = captionSpoilers.get(i); eff.setSuppressUpdates(s); - for (SpoilerEffect eff : replySpoilers) + } + for (int i = 0; i < replySpoilers.size(); i++) { + SpoilerEffect eff = replySpoilers.get(i); eff.setSuppressUpdates(s); + } if (getMessageObject() != null && getMessageObject().textLayoutBlocks != null) { - for (MessageObject.TextLayoutBlock bl : getMessageObject().textLayoutBlocks) { - for (SpoilerEffect eff : bl.spoilers) { + for (int k = 0; k < getMessageObject().textLayoutBlocks.size(); k++) { + MessageObject.TextLayoutBlock bl = getMessageObject().textLayoutBlocks.get(k); + for (int i = 0; i < bl.spoilers.size(); i++) { + SpoilerEffect eff = bl.spoilers.get(i); eff.setSuppressUpdates(s); } } @@ -403,6 +412,18 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate default void didPressReplyMessage(ChatMessageCell cell, int id) { } + default boolean isProgressLoading(ChatMessageCell cell, int type) { + return false; + } + + default String getProgressLoadingBotButtonUrl(ChatMessageCell cell) { + return null; + } + + default CharacterStyle getProgressLoadingLink(ChatMessageCell cell) { + return null; + } + default void didPressUrl(ChatMessageCell cell, CharacterStyle url, boolean longPress) { } @@ -542,7 +563,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private final static int DOCUMENT_ATTACH_TYPE_WALLPAPER = 8; private final static int DOCUMENT_ATTACH_TYPE_THEME = 9; - private static class BotButton { + private class BotButton { private int x; private int y; private int width; @@ -554,6 +575,51 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private float progressAlpha; private long lastUpdateTime; private boolean isInviteButton; + + private LoadingDrawable loadingDrawable; + private Drawable selectorDrawable; + + private boolean pressed; + private float pressT; + private ValueAnimator pressAnimator; + private void setPressed(boolean pressed) { + if (this.pressed != pressed) { + this.pressed = pressed; + invalidateOutbounds(); + if (pressed) { + if (pressAnimator != null) { + pressAnimator.removeAllListeners(); + pressAnimator.cancel(); + } + } + if (!pressed && pressT != 0) { + pressAnimator = ValueAnimator.ofFloat(pressT, 0); + pressAnimator.addUpdateListener(animation -> { + pressT = (float) animation.getAnimatedValue(); + invalidateOutbounds(); + }); + pressAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + pressAnimator = null; + } + }); + pressAnimator.setInterpolator(new OvershootInterpolator(2.0f)); + pressAnimator.setDuration(350); + pressAnimator.start(); + } + } + } + + private float getPressScale() { + if (pressed && pressT != 1f) { + pressT += (float) Math.min(40, 1000f / AndroidUtilities.screenRefreshRate) / 100f; + pressT = Utilities.clamp(pressT, 1f, 0); + invalidateOutbounds(); + } + return 0.96f + 0.04f * (1f - pressT); + } } public static class PollButton { @@ -617,6 +683,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private RadialProgress2 videoRadialProgress; private boolean drawRadialCheckBackground; private ImageReceiver photoImage; + private ImageReceiver blurredPhotoImage; private AvatarDrawable contactAvatarDrawable; private Drawable locationLoadingThumb; @@ -694,9 +761,12 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private boolean instantTextNewLine; private boolean instantPressed; private boolean instantButtonPressed; + private float instantButtonPressProgress; private Drawable[] selectorDrawable = new Drawable[2]; private int[] selectorDrawableMaskType = new int[2]; private RectF instantButtonRect = new RectF(); + private LoadingDrawable instantButtonLoading; + private ValueAnimator instantButtonPressAnimator; private int[] pressedState = new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}; private float animatingLoadingProgressProgress; CharSequence accessibilityText; @@ -775,6 +845,17 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private boolean gamePreviewPressed; private ArrayList urlPathCache = new ArrayList<>(); private ArrayList urlPathSelection = new ArrayList<>(); + private CharacterStyle highlightPathSpan; + private LinkPath highlightPath; + private long highlightPathStart; + + private LoadingDrawable progressLoadingLinkCurrentDrawable; + class LoadingDrawableLocation { + LoadingDrawable drawable; + int blockNum; + }; + private ArrayList progressLoadingLinkDrawables; + private CharacterStyle progressLoadingLink; private Path rectPath = new Path(); private static float[] radii = new float[8]; @@ -876,6 +957,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate public boolean isChat; public boolean isBot; public boolean isMegagroup; + public boolean isForum; + public boolean isForumGeneral; public boolean isThreadChat; public boolean hasDiscussion; public boolean isPinned; @@ -965,6 +1048,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate public boolean needReplyImage; private boolean replyPressed; private boolean replySelectorPressed, replySelectorCanBePressed; + private AnimatedFloat replyPressedFloat; private float replyTouchX, replyTouchY; private TLRPC.PhotoSize currentReplyPhoto; @@ -1006,6 +1090,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate private Paint drillHolePaint; private Path drillHolePath; + private Path mediaSpoilerPath = new Path(); + private float[] mediaSpoilerRadii = new float[8]; + private SpoilerEffect mediaSpoilerEffect = new SpoilerEffect(); + private float mediaSpoilerRevealProgress; + private float mediaSpoilerRevealX; + private float mediaSpoilerRevealY; + private float mediaSpoilerRevealMaxRadius; + private float unlockAlpha = 1f; private float unlockX; private float unlockY; @@ -1044,8 +1136,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate public AnimatedEmojiSpan.EmojiGroupedSpans animatedEmojiReplyStack; public AnimatedEmojiSpan.EmojiGroupedSpans animatedEmojiDescriptionStack; public Drawable replySelector; + public LoadingDrawable replyLoadingDrawable; + private float replyLoadingT; + private float[] replyLoadingSegment; + private float[] replyRoundRectRadii; + private Path replyRoundRectPath; public Rect replySelectorRect = new Rect(); - public int replySelectorColor, replySelectorRadLeft, replySelectorRadRight; + public int replySelectorColor; + public float replySelectorRadLeft, replySelectorRadRight; private ChatMessageCellDelegate delegate; @@ -1166,7 +1264,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate avatarDrawable = new AvatarDrawable(); replyImageReceiver = new ImageReceiver(this); replyImageReceiver.setAllowLoadingOnAttachedOnly(true); - replyImageReceiver.setRoundRadius(AndroidUtilities.dp(2)); + replyImageReceiver.setRoundRadius(AndroidUtilities.dp(4)); locationImageReceiver = new ImageReceiver(this); locationImageReceiver.setAllowLoadingOnAttachedOnly(true); locationImageReceiver.setRoundRadius(AndroidUtilities.dp(26.1f)); @@ -1177,11 +1275,19 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate photoImage.setAllowLoadingOnAttachedOnly(true); photoImage.setUseRoundForThumbDrawable(true); photoImage.setDelegate(this); + blurredPhotoImage = new ImageReceiver(this); + blurredPhotoImage.setAllowLoadingOnAttachedOnly(true); + blurredPhotoImage.setUseRoundForThumbDrawable(true); radialProgress = new RadialProgress2(this, resourcesProvider); videoRadialProgress = new RadialProgress2(this, resourcesProvider); videoRadialProgress.setDrawBackground(false); videoRadialProgress.setCircleRadius(AndroidUtilities.dp(15)); - seekBar = new SeekBar(this); + seekBar = new SeekBar(this) { + @Override + protected void onTimestampUpdate(URLSpanNoUnderline link) { + highlightCaptionLink(link); + } + }; seekBar.setDelegate(this); seekBarWaveform = new SeekBarWaveform(context); seekBarWaveform.setDelegate(this); @@ -1719,7 +1825,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (selectorDrawable[0].getBounds().contains(x, y)) { selectorDrawable[0].setHotspot(x, y); selectorDrawable[0].setState(pressedState); - instantButtonPressed = true; + setInstantButtonPressed(true); } } invalidate(); @@ -1743,7 +1849,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (Build.VERSION.SDK_INT >= 21 && selectorDrawable[0] != null) { selectorDrawable[0].setState(StateSet.NOTHING); } - instantPressed = instantButtonPressed = false; + setInstantButtonPressed(instantPressed = false); invalidate(); } else if (pressedLinkType == 2 || buttonPressed != 0 || miniButtonPressed != 0 || videoButtonPressed != 0 || linkPreviewPressed) { if (videoButtonPressed == 1) { @@ -1940,7 +2046,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (instantButtonRect.contains(x, y)) { selectorDrawable[0].setHotspot(x, y); selectorDrawable[0].setState(pressedState); - instantButtonPressed = true; + setInstantButtonPressed(true); } } invalidate(); @@ -1976,7 +2082,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (Build.VERSION.SDK_INT >= 21 && selectorDrawable[0] != null) { selectorDrawable[0].setState(StateSet.NOTHING); } - instantPressed = instantButtonPressed = false; + setInstantButtonPressed(instantPressed = false); invalidate(); } } else if (event.getAction() == MotionEvent.ACTION_MOVE) { @@ -2121,6 +2227,36 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate return result; } + private void setInstantButtonPressed(boolean pressed) { + if (instantButtonPressed != pressed) { + invalidate(); + if (pressed) { + if (instantButtonPressAnimator != null) { + instantButtonPressAnimator.removeAllListeners(); + instantButtonPressAnimator.cancel(); + } + } + if (!pressed && instantButtonPressProgress != 0) { + instantButtonPressAnimator = ValueAnimator.ofFloat(instantButtonPressProgress, 0); + instantButtonPressAnimator.addUpdateListener(animation -> { + instantButtonPressProgress = (float) animation.getAnimatedValue(); + invalidate(); + }); + instantButtonPressAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + instantButtonPressAnimator = null; + } + }); + instantButtonPressAnimator.setInterpolator(new OvershootInterpolator(5.0f)); + instantButtonPressAnimator.setDuration(350); + instantButtonPressAnimator.start(); + } + instantButtonPressed = pressed; + } + } + private boolean checkDateMotionEvent(MotionEvent event) { if (!currentMessageObject.isImportedForward()) { return false; @@ -2235,15 +2371,16 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate int offset = AndroidUtilities.dp(27); area2 = x >= buttonX + offset && x <= buttonX + offset + side && y >= buttonY + offset && y <= buttonY + offset + side; } + boolean noSpoilersOrRevealed = currentMessageObject == null || !currentMessageObject.hasMediaSpoilers() || currentMessageObject.isMediaSpoilersRevealed; if (area2) { miniButtonPressed = 1; invalidate(); result = true; - } else if (buttonState != -1 && radialProgress.getIcon() != MediaActionDrawable.ICON_NONE && x >= buttonX && x <= buttonX + side && y >= buttonY && y <= buttonY + side) { + } else if (buttonState != -1 && radialProgress.getIcon() != MediaActionDrawable.ICON_NONE && x >= buttonX && x <= buttonX + side && y >= buttonY && y <= buttonY + side && noSpoilersOrRevealed) { buttonPressed = 1; invalidate(); result = true; - } else if (drawVideoImageButton && buttonState != -1 && x >= videoButtonX && x <= videoButtonX + AndroidUtilities.dp(26 + 8) + Math.max(infoWidth, docTitleWidth) && y >= videoButtonY && y <= videoButtonY + AndroidUtilities.dp(30)) { + } else if (drawVideoImageButton && buttonState != -1 && x >= videoButtonX && x <= videoButtonX + AndroidUtilities.dp(26 + 8) + Math.max(infoWidth, docTitleWidth) && y >= videoButtonY && y <= videoButtonY + AndroidUtilities.dp(30) && noSpoilersOrRevealed) { videoButtonPressed = 1; invalidate(); result = true; @@ -2360,7 +2497,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate int offset = AndroidUtilities.dp(27); area2 = x >= buttonX + offset && x <= buttonX + offset + side && y >= buttonY + offset && y <= buttonY + offset + side; } - if (!area2) { + if (!area2 && (currentMessageObject == null || !currentMessageObject.hasMediaSpoilers() || currentMessageObject.isMediaSpoilersRevealed)) { if (buttonState == 0 || buttonState == 1 || buttonState == 2) { area = x >= buttonX - AndroidUtilities.dp(12) && x <= buttonX - AndroidUtilities.dp(12) + backgroundWidth && y >= (drawInstantView ? buttonY : namesOffset + mediaOffsetY) && y <= (drawInstantView ? buttonY + side : namesOffset + mediaOffsetY + AndroidUtilities.dp(82)); } else { @@ -2569,40 +2706,67 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate int y2 = button.y + layoutHeight - AndroidUtilities.dp(2); if (x >= button.x + addX && x <= button.x + addX + button.width && y >= y2 && y <= y2 + button.height) { pressedBotButton = a; - invalidate(); + invalidateOutbounds(); result = true; + if (button.selectorDrawable == null) { + button.selectorDrawable = Theme.createRadSelectorDrawable(getThemedColor(Theme.key_chat_serviceBackgroundSelector), 6, 6); + button.selectorDrawable.setBounds(button.x + addX, y2, button.x + addX + button.width, y2 + button.height); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + button.selectorDrawable.setHotspot(x, y); + } + button.selectorDrawable.setState(pressedState); + button.setPressed(true); final int longPressedBotButton = pressedBotButton; postDelayed(() -> { if (longPressedBotButton == pressedBotButton) { - if (!currentMessageObject.scheduled) { - BotButton button2 = botButtons.get(pressedBotButton); - if (button2.button != null) { - cancelCheckLongPress(); - delegate.didLongPressBotButton(this, button2.button); + BotButton button2 = botButtons.get(pressedBotButton); + if (button2 != null) { + if (button2.selectorDrawable != null) { + button2.selectorDrawable.setState(StateSet.NOTHING); + } + button2.setPressed(false); + if (!currentMessageObject.scheduled) { + if (button2.button != null) { + cancelCheckLongPress(); + delegate.didLongPressBotButton(this, button2.button); + } } } pressedBotButton = -1; - invalidate(); + invalidateOutbounds(); } }, ViewConfiguration.getLongPressTimeout() - 1); break; } } - } else { - if (event.getAction() == MotionEvent.ACTION_UP) { - if (pressedBotButton != -1) { - playSoundEffect(SoundEffectConstants.CLICK); - if (currentMessageObject.scheduled) { - Toast.makeText(getContext(), LocaleController.getString("MessageScheduledBotAction", R.string.MessageScheduledBotAction), Toast.LENGTH_LONG).show(); - } else { - BotButton button = botButtons.get(pressedBotButton); - if (button.button != null) { - delegate.didPressBotButton(this, button.button); - } - } - pressedBotButton = -1; - invalidate(); + } else if (event.getAction() == MotionEvent.ACTION_UP) { + if (pressedBotButton != -1) { + playSoundEffect(SoundEffectConstants.CLICK); + BotButton button = botButtons.get(pressedBotButton); + if (button.selectorDrawable != null) { + button.selectorDrawable.setState(StateSet.NOTHING); } + button.setPressed(false); + if (currentMessageObject.scheduled) { + Toast.makeText(getContext(), LocaleController.getString("MessageScheduledBotAction", R.string.MessageScheduledBotAction), Toast.LENGTH_LONG).show(); + } else { + if (button.button != null) { + delegate.didPressBotButton(this, button.button); + } + } + pressedBotButton = -1; + invalidateOutbounds(); + } + } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { + if (pressedBotButton != -1) { + BotButton button = botButtons.get(pressedBotButton); + if (button.selectorDrawable != null) { + button.selectorDrawable.setState(StateSet.NOTHING); + } + button.setPressed(false); + pressedBotButton = -1; + invalidateOutbounds(); } } return result; @@ -2707,7 +2871,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate imagePressed = false; timePressed = false; gamePreviewPressed = false; - instantPressed = instantButtonPressed = commentButtonPressed = false; + instantPressed = commentButtonPressed = false; + setInstantButtonPressed(false); if (Build.VERSION.SDK_INT >= 21) { for (int a = 0; a < selectorDrawable.length; a++) { if (selectorDrawable[a] != null) { @@ -2793,7 +2958,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate replySelectorPressed = true; replySelector.setState(new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled}); } - }, ViewConfiguration.getTapTimeout() / 4); + }, ViewConfiguration.getTapTimeout() / 6); + invalidate(); } result = true; } @@ -2911,6 +3077,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } replySelectorPressed = false; replySelectorCanBePressed = false; + invalidate(); } playSoundEffect(SoundEffectConstants.CLICK); if (replyPanelIsForward) { @@ -2930,9 +3097,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { replyPressed = false; + replySelectorPressed = false; if (replySelector != null) { replySelector.setState(new int[]{}); } + invalidate(); } else if (event.getAction() == MotionEvent.ACTION_MOVE) { int replyEnd; if (currentMessageObject.shouldDrawWithoutBackground()) { @@ -2947,6 +3116,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (replySelector != null) { replySelector.setState(new int[]{}); } + invalidate(); } else if (replySelector != null && replySelectorCanBePressed && Math.sqrt(Math.pow(x - replyTouchX, 2) + Math.pow((y + getY()) - replyTouchY, 2)) > 0.75f) { replySelectorCanBePressed = false; } @@ -3120,6 +3290,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate currentMessageObject.audioProgress = videoPlayerRewinder.getVideoProgress(); } if (documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO) { + if (seekBar != null) { + seekBar.clearTimestamps(); + } if (infoLayout != null && (PhotoViewer.isPlayingMessage(currentMessageObject) || MediaController.getInstance().isGoingToShowMessageObject(currentMessageObject))) { return; } @@ -3160,6 +3333,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate seekBar.setProgress(currentMessageObject.audioProgress); seekBar.setBufferedProgress(currentMessageObject.bufferedProgress); } + seekBar.clearTimestamps(); } int duration = 0; @@ -3197,6 +3371,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate seekBar.setProgress(currentMessageObject.audioProgress); seekBar.setBufferedProgress(currentMessageObject.bufferedProgress); } + seekBar.updateTimestamps(currentMessageObject, null); } int duration = 0; @@ -3351,6 +3526,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } private void didClickedImage() { + if (currentMessageObject.hasMediaSpoilers() && !currentMessageObject.isMediaSpoilersRevealed) { + startRevealMedia(lastTouchX, lastTouchY); + return; + } if (currentMessageObject.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW) { if (currentMessageObject.messageOwner != null && currentMessageObject.messageOwner.media != null && currentMessageObject.messageOwner.media.extended_media != null && currentMessageObject.messageOwner.reply_markup != null) { @@ -3814,6 +3993,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } replyImageReceiver.onAttachedToWindow(); locationImageReceiver.onAttachedToWindow(); + blurredPhotoImage.onAttachedToWindow(); if (photoImage.onAttachedToWindow()) { if (drawPhotoImage) { updateButtonState(false, false, false); @@ -3863,6 +4043,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate replyImageReceiver.onDetachedFromWindow(); locationImageReceiver.onDetachedFromWindow(); photoImage.onDetachedFromWindow(); + blurredPhotoImage.onDetachedFromWindow(); if (currentMessageObject != null && !currentMessageObject.mediaExists && !currentMessageObject.putInDownloadsStore && !DownloadController.getInstance(currentAccount).isDownloading(currentMessageObject.messageOwner.id)) { TLRPC.Document document = currentMessageObject.getDocument(); @@ -3892,6 +4073,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate lastHeight = AndroidUtilities.displaySize.y; lastWidth = getParentWidth(); isRoundVideo = messageObject != null && messageObject.isRoundVideo(); + mediaSpoilerRevealProgress = 0f; TLRPC.Message newReply = messageObject.hasValidReplyMessageObject() ? messageObject.replyMessageObject.messageOwner : null; boolean messageIdChanged = currentMessageObject == null || currentMessageObject.getId() != messageObject.getId(); boolean messageChanged = currentMessageObject != messageObject || messageObject.forceUpdate || (isRoundVideo && isPlayingRound != (MediaController.getInstance().isPlayingMessage(currentMessageObject) && delegate != null && !delegate.keyboardIsOpened())); @@ -4077,7 +4259,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate hasOldCaptionPreview = false; hasGamePreview = false; hasInvoicePreview = false; - instantPressed = instantButtonPressed = commentButtonPressed = false; + instantPressed = commentButtonPressed = false; + setInstantButtonPressed(false); if (!pollChanged && Build.VERSION.SDK_INT >= 21) { for (int a = 0; a < selectorDrawable.length; a++) { if (selectorDrawable[a] != null) { @@ -5197,10 +5380,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_STICKER) { boolean isWebpSticker = messageObject.isSticker(); - if (!SharedConfig.loopStickers && messageObject.isVideoSticker()) { + if (!SharedConfig.loopStickers() && messageObject.isVideoSticker()) { photoImage.animatedFileDrawableRepeatMaxCount = 1; } - if (SharedConfig.loopStickers || (isWebpSticker && !messageObject.isVideoSticker())) { + if (SharedConfig.loopStickers() || (isWebpSticker && !messageObject.isVideoSticker())) { photoImage.setAutoRepeat(1); } else { currentPhotoFilter = String.format(Locale.US, "%d_%d_nr_messageId=%d", w, h, messageObject.stableId); @@ -5210,7 +5393,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO) { photoImage.setNeedsQualityThumb(true); photoImage.setShouldGenerateQualityThumb(true); - if (SharedConfig.autoplayVideo && ( + if (SharedConfig.autoplayVideo && (!currentMessageObject.hasMediaSpoilers() || currentMessageObject.isMediaSpoilersRevealed || currentMessageObject.revealingMediaSpoilers) && ( currentMessageObject.mediaExists || messageObject.canStreamVideo() && DownloadController.getInstance(currentAccount).canDownloadMedia(currentMessageObject) )) { @@ -5264,6 +5447,13 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } } + if (blurredPhotoImage.getBitmap() != null) { + blurredPhotoImage.getBitmap().recycle(); + blurredPhotoImage.setImageBitmap((Bitmap) null); + } + if (photoImage.getBitmap() != null && !photoImage.getBitmap().isRecycled() && currentMessageObject.hasMediaSpoilers() && !currentMessageObject.isMediaSpoilersRevealed) { + blurredPhotoImage.setImageBitmap(Utilities.stackBlurBitmapMax(photoImage.getBitmap())); + } drawPhotoImage = true; if (type != null && type.equals("video") && duration != 0) { @@ -5290,6 +5480,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } else { photoImage.setImageBitmap((Drawable) null); + if (blurredPhotoImage.getBitmap() != null) { + blurredPhotoImage.getBitmap().recycle(); + blurredPhotoImage.setImageBitmap((Bitmap) null); + } linkPreviewHeight -= AndroidUtilities.dp(6); totalHeight += AndroidUtilities.dp(4); } @@ -5330,6 +5524,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else { photoImage.setImageBitmap((Drawable) null); calcBackgroundWidth(maxWidth, timeMore, maxChildWidth); + + if (blurredPhotoImage.getBitmap() != null) { + blurredPhotoImage.getBitmap().recycle(); + blurredPhotoImage.setImageBitmap((Bitmap) null); + } } } else if (messageObject.type == MessageObject.TYPE_PHONE_CALL) { createSelectorDrawable(0); @@ -6317,7 +6516,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate photoImage.setCrossfadeWithOldImage(true); } } - } else if (SharedConfig.loopStickers || (isWebpSticker && !messageObject.isVideoSticker())) { + } else if (SharedConfig.loopStickers() || (isWebpSticker && !messageObject.isVideoSticker())) { filter = String.format(Locale.US, "%d_%d", w, h); photoImage.setAutoRepeat(1); } else { @@ -6335,7 +6534,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } if (messageObject.getDocument() != null) { if (messageObject.isVideoSticker()) { - if (!SharedConfig.loopStickers) { + if (!loopStickers()) { photoImage.animatedFileDrawableRepeatMaxCount = 1; } photoImage.setImage(ImageLocation.getForDocument(messageObject.getDocument()), ImageLoader.AUTOPLAY_FILTER, @@ -6369,6 +6568,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate reactionsLayoutInBubble.positionOffsetY += AndroidUtilities.dp(4); } } + + if (blurredPhotoImage.getBitmap() != null) { + blurredPhotoImage.getBitmap().recycle(); + blurredPhotoImage.setImageBitmap((Bitmap) null); + } + if (photoImage.getBitmap() != null && !photoImage.getBitmap().isRecycled() && currentMessageObject.hasMediaSpoilers() && !currentMessageObject.isMediaSpoilersRevealed) { + blurredPhotoImage.setImageBitmap(Utilities.stackBlurBitmapMax(photoImage.getBitmap())); + } } else { currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, AndroidUtilities.getPhotoSize()); photoParentObject = messageObject.photoThumbsObject; @@ -6863,7 +7070,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate currentPhotoObjectThumb.size = -1; } - if (SharedConfig.autoplayVideo && messageObject.type == MessageObject.TYPE_VIDEO && !messageObject.needDrawBluredPreview() && + if (SharedConfig.autoplayVideo && (!currentMessageObject.hasMediaSpoilers() || currentMessageObject.isMediaSpoilersRevealed || currentMessageObject.revealingMediaSpoilers) && messageObject.type == MessageObject.TYPE_VIDEO && !messageObject.needDrawBluredPreview() && (currentMessageObject.mediaExists || messageObject.canStreamVideo() && DownloadController.getInstance(currentAccount).canDownloadMedia(currentMessageObject)) ) { if (currentPosition != null) { @@ -6980,6 +7187,13 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } } + if (blurredPhotoImage.getBitmap() != null) { + blurredPhotoImage.getBitmap().recycle(); + blurredPhotoImage.setImageBitmap((Bitmap) null); + } + if (photoImage.getBitmap() != null && !photoImage.getBitmap().isRecycled() && currentMessageObject.hasMediaSpoilers() && !currentMessageObject.isMediaSpoilersRevealed) { + blurredPhotoImage.setImageBitmap(Utilities.stackBlurBitmapMax(photoImage.getBitmap())); + } setMessageObjectInternal(messageObject); if (drawForwardedName && messageObject.needDrawForwarded() && (currentPosition == null || currentPosition.minY == 0)) { @@ -7357,6 +7571,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } if (!drawPhotoImage) { photoImage.setImageBitmap((Drawable) null); + + if (blurredPhotoImage.getBitmap() != null) { + blurredPhotoImage.getBitmap().recycle(); + blurredPhotoImage.setImageBitmap((Bitmap) null); + } } if (documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { if (MessageObject.isDocumentHasThumb(documentAttach)) { @@ -7460,6 +7679,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate toSeekBarProgress = showSeekbar ? 1f : 0f; } + if (seekBar != null) { + seekBar.updateTimestamps(currentMessageObject, null); + } + seekBarWaveform.setProgress(0); if (currentNameStatusDrawable != null) { @@ -7488,6 +7711,35 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate updateFlagSecure(); } + private boolean loopStickers() { + return SharedConfig.loopStickers && !SharedConfig.getLiteMode().enabled(); + } + + private void highlightCaptionLink(URLSpan link) { + try { + if (link != null && captionLayout != null && captionLayout.getText() instanceof Spanned) { + if (link == highlightPathSpan) { + return; + } + highlightPathSpan = link; + Spanned caption = (Spanned) captionLayout.getText(); + int start = caption.getSpanStart(highlightPathSpan); + int end = caption.getSpanEnd(highlightPathSpan); + if (highlightPath != null) { + highlightPath.rewind(); + } else { + highlightPath = new LinkPath(true); + } + highlightPath.setCurrentLayout(captionLayout, start, 0); + captionLayout.getSelectionPath(start, end, highlightPath); + highlightPathStart = System.currentTimeMillis(); + invalidate(); + } + } catch (Exception e) { + FileLog.e(e); + } + } + private void calculateUnlockXY() { if (currentMessageObject.type == MessageObject.TYPE_EXTENDED_MEDIA_PREVIEW && unlockLayout != null) { unlockX = backgroundDrawableLeft + (photoImage.getImageWidth() - unlockLayout.getWidth()) / 2f; @@ -7591,7 +7843,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } } if (replySelector != null) { + replySelectorPressed = false; replySelector.setState(new int[]{}); + invalidate(); } if (topicButton != null) { topicButton.resetClick(); @@ -7639,7 +7893,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate gamePreviewPressed = false; if (pressedVoteButton != -1 || pollHintPressed || psaHintPressed || instantPressed || otherPressed || commentButtonPressed) { - instantPressed = instantButtonPressed = commentButtonPressed = false; + instantPressed = commentButtonPressed = false; + setInstantButtonPressed(false); pressedVoteButton = -1; pollHintPressed = false; psaHintPressed = false; @@ -9179,9 +9434,15 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate canvas.save(); canvas.scale(-1f, 1, photoImage.getCenterX(), photoImage.getCenterY()); imageDrawn = photoImage.draw(canvas); + if (currentMessageObject.hasMediaSpoilers()) { + drawBlurredPhoto(canvas); + } canvas.restore(); } else { imageDrawn = photoImage.draw(canvas); + if (currentMessageObject.hasMediaSpoilers()) { + drawBlurredPhoto(canvas); + } } photoImage.setSkipUpdateFrame(false); } @@ -9542,7 +9803,19 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate int linkPreviewY = startY; Theme.chat_replyLinePaint.setColor(getThemedColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outPreviewLine : Theme.key_chat_inPreviewLine)); - canvas.drawRect(linkX, linkPreviewY - AndroidUtilities.dp(3), linkX + AndroidUtilities.dp(2), linkPreviewY + linkPreviewHeight, Theme.chat_replyLinePaint); + AndroidUtilities.rectTmp.set(linkX, linkPreviewY - AndroidUtilities.dp(3), linkX + AndroidUtilities.dp(3), linkPreviewY + linkPreviewHeight); + if (replyRoundRectPath == null) { + replyRoundRectPath = new Path(); + } else { + replyRoundRectPath.rewind(); + } + if (replyRoundRectRadii == null) { + replyRoundRectRadii = new float[8]; + replyRoundRectRadii[0] = replyRoundRectRadii[1] = replyRoundRectRadii[6] = replyRoundRectRadii[7] = AndroidUtilities.dp(2); // left + replyRoundRectRadii[2] = replyRoundRectRadii[3] = replyRoundRectRadii[4] = replyRoundRectRadii[5] = AndroidUtilities.dp(1); // right + } + replyRoundRectPath.addRoundRect(AndroidUtilities.rectTmp, replyRoundRectRadii, Path.Direction.CW); + canvas.drawPath(replyRoundRectPath, Theme.chat_replyLinePaint); if (siteNameLayout != null) { Theme.chat_replyNamePaint.setColor(getThemedColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outSiteNameText : Theme.key_chat_inSiteNameText)); @@ -9708,6 +9981,84 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate transitionParams.recordDrawingState(); } + private void startRevealMedia(float x, float y) { + mediaSpoilerRevealMaxRadius = (float) Math.sqrt(Math.pow(photoImage.getImageWidth(), 2) + Math.pow(photoImage.getImageHeight(), 2)); + startRevealMedia(x, y, mediaSpoilerRevealMaxRadius); + } + + private void startRevealMedia(float x, float y, float maxRadius) { + if (currentMessageObject.isMediaSpoilersRevealed || mediaSpoilerRevealProgress != 0f) { + return; + } + + if (currentMessageObject.type == MessageObject.TYPE_VIDEO) { + currentMessageObject.forceUpdate = true; + currentMessageObject.revealingMediaSpoilers = true; + setMessageContent(currentMessageObject, currentMessagesGroup, pinnedBottom, pinnedTop); + currentMessageObject.revealingMediaSpoilers = false; + currentMessageObject.forceUpdate = false; + + if (currentMessagesGroup != null) { + radialProgress.setProgress(0f, false); + } + } + + mediaSpoilerRevealX = x; + mediaSpoilerRevealY = y; + + ValueAnimator animator = ValueAnimator.ofFloat(0, 1).setDuration((long) MathUtils.clamp(mediaSpoilerRevealMaxRadius * 0.3f, 250, 550)); + animator.setInterpolator(CubicBezierInterpolator.EASE_BOTH); + animator.addUpdateListener(animation -> { + mediaSpoilerRevealProgress = (float) animation.getAnimatedValue(); + invalidate(); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + currentMessageObject.isMediaSpoilersRevealed = true; + invalidate(); + } + }); + animator.start(); + } + + private void drawBlurredPhoto(Canvas canvas) { + if (currentMessageObject.isMediaSpoilersRevealed || mediaSpoilerRevealProgress == 1f) { + return; + } + + int[] rad = photoImage.getRoundRadius(); + mediaSpoilerRadii[0] = mediaSpoilerRadii[1] = rad[0]; + mediaSpoilerRadii[2] = mediaSpoilerRadii[3] = rad[1]; + mediaSpoilerRadii[4] = mediaSpoilerRadii[5] = rad[2]; + mediaSpoilerRadii[6] = mediaSpoilerRadii[7] = rad[3]; + + mediaSpoilerPath.rewind(); + AndroidUtilities.rectTmp.set(photoImage.getImageX(), photoImage.getImageY(), photoImage.getImageX2(), photoImage.getImageY2()); + mediaSpoilerPath.addRoundRect(AndroidUtilities.rectTmp, mediaSpoilerRadii, Path.Direction.CW); + + canvas.save(); + canvas.clipPath(mediaSpoilerPath); + + if (mediaSpoilerRevealProgress != 0f) { + mediaSpoilerPath.rewind(); + mediaSpoilerPath.addCircle(mediaSpoilerRevealX, mediaSpoilerRevealY, mediaSpoilerRevealMaxRadius * mediaSpoilerRevealProgress, Path.Direction.CW); + canvas.clipPath(mediaSpoilerPath, Region.Op.DIFFERENCE); + } + + blurredPhotoImage.setImageCoords(photoImage.getImageX(), photoImage.getImageY(), photoImage.getImageWidth(), photoImage.getImageHeight()); + blurredPhotoImage.setRoundRadius(photoImage.getRoundRadius()); + blurredPhotoImage.draw(canvas); + + int sColor = Color.WHITE; + mediaSpoilerEffect.setColor(ColorUtils.setAlphaComponent(sColor, (int) (Color.alpha(sColor) * 0.325f * photoImage.getAlpha()))); + mediaSpoilerEffect.setBounds((int) photoImage.getImageX(), (int) photoImage.getImageY(), (int) photoImage.getImageX2(), (int) photoImage.getImageY2()); + mediaSpoilerEffect.draw(canvas); + canvas.restore(); + + invalidate(); + } + private float getUseTranscribeButtonProgress() { if (transitionParams.animateUseTranscribeButton) { if (useTranscribeButton) { @@ -9803,7 +10154,19 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (alpha != 1f) { Theme.chat_replyLinePaint.setAlpha((int) (alpha * Theme.chat_replyLinePaint.getAlpha())); } - canvas.drawRect(linkX, linkPreviewY - AndroidUtilities.dp(3), linkX + AndroidUtilities.dp(2), linkPreviewY + linkPreviewHeight + AndroidUtilities.dp(3), Theme.chat_replyLinePaint); + AndroidUtilities.rectTmp.set(linkX, linkPreviewY - AndroidUtilities.dp(3), linkX + AndroidUtilities.dp(3), linkPreviewY + linkPreviewHeight + AndroidUtilities.dp(3)); + if (replyRoundRectPath == null) { + replyRoundRectPath = new Path(); + } else { + replyRoundRectPath.rewind(); + } + if (replyRoundRectRadii == null) { + replyRoundRectRadii = new float[8]; + replyRoundRectRadii[0] = replyRoundRectRadii[1] = replyRoundRectRadii[6] = replyRoundRectRadii[7] = AndroidUtilities.dp(2); // left + replyRoundRectRadii[2] = replyRoundRectRadii[3] = replyRoundRectRadii[4] = replyRoundRectRadii[5] = AndroidUtilities.dp(1); // right + } + replyRoundRectPath.addRoundRect(AndroidUtilities.rectTmp, replyRoundRectRadii, Path.Direction.CW); + canvas.drawPath(replyRoundRectPath, Theme.chat_replyLinePaint); } if (siteNameLayout != null) { @@ -9897,9 +10260,17 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (alpha != 1f) { photoImage.setAlpha(alpha); imageDrawn = photoImage.draw(canvas); + if (currentMessageObject.hasMediaSpoilers()) { + blurredPhotoImage.setAlpha(alpha); + drawBlurredPhoto(canvas); + blurredPhotoImage.setAlpha(1f); + } photoImage.setAlpha(1f); } else { imageDrawn = photoImage.draw(canvas); + if (currentMessageObject.hasMediaSpoilers()) { + drawBlurredPhoto(canvas); + } } } } @@ -9956,6 +10327,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate invalidate(); } } + drawProgressLoadingLink(canvas, -2); if (delegate != null && delegate.getTextSelectionHelper() != null && getDelegate().getTextSelectionHelper().isSelected(currentMessageObject)) { delegate.getTextSelectionHelper().drawDescription(currentMessageObject.isOutOwner(), descriptionLayout, canvas); } @@ -9989,9 +10361,17 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (alpha != 1f) { photoImage.setAlpha(alpha); imageDrawn = photoImage.draw(canvas); + if (currentMessageObject.hasMediaSpoilers()) { + blurredPhotoImage.setAlpha(alpha); + drawBlurredPhoto(canvas); + blurredPhotoImage.setAlpha(1f); + } photoImage.setAlpha(1f); } else { imageDrawn = photoImage.draw(canvas); + if (currentMessageObject.hasMediaSpoilers()) { + drawBlurredPhoto(canvas); + } } } } @@ -10059,22 +10439,68 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate Drawable instantDrawable; int instantY = startY + linkPreviewHeight + AndroidUtilities.dp(10); Paint backPaint = Theme.chat_instantViewRectPaint; + boolean loading = delegate != null && delegate.isProgressLoading(this, ChatActivity.PROGRESS_INSTANT); + if (instantButtonLoading != null && !loading && !instantButtonLoading.isDisappeared() && !instantButtonLoading.isDisappearing()) { + instantButtonLoading.disappear(); + } + if (instantButtonLoading == null && loading) { + instantButtonLoading = new LoadingDrawable(); + instantButtonLoading.strokePaint.setStrokeWidth(AndroidUtilities.dp(1.25f)); + instantButtonLoading.setAppearByGradient(true); + } else if (instantButtonLoading != null && loading && (instantButtonLoading.isDisappeared() || instantButtonLoading.isDisappearing())) { + instantButtonLoading.reset(); + instantButtonLoading.resetDisappear(); + } if (currentMessageObject.isOutOwner()) { instantDrawable = getThemedDrawable(Theme.key_drawable_msgOutInstant); Theme.chat_instantViewPaint.setColor(getThemedColor(Theme.key_chat_outPreviewInstantText)); backPaint.setColor(getThemedColor(Theme.key_chat_outPreviewInstantText)); + if (instantButtonLoading != null) { + instantButtonLoading.setColors( + Theme.multAlpha(getThemedColor(Theme.key_chat_outPreviewInstantText), .1f), + Theme.multAlpha(getThemedColor(Theme.key_chat_outPreviewInstantText), .3f), + Theme.multAlpha(getThemedColor(Theme.key_chat_outPreviewInstantText), .3f), + Theme.multAlpha(getThemedColor(Theme.key_chat_outPreviewInstantText), 1.2f) + ); + } } else { instantDrawable = getThemedDrawable(Theme.key_drawable_msgInInstant); Theme.chat_instantViewPaint.setColor(getThemedColor(Theme.key_chat_inPreviewInstantText)); backPaint.setColor(getThemedColor(Theme.key_chat_inPreviewInstantText)); + if (instantButtonLoading != null) { + instantButtonLoading.setColors( + Theme.multAlpha(getThemedColor(Theme.key_chat_inPreviewInstantText), .1f), + Theme.multAlpha(getThemedColor(Theme.key_chat_inPreviewInstantText), .3f), + Theme.multAlpha(getThemedColor(Theme.key_chat_inPreviewInstantText), .3f), + Theme.multAlpha(getThemedColor(Theme.key_chat_inPreviewInstantText), 1.2f) + ); + } } instantButtonRect.set(linkX, instantY, linkX + instantWidth, instantY + AndroidUtilities.dp(36)); + if (instantButtonLoading != null) { + instantButtonLoading.setBounds(instantButtonRect); + instantButtonLoading.setRadiiDp(6); + } + if (instantButtonPressed && instantButtonPressProgress != 1f) { + instantButtonPressProgress += (float) Math.min(40, 1000f / AndroidUtilities.screenRefreshRate) / 100f; + instantButtonPressProgress = Utilities.clamp(instantButtonPressProgress, 1f, 0); + invalidate(); + } + float scale = 0.98f + 0.02f * (1f - instantButtonPressProgress); + if (scale != 1) { + canvas.save(); + canvas.scale(scale, scale, instantButtonRect.centerX(), instantButtonRect.centerY()); + } if (Build.VERSION.SDK_INT >= 21) { selectorDrawableMaskType[0] = 0; selectorDrawable[0].setBounds(linkX, instantY, linkX + instantWidth, instantY + AndroidUtilities.dp(36)); selectorDrawable[0].draw(canvas); } + if (instantButtonLoading != null && !instantButtonLoading.isDisappeared()) { + instantButtonLoading.draw(canvas); + invalidate(); + } canvas.drawRoundRect(instantButtonRect, AndroidUtilities.dp(6), AndroidUtilities.dp(6), backPaint); if (drawInstantViewType == 0) { setDrawableBounds(instantDrawable, instantTextLeftX + instantTextX + linkX - AndroidUtilities.dp(15), instantY + AndroidUtilities.dp(11.5f), AndroidUtilities.dp(9), AndroidUtilities.dp(13)); @@ -10086,6 +10512,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate instantViewLayout.draw(canvas); canvas.restore(); } + if (scale != 1) { + canvas.restore(); + } } } @@ -10119,14 +10548,57 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate for (int a = 0; a < botButtons.size(); a++) { BotButton button = botButtons.get(a); float y = button.y + layoutHeight - AndroidUtilities.dp(2) + transitionParams.deltaBottom; + float s = button.getPressScale(); rect.set(button.x + addX, y, button.x + addX + button.width, y + button.height); + if (s != 1) { + canvas.save(); + canvas.scale(s, s, rect.centerX(), rect.centerY()); + } applyServiceShaderMatrix(); - canvas.drawRoundRect(rect, AndroidUtilities.dp(6), AndroidUtilities.dp(6), getThemedPaint(a == pressedBotButton ? Theme.key_paint_chatActionBackgroundSelected : Theme.key_paint_chatActionBackground)); + canvas.drawRoundRect(rect, AndroidUtilities.dp(6), AndroidUtilities.dp(6), getThemedPaint(Theme.key_paint_chatActionBackground)); if (hasGradientService()) { canvas.drawRoundRect(rect, AndroidUtilities.dp(6), AndroidUtilities.dp(6), Theme.chat_actionBackgroundGradientDarkenPaint); } + boolean drawProgress = (button.button instanceof TLRPC.TL_keyboardButtonCallback || button.button instanceof TLRPC.TL_keyboardButtonGame || button.button instanceof TLRPC.TL_keyboardButtonBuy || button.button instanceof TLRPC.TL_keyboardButtonUrlAuth) && SendMessagesHelper.getInstance(currentAccount).isSendingCallback(currentMessageObject, button.button) || + button.button instanceof TLRPC.TL_keyboardButtonRequestGeoLocation && SendMessagesHelper.getInstance(currentAccount).isSendingCurrentLocation(currentMessageObject, button.button) || + button.button instanceof TLRPC.TL_keyboardButtonUrl && delegate != null && delegate.isProgressLoading(this, ChatActivity.PROGRESS_BOT_BUTTON) && delegate.getProgressLoadingBotButtonUrl(this) == button.button.url; + + if (drawProgress) { + if (button.loadingDrawable == null) { + button.loadingDrawable = new LoadingDrawable(); + button.loadingDrawable.setRadiiDp(5.5f); + button.loadingDrawable.setAppearByGradient(true); + button.loadingDrawable.strokePaint.setStrokeWidth(AndroidUtilities.dpf2(1.25f)); + } else if (button.loadingDrawable.isDisappeared() || button.loadingDrawable.isDisappearing()) { + button.loadingDrawable.reset(); + button.loadingDrawable.resetDisappear(); + } + } else if (button.loadingDrawable != null && !button.loadingDrawable.isDisappearing() && !button.loadingDrawable.isDisappeared()) { + button.loadingDrawable.disappear(); + } + + if (button.loadingDrawable != null && (drawProgress || button.loadingDrawable.isDisappearing())) { + rect.inset(AndroidUtilities.dpf2(.625f), AndroidUtilities.dpf2(.625f)); + button.loadingDrawable.setBounds(rect); + button.loadingDrawable.setColors( + Theme.multAlpha(Theme.getColor(Theme.key_chat_serviceBackgroundSelector, resourcesProvider), 1f), + Theme.multAlpha(Theme.getColor(Theme.key_chat_serviceBackgroundSelector, resourcesProvider), 2.5f), + Theme.multAlpha(Theme.getColor(Theme.key_chat_serviceBackgroundSelector, resourcesProvider), 3f), + Theme.multAlpha(Theme.getColor(Theme.key_chat_serviceBackgroundSelector, resourcesProvider), 10f) + ); + button.loadingDrawable.setAlpha((int) (0xFF * alpha)); + button.loadingDrawable.draw(canvas); + invalidateOutbounds(); + } + + if (button.selectorDrawable != null) { + button.selectorDrawable.setBounds(button.x + addX, (int) y, button.x + addX + button.width, (int) y + button.height); + button.selectorDrawable.setAlpha((int) (0xFF * alpha)); + button.selectorDrawable.draw(canvas); + } + canvas.save(); canvas.translate(button.x + addX + AndroidUtilities.dp(5), y + (AndroidUtilities.dp(44) - button.title.getLineBottom(button.title.getLineCount() - 1)) / 2); button.title.draw(canvas); @@ -10151,50 +10623,14 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate int x = button.x + button.width - AndroidUtilities.dp(3) - drawable.getIntrinsicWidth() + addX; setDrawableBounds(drawable, x, y + AndroidUtilities.dp(3)); drawable.draw(canvas); - } else if (button.button instanceof TLRPC.TL_keyboardButtonCallback || button.button instanceof TLRPC.TL_keyboardButtonRequestGeoLocation || button.button instanceof TLRPC.TL_keyboardButtonGame || button.button instanceof TLRPC.TL_keyboardButtonBuy || button.button instanceof TLRPC.TL_keyboardButtonUrlAuth) { - if (button.button instanceof TLRPC.TL_keyboardButtonBuy) { - int x = button.x + button.width - AndroidUtilities.dp(5) - Theme.chat_botCardDrawable.getIntrinsicWidth() + addX; - setDrawableBounds(Theme.chat_botCardDrawable, x, y + AndroidUtilities.dp(4)); - Theme.chat_botCardDrawable.draw(canvas); - } - boolean drawProgress = (button.button instanceof TLRPC.TL_keyboardButtonCallback || button.button instanceof TLRPC.TL_keyboardButtonGame || button.button instanceof TLRPC.TL_keyboardButtonBuy || button.button instanceof TLRPC.TL_keyboardButtonUrlAuth) && SendMessagesHelper.getInstance(currentAccount).isSendingCallback(currentMessageObject, button.button) || - button.button instanceof TLRPC.TL_keyboardButtonRequestGeoLocation && SendMessagesHelper.getInstance(currentAccount).isSendingCurrentLocation(currentMessageObject, button.button); - if (drawProgress || button.progressAlpha != 0) { - Theme.chat_botProgressPaint.setAlpha(Math.min(255, (int) (button.progressAlpha * 255))); - int x = button.x + button.width - AndroidUtilities.dp(9 + 3) + addX; - if (button.button instanceof TLRPC.TL_keyboardButtonBuy) { - y += AndroidUtilities.dp(26); - } - rect.set(x, y + AndroidUtilities.dp(4), x + AndroidUtilities.dp(8), y + AndroidUtilities.dp(8 + 4)); - canvas.drawArc(rect, button.angle, 220, false, Theme.chat_botProgressPaint); - invalidate(); - if (getParent() != null) { - ((View) getParent()).invalidate(); - } - long newTime = System.currentTimeMillis(); - if (Math.abs(button.lastUpdateTime - System.currentTimeMillis()) < 1000) { - long delta = (newTime - button.lastUpdateTime); - float dt = 360 * delta / 2000.0f; - button.angle += dt; - button.angle -= 360 * (button.angle / 360); - if (drawProgress) { - if (button.progressAlpha < 1.0f) { - button.progressAlpha += delta / 200.0f; - if (button.progressAlpha > 1.0f) { - button.progressAlpha = 1.0f; - } - } - } else { - if (button.progressAlpha > 0.0f) { - button.progressAlpha -= delta / 200.0f; - if (button.progressAlpha < 0.0f) { - button.progressAlpha = 0.0f; - } - } - } - } - button.lastUpdateTime = newTime; - } + } else if (button.button instanceof TLRPC.TL_keyboardButtonBuy) { + int x = button.x + button.width - AndroidUtilities.dp(5) - Theme.chat_botCardDrawable.getIntrinsicWidth() + addX; + setDrawableBounds(Theme.chat_botCardDrawable, x, y + AndroidUtilities.dp(4)); + Theme.chat_botCardDrawable.draw(canvas); + } + + if (s != 1) { + canvas.restore(); } } canvas.restore(); @@ -10264,10 +10700,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate MessageObject.TextLayoutBlock block = textLayoutBlocks.get(a); canvas.save(); canvas.translate(textX - (block.isRtl() ? (int) Math.ceil(currentMessageObject.textXOffset) : 0), textY + block.textYOffset + transitionYOffsetForDrawables); - if (a == linkBlockNum && !drawOnlyText) { - if (links.draw(canvas)) { + if (a == linkBlockNum) { + if (!drawOnlyText && links.draw(canvas)) { invalidate(); } + drawProgressLoadingLink(canvas, a); } if (a == linkSelectionBlockNum && !urlPathSelection.isEmpty() && !drawOnlyText) { for (int b = 0; b < urlPathSelection.size(); b++) { @@ -11239,6 +11676,15 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate updateButtonState(false, true, false); } } + if (set && !thumb && currentMessageObject != null) { + if (blurredPhotoImage.getBitmap() != null) { + blurredPhotoImage.getBitmap().recycle(); + blurredPhotoImage.setImageBitmap((Bitmap) null); + } + if (currentMessageObject.hasMediaSpoilers() && imageReceiver.getBitmap() != null && !imageReceiver.getBitmap().isRecycled()) { + blurredPhotoImage.setImageBitmap(Utilities.stackBlurBitmapMax(imageReceiver.getBitmap())); + } + } } public boolean setCurrentDiceValue(boolean instant) { @@ -11972,7 +12418,7 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate topicButton = null; } - if ((!isThreadChat || messageObject.getReplyTopMsgId() != 0) && messageObject.hasValidReplyMessageObject() || messageObject.messageOwner.fwd_from != null && messageObject.isDice()) { + if ((!isThreadChat || messageObject.getReplyTopMsgId(isForum) != 0 || isForumGeneral) && messageObject.hasValidReplyMessageObject() || messageObject.messageOwner.fwd_from != null && messageObject.isDice()) { if (currentPosition == null || currentPosition.minY == 0) { if (!messageObject.isAnyKindOfSticker() && messageObject.type != MessageObject.TYPE_ROUND_VIDEO || messageObject.type == MessageObject.TYPE_EMOJIS) { namesOffset += AndroidUtilities.dp(14) + (Theme.chat_replyTextPaint.getTextSize() + Theme.chat_replyNamePaint.getTextSize()); @@ -11996,10 +12442,11 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate CharSequence stringFinalText = null; String name = null; - if ((!isThreadChat || messageObject.getReplyTopMsgId() != 0) && messageObject.hasValidReplyMessageObject()) { + if ((!isThreadChat || messageObject.getReplyTopMsgId(isForum) != 0 || isForumGeneral) && messageObject.hasValidReplyMessageObject()) { lastReplyMessage = messageObject.replyMessageObject.messageOwner; int cacheType = 1; int size = 0; + boolean hasReplySpoiler = messageObject.replyMessageObject.hasMediaSpoilers(); TLObject photoObject; TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(messageObject.replyMessageObject.photoThumbs2, 320); TLRPC.PhotoSize thumbPhotoSize = FileLoader.getClosestPhotoSizeWithSize(messageObject.replyMessageObject.photoThumbs2, 40); @@ -12027,10 +12474,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (messageObject.replyMessageObject.isRoundVideo()) { replyImageReceiver.setRoundRadius(AndroidUtilities.dp(32)); } else { - replyImageReceiver.setRoundRadius(AndroidUtilities.dp(2)); + replyImageReceiver.setRoundRadius(AndroidUtilities.dp(4)); } currentReplyPhoto = photoSize; - replyImageReceiver.setImage(ImageLocation.getForObject(photoSize, photoObject), "50_50", ImageLocation.getForObject(thumbPhotoSize, photoObject), "50_50_b", size, null, messageObject.replyMessageObject, cacheType); + replyImageReceiver.setImage(ImageLocation.getForObject(photoSize, photoObject), hasReplySpoiler ? "20_20_b" : "50_50", ImageLocation.getForObject(thumbPhotoSize, photoObject), hasReplySpoiler ? "50_50_b4" : "50_50_b", size, null, messageObject.replyMessageObject, cacheType); needReplyImage = true; maxWidth -= AndroidUtilities.dp(16) + (Theme.chat_replyTextPaint.getTextSize() + Theme.chat_replyNamePaint.getTextSize()); } @@ -12052,7 +12499,10 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } else if (messageObject.customReplyName != null) { name = messageObject.customReplyName; } else { - name = messageObject.replyMessageObject.getForwardedName(); + if (drawForwardedName) { + name = messageObject.replyMessageObject.getForwardedName(); + } + if (name == null) { long fromId = messageObject.replyMessageObject.getFromChatId(); if (fromId > 0) { @@ -12118,15 +12568,17 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate replyImageReceiver.setImageBitmap((Drawable) null); needReplyImage = false; replyPanelIsForward = true; - if (messageObject.messageOwner.fwd_from.from_id instanceof TLRPC.TL_peerChannel) { - currentForwardChannel = MessagesController.getInstance(currentAccount).getChat(messageObject.messageOwner.fwd_from.from_id.channel_id); - } else if (messageObject.messageOwner.fwd_from.from_id instanceof TLRPC.TL_peerChat) { - currentForwardChannel = MessagesController.getInstance(currentAccount).getChat(messageObject.messageOwner.fwd_from.from_id.chat_id); - } else if (messageObject.messageOwner.fwd_from.from_id instanceof TLRPC.TL_peerUser) { - currentForwardUser = MessagesController.getInstance(currentAccount).getUser(messageObject.messageOwner.fwd_from.from_id.user_id); - } - if (messageObject.messageOwner.fwd_from.from_name != null) { - currentForwardName = messageObject.messageOwner.fwd_from.from_name; + if (messageObject.messageOwner.fwd_from != null) { + if (messageObject.messageOwner.fwd_from.from_id instanceof TLRPC.TL_peerChannel) { + currentForwardChannel = MessagesController.getInstance(currentAccount).getChat(messageObject.messageOwner.fwd_from.from_id.channel_id); + } else if (messageObject.messageOwner.fwd_from.from_id instanceof TLRPC.TL_peerChat) { + currentForwardChannel = MessagesController.getInstance(currentAccount).getChat(messageObject.messageOwner.fwd_from.from_id.chat_id); + } else if (messageObject.messageOwner.fwd_from.from_id instanceof TLRPC.TL_peerUser) { + currentForwardUser = MessagesController.getInstance(currentAccount).getUser(messageObject.messageOwner.fwd_from.from_id.user_id); + } + if (messageObject.messageOwner.fwd_from.from_name != null) { + currentForwardName = messageObject.messageOwner.fwd_from.from_name; + } } if (currentForwardUser != null || currentForwardChannel != null || currentForwardName != null) { @@ -12196,6 +12648,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } animatedEmojiReplyStack = AnimatedEmojiSpan.update(AnimatedEmojiDrawable.CACHE_TYPE_MESSAGES, this, false, animatedEmojiReplyStack, replyTextLayout); } + if (replyNameWidth > replyTextWidth) { + replyNameWidth += AndroidUtilities.dp(Math.max(2, SharedConfig.bubbleRadius / 4f)); + } } catch (Exception e) { FileLog.e(e); } @@ -13988,11 +14443,18 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate } replyStartY = this.replyStartY * transitionParams.animateChangeProgress + transitionParams.animateFromReplyY * (1f - transitionParams.animateChangeProgress); } + final boolean loading = currentMessageObject != null && delegate != null && delegate.isProgressLoading(this, ChatActivity.PROGRESS_REPLY); + if (replyPressedFloat == null) { + replyPressedFloat = new AnimatedFloat(this); + } + float replyPressedT = replyPressedFloat.set(replySelectorPressed || loading ? 1f : 0f); + int rippleColor = getThemedColor(Theme.key_listSelector); if (currentMessageObject.shouldDrawWithoutBackground()) { Theme.chat_replyLinePaint.setColor(getThemedColor(Theme.key_chat_stickerReplyLine)); int oldAlpha = Theme.chat_replyLinePaint.getAlpha(); Theme.chat_replyLinePaint.setAlpha((int) (oldAlpha * timeAlpha * replyForwardAlpha)); + rippleColor = ColorUtils.setAlphaComponent(Theme.chat_replyLinePaint.getColor(), Color.alpha(rippleColor)); Theme.chat_replyNamePaint.setColor(getThemedColor(Theme.key_chat_stickerReplyNameText)); oldAlpha = Theme.chat_replyNamePaint.getAlpha(); Theme.chat_replyNamePaint.setAlpha((int) (oldAlpha * timeAlpha * replyForwardAlpha)); @@ -14022,7 +14484,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (currentMessageObject.hasValidReplyMessageObject() && (currentMessageObject.replyMessageObject.type == MessageObject.TYPE_TEXT || !TextUtils.isEmpty(currentMessageObject.replyMessageObject.caption)) && !(MessageObject.getMedia(currentMessageObject.replyMessageObject.messageOwner) instanceof TLRPC.TL_messageMediaGame || MessageObject.getMedia(currentMessageObject.replyMessageObject.messageOwner) instanceof TLRPC.TL_messageMediaInvoice)) { Theme.chat_replyTextPaint.setColor(getThemedColor(Theme.key_chat_outReplyMessageText)); } else { - Theme.chat_replyTextPaint.setColor(getThemedColor(isDrawSelectionBackground() ? Theme.key_chat_outReplyMediaMessageSelectedText : Theme.key_chat_outReplyMediaMessageText)); + int color = getThemedColor(isDrawSelectionBackground() ? Theme.key_chat_outReplyMediaMessageSelectedText : Theme.key_chat_outReplyMediaMessageText); + Theme.chat_replyTextPaint.setColor(ColorUtils.blendARGB(color, Theme.adaptHue(color, Theme.chat_replyNamePaint.getColor()), replyPressedT)); } } else { if (currentReplyUserId == 0) { @@ -14038,7 +14501,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (currentMessageObject.hasValidReplyMessageObject() && (currentMessageObject.replyMessageObject.type == MessageObject.TYPE_TEXT || !TextUtils.isEmpty(currentMessageObject.replyMessageObject.caption)) && !(MessageObject.getMedia(currentMessageObject.replyMessageObject.messageOwner) instanceof TLRPC.TL_messageMediaGame || MessageObject.getMedia(currentMessageObject.replyMessageObject.messageOwner) instanceof TLRPC.TL_messageMediaInvoice)) { Theme.chat_replyTextPaint.setColor(getThemedColor(Theme.key_chat_inReplyMessageText)); } else { - Theme.chat_replyTextPaint.setColor(getThemedColor(isDrawSelectionBackground() ? Theme.key_chat_inReplyMediaMessageSelectedText : Theme.key_chat_inReplyMediaMessageText)); + int color = getThemedColor(isDrawSelectionBackground() ? Theme.key_chat_inReplyMediaMessageSelectedText : Theme.key_chat_inReplyMediaMessageText); + Theme.chat_replyTextPaint.setColor(ColorUtils.blendARGB(color, Theme.adaptHue(color, Theme.chat_replyNamePaint.getColor()), replyPressedT)); } } } @@ -14051,9 +14515,9 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate restoreToCount = canvas.saveLayerAlpha(AndroidUtilities.rectTmp, (int) (0xFF * getAlpha() * replyForwardAlpha), Canvas.ALL_SAVE_FLAG); } - int leftRad, rightRad, bottomRad = AndroidUtilities.dp(Math.min(2, SharedConfig.bubbleRadius)); + float leftRad, rightRad, bottomRad = Math.min(6.75f, SharedConfig.bubbleRadius); if (currentMessageObject.shouldDrawWithoutBackground()) { - leftRad = rightRad = bottomRad = AndroidUtilities.dp(3); + leftRad = rightRad = bottomRad = 9; replySelectorRect.set( (int) (replyStartX - AndroidUtilities.dp(7)), (int) (replyStartY - AndroidUtilities.dp(7)), @@ -14064,22 +14528,22 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (drawTopic || drawNameLayout || (drawForwardedName && forwardedNameLayout[0] != null)) { leftRad = bottomRad; } else if (currentMessageObject.isOutOwner() || !drawPinnedTop) { - leftRad = AndroidUtilities.dp(SharedConfig.bubbleRadius / 3f); + leftRad = SharedConfig.bubbleRadius * .75f; } else { - leftRad = AndroidUtilities.dp(Math.min(6, SharedConfig.bubbleRadius) / 3f); + leftRad = Math.min(6, SharedConfig.bubbleRadius); } if (drawTopic || drawNameLayout || (drawForwardedName && forwardedNameLayout[0] != null)) { rightRad = bottomRad; } else if (!currentMessageObject.isOutOwner() || !drawPinnedTop) { - rightRad = AndroidUtilities.dp(SharedConfig.bubbleRadius / 3f); + rightRad = SharedConfig.bubbleRadius * .75f; } else { - rightRad = AndroidUtilities.dp(Math.min(6, SharedConfig.bubbleRadius) / 3f); + rightRad = Math.min(6, SharedConfig.bubbleRadius) / 3f; } replySelectorRect.set( - (int) (getCurrentBackgroundLeft() + AndroidUtilities.dp(6 + (!currentMessageObject.isOutOwner() && !mediaBackground && !drawPinnedBottom ? 6 : 0)) + getExtraTextX()), - (int) (replyStartY - AndroidUtilities.dp(drawNameLayout ? 3 : (3 + (!mediaBackground && drawPinnedTop ? 2 : 0)))), - (int) ((currentMessagesGroup != null ? replyStartX + Math.max(replyNameWidth, replyTextWidth) + AndroidUtilities.dp(10) : getBackgroundDrawableRight()) - AndroidUtilities.dp(6 + (currentMessageObject.isOutOwner() && !mediaBackground && !drawPinnedBottom ? 6 : 0)) - getExtraTextX()), - (int) (replyStartY + replyHeight + AndroidUtilities.dp(2)) + (int) (getCurrentBackgroundLeft() + AndroidUtilities.dp(5 + (!currentMessageObject.isOutOwner() && !mediaBackground && !drawPinnedBottom ? 6 : 0)) + getExtraTextX()), + (int) (replyStartY - AndroidUtilities.dp(drawNameLayout ? 4 : (4 + (!mediaBackground && drawPinnedTop ? 2 : 0)) - (drawForwardedName && forwardedNameLayout[0] != null ? 2 : 0))), + (int) ((currentMessagesGroup != null ? replyStartX + Math.max(replyNameWidth, replyTextWidth) + AndroidUtilities.dp(10) : getBackgroundDrawableRight()) - AndroidUtilities.dp(5 + (currentMessageObject.isOutOwner() && !mediaBackground && !drawPinnedBottom ? 6 : 0)) - getExtraTextX()), + (int) (replyStartY + replyHeight + AndroidUtilities.dp(4)) ); } if (replySelector == null) { @@ -14093,15 +14557,102 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (rippleColor != replySelectorColor) { Theme.setSelectorDrawableColor(replySelector, replySelectorColor = rippleColor, true); } + if (loading) { + if (replyLoadingDrawable == null) { + replyLoadingDrawable = new LoadingDrawable(); + replyLoadingDrawable.setAppearByGradient(true); + replyLoadingDrawable.setGradientScale(3.5f); + replyLoadingDrawable.setSpeed(.5f); + } + + int a = Color.alpha(rippleColor); + replyLoadingDrawable.setColors( + ColorUtils.setAlphaComponent(rippleColor, MathUtils.clamp(a / 2, 0, 0xFF)), + ColorUtils.setAlphaComponent(rippleColor, MathUtils.clamp(a * 2, 0, 0xFF)), + ColorUtils.setAlphaComponent(rippleColor, MathUtils.clamp(a / 2, 0, 0xFF)), + ColorUtils.setAlphaComponent(rippleColor, 0xFF) + ); + + replyLoadingDrawable.setBounds(replySelectorRect); + replyLoadingDrawable.setRadiiDp(leftRad, rightRad, bottomRad, bottomRad); + replyLoadingDrawable.strokePaint.setStrokeWidth(AndroidUtilities.dp(1)); + + replyLoadingDrawable.draw(canvas); + } else if (replyLoadingDrawable != null) { + replyLoadingDrawable.reset(); + } replySelector.draw(canvas); - AndroidUtilities.rectTmp.set(replyStartX, replyStartY, replyStartX + AndroidUtilities.dp(2), replyStartY + replyHeight); + AndroidUtilities.rectTmp.set(replyStartX, replyStartY, replyStartX + AndroidUtilities.dp(3), replyStartY + replyHeight); + if (replyRoundRectPath == null) { + replyRoundRectPath = new Path(); + } else { + replyRoundRectPath.rewind(); + } + if (replyRoundRectRadii == null) { + replyRoundRectRadii = new float[8]; + replyRoundRectRadii[0] = replyRoundRectRadii[1] = replyRoundRectRadii[6] = replyRoundRectRadii[7] = AndroidUtilities.dp(2); // left + replyRoundRectRadii[2] = replyRoundRectRadii[3] = replyRoundRectRadii[4] = replyRoundRectRadii[5] = AndroidUtilities.dp(1); // right + } + replyRoundRectPath.addRoundRect(AndroidUtilities.rectTmp, replyRoundRectRadii, Path.Direction.CW); + int wasAlpha = Theme.chat_replyLinePaint.getAlpha(); + if (loading) { + Theme.chat_replyLinePaint.setAlpha((int) (wasAlpha * .3f)); + canvas.drawPath(replyRoundRectPath, Theme.chat_replyLinePaint); + Theme.chat_replyLinePaint.setAlpha(wasAlpha); - canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(1), AndroidUtilities.dp(1), Theme.chat_replyLinePaint); + replyRoundRectPath.rewind(); + replyLoadingT += 1000f / AndroidUtilities.screenRefreshRate; + if (replyLoadingSegment == null) { + replyLoadingSegment = new float[2]; + } + + float x = (float) Math.pow(replyLoadingT / 120f / 4f, .85f) * 4f; + final float from = MathUtils.clamp(.5f * ((Math.max(x, .5f) + 1.5f) % 3.5f), 0, 1); + final float to = MathUtils.clamp(.5f * (((x + 1.5f) % 3.5f) - 1.5f), 0, 1); + + AndroidUtilities.rectTmp.set( + replyStartX, + replyStartY + replyHeight * (1f - CubicBezierInterpolator.EASE_IN.getInterpolation(from)), + replyStartX + AndroidUtilities.dp(3), + replyStartY + replyHeight * (1f - CubicBezierInterpolator.EASE_OUT.getInterpolation(to)) + ); + replyRoundRectPath.addRoundRect(AndroidUtilities.rectTmp, replyRoundRectRadii, Path.Direction.CW); + canvas.drawPath(replyRoundRectPath, Theme.chat_replyLinePaint); + + invalidate(); + } else { + replyLoadingT = 0; + canvas.drawPath(replyRoundRectPath, Theme.chat_replyLinePaint); + } + Theme.chat_replyLinePaint.setAlpha(wasAlpha); if (needReplyImage) { replyImageReceiver.setAlpha(replyForwardAlpha); replyImageReceiver.setImageCoords(replyStartX + offset, replyStartY, replyHeight, replyHeight); replyImageReceiver.draw(canvas); + + if (currentMessageObject != null && currentMessageObject.hasValidReplyMessageObject() && currentMessageObject.replyMessageObject.hasMediaSpoilers()) { + int[] rad = replyImageReceiver.getRoundRadius(); + mediaSpoilerRadii[0] = mediaSpoilerRadii[1] = rad[0]; + mediaSpoilerRadii[2] = mediaSpoilerRadii[3] = rad[1]; + mediaSpoilerRadii[4] = mediaSpoilerRadii[5] = rad[2]; + mediaSpoilerRadii[6] = mediaSpoilerRadii[7] = rad[3]; + + mediaSpoilerPath.rewind(); + AndroidUtilities.rectTmp.set(replyImageReceiver.getImageX(), replyImageReceiver.getImageY(), replyImageReceiver.getImageX2(), replyImageReceiver.getImageY2()); + mediaSpoilerPath.addRoundRect(AndroidUtilities.rectTmp, mediaSpoilerRadii, Path.Direction.CW); + + canvas.save(); + canvas.clipPath(mediaSpoilerPath); + + int sColor = Color.WHITE; + mediaSpoilerEffect.setColor(ColorUtils.setAlphaComponent(sColor, (int) (Color.alpha(sColor) * 0.325f * replyImageReceiver.getAlpha()))); + mediaSpoilerEffect.setBounds((int) replyImageReceiver.getImageX(), (int) replyImageReceiver.getImageY(), (int) replyImageReceiver.getImageX2(), (int) replyImageReceiver.getImageY2()); + mediaSpoilerEffect.draw(canvas); + canvas.restore(); + + invalidate(); + } } if (replyNameLayout != null) { @@ -14558,7 +15109,6 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate float captionY = this.captionY; float captionX = this.captionX; - if (transitionParams.animateBackgroundBoundsInner) { if (transitionParams.transformGroupToSingleMessage) { captionY -= getTranslationY(); @@ -14608,11 +15158,28 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate if (links.draw(canvas)) { invalidate(); } + drawProgressLoadingLink(canvas, -1); if (!urlPathSelection.isEmpty()) { for (int b = 0; b < urlPathSelection.size(); b++) { canvas.drawPath(urlPathSelection.get(b), Theme.chat_textSearchSelectionPaint); } } + if (highlightPath != null) { + float t = (System.currentTimeMillis() - highlightPathStart) / 850f; + if (t > 1) { + highlightPath = null; + } else { + int wasAlpha = Theme.chat_textSearchSelectionPaint.getAlpha(); + Theme.chat_textSearchSelectionPaint.setAlpha((int) (wasAlpha * .8f * (4 * t * (1 - t)))); + canvas.save(); + float s = .4f + .6f * AndroidUtilities.overshootInterpolator.getInterpolation(Math.min(1, t * 4f)); + canvas.scale(s, s, highlightPath.centerX, highlightPath.centerY); + canvas.drawPath(highlightPath, Theme.chat_textSearchSelectionPaint); + canvas.restore(); + Theme.chat_textSearchSelectionPaint.setAlpha(wasAlpha); + } + invalidate(); + } if (currentMessageObject.type == MessageObject.TYPE_ROUND_VIDEO) { Theme.chat_timePaint.setColor( ColorUtils.blendARGB( @@ -14649,6 +15216,110 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate canvas.restore(); } + public void drawProgressLoadingLink(Canvas canvas, int blockNum) { + updateProgressLoadingLink(); + if (progressLoadingLinkDrawables == null || progressLoadingLinkDrawables.isEmpty()) { + return; + } + int color = getThemedColor(currentMessageObject != null && currentMessageObject.isOutOwner() ? Theme.key_chat_outLinkSelectBackground : Theme.key_chat_linkSelectBackground); + for (int i = 0; i < progressLoadingLinkDrawables.size(); ++i) { + LoadingDrawableLocation location = progressLoadingLinkDrawables.get(i); + if (location.blockNum == blockNum) { + LoadingDrawable drawable = location.drawable; + drawable.setColors( + Theme.multAlpha(color, .85f), + Theme.multAlpha(color, 2f), + Theme.multAlpha(color, 3.5f), + Theme.multAlpha(color, 6f) + ); + drawable.draw(canvas); + invalidate(); + if (drawable.isDisappeared()) { + progressLoadingLinkDrawables.remove(i); + i--; + } + } + } + } + + public void updateProgressLoadingLink() { + if (delegate == null) { + return; + } + if (!delegate.isProgressLoading(this, ChatActivity.PROGRESS_LINK)) { + progressLoadingLink = null; + if (progressLoadingLinkDrawables != null && !progressLoadingLinkDrawables.isEmpty()) { + for (int i = 0; i < progressLoadingLinkDrawables.size(); ++i) { + LoadingDrawableLocation location = progressLoadingLinkDrawables.get(i); + if (!location.drawable.isDisappearing()) { + location.drawable.disappear(); + } + } + } + return; + } + + CharacterStyle link = delegate.getProgressLoadingLink(this); + if (link == progressLoadingLink) { + return; + } + progressLoadingLink = link; + if (progressLoadingLinkCurrentDrawable != null) { + progressLoadingLinkCurrentDrawable.disappear(); + progressLoadingLinkCurrentDrawable = null; + } + progressLoadingLinkCurrentDrawable = new LoadingDrawable(); + progressLoadingLinkCurrentDrawable.setAppearByGradient(true); + LinkPath path = new LinkPath(true); + progressLoadingLinkCurrentDrawable.usePath(path); + progressLoadingLinkCurrentDrawable.setRadiiDp(5); + LoadingDrawableLocation location = new LoadingDrawableLocation(); + location.drawable = progressLoadingLinkCurrentDrawable; + location.blockNum = -3; + if (progressLoadingLinkDrawables == null) { + progressLoadingLinkDrawables = new ArrayList<>(); + } + progressLoadingLinkDrawables.add(location); + if (progressLoadingLink != null) { + final int count = Math.max(0, currentMessageObject != null && currentMessageObject.textLayoutBlocks != null ? currentMessageObject.textLayoutBlocks.size() : 0); + for (int i = -2; i < count; ++i) { + float yOffset = 0; + Layout layout; + if (i == -2) { + layout = descriptionLayout; + } else if (i == -1) { + layout = captionLayout; + } else { + layout = currentMessageObject.textLayoutBlocks.get(i).textLayout; + yOffset = currentMessageObject.textLayoutBlocks.get(i).textYOffset; + } + + if (layout != null && layout.getText() instanceof Spanned) { + Spanned spanned = (Spanned) layout.getText(); + CharacterStyle[] spans = spanned.getSpans(0, spanned.length(), CharacterStyle.class); + if (spans != null) { + for (int j = 0; j < spans.length; ++j) { + if (spans[j] == progressLoadingLink) { + location.blockNum = i; + break; + } + } + } + + if (location.blockNum == i) { + path.rewind(); + int start = spanned.getSpanStart(progressLoadingLink); + int end = spanned.getSpanEnd(progressLoadingLink); + path.setCurrentLayout(layout, start, yOffset); + layout.getSelectionPath(start, end, path); + progressLoadingLinkCurrentDrawable.updateBounds(); + return; + } + } + } + } + } + public boolean needDrawTime() { return !forceNotDrawTime; } @@ -16389,11 +17060,17 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate canvas.scale(scale, scale, radialProgress.getProgressRect().centerX(), radialProgress.getProgressRect().centerY()); restore = true; } - if ((!isRoundVideo || !hasLinkPreview) && (!currentMessageObject.needDrawBluredPreview() || !MediaController.getInstance().isPlayingMessage(currentMessageObject))) { + if ((!isRoundVideo || !hasLinkPreview) && (!currentMessageObject.needDrawBluredPreview() || !MediaController.getInstance().isPlayingMessage(currentMessageObject)) && !(currentMessageObject.hasMediaSpoilers() && (!currentMessageObject.isMediaSpoilersRevealed || !currentMessageObject.revealingMediaSpoilers) && SharedConfig.autoplayVideo && currentMessagesGroup == null && (radialProgress.getIcon() == MediaActionDrawable.ICON_PLAY || radialProgress.getIcon() == MediaActionDrawable.ICON_NONE))) { if (isRoundVideo && !on) { radialProgress.overrideCircleAlpha = .25f + .75f * (1f - getVideoTranscriptionProgress()); } + if ((!SharedConfig.autoplayVideo || currentMessagesGroup != null) && currentMessageObject.hasMediaSpoilers() && !currentMessageObject.isMediaSpoilersRevealed && radialProgress.getIcon() == MediaActionDrawable.ICON_PLAY) { + canvas.saveLayerAlpha(radialProgress.getProgressRect(), (int) (mediaSpoilerRevealProgress * 0xFF), Canvas.ALL_SAVE_FLAG); + } radialProgress.draw(canvas); + if ((!SharedConfig.autoplayVideo || currentMessagesGroup != null) && currentMessageObject.hasMediaSpoilers() && !currentMessageObject.isMediaSpoilersRevealed && radialProgress.getIcon() == MediaActionDrawable.ICON_PLAY) { + canvas.restore(); + } if (isRoundVideo && !on) { radialProgress.overrideCircleAlpha = 1f; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/CheckBoxCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/CheckBoxCell.java index 8af9ecbef..757e14bd0 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/CheckBoxCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/CheckBoxCell.java @@ -10,19 +10,27 @@ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; +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.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; +import android.widget.LinearLayout; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.Emoji; import org.telegram.messenger.LocaleController; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.CheckBox2; import org.telegram.ui.Components.CheckBoxSquare; +import org.telegram.ui.Components.CubicBezierInterpolator; import org.telegram.ui.Components.LayoutHelper; public class CheckBoxCell extends FrameLayout { @@ -40,6 +48,12 @@ public class CheckBoxCell extends FrameLayout { private int currentType; private int checkBoxSize = 18; + private LinearLayout contentView; + + private Boolean collapsed; + private View collapsedArrow; + private boolean collapseArrowSet; + public CheckBoxCell(Context context, int type) { this(context, type, 17, null); } @@ -54,7 +68,20 @@ public class CheckBoxCell extends FrameLayout { currentType = type; - textView = new TextView(context); + textView = new TextView(context) { + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + updateCollapseArrowTranslation(); + } + + @Override + public void setText(CharSequence text, BufferType type) { + text = Emoji.replaceEmoji(text, getPaint().getFontMetricsInt(), false); + super.setText(text, type); + } + }; + NotificationCenter.listenEmojiLoading(textView); textView.setTag(getThemedColor(type == 1 || type == 5 ? Theme.key_dialogTextBlack : Theme.key_windowBackgroundWhiteBlackText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setLines(1); @@ -71,7 +98,7 @@ public class CheckBoxCell extends FrameLayout { addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, (LocaleController.isRTL ? 8 : 29), 0, (LocaleController.isRTL ? 29 : 8), 0)); } else { int offset = type == 4 ? 56 : 46; - addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, (LocaleController.isRTL ? padding : offset + (padding - 17)), 0, (LocaleController.isRTL ? offset + (padding - 17) : padding), 0)); + addView(textView, LayoutHelper.createFrame(type == 4 ? LayoutHelper.WRAP_CONTENT : LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, (LocaleController.isRTL ? padding : offset + (padding - 17)), 0, (LocaleController.isRTL ? offset + (padding - 17) : padding), 0)); } } @@ -114,11 +141,82 @@ public class CheckBoxCell extends FrameLayout { valueTextView.setTextColor(getThemedColor(currentType == 1 || currentType == 5 ? Theme.key_dialogTextBlue : Theme.key_windowBackgroundWhiteValueText)); } + private View click1Container, click2Container; + public void setOnSectionsClickListener(OnClickListener onTextClick, OnClickListener onCheckboxClick) { + if (onTextClick == null) { + if (click1Container != null) { + removeView(click1Container); + click1Container = null; + } + } else { + if (click1Container == null) { + click1Container = new View(getContext()); + click1Container.setBackground(Theme.createSelectorDrawable(getThemedColor(Theme.key_listSelector), Theme.RIPPLE_MASK_ALL)); + addView(click1Container, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.FILL)); + } + click1Container.setOnClickListener(onTextClick); + } + + if (onCheckboxClick == null) { + if (click2Container != null) { + removeView(click2Container); + click2Container = null; + } + } else { + if (click2Container == null) { + click2Container = new View(getContext()); + addView(click2Container, LayoutHelper.createFrame(56, LayoutHelper.MATCH_PARENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); + } + click2Container.setOnClickListener(onCheckboxClick); + } + } + + public void setCollapsed(Boolean collapsed) { + if (collapsed == null) { + if (collapsedArrow != null) { + removeView(collapsedArrow); + collapsedArrow = null; + } + } else { + if (collapsedArrow == null) { + collapsedArrow = new View(getContext()); + Drawable drawable = getContext().getResources().getDrawable(R.drawable.arrow_more).mutate(); + drawable.setColorFilter(new PorterDuffColorFilter(getThemedColor(Theme.key_windowBackgroundWhiteBlackText), PorterDuff.Mode.MULTIPLY)); + collapsedArrow.setBackground(drawable); + addView(collapsedArrow, LayoutHelper.createFrame(16, 16, Gravity.CENTER_VERTICAL)); + } + + updateCollapseArrowTranslation(); + collapsedArrow.animate().cancel(); + collapsedArrow.animate().rotation(collapsed ? 0 : 180).setDuration(340).setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT).start(); + } + this.collapsed = collapsed; + } + + private void updateCollapseArrowTranslation() { + if (collapsedArrow == null) { + return; + } + + float textWidth = 0; + try { + textWidth = textView.getMeasuredWidth(); + } catch (Exception e) {} + + float translateX; + if (LocaleController.isRTL) { + translateX = textView.getRight() - textWidth - AndroidUtilities.dp(20); + } else { + translateX = textView.getLeft() + textWidth + AndroidUtilities.dp(4); + } + collapsedArrow.setTranslationX(translateX); + collapseArrowSet = true; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); if (currentType == 3) { - int width = MeasureSpec.getSize(widthMeasureSpec); - valueTextView.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(10), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(50), MeasureSpec.EXACTLY)); textView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(34), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(50), MeasureSpec.EXACTLY)); checkBox.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(checkBoxSize), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(checkBoxSize), MeasureSpec.EXACTLY)); @@ -129,12 +227,29 @@ public class CheckBoxCell extends FrameLayout { } else { setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), AndroidUtilities.dp(50) + (needDivider ? 1 : 0)); - int availableWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - AndroidUtilities.dp(34); + int availableWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - AndroidUtilities.dp(currentType == TYPE_CHECK_BOX_ROUND ? 60 : 34); + if (valueTextView.getLayoutParams() instanceof MarginLayoutParams) { + availableWidth -= ((MarginLayoutParams) valueTextView.getLayoutParams()).rightMargin; + } valueTextView.measure(MeasureSpec.makeMeasureSpec(availableWidth / 2, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); - textView.measure(MeasureSpec.makeMeasureSpec(availableWidth - valueTextView.getMeasuredWidth() - AndroidUtilities.dp(8), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); + textView.measure(MeasureSpec.makeMeasureSpec(availableWidth - (int) Math.abs(textView.getTranslationX()) - valueTextView.getMeasuredWidth() - AndroidUtilities.dp(8), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); checkBox.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(checkBoxSize), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(checkBoxSize), MeasureSpec.EXACTLY)); } + + if (click1Container != null) { + MarginLayoutParams margin = (MarginLayoutParams) click1Container.getLayoutParams(); + click1Container.measure(MeasureSpec.makeMeasureSpec(width - margin.leftMargin - margin.rightMargin, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(50), MeasureSpec.EXACTLY)); + } + if (click2Container != null) { + click2Container.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(56), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(50), MeasureSpec.EXACTLY)); + } + if (collapsedArrow != null) { + collapsedArrow.measure( + MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(16), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(16), MeasureSpec.EXACTLY) + ); + } } public void setTextColor(int color) { @@ -151,6 +266,21 @@ public class CheckBoxCell extends FrameLayout { valueTextView.setText(value); needDivider = divider; setWillNotDraw(!divider); + collapseArrowSet = false; + } + + public void setPad(int pad) { + int offset = AndroidUtilities.dp(pad * 40 * (LocaleController.isRTL ? -1 : 1)); + if (checkBox != null) { + checkBox.setTranslationX(offset); + } + textView.setTranslationX(offset); + if (click1Container != null) { + click1Container.setTranslationX(offset); + } + if (click2Container != null) { + click2Container.setTranslationX(offset); + } } public void setNeedDivider(boolean needDivider){ @@ -238,8 +368,8 @@ public class CheckBoxCell extends FrameLayout { @Override protected void onDraw(Canvas canvas) { if (needDivider) { - int offset = currentType == TYPE_CHECK_BOX_ROUND ? 60 : 20; - canvas.drawLine(LocaleController.isRTL ? 0 : AndroidUtilities.dp(offset), getMeasuredHeight() - 1, getMeasuredWidth() - (LocaleController.isRTL ? AndroidUtilities.dp(offset) : 0), getMeasuredHeight() - 1, Theme.dividerPaint); + int offset = AndroidUtilities.dp(currentType == TYPE_CHECK_BOX_ROUND ? 60 : 20) + (int) Math.abs(textView.getTranslationX()); + canvas.drawLine(LocaleController.isRTL ? 0 : offset, getMeasuredHeight() - 1, getMeasuredWidth() - (LocaleController.isRTL ? offset : 0), getMeasuredHeight() - 1, Theme.dividerPaint); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java index b50be8a88..4771395ad 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java @@ -17,6 +17,7 @@ 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.PorterDuffXfermode; import android.graphics.RectF; @@ -76,6 +77,7 @@ import org.telegram.ui.Adapters.DialogsAdapter; import org.telegram.ui.Components.AnimatedEmojiDrawable; import org.telegram.ui.Components.AnimatedEmojiSpan; import org.telegram.ui.Components.AvatarDrawable; +import org.telegram.ui.Components.BubbleCounterPath; import org.telegram.ui.Components.CanvasButton; import org.telegram.ui.Components.CheckBox2; import org.telegram.ui.Components.ColoredImageSpan; @@ -153,6 +155,9 @@ public class DialogCell extends BaseCell { private Paint timerPaint; private Paint timerPaint2; + private Path thumbPath = new Path(); + private SpoilerEffect thumbSpoiler = new SpoilerEffect(); + public void setMoving(boolean moving) { this.moving = moving; } @@ -323,6 +328,7 @@ public class DialogCell extends BaseCell { private Paint thumbBackgroundPaint; private ImageReceiver[] thumbImage = new ImageReceiver[3]; private boolean[] drawPlay = new boolean[3]; + private boolean[] drawSpoiler = new boolean[3]; public ImageReceiver avatarImage = new ImageReceiver(this); private AvatarDrawable avatarDrawable = new AvatarDrawable(); @@ -450,6 +456,8 @@ public class DialogCell extends BaseCell { private RectF rect = new RectF(); private DialogsAdapter.DialogsPreloader preloader; + private Path counterPath; + private RectF counterPathRect; private int animateToStatusDrawableParams; private int animateFromStatusDrawableParams; @@ -469,7 +477,6 @@ public class DialogCell extends BaseCell { private final DialogUpdateHelper updateHelper = new DialogUpdateHelper(); public static class BounceInterpolator implements Interpolator { - public float getInterpolation(float t) { if (t < 0.33f) { return 0.1f * (t / 0.33f); @@ -591,7 +598,7 @@ public class DialogCell extends BaseCell { } private void checkTtl() { - showTtl = ttlPeriod > 0 && !hasCall && !isOnline(); + showTtl = ttlPeriod > 0 && !hasCall && !isOnline() && !(checkBox != null && checkBox.isChecked()); ttlProgress = showTtl ? 1.0f : 0.0f; } @@ -847,7 +854,8 @@ public class DialogCell extends BaseCell { return; } if (isDialogCell) { - if (!updateHelper.update()) { + boolean needUpdate = updateHelper.update(); + if (!needUpdate && currentDialogFolderId == 0) { return; } } @@ -1187,7 +1195,7 @@ public class DialogCell extends BaseCell { mess = mess.substring(0, 150); } Spannable messSpan = new SpannableStringBuilder(mess); - MediaDataController.addTextStyleRuns(draftMessage, messSpan, TextStyleSpan.FLAG_STYLE_SPOILER); + MediaDataController.addTextStyleRuns(draftMessage, messSpan, TextStyleSpan.FLAG_STYLE_SPOILER | TextStyleSpan.FLAG_STYLE_STRIKE); if (draftMessage != null && draftMessage.entities != null) { MediaDataController.addAnimatedEmojiSpans(draftMessage.entities, messSpan, currentMessagePaint == null ? null : currentMessagePaint.getFontMetricsInt()); } @@ -1307,6 +1315,10 @@ public class DialogCell extends BaseCell { messageString = msgText; } currentMessagePaint = Theme.dialogs_messagePrintingPaint[paintIndex]; + if (message.type == MessageObject.TYPE_SUGGEST_PHOTO) { + updateMessageThumbs(); + messageString = applyThumbs(messageString); + } } else { needEmoji = true; updateMessageThumbs(); @@ -1411,7 +1423,7 @@ public class DialogCell extends BaseCell { } else { SpannableStringBuilder msgBuilder = new SpannableStringBuilder(message.caption); if (message != null && message.messageOwner != null) { - MediaDataController.addTextStyleRuns(message.messageOwner.entities, message.caption, msgBuilder, TextStyleSpan.FLAG_STYLE_SPOILER); + MediaDataController.addTextStyleRuns(message.messageOwner.entities, message.caption, msgBuilder, TextStyleSpan.FLAG_STYLE_SPOILER | TextStyleSpan.FLAG_STYLE_STRIKE); MediaDataController.addAnimatedEmojiSpans(message.messageOwner.entities, msgBuilder, currentMessagePaint == null ? null : currentMessagePaint.getFontMetricsInt()); } messageString = new SpannableStringBuilder(emoji).append(msgBuilder); @@ -1443,7 +1455,7 @@ public class DialogCell extends BaseCell { messageString = AndroidUtilities.ellipsizeCenterEnd(messageString, message.highlightedWords.get(0), w, currentMessagePaint, 130).toString(); } else { SpannableStringBuilder stringBuilder = new SpannableStringBuilder(msgText); - MediaDataController.addTextStyleRuns(message, stringBuilder, TextStyleSpan.FLAG_STYLE_SPOILER); + MediaDataController.addTextStyleRuns(message, stringBuilder, TextStyleSpan.FLAG_STYLE_SPOILER | TextStyleSpan.FLAG_STYLE_STRIKE); if (message != null && message.messageOwner != null) { MediaDataController.addAnimatedEmojiSpans(message.messageOwner.entities, stringBuilder, currentMessagePaint == null ? null : currentMessagePaint.getFontMetricsInt()); } @@ -2457,6 +2469,7 @@ public class DialogCell extends BaseCell { addView(checkBox); } checkBox.setChecked(checked, animated); + checkTtl(); } private MessageObject findFolderTopMessage() { @@ -3630,6 +3643,21 @@ public class DialogCell extends BaseCell { thumbBackgroundPaint ); thumbImage[i].draw(canvas); + if (drawSpoiler[i]) { + thumbPath.rewind(); + thumbPath.addRoundRect(AndroidUtilities.rectTmp, thumbImage[i].getRoundRadius()[0], thumbImage[i].getRoundRadius()[1], Path.Direction.CW); + + canvas.save(); + canvas.clipPath(thumbPath); + + int sColor = Color.WHITE; + thumbSpoiler.setColor(ColorUtils.setAlphaComponent(sColor, (int) (Color.alpha(sColor) * 0.325f))); + thumbSpoiler.setBounds((int) thumbImage[i].getImageX(), (int) thumbImage[i].getImageY(), (int) thumbImage[i].getImageX2(), (int) thumbImage[i].getImageY2()); + thumbSpoiler.draw(canvas); + invalidate(); + + canvas.restore(); + } if (drawPlay[i]) { int x = (int) (thumbImage[i].getCenterX() - Theme.dialogs_playDrawable.getIntrinsicWidth() / 2); int y = (int) (thumbImage[i].getCenterY() - Theme.dialogs_playDrawable.getIntrinsicHeight() / 2); @@ -3699,6 +3727,9 @@ public class DialogCell extends BaseCell { } canvas.save(); float s = ttlProgress * (1f - rightFragmentOpenedProgress); + if (checkBox != null) { + s *= 1f - checkBox.getProgress(); + } canvas.scale(s, s, left, top); canvas.drawCircle(left, top, AndroidUtilities.dpf2(11f), timerPaint); canvas.drawCircle(left, top, AndroidUtilities.dpf2(11f), timerPaint2); @@ -3766,6 +3797,9 @@ public class DialogCell extends BaseCell { float size1; float size2; + if (SharedConfig.getLiteMode().enabled()) { + innerProgress = 0.65f; + } if (progressStage == 0) { size1 = AndroidUtilities.dp(1) + AndroidUtilities.dp(4) * innerProgress; size2 = AndroidUtilities.dp(3) - AndroidUtilities.dp(2) * innerProgress; @@ -3808,15 +3842,17 @@ public class DialogCell extends BaseCell { canvas.restore(); } - innerProgress += 16f / 400.0f; - if (innerProgress >= 1.0f) { - innerProgress = 0.0f; - progressStage++; - if (progressStage >= 8) { - progressStage = 0; + if (!SharedConfig.getLiteMode().enabled()) { + innerProgress += 16f / 400.0f; + if (innerProgress >= 1.0f) { + innerProgress = 0.0f; + progressStage++; + if (progressStage >= 8) { + progressStage = 0; + } } + needInvalidate = true; } - needInvalidate = true; if (hasCall) { if (chatCallProgress < 1.0f) { @@ -4004,6 +4040,7 @@ public class DialogCell extends BaseCell { } private void drawCounter(Canvas canvas, boolean drawCounterMuted, int countTop, int countLeftLocal, int countLeftOld, float globalScale, boolean outline) { + final boolean drawBubble = isForumCell() || isFolderCell(); if (drawCount && drawCount2 || countChangeProgress != 1f) { final float progressFinal = (unreadCount == 0 && !markUnread) ? 1f - countChangeProgress : countChangeProgress; Paint paint; @@ -4014,6 +4051,8 @@ public class DialogCell extends BaseCell { counterPaintOutline = new Paint(); counterPaintOutline.setStyle(Paint.Style.STROKE); counterPaintOutline.setStrokeWidth(AndroidUtilities.dp(2)); + counterPaintOutline.setStrokeJoin(Paint.Join.ROUND); + counterPaintOutline.setStrokeCap(Paint.Cap.ROUND); } int color = Theme.getColor(Theme.key_chats_pinnedOverlay); counterPaintOutline.setColor(ColorUtils.blendARGB( @@ -4060,9 +4099,27 @@ public class DialogCell extends BaseCell { canvas.scale(progressFinal, progressFinal, rect.centerX(), rect.centerY()); } - canvas.drawRoundRect(rect, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, paint); - if (outline) { - canvas.drawRoundRect(rect, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, counterPaintOutline); + if (drawBubble) { + if (counterPath == null || counterPathRect == null || !counterPathRect.equals(rect)) { + if (counterPathRect == null) { + counterPathRect = new RectF(rect); + } else { + counterPathRect.set(rect); + } + if (counterPath == null) { + counterPath = new Path(); + } + BubbleCounterPath.addBubbleRect(counterPath, counterPathRect, AndroidUtilities.dp(11.5f)); + } + canvas.drawPath(counterPath, paint); + if (outline) { + canvas.drawPath(counterPath, counterPaintOutline); + } + } else { + canvas.drawRoundRect(rect, AndroidUtilities.dp(11.5f), AndroidUtilities.dp(11.5f), paint); + if (outline) { + canvas.drawRoundRect(rect, AndroidUtilities.dp(11.5f), AndroidUtilities.dp(11.5f), counterPaintOutline); + } } if (drawLayout != null) { canvas.save(); @@ -4092,12 +4149,29 @@ public class DialogCell extends BaseCell { scale += 0.1f * CubicBezierInterpolator.EASE_IN.getInterpolation((1f - (progressFinal - 0.5f) * 2)); } - canvas.save(); canvas.scale(scale * globalScale, scale * globalScale, rect.centerX(), rect.centerY()); - canvas.drawRoundRect(rect, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, paint); - if (outline) { - canvas.drawRoundRect(rect, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, counterPaintOutline); + if (drawBubble) { + if (counterPath == null || counterPathRect == null || !counterPathRect.equals(rect)) { + if (counterPathRect == null) { + counterPathRect = new RectF(rect); + } else { + counterPathRect.set(rect); + } + if (counterPath == null) { + counterPath = new Path(); + } + BubbleCounterPath.addBubbleRect(counterPath, counterPathRect, AndroidUtilities.dp(11.5f)); + } + canvas.drawPath(counterPath, paint); + if (outline) { + canvas.drawPath(counterPath, counterPaintOutline); + } + } else { + canvas.drawRoundRect(rect, AndroidUtilities.dp(11.5f), AndroidUtilities.dp(11.5f), paint); + if (outline) { + canvas.drawRoundRect(rect, AndroidUtilities.dp(11.5f), AndroidUtilities.dp(11.5f), counterPaintOutline); + } } if (countAnimationStableLayout != null) { canvas.save(); @@ -4424,9 +4498,11 @@ public class DialogCell extends BaseCell { hasVideoThumb = hasVideoThumb || (message.isVideo() || message.isRoundVideo()); if (thumbsCount < 3) { thumbsCount++; - drawPlay[index] = message.isVideo() || message.isRoundVideo(); + drawPlay[index] = (message.isVideo() || message.isRoundVideo()) && !message.hasMediaSpoilers(); + drawSpoiler[index] = message.hasMediaSpoilers(); int size = message.type == MessageObject.TYPE_PHOTO && selectedThumb != null ? selectedThumb.size : 0; - thumbImage[index].setImage(ImageLocation.getForObject(selectedThumb, message.photoThumbsObject), "20_20", ImageLocation.getForObject(smallThumb, message.photoThumbsObject), "20_20", size, null, message, 0); + String filter = message.hasMediaSpoilers() ? "5_5_b" : "20_20"; + thumbImage[index].setImage(ImageLocation.getForObject(selectedThumb, message.photoThumbsObject), filter, ImageLocation.getForObject(smallThumb, message.photoThumbsObject), filter, size, null, message, 0); thumbImage[index].setRoundRadius(message.isRoundVideo() ? AndroidUtilities.dp(18) : AndroidUtilities.dp(2)); needEmoji = false; } @@ -4534,7 +4610,7 @@ public class DialogCell extends BaseCell { mess = mess.subSequence(0, 150); } SpannableStringBuilder msgBuilder = new SpannableStringBuilder(mess); - MediaDataController.addTextStyleRuns(message.messageOwner.entities, mess, msgBuilder, TextStyleSpan.FLAG_STYLE_SPOILER); + MediaDataController.addTextStyleRuns(message.messageOwner.entities, mess, msgBuilder, TextStyleSpan.FLAG_STYLE_SPOILER | TextStyleSpan.FLAG_STYLE_STRIKE); if (message != null && message.messageOwner != null) { MediaDataController.addAnimatedEmojiSpans(message.messageOwner.entities, msgBuilder, currentMessagePaint == null ? null : currentMessagePaint.getFontMetricsInt()); } @@ -4616,7 +4692,7 @@ public class DialogCell extends BaseCell { mess = AndroidUtilities.replaceNewLines(mess); } mess = new SpannableStringBuilder(mess); - MediaDataController.addTextStyleRuns(message, (Spannable) mess, TextStyleSpan.FLAG_STYLE_SPOILER); + MediaDataController.addTextStyleRuns(message, (Spannable) mess, TextStyleSpan.FLAG_STYLE_SPOILER | TextStyleSpan.FLAG_STYLE_STRIKE); if (message != null && message.messageOwner != null) { MediaDataController.addAnimatedEmojiSpans(message.messageOwner.entities, mess, currentMessagePaint == null ? null : currentMessagePaint.getFontMetricsInt()); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerUserCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerUserCell.java index fcff6fb5f..1557a7847 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerUserCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerUserCell.java @@ -10,9 +10,9 @@ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; +import android.graphics.Path; import android.graphics.RectF; import android.graphics.drawable.Drawable; -import android.os.SystemClock; import android.view.Gravity; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/GroupCallUserCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/GroupCallUserCell.java index dbceb0a9b..2892eb426 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/GroupCallUserCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/GroupCallUserCell.java @@ -33,6 +33,7 @@ import org.telegram.messenger.ImageLocation; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.R; +import org.telegram.messenger.SharedConfig; import org.telegram.messenger.UserObject; import org.telegram.messenger.Utilities; import org.telegram.messenger.voip.VoIPService; @@ -983,6 +984,9 @@ public class GroupCallUserCell extends FrameLayout { } public void draw(Canvas canvas, float cx, float cy, View parentView) { + if (SharedConfig.getLiteMode().enabled()) { + return; + } float scaleBlob = 0.8f + 0.4f * amplitude; if (showWaves || wavesEnter != 0) { canvas.save(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java index b6e03a558..efe5d4e9c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java @@ -21,6 +21,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ChatObject; import org.telegram.messenger.Emoji; import org.telegram.messenger.MediaDataController; +import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.UserConfig; import org.telegram.messenger.UserObject; import org.telegram.tgnet.TLRPC; @@ -47,13 +48,20 @@ public class MentionCell extends LinearLayout { setOrientation(HORIZONTAL); avatarDrawable = new AvatarDrawable(); - avatarDrawable.setTextSize(AndroidUtilities.dp(12)); + avatarDrawable.setTextSize(AndroidUtilities.dp(18)); imageView = new BackupImageView(context); imageView.setRoundRadius(AndroidUtilities.dp(14)); addView(imageView, LayoutHelper.createLinear(28, 28, 12, 4, 0, 0)); - nameTextView = new TextView(context); + nameTextView = new TextView(context) { + @Override + public void setText(CharSequence text, BufferType type) { + text = Emoji.replaceEmoji(text, getPaint().getFontMetricsInt(), false); + super.setText(text, type); + } + }; + NotificationCenter.listenEmojiLoading(nameTextView); nameTextView.setTextColor(getThemedColor(Theme.key_windowBackgroundWhiteBlackText)); nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); nameTextView.setSingleLine(true); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoAttachPhotoCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoAttachPhotoCell.java index e2e079774..83b2d96fd 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoAttachPhotoCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoAttachPhotoCell.java @@ -12,11 +12,16 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; 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.Rect; import android.graphics.RectF; +import android.graphics.Region; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; @@ -34,9 +39,13 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; +import androidx.core.graphics.ColorUtils; +import androidx.core.math.MathUtils; + import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLoader; import org.telegram.messenger.ImageLocation; +import org.telegram.messenger.ImageReceiver; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaController; import org.telegram.messenger.MessageObject; @@ -45,7 +54,9 @@ 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.CubicBezierInterpolator; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.spoilers.SpoilerEffect; import org.telegram.ui.PhotoViewer; public class PhotoAttachPhotoCell extends FrameLayout { @@ -74,6 +85,19 @@ public class PhotoAttachPhotoCell extends FrameLayout { private AnimatorSet animator; private final Theme.ResourcesProvider resourcesProvider; + private SpoilerEffect spoilerEffect = new SpoilerEffect(); + private boolean hasSpoiler; + + private Path path = new Path(); + private float spoilerRevealX; + private float spoilerRevealY; + private float spoilerMaxRadius; + private float spoilerRevealProgress; + + private Bitmap imageViewCrossfadeSnapshot; + private Float crossfadeDuration; + private float imageViewCrossfadeProgress = 1f; + public interface PhotoAttachPhotoCellDelegate { void onCheckClick(PhotoAttachPhotoCell v); } @@ -87,7 +111,62 @@ public class PhotoAttachPhotoCell extends FrameLayout { container = new FrameLayout(context); addView(container, LayoutHelper.createFrame(80, 80)); - imageView = new BackupImageView(context); + int sColor = Color.WHITE; + spoilerEffect.setColor(ColorUtils.setAlphaComponent(sColor, (int) (Color.alpha(sColor) * 0.325f))); + imageView = new BackupImageView(context) { + private Paint crossfadePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private long lastUpdate; + + @Override + protected void onDraw(Canvas canvas) { + ImageReceiver imageReceiver = animatedEmojiDrawable != null ? animatedEmojiDrawable.getImageReceiver() : this.imageReceiver; + if (imageReceiver == null) { + return; + } + if (width != -1 && height != -1) { + imageReceiver.setImageCoords((getWidth() - width) / 2, (getHeight() - height) / 2, width, height); + blurImageReceiver.setImageCoords((getWidth() - width) / 2, (getHeight() - height) / 2, width, height); + } else { + imageReceiver.setImageCoords(0, 0, getWidth(), getHeight()); + blurImageReceiver.setImageCoords(0, 0, getWidth(), getHeight()); + } + imageReceiver.draw(canvas); + + if (hasSpoiler && spoilerRevealProgress != 1f && (photoEntry == null || !photoEntry.isAttachSpoilerRevealed)) { + if (spoilerRevealProgress != 0f) { + canvas.save(); + path.rewind(); + path.addCircle(spoilerRevealX, spoilerRevealY, spoilerMaxRadius * spoilerRevealProgress, Path.Direction.CW); + canvas.clipPath(path, Region.Op.DIFFERENCE); + } + + blurImageReceiver.draw(canvas); + spoilerEffect.setBounds(0, 0, getWidth(), getHeight()); + spoilerEffect.draw(canvas); + invalidate(); + + if (spoilerRevealProgress != 0f) { + canvas.restore(); + } + } + + if (imageViewCrossfadeProgress != 1f && imageViewCrossfadeSnapshot != null) { + crossfadePaint.setAlpha((int) (CubicBezierInterpolator.DEFAULT.getInterpolation(1f - imageViewCrossfadeProgress) * 0xFF)); + canvas.drawBitmap(imageViewCrossfadeSnapshot, 0, 0, crossfadePaint); + long dt = Math.min(16, System.currentTimeMillis() - lastUpdate); + float duration = crossfadeDuration == null ? 250f : crossfadeDuration; + imageViewCrossfadeProgress = Math.min(1f, imageViewCrossfadeProgress + dt / duration); + lastUpdate = System.currentTimeMillis(); + invalidate(); + } else if (imageViewCrossfadeProgress == 1f && imageViewCrossfadeSnapshot != null) { + imageViewCrossfadeSnapshot.recycle(); + imageViewCrossfadeSnapshot = null; + crossfadeDuration = null; + invalidate(); + } + } + }; + imageView.setBlurAllowed(true); container.addView(imageView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); videoInfoContainer = new FrameLayout(context) { @@ -110,7 +189,7 @@ public class PhotoAttachPhotoCell extends FrameLayout { videoTextView = new TextView(context); videoTextView.setTextColor(0xffffffff); - videoTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + videoTextView.setTypeface(AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_ROBOTO_MEDIUM)); videoTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); videoTextView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); videoInfoContainer.addView(videoTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.CENTER_VERTICAL, 13, -0.7f, 0, 0)); @@ -128,6 +207,60 @@ public class PhotoAttachPhotoCell extends FrameLayout { itemSize = AndroidUtilities.dp(80); } + public boolean canRevealSpoiler() { + return hasSpoiler && spoilerRevealProgress == 0f && (photoEntry == null || !photoEntry.isAttachSpoilerRevealed); + } + + public void startRevealMedia(float x, float y) { + spoilerRevealX = x; + spoilerRevealY = y; + + spoilerMaxRadius = (float) Math.sqrt(Math.pow(getWidth(), 2) + Math.pow(getHeight(), 2)); + ValueAnimator animator = ValueAnimator.ofFloat(0, 1).setDuration((long) MathUtils.clamp(spoilerMaxRadius * 0.3f, 250, 550)); + animator.setInterpolator(CubicBezierInterpolator.EASE_BOTH); + animator.addUpdateListener(animation -> { + spoilerRevealProgress = (float) animation.getAnimatedValue(); + invalidate(); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + photoEntry.isAttachSpoilerRevealed = true; + invalidate(); + } + }); + animator.start(); + } + + public void setHasSpoiler(boolean hasSpoiler) { + setHasSpoiler(hasSpoiler, null); + } + + public void setHasSpoiler(boolean hasSpoiler, Float crossfadeDuration) { + if (this.hasSpoiler != hasSpoiler) { + spoilerRevealProgress = 0f; + if (isLaidOut()) { + Bitmap prevSnapshot = imageViewCrossfadeSnapshot; + imageViewCrossfadeSnapshot = AndroidUtilities.snapshotView(imageView); + if (prevSnapshot != null) { + prevSnapshot.recycle(); + } + imageViewCrossfadeProgress = 0f; + } else { + if (imageViewCrossfadeSnapshot != null) { + imageViewCrossfadeSnapshot.recycle(); + imageViewCrossfadeSnapshot = null; + } + imageViewCrossfadeProgress = 1f; + } + + this.hasSpoiler = hasSpoiler; + this.crossfadeDuration = crossfadeDuration; + imageView.setHasBlur(hasSpoiler); + imageView.invalidate(); + } + } + public void setIsVertical(boolean value) { isVertical = value; } @@ -216,6 +349,7 @@ public class PhotoAttachPhotoCell extends FrameLayout { checkBox.setAlpha(showing ? 0.0f : 1.0f); videoInfoContainer.setAlpha(showing ? 0.0f : 1.0f); requestLayout(); + setHasSpoiler(entry.hasSpoiler); } public void setPhotoEntry(MediaController.SearchImage searchImage, boolean needCheckShow, boolean last) { @@ -254,6 +388,7 @@ public class PhotoAttachPhotoCell extends FrameLayout { checkBox.setAlpha(showing ? 0.0f : 1.0f); videoInfoContainer.setAlpha(showing ? 0.0f : 1.0f); requestLayout(); + setHasSpoiler(false); } public boolean isChecked() { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java index a9ccc10f8..4dc1997c0 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java @@ -16,11 +16,13 @@ import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; +import android.view.MotionEvent; import android.view.accessibility.AccessibilityNodeInfo; import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ChatObject; +import org.telegram.messenger.ContactsController; import org.telegram.messenger.DialogObject; import org.telegram.messenger.Emoji; import org.telegram.messenger.ImageLocation; @@ -32,14 +34,15 @@ import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; import org.telegram.messenger.UserObject; 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.Components.AnimatedEmojiDrawable; import org.telegram.ui.Components.AvatarDrawable; +import org.telegram.ui.Components.CanvasButton; import org.telegram.ui.Components.CheckBox2; import org.telegram.ui.Components.CombinedDrawable; import org.telegram.ui.Components.Premium.PremiumGradient; +import org.telegram.ui.Components.RecyclerListView; import org.telegram.ui.NotificationsSettingsActivity; import java.util.Locale; @@ -55,6 +58,7 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No private TLRPC.User user; private TLRPC.Chat chat; private TLRPC.EncryptedChat encryptedChat; + private ContactsController.Contact contact; private long dialog_id; private String lastName; @@ -74,6 +78,9 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No private int nameLockLeft; private int nameLockTop; private int nameWidth; + CanvasButton actionButton; + private StaticLayout actionLayout; + private int actionLeft; private int sublabelOffsetX; private int sublabelOffsetY; @@ -84,6 +91,7 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No private int countLeft; private int countWidth; private StaticLayout countLayout; + private boolean[] isOnline; private boolean drawCheck; @@ -118,14 +126,20 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No statusDrawable = new AnimatedEmojiDrawable.SwapAnimatedEmojiDrawable(this, AndroidUtilities.dp(20)); } - public void setData(TLObject object, TLRPC.EncryptedChat ec, CharSequence n, CharSequence s, boolean needCount, boolean saved) { + public void setData(Object object, TLRPC.EncryptedChat ec, CharSequence n, CharSequence s, boolean needCount, boolean saved) { currentName = n; if (object instanceof TLRPC.User) { user = (TLRPC.User) object; chat = null; + contact = null; } else if (object instanceof TLRPC.Chat) { chat = (TLRPC.Chat) object; user = null; + contact = null; + } else if (object instanceof ContactsController.Contact) { + contact = (ContactsController.Contact) object; + chat = null; + user = null; } encryptedChat = ec; subLabel = s; @@ -229,7 +243,7 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - if (user == null && chat == null && encryptedChat == null) { + if (user == null && chat == null && encryptedChat == null && contact == null) { return; } if (checkBox != null) { @@ -296,6 +310,23 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No drawCheck = user.verified; drawPremium = !user.self && MessagesController.getInstance(currentAccount).isPremiumUser(user); updateStatus(drawCheck, user, false); + } else if (contact != null) { + if (!LocaleController.isRTL) { + nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + } else { + nameLeft = AndroidUtilities.dp(11); + } + if (actionButton == null) { + actionButton = new CanvasButton(this); + actionButton.setDelegate(() -> { + if (getParent() instanceof RecyclerListView) { + RecyclerListView parent = (RecyclerListView) getParent(); + parent.getOnItemClickListener().onItemClick(this, parent.getChildAdapterPosition(this)); + } else { + callOnClick(); + } + }); + } } } @@ -332,6 +363,18 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No if (drawNameLock) { nameWidth -= AndroidUtilities.dp(6) + Theme.dialogs_lockDrawable.getIntrinsicWidth(); } + if (contact != null) { + int w = (int) (Theme.dialogs_countTextPaint.measureText(LocaleController.getString(R.string.Invite)) + 1); + + actionLayout = new StaticLayout(LocaleController.getString(R.string.Invite), Theme.dialogs_countTextPaint, w, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (!LocaleController.isRTL) { + actionLeft = getMeasuredWidth() - w - AndroidUtilities.dp(19) - AndroidUtilities.dp(16); + } else { + actionLeft = AndroidUtilities.dp(19) + AndroidUtilities.dp(16); + nameLeft += w; + } + nameWidth -= AndroidUtilities.dp(32) + w; + } nameWidth -= getPaddingLeft() + getPaddingRight(); statusWidth -= getPaddingLeft() + getPaddingRight(); @@ -550,6 +593,9 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No } avatarDrawable.setInfo(chat); avatarImage.setImage(ImageLocation.getForUserOrChat(chat, ImageLocation.TYPE_SMALL), "50_50", ImageLocation.getForUserOrChat(chat, ImageLocation.TYPE_STRIPPED), "50_50", thumb, chat, 0); + } else if (contact != null) { + avatarDrawable.setInfo(0, contact.first_name, contact.last_name); + avatarImage.setImage(null, null, avatarDrawable, null, null, 0); } else { avatarDrawable.setInfo(0, null, null); avatarImage.setImage(null, null, avatarDrawable, null, null, 0); @@ -621,7 +667,7 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No @Override protected void onDraw(Canvas canvas) { - if (user == null && chat == null && encryptedChat == null) { + if (user == null && chat == null && encryptedChat == null && contact == null) { return; } @@ -676,6 +722,19 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No canvas.restore(); } + if (actionLayout != null) { + actionButton.setColor(Theme.getColor(Theme.key_chats_unreadCounter), Theme.getColor(Theme.key_chats_unreadCounterText)); + AndroidUtilities.rectTmp.set(actionLeft, countTop, actionLeft + actionLayout.getWidth(), countTop + AndroidUtilities.dp(23)); + AndroidUtilities.rectTmp.inset(-AndroidUtilities.dp(16), -AndroidUtilities.dp(4)); + actionButton.setRect(AndroidUtilities.rectTmp); + actionButton.setRounded(true); + actionButton.draw(canvas); + + canvas.save(); + canvas.translate(actionLeft, countTop + AndroidUtilities.dp(4)); + actionLayout.draw(canvas); + canvas.restore(); + } avatarImage.draw(canvas); } @@ -713,4 +772,17 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No } checkBox.setChecked(checked, animated); } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return onTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (actionButton != null && actionButton.checkTouchEvent(event)) { + return true; + } + return super.onTouchEvent(event); + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SessionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SessionCell.java index 7b117e8a5..2d747a065 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SessionCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SessionCell.java @@ -94,7 +94,7 @@ public class SessionCell extends FrameLayout { imageView.setRoundRadius(AndroidUtilities.dp(10)); addView(imageView, LayoutHelper.createFrame(42, 42, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, (LocaleController.isRTL ? 0 : 16), 9, (LocaleController.isRTL ? 16 : 0), 0)); - addView(linearLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 30, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, (LocaleController.isRTL ? 15 : 72), 7.333f, (LocaleController.isRTL ? 72 : 15), 0)); + addView(linearLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 30, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, (LocaleController.isRTL ? 15 : 72), 6.333f, (LocaleController.isRTL ? 72 : 15), 0)); } @@ -432,7 +432,7 @@ public class SessionCell extends FrameLayout { } } if (needDivider) { - int margin = currentType == 1 ? 49 : 21; + int margin = currentType == 1 ? 49 : 72; canvas.drawLine(LocaleController.isRTL ? 0 : AndroidUtilities.dp(margin), getMeasuredHeight() - 1, getMeasuredWidth() - (LocaleController.isRTL ? AndroidUtilities.dp(margin) : 0), getMeasuredHeight() - 1, Theme.dividerPaint); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareTopicCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareTopicCell.java index f7f13a558..209720964 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareTopicCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareTopicCell.java @@ -66,6 +66,9 @@ public class ShareTopicCell extends FrameLayout { } public void setTopic(TLRPC.Dialog dialog, TLRPC.TL_forumTopic topic, boolean checked, CharSequence name) { + if (dialog == null) { + return; + } TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-dialog.id); if (name != null) { nameTextView.setText(name); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedAudioCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedAudioCell.java index 0f09dd5fc..623ac1c0a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedAudioCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedAudioCell.java @@ -90,6 +90,8 @@ public class SharedAudioCell extends FrameLayout implements DownloadController.F boolean showReorderIcon; float showReorderIconProgress; + boolean showName = true; + float showNameProgress = 0; public SharedAudioCell(Context context) { this(context, VIEW_TYPE_DEFAULT, null); @@ -285,6 +287,11 @@ public class SharedAudioCell extends FrameLayout implements DownloadController.F radialProgress.initMiniIcons(); } + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return onTouchEvent(ev); + } + private boolean checkAudioMotionEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); @@ -302,6 +309,7 @@ public class SharedAudioCell extends FrameLayout implements DownloadController.F invalidate(); result = true; } else if (checkForButtonPress && radialProgress.getProgressRect().contains(x, y)) { + requestDisallowInterceptTouchEvent(true); buttonPressed = true; radialProgress.setPressed(buttonPressed, false); invalidate(); @@ -319,7 +327,9 @@ public class SharedAudioCell extends FrameLayout implements DownloadController.F didPressedButton(); invalidate(); } + requestDisallowInterceptTouchEvent(false); } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { + requestDisallowInterceptTouchEvent(false); miniButtonPressed = false; buttonPressed = false; invalidate(); @@ -330,7 +340,7 @@ public class SharedAudioCell extends FrameLayout implements DownloadController.F } } radialProgress.setPressed(miniButtonPressed, true); - return result; + return result || buttonPressed; } @Override @@ -575,6 +585,14 @@ public class SharedAudioCell extends FrameLayout implements DownloadController.F @Override protected void dispatchDraw(Canvas canvas) { + if (showName && showNameProgress != 1f) { + showNameProgress += 16 / 150f; + invalidate(); + } else if (!showName && showNameProgress != 0) { + showNameProgress -= 16 / 150f; + invalidate(); + } + showNameProgress = Utilities.clamp(showNameProgress, 1f, 0); if (enterAlpha != 1f && globalGradientView != null) { canvas.saveLayerAlpha(0, 0, getMeasuredWidth(), getMeasuredHeight(), (int) ((1f - enterAlpha) * 255), Canvas.ALL_SAVE_FLAG); globalGradientView.setViewType(FlickerLoadingView.AUDIO_TYPE); @@ -630,11 +648,18 @@ public class SharedAudioCell extends FrameLayout implements DownloadController.F } if (titleLayout != null) { + int oldAlpha = Theme.chat_contextResult_titleTextPaint.getAlpha(); + if (showNameProgress != 1f) { + Theme.chat_contextResult_titleTextPaint.setAlpha((int) (oldAlpha * showNameProgress)); + } canvas.save(); canvas.translate(AndroidUtilities.dp(LocaleController.isRTL ? 8 : AndroidUtilities.leftBaseline) + (LocaleController.isRTL && dateLayout != null ? dateLayout.getWidth() + AndroidUtilities.dp(4) : 0), titleY); titleLayout.draw(canvas); AnimatedEmojiSpan.drawAnimatedEmojis(canvas, titleLayout, titleLayoutEmojis, 0, null, 0, 0, 0, 1f); canvas.restore(); + if (showNameProgress != 1f) { + Theme.chat_contextResult_titleTextPaint.setAlpha(oldAlpha); + } } if (captionLayout != null) { @@ -647,14 +672,22 @@ public class SharedAudioCell extends FrameLayout implements DownloadController.F if (descriptionLayout != null) { Theme.chat_contextResult_descriptionTextPaint.setColor(getThemedColor(Theme.key_windowBackgroundWhiteGrayText2)); + int oldAlpha = Theme.chat_contextResult_descriptionTextPaint.getAlpha(); + if (showNameProgress != 1f) { + Theme.chat_contextResult_descriptionTextPaint.setAlpha((int) (oldAlpha * showNameProgress)); + } canvas.save(); canvas.translate(AndroidUtilities.dp(LocaleController.isRTL ? 8 : AndroidUtilities.leftBaseline), descriptionY); descriptionLayout.draw(canvas); AnimatedEmojiSpan.drawAnimatedEmojis(canvas, descriptionLayout, descriptionLayoutEmojis, 0, null, 0, 0, 0, 1f); canvas.restore(); + if (showNameProgress != 1f) { + Theme.chat_contextResult_descriptionTextPaint.setAlpha(oldAlpha); + } } radialProgress.setProgressColor(getThemedColor(buttonPressed ? Theme.key_chat_inAudioSelectedProgress : Theme.key_chat_inAudioProgress)); + radialProgress.setOverlayImageAlpha(showNameProgress); radialProgress.draw(canvas); if (needDivider) { @@ -680,5 +713,16 @@ public class SharedAudioCell extends FrameLayout implements DownloadController.F } invalidate(); } + + public void showName(boolean show, boolean animated) { + if (!animated) { + showNameProgress = show ? 1f : 0f; + } + if (showName == show) { + return; + } + showName = show; + invalidate(); + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedDocumentCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedDocumentCell.java index fbd5c28ed..c21af8923 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedDocumentCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedDocumentCell.java @@ -87,6 +87,7 @@ public class SharedDocumentCell extends FrameLayout implements DownloadControlle public final static int VIEW_TYPE_DEFAULT = 0; public final static int VIEW_TYPE_PICKER = 1; public final static int VIEW_TYPE_GLOBAL_SEARCH = 2; + public final static int VIEW_TYPE_CACHE = 3; private SpannableStringBuilder dotSpan; private CharSequence caption; @@ -270,7 +271,9 @@ public class SharedDocumentCell extends FrameLayout implements DownloadControlle } if (thumb != null || resId != 0) { if (thumb != null) { - thumbImageView.setImage(thumb, "42_42", null); + if (viewType != VIEW_TYPE_CACHE) { + thumbImageView.setImage(thumb, "42_42", null); + } } else { Drawable drawable = Theme.createCircleDrawableWithIcon(AndroidUtilities.dp(42), resId); String iconKey; @@ -299,8 +302,10 @@ public class SharedDocumentCell extends FrameLayout implements DownloadControlle } else { extTextView.setAlpha(1.0f); placeholderImageView.setAlpha(1.0f); - thumbImageView.setImageBitmap(null); - thumbImageView.setVisibility(INVISIBLE); + if (viewType != VIEW_TYPE_CACHE) { + thumbImageView.setImageBitmap(null); + thumbImageView.setVisibility(INVISIBLE); + } } setWillNotDraw(!needDivider); } @@ -723,10 +728,10 @@ public class SharedDocumentCell extends FrameLayout implements DownloadControlle if (showReorderIcon || showReorderIconProgress != 0) { if (showReorderIcon && showReorderIconProgress != 1f) { - showReorderIconProgress += 16 /150f; + showReorderIconProgress += 16 / 150f; invalidate(); } else if (!showReorderIcon && showReorderIconProgress != 0) { - showReorderIconProgress -= 16 /150f; + showReorderIconProgress -= 16 / 150f; invalidate(); } showReorderIconProgress = Utilities.clamp(showReorderIconProgress, 1f, 0); @@ -765,4 +770,16 @@ public class SharedDocumentCell extends FrameLayout implements DownloadControlle } invalidate(); } + + public void setPhoto(String path) { + if (path.endsWith("mp4")) { + thumbImageView.setImage("vthumb://0:" + path, null, null); + thumbImageView.setVisibility(View.VISIBLE); + } else if (path.endsWith(".jpg") || path.endsWith(".jpeg") || path.endsWith(".png") || path.endsWith(".gif")) { + thumbImageView.setImage("thumb://0:" + path, null, null); + thumbImageView.setVisibility(View.VISIBLE); + } else { + thumbImageView.setVisibility(View.GONE); + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedPhotoVideoCell2.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedPhotoVideoCell2.java index 5b64dfb2c..989cc7999 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedPhotoVideoCell2.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedPhotoVideoCell2.java @@ -4,19 +4,24 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; 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.Region; import android.graphics.drawable.Drawable; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; import android.util.SparseArray; +import android.view.MotionEvent; import android.view.View; import androidx.core.content.ContextCompat; import androidx.core.graphics.ColorUtils; +import androidx.core.math.MathUtils; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.DownloadController; @@ -26,16 +31,21 @@ import org.telegram.messenger.ImageReceiver; import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; +import org.telegram.messenger.Utilities; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.CanvasButton; import org.telegram.ui.Components.CheckBoxBase; +import org.telegram.ui.Components.CubicBezierInterpolator; import org.telegram.ui.Components.FlickerLoadingView; +import org.telegram.ui.Components.spoilers.SpoilerEffect; import org.telegram.ui.PhotoViewer; public class SharedPhotoVideoCell2 extends View { public ImageReceiver imageReceiver = new ImageReceiver(); + public ImageReceiver blurImageReceiver = new ImageReceiver(); int currentAccount; MessageObject currentMessageObject; int currentParentColumnsCount; @@ -45,6 +55,7 @@ public class SharedPhotoVideoCell2 extends View { float imageScale = 1f; boolean showVideoLayout; StaticLayout videoInfoLayot; + boolean drawVideoIcon = true; String videoText; CheckBoxBase checkBoxBase; SharedResources sharedResources; @@ -56,6 +67,19 @@ public class SharedPhotoVideoCell2 extends View { static long lastUpdateDownloadSettingsTime; static boolean lastAutoDownload; + private Path path = new Path(); + private SpoilerEffect mediaSpoilerEffect = new SpoilerEffect(); + private float spoilerRevealProgress; + private float spoilerRevealX; + private float spoilerRevealY; + private float spoilerMaxRadius; + + public final static int STYLE_SHARED_MEDIA = 0; + public final static int STYLE_CACHE = 1; + private int style = STYLE_SHARED_MEDIA; + + CanvasButton canvasButton; + public SharedPhotoVideoCell2(Context context, SharedResources sharedResources, int currentAccount) { super(context); this.sharedResources = sharedResources; @@ -63,6 +87,41 @@ public class SharedPhotoVideoCell2 extends View { setChecked(false, false); imageReceiver.setParentView(this); + blurImageReceiver.setParentView(this); + + imageReceiver.setDelegate((imageReceiver1, set, thumb, memCache) -> { + if (set && !thumb && currentMessageObject != null && currentMessageObject.hasMediaSpoilers() && imageReceiver.getBitmap() != null) { + if (blurImageReceiver.getBitmap() != null) { + blurImageReceiver.getBitmap().recycle(); + } + blurImageReceiver.setImageBitmap(Utilities.stackBlurBitmapMax(imageReceiver.getBitmap())); + } + }); + } + + public void setStyle(int style) { + if (this.style == style) { + return; + } + this.style = style; + if (style == STYLE_CACHE) { + checkBoxBase = new CheckBoxBase(this, 21, null); + checkBoxBase.setColor(null, Theme.key_sharedMedia_photoPlaceholder, Theme.key_checkboxCheck); + checkBoxBase.setDrawUnchecked(true); + checkBoxBase.setBackgroundType(0); + checkBoxBase.setBounds(0, 0, AndroidUtilities.dp(24), AndroidUtilities.dp(24)); + if (attached) { + checkBoxBase.onAttachedToWindow(); + } + canvasButton = new CanvasButton(this); + canvasButton.setDelegate(() -> { + onCheckBoxPressed(); + }); + } + } + + public void onCheckBoxPressed() { + } @@ -78,6 +137,7 @@ public class SharedPhotoVideoCell2 extends View { currentMessageObject = messageObject; if (messageObject == null) { imageReceiver.onDetachedFromWindow(); + blurImageReceiver.onDetachedFromWindow(); videoText = null; videoInfoLayot = null; showVideoLayout = false; @@ -85,6 +145,7 @@ public class SharedPhotoVideoCell2 extends View { } else { if (attached) { imageReceiver.onAttachedToWindow(); + blurImageReceiver.onAttachedToWindow(); } } String restrictionReason = MessagesController.getRestrictionReason(messageObject.messageOwner.restriction_reason); @@ -170,6 +231,15 @@ public class SharedPhotoVideoCell2 extends View { if (showImageStub) { imageReceiver.setImageBitmap(ContextCompat.getDrawable(getContext(), R.drawable.photo_placeholder_in)); } + + if (blurImageReceiver.getBitmap() != null) { + blurImageReceiver.getBitmap().recycle(); + blurImageReceiver.setImageBitmap((Bitmap) null); + } + if (imageReceiver.getBitmap() != null && currentMessageObject.hasMediaSpoilers() && !currentMessageObject.isMediaSpoilersRevealed) { + blurImageReceiver.setImageBitmap(Utilities.stackBlurBitmapMax(imageReceiver.getBitmap())); + } + invalidate(); } @@ -182,6 +252,11 @@ public class SharedPhotoVideoCell2 extends View { return lastAutoDownload; } + public void setVideoText(String videoText, boolean drawVideoIcon) { + this.videoText = videoText; + showVideoLayout = videoText != null; + this.drawVideoIcon = drawVideoIcon; + } @Override protected void onDraw(Canvas canvas) { @@ -207,7 +282,7 @@ public class SharedPhotoVideoCell2 extends View { } if (currentMessageObject == null || !imageReceiver.hasBitmapImage() || imageReceiver.getCurrentAlpha() != 1.0f || imageAlpha != 1f) { - if (SharedPhotoVideoCell2.this.getParent() != null) { + if (SharedPhotoVideoCell2.this.getParent() != null && globalGradientView != null) { globalGradientView.setParentSize(((View) SharedPhotoVideoCell2.this.getParent()).getMeasuredWidth(), SharedPhotoVideoCell2.this.getMeasuredHeight(), -getX()); globalGradientView.updateColors(); globalGradientView.updateGradient(); @@ -219,12 +294,9 @@ public class SharedPhotoVideoCell2 extends View { } invalidate(); } - if (currentMessageObject == null) { - return; - } if (imageAlpha != 1f) { - canvas.saveLayerAlpha(0,0, padding * 2 + imageWidth, padding * 2 + imageHeight, (int) (255 * imageAlpha), Canvas.ALL_SAVE_FLAG); + canvas.saveLayerAlpha(0, 0, padding * 2 + imageWidth, padding * 2 + imageHeight, (int) (255 * imageAlpha), Canvas.ALL_SAVE_FLAG); } else { canvas.save(); } @@ -232,25 +304,48 @@ public class SharedPhotoVideoCell2 extends View { if ((checkBoxBase != null && checkBoxBase.isChecked()) || PhotoViewer.isShowingImage(currentMessageObject)) { canvas.drawRect(padding, padding, imageWidth, imageHeight, sharedResources.backgroundPaint); } - if (currentMessageObject != null) { - if (checkBoxProgress > 0) { - float offset = AndroidUtilities.dp(10) * checkBoxProgress; - imageReceiver.setImageCoords(padding + offset, padding + offset, imageWidth - offset * 2, imageHeight - offset * 2); - } else { - float localPadding = padding; - if (crossfadeProgress > 0.5f && crossfadeToColumnsCount != 9 && currentParentColumnsCount != 9) { - localPadding += 1; - } - imageReceiver.setImageCoords(localPadding, localPadding, imageWidth, imageHeight); + + if (checkBoxProgress > 0) { + float offset = AndroidUtilities.dp(10) * checkBoxProgress; + imageReceiver.setImageCoords(padding + offset, padding + offset, imageWidth - offset * 2, imageHeight - offset * 2); + blurImageReceiver.setImageCoords(padding + offset, padding + offset, imageWidth - offset * 2, imageHeight - offset * 2); + } else { + float localPadding = padding; + if (crossfadeProgress > 0.5f && crossfadeToColumnsCount != 9 && currentParentColumnsCount != 9) { + localPadding += 1; } - if (!PhotoViewer.isShowingImage(currentMessageObject)) { - imageReceiver.draw(canvas); - if (highlightProgress > 0) { - sharedResources.highlightPaint.setColor(ColorUtils.setAlphaComponent(Color.BLACK, (int) (0.5f * highlightProgress * 255))); - canvas.drawRect(imageReceiver.getDrawRegion(), sharedResources.highlightPaint); + imageReceiver.setImageCoords(localPadding, localPadding, imageWidth, imageHeight); + blurImageReceiver.setImageCoords(localPadding, localPadding, imageWidth, imageHeight); + } + if (!PhotoViewer.isShowingImage(currentMessageObject)) { + imageReceiver.draw(canvas); + if (currentMessageObject != null && currentMessageObject.hasMediaSpoilers() && !currentMessageObject.isMediaSpoilersRevealedInSharedMedia) { + canvas.save(); + canvas.clipRect(padding, padding, padding + imageWidth, padding + imageHeight); + + if (spoilerRevealProgress != 0f) { + path.rewind(); + path.addCircle(spoilerRevealX, spoilerRevealY, spoilerMaxRadius * spoilerRevealProgress, Path.Direction.CW); + + canvas.clipPath(path, Region.Op.DIFFERENCE); } + + blurImageReceiver.draw(canvas); + + int sColor = Color.WHITE; + mediaSpoilerEffect.setColor(ColorUtils.setAlphaComponent(sColor, (int) (Color.alpha(sColor) * 0.325f))); + mediaSpoilerEffect.setBounds((int) imageReceiver.getImageX(), (int) imageReceiver.getImageY(), (int) imageReceiver.getImageX2(), (int) imageReceiver.getImageY2()); + mediaSpoilerEffect.draw(canvas); + canvas.restore(); + + invalidate(); + } + if (highlightProgress > 0) { + sharedResources.highlightPaint.setColor(ColorUtils.setAlphaComponent(Color.BLACK, (int) (0.5f * highlightProgress * 255))); + canvas.drawRect(imageReceiver.getDrawRegion(), sharedResources.highlightPaint); } } + if (showVideoLayout) { canvas.save(); canvas.clipRect(padding, padding, padding + imageWidth, padding + imageHeight); @@ -262,32 +357,75 @@ public class SharedPhotoVideoCell2 extends View { if (videoInfoLayot == null) { width = AndroidUtilities.dp(17); } else { - width = AndroidUtilities.dp(14) + videoInfoLayot.getWidth() + AndroidUtilities.dp(4); + width = AndroidUtilities.dp(4) + videoInfoLayot.getWidth() + AndroidUtilities.dp(4); + } + if (drawVideoIcon) { + width += AndroidUtilities.dp(10); } canvas.translate(AndroidUtilities.dp(5), AndroidUtilities.dp(1) + imageHeight - AndroidUtilities.dp(17) - AndroidUtilities.dp(4)); AndroidUtilities.rectTmp.set(0, 0, width, AndroidUtilities.dp(17)); canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(4), AndroidUtilities.dp(4), Theme.chat_timeBackgroundPaint); - canvas.save(); - canvas.translate(videoInfoLayot == null ? AndroidUtilities.dp(5) : AndroidUtilities.dp(4), (AndroidUtilities.dp(17) - sharedResources.playDrawable.getIntrinsicHeight()) / 2f); - sharedResources.playDrawable.setAlpha((int) (255 * imageAlpha)); - sharedResources.playDrawable.draw(canvas); - canvas.restore(); + if (drawVideoIcon) { + canvas.save(); + canvas.translate(videoInfoLayot == null ? AndroidUtilities.dp(5) : AndroidUtilities.dp(4), (AndroidUtilities.dp(17) - sharedResources.playDrawable.getIntrinsicHeight()) / 2f); + sharedResources.playDrawable.setAlpha((int) (255 * imageAlpha)); + sharedResources.playDrawable.draw(canvas); + canvas.restore(); + } if (videoInfoLayot != null) { - canvas.translate(AndroidUtilities.dp(14), (AndroidUtilities.dp(17) - videoInfoLayot.getHeight()) / 2f); + canvas.translate(AndroidUtilities.dp(4 + (drawVideoIcon ? 10 : 0)), (AndroidUtilities.dp(17) - videoInfoLayot.getHeight()) / 2f); videoInfoLayot.draw(canvas); } canvas.restore(); } - if (checkBoxBase != null && checkBoxBase.getProgress() != 0) { + if (checkBoxBase != null && (style == STYLE_CACHE || checkBoxBase.getProgress() != 0)) { canvas.save(); - canvas.translate(imageWidth + AndroidUtilities.dp(2) - AndroidUtilities.dp(25), 0); + float x, y; + if (style == STYLE_CACHE) { + x = imageWidth + AndroidUtilities.dp(2) - AndroidUtilities.dp(25) - AndroidUtilities.dp(4); + y = AndroidUtilities.dp(4); + } else { + x = imageWidth + AndroidUtilities.dp(2) - AndroidUtilities.dp(25); + y = 0; + } + canvas.translate(x, y); checkBoxBase.draw(canvas); + if (canvasButton != null) { + AndroidUtilities.rectTmp.set(x, y, x + checkBoxBase.bounds.width(), y + checkBoxBase.bounds.height()); + canvasButton.setRect(AndroidUtilities.rectTmp); + } canvas.restore(); } + canvas.restore(); } + public boolean canRevealSpoiler() { + return currentMessageObject != null && currentMessageObject.hasMediaSpoilers() && spoilerRevealProgress == 0f && !currentMessageObject.isMediaSpoilersRevealedInSharedMedia; + } + + public void startRevealMedia(float x, float y) { + spoilerRevealX = x; + spoilerRevealY = y; + + spoilerMaxRadius = (float) Math.sqrt(Math.pow(getWidth(), 2) + Math.pow(getHeight(), 2)); + ValueAnimator animator = ValueAnimator.ofFloat(0, 1).setDuration((long) MathUtils.clamp(spoilerMaxRadius * 0.3f, 250, 550)); + animator.setInterpolator(CubicBezierInterpolator.EASE_BOTH); + animator.addUpdateListener(animation -> { + spoilerRevealProgress = (float) animation.getAnimatedValue(); + invalidate(); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + currentMessageObject.isMediaSpoilersRevealedInSharedMedia = true; + invalidate(); + } + }); + animator.start(); + } + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); @@ -297,6 +435,7 @@ public class SharedPhotoVideoCell2 extends View { } if (currentMessageObject != null) { imageReceiver.onAttachedToWindow(); + blurImageReceiver.onAttachedToWindow(); } } @@ -309,6 +448,7 @@ public class SharedPhotoVideoCell2 extends View { } if (currentMessageObject != null) { imageReceiver.onDetachedFromWindow(); + blurImageReceiver.onDetachedFromWindow(); } } @@ -377,7 +517,7 @@ public class SharedPhotoVideoCell2 extends View { return; } if (checkBoxBase == null) { - checkBoxBase = new CheckBoxBase(this,21, null); + checkBoxBase = new CheckBoxBase(this, 21, null); checkBoxBase.setColor(null, Theme.key_sharedMedia_photoPlaceholder, Theme.key_checkboxCheck); checkBoxBase.setDrawUnchecked(false); checkBoxBase.setBackgroundType(1); @@ -452,10 +592,20 @@ public class SharedPhotoVideoCell2 extends View { public String getFilterString(int width) { String str = imageFilters.get(width); if (str == null) { - str = width + "_" + width + "_isc"; + str = width + "_" + width + "_isc"; imageFilters.put(width, str); } return str; } } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (canvasButton != null) { + if (canvasButton.checkTouchEvent(event)) { + return true; + } + } + return super.onTouchEvent(event); + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerEmojiCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerEmojiCell.java index 78b6f935f..595d55261 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerEmojiCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerEmojiCell.java @@ -32,6 +32,7 @@ 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.SvgHelper; import org.telegram.messenger.UserConfig; import org.telegram.tgnet.TLRPC; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCell.java index 39b735e52..70771dab0 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCell.java @@ -10,12 +10,14 @@ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.view.Gravity; +import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.ImageView; @@ -33,6 +35,7 @@ import org.telegram.ui.Components.Switch; public class TextCell extends FrameLayout { public final SimpleTextView textView; + private final SimpleTextView subtitleView; public final AnimatedTextView valueTextView; public final RLottieImageView imageView; private Switch checkBox; @@ -78,6 +81,13 @@ public class TextCell extends FrameLayout { textView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT)); + subtitleView = new SimpleTextView(context); + subtitleView.setTextColor(Theme.getColor(dialog ? Theme.key_dialogTextGray : Theme.key_windowBackgroundWhiteGrayText, resourcesProvider)); + subtitleView.setTextSize(13); + subtitleView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + subtitleView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); + addView(subtitleView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT)); + valueTextView = new AnimatedTextView(context); valueTextView.setTextColor(Theme.getColor(dialog ? Theme.key_dialogTextBlue2 : Theme.key_windowBackgroundWhiteValueText, resourcesProvider)); valueTextView.setPadding(0, AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18)); @@ -144,10 +154,12 @@ public class TextCell extends FrameLayout { if (prioritizeTitleOverValue) { textView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(71 + leftPadding), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(20), MeasureSpec.EXACTLY)); + subtitleView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(71 + leftPadding), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(20), MeasureSpec.EXACTLY)); valueTextView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(103 + leftPadding) - textView.getTextWidth(), LocaleController.isRTL ? MeasureSpec.AT_MOST : MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(20), MeasureSpec.EXACTLY)); } else { valueTextView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(leftPadding), LocaleController.isRTL ? MeasureSpec.AT_MOST : MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(20), MeasureSpec.EXACTLY)); textView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(71 + leftPadding) - valueTextView.width(), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(20), MeasureSpec.EXACTLY)); + subtitleView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(71 + leftPadding) - valueTextView.width(), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(20), MeasureSpec.EXACTLY)); } if (imageView.getVisibility() == VISIBLE) { imageView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); @@ -181,14 +193,21 @@ public class TextCell extends FrameLayout { } valueTextView.layout(viewLeft, viewTop, viewLeft + valueTextView.getMeasuredWidth(), viewTop + valueTextView.getMeasuredHeight()); - viewTop = (height - textView.getTextHeight()) / 2; + if (LocaleController.isRTL) { viewLeft = getMeasuredWidth() - textView.getMeasuredWidth() - AndroidUtilities.dp(imageView.getVisibility() == VISIBLE ? offsetFromImage : leftPadding); } else { viewLeft = AndroidUtilities.dp(imageView.getVisibility() == VISIBLE ? offsetFromImage : leftPadding); } - textView.layout(viewLeft, viewTop, viewLeft + textView.getMeasuredWidth(), viewTop + textView.getMeasuredHeight()); - + if (subtitleView.getVisibility() == View.VISIBLE) { + viewTop = (height - textView.getTextHeight() - subtitleView.getTextHeight() - AndroidUtilities.dp(2)) / 2; + textView.layout(viewLeft, viewTop, viewLeft + textView.getMeasuredWidth(), viewTop + textView.getMeasuredHeight()); + viewTop = viewTop + textView.getTextHeight() + AndroidUtilities.dp(2); + subtitleView.layout(viewLeft, viewTop, viewLeft + subtitleView.getMeasuredWidth(), viewTop + subtitleView.getMeasuredHeight()); + } else { + viewTop = (height - textView.getTextHeight()) / 2; + textView.layout(viewLeft, viewTop, viewLeft + textView.getMeasuredWidth(), viewTop + textView.getMeasuredHeight()); + } if (imageView.getVisibility() == VISIBLE) { viewTop = AndroidUtilities.dp(5); viewLeft = !LocaleController.isRTL ? AndroidUtilities.dp(imageLeft) : width - imageView.getMeasuredWidth() - AndroidUtilities.dp(imageLeft); @@ -303,6 +322,8 @@ public class TextCell extends FrameLayout { valueTextView.setVisibility(VISIBLE); valueImageView.setVisibility(GONE); imageView.setVisibility(VISIBLE); + imageView.setTranslationX(0); + imageView.setTranslationY(0); imageView.setPadding(0, AndroidUtilities.dp(7), 0, 0); imageView.setImageResource(resId); needDivider = divider; @@ -312,6 +333,17 @@ public class TextCell extends FrameLayout { } } + public void setColorfulIcon(int color, int resId) { + offsetFromImage = 65; + imageView.setVisibility(VISIBLE); + imageView.setPadding(AndroidUtilities.dp(2), AndroidUtilities.dp(2), AndroidUtilities.dp(2), AndroidUtilities.dp(2)); + imageView.setTranslationX(AndroidUtilities.dp(-3)); + imageView.setTranslationY(AndroidUtilities.dp(6)); + imageView.setImageResource(resId); + imageView.setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN)); + imageView.setBackground(Theme.createRoundRectDrawable(AndroidUtilities.dp(8), color)); + } + public void setTextAndCheck(String text, boolean checked, boolean divider) { imageLeft = 21; offsetFromImage = 71; @@ -521,4 +553,13 @@ public class TextCell extends FrameLayout { valueTextView.setAlpha(1f - drawLoadingProgress); super.dispatchDraw(canvas); } + + public void setSubtitle(CharSequence charSequence) { + if (!TextUtils.isEmpty(charSequence)) { + subtitleView.setVisibility(View.VISIBLE); + subtitleView.setText(charSequence); + } else { + subtitleView.setVisibility(View.GONE); + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextSettingsCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextSettingsCell.java index 2f05f76e0..35a1f49da 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextSettingsCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextSettingsCell.java @@ -315,7 +315,8 @@ public class TextSettingsCell extends FrameLayout { super.dispatchDraw(canvas); if (needDivider) { - canvas.drawLine(LocaleController.isRTL ? 0 : AndroidUtilities.dp(20), getMeasuredHeight() - 1, getMeasuredWidth() - (LocaleController.isRTL ? AndroidUtilities.dp(20) : 0), getMeasuredHeight() - 1, Theme.dividerPaint); + int offset = AndroidUtilities.dp(imageView.getVisibility() == View.VISIBLE ? 71 : 20); + canvas.drawLine(LocaleController.isRTL ? 0 : offset, getMeasuredHeight() - 1, getMeasuredWidth() - (LocaleController.isRTL ? offset : 0), getMeasuredHeight() - 1, Theme.dividerPaint); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChangeBioActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChangeBioActivity.java index a65cf0173..888bf14d8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChangeBioActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChangeBioActivity.java @@ -208,7 +208,7 @@ public class ChangeBioActivity extends BaseFragment { return; } - final AlertDialog progressDialog = new AlertDialog(getParentActivity(), 3); + final AlertDialog progressDialog = new AlertDialog(getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER); final TLRPC.TL_account_updateProfile req = new TLRPC.TL_account_updateProfile(); req.about = newName; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChangeUsernameActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChangeUsernameActivity.java index 9b6031960..63840da1e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChangeUsernameActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChangeUsernameActivity.java @@ -1288,7 +1288,7 @@ public class ChangeUsernameActivity extends BaseFragment { return; } - final AlertDialog progressDialog = new AlertDialog(getParentActivity(), 3); + final AlertDialog progressDialog = new AlertDialog(getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER); final TLRPC.TL_account_updateUsername req = new TLRPC.TL_account_updateUsername(); req.username = username; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelAdminLogActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelAdminLogActivity.java index 9319f9ed6..6dbe23469 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelAdminLogActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelAdminLogActivity.java @@ -2710,7 +2710,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio linviteLoading = true; boolean[] canceled = new boolean[1]; - final AlertDialog progressDialog = new AlertDialog(getParentActivity(), 3); + final AlertDialog progressDialog = new AlertDialog(getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER); progressDialog.setOnCancelListener(dialogInterface -> { linviteLoading = false; canceled[0] = true; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelCreateActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelCreateActivity.java index 224a9e2c8..439d9d39e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelCreateActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelCreateActivity.java @@ -265,7 +265,7 @@ public class ChannelCreateActivity extends BaseFragment implements NotificationC return; } AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setTitle(LocaleController.getString("StopLoadingTitle", R.string.StopLoadingTitle)); builder.setMessage(LocaleController.getString("StopLoading", R.string.StopLoading)); builder.setPositiveButton(LocaleController.getString("WaitMore", R.string.WaitMore), null); builder.setNegativeButton(LocaleController.getString("Stop", R.string.Stop), (dialogInterface, i) -> { @@ -571,7 +571,7 @@ public class ChannelCreateActivity extends BaseFragment implements NotificationC } else { cameraDrawable.setCurrentFrame(0, false); } - }); + }, 0); cameraDrawable.setCurrentFrame(0); cameraDrawable.setCustomEndFrame(43); avatarEditor.playAnimation(); @@ -954,7 +954,7 @@ public class ChannelCreateActivity extends BaseFragment implements NotificationC } @Override - public void didUploadPhoto(final TLRPC.InputFile photo, final TLRPC.InputFile video, double videoStartTimestamp, String videoPath, final TLRPC.PhotoSize bigSize, final TLRPC.PhotoSize smallSize) { + public void didUploadPhoto(final TLRPC.InputFile photo, final TLRPC.InputFile video, double videoStartTimestamp, String videoPath, final TLRPC.PhotoSize bigSize, final TLRPC.PhotoSize smallSize, boolean isVideo) { AndroidUtilities.runOnUIThread(() -> { if (photo != null || video != null) { inputPhoto = photo; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java index 3cc8bb5d2..49ec4d63f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java @@ -49,6 +49,7 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Looper; import android.os.SystemClock; import android.os.Vibrator; import android.provider.MediaStore; @@ -149,6 +150,7 @@ import org.telegram.messenger.Utilities; import org.telegram.messenger.VideoEditedInfo; import org.telegram.messenger.browser.Browser; import org.telegram.messenger.support.LongSparseIntArray; +import org.telegram.messenger.utils.PhotoUtilities; import org.telegram.messenger.voip.VoIPService; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLObject; @@ -188,6 +190,7 @@ import org.telegram.ui.Components.AnimatedEmojiSpan; import org.telegram.ui.Components.AnimatedFileDrawable; import org.telegram.ui.Components.AnimationProperties; import org.telegram.ui.Components.AttachBotIntroTopView; +import org.telegram.ui.Components.AudioPlayerAlert; import org.telegram.ui.Components.AutoDeletePopupWrapper; import org.telegram.ui.Components.BackButtonMenu; import org.telegram.ui.Components.BackupImageView; @@ -230,6 +233,7 @@ import org.telegram.ui.Components.FragmentContextView; import org.telegram.ui.Components.GigagroupConvertAlert; import org.telegram.ui.Components.HideViewAfterAnimation; import org.telegram.ui.Components.HintView; +import org.telegram.ui.Components.ImageUpdater; import org.telegram.ui.Components.ImportingAlert; import org.telegram.ui.Components.InstantCameraView; import org.telegram.ui.Components.InviteMembersBottomSheet; @@ -281,6 +285,7 @@ import org.telegram.ui.Components.URLSpanUserMention; import org.telegram.ui.Components.UndoView; import org.telegram.ui.Components.UnreadCounterTextView; import org.telegram.ui.Components.ViewHelper; +import org.telegram.ui.Components.spoilers.SpoilerEffect; import org.telegram.ui.Components.voip.VoIPHelper; import org.telegram.ui.Delegates.ChatActivityMemberRequestsDelegate; import org.telegram.ui.LNavigation.LNavigation; @@ -390,6 +395,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private float reactionsMentionButtonEnterProgress; private FrameLayout pagedownButton; private ImageView pagedownButtonImage; + private ImageView pagedownButtonArrow; + private ImageView pagedownButtonLoading; + private CircularProgressDrawable pagedownButtonLoadingDrawable; private boolean pagedownButtonShowedByScroll; private CounterView pagedownButtonCounter; private FrameLayout mentiondownButton; @@ -609,6 +617,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private TLRPC.PhotoSize replyImageLocation; private TLRPC.PhotoSize replyImageThumbLocation; private TLObject replyImageLocationObject; + private boolean replyImageHasMediaSpoiler; private int pinnedImageSize; private int pinnedImageCacheType; private TLRPC.PhotoSize pinnedImageLocation; @@ -1239,7 +1248,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } wasManualScroll = true; boolean result = true; - if (!actionBar.isActionModeShowed() && (reportType < 0 || (view instanceof ChatActionCell && ((ChatActionCell) view).getMessageObject().messageOwner.action instanceof TLRPC.TL_messageActionSetMessagesTTL))) { + if (!actionBar.isActionModeShowed() && (reportType < 0 || (view instanceof ChatActionCell && (((ChatActionCell) view).getMessageObject().messageOwner.action instanceof TLRPC.TL_messageActionSetMessagesTTL) || ((ChatActionCell) view).getMessageObject().type == MessageObject.TYPE_SUGGEST_PHOTO))) { result = createMenu(view, false, true, x, y); } else { boolean outside = false; @@ -1851,6 +1860,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } AndroidUtilities.runOnUIThread(chatInviteRunnable = () -> { chatInviteRunnable = null; + if (getParentActivity() == null) { + return; + } AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity(), themeDelegate); if (ChatObject.isChannel(currentChat) && !currentChat.megagroup) { builder.setMessage(LocaleController.getString("JoinByPeekChannelText", R.string.JoinByPeekChannelText)); @@ -2087,6 +2099,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not BackButtonMenu.clearPulledDialogs(this, parentLayout.getFragmentStack().indexOf(this) - (replacingChatActivity ? 0 : 1)); } replacingChatActivity = false; + + if (progressDialogCurrent != null) { + progressDialogCurrent.cancel(); + progressDialogCurrent = null; + } } @Override @@ -4444,7 +4461,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } private void drawReplyButton(Canvas canvas) { - if (slidingView == null) { + if (slidingView == null || Thread.currentThread() != Looper.getMainLooper().getThread()) { return; } Paint chatActionBackgroundPaint = getThemedPaint(Theme.key_paint_chatActionBackground); @@ -7067,7 +7084,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not int[] size = MessageObject.getInlineResultWidthAndHeight(result); EmbedBottomSheet.show(ChatActivity.this, null, botContextProvider, result.title != null ? result.title : "", result.description, result.content.url, result.content.url, size[0], size[1], isKeyboardVisible()); } else { - processExternalUrl(0, result.content.url, false); + processExternalUrl(0, result.content.url, null, null, false); } } @@ -7295,9 +7312,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not }); pagedownButtonImage = new ImageView(context); - pagedownButtonImage.setImageResource(R.drawable.pagedown); - pagedownButtonImage.setScaleType(ImageView.ScaleType.CENTER); - pagedownButtonImage.setColorFilter(new PorterDuffColorFilter(getThemedColor(Theme.key_chat_goDownButtonIcon), PorterDuff.Mode.MULTIPLY)); pagedownButtonImage.setPadding(0, AndroidUtilities.dp(2), 0, 0); Drawable drawable; if (Build.VERSION.SDK_INT >= 21) { @@ -7316,11 +7330,28 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not CombinedDrawable combinedDrawable = new CombinedDrawable(shadowDrawable, drawable, 0, 0); combinedDrawable.setIconSize(AndroidUtilities.dp(42), AndroidUtilities.dp(42)); drawable = combinedDrawable; - pagedownButtonImage.setBackgroundDrawable(drawable); + pagedownButtonImage.setBackground(drawable); pagedownButton.addView(pagedownButtonImage, LayoutHelper.createFrame(46, 46, Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM)); pagedownButton.setContentDescription(LocaleController.getString("AccDescrPageDown", R.string.AccDescrPageDown)); + pagedownButtonArrow = new ImageView(context); + pagedownButtonArrow.setImageResource(R.drawable.pagedown); + pagedownButtonArrow.setScaleType(ImageView.ScaleType.CENTER); + pagedownButtonArrow.setColorFilter(new PorterDuffColorFilter(getThemedColor(Theme.key_chat_goDownButtonIcon), PorterDuff.Mode.MULTIPLY)); + pagedownButtonArrow.setPadding(0, AndroidUtilities.dp(2), 0, 0); + pagedownButtonArrow.setPivotX(AndroidUtilities.dp(23)); + pagedownButtonArrow.setPivotY(AndroidUtilities.dp(33)); + pagedownButton.addView(pagedownButtonArrow, LayoutHelper.createFrame(46, 46, Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM)); + + pagedownButtonLoading = new ImageView(context); + pagedownButtonLoadingDrawable = new CircularProgressDrawable(AndroidUtilities.dp(18), AndroidUtilities.dp(1.7f), getThemedColor(Theme.key_chat_goDownButtonIcon)); + pagedownButtonLoadingDrawable.setAngleOffset(90); + pagedownButtonLoading.setImageDrawable(pagedownButtonLoadingDrawable); + pagedownButtonLoading.setAlpha(0f); + pagedownButtonLoading.setVisibility(View.GONE); + pagedownButton.addView(pagedownButtonLoading, LayoutHelper.createFrame(46, 46, Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM)); + pagedownButtonCounter = new CounterView(context, themeDelegate) { @Override public void invalidate() { @@ -8300,7 +8331,33 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not replyLayout.addView(replyObjectHintTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 18, Gravity.TOP | Gravity.LEFT, 52, 24, 0, 0)); - replyImageView = new BackupImageView(context); + SpoilerEffect replySpoilerEffect = new SpoilerEffect(); + replyImageView = new BackupImageView(context) { + Path path = new Path(); + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + + if (replyImageHasMediaSpoiler) { + path.rewind(); + AndroidUtilities.rectTmp.set(imageReceiver.getImageX(), imageReceiver.getImageY(), imageReceiver.getImageX2(), imageReceiver.getImageY2()); + path.addRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(2), AndroidUtilities.dp(2), Path.Direction.CW); + + canvas.save(); + canvas.clipPath(path); + + int sColor = Color.WHITE; + replySpoilerEffect.setColor(ColorUtils.setAlphaComponent(sColor, (int) (Color.alpha(sColor) * 0.325f))); + replySpoilerEffect.setBounds((int) imageReceiver.getImageX(), (int) imageReceiver.getImageY(), (int) imageReceiver.getImageX2(), (int) imageReceiver.getImageY2()); + replySpoilerEffect.draw(canvas); + invalidate(); + + canvas.restore(); + } + } + }; + replyImageView.setBlurAllowed(true); replyImageView.setRoundRadius(AndroidUtilities.dp(2)); replyLayout.addView(replyImageView, LayoutHelper.createFrame(34, 34, Gravity.TOP | Gravity.LEFT, 52, 6, 0, 0)); @@ -8988,12 +9045,15 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not public void onPageDownClicked() { wasManualScroll = true; textSelectionHelper.cancelTextSelectionRunnable(); + Runnable inCaseLoading = () -> { + setPagedownLoading(true, true); + }; if (createUnreadMessageAfterId != 0) { - scrollToMessageId(createUnreadMessageAfterId, 0, false, returnToLoadIndex, true, 0); + scrollToMessageId(createUnreadMessageAfterId, 0, false, returnToLoadIndex, true, 0, inCaseLoading); } else if (returnToMessageId > 0) { - scrollToMessageId(returnToMessageId, 0, true, returnToLoadIndex, true, 0); + scrollToMessageId(returnToMessageId, 0, true, returnToLoadIndex, true, 0, inCaseLoading); } else { - scrollToLastMessage(false, true); + scrollToLastMessage(false, true, inCaseLoading); if (!pinnedMessageIds.isEmpty()) { forceScrollToFirst = true; forceNextPinnedMessageId = pinnedMessageIds.get(0); @@ -10043,6 +10103,57 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return false; } + private float pagedownLoadingT; + private ValueAnimator pagedownAnimator; + private void setPagedownLoading(boolean loading, boolean animated) { + if (animated && Math.abs(pagedownLoadingT - (loading ? 1 : 0)) > .01f) { + if (pagedownAnimator != null) { + pagedownAnimator.cancel(); + } + final boolean[] startedLoading = new boolean[1]; + pagedownButtonArrow.setVisibility(View.VISIBLE); + pagedownAnimator = ValueAnimator.ofFloat(pagedownLoadingT, loading ? 1 : 0); + pagedownAnimator.addUpdateListener(anm -> { + pagedownLoadingT = (float) anm.getAnimatedValue(); + + if (!startedLoading[0] && loading && pagedownLoadingT > .5f) { + startedLoading[0] = true; + pagedownButtonLoadingDrawable.reset(); + pagedownButtonLoading.setVisibility(View.VISIBLE); + } + + pagedownButtonLoading.setAlpha(Math.max(0, (pagedownLoadingT - .5f) * 2)); + pagedownButtonArrow.setScaleX(1f - pagedownLoadingT); + pagedownButtonArrow.setScaleY(1f - pagedownLoadingT); + }); + pagedownAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (loading) { + pagedownButtonArrow.setVisibility(View.GONE); + } else { + pagedownButtonLoading.setVisibility(View.GONE); + } + } + }); + pagedownAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT); + pagedownAnimator.setDuration(260); + pagedownAnimator.start(); + } else { + if (loading) { + pagedownButtonArrow.setScaleX(0); + pagedownButtonArrow.setScaleY(0); + pagedownButtonArrow.setVisibility(View.GONE); + pagedownButtonLoading.setVisibility(View.VISIBLE); + } else { + pagedownButtonArrow.setScaleX(1); + pagedownButtonArrow.setScaleY(1); + pagedownButtonArrow.setVisibility(View.VISIBLE); + pagedownButtonLoading.setVisibility(View.GONE); + } + } + } + private void openForward(boolean fromActionBar) { if (getMessagesController().isChatNoForwards(currentChat) || hasSelectedNoforwardsMessage()) { // We should update text if user changed locale without re-opening chat activity @@ -10264,7 +10375,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not progressDialog.dismiss(); } updatePinnedListButton(false); - progressDialog = new AlertDialog(getParentActivity(), 3, themeDelegate); + progressDialog = new AlertDialog(getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER, themeDelegate); progressDialog.setOnCancelListener(postponedScrollCancelListener); progressDialog.showDelayed(1000); @@ -10367,6 +10478,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not info.videoEditedInfo = photoEntry.editedInfo; info.canDeleteAfter = photoEntry.canDeleteAfter; info.updateStickersOrder = SendMessagesHelper.checkUpdateStickersOrder(photoEntry.caption); + info.hasMediaSpoilers = photoEntry.hasSpoiler; photos.add(info); photoEntry.reset(); } @@ -12029,7 +12141,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { replyingMessageObject = null; } - chatActivityEnterView.setReplyingMessageObject(null); + chatActivityEnterView.setReplyingMessageObject(replyingMessageObject); updateBottomOverlay(); } editingMessageObject = null; @@ -12265,7 +12377,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not replyImageLocation = photoSize; replyImageThumbLocation = thumbPhotoSize; replyImageLocationObject = photoSizeObject; + replyImageHasMediaSpoiler = thumbMediaMessageObject.hasMediaSpoilers(); replyImageView.setImage(ImageLocation.getForObject(replyImageLocation, photoSizeObject), "50_50", ImageLocation.getForObject(thumbPhotoSize, photoSizeObject), "50_50_b", null, size, cacheType, thumbMediaMessageObject); + replyImageView.setHasBlur(replyImageHasMediaSpoiler); replyImageView.setVisibility(View.VISIBLE); layoutParams1.leftMargin = layoutParams2.leftMargin = layoutParams3.leftMargin = AndroidUtilities.dp(96); } @@ -12421,6 +12535,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } public void scrollToLastMessage(boolean skipSponsored, boolean top) { + scrollToLastMessage(skipSponsored, top, null); + } + + public void scrollToLastMessage(boolean skipSponsored, boolean top, Runnable inCaseLoading) { if (chatListView.isFastScrollAnimationRunning()) { return; } @@ -12429,6 +12547,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not forceScrollToFirst = false; chatScrollHelper.setScrollDirection(RecyclerAnimationScrollHelper.SCROLL_DIRECTION_DOWN); if (forwardEndReached[0] && first_unread_id == 0 && startLoadFromMessageId == 0) { + setPagedownLoading(false, true); if (chatLayoutManager.findFirstCompletelyVisibleItemPosition() == 0) { canShowPagedownButton = false; updatePagedownButtonVisibility(true); @@ -12458,16 +12577,34 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (messages != null && messages.size() > 0) { position = Math.min(position, messages.size() - 1); } - chatScrollHelper.scrollToPosition(chatScrollHelperCallback.position = position, chatScrollHelperCallback.offset = 0, chatScrollHelperCallback.bottom = !top, true); + final int finalPosition = position; + Runnable scroll = () -> { + chatScrollHelper.scrollToPosition(chatScrollHelperCallback.position = finalPosition, chatScrollHelperCallback.offset = 0, chatScrollHelperCallback.bottom = !top, true); + }; + if (SCROLL_DEBUG_DELAY && inCaseLoading != null) { + inCaseLoading.run(); + AndroidUtilities.runOnUIThread(() -> { + resetProgressDialogLoading(); + scroll.run(); + }, 7500); + } else { + scroll.run(); + } } } else { if (progressDialog != null) { progressDialog.dismiss(); } + updatePinnedListButton(false); - progressDialog = new AlertDialog(getParentActivity(), 3, themeDelegate); - progressDialog.setOnCancelListener(postponedScrollCancelListener); - progressDialog.showDelayed(1000); + if (inCaseLoading != null) { + inCaseLoading.run(); + } else { + resetProgressDialogLoading(); + progressDialog = new AlertDialog(getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER, themeDelegate); + progressDialog.setOnCancelListener(postponedScrollCancelListener); + progressDialog.showDelayed(1000); + } postponedScrollToLastMessageQueryIndex = lastLoadIndex; postponedScrollMessageId = 0; @@ -12475,7 +12612,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not waitingForLoad.clear(); waitingForLoad.add(lastLoadIndex); - getMessagesController().loadMessages(dialog_id, mergeDialogId, false, 30, 0, 0, true, 0, classGuid, 0, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); + AndroidUtilities.runOnUIThread(() -> { + getMessagesController().loadMessages(dialog_id, mergeDialogId, false, 30, 0, 0, true, 0, classGuid, 0, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); + }, SCROLL_DEBUG_DELAY ? 7500 : 0); } } @@ -13161,10 +13300,35 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private boolean nextScrollForce; private int nextScrollForcePinnedMessageId; + public static final int PROGRESS_REPLY = 0; + public static final int PROGRESS_LINK = 1; + public static final int PROGRESS_INSTANT = 2; + public static final int PROGRESS_BOT_BUTTON = 3; + + private int progressDialogAtMessageId; + private int progressDialogAtMessageType; + private CharacterStyle progressDialogLinkSpan; + private String progressDialogBotButtonUrl; + private Browser.Progress progressDialogCurrent; + private void resetProgressDialogLoading() { + progressDialogLinkSpan = null; + progressDialogAtMessageId = 0; + progressDialogAtMessageType = -1; + progressDialogBotButtonUrl = null; + progressDialogCurrent = null; + + setPagedownLoading(false, true); + } + + public static final boolean SCROLL_DEBUG_DELAY = false; private boolean pinnedProgressIsShowing; Runnable updatePinnedProgressRunnable; public void scrollToMessageId(int id, int fromMessageId, boolean select, int loadIndex, boolean forceScroll, int forcePinnedMessageId) { + scrollToMessageId(id, fromMessageId, select, loadIndex, forceScroll, forcePinnedMessageId, null); + } + + public void scrollToMessageId(int id, int fromMessageId, boolean select, int loadIndex, boolean forceScroll, int forcePinnedMessageId, Runnable inCaseLoading) { if (id == 0 || NotificationCenter.getInstance(currentAccount).isAnimationInProgress() || getParentActivity() == null) { if (NotificationCenter.getInstance(currentAccount).isAnimationInProgress()) { nextScrollToMessageId = id; @@ -13220,7 +13384,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } chatScrollHelper.setScrollDirection(scrollDirection); - if (object != null) { + if (!SCROLL_DEBUG_DELAY && object != null) { MessageObject.GroupedMessages groupedMessages = groupedMessagesMap.get(object.getGroupId()); if (object.getGroupId() != 0 && groupedMessages != null) { MessageObject primary = groupedMessages.findPrimaryMessageObject(); @@ -13311,10 +13475,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not showPinnedProgress(forceNextPinnedMessageId != 0); - progressDialog = new AlertDialog(getParentActivity(), 3, themeDelegate); - progressDialog.setOnShowListener(dialogInterface -> showPinnedProgress(false)); - progressDialog.setOnCancelListener(postponedScrollCancelListener); - progressDialog.showDelayed(400); + if (inCaseLoading != null) { + inCaseLoading.run(); + } else { + progressDialog = new AlertDialog(getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER, themeDelegate); + progressDialog.setOnShowListener(dialogInterface -> showPinnedProgress(false)); + progressDialog.setOnCancelListener(postponedScrollCancelListener); + progressDialog.showDelayed(400); + } waitingForLoad.clear(); removeSelectedMessageHighlight(); @@ -13329,7 +13497,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not postponedScrollToLastMessageQueryIndex = lastLoadIndex; postponedScrollMinMessageId = minMessageId[0]; postponedScrollMessageId = id; - getMessagesController().loadMessages(loadIndex == 0 ? dialog_id : mergeDialogId, 0, false, ((isThreadChat() && !isTopic) || AndroidUtilities.isTablet()) ? 30 : 20, startLoadFromMessageId, 0, true, 0, classGuid, 3, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); + AndroidUtilities.runOnUIThread(() -> { + getMessagesController().loadMessages(loadIndex == 0 ? dialog_id : mergeDialogId, 0, false, ((isThreadChat() && !isTopic) || AndroidUtilities.isTablet()) ? 30 : 20, startLoadFromMessageId, 0, true, 0, classGuid, 3, 0, chatMode, threadMessageId, replyMaxReadId, lastLoadIndex++, isTopic); + }, SCROLL_DEBUG_DELAY ? 7500 : 0); } else { View child = chatListView.getChildAt(0); if (child != null && child.getTop() <= 0) { @@ -13341,7 +13511,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not needSelectFromMessageId = select; } - private void showPinnedProgress(boolean show) { if (show) { if (updatePinnedProgressRunnable == null) { @@ -13689,7 +13858,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return 2; } else if (messageObject.type == 6) { return -1; - } else if (messageObject.type == 10 || messageObject.type == MessageObject.TYPE_ACTION_PHOTO) { + } else if (messageObject.type == 10 || messageObject.type == MessageObject.TYPE_ACTION_PHOTO || messageObject.type == MessageObject.TYPE_SUGGEST_PHOTO) { if (messageObject.getId() == 0) { return -1; } @@ -14163,7 +14332,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not int type = getMessageType(message); - if (type < 2 || type == 20) { + if (type < 2 || type == 20 || type == MessageObject.TYPE_SUGGEST_PHOTO) { return; } addToSelectedMessages(message, outside); @@ -14371,7 +14540,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not }, this); } else { fillEditingMediaWithCaption(caption, null); - SendMessagesHelper.prepareSendingVideo(getAccountInstance(), videoPath, null, dialog_id, replyingMessageObject, getThreadMessage(), null, null, 0, editingMessageObject, true, 0, false); + SendMessagesHelper.prepareSendingVideo(getAccountInstance(), videoPath, null, dialog_id, replyingMessageObject, getThreadMessage(), null, null, 0, editingMessageObject, true, 0, false, false); afterMessageSend(); } } @@ -14619,6 +14788,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not break; } } + if (imageReceiver == null && message.messageOwner != null && message.messageOwner.action != null && message.messageOwner.action.photo != null && message.messageOwner.action.photo.video_sizes != null && !message.messageOwner.action.photo.video_sizes.isEmpty()) { + TLRPC.VideoSize photoSize = message.messageOwner.action.photo.video_sizes.get(0); + if (photoSize.location != null && photoSize.location.volume_id == fileLocation.volume_id && photoSize.location.local_id == fileLocation.local_id) { + imageReceiver = cell.getPhotoImage(); + } + } } } } @@ -14791,7 +14966,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } private boolean isSkeletonVisible() { - if (justCreatedTopic || justCreatedChat || currentUser != null || !SharedConfig.animationsEnabled()) { + if (justCreatedTopic || justCreatedChat || currentUser != null || !SharedConfig.animationsEnabled() || SharedConfig.getLiteMode().enabled()) { return false; } int childHeight = 0; @@ -15017,6 +15192,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (progressDialog != null) { progressDialog.dismiss(); } + resetProgressDialogLoading(); showPinnedProgress(false); if (postponedScrollIsCanceled) { return; @@ -15855,6 +16031,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (progressDialog != null) { progressDialog.dismiss(); } + resetProgressDialogLoading(); updatePinnedListButton(false); if (postponedScrollMessageId == 0) { chatScrollHelperCallback.scrollTo = null; @@ -17851,6 +18028,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } else if (id == NotificationCenter.topicsDidLoaded) { if (isTopic) { + if (getParentActivity() == null) { + return; + } updateTopicTitleIcon(); updateTopicHeader(); updateBottomOverlay(); @@ -18243,6 +18423,20 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (!isAd) { isAd = messageObject.isSponsored(); } + if (messageObject.getId() > 0 && messageObject.type == MessageObject.TYPE_SUGGEST_PHOTO) { + for (int i = 0; i < messages.size(); i++) { + if (messages.get(i).type == MessageObject.TYPE_SUGGEST_PHOTO && messages.get(i).getId() < 0) { + messagesDict[0].remove(messages.get(i).getId()); + messagesDict[0].put(messageObject.getId(), messageObject); + messageObject.stableId = messages.get(i).stableId; + PhotoUtilities.replacePhotoImagesInCache(currentAccount, messages.get(i).messageOwner.action.photo, messageObject.messageOwner.action.photo); + messages.set(i, messageObject); + + chatAdapter.notifyItemChanged(chatAdapter.messagesStartRow + i); + break; + } + } + } int messageId = messageObject.getId(); if (threadMessageId != 0) { if (messageId > 0 && messageId <= (messageObject.isOut() ? threadMaxOutboxReadId : threadMaxInboxReadId)) { @@ -18455,10 +18649,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not int lastAdIndex = -1; for (int a = 0; a < arr.size(); a++) { MessageObject obj = arr.get(a); - if (obj.scheduled != (chatMode == MODE_SCHEDULED) || threadMessageId != 0 && (!ChatObject.isForum(currentChat) || threadMessageId != 1) && threadMessageId != obj.getReplyTopMsgId() && threadMessageId != obj.getReplyMsgId()) { + if (obj.scheduled != (chatMode == MODE_SCHEDULED) || threadMessageId != 0 && (!ChatObject.isForum(currentChat) || (!isTopic || getTopicId() != MessageObject.getTopicId(obj.messageOwner, ChatObject.isForum(currentChat)))) && threadMessageId != obj.getReplyTopMsgId() && threadMessageId != obj.getReplyMsgId()) { continue; } - if (obj.isOut()) { + if (obj.isOut() && messagesDict[0].indexOfKey(obj.getId()) < 0) { rotateMotionBackgroundDrawable(); } int placeToPaste = -1; @@ -19451,6 +19645,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not parentLayout.getDrawerLayoutContainer().setBehindKeyboardColor(Theme.getColor(Theme.key_windowBackgroundWhite)); } TranscribeButton.resetVideoTranscriptionsOpen(); + + if (progressDialogCurrent != null) { + progressDialogCurrent.cancel(); + progressDialogCurrent = null; + } } public void saveKeyboardPositionBeforeTransition() { @@ -19682,13 +19881,16 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not introTopView.setColor(Theme.getColor(Theme.key_chat_attachContactIcon)); introTopView.setBackgroundColor(Theme.getColor(Theme.key_dialogTopBackground)); introTopView.setAttachBot(attachMenuBot); - new AlertDialog.Builder(getParentActivity()) + + AtomicBoolean allowWrite = new AtomicBoolean(); + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()) .setTopView(introTopView) .setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("BotRequestAttachPermission", R.string.BotRequestAttachPermission, UserObject.getUserName(user)))) .setPositiveButton(LocaleController.getString(R.string.BotAddToMenu), (dialog, which) -> { TLRPC.TL_messages_toggleBotInAttachMenu botRequest = new TLRPC.TL_messages_toggleBotInAttachMenu(); botRequest.bot = MessagesController.getInstance(currentAccount).getInputUser(user.id); botRequest.enabled = true; + botRequest.write_allowed = allowWrite.get(); ConnectionsManager.getInstance(currentAccount).sendRequest(botRequest, (response2, error2) -> AndroidUtilities.runOnUIThread(() -> { if (error2 == null) { MediaDataController.getInstance(currentAccount).loadAttachMenuBots(false, true); @@ -19697,8 +19899,26 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } }), ConnectionsManager.RequestFlagInvokeAfter | ConnectionsManager.RequestFlagFailOnServerErrors); }) - .setNegativeButton(LocaleController.getString(R.string.Cancel), null) - .show(); + .setNegativeButton(LocaleController.getString(R.string.Cancel), null); + + if (attachMenuBot.request_write_access) { + allowWrite.set(true); + + CheckBoxCell cell = new CheckBoxCell(getParentActivity(), 5, getResourceProvider()); + cell.setPadding(0, AndroidUtilities.dp(8), 0, AndroidUtilities.dp(8)); + cell.setBackground(Theme.getSelectorDrawable(false)); + cell.setMultiline(true); + cell.setText(AndroidUtilities.replaceTags(LocaleController.formatString("OpenUrlOption2", R.string.OpenUrlOption2, UserObject.getUserName(user))), "", true, false); + cell.setPadding(LocaleController.isRTL ? AndroidUtilities.dp(16) : AndroidUtilities.dp(8), 0, LocaleController.isRTL ? AndroidUtilities.dp(8) : AndroidUtilities.dp(16), 0); + cell.setOnClickListener(v -> { + boolean allow = !cell.isChecked(); + cell.setChecked(allow, true); + allowWrite.set(allow); + }); + + builder.setView(cell); + } + builder.show(); } } })); @@ -22136,6 +22356,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } private CharSequence getMessageCaption(MessageObject messageObject, MessageObject.GroupedMessages group) { + return getMessageCaption(messageObject, group, null); + } + + private CharSequence getMessageCaption(MessageObject messageObject, MessageObject.GroupedMessages group, int[] msgId) { String restrictionReason = MessagesController.getRestrictionReason(messageObject.messageOwner.restriction_reason); if (!TextUtils.isEmpty(restrictionReason)) { return restrictionReason; @@ -22157,6 +22381,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return null; } caption = message.caption; + if (msgId != null) { + msgId[0] = message.getId(); + } } } return caption; @@ -22342,6 +22569,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } } + int[] messageIdToTranslate = new int[] { message.getId() }; CharSequence messageTextToTranslate = null; if (message.messageOwner.action instanceof TLRPC.TL_messageActionSetMessagesTTL && single && (dialog_id >= 0 || (currentChat != null && ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_DELETE_MESSAGES)))) { AutoDeletePopupWrapper autoDeletePopupWrapper = new AutoDeletePopupWrapper(contentView.getContext(), null, new AutoDeletePopupWrapper.Callback() { @@ -22366,7 +22594,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not selectedObject = message; selectedObjectGroup = groupedMessages; if (selectedObject.type != MessageObject.TYPE_EMOJIS && selectedObject.type != MessageObject.TYPE_ANIMATED_STICKER && selectedObject.type != MessageObject.TYPE_STICKER) { - messageTextToTranslate = getMessageCaption(selectedObject, selectedObjectGroup); + messageTextToTranslate = getMessageCaption(selectedObject, selectedObjectGroup, messageIdToTranslate); if (messageTextToTranslate == null && selectedObject.isPoll()) { try { TLRPC.Poll poll = ((TLRPC.TL_messageMediaPoll) selectedObject.messageOwner.media).poll; @@ -22685,7 +22913,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } if (!selectedObject.isSponsored() && chatMode != MODE_SCHEDULED && (!selectedObject.needDrawBluredPreview() || selectedObject.hasExtendedMediaPreview()) && !selectedObject.isLiveLocation() && selectedObject.type != MessageObject.TYPE_PHONE_CALL && !noforwards && - selectedObject.type != MessageObject.TYPE_GIFT_PREMIUM) { + selectedObject.type != MessageObject.TYPE_GIFT_PREMIUM && selectedObject.type != MessageObject.TYPE_SUGGEST_PHOTO) { items.add(LocaleController.getString("Forward", R.string.Forward)); options.add(OPTION_FORWARD); icons.add(R.drawable.msg_forward); @@ -23364,8 +23592,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not didPressMessageUrl(link, false, selectedObject, v instanceof ChatMessageCell ? (ChatMessageCell) v : null); return true; }; - TLRPC.InputPeer inputPeer = getMessagesController().getInputPeer(dialog_id); - int messageId = selectedObject == null || selectedObject.messageOwner == null ? 0 : selectedObject.messageOwner.id; + TLRPC.InputPeer inputPeer = selectedObject != null && (selectedObject.isPoll() || selectedObject.isVoiceTranscriptionOpen()) ? null : getMessagesController().getInputPeer(dialog_id); if (LanguageDetector.hasSupport()) { final String[] fromLang = {null}; cell.setVisibility(View.GONE); @@ -23399,7 +23626,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (selectedObject == null || i >= options.size() || getParentActivity() == null) { return; } - TranslateAlert alert = TranslateAlert.showAlert(getParentActivity(), this, currentAccount, inputPeer, messageId, fromLang[0], toLang, finalMessageText, noforwards, onLinkPress, () -> dimBehindView(false)); + TranslateAlert alert = TranslateAlert.showAlert(getParentActivity(), this, currentAccount, inputPeer, messageIdToTranslate[0], fromLang[0], toLang, finalMessageText, noforwards, onLinkPress, () -> dimBehindView(false)); alert.showDim(false); closeMenu(false); }); @@ -23413,7 +23640,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (selectedObject == null || i >= options.size() || getParentActivity() == null) { return; } - TranslateAlert alert = TranslateAlert.showAlert(getParentActivity(), this, currentAccount, inputPeer, messageId, "und", toLang, finalMessageText, noforwards, onLinkPress, () -> dimBehindView(false)); + TranslateAlert alert = TranslateAlert.showAlert(getParentActivity(), this, currentAccount, inputPeer, messageIdToTranslate[0], "und", toLang, finalMessageText, noforwards, onLinkPress, () -> dimBehindView(false)); alert.showDim(false); closeMenu(false); }); @@ -23984,7 +24211,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not MessageObject message = cell.getMessageObject(); TLRPC.Document document = message.getDocument(); boolean isEmoji; - if ((isEmoji = message.isAnimatedEmoji()) || MessageObject.isAnimatedStickerDocument(document, currentEncryptedChat == null || message.isOut()) && !SharedConfig.loopStickers) { + if ((isEmoji = message.isAnimatedEmoji()) || MessageObject.isAnimatedStickerDocument(document, currentEncryptedChat == null || message.isOut()) && !SharedConfig.loopStickers()) { ImageReceiver imageReceiver = cell.getPhotoImage(); RLottieDrawable drawable = imageReceiver.getLottieAnimation(); if (drawable != null) { @@ -24030,7 +24257,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (!message.isAnimatedAnimatedEmoji()) { setupStickerVibrationAndSound(cell); } - if ((message.isAnimatedEmoji()) || MessageObject.isAnimatedStickerDocument(document, currentEncryptedChat == null || message.isOut()) && !SharedConfig.loopStickers) { + if ((message.isAnimatedEmoji()) || MessageObject.isAnimatedStickerDocument(document, currentEncryptedChat == null || message.isOut()) && !SharedConfig.loopStickers()) { ImageReceiver imageReceiver = cell.getPhotoImage(); RLottieDrawable drawable = imageReceiver.getLottieAnimation(); if (drawable != null) { @@ -24621,7 +24848,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not break; } case OPTION_UNVOTE: { - final AlertDialog[] progressDialog = new AlertDialog[]{new AlertDialog(getParentActivity(), 3, themeDelegate)}; + final AlertDialog[] progressDialog = new AlertDialog[]{new AlertDialog(getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER, themeDelegate)}; int requestId = getSendMessagesHelper().sendVote(selectedObject, null, () -> { try { progressDialog[0].dismiss(); @@ -24655,7 +24882,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not builder.setMessage(LocaleController.getString("StopPollAlertText", R.string.StopPollAlertText)); } builder.setPositiveButton(LocaleController.getString("Stop", R.string.Stop), (dialogInterface, i) -> { - final AlertDialog[] progressDialog = new AlertDialog[]{new AlertDialog(getParentActivity(), 3, themeDelegate)}; + final AlertDialog[] progressDialog = new AlertDialog[]{new AlertDialog(getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER, themeDelegate)}; TLRPC.TL_messages_editMessage req = new TLRPC.TL_messages_editMessage(); TLRPC.TL_messageMediaPoll mediaPoll = (TLRPC.TL_messageMediaPoll) object.messageOwner.media; TLRPC.TL_inputMediaPoll poll = new TLRPC.TL_inputMediaPoll(); @@ -24849,6 +25076,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return; } } + args.putBoolean("historyPreloaded", false); addToPulledDialogsMyself(); ChatActivity chatActivity = new ChatActivity(args); if (topicKey.topicId != 0) { @@ -25292,9 +25520,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not fillEditingMediaWithCaption(photoEntry.caption, photoEntry.entities); if (photoEntry.isVideo) { if (videoEditedInfo != null) { - SendMessagesHelper.prepareSendingVideo(getAccountInstance(), photoEntry.path, videoEditedInfo, dialog_id, replyingMessageObject, getThreadMessage(), photoEntry.caption, photoEntry.entities, photoEntry.ttl, editingMessageObject, notify, scheduleDate, forceDocument); + SendMessagesHelper.prepareSendingVideo(getAccountInstance(), photoEntry.path, videoEditedInfo, dialog_id, replyingMessageObject, getThreadMessage(), photoEntry.caption, photoEntry.entities, photoEntry.ttl, editingMessageObject, notify, scheduleDate, forceDocument, photoEntry.hasSpoiler); } else { - SendMessagesHelper.prepareSendingVideo(getAccountInstance(), photoEntry.path, null, dialog_id, replyingMessageObject, getThreadMessage(), photoEntry.caption, photoEntry.entities, photoEntry.ttl, editingMessageObject, notify, scheduleDate, forceDocument); + SendMessagesHelper.prepareSendingVideo(getAccountInstance(), photoEntry.path, null, dialog_id, replyingMessageObject, getThreadMessage(), photoEntry.caption, photoEntry.entities, photoEntry.ttl, editingMessageObject, notify, scheduleDate, forceDocument, photoEntry.hasSpoiler); } } else { if (photoEntry.imagePath != null) { @@ -25635,7 +25863,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (!cells[0].isChecked()) { Browser.openUrl(getParentActivity(), url, false); } else { - final AlertDialog[] progressDialog = new AlertDialog[]{new AlertDialog(getParentActivity(), 3, themeDelegate)}; + final AlertDialog[] progressDialog = new AlertDialog[]{new AlertDialog(getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER, themeDelegate)}; TLRPC.TL_messages_acceptUrlAuth req = new TLRPC.TL_messages_acceptUrlAuth(); if (buttonReq.url != null) { req.url = buttonReq.url; @@ -25823,17 +26051,16 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not String username = Browser.extractUsername(str); if (username != null) { username = username.toLowerCase(); - if (ChatObject.hasPublicLink(currentChat, username) || - currentUser != null && !TextUtils.isEmpty(currentUser.username) && username.equals(currentUser.username.toLowerCase())) { + if (ChatObject.hasPublicLink(currentChat, username) || UserObject.hasPublicUsername(currentUser, username)) { if (avatarContainer != null) { avatarContainer.openProfile(false); } else { shakeContent(); } } else if (str.startsWith("@")) { - getMessagesController().openByUserName(username, ChatActivity.this, 0); + getMessagesController().openByUserName(username, ChatActivity.this, 0, makeProgressForLink(cell, url)); } else { - processExternalUrl(0, str, false); + processExternalUrl(0, str, url, cell, false); } } else if (str.startsWith("#") || str.startsWith("$")) { if (ChatObject.isChannel(currentChat)) { @@ -25849,7 +26076,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not presentFragment(fragment); } } else { - processExternalUrl(0, str, false); + processExternalUrl(0, str, url, cell, false); } } } @@ -25869,7 +26096,63 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } - private void processExternalUrl(int type, String url, boolean forceAlert) { + private Browser.Progress makeProgressForLink(ChatMessageCell cell, CharacterStyle span) { + if (progressDialogCurrent != null) { + progressDialogCurrent.cancel(true); + progressDialogCurrent = null; + } + return progressDialogCurrent = span != null && cell != null && cell.getMessageObject() != null ? new Browser.Progress() { + @Override + public void init() { + progressDialogAtMessageId = cell.getMessageObject().getId(); + progressDialogAtMessageType = PROGRESS_LINK; + progressDialogLinkSpan = span; + + cell.invalidate(); + } + + @Override + public void end(boolean replaced) { + if (!replaced) { + AndroidUtilities.runOnUIThread(() -> { + if (progressDialogAtMessageId == cell.getMessageObject().getId()) { + resetProgressDialogLoading(); + } + }, 240); + } + } + } : null; + } + + private Browser.Progress makeProgressForBotButton(ChatMessageCell cell, String url) { + if (progressDialogCurrent != null) { + progressDialogCurrent.cancel(true); + progressDialogCurrent = null; + } + return progressDialogCurrent = url != null && cell != null && cell.getMessageObject() != null ? new Browser.Progress() { + @Override + public void init() { + progressDialogAtMessageId = cell.getMessageObject().getId(); + progressDialogAtMessageType = PROGRESS_BOT_BUTTON; + progressDialogBotButtonUrl = url; + + cell.invalidate(); + } + + @Override + public void end(boolean replaced) { + if (!replaced) { + AndroidUtilities.runOnUIThread(() -> { + if (progressDialogAtMessageId == cell.getMessageObject().getId()) { + resetProgressDialogLoading(); + } + }, 240); + } + } + } : null; + } + + private void processExternalUrl(int type, String url, CharacterStyle span, ChatMessageCell cell, boolean forceAlert) { try { Uri uri = Uri.parse(url); String host = uri.getHost() != null ? uri.getHost().toLowerCase() : ""; @@ -25882,17 +26165,17 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } if (forceAlert || AndroidUtilities.shouldShowUrlInAlert(url)) { if (type == 0 || type == 2) { - AlertsCreator.showOpenUrlAlert(ChatActivity.this, url, true, true, true, themeDelegate); + AlertsCreator.showOpenUrlAlert(ChatActivity.this, url, true, true, true, makeProgressForLink(cell, span), themeDelegate); } else if (type == 1) { - AlertsCreator.showOpenUrlAlert(ChatActivity.this, url, true, true, false, themeDelegate); + AlertsCreator.showOpenUrlAlert(ChatActivity.this, url, true, true, false, makeProgressForLink(cell, span), themeDelegate); } } else { if (type == 0) { - Browser.openUrl(getParentActivity(), url); + Browser.openUrl(getParentActivity(), Uri.parse(url), true, true, makeProgressForLink(cell, span)); } else if (type == 1) { - Browser.openUrl(getParentActivity(), url, inlineReturn == 0, false); + Browser.openUrl(getParentActivity(), Uri.parse(url), inlineReturn == 0, false, makeProgressForLink(cell, span)); } else if (type == 2) { - Browser.openUrl(getParentActivity(), url, inlineReturn == 0); + Browser.openUrl(getParentActivity(), Uri.parse(url), inlineReturn == 0, true, makeProgressForLink(cell, span)); } } } @@ -25976,10 +26259,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (longPress && cell != null) { cell.resetPressedLink(-1); } + showDialog(new AudioPlayerAlert(getContext(), themeDelegate)); } else if (str.startsWith("card:")) { final ChatMessageCell finalCell = cell; String number = str.substring(5); - final AlertDialog[] progressDialog = new AlertDialog[]{new AlertDialog(getParentActivity(), 3, themeDelegate)}; + final AlertDialog[] progressDialog = new AlertDialog[]{new AlertDialog(getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER, themeDelegate)}; TLRPC.TL_payments_getBankCardData req = new TLRPC.TL_payments_getBankCardData(); req.number = number; int requestId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { @@ -26047,7 +26331,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not builder.setTitleMultipleLines(true); builder.setItems(noforwards ? new CharSequence[] {LocaleController.getString("Open", R.string.Open)} : new CharSequence[]{LocaleController.getString("Open", R.string.Open), LocaleController.getString("Copy", R.string.Copy)}, (dialog, which) -> { if (which == 0) { - processExternalUrl(1, urlFinal, false); + processExternalUrl(1, urlFinal, url, finalCell, false); } else if (which == 1) { String url1 = urlFinal; boolean tel = false; @@ -26078,7 +26362,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { boolean forceAlert = url instanceof URLSpanReplacement; if (url instanceof URLSpanReplacement && (urlFinal == null || !urlFinal.startsWith("mailto:")) || AndroidUtilities.shouldShowUrlInAlert(urlFinal)) { - if (openLinkInternally(urlFinal, messageObject != null ? messageObject.getId() : 0)) { + if (openLinkInternally(urlFinal, cell, url, messageObject != null ? messageObject.getId() : 0)) { return; } forceAlert = true; @@ -26092,14 +26376,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return; } } - if (openLinkInternally(urlFinal, messageObject != null ? messageObject.getId() : 0)) { + if (openLinkInternally(urlFinal, cell, url, messageObject != null ? messageObject.getId() : 0)) { return; } } if (Browser.urlMustNotHaveConfirmation(urlFinal)) { forceAlert = false; } - processExternalUrl(2, urlFinal, forceAlert); + processExternalUrl(2, urlFinal, url, cell, forceAlert); } } } @@ -26666,7 +26950,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not !(button instanceof TLRPC.TL_keyboardButtonUserProfile)) { return; } - chatActivityEnterView.didPressedBotButton(button, cell.getMessageObject(), cell.getMessageObject()); + chatActivityEnterView.didPressedBotButton(button, cell.getMessageObject(), cell.getMessageObject(), makeProgressForBotButton(cell, button instanceof TLRPC.TL_keyboardButtonUrl ? button.url : null)); } @Override @@ -27002,10 +27286,34 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatActivityDelegate.openReplyMessage(id); finishFragment(); } else { - scrollToMessageId(id, messageObject.getId(), true, messageObject.getDialogId() == mergeDialogId ? 1 : 0, true, 0); + scrollToMessageId(id, messageObject.getId(), true, messageObject.getDialogId() == mergeDialogId ? 1 : 0, true, 0, () -> { + progressDialogAtMessageId = messageObject.getId(); + progressDialogAtMessageType = PROGRESS_REPLY; + }); } } + @Override + public boolean isProgressLoading(ChatMessageCell cell, int type) { + return progressDialogAtMessageId != 0 && cell.getMessageObject() != null && progressDialogAtMessageId == cell.getMessageObject().getId() && progressDialogAtMessageType == type; + } + + @Override + public CharacterStyle getProgressLoadingLink(ChatMessageCell cell) { + if (cell.getMessageObject() != null && progressDialogAtMessageId != 0 && progressDialogAtMessageId == cell.getMessageObject().getId() && progressDialogAtMessageType == PROGRESS_LINK) { + return progressDialogLinkSpan; + } + return null; + } + + @Override + public String getProgressLoadingBotButtonUrl(ChatMessageCell cell) { + if (cell.getMessageObject() != null && progressDialogAtMessageId != 0 && progressDialogAtMessageId == cell.getMessageObject().getId() && progressDialogAtMessageType == PROGRESS_BOT_BUTTON) { + return progressDialogBotButtonUrl; + } + return null; + } + @Override public void didPressViaBotNotInline(ChatMessageCell cell, long botId) { Bundle args = new Bundle(); @@ -27192,8 +27500,27 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } } else if (messageObject.messageOwner.media != null && messageObject.messageOwner.media.webpage != null) { - if (!openLinkInternally(messageObject.messageOwner.media.webpage.url, messageObject.getId())) { - Browser.openUrl(getParentActivity(), messageObject.messageOwner.media.webpage.url); + if (!openLinkInternally(messageObject.messageOwner.media.webpage.url, cell, null, messageObject.getId(), PROGRESS_INSTANT)) { + if (progressDialogCurrent != null) { + progressDialogCurrent.cancel(true); + } + progressDialogCurrent = cell == null || cell.getMessageObject() == null ? null : new Browser.Progress() { + @Override + public void init() { + progressDialogAtMessageId = cell.getMessageObject().getId(); + progressDialogAtMessageType = PROGRESS_INSTANT; + progressDialogLinkSpan = null; + cell.invalidate(); + } + + @Override + public void end(boolean replaced) { + if (!replaced) { + AndroidUtilities.runOnUIThread(ChatActivity.this::resetProgressDialogLoading, 250); + } + } + }; + Browser.openUrl(getParentActivity(), Uri.parse(messageObject.messageOwner.media.webpage.url), true, true, progressDialogCurrent); } } } @@ -27333,9 +27660,98 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not MessageObject message = cell.getMessageObject(); PhotoViewer.getInstance().setParentActivity(ChatActivity.this, themeDelegate); TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(message.photoThumbs, 640); - if (photoSize != null) { + TLRPC.VideoSize videoSize = null; + if (message.messageOwner.action.photo.video_sizes != null && !message.messageOwner.action.photo.video_sizes.isEmpty()) { + videoSize = message.messageOwner.action.photo.video_sizes.get(0); + } + if (cell.getMessageObject().type == MessageObject.TYPE_SUGGEST_PHOTO && !message.isOutOwner()) { + if (message.settingAvatar) { + return; + } + final ArrayList photos = new ArrayList<>(); + ImageLocation.getForPhoto(videoSize, message.messageOwner.action.photo); + File file = videoSize == null ? getFileLoader().getPathToAttach(message.messageOwner.action.photo) : getFileLoader().getPathToAttach(videoSize); + File file2 = new File(FileLoader.getDirectory(FileLoader.MEDIA_DIR_CACHE), file.getName()); + if (!file.exists()) { + if (file2.exists()) { + file = file2; + } else { + //TODO photo not downloaded yet + return; + } + } + final MediaController.PhotoEntry entry = new MediaController.PhotoEntry(0, 0, 0, file.getAbsolutePath(), 0, false, 0, 0, 0); + entry.caption = chatActivityEnterView.getFieldText(); + entry.isVideo = videoSize != null; + photos.add(entry); + + PhotoViewer.getInstance().openPhotoForSelect(photos, 0, PhotoViewer.SELECT_TYPE_AVATAR, false, new PhotoViewer.EmptyPhotoViewerProvider() { + @Override + public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index, boolean needPreview) { + return photoViewerProvider.getPlaceForPhoto(message, fileLocation, index, needPreview); + } + + @Override + public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo, boolean notify, int scheduleDate, boolean forceDocument) { + message.settingAvatar = true; + if (entry.imagePath != null || entry.isVideo) { + PhotoUtilities.setImageAsAvatar(entry, ChatActivity.this, () -> { + message.settingAvatar = false; + }); + } else { + TLRPC.TL_photos_updateProfilePhoto req = new TLRPC.TL_photos_updateProfilePhoto(); + req.id = new TLRPC.TL_inputPhoto(); + req.id.id = message.messageOwner.action.photo.id; + req.id.access_hash = message.messageOwner.action.photo.access_hash; + req.id.file_reference = message.messageOwner.action.photo.file_reference; + + getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + if (response instanceof TLRPC.TL_photos_photo) { + TLRPC.TL_photos_photo photos_photo = (TLRPC.TL_photos_photo) response; + getMessagesController().putUsers(photos_photo.users, false); + TLRPC.User user = getMessagesController().getUser(getUserConfig().clientUserId); + if (photos_photo.photo instanceof TLRPC.TL_photo) { + if (user != null) { + PhotoUtilities.applyPhotoToUser(message.messageOwner.action.photo, user, false); + getUserConfig().setCurrentUser(user); + getUserConfig().saveConfig(true); + CharSequence title = AndroidUtilities.replaceTags(LocaleController.getString("ApplyAvatarHint", R.string.ApplyAvatarHintTitle)); + CharSequence subtitle = AndroidUtilities.replaceSingleTag(LocaleController.getString("ApplyAvatarHint", R.string.ApplyAvatarHint), () -> { + Bundle args = new Bundle(); + args.putLong("user_id", UserConfig.getInstance(currentAccount).clientUserId); + presentFragment(new ProfileActivity(args)); + }); + BulletinFactory.of(ChatActivity.this).createUsersBulletin(Collections.singletonList(user), title, subtitle).show(); + } + } + } + message.settingAvatar = false; + })); + } + } + + }, null); + if (entry.isVideo) { + PhotoViewer.getInstance().setTitle(LocaleController.getString(R.string.SuggestedVideo)); + } else { + PhotoViewer.getInstance().setTitle(LocaleController.getString(R.string.SuggestedPhoto)); + } + ImageUpdater.AvatarFor avatarFor = new ImageUpdater.AvatarFor(getUserConfig().getCurrentUser(), ImageUpdater.TYPE_SET_PHOTO_FOR_USER); + avatarFor.isVideo = videoSize != null; + avatarFor.fromObject = getMessagesController().getUser(dialog_id); + PhotoViewer.getInstance().setAvatarFor(avatarFor); + } else if (videoSize != null) { + ImageLocation imageLocation = ImageLocation.getForPhoto(videoSize, message.messageOwner.action.photo); + PhotoViewer.getInstance().openPhoto(videoSize.location, imageLocation, photoViewerProvider); + if (cell.getMessageObject().type == MessageObject.TYPE_SUGGEST_PHOTO) { + PhotoViewer.getInstance().setTitle(LocaleController.getString("SuggestedVideo", R.string.SuggestedVideo)); + } + } else if (photoSize != null) { ImageLocation imageLocation = ImageLocation.getForPhoto(photoSize, message.messageOwner.action.photo); PhotoViewer.getInstance().openPhoto(photoSize.location, imageLocation, photoViewerProvider); + if (cell.getMessageObject().type == MessageObject.TYPE_SUGGEST_PHOTO) { + PhotoViewer.getInstance().setTitle(LocaleController.getString("SuggestedPhoto", R.string.SuggestedPhoto)); + } } else { PhotoViewer.getInstance().openPhoto(message, null, 0, 0, 0, photoViewerProvider); } @@ -27353,6 +27769,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public boolean didLongPress(ChatActionCell cell, float x, float y) { + if (inPreviewMode) { + return false; + } return createMenu(cell, false, false, x, y); } @@ -27416,7 +27835,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not hideFieldPanel(false); } } else { - processExternalUrl(0, url, false); + processExternalUrl(0, url, null, null, false); } }); } else if (viewType == 4) { @@ -27451,7 +27870,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not messageCell.isChat = currentChat != null || UserObject.isUserSelf(currentUser) || UserObject.isReplyUser(currentUser); messageCell.isBot = currentUser != null && currentUser.bot; messageCell.isMegagroup = ChatObject.isChannel(currentChat) && currentChat.megagroup; - messageCell.isThreadChat = threadMessageId != 0 || currentChat != null && currentChat.forum && isTopic; + messageCell.isForum = ChatObject.isForum(currentChat); + messageCell.isForumGeneral = ChatObject.isForum(currentChat) && isTopic && getTopicId() == 1; + messageCell.isThreadChat = threadMessageId != 0 || messageCell.isForum && isTopic; messageCell.hasDiscussion = chatMode != MODE_SCHEDULED && ChatObject.isChannel(currentChat) && currentChat.has_link && !currentChat.megagroup; messageCell.isPinned = chatMode == 0 && (pinnedMessageObjects.containsKey(message.getId()) || groupedMessages != null && !groupedMessages.messages.isEmpty() && pinnedMessageObjects.containsKey(groupedMessages.messages.get(0).getId())); messageCell.linkedChatId = chatMode != MODE_SCHEDULED && chatInfo != null ? chatInfo.linked_chat_id : 0; @@ -28200,10 +28621,20 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } - private boolean openLinkInternally(String urlFinal, int fromMessageId) { + private boolean openLinkInternally(String urlFinal, ChatMessageCell cell, CharacterStyle span, int fromMessageId) { + return openLinkInternally(urlFinal, cell, span, fromMessageId, PROGRESS_LINK); + } + + private boolean openLinkInternally(String urlFinal, ChatMessageCell cell, CharacterStyle span, int fromMessageId, int fromMessageProgressType) { if (currentChat == null || urlFinal == null) { return false; } + Runnable setupProgressLoading = cell != null && (span != null || fromMessageProgressType != PROGRESS_LINK) ? () -> { + progressDialogAtMessageId = fromMessageId; + progressDialogAtMessageType = fromMessageProgressType; + progressDialogLinkSpan = span; + cell.invalidate(); + } : null; if (urlFinal.startsWith("tg:privatepost") || urlFinal.startsWith("tg://privatepost")) { String urlTmp = urlFinal.replace("tg:privatepost", "tg://telegram.org").replace("tg://privatepost", "tg://telegram.org"); Uri data = Uri.parse(urlTmp); @@ -28219,7 +28650,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatActivityDelegate.openReplyMessage(messageId); finishFragment(); } else { - scrollToMessageId(messageId, fromMessageId, true, 0, false, 0); + scrollToMessageId(messageId, fromMessageId, true, 0, false, 0, setupProgressLoading); } } return true; @@ -28258,7 +28689,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (startFromVideoTimestamp >= 0) { startFromVideoMessageId = messageId; } - scrollToMessageId(messageId, fromMessageId, true, 0, false, 0); + scrollToMessageId(messageId, fromMessageId, true, 0, false, 0, setupProgressLoading); } } return true; @@ -28277,7 +28708,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatActivityDelegate.openReplyMessage(messageId); finishFragment(); } else { - scrollToMessageId(messageId, fromMessageId, true, 0, false, 0); + scrollToMessageId(messageId, fromMessageId, true, 0, false, 0, setupProgressLoading); } return true; } @@ -28323,7 +28754,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatActivityDelegate.openReplyMessage(messageId); finishFragment(); } else { - scrollToMessageId(messageId, fromMessageId, true, 0, false, 0); + scrollToMessageId(messageId, fromMessageId, true, 0, false, 0, setupProgressLoading); } return true; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatEditActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatEditActivity.java index 325f26083..dbbb3f161 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatEditActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatEditActivity.java @@ -618,7 +618,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image cameraDrawable.setCurrentFrame(0, false); } - }); + }, 0); cameraDrawable.setCurrentFrame(0); cameraDrawable.setCustomEndFrame(43); setAvatarCell.imageView.playAnimation(); @@ -1110,7 +1110,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image } @Override - public void didUploadPhoto(final TLRPC.InputFile photo, final TLRPC.InputFile video, double videoStartTimestamp, String videoPath, final TLRPC.PhotoSize bigSize, final TLRPC.PhotoSize smallSize) { + public void didUploadPhoto(final TLRPC.InputFile photo, final TLRPC.InputFile video, double videoStartTimestamp, String videoPath, final TLRPC.PhotoSize bigSize, final TLRPC.PhotoSize smallSize, boolean isVideo) { AndroidUtilities.runOnUIThread(() -> { avatar = smallSize.location; if (photo != null || video != null) { @@ -1226,7 +1226,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image if (imageUpdater.isUploadingImage()) { createAfterUpload = true; - progressDialog = new AlertDialog(getParentActivity(), 3); + progressDialog = new AlertDialog(getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER); progressDialog.setOnCancelListener(dialog -> { createAfterUpload = false; progressDialog = null; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatLinkActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatLinkActivity.java index 0208698ba..b6a144b84 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatLinkActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatLinkActivity.java @@ -428,7 +428,7 @@ public class ChatLinkActivity extends BaseFragment implements NotificationCenter builder.setMessage(AndroidUtilities.replaceTags(message)); builder.setPositiveButton(LocaleController.getString("DiscussionUnlink", R.string.DiscussionUnlink), (dialogInterface, i) -> { if (!isChannel || info.linked_chat_id != 0) { - final AlertDialog[] progressDialog = new AlertDialog[]{new AlertDialog(getParentActivity(), 3)}; + final AlertDialog[] progressDialog = new AlertDialog[]{new AlertDialog(getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER)}; TLRPC.TL_channels_setDiscussionGroup req = new TLRPC.TL_channels_setDiscussionGroup(); if (isChannel) { req.broadcast = MessagesController.getInputChannel(currentChat); @@ -481,7 +481,7 @@ public class ChatLinkActivity extends BaseFragment implements NotificationCenter if (query) { getMessagesController().loadFullChat(chat.id, 0, true); waitingForFullChat = chat; - waitingForFullChatProgressAlert = new AlertDialog(getParentActivity(), 3); + waitingForFullChatProgressAlert = new AlertDialog(getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER); AndroidUtilities.runOnUIThread(() -> { if (waitingForFullChatProgressAlert == null) { return; @@ -561,7 +561,7 @@ public class ChatLinkActivity extends BaseFragment implements NotificationCenter }); return; } - final AlertDialog[] progressDialog = new AlertDialog[]{createFragment != null ? null : new AlertDialog(getParentActivity(), 3)}; + final AlertDialog[] progressDialog = new AlertDialog[]{createFragment != null ? null : new AlertDialog(getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER)}; TLRPC.TL_channels_setDiscussionGroup req = new TLRPC.TL_channels_setDiscussionGroup(); req.broadcast = MessagesController.getInputChannel(currentChat); req.group = MessagesController.getInputChannel(chat); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java index 7ea7f184f..c949e900c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java @@ -19,6 +19,7 @@ import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; import android.text.style.ForegroundColorSpan; +import android.util.Log; import android.util.SparseIntArray; import android.view.Gravity; import android.view.View; @@ -137,6 +138,9 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente private int participantsEndRow; private int participantsDividerRow; private int participantsDivider2Row; + private boolean hideMembersToggleLoading; + private int hideMembersRow; + private int hideMembersInfoRow; private int slowmodeRow; private int slowmodeSelectRow; @@ -241,6 +245,8 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente antiSpamInfoRow = -1; addNewRow = -1; addNew2Row = -1; + hideMembersRow = -1; + hideMembersInfoRow = -1; addNewSectionRow = -1; restricted1SectionRow = -1; participantsStartRow = -1; @@ -392,6 +398,10 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente loadingUserCellRow = rowCount++; } } else if (type == TYPE_USERS) { + if (!ChatObject.isChannelAndNotMegaGroup(currentChat) && !needOpenSearch) { + hideMembersRow = rowCount++; + hideMembersInfoRow = rowCount++; + } if (selectType == SELECT_TYPE_MEMBERS && ChatObject.canAddUsers(currentChat)) { addNewRow = rowCount++; } @@ -866,7 +876,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente getMessagesController().processUpdates((TLRPC.Updates) res, false); getMessagesController().putChatFull(info); } - if (err != null && !"".equals(err.text)) { + if (err != null && !"CHAT_NOT_MODIFIED".equals(err.text)) { AndroidUtilities.runOnUIThread(() -> { if (getParentActivity() == null) { return; @@ -880,6 +890,36 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente }); } return; + } else if (position == hideMembersRow) { + final TextCell textCell = (TextCell) view; + if (info != null && !info.participants_hidden && info.participants_count < getMessagesController().hiddenMembersGroupSizeMin) { + BulletinFactory.of(this).createSimpleBulletin(R.raw.contacts_sync_off, AndroidUtilities.replaceTags(LocaleController.formatPluralString("ChannelHiddenMembersForbidden", getMessagesController().hiddenMembersGroupSizeMin))).show(); + } else if (info != null && ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_BLOCK_USERS) && !hideMembersToggleLoading) { + hideMembersToggleLoading = true; + boolean wasParticipantsHidden = info.participants_hidden; + TLRPC.TL_channels_toggleParticipantsHidden req = new TLRPC.TL_channels_toggleParticipantsHidden(); + req.channel = getMessagesController().getInputChannel(chatId); + textCell.setChecked(req.enabled = (info.participants_hidden = !info.participants_hidden)); + textCell.getCheckBox().setIcon(ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_BLOCK_USERS) && (info == null || info.participants_hidden || info.participants_count >= getMessagesController().hiddenMembersGroupSizeMin) ? 0 : R.drawable.permission_locked); + getConnectionsManager().sendRequest(req, (res, err) -> { + if (res != null) { + getMessagesController().processUpdates((TLRPC.Updates) res, false); + getMessagesController().putChatFull(info); + } + if (err != null && !"CHAT_NOT_MODIFIED".equals(err.text)) { + AndroidUtilities.runOnUIThread(() -> { + if (getParentActivity() == null) { + return; + } + textCell.setChecked(info.participants_hidden = wasParticipantsHidden); + textCell.getCheckBox().setIcon(ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_BLOCK_USERS) && (info == null || !info.participants_hidden || info.participants_count >= getMessagesController().hiddenMembersGroupSizeMin) ? 0 : R.drawable.permission_locked); + BulletinFactory.of(ChatUsersActivity.this).createSimpleBulletin(R.raw.error, LocaleController.getString("UnknownError", R.string.UnknownError)).show(); + }); + } + hideMembersToggleLoading = false; + }); + } + return; } else if (position == removedUsersRow) { Bundle args = new Bundle(); args.putLong("chat_id", chatId); @@ -2910,7 +2950,18 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente } return true; } - return viewType == 0 || viewType == 2 || viewType == 6 || viewType == 12 && ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_DELETE_MESSAGES); + int position = holder.getAdapterPosition(); + if (viewType == 0 || viewType == 2 || viewType == 6) { + return true; + } + if (viewType == 12) { + if (position == antiSpamRow) { + return ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_DELETE_MESSAGES); + } else if (position == hideMembersRow) { + return ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_BLOCK_USERS); + } + } + return false; } @Override @@ -3153,6 +3204,9 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente } else { privacyCell.setText(LocaleController.formatString("SlowmodeInfoSelected", R.string.SlowmodeInfoSelected, formatSeconds(seconds))); } + } else if (position == hideMembersInfoRow) { + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + privacyCell.setText(LocaleController.getString("ChannelHideMembersInfo", R.string.ChannelHideMembersInfo)); } else if (position == gigaInfoRow) { privacyCell.setText(LocaleController.getString("BroadcastGroupConvertInfo", R.string.BroadcastGroupConvertInfo)); } @@ -3296,6 +3350,9 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente if (position == antiSpamRow) { textCell.getCheckBox().setIcon(ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_DELETE_MESSAGES) && (info == null || info.antispam || info.participants_count >= getMessagesController().telegramAntispamGroupSizeMin) ? 0 : R.drawable.permission_locked); textCell.setTextAndCheckAndIcon(LocaleController.getString("ChannelAntiSpam", R.string.ChannelAntiSpam), info != null && info.antispam, R.drawable.msg_policy, false); + } else if (position == hideMembersRow) { + textCell.getCheckBox().setIcon(ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_BLOCK_USERS) && (info == null || info.participants_hidden || info.participants_count >= getMessagesController().hiddenMembersGroupSizeMin) ? 0 : R.drawable.permission_locked); + textCell.setTextAndCheck(LocaleController.getString("ChannelHideMembers", R.string.ChannelHideMembers), info != null && info.participants_hidden, false); } break; } @@ -3320,7 +3377,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente return 3; } else if (position == restricted1SectionRow || position == permissionsSectionRow || position == slowmodeRow || position == gigaHeaderRow) { return 5; - } else if (position == participantsInfoRow || position == slowmodeInfoRow || position == gigaInfoRow || position == antiSpamInfoRow) { + } else if (position == participantsInfoRow || position == slowmodeInfoRow || position == gigaInfoRow || position == antiSpamInfoRow || position == hideMembersInfoRow) { return 1; } else if (position == blockedEmptyRow) { return 4; @@ -3337,7 +3394,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente return 10; } else if (position == loadingUserCellRow) { return 11; - } else if (position == antiSpamRow) { + } else if (position == antiSpamRow || position == hideMembersRow) { return 12; } return 0; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CodeNumberField.java b/TMessagesProj/src/main/java/org/telegram/ui/CodeNumberField.java index 18f43eddc..c2755a9df 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CodeNumberField.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CodeNumberField.java @@ -250,7 +250,11 @@ public class CodeNumberField extends EditTextBoldCursor { if (clipboard == null || clipboard.getPrimaryClipDescription() == null) { return false; } - clipboard.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); + ClipDescription description = clipboard.getPrimaryClipDescription(); + if (description == null) { + return false; + } + description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0); int i = -1; String text = item == null || item.getText() == null ? "" : item.getText().toString(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java index 74984526b..69dd43c58 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java @@ -70,6 +70,7 @@ import org.telegram.messenger.BuildVars; import org.telegram.messenger.ChatObject; import org.telegram.messenger.ContactsController; import org.telegram.messenger.DialogObject; +import org.telegram.messenger.Emoji; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessageObject; @@ -661,13 +662,27 @@ public class AlertsCreator { } public static AlertDialog.Builder createSimpleAlert(Context context, final String title, final String text, Theme.ResourcesProvider resourcesProvider) { + return createSimpleAlert(context, title, text, null, null, resourcesProvider); + } + + public static AlertDialog.Builder createSimpleAlert(Context context, final String title, final String text, String positiveButton, Runnable positive, Theme.ResourcesProvider resourcesProvider) { if (context == null || text == null) { return null; } AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(title == null ? LocaleController.getString("AppName", R.string.AppName) : title); builder.setMessage(text); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + if (positiveButton == null) { + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + } else { + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + builder.setPositiveButton(positiveButton, (dialog, which) -> { + dialog.dismiss(); + if (positive != null) { + positive.run(); + } + }); + } return builder; } @@ -1081,20 +1096,20 @@ public class AlertsCreator { } public static void showOpenUrlAlert(BaseFragment fragment, String url, boolean punycode, boolean ask) { - showOpenUrlAlert(fragment, url, punycode, true, ask, null); + showOpenUrlAlert(fragment, url, punycode, true, ask, null, null); } public static void showOpenUrlAlert(BaseFragment fragment, String url, boolean punycode, boolean ask, Theme.ResourcesProvider resourcesProvider) { - showOpenUrlAlert(fragment, url, punycode, true, ask, resourcesProvider); + showOpenUrlAlert(fragment, url, punycode, true, ask, null, resourcesProvider); } - public static void showOpenUrlAlert(BaseFragment fragment, String url, boolean punycode, boolean tryTelegraph, boolean ask, Theme.ResourcesProvider resourcesProvider) { + public static void showOpenUrlAlert(BaseFragment fragment, String url, boolean punycode, boolean tryTelegraph, boolean ask, Browser.Progress progress, Theme.ResourcesProvider resourcesProvider) { if (fragment == null || fragment.getParentActivity() == null) { return; } long inlineReturn = (fragment instanceof ChatActivity) ? ((ChatActivity) fragment).getInlineReturn() : 0; if (Browser.isInternalUrl(url, null) || !ask) { - Browser.openUrl(fragment.getParentActivity(), url, inlineReturn == 0, tryTelegraph); + Browser.openUrl(fragment.getParentActivity(), Uri.parse(url), inlineReturn == 0, tryTelegraph, progress); } else { String urlFinal; if (punycode) { @@ -1119,7 +1134,7 @@ public class AlertsCreator { } builder.setMessage(stringBuilder); builder.setMessageTextViewClickable(false); - builder.setPositiveButton(LocaleController.getString("Open", R.string.Open), (dialogInterface, i) -> Browser.openUrl(fragment.getParentActivity(), url, inlineReturn == 0, tryTelegraph)); + builder.setPositiveButton(LocaleController.getString("Open", R.string.Open), (dialogInterface, i) -> Browser.openUrl(fragment.getParentActivity(), Uri.parse(url), inlineReturn == 0, tryTelegraph, progress)); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); fragment.showDialog(builder.create()); } @@ -1190,7 +1205,7 @@ public class AlertsCreator { } } if (supportUser == null) { - final AlertDialog progressDialog = new AlertDialog(fragment.getParentActivity(), 3); + final AlertDialog progressDialog = new AlertDialog(fragment.getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER); progressDialog.setCanCancel(false); progressDialog.show(); TLRPC.TL_help_getSupport req = new TLRPC.TL_help_getSupport(); @@ -1343,7 +1358,14 @@ public class AlertsCreator { CheckBoxCell[] cell = new CheckBoxCell[1]; - TextView messageTextView = new TextView(context); + TextView messageTextView = new TextView(context) { + @Override + public void setText(CharSequence text, BufferType type) { + text = Emoji.replaceEmoji(text, getPaint().getFontMetricsInt(), false); + super.setText(text, type); + } + }; + NotificationCenter.listenEmojiLoading(messageTextView); messageTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); messageTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); messageTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); @@ -1362,7 +1384,7 @@ public class AlertsCreator { builder.setView(frameLayout); AvatarDrawable avatarDrawable = new AvatarDrawable(); - avatarDrawable.setTextSize(AndroidUtilities.dp(12)); + avatarDrawable.setTextSize(AndroidUtilities.dp(18)); BackupImageView imageView = new BackupImageView(context); imageView.setRoundRadius(AndroidUtilities.dp(20)); @@ -1619,7 +1641,14 @@ public class AlertsCreator { CheckBoxCell[] cell = new CheckBoxCell[1]; - TextView messageTextView = new TextView(context); + TextView messageTextView = new TextView(context) { + @Override + public void setText(CharSequence text, BufferType type) { + text = Emoji.replaceEmoji(text, getPaint().getFontMetricsInt(), false); + super.setText(text, type); + } + }; + NotificationCenter.listenEmojiLoading(messageTextView); messageTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); messageTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); messageTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); @@ -1730,7 +1759,14 @@ public class AlertsCreator { message = LocaleController.formatString("CallAlert", R.string.CallAlert, UserObject.getUserName(user)); } - TextView messageTextView = new TextView(context); + TextView messageTextView = new TextView(context) { + @Override + public void setText(CharSequence text, BufferType type) { + text = Emoji.replaceEmoji(text, getPaint().getFontMetricsInt(), false); + super.setText(text, type); + } + }; + NotificationCenter.listenEmojiLoading(messageTextView); messageTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); messageTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); messageTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); @@ -2115,6 +2151,52 @@ public class AlertsCreator { baseFragment.showDialog(builder.create()); } + public static void createContactInviteDialog(BaseFragment parentFragment, String fisrtName, String lastName, String phone) { + AlertDialog.Builder builder = new AlertDialog.Builder(parentFragment.getParentActivity()); + builder.setTitle(LocaleController.getString("ContactNotRegisteredTitle", R.string.ContactNotRegisteredTitle)); + builder.setMessage(LocaleController.formatString("ContactNotRegistered", R.string.ContactNotRegistered, ContactsController.formatName(fisrtName, lastName))); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + builder.setPositiveButton(LocaleController.getString("Invite", R.string.Invite), (dialog, which) -> { + try { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.fromParts("sms", phone, null)); + intent.putExtra("sms_body", ContactsController.getInstance(parentFragment.getCurrentAccount()).getInviteText(1)); + parentFragment.getParentActivity().startActivityForResult(intent, 500); + } catch (Exception e) { + FileLog.e(e); + } + }); + parentFragment.showDialog(builder.create()); + } + + public static ActionBarPopupWindow createSimplePopup(BaseFragment fragment, View popupView, View anhcorView, float x, float y) { + if (fragment == null || anhcorView == null || popupView == null) { + return null; + } + ActionBarPopupWindow popupWindow = new ActionBarPopupWindow(popupView, LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT); + popupWindow.setPauseNotifications(true); + popupWindow.setDismissAnimationDuration(220); + popupWindow.setOutsideTouchable(true); + popupWindow.setClippingEnabled(true); + popupWindow.setAnimationStyle(R.style.PopupContextAnimation); + popupWindow.setFocusable(true); + popupView.measure(View.MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(1000), View.MeasureSpec.AT_MOST), View.MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(1000), View.MeasureSpec.AT_MOST)); + popupWindow.setInputMethodMode(ActionBarPopupWindow.INPUT_METHOD_NOT_NEEDED); + popupWindow.getContentView().setFocusableInTouchMode(true); + float viewX = 0, viewY = 0; + View child = anhcorView; + while (child != anhcorView.getRootView()) { + viewX += child.getX(); + viewY += child.getY(); + child = (View) child.getParent(); + if (child == null) { + break; + } + } + popupWindow.showAtLocation(anhcorView.getRootView(), 0, (int) (viewX + x - popupView.getMeasuredWidth() / 2f), (int) (viewY + y - popupView.getMeasuredHeight() / 2f)); + popupWindow.dimBehind(); + return popupWindow; + } + public interface BlockDialogCallback { void run(boolean report, boolean delete); } @@ -2459,6 +2541,8 @@ public class AlertsCreator { return LocaleController.formatPluralString("Hours", value); } }; + hourPicker.setWrapSelectorWheel(true); + hourPicker.setAllItemsCount(24); hourPicker.setItemCount(5); hourPicker.setTextColor(datePickerColors.textColor); hourPicker.setTextOffset(-AndroidUtilities.dp(10)); @@ -2468,6 +2552,8 @@ public class AlertsCreator { return LocaleController.formatPluralString("Minutes", value); } }; + minutePicker.setWrapSelectorWheel(true); + minutePicker.setAllItemsCount(60); minutePicker.setItemCount(5); minutePicker.setTextColor(datePickerColors.textColor); minutePicker.setTextOffset(-AndroidUtilities.dp(34)); @@ -3737,6 +3823,44 @@ public class AlertsCreator { return builder.create(); } + public static BottomSheet createMuteAlert(BaseFragment fragment, ArrayList dialog_ids, int topicId, Theme.ResourcesProvider resourcesProvider) { + if (fragment == null || fragment.getParentActivity() == null) { + return null; + } + + BottomSheet.Builder builder = new BottomSheet.Builder(fragment.getParentActivity(), false, resourcesProvider); + builder.setTitle(LocaleController.getString("Notifications", R.string.Notifications), true); + CharSequence[] items = new CharSequence[]{ + LocaleController.formatString("MuteFor", R.string.MuteFor, LocaleController.formatPluralString("Hours", 1)), + LocaleController.formatString("MuteFor", R.string.MuteFor, LocaleController.formatPluralString("Hours", 8)), + LocaleController.formatString("MuteFor", R.string.MuteFor, LocaleController.formatPluralString("Days", 2)), + LocaleController.getString("MuteDisable", R.string.MuteDisable) + }; + builder.setItems(items, (dialogInterface, i) -> { + int setting; + if (i == 0) { + setting = NotificationsController.SETTING_MUTE_HOUR; + } else if (i == 1) { + setting = NotificationsController.SETTING_MUTE_8_HOURS; + } else if (i == 2) { + setting = NotificationsController.SETTING_MUTE_2_DAYS; + } else { + setting = NotificationsController.SETTING_MUTE_FOREVER; + } + if (dialog_ids != null) { + for (int j = 0; j < dialog_ids.size(); ++j) { + long dialog_id = dialog_ids.get(j); + NotificationsController.getInstance(UserConfig.selectedAccount).setDialogNotificationsSettings(dialog_id, topicId, setting); + } + } + if (BulletinFactory.canShowBulletin(fragment)) { + BulletinFactory.createMuteBulletin(fragment, setting, 0, resourcesProvider).show(); + } + } + ); + return builder.create(); + } + public static void sendReport(TLRPC.InputPeer peer, int type, String message, ArrayList messages) { TLRPC.TL_messages_report request = new TLRPC.TL_messages_report(); request.peer = peer; @@ -4572,69 +4696,11 @@ public class AlertsCreator { } public static Dialog createFreeSpaceDialog(final LaunchActivity parentActivity) { - final int[] selected = new int[1]; - - if (SharedConfig.keepMedia == 2) { - selected[0] = 3; - } else if (SharedConfig.keepMedia == 0) { - selected[0] = 1; - } else if (SharedConfig.keepMedia == 1) { - selected[0] = 2; - } else if (SharedConfig.keepMedia == 3) { - selected[0] = 0; - } - - String[] descriptions = new String[]{ - LocaleController.formatPluralString("Days", 3), - LocaleController.formatPluralString("Weeks", 1), - LocaleController.formatPluralString("Months", 1), - LocaleController.getString("LowDiskSpaceNeverRemove", R.string.LowDiskSpaceNeverRemove) - }; - - final LinearLayout linearLayout = new LinearLayout(parentActivity); - linearLayout.setOrientation(LinearLayout.VERTICAL); - - TextView titleTextView = new TextView(parentActivity); - titleTextView.setText(LocaleController.getString("LowDiskSpaceTitle2", R.string.LowDiskSpaceTitle2)); - titleTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); - titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - titleTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - titleTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); - linearLayout.addView(titleTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 24, 0, 24, 8)); - - for (int a = 0; a < descriptions.length; a++) { - RadioColorCell cell = new RadioColorCell(parentActivity); - cell.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), 0); - cell.setTag(a); - cell.setCheckColor(Theme.getColor(Theme.key_radioBackground), Theme.getColor(Theme.key_dialogRadioBackgroundChecked)); - cell.setTextAndValue(descriptions[a], selected[0] == a); - linearLayout.addView(cell); - cell.setOnClickListener(v -> { - int num = (Integer) v.getTag(); - if (num == 0) { - selected[0] = 3; - } else if (num == 1) { - selected[0] = 0; - } else if (num == 2) { - selected[0] = 1; - } else if (num == 3) { - selected[0] = 2; - } - int count = linearLayout.getChildCount(); - for (int a1 = 0; a1 < count; a1++) { - View child = linearLayout.getChildAt(a1); - if (child instanceof RadioColorCell) { - ((RadioColorCell) child).setChecked(child == v, true); - } - } - }); - } AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); builder.setTitle(LocaleController.getString("LowDiskSpaceTitle", R.string.LowDiskSpaceTitle)); - builder.setMessage(LocaleController.getString("LowDiskSpaceMessage", R.string.LowDiskSpaceMessage)); - builder.setView(linearLayout); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), (dialog, which) -> SharedConfig.setKeepMedia(selected[0])); - builder.setNeutralButton(LocaleController.getString("ClearMediaCache", R.string.ClearMediaCache), (dialog, which) -> parentActivity.presentFragment(new CacheControlActivity())); + builder.setMessage(LocaleController.getString("LowDiskSpaceMessage2", R.string.LowDiskSpaceMessage2)); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + builder.setPositiveButton(LocaleController.getString("LowDiskSpaceButton", R.string.LowDiskSpaceButton), (dialog, which) -> parentActivity.presentFragment(new CacheControlActivity())); return builder.create(); } @@ -5059,7 +5125,7 @@ public class AlertsCreator { } if ((actionUser != null && actionUser.id != UserConfig.getInstance(currentAccount).getClientUserId()) || (actionChat != null && !ChatObject.hasAdminRights(actionChat))) { if (loadParticipant == 1 && !chat.creator && actionUser != null) { - final AlertDialog[] progressDialog = new AlertDialog[]{new AlertDialog(activity, 3)}; + final AlertDialog[] progressDialog = new AlertDialog[]{new AlertDialog(activity, AlertDialog.ALERT_TYPE_SPINNER)}; TLRPC.TL_channels_getParticipant req = new TLRPC.TL_channels_getParticipant(); req.channel = MessagesController.getInputChannel(chat); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedEmojiDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedEmojiDrawable.java index 9fd1fac3f..75729d739 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedEmojiDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedEmojiDrawable.java @@ -11,7 +11,6 @@ import android.graphics.drawable.Drawable; import android.os.Looper; import android.text.TextUtils; import android.view.View; -import android.view.animation.LinearInterpolator; import android.view.animation.OvershootInterpolator; import androidx.annotation.NonNull; @@ -40,6 +39,7 @@ import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.Premium.PremiumLockIconView; +import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -60,10 +60,19 @@ public class AnimatedEmojiDrawable extends Drawable { public static final int CACHE_TYPE_ALERT_EMOJI_STATUS = 9; public static final int CACHE_TYPE_FORUM_TOPIC = 10; public static final int CACHE_TYPE_FORUM_TOPIC_LARGE = 11; + public static final int CACHE_TYPE_RENDERING_VIDEO = 12; + + public int rawDrawIndex; private static HashMap> globalEmojiCache; + @NonNull public static AnimatedEmojiDrawable make(int account, int cacheType, long documentId) { + return make(account, cacheType, documentId, null); + } + + @NonNull + public static AnimatedEmojiDrawable make(int account, int cacheType, long documentId, String absolutePath) { if (globalEmojiCache == null) { globalEmojiCache = new HashMap<>(); } @@ -74,7 +83,7 @@ public class AnimatedEmojiDrawable extends Drawable { } AnimatedEmojiDrawable drawable = cache.get(documentId); if (drawable == null) { - cache.put(documentId, drawable = new AnimatedEmojiDrawable(cacheType, account, documentId)); + cache.put(documentId, drawable = new AnimatedEmojiDrawable(cacheType, account, documentId, absolutePath)); } return drawable; } @@ -150,14 +159,18 @@ public class AnimatedEmojiDrawable extends Drawable { } public void fetchDocument(long id, ReceivedDocument onDone) { - checkThread(); - if (emojiDocumentsCache != null) { - TLRPC.Document cacheDocument = emojiDocumentsCache.get(id); - if (cacheDocument != null) { - onDone.run(cacheDocument); - return; + synchronized (this) { + if (emojiDocumentsCache != null) { + TLRPC.Document cacheDocument = emojiDocumentsCache.get(id); + if (cacheDocument != null) { + onDone.run(cacheDocument); + return; + } } } + if (!checkThread()) { + return; + } if (onDone != null) { if (loadingDocuments == null) { loadingDocuments = new HashMap<>(); @@ -186,10 +199,14 @@ public class AnimatedEmojiDrawable extends Drawable { }); } - private void checkThread() { - if (BuildVars.DEBUG_VERSION && Thread.currentThread() != Looper.getMainLooper().getThread()) { - throw new IllegalStateException("Wrong thread"); + private boolean checkThread() { + if (Thread.currentThread() != Looper.getMainLooper().getThread()) { + if (BuildVars.DEBUG_VERSION) { + FileLog.e("EmojiDocumentFetcher", new IllegalStateException("Wrong thread")); + } + return false; } + return true; } private void loadFromDatabase(ArrayList emojiToLoad) { @@ -284,14 +301,13 @@ public class AnimatedEmojiDrawable extends Drawable { } public void processDocuments(ArrayList documents) { - checkThread(); + if (!checkThread()) { + return; + } for (int i = 0; i < documents.size(); ++i) { if (documents.get(i) instanceof TLRPC.Document) { TLRPC.Document document = (TLRPC.Document) documents.get(i); - if (emojiDocumentsCache == null) { - emojiDocumentsCache = new HashMap<>(); - } - emojiDocumentsCache.put(document.id, document); + putDocument(document); if (loadingDocuments != null) { ArrayList loadingCallbacks = loadingDocuments.remove(document.id); if (loadingCallbacks != null) { @@ -305,15 +321,29 @@ public class AnimatedEmojiDrawable extends Drawable { } } - public TLRPC.InputStickerSet findStickerSet(long documentId) { - if (emojiDocumentsCache == null) { - return null; - } - TLRPC.Document document = emojiDocumentsCache.get(documentId); + public void putDocument(TLRPC.Document document) { if (document == null) { - return null; + return; + } + synchronized (this) { + if (emojiDocumentsCache == null) { + emojiDocumentsCache = new HashMap<>(); + } + emojiDocumentsCache.put(document.id, document); + } + } + + public TLRPC.InputStickerSet findStickerSet(long documentId) { + synchronized (this) { + if (emojiDocumentsCache == null) { + return null; + } + TLRPC.Document document = emojiDocumentsCache.get(documentId); + if (document == null) { + return null; + } + return MessageObject.getInputStickerSet(document); } - return MessageObject.getInputStickerSet(document); } } @@ -340,6 +370,7 @@ public class AnimatedEmojiDrawable extends Drawable { private long documentId; private int cacheType; private int currentAccount; + private String absolutePath; private ImageReceiver imageReceiver; private float alpha = 1f; @@ -351,7 +382,19 @@ public class AnimatedEmojiDrawable extends Drawable { this.documentId = documentId; getDocumentFetcher(currentAccount).fetchDocument(documentId, document -> { this.document = document; - this.initDocument(); + this.initDocument(false); + }); + } + + public AnimatedEmojiDrawable(int cacheType, int currentAccount, long documentId, String absolutePath) { + this.currentAccount = currentAccount; + this.cacheType = cacheType; + updateSize(); + this.documentId = documentId; + this.absolutePath = absolutePath; + getDocumentFetcher(currentAccount).fetchDocument(documentId, document -> { + this.document = document; + this.initDocument(false); }); } @@ -360,7 +403,7 @@ public class AnimatedEmojiDrawable extends Drawable { this.currentAccount = currentAccount; this.document = document; updateSize(); - this.initDocument(); + this.initDocument(false); } private void updateSize() { @@ -382,34 +425,46 @@ public class AnimatedEmojiDrawable extends Drawable { return this.document; } - private void initDocument() { - if (document == null || imageReceiver != null) { + private void initDocument(boolean force) { + if (document == null || (imageReceiver != null && !force)) { return; } - imageReceiver = new ImageReceiver() { - @Override - public void invalidate() { - AnimatedEmojiDrawable.this.invalidate(); - super.invalidate(); - } + if (imageReceiver == null) { + imageReceiver = new ImageReceiver() { + @Override + public void invalidate() { + AnimatedEmojiDrawable.this.invalidate(); + super.invalidate(); + } - @Override - protected boolean setImageBitmapByKey(Drawable drawable, String key, int type, boolean memCache, int guid) { - AnimatedEmojiDrawable.this.invalidate(); - return super.setImageBitmapByKey(drawable, key, type, memCache, guid); - } + @Override + protected boolean setImageBitmapByKey(Drawable drawable, String key, int type, boolean memCache, int guid) { + AnimatedEmojiDrawable.this.invalidate(); + return super.setImageBitmapByKey(drawable, key, type, memCache, guid); + } + }; }; + if (cacheType == CACHE_TYPE_RENDERING_VIDEO) { + imageReceiver.ignoreNotifications = true; + } if (colorFilterToSet != null && canOverrideColor()) { imageReceiver.setColorFilter(colorFilterToSet); } if (cacheType != 0) { + int cacheType = this.cacheType; + if (cacheType == CACHE_TYPE_RENDERING_VIDEO) { + cacheType = CACHE_TYPE_KEYBOARD; + } imageReceiver.setUniqKeyPrefix(cacheType + "_"); } imageReceiver.setVideoThumbIsSame(true); boolean onlyStaticPreview = SharedConfig.getDevicePerformanceClass() == SharedConfig.PERFORMANCE_CLASS_LOW && (cacheType == CACHE_TYPE_KEYBOARD || cacheType == CACHE_TYPE_ALERT_PREVIEW || cacheType == CACHE_TYPE_ALERT_PREVIEW_TAB_STRIP); String filter = sizedp + "_" + sizedp; - if (cacheType != STANDARD_LOTTIE_FRAME && (cacheType != CACHE_TYPE_MESSAGES_LARGE || SharedConfig.getDevicePerformanceClass() < SharedConfig.PERFORMANCE_CLASS_HIGH)) { + if (cacheType == CACHE_TYPE_RENDERING_VIDEO) { + filter += "_d_nostream"; + } + if (cacheType != STANDARD_LOTTIE_FRAME && (cacheType != CACHE_TYPE_MESSAGES_LARGE || SharedConfig.getDevicePerformanceClass() < SharedConfig.PERFORMANCE_CLASS_HIGH) && cacheType != CACHE_TYPE_RENDERING_VIDEO) { filter += "_pcache"; } if (cacheType != CACHE_TYPE_MESSAGES && cacheType != CACHE_TYPE_MESSAGES_LARGE) { @@ -451,12 +506,24 @@ public class AnimatedEmojiDrawable extends Drawable { if (onlyStaticPreview) { mediaLocation = null; } - if (cacheType == STANDARD_LOTTIE_FRAME) { + + if (absolutePath != null) { + imageReceiver.setImageBitmap(new AnimatedFileDrawable(new File(absolutePath), true, 0, null, null, null, 0, currentAccount, true, 512, 512, null)); + } else if (cacheType == STANDARD_LOTTIE_FRAME) { imageReceiver.setImage(null, null, mediaLocation, mediaFilter, null, null, thumbDrawable, document.size, null, document, 1); } else { - imageReceiver.setImage(mediaLocation, mediaFilter, ImageLocation.getForDocument(thumb, document), sizedp + "_" + sizedp, null, null, thumbDrawable, document.size, null, document, 1); + if (SharedConfig.getLiteMode().enabled()) { + if ("video/webm".equals(document.mime_type)) { + imageReceiver.setImage(null, null, ImageLocation.getForDocument(thumb, document), sizedp + "_" + sizedp, null, null, thumbDrawable, document.size, null, document, 1); + } else { + imageReceiver.setImage(mediaLocation, mediaFilter + "_firstframe", ImageLocation.getForDocument(thumb, document), sizedp + "_" + sizedp, null, null, thumbDrawable, document.size, null, document, 1); + } + } else { + imageReceiver.setImage(mediaLocation, mediaFilter, ImageLocation.getForDocument(thumb, document), sizedp + "_" + sizedp, null, null, thumbDrawable, document.size, null, document, 1); + } } + if (cacheType == CACHE_TYPE_EMOJI_STATUS || cacheType == CACHE_TYPE_ALERT_EMOJI_STATUS || cacheType == CACHE_TYPE_FORUM_TOPIC) { imageReceiver.setAutoRepeatCount(2); } else if (cacheType == CACHE_TYPE_FORUM_TOPIC_LARGE) { @@ -470,14 +537,14 @@ public class AnimatedEmojiDrawable extends Drawable { imageReceiver.setLayerNum(6656); } imageReceiver.setAspectFit(true); - if (cacheType != STANDARD_LOTTIE_FRAME) { - imageReceiver.setAllowStartLottieAnimation(true); - imageReceiver.setAllowStartAnimation(true); - imageReceiver.setAutoRepeat(1); - } else { + if (cacheType == CACHE_TYPE_RENDERING_VIDEO || cacheType == STANDARD_LOTTIE_FRAME) { imageReceiver.setAllowStartAnimation(false); imageReceiver.setAllowStartLottieAnimation(false); imageReceiver.setAutoRepeat(0); + } else { + imageReceiver.setAllowStartLottieAnimation(true); + imageReceiver.setAllowStartAnimation(true); + imageReceiver.setAutoRepeat(1); } imageReceiver.setAllowDecodeSingleFrame(true); int roundRadius = 0; @@ -522,66 +589,51 @@ public class AnimatedEmojiDrawable extends Drawable { @Override public void draw(@NonNull Canvas canvas) { - draw(canvas, true); + if (imageReceiver == null) { + return; + } + imageReceiver.setImageCoords(getBounds()); + imageReceiver.setAlpha(alpha); + imageReceiver.draw(canvas); } - public void draw(@NonNull Canvas canvas, boolean canTranslate) { - if (imageReceiver != null) { + public void drawRaw(Canvas canvas, boolean nextFrame, int fps) { + if (imageReceiver == null) { + return; + } + if (imageReceiver.getLottieAnimation() != null) { + RLottieDrawable rlottie = imageReceiver.getLottieAnimation(); + if (nextFrame) { + int inc = (int) Math.round((float) rlottie.getFramesCount() / (rlottie.getDuration() / 1000f) / 30f); + rlottie.currentFrame = (rlottie.currentFrame + inc) % rlottie.getFramesCount(); + } + rlottie.setBounds(getBounds()); + rlottie.drawFrame(canvas, rlottie.currentFrame); + } else if (imageReceiver.getAnimation() != null) { + AnimatedFileDrawable webp = imageReceiver.getAnimation(); + webp.drawFrame(canvas, nextFrame ? fps / 30 : 0); + } else { imageReceiver.setImageCoords(getBounds()); imageReceiver.setAlpha(alpha); imageReceiver.draw(canvas); - } else { - shouldDrawPlaceholder = true; } - drawPlaceholder(canvas, getBounds().centerX(), getBounds().centerY(), getBounds().width() / 2f); } public void draw(Canvas canvas, Rect drawableBounds, float alpha) { - if (imageReceiver != null) { - imageReceiver.setImageCoords(drawableBounds); - imageReceiver.setAlpha(alpha); - imageReceiver.draw(canvas); - } else { - shouldDrawPlaceholder = true; - } - if (drawableBounds != null) { - drawPlaceholder(canvas, drawableBounds.centerX(), drawableBounds.centerY(), drawableBounds.width() / 2f); + if (imageReceiver == null) { + return; } + imageReceiver.setImageCoords(drawableBounds); + imageReceiver.setAlpha(alpha); + imageReceiver.draw(canvas); } public void draw(Canvas canvas, ImageReceiver.BackgroundThreadDrawHolder backgroundThreadDrawHolder, boolean canTranslate) { - if (imageReceiver != null) { - imageReceiver.setAlpha(alpha); - imageReceiver.draw(canvas, backgroundThreadDrawHolder); - } else { - shouldDrawPlaceholder = true; + if (imageReceiver == null) { + return; } - if (backgroundThreadDrawHolder != null) { - drawPlaceholder(canvas, backgroundThreadDrawHolder.imageX + backgroundThreadDrawHolder.imageW / 2, backgroundThreadDrawHolder.imageY + backgroundThreadDrawHolder.imageH / 2, backgroundThreadDrawHolder.imageW / 2); - } - } - - private AnimatedFloat placeholderAlpha = new AnimatedFloat(1f, this::invalidate, 0, 150, new LinearInterpolator()); - private boolean shouldDrawPlaceholder = false; - private void drawPlaceholder(Canvas canvas, float cx, float cy, float r) { -// if (!shouldDrawPlaceholder) { -// return; -// } -// float alpha = placeholderAlpha.set(imageReceiver == null ? 1f : 0f); -// if (alpha < 0) { -// if (imageReceiver != null) { -// shouldDrawPlaceholder = false; -// } -// return; -// } -// if (placeholderPaint == null) { -// placeholderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); -// placeholderPaint.setColor(Theme.isCurrentThemeDark() ? 0x0fffffff : 0x0f000000); -// } -// int wasAlpha = placeholderPaint.getAlpha(); -// placeholderPaint.setAlpha((int) (wasAlpha * alpha)); -// canvas.drawCircle(cx, cy, r, placeholderPaint); -// placeholderPaint.setAlpha(wasAlpha); + imageReceiver.setAlpha(alpha); + imageReceiver.draw(canvas, backgroundThreadDrawHolder); } public void addView(View callback) { @@ -1095,4 +1147,18 @@ public class AnimatedEmojiDrawable extends Drawable { } } } + + public static void lightModeChanged() { + for (HashMap map :globalEmojiCache.values()) { + ArrayList set = new ArrayList(map.keySet()); + for (Long documentId : set) { + AnimatedEmojiDrawable animatedEmojiDrawable = map.get(documentId); + if (animatedEmojiDrawable.attached) { + animatedEmojiDrawable.initDocument(true); + } else { + map.remove(documentId); + } + } + } + } } \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedEmojiSpan.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedEmojiSpan.java index c2622d880..f4ced2de1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedEmojiSpan.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedEmojiSpan.java @@ -21,6 +21,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.Emoji; import org.telegram.messenger.ImageReceiver; import org.telegram.messenger.MessageObject; +import org.telegram.messenger.SharedConfig; import org.telegram.messenger.UserConfig; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.Theme; @@ -42,6 +43,7 @@ public class AnimatedEmojiSpan extends ReplacementSpan { private Paint.FontMetricsInt fontMetrics; private float size = AndroidUtilities.dp(20); public int cacheType = -1; + public String documentAbsolutePath; int measuredSize; boolean spanDrawn; @@ -87,6 +89,16 @@ public class AnimatedEmojiSpan extends ReplacementSpan { return document != null ? document.id : documentId; } + public void replaceFontMetrics(Paint.FontMetricsInt newMetrics) { + this.fontMetrics = newMetrics; + if (fontMetrics != null) { + size = Math.abs(fontMetrics.descent) + Math.abs(fontMetrics.ascent); + if (size == 0) { + size = AndroidUtilities.dp(20); + } + } + } + public void replaceFontMetrics(Paint.FontMetricsInt newMetrics, int newSize, int cacheType) { fontMetrics = newMetrics; size = newSize; @@ -203,6 +215,40 @@ public class AnimatedEmojiSpan extends ReplacementSpan { } } + public static void drawRawAnimatedEmojis(Canvas canvas, Layout layout, EmojiGroupedSpans stack, float offset, List spoilers, float boundTop, float boundBottom, float drawingYOffset, float alpha, int fps) { + if (canvas == null || layout == null || stack == null) { + return; + } + + boolean needRestore = false; + if (Emoji.emojiDrawingYOffset != 0 || offset != 0) { + needRestore = true; + canvas.save(); + canvas.translate(0, Emoji.emojiDrawingYOffset + AndroidUtilities.dp(20 * offset)); + } + + stack.rawIndex++; + for (int k = 0; k < stack.holders.size(); ++k) { + AnimatedEmojiHolder holder = stack.holders.get(k); + float halfSide = holder.span.measuredSize / 2f; + float cx, cy; + cx = holder.span.lastDrawnCx; + cy = holder.span.lastDrawnCy; + holder.drawableBounds.set((int) (cx - halfSide), (int) (cy - halfSide), (int) (cx + halfSide), (int) (cy + halfSide)); + holder.drawable.setBounds(holder.drawableBounds); + boolean nextFrame = false; + if (holder.drawable.rawDrawIndex < stack.rawIndex) { + holder.drawable.rawDrawIndex = stack.rawIndex; + nextFrame = true; + } + holder.drawable.drawRaw(canvas, nextFrame, fps); + } + + if (needRestore) { + canvas.restore(); + } + } + private static boolean isInsideSpoiler(Layout layout, int start, int end) { if (layout == null || !(layout.getText() instanceof Spanned)) { return false; @@ -233,6 +279,7 @@ public class AnimatedEmojiSpan extends ReplacementSpan { public float alpha; public SpansChunk spansChunk; public boolean insideSpoiler; + private int rawIndex; private ImageReceiver.BackgroundThreadDrawHolder[] backgroundDrawHolder = new ImageReceiver.BackgroundThreadDrawHolder[DrawingInBackgroundThreadDrawable.THREAD_COUNT]; @@ -370,7 +417,9 @@ public class AnimatedEmojiSpan extends ReplacementSpan { holder = new AnimatedEmojiHolder(view, invalidateParent); holder.layout = textLayout; int localCacheType = span.standard ? AnimatedEmojiDrawable.STANDARD_LOTTIE_FRAME : (span.cacheType < 0 ? cacheType : span.cacheType); - if (span.document != null) { + if (span.documentAbsolutePath != null) { + holder.drawable = AnimatedEmojiDrawable.make(UserConfig.selectedAccount, localCacheType, span.getDocumentId(), span.documentAbsolutePath); + } else if (span.document != null) { holder.drawable = AnimatedEmojiDrawable.make(UserConfig.selectedAccount, localCacheType, span.document); } else { holder.drawable = AnimatedEmojiDrawable.make(UserConfig.selectedAccount, localCacheType, span.documentId); @@ -561,6 +610,7 @@ public class AnimatedEmojiSpan extends ReplacementSpan { public ArrayList holders = new ArrayList<>(); HashMap groupedByLayout = new HashMap<>(); ArrayList backgroundDrawingArray = new ArrayList<>(); + private int rawIndex; public boolean isEmpty() { return holders.isEmpty(); @@ -625,6 +675,14 @@ public class AnimatedEmojiSpan extends ReplacementSpan { } } + public void incrementFrames(int inc) { + for (int i = 0; i < holders.size(); i++) { + if (holders.get(i).drawable != null && holders.get(i).drawable.getImageReceiver() != null) { + holders.get(i).drawable.getImageReceiver().incrementFrames(inc); + } + } + } + public void recordPositions(boolean record) { for (int i = 0; i < holders.size(); i++) { holders.get(i).span.recordPositions = record; @@ -672,13 +730,13 @@ public class AnimatedEmojiSpan extends ReplacementSpan { } private void checkBackgroundRendering() { - if (allowBackgroundRendering && holders.size() >= 10 && backgroundThreadDrawable == null) { + if (allowBackgroundRendering && holders.size() >= 10 && backgroundThreadDrawable == null && !SharedConfig.getLiteMode().enabled()) { backgroundThreadDrawable = new DrawingInBackgroundThreadDrawable() { private final ArrayList backgroundHolders = new ArrayList<>(); @Override - public void drawInBackground(Canvas canvas) { + public void drawInBackground(Canvas canvas) { for (int i = 0; i < backgroundHolders.size(); i++) { AnimatedEmojiHolder holder = backgroundHolders.get(i); if (holder != null && holder.backgroundDrawHolder[threadIndex] != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedFileDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedFileDrawable.java index 98e6e64f1..b0e2614bc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedFileDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedFileDrawable.java @@ -481,9 +481,7 @@ public class AnimatedFileDrawable extends BitmapDrawable implements Animatable, if (!precise) { seekToMs(nativePtr, ms, precise); } - if (backgroundBitmap == null) { - backgroundBitmap = Bitmap.createBitmap((int) (metaData[0] * scaleFactor), (int) (metaData[1] * scaleFactor), Bitmap.Config.ARGB_8888); - } + Bitmap backgroundBitmap = Bitmap.createBitmap((int) (metaData[0] * scaleFactor), (int) (metaData[1] * scaleFactor), Bitmap.Config.ARGB_8888); int result; if (precise) { result = getFrameAtTime(nativePtr, ms, backgroundBitmap, metaData, backgroundBitmap.getRowBytes()); @@ -616,13 +614,12 @@ public class AnimatedFileDrawable extends BitmapDrawable implements Animatable, ArrayList bitmapToRecycle = new ArrayList<>(); bitmapToRecycle.add(renderingBitmap); bitmapToRecycle.add(nextRenderingBitmap); + bitmapToRecycle.add(backgroundBitmap); + + renderingBitmap = null; + nextRenderingBitmap = null; + backgroundBitmap = null; - if (renderingBitmap != null) { - renderingBitmap = null; - } - if (nextRenderingBitmap != null) { - nextRenderingBitmap = null; - } if (decodeQueue != null) { decodeQueue.recycle(); decodeQueue = null; @@ -1096,6 +1093,21 @@ public class AnimatedFileDrawable extends BitmapDrawable implements Animatable, return bitmap; } + public void drawFrame(Canvas canvas, int incFrame) { + if (nativePtr == 0) { + return; + } + for (int i = 0; i < incFrame; ++i) { + getNextFrame(); + } + Bitmap bitmap = getBackgroundBitmap(); + if (bitmap == null) { + bitmap = getNextFrame(); + } + AndroidUtilities.rectTmp2.set(0, 0, bitmap.getWidth(), bitmap.getHeight()); + canvas.drawBitmap(getBackgroundBitmap(), AndroidUtilities.rectTmp2, getBounds(), getPaint()); + } + public boolean canLoadFrames() { if (precache) { return bitmapsCache != null; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedFloat.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedFloat.java index 9424466df..92b50debd 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedFloat.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedFloat.java @@ -3,7 +3,6 @@ package org.telegram.ui.Components; import android.animation.TimeInterpolator; import android.os.SystemClock; -import android.util.Log; import android.view.View; import androidx.core.math.MathUtils; @@ -120,7 +119,6 @@ public class AnimatedFloat { } public float set(float mustBe, boolean force) { - final long now = SystemClock.elapsedRealtime(); if (force || transitionDuration <= 0 || firstSet) { value = targetValue = mustBe; transition = false; @@ -129,9 +127,10 @@ public class AnimatedFloat { transition = true; targetValue = mustBe; startValue = value; - transitionStart = now; + transitionStart = SystemClock.elapsedRealtime(); } if (transition) { + final long now = SystemClock.elapsedRealtime(); final float t = MathUtils.clamp((now - transitionStart - transitionDelay) / (float) transitionDuration, 0, 1); if (now - transitionStart >= transitionDelay) { if (transitionInterpolator == null) { @@ -154,6 +153,10 @@ public class AnimatedFloat { return value; } + public boolean isInProgress() { + return transition; + } + public float getTransitionProgress() { if (!transition) { return 0; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedTextView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedTextView.java index 4a9a4b82a..b3d2f6b60 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedTextView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedTextView.java @@ -71,6 +71,7 @@ public class AnimatedTextView extends View { private Runnable onAnimationFinishListener; private boolean allowCancel; + public boolean ignoreRTL; public AnimatedTextDrawable() { this(false, false, false); @@ -113,7 +114,7 @@ public class AnimatedTextView extends View { } canvas.save(); int lwidth = j >= 0 ? width : currentWidth; - if (isRTL) { + if (isRTL && !ignoreRTL) { x = -x + 2 * lwidth - currentLayout[i].getWidth() - fullWidth; } if ((gravity | ~Gravity.LEFT) != ~0) { @@ -121,11 +122,11 @@ public class AnimatedTextView extends View { x += fullWidth - lwidth; } else if ((gravity | ~Gravity.CENTER_HORIZONTAL) == ~0) { x += (fullWidth - lwidth) / 2f; - } else if (isRTL) { + } else if (isRTL && !ignoreRTL) { x += fullWidth - lwidth; } } - canvas.translate(Math.round(x), Math.round(y)); + canvas.translate(x, y); currentLayout[i].draw(canvas); canvas.restore(); } @@ -138,7 +139,7 @@ public class AnimatedTextView extends View { float y = textPaint.getTextSize() * moveAmplitude * t * (moveDown ? 1f : -1f); textPaint.setAlpha((int) (alpha * (1f - t))); canvas.save(); - if (isRTL) { + if (isRTL && !ignoreRTL) { x = -x + 2 * oldWidth - oldLayout[i].getWidth() - fullWidth; } if ((gravity | ~Gravity.LEFT) != ~0) { @@ -146,11 +147,11 @@ public class AnimatedTextView extends View { x += fullWidth - oldWidth; } else if ((gravity | ~Gravity.CENTER_HORIZONTAL) == ~0) { x += (fullWidth - oldWidth) / 2f; - } else if (isRTL) { + } else if (isRTL && !ignoreRTL) { x += fullWidth - oldWidth; } } - canvas.translate(Math.round(x), Math.round(y)); + canvas.translate(x, y); oldLayout[i].draw(canvas); canvas.restore(); } @@ -161,7 +162,7 @@ public class AnimatedTextView extends View { textPaint.setAlpha(alpha); canvas.save(); float x = currentLayoutOffsets[i]; - if (isRTL) { + if (isRTL && !ignoreRTL) { x = -x + 2 * currentWidth - currentLayout[i].getWidth() - fullWidth; } if ((gravity | ~Gravity.LEFT) != ~0) { @@ -169,11 +170,11 @@ public class AnimatedTextView extends View { x += fullWidth - currentWidth; } else if ((gravity | ~Gravity.CENTER_HORIZONTAL) == ~0) { x += (fullWidth - currentWidth) / 2f; - } else if (isRTL) { + } else if (isRTL && !ignoreRTL) { x += fullWidth - currentWidth; } } - canvas.translate(Math.round(x), 0); + canvas.translate(x, 0); currentLayout[i].draw(canvas); canvas.restore(); } @@ -391,6 +392,10 @@ public class AnimatedTextView extends View { return currentWidth; } + public int getHeight() { + return currentHeight; + } + private StaticLayout makeLayout(CharSequence textPart, int width) { if (width <= 0) { width = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y); @@ -706,6 +711,12 @@ public class AnimatedTextView extends View { super.setBounds(left, top, right, bottom); this.bounds.set(left, top, right, bottom); } + + @NonNull + @Override + public Rect getDirtyBounds() { + return this.bounds; + } } private AnimatedTextDrawable drawable; @@ -736,7 +747,7 @@ public class AnimatedTextView extends View { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); - if (lastMaxWidth != width) { + if (lastMaxWidth != width && getLayoutParams().width != 0) { drawable.setBounds(getPaddingLeft(), getPaddingTop(), width - getPaddingRight(), height - getPaddingBottom()); setText(drawable.getText(), false); } @@ -769,6 +780,10 @@ public class AnimatedTextView extends View { return drawable.isAnimating(); } + private void setIgnoreRTL(boolean value) { + drawable.ignoreRTL = value; + } + private boolean first = true; public void setText(CharSequence text, boolean animated, boolean moveDown) { animated = !first && animated; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AudioPlayerAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AudioPlayerAlert.java index 89b9e58e2..ae8ac9a14 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AudioPlayerAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AudioPlayerAlert.java @@ -620,6 +620,7 @@ public class AudioPlayerAlert extends BottomSheet implements NotificationCenter. return super.onTouch(ev); } }; + seekBarView.setLineWidth(4); seekBarView.setDelegate(new SeekBarView.SeekBarViewDelegate() { @Override public void onSeekBarDrag(boolean stop, float progress) { @@ -645,7 +646,7 @@ public class AudioPlayerAlert extends BottomSheet implements NotificationCenter. } }); seekBarView.setReportChanges(true); - playerLayout.addView(seekBarView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 38, Gravity.TOP | Gravity.LEFT, 5, 70, 5, 0)); + playerLayout.addView(seekBarView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 38 + 6, Gravity.TOP | Gravity.LEFT, 5, 67, 5, 0)); seekBarBufferSpring = new SpringAnimation(new FloatValueHolder(0)) .setSpring(new SpringForce() @@ -1880,6 +1881,7 @@ public class AudioPlayerAlert extends BottomSheet implements NotificationCenter. lastTime = newTime; timeTextView.setText(AndroidUtilities.formatShortDuration(newTime)); } + seekBarView.updateTimestamps(messageObject, null); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AudioVisualizerDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AudioVisualizerDrawable.java index 566210e02..fe6b32ff8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AudioVisualizerDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AudioVisualizerDrawable.java @@ -5,8 +5,8 @@ import android.graphics.Paint; import android.view.View; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.SharedConfig; import org.telegram.ui.ActionBar.Theme; -import org.telegram.ui.Cells.ChatMessageCell; import java.util.Random; @@ -52,6 +52,9 @@ public class AudioVisualizerDrawable { public void setWaveform(boolean playing, boolean animate, float[] waveform) { + if (SharedConfig.getLiteMode().enabled()) { + return; + } if (!playing && !animate) { for (int i = 0; i < 8; i++) { animateTo[i] = current[i] = 0; @@ -111,6 +114,9 @@ public class AudioVisualizerDrawable { float rotation; public void draw(Canvas canvas, float cx, float cy, boolean outOwner, float alpha, Theme.ResourcesProvider resourcesProvider) { + if (SharedConfig.getLiteMode().enabled()) { + return; + } if (outOwner) { p1.setColor(Theme.getColor(Theme.key_chat_outLoader, resourcesProvider)); p1.setAlpha((int) (ALPHA * alpha)); @@ -122,6 +128,9 @@ public class AudioVisualizerDrawable { } public void draw(Canvas canvas, float cx, float cy, boolean outOwner, Theme.ResourcesProvider resourcesProvider) { + if (SharedConfig.getLiteMode().enabled()) { + return; + } if (outOwner) { p1.setColor(Theme.getColor(Theme.key_chat_outLoader, resourcesProvider)); p1.setAlpha(ALPHA); @@ -133,6 +142,9 @@ public class AudioVisualizerDrawable { } public void draw(Canvas canvas, float cx, float cy) { + if (SharedConfig.getLiteMode().enabled()) { + return; + } for (int i = 0; i < 8; i++) { if (animateTo[i] != current[i]) { current[i] += dt[i] * 16; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarsDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarsDrawable.java index 8be264fa8..ca86c3290 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarsDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarsDrawable.java @@ -34,6 +34,7 @@ public class AvatarsDrawable { public final static int STYLE_GROUP_CALL_TOOLTIP = 10; public final static int STYLE_MESSAGE_SEEN = 11; + private boolean showSavedMessages; DrawingState[] currentStates = new DrawingState[3]; DrawingState[] animatingStates = new DrawingState[3]; @@ -316,15 +317,28 @@ public class AvatarsDrawable { animatingStates[index].id = id; } else if (object instanceof TLRPC.User) { currentUser = (TLRPC.User) object; - animatingStates[index].avatarDrawable.setInfo(currentUser); + if (currentUser.self && showSavedMessages) { + animatingStates[index].avatarDrawable.setAvatarType(AvatarDrawable.AVATAR_TYPE_SAVED); + animatingStates[index].avatarDrawable.setScaleSize(0.6f); + } else { + animatingStates[index].avatarDrawable.setAvatarType(AvatarDrawable.AVATAR_TYPE_NORMAL); + animatingStates[index].avatarDrawable.setScaleSize(1f); + animatingStates[index].avatarDrawable.setInfo(currentUser); + } animatingStates[index].id = currentUser.id; } else { currentChat = (TLRPC.Chat) object; + animatingStates[index].avatarDrawable.setAvatarType(AvatarDrawable.AVATAR_TYPE_NORMAL); + animatingStates[index].avatarDrawable.setScaleSize(1f); animatingStates[index].avatarDrawable.setInfo(currentChat); animatingStates[index].id = -currentChat.id; } if (currentUser != null) { - animatingStates[index].imageReceiver.setForUserOrChat(currentUser, animatingStates[index].avatarDrawable); + if (currentUser.self && showSavedMessages) { + animatingStates[index].imageReceiver.setImageBitmap(animatingStates[index].avatarDrawable); + } else { + animatingStates[index].imageReceiver.setForUserOrChat(currentUser, animatingStates[index].avatarDrawable); + } } else { animatingStates[index].imageReceiver.setForUserOrChat(currentChat, animatingStates[index].avatarDrawable); } @@ -569,4 +583,8 @@ public class AvatarsDrawable { setObject(0, 0, null); } } + + public void setShowSavedMessages(boolean showSavedMessages) { + this.showSavedMessages = showSavedMessages; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java index e1d804a0c..fb9176d3f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java @@ -22,20 +22,75 @@ import android.view.View; import org.telegram.messenger.ImageLocation; import org.telegram.messenger.ImageReceiver; import org.telegram.messenger.SecureDocument; +import org.telegram.messenger.Utilities; import org.telegram.tgnet.TLObject; public class BackupImageView extends View { protected ImageReceiver imageReceiver; + protected ImageReceiver blurImageReceiver; protected int width = -1; protected int height = -1; public AnimatedEmojiDrawable animatedEmojiDrawable; private AvatarDrawable avatarDrawable; boolean attached; + protected boolean hasBlur; + protected boolean blurAllowed; + public BackupImageView(Context context) { super(context); imageReceiver = new ImageReceiver(this); + + imageReceiver.setDelegate((imageReceiver1, set, thumb, memCache) -> { + if (set && !thumb) { + checkCreateBlurredImage(); + } + }); + } + + public void setBlurAllowed(boolean blurAllowed) { + if (attached) { + throw new IllegalStateException("You should call setBlurAllowed(...) only when detached!"); + } + this.blurAllowed = blurAllowed; + if (blurAllowed) { + blurImageReceiver = new ImageReceiver(); + } + } + + public void setHasBlur(boolean hasBlur) { + if (hasBlur && !blurAllowed) { + throw new IllegalStateException("You should call setBlurAllowed(...) before calling setHasBlur(true)!"); + } + this.hasBlur = hasBlur; + if (!hasBlur) { + if (blurImageReceiver.getBitmap() != null && !blurImageReceiver.getBitmap().isRecycled()) { + blurImageReceiver.getBitmap().recycle(); + } + blurImageReceiver.setImageBitmap((Bitmap) null); + } + checkCreateBlurredImage(); + } + + private void onNewImageSet() { + if (hasBlur) { + if (blurImageReceiver.getBitmap() != null && !blurImageReceiver.getBitmap().isRecycled()) { + blurImageReceiver.getBitmap().recycle(); + } + blurImageReceiver.setImageBitmap((Bitmap) null); + checkCreateBlurredImage(); + } + } + + private void checkCreateBlurredImage() { + if (hasBlur && blurImageReceiver.getBitmap() == null && imageReceiver.getBitmap() != null) { + Bitmap bitmap = imageReceiver.getBitmap(); + if (bitmap != null && !bitmap.isRecycled()) { + blurImageReceiver.setImageBitmap(Utilities.stackBlurBitmapMax(bitmap)); + invalidate(); + } + } } public void setOrientation(int angle, boolean center) { @@ -56,6 +111,7 @@ public class BackupImageView extends View { public void setImage(ImageLocation mediaLocation, String mediaFilter, ImageLocation imageLocation, String imageFilter, Drawable thumb, Object parentObject) { imageReceiver.setImage(mediaLocation, mediaFilter, imageLocation, imageFilter, null, null, thumb, 0, null, parentObject, 1); + onNewImageSet(); } public void setImage(ImageLocation imageLocation, String imageFilter, Bitmap thumb, Object parentObject) { @@ -72,14 +128,17 @@ public class BackupImageView extends View { thumb = new BitmapDrawable(null, thumbBitmap); } imageReceiver.setImage(imageLocation, imageFilter, null, null, thumb, size, null, parentObject, cacheType); + onNewImageSet(); } public void setForUserOrChat(TLObject object, AvatarDrawable avatarDrawable) { imageReceiver.setForUserOrChat(object, avatarDrawable); + onNewImageSet(); } public void setForUserOrChat(TLObject object, AvatarDrawable avatarDrawable, Object parent) { imageReceiver.setForUserOrChat(object, avatarDrawable, parent); + onNewImageSet(); } public void setImageMedia(ImageLocation mediaLocation, String mediaFilter, ImageLocation imageLocation, String imageFilter, Bitmap thumbBitmap, int size, int cacheType, Object parentObject) { @@ -88,6 +147,7 @@ public class BackupImageView extends View { thumb = new BitmapDrawable(null, thumbBitmap); } imageReceiver.setImage(mediaLocation, mediaFilter, imageLocation, imageFilter, null, null, thumb, size, null, parentObject, cacheType); + onNewImageSet(); } public void setImage(ImageLocation imageLocation, String imageFilter, ImageLocation thumbLocation, String thumbFilter, int size, Object parentObject) { @@ -107,24 +167,29 @@ public class BackupImageView extends View { thumb = new BitmapDrawable(null, thumbBitmap); } imageReceiver.setImage(imageLocation, imageFilter, thumbLocation, thumbFilter, thumb, size, ext, parentObject, 0); + onNewImageSet(); } public void setImage(ImageLocation imageLocation, String imageFilter, ImageLocation thumbLocation, String thumbFilter, String ext, long size, int cacheType, Object parentObject) { imageReceiver.setImage(imageLocation, imageFilter, thumbLocation, thumbFilter, null, size, ext, parentObject, cacheType); + onNewImageSet(); } public void setImageMedia(ImageLocation mediaLocation, String mediaFilter, ImageLocation imageLocation, String imageFilter, ImageLocation thumbLocation, String thumbFilter, String ext, int size, int cacheType, Object parentObject) { imageReceiver.setImage(mediaLocation, mediaFilter, imageLocation, imageFilter, thumbLocation, thumbFilter, null, size, ext, parentObject, cacheType); + onNewImageSet(); } public void setImageBitmap(Bitmap bitmap) { imageReceiver.setImageBitmap(bitmap); + onNewImageSet(); } public void setImageResource(int resId) { Drawable drawable = getResources().getDrawable(resId); imageReceiver.setImageBitmap(drawable); invalidate(); + onNewImageSet(); } public void setImageResource(int resId, int color) { @@ -134,10 +199,12 @@ public class BackupImageView extends View { } imageReceiver.setImageBitmap(drawable); invalidate(); + onNewImageSet(); } public void setImageDrawable(Drawable drawable) { imageReceiver.setImageBitmap(drawable); + onNewImageSet(); } public void setLayerNum(int value) { @@ -146,11 +213,17 @@ public class BackupImageView extends View { public void setRoundRadius(int value) { imageReceiver.setRoundRadius(value); + if (blurAllowed) { + blurImageReceiver.setRoundRadius(value); + } invalidate(); } public void setRoundRadius(int tl, int tr, int bl, int br) { imageReceiver.setRoundRadius(tl, tr, bl ,br); + if (blurAllowed) { + blurImageReceiver.setRoundRadius(tl, tr, bl, br); + } invalidate(); } @@ -184,6 +257,9 @@ public class BackupImageView extends View { super.onDetachedFromWindow(); attached = false; imageReceiver.onDetachedFromWindow(); + if (blurAllowed) { + blurImageReceiver.onDetachedFromWindow(); + } if (animatedEmojiDrawable != null) { animatedEmojiDrawable.removeView(this); } @@ -194,6 +270,9 @@ public class BackupImageView extends View { super.onAttachedToWindow(); attached = true; imageReceiver.onAttachedToWindow(); + if (blurAllowed) { + blurImageReceiver.onAttachedToWindow(); + } if (animatedEmojiDrawable != null) { animatedEmojiDrawable.addView(this); } @@ -207,10 +286,19 @@ public class BackupImageView extends View { } if (width != -1 && height != -1) { imageReceiver.setImageCoords((getWidth() - width) / 2, (getHeight() - height) / 2, width, height); + if (blurAllowed) { + blurImageReceiver.setImageCoords((getWidth() - width) / 2, (getHeight() - height) / 2, width, height); + } } else { imageReceiver.setImageCoords(0, 0, getWidth(), getHeight()); + if (blurAllowed) { + blurImageReceiver.setImageCoords(0, 0, getWidth(), getHeight()); + } } imageReceiver.draw(canvas); + if (blurAllowed) { + blurImageReceiver.draw(canvas); + } } public void setColorFilter(ColorFilter colorFilter) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BlobDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BlobDrawable.java index b3ce3ab82..ef38e7284 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BlobDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BlobDrawable.java @@ -1,12 +1,11 @@ package org.telegram.ui.Components; -import android.animation.ValueAnimator; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; -import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.SharedConfig; import java.util.Random; @@ -86,6 +85,9 @@ public class BlobDrawable { } public void update(float amplitude, float speedScale) { + if (SharedConfig.getLiteMode().enabled()) { + return; + } for (int i = 0; i < N; i++) { progress[i] += (speed[i] * MIN_SPEED) + amplitude * speed[i] * MAX_SPEED * speedScale; if (progress[i] >= 1f) { @@ -98,6 +100,9 @@ public class BlobDrawable { } public void draw(float cX, float cY, Canvas canvas, Paint paint) { + if (SharedConfig.getLiteMode().enabled()) { + return; + } path.reset(); for (int i = 0; i < N; i++) { @@ -166,6 +171,9 @@ public class BlobDrawable { public void setValue(float value, boolean isBig) { animateToAmplitude = value; + if (SharedConfig.getLiteMode().enabled()) { + return; + } if (isBig) { if (animateToAmplitude > amplitude) { animateAmplitudeDiff = (animateToAmplitude - amplitude) / (100f + 300f * animationSpeed); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BotWebViewContainer.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BotWebViewContainer.java index 8fab2afa2..c60b9fa39 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BotWebViewContainer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BotWebViewContainer.java @@ -67,14 +67,12 @@ import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenuSubItem; import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.BottomSheet; import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.CameraScanActivity; import org.telegram.ui.Components.voip.CellFlickerDrawable; -import java.io.BufferedReader; import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; @@ -85,7 +83,7 @@ import java.util.concurrent.atomic.AtomicBoolean; public class BotWebViewContainer extends FrameLayout implements NotificationCenter.NotificationCenterDelegate { private final static String DURGER_KING_USERNAME = "DurgerKingBot"; - private final static int REQUEST_CODE_WEB_VIEW_FILE = 3000, REQUEST_CODE_WEB_PERMISSION = 4000; + private final static int REQUEST_CODE_WEB_VIEW_FILE = 3000, REQUEST_CODE_WEB_PERMISSION = 4000, REQUEST_CODE_QR_CAMERA_PERMISSION = 5000; private final static int DIALOG_SEQUENTIAL_COOLDOWN_TIME = 3000; private final static boolean ENABLE_REQUEST_PHONE = false; @@ -136,6 +134,10 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent private long lastDialogClosed; private long lastDialogCooldownTime; + private BottomSheet cameraBottomSheet; + private boolean hasQRPending; + private String lastQrText; + public BotWebViewContainer(@NonNull Context context, Theme.ResourcesProvider resourcesProvider, int backgroundColor) { super(context); this.resourcesProvider = resourcesProvider; @@ -461,10 +463,10 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent } private void onOpenUri(Uri uri) { - onOpenUri(uri, false); + onOpenUri(uri, false, false); } - private void onOpenUri(Uri uri, boolean suppressPopup) { + private void onOpenUri(Uri uri, boolean tryInstantView, boolean suppressPopup) { if (isRequestingPageOpen || System.currentTimeMillis() - lastClickMs > 10000 && suppressPopup) { return; } @@ -483,18 +485,18 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); - delegate.onCloseRequested(() -> Browser.openUrl(getContext(), uri, true, false)); + delegate.onCloseRequested(() -> Browser.openUrl(getContext(), uri, true, tryInstantView)); } else { - Browser.openUrl(getContext(), uri, true, false); + Browser.openUrl(getContext(), uri, true, tryInstantView); } } else if (suppressPopup) { - Browser.openUrl(getContext(), uri, true, false); + Browser.openUrl(getContext(), uri, true, tryInstantView); } else { isRequestingPageOpen = true; new AlertDialog.Builder(getContext(), resourcesProvider) .setTitle(LocaleController.getString(R.string.OpenUrlTitle)) .setMessage(LocaleController.formatString(R.string.OpenUrlAlert2, uri.toString())) - .setPositiveButton(LocaleController.getString(R.string.Open), (dialog, which) -> Browser.openUrl(getContext(), uri, true, false)) + .setPositiveButton(LocaleController.getString(R.string.Open), (dialog, which) -> Browser.openUrl(getContext(), uri, true, tryInstantView)) .setNegativeButton(LocaleController.getString(R.string.Cancel), null) .setOnDismissListener(dialog -> isRequestingPageOpen = false) .show(); @@ -532,20 +534,6 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent return; } - try { - InputStream in = getResources().getAssets().open("bot_clipboard_wrapper.js"); - BufferedReader r = new BufferedReader(new InputStreamReader(in)); - StringBuilder script = new StringBuilder(); - String line; - while ((line = r.readLine()) != null) { - script.append(line).append("\n"); - } - in.close(); - evaluateJs(script.toString()); - } catch (IOException e) { - FileLog.e(e); - } - AnimatorSet set = new AnimatorSet(); set.playTogether( ObjectAnimator.ofFloat(webView, View.ALPHA, 1f), @@ -951,6 +939,71 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent delegate.onCloseRequested(null); break; } + case "web_app_read_text_from_clipboard": { + try { + JSONObject jsonObject = new JSONObject(eventData); + String reqId = jsonObject.getString("req_id"); + if (!delegate.isClipboardAvailable() || System.currentTimeMillis() - lastClickMs > 10000) { + notifyEvent("clipboard_text_received", new JSONObject().put("req_id", reqId)); + break; + } + + ClipboardManager clipboardManager = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); + CharSequence text = clipboardManager.getText(); + String data = text != null ? text.toString() : ""; + notifyEvent("clipboard_text_received", new JSONObject().put("req_id", reqId).put("data", data)); + } catch (JSONException e) { + FileLog.e(e); + } + break; + } + case "web_app_close_scan_qr_popup": { + if (hasQRPending) { + cameraBottomSheet.dismiss(); + } + break; + } + case "web_app_open_scan_qr_popup": { + try { + if (hasQRPending || parentActivity == null) { + break; + } + + JSONObject jsonObject = new JSONObject(eventData); + lastQrText = jsonObject.optString("text"); + hasQRPending = true; + + if (Build.VERSION.SDK_INT >= 23 && parentActivity.checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + NotificationCenter.getGlobalInstance().addObserver(new NotificationCenter.NotificationCenterDelegate() { + @Override + public void didReceivedNotification(int id, int account, Object... args) { + if (id == NotificationCenter.onRequestPermissionResultReceived) { + int requestCode = (int) args[0]; + // String[] permissions = (String[]) args[1]; + int[] grantResults = (int[]) args[2]; + + if (requestCode == REQUEST_CODE_QR_CAMERA_PERMISSION) { + NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.onRequestPermissionResultReceived); + + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + openQrScanActivity(); + } else { + notifyEvent("scan_qr_popup_closed", new JSONObject()); + } + } + } + } + }, NotificationCenter.onRequestPermissionResultReceived); + parentActivity.requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CODE_QR_CAMERA_PERMISSION); + return; + } + + openQrScanActivity(); + } catch (JSONException e) { + FileLog.e(e); + } + break; + } case "web_app_request_phone": { if (currentDialog != null || !ENABLE_REQUEST_PHONE) { break; @@ -1209,7 +1262,7 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent JSONObject jsonData = new JSONObject(eventData); Uri uri = Uri.parse(jsonData.optString("url")); if (WHITELISTED_SCHEMES.contains(uri.getScheme())) { - onOpenUri(uri, true); + onOpenUri(uri, jsonData.optBoolean("try_instant_view"), true); } } catch (Exception e) { FileLog.e(e); @@ -1313,6 +1366,34 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent } } + private void openQrScanActivity() { + if (parentActivity == null) { + return; + } + + cameraBottomSheet = CameraScanActivity.showAsSheet(parentActivity, false, CameraScanActivity.TYPE_QR_WEB_BOT, new CameraScanActivity.CameraScanActivityDelegate() { + @Override + public void didFindQr(String text) { + try { + notifyEvent("qr_text_received", new JSONObject().put("data", text)); + } catch (JSONException e) { + FileLog.e(e); + } + } + + @Override + public String getSubtitleText() { + return lastQrText; + } + + @Override + public void onDismiss() { + notifyEvent("scan_qr_popup_closed", null); + hasQRPending = false; + } + }); + } + private JSONObject buildThemeParams() { try { JSONObject object = new JSONObject(); @@ -1356,13 +1437,6 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent public void postEvent(String eventType, String eventData) { AndroidUtilities.runOnUIThread(() -> onEventReceived(eventType, eventData)); } - - @JavascriptInterface - public String getClipboardText() { - ClipboardManager clipboardManager = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); - CharSequence text = clipboardManager.getText(); - return text != null ? text.toString() : null; - } } public interface WebViewScrollListener { @@ -1437,6 +1511,13 @@ public class BotWebViewContainer extends FrameLayout implements NotificationCent * Called when WebView is ready (Called web_app_ready or page load finished) */ default void onWebAppReady() {} + + /** + * @return If clipboard access is available to webapp + */ + default boolean isClipboardAvailable() { + return false; + } } public final static class PopupButton { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BottomSheetWithRecyclerListView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BottomSheetWithRecyclerListView.java index be9fb43e7..6daeb3cd4 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BottomSheetWithRecyclerListView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BottomSheetWithRecyclerListView.java @@ -16,6 +16,7 @@ import androidx.recyclerview.widget.RecyclerView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.R; +import org.telegram.messenger.Utilities; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.BottomSheet; @@ -23,86 +24,112 @@ import org.telegram.ui.ActionBar.Theme; public abstract class BottomSheetWithRecyclerListView extends BottomSheet { + private final Drawable headerShadowDrawable; protected RecyclerListView recyclerListView; protected ActionBar actionBar; boolean wasDrawn; - private int contentHeight; + protected int contentHeight; private BaseFragment baseFragment; public final boolean hasFixedSize; protected boolean clipToActionBar; + public NestedSizeNotifierLayout nestedSizeNotifierLayout; public float topPadding = 0.4f; + boolean showShadow = true; + private float shadowAlpha = 1f; public BottomSheetWithRecyclerListView(BaseFragment fragment, boolean needFocus, boolean hasFixedSize) { - this(fragment, needFocus, hasFixedSize, null); + this(fragment, needFocus, hasFixedSize, false, null); } - public BottomSheetWithRecyclerListView(BaseFragment fragment, boolean needFocus, boolean hasFixedSize, Theme.ResourcesProvider resourcesProvider) { + public BottomSheetWithRecyclerListView(BaseFragment fragment, boolean needFocus, boolean hasFixedSize, boolean useNested, Theme.ResourcesProvider resourcesProvider) { super(fragment.getParentActivity(), needFocus, resourcesProvider); this.baseFragment = fragment; this.hasFixedSize = hasFixedSize; Context context = fragment.getParentActivity(); - Drawable headerShadowDrawable = ContextCompat.getDrawable(context, R.drawable.header_shadow).mutate(); - FrameLayout containerView = new FrameLayout(context) { + headerShadowDrawable = ContextCompat.getDrawable(context, R.drawable.header_shadow).mutate(); + FrameLayout containerView; + if (useNested) { + containerView = nestedSizeNotifierLayout = new NestedSizeNotifierLayout(context) { - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - contentHeight = MeasureSpec.getSize(heightMeasureSpec); - onPreMeasure(widthMeasureSpec, heightMeasureSpec); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + contentHeight = MeasureSpec.getSize(heightMeasureSpec); + onPreMeasure(widthMeasureSpec, heightMeasureSpec); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } - @Override - protected void dispatchDraw(Canvas canvas) { - if (!hasFixedSize) { - RecyclerView.ViewHolder holder = recyclerListView.findViewHolderForAdapterPosition(0); - int top = -AndroidUtilities.dp(16); - if (holder != null) { - top = holder.itemView.getBottom() - AndroidUtilities.dp(16); + @Override + protected void dispatchDraw(Canvas canvas) { + preDrawInternal(canvas, this); + super.dispatchDraw(canvas); + postDrawInternal(canvas, this); + } + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + if (!hasFixedSize && clipToActionBar && child == recyclerListView) { + canvas.save(); + canvas.clipRect(0, actionBar.getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight()); + super.drawChild(canvas, child, drawingTime); + canvas.restore(); + return true; } + return super.drawChild(canvas, child, drawingTime); + } - float progressToFullView = 1f - (top + AndroidUtilities.dp(16)) / (float) AndroidUtilities.dp(56); - if (progressToFullView < 0) { - progressToFullView = 0; + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN && event.getY() < shadowDrawable.getBounds().top) { + dismiss(); } - - AndroidUtilities.updateViewVisibilityAnimated(actionBar, progressToFullView != 0f, 1f, wasDrawn); - shadowDrawable.setBounds(0, top, getMeasuredWidth(), getMeasuredHeight()); - shadowDrawable.draw(canvas); - - onPreDraw(canvas, top, progressToFullView); + return super.dispatchTouchEvent(event); } - super.dispatchDraw(canvas); - if (actionBar != null && actionBar.getVisibility() == View.VISIBLE && actionBar.getAlpha() != 0) { - headerShadowDrawable.setBounds(0, actionBar.getBottom(), getMeasuredWidth(), actionBar.getBottom() + headerShadowDrawable.getIntrinsicHeight()); - headerShadowDrawable.setAlpha((int) (255 * actionBar.getAlpha())); - headerShadowDrawable.draw(canvas); - } - wasDrawn = true; - } + }; + } else { + containerView = new FrameLayout(context) { - @Override - protected boolean drawChild(Canvas canvas, View child, long drawingTime) { - if (!hasFixedSize && clipToActionBar && child == recyclerListView) { - canvas.save(); - canvas.clipRect(0, actionBar.getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight()); - super.drawChild(canvas, child, drawingTime); - canvas.restore(); - return true; + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + contentHeight = MeasureSpec.getSize(heightMeasureSpec); + onPreMeasure(widthMeasureSpec, heightMeasureSpec); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); } - return super.drawChild(canvas, child, drawingTime); - } - @Override - public boolean dispatchTouchEvent(MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_DOWN && event.getY() < shadowDrawable.getBounds().top) { - dismiss(); + @Override + protected void dispatchDraw(Canvas canvas) { + preDrawInternal(canvas, this); + super.dispatchDraw(canvas); + postDrawInternal(canvas, this); } - return super.dispatchTouchEvent(event); - } - }; + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + if (!hasFixedSize && clipToActionBar && child == recyclerListView) { + canvas.save(); + canvas.clipRect(0, actionBar.getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight()); + super.drawChild(canvas, child, drawingTime); + canvas.restore(); + return true; + } + return super.drawChild(canvas, child, drawingTime); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN && event.getY() < shadowDrawable.getBounds().top) { + dismiss(); + } + return super.dispatchTouchEvent(event); + } + }; + } recyclerListView = new RecyclerListView(context); recyclerListView.setLayoutManager(new LinearLayoutManager(context)); + if (nestedSizeNotifierLayout != null) { + nestedSizeNotifierLayout.setBottomSheetContainerView(getContainer()); + nestedSizeNotifierLayout.setTargetListView(recyclerListView); + } RecyclerListView.SelectionAdapter adapter = createAdapter(); @@ -184,7 +211,7 @@ public abstract class BottomSheetWithRecyclerListView extends BottomSheet { actionBar.setCastShadows(true); actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setTitle(getTitle()); - actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick(){ + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @Override public void onItemClick(int id) { if (id == -1) { @@ -208,6 +235,44 @@ public abstract class BottomSheetWithRecyclerListView extends BottomSheet { updateStatusBar(); } + private void postDrawInternal(Canvas canvas, View parentView) { + if (showShadow && shadowAlpha != 1f) { + shadowAlpha += 16 / 150f; + parentView.invalidate(); + } else if (!showShadow && shadowAlpha != 0) { + shadowAlpha -= 16 / 150f; + parentView.invalidate(); + } + shadowAlpha = Utilities.clamp(shadowAlpha, 1f, 0f); + if (actionBar != null && actionBar.getVisibility() == View.VISIBLE && actionBar.getAlpha() != 0 && shadowAlpha != 0) { + headerShadowDrawable.setBounds(0, actionBar.getBottom(), parentView.getMeasuredWidth(), actionBar.getBottom() + headerShadowDrawable.getIntrinsicHeight()); + headerShadowDrawable.setAlpha((int) (255 * actionBar.getAlpha() * shadowAlpha)); + headerShadowDrawable.draw(canvas); + } + wasDrawn = true; + } + + private void preDrawInternal(Canvas canvas, View parent) { + if (!hasFixedSize) { + RecyclerView.ViewHolder holder = recyclerListView.findViewHolderForAdapterPosition(0); + int top = -AndroidUtilities.dp(16); + if (holder != null) { + top = holder.itemView.getBottom() - AndroidUtilities.dp(16); + } + + float progressToFullView = 1f - (top + AndroidUtilities.dp(16)) / (float) AndroidUtilities.dp(56); + if (progressToFullView < 0) { + progressToFullView = 0; + } + + AndroidUtilities.updateViewVisibilityAnimated(actionBar, progressToFullView != 0f, 1f, wasDrawn); + shadowDrawable.setBounds(0, top, parent.getMeasuredWidth(), parent.getMeasuredHeight()); + shadowDrawable.draw(canvas); + + onPreDraw(canvas, top, progressToFullView); + } + } + protected void onPreMeasure(int widthMeasureSpec, int heightMeasureSpec) { } @@ -249,4 +314,15 @@ public abstract class BottomSheetWithRecyclerListView extends BottomSheet { } } + public void updateTitle() { + if (actionBar != null) { + actionBar.setTitle(getTitle()); + } + } + + public void setShowShadow(boolean show) { + showShadow = show; + nestedSizeNotifierLayout.invalidate(); + } + } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BubbleCounterPath.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BubbleCounterPath.java new file mode 100644 index 000000000..ca4eb79e9 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BubbleCounterPath.java @@ -0,0 +1,45 @@ +package org.telegram.ui.Components; + +import static org.telegram.messenger.AndroidUtilities.dp; + +import android.graphics.Path; +import android.graphics.RectF; + +public class BubbleCounterPath { + + private static RectF tmpRect; + + public static void addBubbleRect(Path path, RectF bounds, float radius) { + if (path == null) { + return; + } + if (tmpRect == null) { + tmpRect = new RectF(); + } + + final float D = radius * 2; + + path.rewind(); + + tmpRect.set(0, -bounds.height(), D, -bounds.height() + D); + path.arcTo(tmpRect, 180, 90); + + tmpRect.set(bounds.width() - D, -bounds.height(), bounds.width(), -bounds.height() + D); + path.arcTo(tmpRect, 270, 90); + + tmpRect.set(bounds.width() - D, -D, bounds.width(), 0); + path.arcTo(tmpRect, 0, 90); + + path.quadTo(radius, 0, radius, 0); + path.cubicTo(dp(7.62f), dp(-.5f), dp(5.807f), dp(-1.502f), dp(6.02f), dp(-1.386f)); + path.cubicTo(dp(4.814f), dp(-.81f), dp(2.706f), dp(-.133f), dp(3.6f), dp(-.44f)); + path.cubicTo(dp(1.004f), dp(-.206f), dp(-.047f), dp(-.32f), dp(.247f), dp(-.29f)); + path.cubicTo(dp(-.334f), dp(-1.571f), 0, dp(-1.155f), dp(-.06f), dp(-1.154f)); + path.cubicTo(dp(1.083f), dp(-2.123f), dp(1.667f), dp(-3.667f), dp(1.453f), dp(-3.12f)); + path.cubicTo(dp(2.1f), dp(-4.793f), dp(1.24f), dp(-6.267f), dp(1.67f), dp(-5.53f)); + path.quadTo(0, -radius + dp(2.187f), 0, -radius); + path.close(); + + path.offset(bounds.left, bounds.bottom); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Bulletin.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Bulletin.java index 0c1cdb5f5..df5aae36f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Bulletin.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Bulletin.java @@ -78,6 +78,7 @@ public class Bulletin { public static final int TYPE_NAME_CHANGED = 3; public static final int TYPE_ERROR_SUBTITLE = 4; public static final int TYPE_APP_ICON = 5; + public static final int TYPE_SUCCESS = 6; public int tag; public int hash; @@ -1327,8 +1328,10 @@ public class Bulletin { public AvatarsImageView avatarsImageView; public TextView textView; + public TextView subtitleView; + LinearLayout linearLayout; - public UsersLayout(@NonNull Context context, Theme.ResourcesProvider resourcesProvider) { + public UsersLayout(@NonNull Context context, boolean subtitle, Theme.ResourcesProvider resourcesProvider) { super(context, resourcesProvider); avatarsImageView = new AvatarsImageView(context, false); @@ -1336,33 +1339,64 @@ public class Bulletin { avatarsImageView.setAvatarsTextSize(AndroidUtilities.dp(18)); addView(avatarsImageView, LayoutHelper.createFrameRelatively(24 + 12 + 12 + 8, 48, Gravity.START | Gravity.CENTER_VERTICAL, 12, 0, 0, 0)); - textView = new LinkSpanDrawable.LinksTextView(context) { - @Override - public void setText(CharSequence text, BufferType type) { - text = Emoji.replaceEmoji(text, getPaint().getFontMetricsInt(), AndroidUtilities.dp(13), false); - super.setText(text, type); - } - }; - NotificationCenter.listenEmojiLoading(textView); - textView.setTypeface(Typeface.SANS_SERIF); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); - textView.setEllipsize(TextUtils.TruncateAt.END); - textView.setPadding(0, AndroidUtilities.dp(8), 0, AndroidUtilities.dp(8)); - addView(textView, LayoutHelper.createFrameRelatively(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL, 12 + 56 + 2, 0, 8, 0)); + if (!subtitle) { + textView = new LinkSpanDrawable.LinksTextView(context) { + @Override + public void setText(CharSequence text, BufferType type) { + text = Emoji.replaceEmoji(text, getPaint().getFontMetricsInt(), AndroidUtilities.dp(13), false); + super.setText(text, type); + } + }; + NotificationCenter.listenEmojiLoading(textView); + textView.setTypeface(Typeface.SANS_SERIF); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + textView.setEllipsize(TextUtils.TruncateAt.END); + textView.setPadding(0, AndroidUtilities.dp(8), 0, AndroidUtilities.dp(8)); + addView(textView, LayoutHelper.createFrameRelatively(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL, 12 + 56 + 2, 0, 8, 0)); + } else { + linearLayout = new LinearLayout(getContext()); + linearLayout.setOrientation(LinearLayout.VERTICAL); + addView(linearLayout, LayoutHelper.createFrameRelatively(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL, 18 + 56 + 2, 0, 8, 0)); + textView = new LinkSpanDrawable.LinksTextView(context) { + @Override + public void setText(CharSequence text, BufferType type) { + text = Emoji.replaceEmoji(text, getPaint().getFontMetricsInt(), AndroidUtilities.dp(13), false); + super.setText(text, type); + } + }; + NotificationCenter.listenEmojiLoading(textView); + textView.setTypeface(Typeface.SANS_SERIF); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setEllipsize(TextUtils.TruncateAt.END); + textView.setMaxLines(1); + linearLayout.addView(textView); + + subtitleView = new LinkSpanDrawable.LinksTextView(context); + subtitleView.setTypeface(Typeface.SANS_SERIF); + subtitleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); + subtitleView.setEllipsize(TextUtils.TruncateAt.END); + subtitleView.setMaxLines(1); + subtitleView.setLinkTextColor(getThemedColor(Theme.key_undo_cancelColor)); + linearLayout.addView(subtitleView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, 0, 0, 0, 0, 0)); + } textView.setLinkTextColor(getThemedColor(Theme.key_undo_cancelColor)); + setTextColor(getThemedColor(Theme.key_undo_infoColor)); setBackground(getThemedColor(Theme.key_undo_background)); } public UsersLayout(@NonNull Context context, Theme.ResourcesProvider resourcesProvider, int backgroundColor, int textColor) { - this(context, resourcesProvider); + this(context, false, resourcesProvider); setBackground(backgroundColor); setTextColor(textColor); } public void setTextColor(int textColor) { textView.setTextColor(textColor); + if (subtitleView != null) { + subtitleView.setTextColor(textColor); + } } @Override @@ -1441,16 +1475,14 @@ public class Bulletin { if (text) { undoTextView = new TextView(context); undoTextView.setOnClickListener(v -> undo()); - final int leftInset = LocaleController.isRTL ? AndroidUtilities.dp(16) : 0; - final int rightInset = LocaleController.isRTL ? 0 : AndroidUtilities.dp(16); - undoTextView.setBackground(Theme.createCircleSelectorDrawable((undoCancelColor & 0x00ffffff) | 0x19000000, leftInset, rightInset)); + undoTextView.setBackground(Theme.createSelectorDrawable((undoCancelColor & 0x00ffffff) | 0x19000000, Theme.RIPPLE_MASK_ROUNDRECT_6DP)); undoTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); undoTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); undoTextView.setTextColor(undoCancelColor); undoTextView.setText(LocaleController.getString("Undo", R.string.Undo)); undoTextView.setGravity(Gravity.CENTER_VERTICAL); - ViewHelper.setPaddingRelative(undoTextView, 16, 0, 16, 0); - addView(undoTextView, LayoutHelper.createFrameRelatively(LayoutHelper.WRAP_CONTENT, 48, Gravity.CENTER_VERTICAL, 8, 0, 0, 0)); + ViewHelper.setPaddingRelative(undoTextView, 12, 8, 12, 8); + addView(undoTextView, LayoutHelper.createFrameRelatively(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 8, 0, 8, 0)); } else { final ImageView undoImageView = new ImageView(getContext()); undoImageView.setOnClickListener(v -> undo()); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BulletinFactory.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BulletinFactory.java index 580b64099..107db8807 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BulletinFactory.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BulletinFactory.java @@ -20,6 +20,7 @@ import androidx.annotation.NonNull; import androidx.core.graphics.ColorUtils; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.ChatObject; import org.telegram.messenger.DialogObject; import org.telegram.messenger.LocaleController; @@ -37,6 +38,7 @@ import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.PremiumPreviewFragment; import java.util.ArrayList; +import java.util.List; public final class BulletinFactory { @@ -195,8 +197,12 @@ public final class BulletinFactory { return create(layout, Bulletin.DURATION_PROLONG); } - public Bulletin createUsersBulletin(ArrayList users, CharSequence text) { - final Bulletin.UsersLayout layout = new Bulletin.UsersLayout(getContext(), resourcesProvider); + public Bulletin createUsersBulletin(List users, CharSequence text) { + return createUsersBulletin(users, text, null); + } + + public Bulletin createUsersBulletin(List users, CharSequence text, CharSequence subtitle) { + final Bulletin.UsersLayout layout = new Bulletin.UsersLayout(getContext(), subtitle != null, resourcesProvider); int count = 0; if (users != null) { for (int i = 0; i < users.size(); ++i) { @@ -208,20 +214,48 @@ public final class BulletinFactory { layout.avatarsImageView.setObject(count - 1, UserConfig.selectedAccount, user); } } - } - layout.avatarsImageView.commitTransition(false); - layout.textView.setSingleLine(false); - layout.textView.setMaxLines(2); - layout.textView.setText(text); - if (layout.textView.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { - int margin = AndroidUtilities.dp(12 + 56 + 2 - (3 - count) * 12); - if (LocaleController.isRTL) { - ((ViewGroup.MarginLayoutParams) layout.textView.getLayoutParams()).rightMargin = margin; + if (users.size() == 1) { + layout.avatarsImageView.setTranslationX(AndroidUtilities.dp(4)); + layout.avatarsImageView.setScaleX(1.2f); + layout.avatarsImageView.setScaleY(1.2f); } else { - ((ViewGroup.MarginLayoutParams) layout.textView.getLayoutParams()).leftMargin = margin; + layout.avatarsImageView.setScaleX(1f); + layout.avatarsImageView.setScaleY(1f); } } - return create(layout, Bulletin.DURATION_LONG); + layout.avatarsImageView.commitTransition(false); + + + if (subtitle != null) { + layout.textView.setSingleLine(true); + layout.textView.setMaxLines(1); + layout.textView.setText(text); + layout.subtitleView.setText(subtitle); + layout.subtitleView.setSingleLine(true); + layout.subtitleView.setMaxLines(1); + if (layout.linearLayout.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { + int margin = AndroidUtilities.dp(12 + 56 + 2 - (3 - count) * 12); + if (LocaleController.isRTL) { + ((ViewGroup.MarginLayoutParams) layout.linearLayout.getLayoutParams()).rightMargin = margin; + } else { + ((ViewGroup.MarginLayoutParams) layout.linearLayout.getLayoutParams()).leftMargin = margin; + } + } + } else { + layout.textView.setSingleLine(false); + layout.textView.setMaxLines(2); + layout.textView.setText(text); + if (layout.textView.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { + int margin = AndroidUtilities.dp(12 + 56 + 2 - (3 - count) * 12); + if (LocaleController.isRTL) { + ((ViewGroup.MarginLayoutParams) layout.textView.getLayoutParams()).rightMargin = margin; + } else { + ((ViewGroup.MarginLayoutParams) layout.textView.getLayoutParams()).leftMargin = margin; + } + } + } + + return create(layout, Bulletin.DURATION_PROLONG); } public Bulletin createUsersAddedBulletin(ArrayList users, TLRPC.Chat chat) { @@ -400,6 +434,19 @@ public final class BulletinFactory { return create(layout, Bulletin.DURATION_SHORT); } + public Bulletin createSuccessBulletin(CharSequence successMessage) { + return createSuccessBulletin(successMessage, null); + } + + public Bulletin createSuccessBulletin(CharSequence successMessage, Theme.ResourcesProvider resourcesProvider) { + Bulletin.LottieLayout layout = new Bulletin.LottieLayout(getContext(), resourcesProvider); + layout.setAnimation(R.raw.contact_check); + layout.textView.setText(successMessage); + layout.textView.setSingleLine(false); + layout.textView.setMaxLines(2); + return create(layout, Bulletin.DURATION_SHORT); + } + public Bulletin createCaptionLimitBulletin(int count, Runnable callback) { Bulletin.LottieLayout layout = new Bulletin.LottieLayout(getContext(), null); layout.setAnimation(R.raw.caption_limit); @@ -518,7 +565,19 @@ public final class BulletinFactory { } private Context getContext() { - return fragment != null ? fragment.getParentActivity() : containerLayout.getContext(); + Context context = null; + if (fragment != null) { + context = fragment.getParentActivity(); + if (context == null && fragment.getLayoutContainer() != null) { + context = fragment.getLayoutContainer().getContext(); + } + } else if (containerLayout != null) { + context = containerLayout.getContext(); + } + if (context == null) { + context = ApplicationLoader.applicationContext; + } + return context; } //region Static Factory diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/CacheChart.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/CacheChart.java new file mode 100644 index 000000000..6df4767e8 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/CacheChart.java @@ -0,0 +1,764 @@ +package org.telegram.ui.Components; + +import static org.telegram.messenger.AndroidUtilities.dp; +import static org.telegram.messenger.AndroidUtilities.dpf2; +import static org.telegram.messenger.AndroidUtilities.lerp; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.LinearGradient; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.RadialGradient; +import android.graphics.RectF; +import android.graphics.Shader; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; + +import com.google.zxing.common.detector.MathUtils; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.R; +import org.telegram.messenger.SvgHelper; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.Premium.StarParticlesView; + +import java.util.Arrays; + +public class CacheChart extends View { + + private RectF chartBounds = new RectF(); + private RectF chartInnerBounds = new RectF(); + + private static final int SECTIONS_COUNT = 9; + private static final String[] colorKeys = new String[] { + Theme.key_statisticChartLine_lightblue, + Theme.key_statisticChartLine_blue, + Theme.key_statisticChartLine_green, + Theme.key_statisticChartLine_red, + Theme.key_statisticChartLine_lightgreen, + Theme.key_statisticChartLine_orange, + Theme.key_statisticChartLine_cyan, + Theme.key_statisticChartLine_purple, + Theme.key_statisticChartLine_golden + }; + + private static final int[] particles = new int[] { + R.raw.cache_photos, + R.raw.cache_videos, + R.raw.cache_documents, + R.raw.cache_music, + R.raw.cache_videos, + R.raw.cache_stickers, + R.raw.cache_profile_photos, + R.raw.cache_other, + R.raw.cache_other + }; + + private boolean loading = true; + public AnimatedFloat loadingFloat = new AnimatedFloat(this, 750, CubicBezierInterpolator.EASE_OUT_QUINT); + + private boolean complete = false; + private AnimatedFloat completeFloat = new AnimatedFloat(this, 750, CubicBezierInterpolator.EASE_OUT_QUINT); + + private Sector[] sectors = new Sector[SECTIONS_COUNT]; + + private float[] segmentsTmp = new float[2]; + private RectF roundingRect = new RectF(); + private Paint loadingBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + private RectF completePathBounds; + private Path completePath = new Path(); + private Paint completePaintStroke = new Paint(Paint.ANTI_ALIAS_FLAG); + private Paint completePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private LinearGradient completeGradient; + private Matrix completeGradientMatrix; + + private AnimatedTextView.AnimatedTextDrawable topText = new AnimatedTextView.AnimatedTextDrawable(false, true, true); + private AnimatedTextView.AnimatedTextDrawable bottomText = new AnimatedTextView.AnimatedTextDrawable(false, true, true); + + private StarParticlesView.Drawable completeDrawable; + + private static long particlesStart = -1; + class Sector { + + Paint particlePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + Bitmap particle; + + float angleCenter, angleSize; + AnimatedFloat angleCenterAnimated = new AnimatedFloat(CacheChart.this, 650, CubicBezierInterpolator.EASE_OUT_QUINT); + AnimatedFloat angleSizeAnimated = new AnimatedFloat(CacheChart.this, 650, CubicBezierInterpolator.EASE_OUT_QUINT); + float textAlpha; + AnimatedFloat textAlphaAnimated = new AnimatedFloat(CacheChart.this, 0, 150, CubicBezierInterpolator.EASE_OUT); + float textScale = 1; + AnimatedFloat textScaleAnimated = new AnimatedFloat(CacheChart.this, 0, 150, CubicBezierInterpolator.EASE_OUT); + AnimatedTextView.AnimatedTextDrawable text = new AnimatedTextView.AnimatedTextDrawable(false, true, true); + float particlesAlpha; + AnimatedFloat particlesAlphaAnimated = new AnimatedFloat(CacheChart.this, 0, 150, CubicBezierInterpolator.EASE_OUT); + + boolean selected; + AnimatedFloat selectedAnimated = new AnimatedFloat(CacheChart.this, 0, 200, CubicBezierInterpolator.EASE_OUT_QUINT); + + { + text.setTextColor(Color.WHITE); + text.setAnimationProperties(.35f, 0, 200, CubicBezierInterpolator.EASE_OUT_QUINT); + text.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + text.setTextSize(AndroidUtilities.dp(15)); + text.setGravity(Gravity.CENTER); + } + + Path path = new Path(); + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + RectF pathBounds = new RectF(); + Paint uncut = new Paint(Paint.ANTI_ALIAS_FLAG); + Paint cut = new Paint(Paint.ANTI_ALIAS_FLAG); + { + cut.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + particlePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP)); + } + RectF rectF = new RectF(); + + int gradientWidth; + RadialGradient gradient; + Matrix gradientMatrix; + + private float lastAngleCenter, lastAngleSize, lastRounding, lastThickness, lastWidth, lastCx, lastCy; + + private void setupPath( + RectF outerRect, + RectF innerRect, + float angleCenter, float angleSize, + float rounding + ) { + + rounding = Math.min(rounding, (outerRect.width() - innerRect.width()) / 4); + rounding = Math.min(rounding, (float) (Math.PI * (angleSize / 180f) * (innerRect.width() / 2f))); + + float thickness = (outerRect.width() - innerRect.width()) / 2f; + if (lastAngleCenter == angleCenter && + lastAngleSize == angleSize && + lastRounding == rounding && + lastThickness == thickness && + lastWidth == outerRect.width() && + lastCx == outerRect.centerX() && + lastCy == outerRect.centerY() + ) { + return; + } + lastAngleCenter = angleCenter; + lastAngleSize = angleSize; + lastRounding = rounding; + lastThickness = thickness; + lastWidth = outerRect.width(); + lastCx = outerRect.centerX(); + lastCy = outerRect.centerY(); + + float angleFrom = angleCenter - angleSize; + float angleTo = angleCenter + angleSize; + + boolean hasRounding = rounding > 0; + + final float roundingOuterAngle = rounding / (float) (Math.PI * (outerRect.width() - rounding * 2)) * 360f; + final float roundingInnerAngle = rounding / (float) (Math.PI * (innerRect.width() + rounding * 2)) * 360f + SEPARATOR_ANGLE / 4f * (angleSize > 175f ? 0 : 1); + + final float outerRadiusMinusRounding = outerRect.width() / 2 - rounding; + final float innerRadiusPlusRounding = innerRect.width() / 2 + rounding; + + path.rewind(); + if (angleTo - angleFrom < SEPARATOR_ANGLE / 4f) { + return; + } + if (hasRounding) { + setCircleBounds( + roundingRect, + outerRect.centerX() + outerRadiusMinusRounding * Math.cos(toRad(angleFrom + roundingOuterAngle)), + outerRect.centerY() + outerRadiusMinusRounding * Math.sin(toRad(angleFrom + roundingOuterAngle)), + rounding + ); + path.arcTo(roundingRect, angleFrom + roundingOuterAngle - 90, 90); + } + path.arcTo(outerRect, angleFrom + roundingOuterAngle, angleTo - angleFrom - roundingOuterAngle * 2); + if (hasRounding) { + setCircleBounds( + roundingRect, + outerRect.centerX() + outerRadiusMinusRounding * Math.cos(toRad(angleTo - roundingOuterAngle)), + outerRect.centerY() + outerRadiusMinusRounding * Math.sin(toRad(angleTo - roundingOuterAngle)), + rounding + ); + path.arcTo(roundingRect, angleTo - roundingOuterAngle, 90); + setCircleBounds( + roundingRect, + innerRect.centerX() + innerRadiusPlusRounding * Math.cos(toRad(angleTo - roundingInnerAngle)), + innerRect.centerY() + innerRadiusPlusRounding * Math.sin(toRad(angleTo - roundingInnerAngle)), + rounding + ); + path.arcTo(roundingRect, angleTo - roundingInnerAngle + 90, 90); + } + path.arcTo(innerRect, angleTo - roundingInnerAngle, -(angleTo - angleFrom - roundingInnerAngle * 2)); + if (hasRounding) { + setCircleBounds( + roundingRect, + innerRect.centerX() + innerRadiusPlusRounding * Math.cos(toRad(angleFrom + roundingInnerAngle)), + innerRect.centerY() + innerRadiusPlusRounding * Math.sin(toRad(angleFrom + roundingInnerAngle)), + rounding + ); + path.arcTo(roundingRect, angleFrom + roundingInnerAngle + 180, 90); + } + path.close(); + + path.computeBounds(pathBounds, false); + } + + private void setGradientBounds(float centerX, float centerY, float radius, float angle) { + gradientMatrix.reset(); + gradientMatrix.setTranslate(centerX, centerY); +// gradientMatrix.preRotate(angle); + gradient.setLocalMatrix(gradientMatrix); + } + + private void drawParticles( + Canvas canvas, + float cx, float cy, + float textX, float textY, + float angleStart, float angleEnd, + float innerRadius, float outerRadius, + float textAlpha, float alpha + ) { + if (alpha <= 0) { + return; + } + long now = System.currentTimeMillis(); + float sqrt2 = (float) Math.sqrt(2); + if (particlesStart < 0) { + particlesStart = now; + } + float time = (now - particlesStart) / 10000f; + if (particle != null || !pathBounds.isEmpty()) { + int sz = particle.getWidth(); + + float stepangle = 7f; + + angleStart = angleStart % 360; + angleEnd = angleEnd % 360; + + int fromAngle = (int) Math.floor(angleStart / stepangle); + int toAngle = (int) Math.ceil(angleEnd / stepangle); + + for (int i = fromAngle; i <= toAngle; ++i) { + float angle = i * stepangle; + + float t = (float) (((time + 100) * (1f + (Math.sin(angle * 2000) + 1) * .25f)) % 1); + + float r = lerp(innerRadius - sz * sqrt2, outerRadius + sz * sqrt2, t); + + float x = (float) (cx + r * Math.cos(toRad(angle))); + float y = (float) (cy + r * Math.sin(toRad(angle))); + + float particleAlpha = + .65f + * alpha + * (-1.75f * Math.abs(t - .5f) + 1) + * (.25f * (float) (Math.sin(t * Math.PI) - 1) + 1) + * lerp(1, Math.min(MathUtils.distance(x, y, textX, textY) / AndroidUtilities.dpf2(64), 1), textAlpha); + particleAlpha = Math.max(0, Math.min(1, particleAlpha)); + particlePaint.setAlpha((int) (0xFF * particleAlpha)); + + float s = (float) (.75f * (.25f * (float) (Math.sin(t * Math.PI) - 1) + 1) * (.8f + (Math.sin(angle) + 1) * .25f)); + + canvas.save(); + canvas.translate(x, y); + canvas.scale(s, s); + canvas.drawBitmap(particle, -(sz >> 1), -(sz >> 1), particlePaint); + canvas.restore(); + } + } + } + + void draw( + Canvas canvas, + RectF outerRect, RectF innerRect, + float angleCenter, float angleSize, + float rounding, + float alpha, + float textAlpha + ) { + float selected = selectedAnimated.set(this.selected ? 1 : 0); + rectF.set(outerRect); + rectF.inset(selected * -AndroidUtilities.dp(9), selected * -AndroidUtilities.dp(9)); + + float x = (float) (rectF.centerX() + Math.cos(toRad(angleCenter)) * (rectF.width() + innerRect.width()) / 4); + float y = (float) (rectF.centerY() + Math.sin(toRad(angleCenter)) * (rectF.width() + innerRect.width()) / 4); + float loading = textAlpha; + textAlpha *= alpha * textAlphaAnimated.set(this.textAlpha); + float particlesAlpha = particlesAlphaAnimated.set(this.particlesAlpha); + + paint.setAlpha((int) (0xFF * alpha)); + if (angleSize * 2 >= 359f) { + canvas.saveLayerAlpha(rectF, 0xFF, Canvas.ALL_SAVE_FLAG); + canvas.drawCircle(rectF.centerX(), rectF.centerY(), rectF.width() / 2, uncut); + canvas.drawRect(rectF, paint); + drawParticles(canvas, rectF.centerX(), rectF.centerY(), x, y, angleCenter - angleSize, angleCenter + angleSize, innerRect.width() / 2f, rectF.width() / 2f, textAlpha, Math.max(0, loading / .75f - .75f) * particlesAlpha); + canvas.drawCircle(innerRect.centerX(), innerRect.centerY(), innerRect.width() / 2, cut); + canvas.restore(); + } else { + setupPath(rectF, innerRect, angleCenter, angleSize, rounding); + setGradientBounds(rectF.centerX(), outerRect.centerY(), rectF.width() / 2, angleCenter); + + canvas.saveLayerAlpha(rectF, 0xFF, Canvas.ALL_SAVE_FLAG); + canvas.drawPath(path, uncut); + canvas.drawRect(rectF, paint); + drawParticles(canvas, rectF.centerX(), rectF.centerY(), x, y, angleCenter - angleSize, angleCenter + angleSize, innerRect.width() / 2f, rectF.width() / 2f, textAlpha, Math.max(0, loading / .75f - .75f) * particlesAlpha); + canvas.restore(); + } + + float textScale = textScaleAnimated.set(this.textScale); + setCircleBounds(roundingRect, x, y, 0); + if (textScale != 1) { + canvas.save(); + canvas.scale(textScale, textScale, roundingRect.centerX(), roundingRect.centerY()); + } + text.setAlpha((int) (255 * textAlpha)); + text.setBounds((int) roundingRect.left, (int) roundingRect.top, (int) roundingRect.right, (int) roundingRect.bottom); + text.draw(canvas); + if (textScale != 1) { + canvas.restore(); + } + } + } + + public CacheChart(Context context) { + super(context); + + loadingBackgroundPaint.setStyle(Paint.Style.STROKE); + loadingBackgroundPaint.setColor(Theme.getColor(Theme.key_listSelector)); + + completePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + completeGradient = new LinearGradient(0, 0, 0, AndroidUtilities.dp(200), new int[] { 0x006ED556, 0xFF6ED556, 0xFF41BA71, 0x0041BA71 }, new float[] { 0, .07f, .93f, 1 }, Shader.TileMode.CLAMP); + completeGradientMatrix = new Matrix(); + completePaintStroke.setShader(completeGradient); + completePaint.setShader(completeGradient); + completePaintStroke.setStyle(Paint.Style.STROKE); + completePaintStroke.setStrokeCap(Paint.Cap.ROUND); + completePaintStroke.setStrokeJoin(Paint.Join.ROUND); + + topText.setAnimationProperties(.2f, 0, 450, CubicBezierInterpolator.EASE_OUT_QUINT); + topText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + topText.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + topText.setTextSize(AndroidUtilities.dp(32)); + topText.setGravity(Gravity.CENTER); + + bottomText.setAnimationProperties(.6f, 0, 450, CubicBezierInterpolator.EASE_OUT_QUINT); + bottomText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText)); + bottomText.setTextSize(AndroidUtilities.dp(12)); + bottomText.setGravity(Gravity.CENTER); + + for (int i = 0; i < SECTIONS_COUNT; ++i) { + Sector sector = sectors[i] = new Sector(); + final int color2 = Theme.blendOver(Theme.getColor(colorKeys[i]), 0x03000000); + final int color1 = Theme.blendOver(Theme.getColor(colorKeys[i]), 0x30ffffff); + sector.gradientWidth = AndroidUtilities.dp(50); + sector.gradient = new RadialGradient(0, 0, dp(86), new int[]{ color1, color2 }, new float[] { .3f, 1 }, Shader.TileMode.CLAMP); + sector.gradient.setLocalMatrix(sector.gradientMatrix = new Matrix()); + sector.paint.setShader(sector.gradient); + sector.particle = SvgHelper.getBitmap(particles[i], AndroidUtilities.dp(16), AndroidUtilities.dp(16), 0xffffffff); + } + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + float x = event.getX(); + float y = event.getY(); + + float r = MathUtils.distance(chartBounds.centerX(), chartBounds.centerY(), x, y); + float a = (float) (Math.atan2(y - chartBounds.centerY(), x - chartBounds.centerX()) / Math.PI * 180f); + if (a < 0) { + a += 360; + } + + int index = -1; + if (r > chartInnerBounds.width() / 2 && r < chartBounds.width() / 2f + AndroidUtilities.dp(14)) { + for (int i = 0; i < sectors.length; ++i) { + if (a >= sectors[i].angleCenter - sectors[i].angleSize && + a <= sectors[i].angleCenter + sectors[i].angleSize) { + index = i; + break; + } + } + } + + if (event.getAction() == MotionEvent.ACTION_DOWN) { + setSelected(index); + if (index >= 0) { + onSectionDown(index, index != -1); + if (getParent() != null) { + getParent().requestDisallowInterceptTouchEvent(true); + } + } + return true; + } else if (event.getAction() == MotionEvent.ACTION_MOVE) { + onSectionDown(index, index != -1); + setSelected(index); + if (index != -1) { + return true; + } + } else if (event.getAction() == MotionEvent.ACTION_UP) { + boolean done = false; + if (index != -1) { + onSectionClick(index); + done = true; + } + setSelected(-1); + onSectionDown(index, false); + if (done) { + return true; + } + } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { + setSelected(-1); + onSectionDown(index, false); + } + + return super.dispatchTouchEvent(event); + } + + protected void onSectionDown(int index, boolean down) { + + } + + protected void onSectionClick(int index) { + + } + + private int selectedIndex = -1; + public void setSelected(int index) { + if (index == selectedIndex) { + return; + } + for (int i = 0; i < sectors.length; ++i) { + if (index == i && sectors[i].angleSize <= 0) { + index = -1; + } + sectors[i].selected = index == i; + } + selectedIndex = index; + invalidate(); + } + + private static final float SEPARATOR_ANGLE = 2; + + private int[] tempPercents; + private float[] tempFloat; + + public static class SegmentSize { + int index; + boolean selected; + long size; + + public static SegmentSize of(long size, boolean selected) { + SegmentSize segment = new SegmentSize(); + segment.size = size; + segment.selected = selected; + return segment; + } + } + + public void setSegments(long totalSize, SegmentSize ...segments) { + if (segments == null || segments.length == 0) { + loading = true; + complete = totalSize == 0; + topText.setText(""); + bottomText.setText(""); + for (int i = 0; i < sectors.length; ++i) { + sectors[i].textAlpha = 0; + } + invalidate(); + return; + } + + loading = false; + + SpannableString percent = new SpannableString("%"); +// percent.setSpan(new RelativeSizeSpan(0.733f), 0, percent.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + int segmentsCount = segments.length; + long segmentsSum = 0; + for (int i = 0; i < segments.length; ++i) { + if (segments[i] == null) { + segments[i] = new SegmentSize(); + segments[i].size = 0; + } + segments[i].index = i; + if (segments[i] != null && segments[i].selected) { + segmentsSum += segments[i].size; + } + if (segments[i] == null || segments[i].size <= 0 || !segments[i].selected) { + segmentsCount--; + } + } + + if (segmentsSum <= 0) { + loading = true; + complete = totalSize <= 0; + topText.setText(""); + bottomText.setText(""); + for (int i = 0; i < sectors.length; ++i) { + sectors[i].textAlpha = 0; + } + invalidate(); + return; + } + + int underCount = 0; + float minus = 0; + for (int i = 0; i < segments.length; ++i) { + float progress = segments[i] == null || !segments[i].selected ? 0 : (float) segments[i].size / segmentsSum; + if (progress > 0 && progress < .02f) { + underCount++; + minus += progress; + } + } + final int count = Math.min(segments.length, sectors.length); + + if (tempPercents == null || tempPercents.length != segments.length) { + tempPercents = new int[segments.length]; + } + if (tempFloat == null || tempFloat.length != segments.length) { + tempFloat = new float[segments.length]; + } + for (int i = 0; i < segments.length; ++i) { + tempFloat[i] = segments[i] == null || !segments[i].selected ? 0 : segments[i].size / (float) segmentsSum; + } + AndroidUtilities.roundPercents(tempFloat, tempPercents); + Arrays.sort(segments, (a, b) -> Long.compare(a.size, b.size)); + for (int i = 0; i < segments.length - 1; ++i) { + if (segments[i].index == segments.length - 1) { + int from = i, to = 0; + SegmentSize temp = segments[to]; + segments[to] = segments[from]; + segments[from] = temp; + break; + } + } + + final float sum = 360 - SEPARATOR_ANGLE * (segmentsCount < 2 ? 0 : segmentsCount); + float prev = 0; + for (int index = 0, k = 0; index < segments.length; ++index) { + int i = segments[index].index; + float progress = segments[index] == null || !segments[index].selected ? 0 : (float) segments[index].size / segmentsSum; + SpannableStringBuilder string = new SpannableStringBuilder(); + string.append(String.format("%d", tempPercents[i])); + string.append(percent); + sectors[i].textAlpha = progress > .05 && progress < 1 ? 1f : 0f; + sectors[i].textScale = progress < .08f || tempPercents[i] >= 100 ? .85f : 1f; + sectors[i].particlesAlpha = 1; + if (sectors[i].textAlpha > 0) { + sectors[i].text.setText(string); + } + if (progress < .02f && progress > 0) { + progress = .02f; + } else { + progress *= 1f - (.02f * underCount - minus); + } + float angleFrom = (prev * sum + k * SEPARATOR_ANGLE); + float angleTo = angleFrom + progress * sum; + if (progress <= 0) { + sectors[i].angleCenter = (angleFrom + angleTo) / 2; + sectors[i].angleSize = Math.abs(angleTo - angleFrom) / 2; + sectors[i].textAlpha = 0; + continue; + } + sectors[i].angleCenter = (angleFrom + angleTo) / 2; + sectors[i].angleSize = Math.abs(angleTo - angleFrom) / 2; + prev += progress; + k++; + } + + String[] fileSize = AndroidUtilities.formatFileSize(segmentsSum).split(" "); + String top = fileSize.length > 0 ? fileSize[0] : ""; + if (top.length() >= 4 && segmentsSum < 1024L * 1024L * 1024L) { + top = top.split("\\.")[0]; + } + topText.setText(top); + bottomText.setText(fileSize.length > 1 ? fileSize[1] : ""); + + invalidate(); + } + + private static float toRad(float angles) { + return (float) (angles / 180f * Math.PI); + } + + private static float toAngl(float rad) { + return (float) (rad / Math.PI * 180f); + } + + private static float lerpAngle(float a, float b, float f) { + return (a + (((b - a + 360 + 180) % 360) - 180) * f + 360) % 360; + } + + private static void setCircleBounds(RectF rect, float centerX, float centerY, float radius) { + rect.set(centerX - radius, centerY - radius, centerX + radius, centerY + radius); + } + private static void setCircleBounds(RectF rect, double centerX, double centerY, float radius) { + setCircleBounds(rect, (float) centerX, (float) centerY, radius); + } + + private static Long start, loadedStart; + + @Override + protected void dispatchDraw(Canvas canvas) { + final float loading = loadingFloat.set(this.loading ? 1f : 0f); + final float complete = completeFloat.set(this.complete ? 1f : 0f); + + chartInnerBounds.set(chartBounds); + final float thickness = lerp(dpf2(38), dpf2(10), loading); + chartInnerBounds.inset(thickness, thickness); + + final float rounding = lerp(0, dp(60), loading); + + if (start == null) { + start = System.currentTimeMillis(); + } + if (!this.loading && loadedStart == null) { + loadedStart = System.currentTimeMillis(); + } else if (this.loading && loadedStart != null) { + loadedStart = null; + } + float loadingTime = ((loadedStart == null ? System.currentTimeMillis() : loadedStart) - start) * 0.6f; + + CircularProgressDrawable.getSegments(loadingTime % 5400, segmentsTmp); + float minAngle = segmentsTmp[0], maxAngle = segmentsTmp[1]; + + if (loading > 0) { + loadingBackgroundPaint.setStrokeWidth(thickness); + int wasAlpha = loadingBackgroundPaint.getAlpha(); + loadingBackgroundPaint.setAlpha((int) (wasAlpha * loading)); + canvas.drawCircle(chartBounds.centerX(), chartBounds.centerY(), (chartBounds.width() - thickness) / 2, loadingBackgroundPaint); + loadingBackgroundPaint.setAlpha(wasAlpha); + } + + boolean wouldUpdate = loading > 0 || complete > 0; + + for (int i = 0; i < SECTIONS_COUNT; ++i) { + Sector sector = sectors[i]; + + CircularProgressDrawable.getSegments((loadingTime + i * 80) % 5400, segmentsTmp); + float angleFrom = Math.min(Math.max(segmentsTmp[0], minAngle), maxAngle); + float angleTo = Math.min(Math.max(segmentsTmp[1], minAngle), maxAngle); + if (loading >= 1 && angleFrom >= angleTo) { + continue; + } + + float angleCenter = (angleFrom + angleTo) / 2; + float angleSize = Math.abs(angleTo - angleFrom) / 2; + if (loading <= 0) { + angleCenter = sector.angleCenterAnimated.set(sector.angleCenter); + angleSize = sector.angleSizeAnimated.set(sector.angleSize); + } else if (loading < 1) { + float angleCenterSector = sector.angleCenterAnimated.set(sector.angleCenter); + angleCenter = lerp(angleCenterSector + (float) Math.floor(maxAngle / 360) * 360, angleCenter, loading); + angleSize = lerp(sector.angleSizeAnimated.set(sector.angleSize), angleSize, loading); + } + wouldUpdate = sector.angleCenterAnimated.isInProgress() || sector.angleSizeAnimated.isInProgress() || wouldUpdate; + + sector.draw(canvas, chartBounds, chartInnerBounds, angleCenter, angleSize, rounding, 1f - complete, 1f - loading); + } + + topText.setAlpha((int) (255 * (1f - loading) * (1f - complete))); + topText.setBounds((int) (chartBounds.centerX()), (int) (chartBounds.centerY() - AndroidUtilities.dp(5)), (int) (chartBounds.centerX()), (int) (chartBounds.centerY() - AndroidUtilities.dp(3))); + topText.draw(canvas); + wouldUpdate = topText.isAnimating() || wouldUpdate; + + bottomText.setAlpha((int) (255 * (1f - loading) * (1f - complete))); + bottomText.setBounds((int) (chartBounds.centerX()), (int) (chartBounds.centerY() + AndroidUtilities.dp(22)), (int) (chartBounds.centerX()), (int) (chartBounds.centerY() + AndroidUtilities.dp(22))); + bottomText.draw(canvas); + wouldUpdate = bottomText.isAnimating() || wouldUpdate; + + if (complete > 0) { + if (completeDrawable == null) { + completeDrawable = new StarParticlesView.Drawable(25); + completeDrawable.type = 100; + completeDrawable.roundEffect = false; + completeDrawable.useRotate = true; + completeDrawable.useBlur = false; + completeDrawable.checkBounds = true; + completeDrawable.size1 = 18; + completeDrawable.distributionAlgorithm = false; + completeDrawable.excludeRadius = AndroidUtilities.dp(80); + completeDrawable.k1 = completeDrawable.k2 = completeDrawable.k3 = 0.7f; + completeDrawable.init(); + + float d = Math.min(getMeasuredHeight(), Math.min(getMeasuredWidth(), AndroidUtilities.dp(150))); + completeDrawable.rect.set(0, 0, d, d); + completeDrawable.rect.offset((getMeasuredWidth() - completeDrawable.rect.width()) / 2, (getMeasuredHeight() - completeDrawable.rect.height()) / 2); + completeDrawable.rect2.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); + completeDrawable.resetPositions(); + } + + canvas.saveLayerAlpha(0, 0, getWidth(), getHeight(), 255, Canvas.ALL_SAVE_FLAG); + completeDrawable.onDraw(canvas, complete); + completePaint.setAlpha((int) (0xFF * complete)); + canvas.drawRect(0, 0, getWidth(), getHeight(), completePaint); + canvas.restore(); + + completePaintStroke.setStrokeWidth(thickness); + completePaintStroke.setAlpha((int) (0xFF * complete)); + canvas.drawCircle(chartBounds.centerX(), chartBounds.centerY(), (chartBounds.width() - thickness) / 2, completePaintStroke); + + if (completePathBounds == null || completePathBounds.equals(chartBounds)) { + if (completePathBounds == null) { + completePathBounds = new RectF(); + } + completePathBounds.set(chartBounds); + completePath.rewind(); + completePath.moveTo(chartBounds.left + chartBounds.width() * .348f, chartBounds.top + chartBounds.height() * .538f); + completePath.lineTo(chartBounds.left + chartBounds.width() * .447f, chartBounds.top + chartBounds.height() * .636f); + completePath.lineTo(chartBounds.left + chartBounds.width() * .678f, chartBounds.top + chartBounds.height() * .402f); + } + completePaintStroke.setStrokeWidth(AndroidUtilities.dp(10)); + canvas.drawPath(completePath, completePaintStroke); + } + + if (wouldUpdate || true) { + invalidate(); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int width = MeasureSpec.getSize(widthMeasureSpec); + final int height = dp(200); + + final int d = dp(172); + chartBounds.set( + (width - d) / 2f, + (height - d) / 2f, + (width + d) / 2f, + (height + d) / 2f + ); + + completeGradientMatrix.reset(); + completeGradientMatrix.setTranslate(chartBounds.left, 0); + completeGradient.setLocalMatrix(completeGradientMatrix); + + if (completeDrawable != null) { + completeDrawable.rect.set(0, 0, AndroidUtilities.dp(140), AndroidUtilities.dp(140)); + completeDrawable.rect.offset((getMeasuredWidth() - completeDrawable.rect.width()) / 2, (getMeasuredHeight() - completeDrawable.rect.height()) / 2); + completeDrawable.rect2.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); + completeDrawable.resetPositions(); + } + + super.onMeasure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) + ); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/CanvasButton.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/CanvasButton.java index e01258d42..97303f65a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/CanvasButton.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/CanvasButton.java @@ -48,21 +48,32 @@ public class CanvasButton { } }; private boolean longPressEnabled; + CornerPathEffect pathEffect; + boolean rounded; + float roundRadius = AndroidUtilities.dp(12); + Paint maskPaint; public CanvasButton(View parent) { this.parent = parent; - paint.setPathEffect(new CornerPathEffect(AndroidUtilities.dp(12))); - + paint.setPathEffect(pathEffect = new CornerPathEffect(roundRadius)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - final Paint maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG); maskPaint.setFilterBitmap(true); maskPaint.setPathEffect(new CornerPathEffect(AndroidUtilities.dp(12))); maskPaint.setColor(0xffffffff); + + final Paint maskPaint2 = new Paint(Paint.ANTI_ALIAS_FLAG); + maskPaint2.setFilterBitmap(true); + maskPaint2.setColor(0xffffffff); Drawable maskDrawable = new Drawable() { @Override public void draw(Canvas canvas) { - drawInternal(canvas, maskPaint); + if (usingRectCount > 1) { + drawInternal(canvas, maskPaint); + } else { + drawInternal(canvas, maskPaint2); + } } @Override @@ -128,9 +139,21 @@ public class CanvasButton { } pathCreated = true; } + paint.setPathEffect(pathEffect); canvas.drawPath(drawingPath, paint); } else if (usingRectCount == 1) { - canvas.drawRoundRect(drawingRects.get(0), AndroidUtilities.dp(10), AndroidUtilities.dp(10), paint); + if (selectorDrawable != null) { + selectorDrawable.setBounds((int) drawingRects.get(0).left, (int) drawingRects.get(0).top, (int) drawingRects.get(0).right, (int) drawingRects.get(0).bottom); + } + if (rounded) { + paint.setPathEffect(null); + float rad = Math.min(drawingRects.get(0).width(), drawingRects.get(0).height()) / 2f; + canvas.drawRoundRect(drawingRects.get(0), rad, rad, paint); + } else { + paint.setPathEffect(pathEffect); + canvas.drawRoundRect(drawingRects.get(0), 0, 0, paint); + } + } } @@ -182,9 +205,13 @@ public class CanvasButton { } public void setColor(int color) { + setColor(color, color); + } + + public void setColor(int color, int selectorColor) { paint.setColor(color); if (selectorDrawable != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - Theme.setSelectorDrawableColor(selectorDrawable, color, true); + Theme.setSelectorDrawableColor(selectorDrawable, selectorColor, true); } } @@ -215,4 +242,22 @@ public class CanvasButton { longPressEnabled = true; longPressRunnable = runnable; } + + public void setRounded(boolean rounded) { + this.rounded = rounded; + } + + public void setRoundRadius(int radius) { + roundRadius = radius; + pathEffect = new CornerPathEffect(radius); + maskPaint.setPathEffect(new CornerPathEffect(radius)); + } + + public void cancelRipple() { + if (Build.VERSION.SDK_INT >= 21 && selectorDrawable != null) { + selectorDrawable.setState(StateSet.NOTHING); + selectorDrawable.jumpToCurrentState(); + } + + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java index 75a31b271..3743f1316 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java @@ -3803,6 +3803,8 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific preferences.edit().putBoolean(isChannel ? "currentModeVideoChannel" : "currentModeVideo", visible).apply(); } audioVideoSendButton.setState(isInVideoMode() ? ChatActivityEnterViewAnimatedIconView.State.VIDEO : ChatActivityEnterViewAnimatedIconView.State.VOICE, animated); + audioVideoSendButton.setContentDescription(LocaleController.getString(isInVideoMode() ? R.string.AccDescrVideoMessage : R.string.AccDescrVoiceMessage)); + audioVideoButtonContainer.setContentDescription(LocaleController.getString(isInVideoMode() ? R.string.AccDescrVideoMessage : R.string.AccDescrVoiceMessage)); audioVideoSendButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); } @@ -4875,7 +4877,7 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific editingMessageObject.editingMessage = message[0]; editingMessageObject.editingMessageEntities = entities; editingMessageObject.editingMessageSearchWebPage = messageWebPageSearch; - SendMessagesHelper.getInstance(currentAccount).editMessage(editingMessageObject, null, null, null, null, null, false, null); + SendMessagesHelper.getInstance(currentAccount).editMessage(editingMessageObject, null, null, null, null, null, false, editingMessageObject.hasMediaSpoilers(), null); } setEditingMessageObject(null, false); } @@ -7145,6 +7147,10 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific } public boolean didPressedBotButton(final TLRPC.KeyboardButton button, final MessageObject replyMessageObject, final MessageObject messageObject) { + return didPressedBotButton(button, replyMessageObject, messageObject, null); + } + + public boolean didPressedBotButton(final TLRPC.KeyboardButton button, final MessageObject replyMessageObject, final MessageObject messageObject, final Browser.Progress progress) { if (button == null || messageObject == null) { return false; } @@ -7152,9 +7158,9 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific SendMessagesHelper.getInstance(currentAccount).sendMessage(button.text, dialog_id, replyMessageObject, getThreadMessage(), null, false, null, null, null, true, 0, null, false); } else if (button instanceof TLRPC.TL_keyboardButtonUrl) { if (Browser.urlMustNotHaveConfirmation(button.url)) { - Browser.openUrl(parentActivity, button.url); + Browser.openUrl(parentActivity, Uri.parse(button.url), true, true, progress); } else { - AlertsCreator.showOpenUrlAlert(parentFragment, button.url, false, true, resourcesProvider); + AlertsCreator.showOpenUrlAlert(parentFragment, button.url, false, true, true, progress, resourcesProvider); } } else if (button instanceof TLRPC.TL_keyboardButtonRequestPhone) { parentFragment.shareMyContact(2, messageObject); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java index 854ae387c..47e1f0a83 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java @@ -120,6 +120,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N public boolean canOpenPreview = false; private boolean isSoundPicker = false; + private ImageUpdater.AvatarFor setAvatarFor; public void setCanOpenPreview(boolean canOpenPreview) { this.canOpenPreview = canOpenPreview; @@ -309,6 +310,11 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N public void onSetBackButtonVisible(boolean visible) { AndroidUtilities.updateImageViewImageAnimated(actionBar.getBackButton(), visible ? R.drawable.ic_ab_back : R.drawable.ic_close_white); } + + @Override + public boolean isClipboardAvailable() { + return true; + } }); MessageObject replyingObject = ((ChatActivity) baseFragment).getChatActivityEnterView().getReplyingMessageObject(); botAttachLayouts.get(id).requestWebView(currentAccount, ((ChatActivity) baseFragment).getDialogId(), id, false, replyingObject != null ? replyingObject.messageOwner.id : 0, startCommand); @@ -329,6 +335,14 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } } + public void avatarFor(ImageUpdater.AvatarFor avatarFor) { + setAvatarFor = avatarFor; + } + + public ImageUpdater.AvatarFor getAvatarFor() { + return setAvatarFor; + } + public interface ChatAttachViewDelegate { void didPressedButton(int button, boolean arg, boolean notify, int scheduleDate, boolean forceDocument); @@ -360,6 +374,9 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N @Override public void setValue(AttachAlertLayout object, float value) { translationProgress = value; + if (nextAttachLayout == null) { + return; + } if (nextAttachLayout instanceof ChatAttachAlertPhotoLayoutPreview || currentAttachLayout instanceof ChatAttachAlertPhotoLayoutPreview) { int width = Math.max(nextAttachLayout.getWidth(), currentAttachLayout.getWidth()); if (nextAttachLayout instanceof ChatAttachAlertPhotoLayoutPreview) { @@ -1855,6 +1872,8 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N selectedTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); selectedTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); selectedTextView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); + selectedTextView.setMaxLines(1); + selectedTextView.setEllipsize(TextUtils.TruncateAt.END); selectedView.addView(selectedTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL)); selectedArrowImageView = new ImageView(context); @@ -2790,6 +2809,14 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } } + public AttachAlertLayout getCurrentAttachLayout() { + return currentAttachLayout; + } + + public ChatAttachAlertPhotoLayoutPreview getPhotoPreviewLayout() { + return photoPreviewLayout; + } + public void updatePhotoPreview(boolean show) { if (show) { if (!canOpenPreview) { @@ -3775,6 +3802,10 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } } + public TextView getSelectedTextView() { + return selectedTextView; + } + public void setSoundPicker() { isSoundPicker = true; buttonsRecyclerView.setVisibility(View.GONE); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertPhotoLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertPhotoLayout.java index e5da938c6..c32044ed2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertPhotoLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertPhotoLayout.java @@ -81,6 +81,7 @@ import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.ActionBarMenuSubItem; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.BasePermissionsActivity; import org.telegram.ui.Cells.PhotoAttachCameraCell; @@ -94,6 +95,8 @@ import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; +import java.util.Map; public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayout implements NotificationCenter.NotificationCenterDelegate { @@ -196,8 +199,14 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou public final static int group = 0; public final static int compress = 1; - public final static int open_in = 2; - public final static int preview = 3; + public final static int spoiler = 2; + public final static int open_in = 3; + public final static int preview_gap = 4; + public final static int preview = 5; + + private ActionBarMenuSubItem spoilerItem; + private ActionBarMenuSubItem compressItem; + protected ActionBarMenuSubItem previewItem; boolean forceDarkTheme; private int animationIndex = -1; @@ -287,15 +296,42 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou } } + private void setCurrentSpoilerVisible(int i, boolean visible) { + PhotoViewer photoViewer = PhotoViewer.getInstance(); + int index = i == -1 ? photoViewer.getCurrentIndex() : i; + List photos = photoViewer.getImagesArrLocals(); + boolean hasSpoiler = photos != null && !photos.isEmpty() && index < photos.size() && photos.get(index) instanceof MediaController.PhotoEntry && ((MediaController.PhotoEntry) photos.get(index)).hasSpoiler; + + if (hasSpoiler) { + MediaController.PhotoEntry entry = (MediaController.PhotoEntry) photos.get(index); + + gridView.forAllChild(view -> { + if (view instanceof PhotoAttachPhotoCell) { + PhotoAttachPhotoCell cell = (PhotoAttachPhotoCell) view; + if (cell.getPhotoEntry() == entry) { + cell.setHasSpoiler(visible, 250f); + } + } + }); + } + } + private PhotoViewer.PhotoViewerProvider photoViewerProvider = new BasePhotoProvider() { @Override public void onOpen() { pauseCameraPreview(); + setCurrentSpoilerVisible(-1, true); + } + + @Override + public void onPreClose() { + setCurrentSpoilerVisible(-1, false); } @Override public void onClose() { resumeCameraPreview(); + AndroidUtilities.runOnUIThread(()-> setCurrentSpoilerVisible(-1, true), 150); } @Override @@ -535,10 +571,14 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou checkCamera(false); - parentAlert.selectedMenuItem.addSubItem(group, LocaleController.getString("SendWithoutGrouping", R.string.SendWithoutGrouping)); - parentAlert.selectedMenuItem.addSubItem(compress, LocaleController.getString("SendWithoutCompression", R.string.SendWithoutCompression)); + compressItem = parentAlert.selectedMenuItem.addSubItem(compress, R.drawable.msg_filehq, LocaleController.getString("SendWithoutCompression", R.string.SendWithoutCompression)); + parentAlert.selectedMenuItem.addSubItem(group, R.drawable.msg_ungroup, LocaleController.getString("SendWithoutGrouping", R.string.SendWithoutGrouping)); + spoilerItem = parentAlert.selectedMenuItem.addSubItem(spoiler, R.drawable.msg_spoiler, LocaleController.getString("EnablePhotoSpoiler", R.string.EnablePhotoSpoiler)); parentAlert.selectedMenuItem.addSubItem(open_in, R.drawable.msg_openin, LocaleController.getString("OpenInExternalApp", R.string.OpenInExternalApp)); - parentAlert.selectedMenuItem.addSubItem(preview, LocaleController.getString("AttachMediaPreviewButton", R.string.AttachMediaPreviewButton)); + View gap = parentAlert.selectedMenuItem.addGap(preview_gap); + gap.setBackgroundColor(Theme.getColor(Theme.key_actionBarDefaultSubmenuSeparator, resourcesProvider)); + previewItem = parentAlert.selectedMenuItem.addSubItem(preview, R.drawable.msg_view_file, LocaleController.getString("AttachMediaPreviewButton", R.string.AttachMediaPreviewButton)); + parentAlert.selectedMenuItem.setFitSubItems(true); gridView = new RecyclerListView(context, resourcesProvider) { @Override @@ -634,7 +674,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou } }); gridView.setLayoutManager(layoutManager); - gridView.setOnItemClickListener((view, position) -> { + gridView.setOnItemClickListener((view, position, x, y) -> { if (!mediaEnabled || parentAlert.baseFragment == null || parentAlert.baseFragment.getParentActivity() == null) { return; } @@ -693,10 +733,28 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou photoEntry1.caption = parentAlert.getCommentTextView().getText(); } } - PhotoViewer.getInstance().openPhotoForSelect(arrayList, position, type, false, photoViewerProvider, chatActivity); - if (captionForAllMedia()) { - PhotoViewer.getInstance().setCaption(parentAlert.getCommentTextView().getText()); + if (parentAlert.getAvatarFor() != null) { + boolean isVideo = false; + if (arrayList.get(position) instanceof MediaController.PhotoEntry) { + isVideo = ((MediaController.PhotoEntry) arrayList.get(position)).isVideo; + } + parentAlert.getAvatarFor().isVideo = isVideo; } + + boolean hasSpoiler = arrayList.get(position) instanceof MediaController.PhotoEntry && ((MediaController.PhotoEntry) arrayList.get(position)).hasSpoiler; + + if (hasSpoiler) { + setCurrentSpoilerVisible(position, false); + } + int finalPosition = position; + AndroidUtilities.runOnUIThread(()->{ + PhotoViewer.getInstance().openPhotoForSelect(arrayList, finalPosition, type, false, photoViewerProvider, chatActivity); + + PhotoViewer.getInstance().setAvatarFor(parentAlert.getAvatarFor()); + if (captionForAllMedia()) { + PhotoViewer.getInstance().setCaption(parentAlert.getCommentTextView().getText()); + } + }, hasSpoiler ? 250 : 0); } else { if (SharedConfig.inappCamera) { openCamera(true); @@ -1162,6 +1220,9 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou } private void clearSelectedPhotos() { + spoilerItem.setText(LocaleController.getString(R.string.EnablePhotoSpoiler)); + spoilerItem.setAnimatedIcon(R.raw.photo_spoiler); + parentAlert.selectedMenuItem.showSubItem(compress); if (!selectedPhotos.isEmpty()) { for (HashMap.Entry entry : selectedPhotos.entrySet()) { MediaController.PhotoEntry photoEntry = (MediaController.PhotoEntry) entry.getValue(); @@ -1409,6 +1470,9 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou arrayList = getAllPhotosArray(); index = cameraPhotos.size() - 1; } + if (parentAlert.getAvatarFor() != null && entry != null) { + parentAlert.getAvatarFor().isVideo = entry.isVideo; + } PhotoViewer.getInstance().openPhotoForSelect(arrayList, index, type, false, new BasePhotoProvider() { @Override @@ -1535,6 +1599,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou return parentAlert.maxSelectedPhotos != 1; } }, chatActivity); + PhotoViewer.getInstance().setAvatarFor(parentAlert.getAvatarFor()); } private void showZoomControls(boolean show, boolean animated) { @@ -2550,6 +2615,59 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou parentAlert.applyCaption(); parentAlert.delegate.didPressedButton(4, true, true, 0, false); } + } else if (id == spoiler) { + if (parentAlert.getPhotoPreviewLayout() != null) { + parentAlert.getPhotoPreviewLayout().startMediaCrossfade(); + } + + boolean spoilersEnabled = false; + for (Map.Entry en : selectedPhotos.entrySet()) { + MediaController.PhotoEntry entry = (MediaController.PhotoEntry) en.getValue(); + if (entry.hasSpoiler) { + spoilersEnabled = true; + break; + } + } + spoilersEnabled = !spoilersEnabled; + boolean finalSpoilersEnabled = spoilersEnabled; + AndroidUtilities.runOnUIThread(()-> { + spoilerItem.setText(LocaleController.getString(finalSpoilersEnabled ? R.string.DisablePhotoSpoiler : R.string.EnablePhotoSpoiler)); + if (finalSpoilersEnabled) { + spoilerItem.setIcon(R.drawable.msg_spoiler_off); + } else { + spoilerItem.setAnimatedIcon(R.raw.photo_spoiler); + } + if (finalSpoilersEnabled) { + parentAlert.selectedMenuItem.hideSubItem(compress); + } else { + parentAlert.selectedMenuItem.showSubItem(compress); + } + }, 200); + + List selectedIds = new ArrayList<>(); + for (HashMap.Entry entry : selectedPhotos.entrySet()) { + if (entry.getValue() instanceof MediaController.PhotoEntry) { + MediaController.PhotoEntry photoEntry = (MediaController.PhotoEntry) entry.getValue(); + photoEntry.hasSpoiler = spoilersEnabled; + photoEntry.isChatPreviewSpoilerRevealed = false; + photoEntry.isAttachSpoilerRevealed = false; + selectedIds.add(photoEntry.imageId); + } + } + + gridView.forAllChild(view -> { + if (view instanceof PhotoAttachPhotoCell) { + MediaController.PhotoEntry entry = ((PhotoAttachPhotoCell) view).getPhotoEntry(); + ((PhotoAttachPhotoCell) view).setHasSpoiler(entry != null && selectedIds.contains(entry.imageId) && finalSpoilersEnabled); + } + }); + if (parentAlert.getCurrentAttachLayout() != this) { + adapter.notifyDataSetChanged(); + } + + if (parentAlert.getPhotoPreviewLayout() != null) { + parentAlert.getPhotoPreviewLayout().invalidateGroupsView(); + } } else if (id == open_in) { try { if (parentAlert.baseFragment instanceof ChatActivity || parentAlert.avatarPicker == 2) { @@ -2582,7 +2700,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou FileLog.e(e); } } else if (id == preview) { - parentAlert.updatePhotoPreview(true); + parentAlert.updatePhotoPreview(parentAlert.getCurrentAttachLayout() != parentAlert.getPhotoPreviewLayout()); } else if (id >= 10) { selectedAlbumEntry = dropDownAlbums.get(id - 10); if (selectedAlbumEntry == galleryAlbumEntry) { @@ -2618,9 +2736,22 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou parentAlert.selectedMenuItem.hideSubItem(open_in); } if (count > 1) { + parentAlert.selectedMenuItem.showSubItem(preview_gap); parentAlert.selectedMenuItem.showSubItem(preview); + compressItem.setText(LocaleController.getString(R.string.SendAsFiles)); } else { + parentAlert.selectedMenuItem.hideSubItem(preview_gap); parentAlert.selectedMenuItem.hideSubItem(preview); + if (count != 0) { + compressItem.setText(LocaleController.getString(R.string.SendAsFile)); + } + } + if (count == 0 || parentAlert != null && parentAlert.baseFragment instanceof ChatActivity && ((ChatActivity) parentAlert.baseFragment).isSecretChat()) { + spoilerItem.setText(LocaleController.getString(R.string.EnablePhotoSpoiler)); + spoilerItem.setAnimatedIcon(R.raw.photo_spoiler); + parentAlert.selectedMenuItem.hideSubItem(spoiler); + } else { + parentAlert.selectedMenuItem.showSubItem(spoiler); } } @@ -2692,7 +2823,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou @Override void onResume() { - if (parentAlert.isShowing() && !parentAlert.isDismissed()) { + if (parentAlert.isShowing() && !parentAlert.isDismissed() && !PhotoViewer.getInstance().isVisible()) { checkCamera(false); } } @@ -2985,6 +3116,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou private void resumeCameraPreview() { try { + checkCamera(false); if (cameraView != null) { CameraSession cameraSession = cameraView.getCameraSession(); if (cameraSession != null) { @@ -3002,6 +3134,12 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou cameraView.setVisibility(GONE); cameraIcon.setVisibility(GONE); } + for (Map.Entry en : selectedPhotos.entrySet()) { + if (en.getValue() instanceof MediaController.PhotoEntry) { + ((MediaController.PhotoEntry) en.getValue()).isAttachSpoilerRevealed = false; + } + } + adapter.notifyDataSetChanged(); } @Override @@ -3358,6 +3496,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou cameraAttachAdapter.notifyItemChanged(updateIndex); } parentAlert.updateCountButton(added ? 1 : 2); + cell.setHasSpoiler(photoEntry.hasSpoiler); }); return new RecyclerListView.Holder(cell); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertPhotoLayoutPreview.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertPhotoLayoutPreview.java index bd5f5a5a5..44a61c854 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertPhotoLayoutPreview.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertPhotoLayoutPreview.java @@ -12,9 +12,12 @@ import android.animation.ValueAnimator; 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.Rect; import android.graphics.RectF; +import android.graphics.Region; import android.graphics.drawable.Drawable; import android.media.MediaMetadataRetriever; import android.os.Build; @@ -33,6 +36,8 @@ import android.view.animation.Interpolator; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.core.graphics.ColorUtils; +import androidx.core.math.MathUtils; import androidx.exifinterface.media.ExifInterface; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -45,6 +50,7 @@ import org.telegram.messenger.MediaController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.R; import org.telegram.messenger.SharedConfig; +import org.telegram.messenger.Utilities; import org.telegram.messenger.VideoEditedInfo; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; @@ -53,6 +59,7 @@ import org.telegram.ui.ActionBar.ActionBarMenuItem; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Cells.ChatActionCell; import org.telegram.ui.ChatActivity; +import org.telegram.ui.Components.spoilers.SpoilerEffect; import org.telegram.ui.PhotoViewer; import java.util.ArrayList; @@ -186,6 +193,18 @@ public class ChatAttachAlertPhotoLayoutPreview extends ChatAttachAlert.AttachAle videoPlayImage = context.getResources().getDrawable(R.drawable.play_mini_video); } + public void startMediaCrossfade() { + for (PreviewGroupsView.PreviewGroupCell cell : groupsView.groupCells) { + for (PreviewGroupsView.PreviewGroupCell.MediaCell mediaCell : cell.media) { + mediaCell.startCrossfade(); + } + } + } + + public void invalidateGroupsView() { + groupsView.invalidate(); + } + private ViewPropertyAnimator headerAnimator; private ChatAttachAlertPhotoLayout photoLayout; private boolean shown = false; @@ -209,7 +228,10 @@ public class ChatAttachAlertPhotoLayoutPreview extends ChatAttachAlert.AttachAle postDelayed(() -> { if (shown) { - parentAlert.selectedMenuItem.hideSubItem(ChatAttachAlertPhotoLayout.preview); + if (parentAlert.getPhotoLayout() != null) { + parentAlert.getPhotoLayout().previewItem.setIcon(R.drawable.ic_ab_back); + parentAlert.getPhotoLayout().previewItem.setText(LocaleController.getString(R.string.Back)); + } } }, 250); @@ -236,7 +258,10 @@ public class ChatAttachAlertPhotoLayoutPreview extends ChatAttachAlert.AttachAle headerAnimator.start(); if (getSelectedItemsCount() > 1) { - parentAlert.selectedMenuItem.showSubItem(ChatAttachAlertPhotoLayout.preview); + if (parentAlert.getPhotoLayout() != null) { + parentAlert.getPhotoLayout().previewItem.setIcon(R.drawable.msg_view_file); + parentAlert.getPhotoLayout().previewItem.setText(LocaleController.getString(R.string.AttachMediaPreviewButton)); + } } groupsView.toPhotoLayout(photoLayout, true); @@ -253,6 +278,13 @@ public class ChatAttachAlertPhotoLayoutPreview extends ChatAttachAlert.AttachAle if (undoView != null) { undoView.hide(false, 0); } + for (PreviewGroupsView.PreviewGroupCell cell : groupsView.groupCells) { + for (PreviewGroupsView.PreviewGroupCell.MediaCell mediaCell : cell.media) { + if (mediaCell.wasSpoiler && mediaCell.photoEntry != null) { + mediaCell.photoEntry.isChatPreviewSpoilerRevealed = false; + } + } + } } @Override @@ -1725,82 +1757,86 @@ public class ChatAttachAlertPhotoLayoutPreview extends ChatAttachAlert.AttachAle stopDragging(); result = true; } else if (action == MotionEvent.ACTION_UP && draggingCell == null && tapMediaCell != null && tapGroupCell != null) { + if (tapMediaCell.wasSpoiler && tapMediaCell.spoilerRevealProgress == 0f) { + tapMediaCell.startRevealMedia(event.getX(), event.getY()); + result = true; + } else { + RectF cellRect = tapMediaCell.drawingRect(); + AndroidUtilities.rectTmp.set(cellRect.right - AndroidUtilities.dp(36.4f), tapGroupCell.top + cellRect.top, cellRect.right, tapGroupCell.top + cellRect.top + AndroidUtilities.dp(36.4f)); + boolean tappedAtIndex = AndroidUtilities.rectTmp.contains(touchX, touchY - tapMediaCell.groupCell.y); - RectF cellRect = tapMediaCell.drawingRect(); - AndroidUtilities.rectTmp.set(cellRect.right - AndroidUtilities.dp(36.4f), tapGroupCell.top + cellRect.top, cellRect.right, tapGroupCell.top + cellRect.top + AndroidUtilities.dp(36.4f)); - boolean tappedAtIndex = AndroidUtilities.rectTmp.contains(touchX, touchY - tapMediaCell.groupCell.y); - - if (tappedAtIndex) { - if (getSelectedItemsCount() > 1) { - // short tap -> remove photo - final MediaController.PhotoEntry photo = tapMediaCell.photoEntry; - final int index = tapGroupCell.group.photos.indexOf(photo); - if (index >= 0) { - saveDeletedImageId(photo); - final PreviewGroupCell groupCell = tapGroupCell; - groupCell.group.photos.remove(index); - groupCell.setGroup(groupCell.group, true); - updateGroups(); - toPhotoLayout(photoLayout, false); - - final int currentUndoViewId = ++undoViewId; - undoView.showWithAction(0, ACTION_PREVIEW_MEDIA_DESELECTED, photo, null, () -> { - if (draggingAnimator != null) { - draggingAnimator.cancel(); - } - draggingCell = null; - draggingT = 0; - pushToGroup(groupCell, photo, index); + if (tappedAtIndex) { + if (getSelectedItemsCount() > 1) { + // short tap -> remove photo + final MediaController.PhotoEntry photo = tapMediaCell.photoEntry; + final int index = tapGroupCell.group.photos.indexOf(photo); + if (index >= 0) { + saveDeletedImageId(photo); + final PreviewGroupCell groupCell = tapGroupCell; + groupCell.group.photos.remove(index); + groupCell.setGroup(groupCell.group, true); updateGroups(); toPhotoLayout(photoLayout, false); - }); - postDelayed(() -> { - if (currentUndoViewId == undoViewId && undoView.isShown()) { - undoView.hide(true, 1); - } - }, 1000 * 4); - } + final int currentUndoViewId = ++undoViewId; + undoView.showWithAction(0, ACTION_PREVIEW_MEDIA_DESELECTED, photo, null, () -> { + if (draggingAnimator != null) { + draggingAnimator.cancel(); + } + draggingCell = null; + draggingT = 0; + pushToGroup(groupCell, photo, index); + updateGroups(); + toPhotoLayout(photoLayout, false); + }); - if (draggingAnimator != null) { - draggingAnimator.cancel(); + postDelayed(() -> { + if (currentUndoViewId == undoViewId && undoView.isShown()) { + undoView.hide(true, 1); + } + }, 1000 * 4); + } + + if (draggingAnimator != null) { + draggingAnimator.cancel(); + } } - } - } else { - calcPhotoArrays(); - ArrayList arrayList = getPhotos(); - int position = arrayList.indexOf(tapMediaCell.photoEntry); - ChatActivity chatActivity; - int type; - if (parentAlert.avatarPicker != 0) { - chatActivity = null; - type = PhotoViewer.SELECT_TYPE_AVATAR; - } else if (parentAlert.baseFragment instanceof ChatActivity) { - chatActivity = (ChatActivity) parentAlert.baseFragment; - type = 0; } else { - chatActivity = null; - type = 4; - } - if (!parentAlert.delegate.needEnterComment()) { - AndroidUtilities.hideKeyboard(parentAlert.baseFragment.getFragmentView().findFocus()); - AndroidUtilities.hideKeyboard(parentAlert.getContainer().findFocus()); - } - PhotoViewer.getInstance().setParentActivity(parentAlert.baseFragment, resourcesProvider); - PhotoViewer.getInstance().setParentAlert(parentAlert); - PhotoViewer.getInstance().setMaxSelectedPhotos(parentAlert.maxSelectedPhotos, parentAlert.allowOrder); - photoViewerProvider.init(arrayList); - ArrayList objectArrayList = new ArrayList<>(arrayList); - PhotoViewer.getInstance().openPhotoForSelect(objectArrayList, position, type, false, photoViewerProvider, chatActivity); - if (photoLayout.captionForAllMedia()) { - PhotoViewer.getInstance().setCaption(parentAlert.getCommentTextView().getText()); + calcPhotoArrays(); + ArrayList arrayList = getPhotos(); + int position = arrayList.indexOf(tapMediaCell.photoEntry); + ChatActivity chatActivity; + int type; + if (parentAlert.avatarPicker != 0) { + chatActivity = null; + type = PhotoViewer.SELECT_TYPE_AVATAR; + } else if (parentAlert.baseFragment instanceof ChatActivity) { + chatActivity = (ChatActivity) parentAlert.baseFragment; + type = 0; + } else { + chatActivity = null; + type = 4; + } + if (!parentAlert.delegate.needEnterComment()) { + AndroidUtilities.hideKeyboard(parentAlert.baseFragment.getFragmentView().findFocus()); + AndroidUtilities.hideKeyboard(parentAlert.getContainer().findFocus()); + } + PhotoViewer.getInstance().setParentActivity(parentAlert.baseFragment, resourcesProvider); + PhotoViewer.getInstance().setParentAlert(parentAlert); + PhotoViewer.getInstance().setMaxSelectedPhotos(parentAlert.maxSelectedPhotos, parentAlert.allowOrder); + photoViewerProvider.init(arrayList); + ArrayList objectArrayList = new ArrayList<>(arrayList); + PhotoViewer.getInstance().openPhotoForSelect(objectArrayList, position, type, false, photoViewerProvider, chatActivity); + if (photoLayout.captionForAllMedia()) { + PhotoViewer.getInstance().setCaption(parentAlert.getCommentTextView().getText()); + } } + tapMediaCell = null; + tapTime = 0; + draggingCell = null; + draggingT = 0; + result = true; } - tapMediaCell = null; - tapTime = 0; - draggingCell = null; - draggingT = 0; - result = true; } if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { @@ -1875,6 +1911,8 @@ public class ChatAttachAlertPhotoLayoutPreview extends ChatAttachAlert.AttachAle public MediaController.PhotoEntry photoEntry; public ImageReceiver image; + public ImageReceiver blurredImage; + public boolean wasSpoiler; private RectF fromRect = null; public RectF rect = new RectF(); private long lastUpdate = 0; @@ -1883,11 +1921,43 @@ public class ChatAttachAlertPhotoLayoutPreview extends ChatAttachAlert.AttachAle public float fromScale = 1f; public float scale = 0f; + private float spoilerRevealProgress; + private float spoilerRevealX; + private float spoilerRevealY; + private float spoilerMaxRadius; + public RectF fromRoundRadiuses = null; public RectF roundRadiuses = new RectF(); private String videoDurationText = null; + private SpoilerEffect spoilerEffect = new SpoilerEffect(); + private Path path = new Path(); + private float[] radii = new float[8]; + + private Bitmap spoilerCrossfadeBitmap; + private float spoilerCrossfadeProgress = 1f; + private Paint spoilerCrossfadePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + public void startCrossfade() { + RectF drawingRect = this.drawingRect(); + int w = Math.max(1, Math.round(drawingRect.width())); + int h = Math.max(1, Math.round(drawingRect.height())); + Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + canvas.save(); + canvas.translate(-drawingRect.left, -drawingRect.top); + draw(canvas); + canvas.restore(); + + if (spoilerCrossfadeBitmap != null && !spoilerCrossfadeBitmap.isRecycled()) { + spoilerCrossfadeBitmap.recycle(); + } + spoilerCrossfadeBitmap = bitmap; + spoilerCrossfadeProgress = 0f; + invalidate(); + } + private void setImage(MediaController.PhotoEntry photoEntry) { this.photoEntry = photoEntry; if (photoEntry != null && photoEntry.isVideo) { @@ -1897,6 +1967,19 @@ public class ChatAttachAlertPhotoLayoutPreview extends ChatAttachAlert.AttachAle } if (image == null) { image = new ImageReceiver(PreviewGroupsView.this); + blurredImage = new ImageReceiver(PreviewGroupsView.this); + + image.setDelegate((imageReceiver, set, thumb, memCache) -> { + if (set && !thumb && photoEntry != null && photoEntry.hasSpoiler && blurredImage.getBitmap() == null) { + if (blurredImage.getBitmap() != null && !blurredImage.getBitmap().isRecycled()) { + blurredImage.getBitmap().recycle(); + blurredImage.setImageBitmap((Bitmap) null); + } + + Bitmap bitmap = imageReceiver.getBitmap(); + blurredImage.setImageBitmap(Utilities.stackBlurBitmapMax(bitmap)); + } + }); } if (photoEntry != null) { if (photoEntry.thumbPath != null) { @@ -2178,6 +2261,28 @@ public class ChatAttachAlertPhotoLayoutPreview extends ChatAttachAlert.AttachAle } } + private void startRevealMedia(float x, float y) { + spoilerRevealX = x; + spoilerRevealY = y; + + RectF drawingRect = drawingRect(); + spoilerMaxRadius = (float) Math.sqrt(Math.pow(drawingRect.width(), 2) + Math.pow(drawingRect.height(), 2)); + ValueAnimator animator = ValueAnimator.ofFloat(0, 1).setDuration((long) MathUtils.clamp(spoilerMaxRadius * 0.3f, 250, 550)); + animator.setInterpolator(CubicBezierInterpolator.EASE_BOTH); + animator.addUpdateListener(animation -> { + spoilerRevealProgress = (float) animation.getAnimatedValue(); + invalidate(); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + photoEntry.isChatPreviewSpoilerRevealed = true; + invalidate(); + } + }); + animator.start(); + } + private float visibleT = 1; private long lastVisibleTUpdate = 0; @@ -2218,6 +2323,72 @@ public class ChatAttachAlertPhotoLayoutPreview extends ChatAttachAlert.AttachAle image.setAlpha(scale); image.draw(canvas); + if (photoEntry != null && photoEntry.hasSpoiler && !photoEntry.isChatPreviewSpoilerRevealed) { + if (!wasSpoiler && blurredImage.getBitmap() == null && image.getBitmap() != null) { + wasSpoiler = true; + blurredImage.setImageBitmap(Utilities.stackBlurBitmapMax(image.getBitmap())); + } else if (!wasSpoiler && blurredImage.getBitmap() != null) { + wasSpoiler = true; + } + + radii[0] = radii[1] = tl; + radii[2] = radii[3] = tr; + radii[4] = radii[5] = br; + radii[6] = radii[7] = bl; + + canvas.save(); + path.rewind(); + path.addRoundRect(drawingRect, radii, Path.Direction.CW); + canvas.clipPath(path); + + if (spoilerRevealProgress != 0f) { + path.rewind(); + path.addCircle(spoilerRevealX, spoilerRevealY, spoilerMaxRadius * spoilerRevealProgress, Path.Direction.CW); + canvas.clipPath(path, Region.Op.DIFFERENCE); + } + + blurredImage.setRoundRadius((int) tl, (int) tr, (int) br, (int) bl); + blurredImage.setImageCoords(drawingRect.left, drawingRect.top, drawingRect.width(), drawingRect.height()); + blurredImage.setAlpha(scale); + blurredImage.draw(canvas); + + int sColor = Color.WHITE; + spoilerEffect.setColor(ColorUtils.setAlphaComponent(sColor, (int) (Color.alpha(sColor) * 0.325f * scale))); + spoilerEffect.setBounds(0, 0, getWidth(), getHeight()); + spoilerEffect.draw(canvas); + canvas.restore(); + + invalidate(); + PreviewGroupsView.this.invalidate(); + } + + if (spoilerCrossfadeProgress != 1f && spoilerCrossfadeBitmap != null) { + radii[0] = radii[1] = tl; + radii[2] = radii[3] = tr; + radii[4] = radii[5] = br; + radii[6] = radii[7] = bl; + + canvas.save(); + path.rewind(); + path.addRoundRect(drawingRect, radii, Path.Direction.CW); + canvas.clipPath(path); + + long dt = Math.min(16, SystemClock.elapsedRealtime() - lastUpdate); + spoilerCrossfadeProgress = Math.min(1f, spoilerCrossfadeProgress + dt / 250f); + + spoilerCrossfadePaint.setAlpha((int) ((1f - spoilerCrossfadeProgress) * 0xFF)); + canvas.drawBitmap(spoilerCrossfadeBitmap, drawingRect.left, drawingRect.top, spoilerCrossfadePaint); + + canvas.restore(); + + invalidate(); + } else if (spoilerCrossfadeProgress == 1f && spoilerCrossfadeBitmap != null) { + spoilerCrossfadeBitmap.recycle(); + spoilerCrossfadeBitmap = null; + + invalidate(); + } + int index = indexStart + group.photos.indexOf(photoEntry); String indexText = index >= 0 ? (index + 1) + "" : null; float shouldVisibleT = image.getVisible() ? 1 : 0; @@ -2325,11 +2496,6 @@ public class ChatAttachAlertPhotoLayoutPreview extends ChatAttachAlert.AttachAle return getT() >= 0.95f ? this.groupHeight * maxHeight * getPreviewScale() : measure(); } - public boolean needToUpdate = false; - public void invalidate() { - needToUpdate = true; - } - private Theme.MessageDrawable messageBackground = (Theme.MessageDrawable) getThemedDrawable(Theme.key_drawable_msgOutMedia); private Theme.MessageDrawable.PathDrawParams backgroundCacheParams = new Theme.MessageDrawable.PathDrawParams(); public boolean draw(Canvas canvas) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/CheckBoxBase.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/CheckBoxBase.java index 4e1642f73..296f5e061 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/CheckBoxBase.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/CheckBoxBase.java @@ -3,7 +3,6 @@ package org.telegram.ui.Components; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; @@ -26,7 +25,7 @@ import org.telegram.ui.ActionBar.Theme; public class CheckBoxBase { private View parentView; - private Rect bounds = new Rect(); + public Rect bounds = new Rect(); private RectF rect = new RectF(); private static Paint paint; @@ -37,9 +36,6 @@ public class CheckBoxBase { private Path path = new Path(); - private Bitmap drawBitmap; - private Canvas bitmapCanvas; - private boolean enabled = true; private boolean attachedToWindow; @@ -95,9 +91,6 @@ public class CheckBoxBase { backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); backgroundPaint.setStyle(Paint.Style.STROKE); backgroundPaint.setStrokeWidth(AndroidUtilities.dp(1.2f)); - - drawBitmap = Bitmap.createBitmap(AndroidUtilities.dp(size), AndroidUtilities.dp(size), Bitmap.Config.ARGB_4444); - bitmapCanvas = new Canvas(drawBitmap); } public void onAttachedToWindow() { @@ -249,11 +242,6 @@ public class CheckBoxBase { } public void draw(Canvas canvas) { - if (drawBitmap == null) { - return; - } - - drawBitmap.eraseColor(0); float rad = AndroidUtilities.dp(size / 2); float outerRad = rad; if (backgroundType == 12 || backgroundType == 13) { @@ -278,7 +266,7 @@ public class CheckBoxBase { } else if (backgroundType == 6 || backgroundType == 7) { paint.setColor(getThemedColor(background2ColorKey)); backgroundPaint.setColor(getThemedColor(checkColorKey)); - } else if (backgroundType == 10) { + } else if (backgroundType == 10 || backgroundType == 14) { backgroundPaint.setColor(getThemedColor(background2ColorKey)); } else { paint.setColor((Theme.getServiceMessageColor() & 0x00ffffff) | 0x28000000); @@ -303,7 +291,7 @@ public class CheckBoxBase { if (drawUnchecked && backgroundType >= 0) { if (backgroundType == 12 || backgroundType == 13) { //draw nothing - } else if (backgroundType == 8 || backgroundType == 10) { + } else if (backgroundType == 8 || backgroundType == 10 || backgroundType == 14) { canvas.drawCircle(cx, cy, rad - AndroidUtilities.dp(1.5f), backgroundPaint); } else if (backgroundType == 6 || backgroundType == 7) { canvas.drawCircle(cx, cy, rad - AndroidUtilities.dp(1), paint); @@ -313,7 +301,7 @@ public class CheckBoxBase { } } paint.setColor(getThemedColor(checkColorKey)); - if (backgroundType != -1 && backgroundType != 7 && backgroundType != 8 && backgroundType != 9 && backgroundType != 10) { + if (backgroundType != -1 && backgroundType != 7 && backgroundType != 8 && backgroundType != 9 && backgroundType != 10 && backgroundType != 14) { if (backgroundType == 12 || backgroundType == 13) { backgroundPaint.setStyle(Paint.Style.FILL); if (messageDrawable != null && messageDrawable.hasGradient()) { @@ -378,20 +366,24 @@ public class CheckBoxBase { } if (backgroundType != -1) { + float sizeHalf = AndroidUtilities.dp(size) / 2f; + int restoreCount = canvas.save(); + canvas.translate(cx - sizeHalf, cy - sizeHalf); + canvas.saveLayerAlpha(0, 0, AndroidUtilities.dp(size), AndroidUtilities.dp(size), 255, Canvas.ALL_SAVE_FLAG); Paint circlePaint = circlePaintProvider.provide(null); if (backgroundType == 12 || backgroundType == 13) { int a = circlePaint.getAlpha(); circlePaint.setAlpha((int) (255 * roundProgress)); - bitmapCanvas.drawCircle(drawBitmap.getWidth() / 2, drawBitmap.getHeight() / 2, rad * roundProgress, circlePaint); + canvas.drawCircle(sizeHalf, sizeHalf, rad * roundProgress, circlePaint); if (circlePaint != paint) { circlePaint.setAlpha(a); } } else { rad -= AndroidUtilities.dp(0.5f); - bitmapCanvas.drawCircle(drawBitmap.getWidth() / 2, drawBitmap.getHeight() / 2, rad, circlePaint); - bitmapCanvas.drawCircle(drawBitmap.getWidth() / 2, drawBitmap.getHeight() / 2, rad * (1.0f - roundProgress), eraser); + canvas.drawCircle(sizeHalf, sizeHalf, rad, circlePaint); + canvas.drawCircle(sizeHalf, sizeHalf, rad * (1.0f - roundProgress), eraser); } - canvas.drawBitmap(drawBitmap, cx - drawBitmap.getWidth() / 2, cy - drawBitmap.getHeight() / 2, null); + canvas.restoreToCount(restoreCount); } if (checkProgress != 0) { if (checkedText != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/CircularProgressDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/CircularProgressDrawable.java index 4ec8de90c..dcf35cb2e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/CircularProgressDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/CircularProgressDrawable.java @@ -4,7 +4,6 @@ import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; -import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.SystemClock; @@ -33,19 +32,20 @@ public class CircularProgressDrawable extends Drawable { } private long start = -1; - private final FastOutSlowInInterpolator interpolator = new FastOutSlowInInterpolator(); - private float segmentFrom, segmentTo; + private static final FastOutSlowInInterpolator interpolator = new FastOutSlowInInterpolator(); + private float[] segment = new float[2]; private void updateSegment() { final long now = SystemClock.elapsedRealtime(); final long t = (now - start) % 5400; - segmentFrom = 1520 * t / 5400f - 20; - segmentTo = 1520 * t / 5400f; - float fraction; + getSegments(t, segment); + } + + public static void getSegments(float t, float[] segments) { + segments[0] = 1520 * t / 5400f - 20; + segments[1] = 1520 * t / 5400f; for (int i = 0; i < 4; ++i) { - fraction = (t - i * 1350) / 667f; - segmentTo += interpolator.getInterpolation(fraction) * 250; - fraction = (t - (667 + i * 1350)) / 667f; - segmentFrom += interpolator.getInterpolation(fraction) * 250; + segments[1] += interpolator.getInterpolation((t - i * 1350) / 667f) * 250; + segments[0] += interpolator.getInterpolation((t - (667 + i * 1350)) / 667f) * 250; } } @@ -53,7 +53,9 @@ public class CircularProgressDrawable extends Drawable { paint.setStyle(Paint.Style.STROKE); } + private float angleOffset; private final RectF bounds = new RectF(); + @Override public void draw(@NonNull Canvas canvas) { if (start < 0) { @@ -62,14 +64,22 @@ public class CircularProgressDrawable extends Drawable { updateSegment(); canvas.drawArc( bounds, - segmentFrom, - segmentTo - segmentFrom, + angleOffset + segment[0], + segment[1] - segment[0], false, paint ); invalidateSelf(); } + public void reset() { + start = -1; + } + + public void setAngleOffset(float angleOffset) { + this.angleOffset = angleOffset; + } + @Override public void setBounds(int left, int top, int right, int bottom) { int width = right - left, height = bottom - top; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropAreaView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropAreaView.java index 7b64c6e30..9af5041e6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropAreaView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropAreaView.java @@ -10,29 +10,33 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; -import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; import android.os.Build; -import androidx.annotation.Keep; - import android.os.SystemClock; +import android.text.Layout; +import android.text.StaticLayout; import android.text.TextPaint; -import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.Window; -import android.view.WindowManager; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; +import androidx.annotation.Keep; +import androidx.core.graphics.ColorUtils; + import org.telegram.messenger.AndroidUtilities; import org.telegram.ui.BubbleActivity; public class CropAreaView extends ViewGroup { + public int size; + public float left, top; + + private enum Control { NONE, TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, TOP, LEFT, BOTTOM, RIGHT } @@ -97,6 +101,8 @@ public class CropAreaView extends ViewGroup { private boolean freeform = true; private Bitmap circleBitmap; private Paint eraserPaint; + private String subtitle; + private StaticLayout subtitleLayout; private Animator animator; @@ -147,6 +153,28 @@ public class CropAreaView extends ViewGroup { setWillNotDraw(false); } + TextPaint subtitlePaint; + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + updateSubtitle(); + } + + private void updateSubtitle() { + if (subtitle != null) { + if (subtitlePaint == null) { + subtitlePaint = new TextPaint(); + subtitlePaint.setColor(ColorUtils.setAlphaComponent(Color.WHITE, 120)); + subtitlePaint.setTextSize(AndroidUtilities.dp(13)); + subtitlePaint.setTextAlign(Paint.Align.CENTER); + } + subtitleLayout = new StaticLayout(subtitle, subtitlePaint, getMeasuredWidth() - AndroidUtilities.dp(120), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + } else { + subtitleLayout = null; + } + } + @Override protected void onLayout(boolean b, int i, int i1, int i2, int i3) {} @@ -330,7 +358,7 @@ public class CropAreaView extends ViewGroup { } else { float width = getMeasuredWidth() - 2 * sidePadding; float height = getMeasuredHeight() - bottomPadding - (Build.VERSION.SDK_INT >= 21 && !inBubbleMode ? AndroidUtilities.statusBarHeight : 0) - 2 * sidePadding; - int size = (int) Math.min(width, height); + size = (int) Math.min(width, height); if (circleBitmap == null || circleBitmap.getWidth() != size) { boolean hasBitmap = circleBitmap != null; @@ -355,8 +383,8 @@ public class CropAreaView extends ViewGroup { if (circleBitmap != null) { bitmapPaint.setAlpha((int) (255 * frameAlpha)); dimPaint.setAlpha((int) (0x7f * frameAlpha)); - float left = sidePadding + (width - size) / 2.0f; - float top = sidePadding + (height - size) / 2.0f + (Build.VERSION.SDK_INT >= 21 && !inBubbleMode ? AndroidUtilities.statusBarHeight : 0); + left = sidePadding + (width - size) / 2.0f; + top = sidePadding + (height - size) / 2.0f + (Build.VERSION.SDK_INT >= 21 && !inBubbleMode ? AndroidUtilities.statusBarHeight : 0); float right = left + size; float bottom = top + size; canvas.drawRect(0, 0, getWidth(), (int) top, dimPaint); @@ -364,6 +392,13 @@ public class CropAreaView extends ViewGroup { canvas.drawRect((int) right, (int) top, getWidth(), (int) bottom, dimPaint); canvas.drawRect(0, (int) bottom, getWidth(), getHeight(), dimPaint); canvas.drawBitmap(circleBitmap, (int) left, (int) top, bitmapPaint); + + if (getMeasuredHeight() > getMeasuredWidth() && subtitleLayout != null) { + canvas.save(); + canvas.translate(getMeasuredWidth() / 2f, bottom + AndroidUtilities.dp(16)); + subtitleLayout.draw(canvas); + canvas.restore(); + } } } @@ -873,4 +908,11 @@ public class CropAreaView extends ViewGroup { public void getCropRect(RectF rect) { rect.set(actualRect); } + + public void setSubtitle(String subtitle) { + this.subtitle = subtitle; + if (getMeasuredWidth() > 0) { + updateSubtitle(); + } + } } \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropView.java index 483581c2b..c95616536 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropView.java @@ -75,6 +75,10 @@ public class CropView extends FrameLayout implements CropAreaView.AreaViewListen private int bitmapRotation; + public void setSubtitle(String subtitle) { + areaView.setSubtitle(subtitle); + } + public class CropState { public float width; public float height; @@ -256,11 +260,11 @@ public class CropView extends FrameLayout implements CropAreaView.AreaViewListen } public float getStateFullOrientation() { - return state.baseRotation + state.orientation; + return state == null ? 0 : state.baseRotation + state.orientation; } public boolean getStateMirror() { - return state.mirrored; + return state != null && state.mirrored; } public CropView(Context context) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/DrawingInBackgroundThreadDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/DrawingInBackgroundThreadDrawable.java index 65044138d..8331822ef 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/DrawingInBackgroundThreadDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/DrawingInBackgroundThreadDrawable.java @@ -80,10 +80,7 @@ public class DrawingInBackgroundThreadDrawable implements NotificationCenter.Not bitmapUpdating = false; onFrameReady(); if (!attachedToWindow) { - if (backgroundBitmap != null) { - backgroundBitmap.recycle(); - backgroundBitmap = null; - } + recycleBitmaps(); return; } if (frameGuid != lastFrameId) { @@ -187,6 +184,7 @@ public class DrawingInBackgroundThreadDrawable implements NotificationCenter.Not public void onAttachToWindow() { attachedToWindow = true; + error = false; currentOpenedLayerFlags = NotificationCenter.getGlobalInstance().getCurrentHeavyOperationFlags(); currentOpenedLayerFlags &= ~currentLayerNum; if (currentOpenedLayerFlags == 0) { @@ -195,20 +193,33 @@ public class DrawingInBackgroundThreadDrawable implements NotificationCenter.Not onResume(); } } + NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.stopAllHeavyOperations); NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.startAllHeavyOperations); } public void onDetachFromWindow() { + if (!bitmapUpdating) { + recycleBitmaps(); + } + attachedToWindow = false; + NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.stopAllHeavyOperations); + NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.startAllHeavyOperations); + } + + private void recycleBitmaps() { ArrayList bitmaps = new ArrayList<>(); if (bitmap != null) { bitmaps.add(bitmap); } + if (backgroundBitmap != null) { + bitmaps.add(backgroundBitmap); + } bitmap = null; + backgroundBitmap = null; + backgroundCanvas = null; + bitmapCanvas = null; AndroidUtilities.recycleBitmaps(bitmaps); - attachedToWindow = false; - NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.stopAllHeavyOperations); - NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.startAllHeavyOperations); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextEffects.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextEffects.java index 6ed158267..b42ac7750 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextEffects.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextEffects.java @@ -5,6 +5,7 @@ import android.graphics.Canvas; import android.graphics.Path; import android.graphics.Rect; import android.graphics.Region; +import android.os.Looper; import android.text.Editable; import android.text.Layout; import android.text.Spannable; @@ -12,6 +13,7 @@ import android.view.MotionEvent; import android.widget.EditText; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.FileLog; import org.telegram.ui.Components.spoilers.SpoilerEffect; import org.telegram.ui.Components.spoilers.SpoilersClickDetector; @@ -33,9 +35,18 @@ public class EditTextEffects extends EditText { private float lastRippleX, lastRippleY; private boolean postedSpoilerTimeout; private AnimatedEmojiSpan.EmojiGroupedSpans animatedEmojiDrawables; + protected boolean animatedEmojiRawDraw; + protected int animatedEmojiRawDrawFps; + protected int animatedEmojiOffsetX; private Layout lastLayout = null; private int lastTextLength; + public void incrementFrames(int frames) { + if (animatedEmojiDrawables != null) { + animatedEmojiDrawables.incrementFrames(frames); + } + } + private Runnable spoilerTimeout = () -> { postedSpoilerTimeout = false; isSpoilersRevealed = false; @@ -55,7 +66,9 @@ public class EditTextEffects extends EditText { public EditTextEffects(Context context) { super(context); - clickDetector = new SpoilersClickDetector(this, spoilers, this::onSpoilerClicked); + if (Looper.getMainLooper().getThread() == Thread.currentThread()) { + clickDetector = new SpoilersClickDetector(this, spoilers, this::onSpoilerClicked); + } } private void onSpoilerClicked(SpoilerEffect eff, float x, float y) { @@ -127,6 +140,10 @@ public class EditTextEffects extends EditText { AnimatedEmojiSpan.release(this, animatedEmojiDrawables); } + public void recycleEmojis() { + AnimatedEmojiSpan.release(this, animatedEmojiDrawables); + } + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); @@ -145,21 +162,25 @@ public class EditTextEffects extends EditText { 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); + try { + 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; + for (SpoilerEffect eff : spoilers) { + if (eff.getBounds().contains(x, y)) { + int selOffset = lengthAfter - lengthBefore; + selStart += selOffset; + selEnd += selOffset; + onSpoilerClicked(eff, x, y); + break; + } } } + } catch (Exception e) { + FileLog.e(e); } } updateAnimatedEmoji(true); @@ -192,7 +213,7 @@ public class EditTextEffects extends EditText { @Override public boolean dispatchTouchEvent(MotionEvent event) { boolean detector = false; - if (shouldRevealSpoilersByTouch && clickDetector.onTouchEvent(event)) { + if (shouldRevealSpoilersByTouch && clickDetector != null && clickDetector.onTouchEvent(event)) { int act = event.getActionMasked(); if (act == MotionEvent.ACTION_UP) { MotionEvent c = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0); @@ -243,7 +264,14 @@ public class EditTextEffects extends EditText { updateAnimatedEmoji(false); super.onDraw(canvas); if (animatedEmojiDrawables != null) { - AnimatedEmojiSpan.drawAnimatedEmojis(canvas, getLayout(), animatedEmojiDrawables, 0, spoilers, computeVerticalScrollOffset() - AndroidUtilities.dp(6), computeVerticalScrollOffset() + computeVerticalScrollExtent(), 0, 1f); + canvas.save(); + canvas.translate(animatedEmojiOffsetX, 0); + if (animatedEmojiRawDraw) { + AnimatedEmojiSpan.drawRawAnimatedEmojis(canvas, getLayout(), animatedEmojiDrawables, 0, spoilers, computeVerticalScrollOffset() - AndroidUtilities.dp(6), computeVerticalScrollOffset() + computeVerticalScrollExtent(), 0, 1f, animatedEmojiRawDrawFps); + } else { + AnimatedEmojiSpan.drawAnimatedEmojis(canvas, getLayout(), animatedEmojiDrawables, 0, spoilers, computeVerticalScrollOffset() - AndroidUtilities.dp(6), computeVerticalScrollOffset() + computeVerticalScrollExtent(), 0, 1f); + } + canvas.restore(); } canvas.restore(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiPacksAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiPacksAlert.java index a1484244e..1b8add9e5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiPacksAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiPacksAlert.java @@ -107,13 +107,20 @@ public class EmojiPacksAlert extends BottomSheet implements NotificationCenter.N return false; } + public EmojiPacksAlert(BaseFragment fragment, Context context, Theme.ResourcesProvider resourceProvider, TLObject parentObject) { + this(fragment, context, resourceProvider, null, parentObject); + } + public EmojiPacksAlert(BaseFragment fragment, Context context, Theme.ResourcesProvider resourceProvider, ArrayList stickerSets) { + this(fragment, context, resourceProvider, stickerSets, null); + } + + private EmojiPacksAlert(BaseFragment fragment, Context context, Theme.ResourcesProvider resourceProvider, ArrayList stickerSets, TLObject parentObject) { super(context, false, resourceProvider); - boolean single = stickerSets.size() <= 1; this.fragment = fragment; fixNavigationBar(); - customEmojiPacks = new EmojiPacksLoader(currentAccount, stickerSets) { + customEmojiPacks = new EmojiPacksLoader(currentAccount, stickerSets, parentObject) { @Override protected void onUpdate() { updateButton(); @@ -384,7 +391,7 @@ public class EmojiPacksAlert extends BottomSheet implements NotificationCenter.N (int) (cx + hw * imageView.getScaleX() * scale), (int) (cy + hh * imageView.getScaleY() * scale) ); - drawable.draw(canvas, false); + drawable.draw(canvas); } } } @@ -624,7 +631,8 @@ public class EmojiPacksAlert extends BottomSheet implements NotificationCenter.N buttonsView.addView(premiumButtonView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM, 12, 10, 12, 10)); updateButton(); - MediaDataController.getInstance(fragment.getCurrentAccount()).checkStickers(MediaDataController.TYPE_EMOJIPACKS); + int currentAccount = fragment == null ? UserConfig.selectedAccount : fragment.getCurrentAccount(); + MediaDataController.getInstance(currentAccount).checkStickers(MediaDataController.TYPE_EMOJIPACKS); } protected void onButtonClicked(boolean install) { @@ -772,6 +780,13 @@ public class EmojiPacksAlert extends BottomSheet implements NotificationCenter.N MediaDataController.getInstance(fragment.getCurrentAccount()).toggleStickerSet(fragment.getFragmentView().getContext(), set, 0, fragment, true, showBulletin, onUndo); } + public static void uninstallSet(Context context, TLRPC.TL_messages_stickerSet set, boolean showBulletin, Runnable onUndo) { + if (set == null) { + return; + } + MediaDataController.getInstance(UserConfig.selectedAccount).toggleStickerSet(context, set, 0, null, true, showBulletin, onUndo); + } + private ValueAnimator loadAnimator; private void loadAnimation() { if (loadAnimator != null) { @@ -894,7 +909,7 @@ public class EmojiPacksAlert extends BottomSheet implements NotificationCenter.N } else { for (int i = 0; i < installedPacks.size(); ++i) { TLRPC.TL_messages_stickerSet stickerSet = installedPacks.get(i); - uninstallSet(fragment, stickerSet, i == 0, null); + uninstallSet(getContext(), stickerSet, i == 0, null); } } onButtonClicked(false); @@ -1482,20 +1497,75 @@ public class EmojiPacksAlert extends BottomSheet implements NotificationCenter.N final int loadingStickersCount = 12; public ArrayList inputStickerSets; + public TLObject parentObject; public ArrayList stickerSets; public ArrayList[] data; private int currentAccount; - public EmojiPacksLoader(int currentAccount, ArrayList inputStickerSets) { + public EmojiPacksLoader(int currentAccount, ArrayList inputStickerSets, TLObject parentObject) { this.currentAccount = currentAccount; - if (inputStickerSets == null) { + if (inputStickerSets == null && parentObject == null) { inputStickerSets = new ArrayList<>(); } this.inputStickerSets = inputStickerSets; + this.parentObject = parentObject; init(); } private void init() { + if ((parentObject instanceof TLRPC.Photo || parentObject instanceof TLRPC.Document) && (this.inputStickerSets == null || this.inputStickerSets.isEmpty())) { + data = new ArrayList[2]; + putStickerSet(0, null); + putStickerSet(1, null); + final TLRPC.TL_messages_getAttachedStickers req = new TLRPC.TL_messages_getAttachedStickers(); + if (parentObject instanceof TLRPC.Photo) { + TLRPC.Photo photo = (TLRPC.Photo) parentObject; + TLRPC.TL_inputStickeredMediaPhoto inputStickeredMediaPhoto = new TLRPC.TL_inputStickeredMediaPhoto(); + inputStickeredMediaPhoto.id = new TLRPC.TL_inputPhoto(); + inputStickeredMediaPhoto.id.id = photo.id; + inputStickeredMediaPhoto.id.access_hash = photo.access_hash; + inputStickeredMediaPhoto.id.file_reference = photo.file_reference; + if (inputStickeredMediaPhoto.id.file_reference == null) { + inputStickeredMediaPhoto.id.file_reference = new byte[0]; + } + req.media = inputStickeredMediaPhoto; + } else if (parentObject instanceof TLRPC.Document) { + TLRPC.Document document = (TLRPC.Document) parentObject; + TLRPC.TL_inputStickeredMediaDocument inputStickeredMediaDocument = new TLRPC.TL_inputStickeredMediaDocument(); + inputStickeredMediaDocument.id = new TLRPC.TL_inputDocument(); + inputStickeredMediaDocument.id.id = document.id; + inputStickeredMediaDocument.id.access_hash = document.access_hash; + inputStickeredMediaDocument.id.file_reference = document.file_reference; + if (inputStickeredMediaDocument.id.file_reference == null) { + inputStickeredMediaDocument.id.file_reference = new byte[0]; + } + req.media = inputStickeredMediaDocument; + } + ConnectionsManager.getInstance(currentAccount).sendRequest(req, (res, err) -> { + AndroidUtilities.runOnUIThread(() -> { + if (err != null || !(res instanceof TLRPC.Vector)) { + EmojiPacksAlert.this.dismiss(); + if (fragment != null && fragment.getParentActivity() != null) { + BulletinFactory.of(fragment).createErrorBulletin(LocaleController.getString("UnknownError", R.string.UnknownError)).show(); + } + } else { + TLRPC.Vector vector = (TLRPC.Vector) res; + if (inputStickerSets == null) { + inputStickerSets = new ArrayList<>(); + } + for (int i = 0; i < vector.objects.size(); ++i) { + Object object = vector.objects.get(i); + if (object instanceof TLRPC.StickerSetCovered && ((TLRPC.StickerSetCovered) object).set != null) { + inputStickerSets.add(MediaDataController.getInputStickerSet(((TLRPC.StickerSetCovered) object).set)); + } + } + parentObject = null; + init(); + } + }); + }); + return; + } stickerSets = new ArrayList<>(inputStickerSets.size()); data = new ArrayList[inputStickerSets.size()]; NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.groupStickersDidLoad); @@ -1521,6 +1591,7 @@ public class EmojiPacksAlert extends BottomSheet implements NotificationCenter.N stickerSets.add(stickerSet); putStickerSet(i, stickerSet); } + onUpdate(); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiTabsStrip.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiTabsStrip.java index 8d69c5e12..daaabd11b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiTabsStrip.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiTabsStrip.java @@ -712,11 +712,7 @@ public class EmojiTabsStrip extends ScrollableHorizontalScrollView { if (drawable != null) { drawable.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); drawable.setAlpha(255); - if (drawable instanceof AnimatedEmojiDrawable) { - ((AnimatedEmojiDrawable) drawable).draw(canvas, false); - } else { - drawable.draw(canvas); - } + drawable.draw(canvas); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java index 69bc5436b..d03435e5d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java @@ -21,6 +21,7 @@ import android.content.DialogInterface; import android.content.SharedPreferences; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.ColorFilter; import android.graphics.DashPathEffect; import android.graphics.Outline; import android.graphics.Paint; @@ -336,6 +337,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific private Long emojiScrollToStickerId; private LongSparseArray animatedEmojiDrawables; + private PorterDuffColorFilter animatedEmojiTextColorFilter; private Runnable checkExpandStickerTabsRunnable = new Runnable() { @Override @@ -1387,6 +1389,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific if (needAnimatedEmoji) { MediaDataController.getInstance(currentAccount).checkStickers(MediaDataController.TYPE_EMOJIPACKS); MediaDataController.getInstance(currentAccount).checkFeaturedEmoji(); + animatedEmojiTextColorFilter = new PorterDuffColorFilter(getThemedColor(Theme.key_windowBackgroundWhiteBlackText), PorterDuff.Mode.SRC_IN); } emojiGridView = new EmojiGridView(context); DefaultItemAnimator emojiItemAnimator = new DefaultItemAnimator(); @@ -1656,6 +1659,11 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } return true; } + + @Override + protected ColorFilter getEmojiColorFilter() { + return animatedEmojiTextColorFilter; + } }; if (needSearch) { emojiSearchField = new SearchField(context, 1); @@ -2793,7 +2801,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific if (imageViewEmojis == null) { return; } - boolean drawInUi = imageViewEmojis.size() <= 4 || SharedConfig.getDevicePerformanceClass() == SharedConfig.PERFORMANCE_CLASS_LOW; + boolean drawInUi = imageViewEmojis.size() <= 4 || SharedConfig.getDevicePerformanceClass() == SharedConfig.PERFORMANCE_CLASS_LOW || SharedConfig.getLiteMode().enabled(); if (!drawInUi) { boolean animatedExpandIn = animateExpandStartTime > 0 && (SystemClock.elapsedRealtime() - animateExpandStartTime) < animateExpandDuration(); for (int i = 0; i < imageViewEmojis.size(); i++) { @@ -2836,7 +2844,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific AndroidUtilities.rectTmp2.set(imageView.getLeft() + imageView.getPaddingLeft() - startOffset, topOffset, imageView.getRight() - imageView.getPaddingRight() - startOffset, topOffset + imageView.getMeasuredHeight() - imageView.getPaddingTop() - imageView.getPaddingBottom()); imageView.backgroundThreadDrawHolder[threadIndex].setBounds(AndroidUtilities.rectTmp2); imageView.drawable = drawable; - imageView.drawable.setColorFilter(Theme.chat_animatedEmojiTextColorFilter); + imageView.drawable.setColorFilter(animatedEmojiTextColorFilter); imageView.imageReceiver = drawable.getImageReceiver(); drawInBackgroundViews.add(imageView); } @@ -2898,10 +2906,10 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific if (scale != 1) { canvas.save(); canvas.scale(scale, scale, AndroidUtilities.rectTmp2.centerX(), AndroidUtilities.rectTmp2.centerY()); - drawable.draw(canvas, false); + drawable.draw(canvas); canvas.restore(); } else { - drawable.draw(canvas, false); + drawable.draw(canvas); } } canvas.restore(); @@ -5160,6 +5168,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific gifSearchAdapter.progressEmptyView.textView.setTextColor(getThemedColor(Theme.key_chat_emojiPanelEmptyText)); gifSearchAdapter.progressEmptyView.progressView.setProgressColor(getThemedColor(Theme.key_progressCircle)); } + animatedEmojiTextColorFilter = new PorterDuffColorFilter(getThemedColor(Theme.key_windowBackgroundWhiteBlackText), PorterDuff.Mode.SRC_IN); for (int a = 0; a < tabIcons.length; a++) { Theme.setEmojiDrawableColor(tabIcons[a], getThemedColor(Theme.key_chat_emojiBottomPanelIcon), false); @@ -5654,6 +5663,9 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } } } + if (emojiAdapter != null) { + emojiAdapter.notifyDataSetChanged(true); + } } else if (id == NotificationCenter.emojiLoaded) { if (stickersGridView != null) { int count = stickersGridView.getChildCount(); @@ -5664,6 +5676,16 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } } } + if (emojiGridView != null) { + emojiGridView.invalidate(); + int count = emojiGridView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = emojiGridView.getChildAt(a); + if (child instanceof ImageViewEmoji) { + child.invalidate(); + } + } + } if (pickerView != null) { pickerView.invalidate(); } @@ -6247,7 +6269,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific public static class EmojiPack { public int index; public TLRPC.StickerSet set; - public ArrayList documents; + public ArrayList documents = new ArrayList<>(); public boolean free; public boolean installed; public boolean featured; @@ -6538,14 +6560,14 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific return VIEW_TYPE_EMOJI; } - public void processEmoji(boolean updatEmojipacks) { + public void processEmoji(boolean updateEmojipacks) { emojipacksProcessed.clear(); if (!allowAnimatedEmoji) { return; } MediaDataController mediaDataController = MediaDataController.getInstance(currentAccount); - if (updatEmojipacks || frozenEmojiPacks == null) { + if (updateEmojipacks || frozenEmojiPacks == null) { frozenEmojiPacks = new ArrayList<>(mediaDataController.getStickerSets(MediaDataController.TYPE_EMOJIPACKS)); } ArrayList installedEmojipacks = frozenEmojiPacks; @@ -6625,7 +6647,16 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific pack.installed = mediaDataController.isStickerPackInstalled(set.set.id); pack.set = set.set; - pack.documents = set instanceof TLRPC.TL_stickerSetFullCovered ? ((TLRPC.TL_stickerSetFullCovered) set).documents : set.covers; + if (set instanceof TLRPC.TL_stickerSetFullCovered) { + pack.documents = ((TLRPC.TL_stickerSetFullCovered) set).documents; + } else if (set instanceof TLRPC.TL_stickerSetNoCovered) { + TLRPC.TL_messages_stickerSet stickerSet = mediaDataController.getStickerSet(MediaDataController.getInputStickerSet(set.set), false); + if (stickerSet != null) { + pack.documents = stickerSet.documents; + } + } else { + pack.documents = set.covers; + } pack.index = index++; boolean premium = false; for (int j = 0; j < pack.documents.size(); ++j) { @@ -6942,7 +6973,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific return; } loadingUrl[0] = true; - final AlertDialog progressDialog[] = new AlertDialog[]{new AlertDialog(getContext(), 3)}; + final AlertDialog progressDialog[] = new AlertDialog[]{new AlertDialog(getContext(), AlertDialog.ALERT_TYPE_SPINNER)}; TLRPC.TL_messages_getEmojiURL req = new TLRPC.TL_messages_getEmojiURL(); req.lang_code = lastSearchAlias != null ? lastSearchAlias : lastSearchKeyboardLanguage[0]; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/FilterTabsView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/FilterTabsView.java index c91950566..14152e766 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/FilterTabsView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/FilterTabsView.java @@ -38,7 +38,6 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.TextView; -import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import androidx.core.graphics.ColorUtils; import androidx.recyclerview.widget.DefaultItemAnimator; @@ -1694,7 +1693,7 @@ public class FilterTabsView extends FrameLayout { return true; } - private void resetDefaultPosition() { + private Runnable resetDefaultPosition = () -> { if (UserConfig.getInstance(UserConfig.selectedAccount).isPremium()) { return; } @@ -1706,7 +1705,7 @@ public class FilterTabsView extends FrameLayout { break; } } - } + }; @Override public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { @@ -1715,8 +1714,8 @@ public class FilterTabsView extends FrameLayout { viewHolder.itemView.setPressed(true); viewHolder.itemView.setBackgroundColor(Theme.getColor(backgroundColorKey)); } else { - AndroidUtilities.cancelRunOnUIThread(this::resetDefaultPosition); - AndroidUtilities.runOnUIThread(this::resetDefaultPosition, 320); + AndroidUtilities.cancelRunOnUIThread(resetDefaultPosition); + AndroidUtilities.runOnUIThread(resetDefaultPosition, 320); } super.onSelectedChanged(viewHolder, actionState); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/FlickerLoadingView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/FlickerLoadingView.java index f9d97fa97..011733813 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/FlickerLoadingView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/FlickerLoadingView.java @@ -43,6 +43,7 @@ public class FlickerLoadingView extends View { public final static int REACTED_TYPE_WITH_EMOJI_HINT = 23; public static final int TOPIC_CELL_TYPE = 24; public static final int DIALOG_CACHE_CONTROL = 25; + public static final int CHECKBOX_TYPE = 26; private int gradientWidth; private LinearGradient gradient; @@ -682,6 +683,26 @@ public class FlickerLoadingView extends View { // checkRtl(rectF); canvas.drawRoundRect(rectF, AndroidUtilities.dp(4), AndroidUtilities.dp(4), paint); + h += getCellHeight(getMeasuredWidth()); + k++; + if (isSingleCell && k >= itemsCount) { + break; + } + } + } else if (viewType == CHECKBOX_TYPE) { + int k = 0; + while (h <= getMeasuredHeight()) { + int r = AndroidUtilities.dp(21) >> 1; + canvas.drawCircle((LocaleController.isRTL ? getMeasuredWidth() - AndroidUtilities.dp(21) - r : AndroidUtilities.dp(21) + r), h + AndroidUtilities.dp(16) + r, r, paint); + + rectF.set(AndroidUtilities.dp(60), h + AndroidUtilities.dp(21), AndroidUtilities.dp(190), h + AndroidUtilities.dp(29)); + checkRtl(rectF); + canvas.drawRoundRect(rectF, AndroidUtilities.dp(4), AndroidUtilities.dp(4), paint); + + rectF.set(getMeasuredWidth() - AndroidUtilities.dp(16), h + AndroidUtilities.dp(21), getMeasuredWidth() - AndroidUtilities.dp(62), h + AndroidUtilities.dp(29)); + checkRtl(rectF); + canvas.drawRoundRect(rectF, AndroidUtilities.dp(4), AndroidUtilities.dp(4), paint); + h += getCellHeight(getMeasuredWidth()); k++; if (isSingleCell && k >= itemsCount) { @@ -807,6 +828,8 @@ public class FlickerLoadingView extends View { return AndroidUtilities.dp(60); case DIALOG_CACHE_CONTROL: return AndroidUtilities.dp(51); + case CHECKBOX_TYPE: + return AndroidUtilities.dp(50) + 1; } return 0; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Forum/ForumUtilities.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Forum/ForumUtilities.java index 94475ecfe..26f24d1c1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Forum/ForumUtilities.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Forum/ForumUtilities.java @@ -295,6 +295,9 @@ public class ForumUtilities { name = fromChat.title; } + if ((topicEdit.flags & 8) != 0) { + return AndroidUtilities.replaceCharSequence("%s", topicEdit.hidden ? LocaleController.getString(R.string.TopicHidden2) : LocaleController.getString(R.string.TopicShown2), name); + } if ((topicEdit.flags & 4) != 0) { CharSequence charSequence = AndroidUtilities.replaceCharSequence("%2$s", topicEdit.closed ? LocaleController.getString(R.string.TopicWasClosedAction) : LocaleController.getString(R.string.TopicWasReopenedAction), ForumUtilities.getTopicSpannedName(topic, null)); return AndroidUtilities.replaceCharSequence("%1$s", charSequence, name); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/IPhotoPaintView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/IPhotoPaintView.java new file mode 100644 index 000000000..8dea0de18 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/IPhotoPaintView.java @@ -0,0 +1,60 @@ +package org.telegram.ui.Components; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.view.MotionEvent; +import android.view.View; + +import org.telegram.messenger.VideoEditedInfo; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.PhotoViewer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public interface IPhotoPaintView { + default View getView() { + if (this instanceof View) { + return (View) this; + } + throw new IllegalArgumentException("You should override getView() if you're not inheriting from it."); + } + + void init(); + void shutdown(); + void onResume(); + + void setOffsetTranslationY(float y, float panProgress, int keyboardHeight, boolean isPan); + float getOffsetTranslationY(); + void updateColors(); + boolean hasChanges(); + Bitmap getBitmap(ArrayList entities, Bitmap[] thumbBitmap); + long getLcm(); + View getDoneView(); + View getCancelView(); + void maybeShowDismissalAlert(PhotoViewer photoViewer, Activity parentActivity, Runnable okRunnable); + boolean onTouch(MotionEvent ev); + void setTransform(float scale, float trX, float trY, float imageWidth, float imageHeight); + void setOnDoneButtonClickedListener(Runnable callback); + void onBackPressed(); + int getEmojiPadding(boolean panned); + + void updateZoom(boolean zoomedOut); + + float adjustPanLayoutHelperProgress(); + + default int getAdditionalBottom() { + return 0; + } + + default int getAdditionalTop() { + return 0; + } + + default List getMasks() { + return Collections.emptyList(); + } + + default void onCleanupEntities() {} +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ImageUpdater.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ImageUpdater.java index 96a2bc2fd..211bb858e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ImageUpdater.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ImageUpdater.java @@ -45,6 +45,7 @@ import org.telegram.messenger.UserConfig; import org.telegram.messenger.Utilities; import org.telegram.messenger.VideoEditedInfo; import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.BottomSheet; @@ -76,6 +77,7 @@ public class ImageUpdater implements NotificationCenter.NotificationCenterDelega public String currentPicturePath; private TLRPC.PhotoSize bigPhoto; private TLRPC.PhotoSize smallPhoto; + private boolean isVideo; private String uploadingImage; private String uploadingVideo; private String videoPath; @@ -88,6 +90,7 @@ public class ImageUpdater implements NotificationCenter.NotificationCenterDelega private boolean searchAvailable = true; private boolean uploadAfterSelect = true; + private TLRPC.User user; private TLRPC.InputFile uploadedPhoto; private TLRPC.InputFile uploadedVideo; @@ -96,11 +99,61 @@ public class ImageUpdater implements NotificationCenter.NotificationCenterDelega private boolean canSelectVideo; private boolean forceDarkTheme; private boolean showingFromDialog; + private boolean canceled; private final static int attach_photo = 0; + public final static int TYPE_DEFAULT = 0; + public final static int TYPE_SET_PHOTO_FOR_USER = 1; + public final static int TYPE_SUGGEST_PHOTO_FOR_USER = 2; + + private int type; + + public void processEntry(MediaController.PhotoEntry photoEntry) { + String path = null; + if (photoEntry.imagePath != null) { + path = photoEntry.imagePath; + } else { + path = photoEntry.path; + } + MessageObject avatarObject = null; + Bitmap bitmap; + if (photoEntry.isVideo || photoEntry.editedInfo != null) { + TLRPC.TL_message message = new TLRPC.TL_message(); + message.id = 0; + message.message = ""; + message.media = new TLRPC.TL_messageMediaEmpty(); + message.action = new TLRPC.TL_messageActionEmpty(); + message.dialog_id = 0; + avatarObject = new MessageObject(UserConfig.selectedAccount, message, false, false); + avatarObject.messageOwner.attachPath = new File(FileLoader.getDirectory(FileLoader.MEDIA_DIR_CACHE), SharedConfig.getLastLocalId() + "_avatar.mp4").getAbsolutePath(); + avatarObject.videoEditedInfo = photoEntry.editedInfo; + bitmap = ImageLoader.loadBitmap(photoEntry.thumbPath, null, 800, 800, true); + } else { + bitmap = ImageLoader.loadBitmap(path, null, 800, 800, true); + } + processBitmap(bitmap, avatarObject); + } + + public void cancel() { + canceled = true; + if (uploadingImage != null) { + FileLoader.getInstance(currentAccount).cancelFileUpload(uploadingImage, false); + } + if (uploadingVideo != null) { + FileLoader.getInstance(currentAccount).cancelFileUpload(uploadingVideo, false); + } + if (delegate != null) { + delegate.didUploadFailed(); + } + } + + public boolean isCanceled() { + return canceled; + } + public interface ImageUpdaterDelegate { - void didUploadPhoto(TLRPC.InputFile photo, TLRPC.InputFile video, double videoStartTimestamp, String videoPath, TLRPC.PhotoSize bigSize, TLRPC.PhotoSize smallSize); + void didUploadPhoto(TLRPC.InputFile photo, TLRPC.InputFile video, double videoStartTimestamp, String videoPath, TLRPC.PhotoSize bigSize, TLRPC.PhotoSize smallSize, boolean isVideo); default String getInitialSearchString() { return null; @@ -113,6 +166,14 @@ public class ImageUpdater implements NotificationCenter.NotificationCenterDelega default void didStartUpload(boolean isVideo) { } + + default void didUploadFailed() { + + } + + default boolean canFinishFragment() { + return true; + } } public boolean isUploadingImage() { @@ -120,6 +181,7 @@ public class ImageUpdater implements NotificationCenter.NotificationCenterDelega } public void clear() { + canceled = false; if (uploadingImage != null || uploadingVideo != null || convertingVideo != null) { clearAfterUpdate = true; } else { @@ -138,23 +200,36 @@ public class ImageUpdater implements NotificationCenter.NotificationCenterDelega public ImageUpdater(boolean allowVideo) { imageReceiver = new ImageReceiver(null); - canSelectVideo = allowVideo && Build.VERSION.SDK_INT > 18; + canSelectVideo = allowVideo; + } + + public void setCanSelectVideo(boolean canSelectVideo) { + this.canSelectVideo = canSelectVideo; } public void setDelegate(ImageUpdaterDelegate imageUpdaterDelegate) { delegate = imageUpdaterDelegate; } - public void openMenu(boolean hasAvatar, Runnable onDeleteAvatar, DialogInterface.OnDismissListener onDismiss) { + public void openMenu(boolean hasAvatar, Runnable onDeleteAvatar, DialogInterface.OnDismissListener onDismiss, int type) { if (parentFragment == null || parentFragment.getParentActivity() == null) { return; } + canceled = false; + this.type = type; if (useAttachMenu) { openAttachMenu(onDismiss); return; } BottomSheet.Builder builder = new BottomSheet.Builder(parentFragment.getParentActivity()); - builder.setTitle(LocaleController.getString("ChoosePhoto", R.string.ChoosePhoto), true); + + if (type == TYPE_SET_PHOTO_FOR_USER) { + builder.setTitle(LocaleController.formatString("SetPhotoFor", R.string.SetPhotoFor, user.first_name), true); + } else if (type == TYPE_SUGGEST_PHOTO_FOR_USER) { + builder.setTitle(LocaleController.formatString("SuggestPhotoFor", R.string.SuggestPhotoFor, user.first_name), true); + } else { + builder.setTitle(LocaleController.getString("ChoosePhoto", R.string.ChoosePhoto), true); + } ArrayList items = new ArrayList<>(); ArrayList icons = new ArrayList<>(); @@ -311,6 +386,11 @@ public class ImageUpdater implements NotificationCenter.NotificationCenterDelega public void onCaptionChanged(CharSequence caption) { } + + @Override + public boolean canFinishFragment() { + return delegate.canFinishFragment(); + } }); fragment.setMaxSelectedPhotos(1, false); fragment.setInitialSearchString(delegate.getInitialSearchString()); @@ -334,6 +414,9 @@ public class ImageUpdater implements NotificationCenter.NotificationCenterDelega } chatAttachAlert.init(); chatAttachAlert.setOnHideListener(onDismissListener); + if (type != 0) { + chatAttachAlert.avatarFor(new AvatarFor(user, type)); + } parentFragment.showDialog(chatAttachAlert); } @@ -444,6 +527,11 @@ public class ImageUpdater implements NotificationCenter.NotificationCenterDelega } }); } + if (type == TYPE_SET_PHOTO_FOR_USER) { + chatAttachAlert.getSelectedTextView().setText(LocaleController.formatString("SetPhotoFor", R.string.SetPhotoFor, user.first_name)); + } else if (type == TYPE_SUGGEST_PHOTO_FOR_USER) { + chatAttachAlert.getSelectedTextView().setText(LocaleController.formatString("SuggestPhotoFor", R.string.SuggestPhotoFor, user.first_name)); + } } private void didSelectPhotos(ArrayList photos) { @@ -638,30 +726,8 @@ public class ImageUpdater implements NotificationCenter.NotificationCenterDelega PhotoViewer.getInstance().openPhotoForSelect(arrayList, 0, PhotoViewer.SELECT_TYPE_AVATAR, false, new PhotoViewer.EmptyPhotoViewerProvider() { @Override public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo, boolean notify, int scheduleDate, boolean forceDocument) { - String path = null; MediaController.PhotoEntry photoEntry = (MediaController.PhotoEntry) arrayList.get(0); - if (photoEntry.imagePath != null) { - path = photoEntry.imagePath; - } else if (photoEntry.path != null) { - path = photoEntry.path; - } - MessageObject avatarObject = null; - Bitmap bitmap; - if (photoEntry.isVideo || photoEntry.editedInfo != null) { - TLRPC.TL_message message = new TLRPC.TL_message(); - message.id = 0; - message.message = ""; - message.media = new TLRPC.TL_messageMediaEmpty(); - message.action = new TLRPC.TL_messageActionEmpty(); - message.dialog_id = 0; - avatarObject = new MessageObject(UserConfig.selectedAccount, message, false, false); - avatarObject.messageOwner.attachPath = new File(FileLoader.getDirectory(FileLoader.MEDIA_DIR_CACHE), SharedConfig.getLastLocalId() + "_avatar.mp4").getAbsolutePath(); - avatarObject.videoEditedInfo = photoEntry.editedInfo; - bitmap = ImageLoader.loadBitmap(photoEntry.thumbPath, null, 800, 800, true); - } else { - bitmap = ImageLoader.loadBitmap(path, null, 800, 800, true); - } - processBitmap(bitmap, avatarObject); + processEntry(photoEntry); } @Override @@ -758,10 +824,12 @@ public class ImageUpdater implements NotificationCenter.NotificationCenterDelega if (delegate != null) { delegate.didStartUpload(true); } + isVideo = true; } else { if (delegate != null) { delegate.didStartUpload(false); } + isVideo = false; } NotificationCenter.getInstance(currentAccount).addObserver(ImageUpdater.this, NotificationCenter.fileUploaded); NotificationCenter.getInstance(currentAccount).addObserver(ImageUpdater.this, NotificationCenter.fileUploadProgressChanged); @@ -771,7 +839,7 @@ public class ImageUpdater implements NotificationCenter.NotificationCenterDelega } } if (delegate != null) { - delegate.didUploadPhoto(null, null, 0, null, bigPhoto, smallPhoto); + delegate.didUploadPhoto(null, null, 0, null, bigPhoto, smallPhoto, isVideo); } } } @@ -793,6 +861,8 @@ public class ImageUpdater implements NotificationCenter.NotificationCenterDelega } } + private float currentImageProgress; + @Override public void didReceivedNotification(int id, int account, Object... args) { if (id == NotificationCenter.fileUploaded || id == NotificationCenter.fileUploadFailed) { @@ -817,7 +887,7 @@ public class ImageUpdater implements NotificationCenter.NotificationCenterDelega NotificationCenter.getInstance(currentAccount).removeObserver(ImageUpdater.this, NotificationCenter.fileUploadFailed); if (id == NotificationCenter.fileUploaded) { if (delegate != null) { - delegate.didUploadPhoto(uploadedPhoto, uploadedVideo, videoTimestamp, videoPath, bigPhoto, smallPhoto); + delegate.didUploadPhoto(uploadedPhoto, uploadedVideo, videoTimestamp, videoPath, bigPhoto, smallPhoto, isVideo); } } cleanup(); @@ -829,10 +899,11 @@ public class ImageUpdater implements NotificationCenter.NotificationCenterDelega Long loadedSize = (Long) args[1]; Long totalSize = (Long) args[2]; float progress = Math.min(1f, loadedSize / (float) totalSize); - delegate.onUploadProgressChanged(progress); + delegate.onUploadProgressChanged(currentImageProgress = progress); } } else if (id == NotificationCenter.fileLoaded || id == NotificationCenter.fileLoadFailed || id == NotificationCenter.httpFileDidLoad || id == NotificationCenter.httpFileDidFailedLoad) { String path = (String) args[0]; + currentImageProgress = 1f; if (path.equals(uploadingImage)) { NotificationCenter.getInstance(currentAccount).removeObserver(ImageUpdater.this, NotificationCenter.fileLoaded); NotificationCenter.getInstance(currentAccount).removeObserver(ImageUpdater.this, NotificationCenter.fileLoadFailed); @@ -845,6 +916,9 @@ public class ImageUpdater implements NotificationCenter.NotificationCenterDelega processBitmap(bitmap, null); } else { imageReceiver.setImageBitmap((Drawable) null); + if (delegate != null) { + delegate.didUploadFailed(); + } } } } else if (id == NotificationCenter.filePreparingFailed) { @@ -919,4 +993,27 @@ public class ImageUpdater implements NotificationCenter.NotificationCenterDelega public void setShowingFromDialog(boolean b) { showingFromDialog = b; } + + public void setUser(TLRPC.User user) { + this.user = user; + } + + public float getCurrentImageProgress() { + return currentImageProgress; + } + + public static class AvatarFor { + + public final TLObject object; + public TLRPC.User fromObject; + public final int type; + public boolean self; + public boolean isVideo; + + public AvatarFor(TLObject object, int type) { + this.object = object; + this.type = type; + self = object instanceof TLRPC.User && ((TLRPC.User) object).self; + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/JoinCallAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/JoinCallAlert.java index 78be91ee2..86875118b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/JoinCallAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/JoinCallAlert.java @@ -22,6 +22,10 @@ import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.core.widget.NestedScrollView; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import org.telegram.messenger.AccountInstance; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ChatObject; @@ -45,10 +49,6 @@ import org.telegram.ui.Cells.ShareDialogCell; import java.util.ArrayList; -import androidx.core.widget.NestedScrollView; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - public class JoinCallAlert extends BottomSheet { private Drawable shadowDrawable; @@ -202,7 +202,7 @@ public class JoinCallAlert extends BottomSheet { callback.run(cachedChats.size() == 1); return; } - final AlertDialog progressDialog = new AlertDialog(context, 3); + final AlertDialog progressDialog = new AlertDialog(context, AlertDialog.ALERT_TYPE_SPINNER); TLRPC.TL_phone_getGroupCallJoinAs req = new TLRPC.TL_phone_getGroupCallJoinAs(); req.peer = accountInstance.getMessagesController().getInputPeer(did); int reqId = accountInstance.getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { @@ -242,7 +242,7 @@ public class JoinCallAlert extends BottomSheet { showAlert(context, did, cachedChats, fragment, type, scheduledPeer, delegate); } } else { - final AlertDialog progressDialog = new AlertDialog(context, 3); + final AlertDialog progressDialog = new AlertDialog(context, AlertDialog.ALERT_TYPE_SPINNER); TLRPC.TL_phone_getGroupCallJoinAs req = new TLRPC.TL_phone_getGroupCallJoinAs(); req.peer = accountInstance.getMessagesController().getInputPeer(did); int reqId = accountInstance.getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkActionView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkActionView.java index 12dffb0f2..c71b99d7f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkActionView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkActionView.java @@ -359,13 +359,14 @@ public class LinkActionView extends LinearLayout { } private void showQrCode() { - qrCodeBottomSheet = new QRCodeBottomSheet(getContext(), link, isChannel ? LocaleController.getString("QRCodeLinkHelpChannel", R.string.QRCodeLinkHelpChannel) : LocaleController.getString("QRCodeLinkHelpGroup", R.string.QRCodeLinkHelpGroup)) { + qrCodeBottomSheet = new QRCodeBottomSheet(getContext(), LocaleController.getString("InviteByQRCode", R.string.InviteByQRCode), link, isChannel ? LocaleController.getString("QRCodeLinkHelpChannel", R.string.QRCodeLinkHelpChannel) : LocaleController.getString("QRCodeLinkHelpGroup", R.string.QRCodeLinkHelpGroup), false) { @Override public void dismiss() { super.dismiss(); qrCodeBottomSheet = null; } }; + qrCodeBottomSheet.setCenterAnimation(R.raw.qr_code_logo); qrCodeBottomSheet.show(); if (actionBarPopupWindow != null) { actionBarPopupWindow.dismiss(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkPath.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkPath.java index 9490fcfa1..377f61b61 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkPath.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkPath.java @@ -26,8 +26,10 @@ public class LinkPath extends Path { private int baselineShift; private int lineHeight; + public float centerX, centerY; + public static int getRadius() { - return AndroidUtilities.dp(4); + return AndroidUtilities.dp(5); } private static CornerPathEffect roundedEffect; @@ -133,6 +135,8 @@ public class LinkPath extends Path { } else if (baselineShift > 0) { y += baselineShift; } + centerX = (right + left) / 2; + centerY = (y2 + y) / 2; if (useRoundRect) { // final CharSequence text = currentLayout.getText(); // int startOffset = currentLayout.getOffsetForHorizontal(currentLine, left), endOffset = currentLayout.getOffsetForHorizontal(currentLine, right) + 1; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkSpanDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkSpanDrawable.java index 23906ebf1..de688ad17 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkSpanDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkSpanDrawable.java @@ -11,6 +11,7 @@ import android.os.SystemClock; import android.text.Layout; import android.text.Spannable; import android.text.SpannableString; +import android.text.Spanned; import android.text.style.CharacterStyle; import android.text.style.ClickableSpan; import android.util.Pair; @@ -224,6 +225,9 @@ public class LinkSpanDrawable { private ArrayList> mLinks = new ArrayList<>(); private int mLinksCount = 0; + private ArrayList> mLoading = new ArrayList<>(); + private int mLoadingCount = 0; + public void addLink(LinkSpanDrawable link) { addLink(link, null); } @@ -234,6 +238,38 @@ public class LinkSpanDrawable { invalidate(obj); } + public static LoadingDrawable makeLoading(Layout layout, CharacterStyle span) { + return makeLoading(layout, span, 0); + } + + public static LoadingDrawable makeLoading(Layout layout, CharacterStyle span, float yOffset) { + if (layout == null || span == null || !(layout.getText() instanceof Spanned)) { + return null; + } + Spanned spanned = (Spanned) layout.getText(); + LinkPath path = new LinkPath(true); + int start = spanned.getSpanStart(span); + int end = spanned.getSpanEnd(span); + path.setCurrentLayout(layout, start, yOffset); + layout.getSelectionPath(start, end, path); + LoadingDrawable loadingDrawable = new LoadingDrawable(); + loadingDrawable.usePath(path); + loadingDrawable.setAppearByGradient(true); + loadingDrawable.setRadiiDp(CORNER_RADIUS_DP); + loadingDrawable.updateBounds(); + return loadingDrawable; + } + + public void addLoading(LoadingDrawable loadingDrawable) { + addLoading(loadingDrawable, null); + } + + public void addLoading(LoadingDrawable loadingDrawable, Object obj) { + mLoading.add(new Pair<>(loadingDrawable, obj)); + mLoadingCount++; + invalidate(obj); + } + public void removeLink(LinkSpanDrawable link) { removeLink(link, true); } @@ -295,6 +331,46 @@ public class LinkSpanDrawable { } } + public void removeLoading(LoadingDrawable loadingDrawable, boolean animated) { + if (loadingDrawable == null) { + return; + } + for (int i = 0; i < mLoadingCount; ++i) { + if (mLoading.get(i).first == loadingDrawable) { + removeLoadingAt(i, animated); + break; + } + } + } + + private void removeLoadingAt(int index, boolean animated) { + if (index < 0 || index >= mLoadingCount) { + return; + } + Pair pair = mLoading.get(index); + if (pair == null) { + return; + } + LoadingDrawable loadingDrawable = pair.first; + if (animated) { + if (!loadingDrawable.isDisappeared()) { + if (!loadingDrawable.isDisappearing()) + loadingDrawable.disappear(); + AndroidUtilities.runOnUIThread(() -> { + removeLoading(loadingDrawable, false); + }, loadingDrawable.timeToDisappear()); + } else { + removeLoading(loadingDrawable, false); + } + } else { + mLoading.remove(pair); + loadingDrawable.reset(); + loadingDrawable.resetDisappear(); + mLoadingCount = mLoading.size(); + invalidate(pair.second); + } + } + public void clear() { clear(true); } @@ -315,6 +391,22 @@ public class LinkSpanDrawable { } } + public void clearLoading(boolean animated) { + if (animated) { + for (int i = 0; i < mLoadingCount; ++i) { + removeLoadingAt(i, true); + } + } else if (mLoadingCount > 0) { + for (int i = 0; i < mLoadingCount; ++i) { + mLoading.get(i).first.reset(); + invalidate(mLoading.get(i).second, false); + } + mLoading.clear(); + mLoadingCount = 0; + invalidate(); + } + } + public void removeLinks(Object obj) { removeLinks(obj, true); } @@ -329,6 +421,10 @@ public class LinkSpanDrawable { public boolean draw(Canvas canvas) { boolean invalidate = false; + for (int i = 0; i < mLoadingCount; ++i) { + mLoading.get(i).first.draw(canvas); + invalidate = true; + } for (int i = 0; i < mLinksCount; ++i) { invalidate = mLinks.get(i).first.draw(canvas) || invalidate; } @@ -337,6 +433,12 @@ public class LinkSpanDrawable { public boolean draw(Canvas canvas, Object obj) { boolean invalidate = false; + for (int i = 0; i < mLoadingCount; ++i) { + if (mLoading.get(i).second == obj) { + mLoading.get(i).first.draw(canvas); + invalidate = true; + } + } for (int i = 0; i < mLinksCount; ++i) { if (mLinks.get(i).second == obj) { invalidate = mLinks.get(i).first.draw(canvas) || invalidate; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ListView/AdapterWithDiffUtils.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ListView/AdapterWithDiffUtils.java index 47ac696ea..61a772bec 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ListView/AdapterWithDiffUtils.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ListView/AdapterWithDiffUtils.java @@ -18,7 +18,6 @@ public abstract class AdapterWithDiffUtils extends RecyclerListView.SelectionAda DiffUtil.calculateDiff(callback).dispatchUpdatesTo(this); } - public static class Item { public final int viewType; public boolean selectable; @@ -37,7 +36,6 @@ public abstract class AdapterWithDiffUtils extends RecyclerListView.SelectionAda } return false; } - } private class DiffUtilsCallback extends DiffUtil.Callback { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/LoadingDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/LoadingDrawable.java index 82ef6b8a3..fcf811331 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/LoadingDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/LoadingDrawable.java @@ -3,10 +3,14 @@ package org.telegram.ui.Components; import android.graphics.Canvas; import android.graphics.ColorFilter; +import android.graphics.CornerPathEffect; import android.graphics.LinearGradient; +import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; @@ -21,77 +25,318 @@ import org.telegram.ui.ActionBar.Theme; public class LoadingDrawable extends Drawable { + private static final float APPEAR_DURATION = 550; + private static final float DISAPPEAR_DURATION = 320; + public Theme.ResourcesProvider resourcesProvider; public LoadingDrawable(Theme.ResourcesProvider resourcesProvider) { + this(); this.resourcesProvider = resourcesProvider; } public LoadingDrawable(String colorKey1, String colorKey2, Theme.ResourcesProvider resourcesProvider) { + this(); this.colorKey1 = colorKey1; this.colorKey2 = colorKey2; this.resourcesProvider = resourcesProvider; } - private long start = -1; - private LinearGradient gradient; + public LoadingDrawable(int color1, int color2) { + this(); + this.color1 = color1; + this.color2 = color2; + } + + public LoadingDrawable() { + strokePaint.setStyle(Paint.Style.STROKE); + strokePaint.setStrokeWidth(AndroidUtilities.density > 2 ? 2 : 1); + } + + public void setColors(int color1, int color2) { + this.color1 = color1; + this.color2 = color2; + this.stroke = false; + } + + public void setColors(int color1, int color2, int strokeColor1, int strokeColor2) { + this.color1 = color1; + this.color2 = color2; + this.stroke = true; + this.strokeColor1 = strokeColor1; + this.strokeColor2 = strokeColor2; + } + + public void setBackgroundColor(int backgroundColor) { + if (this.backgroundPaint == null) { + this.backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + } + this.backgroundPaint.setColor(this.backgroundColor = backgroundColor); + } + + public boolean isDisappearing() { + return disappearStart > 0 && (SystemClock.elapsedRealtime() - disappearStart) < DISAPPEAR_DURATION; + } + + public boolean isDisappeared() { + return disappearStart > 0 && (SystemClock.elapsedRealtime() - disappearStart) >= DISAPPEAR_DURATION; + } + + public long timeToDisappear() { + if (disappearStart > 0) { + return (long) DISAPPEAR_DURATION - (SystemClock.elapsedRealtime() - disappearStart); + } + return 0; + } + + private long start = -1, disappearStart = -1; + private LinearGradient gradient, strokeGradient; + private Matrix matrix = new Matrix(), strokeMatrix = new Matrix(); private int gradientColor1, gradientColor2; + private int gradientStrokeColor1, gradientStrokeColor2; public String colorKey1 = Theme.key_dialogBackground; public String colorKey2 = Theme.key_dialogBackgroundGray; - public Integer color1, color2; + public boolean stroke; + public Integer backgroundColor, color1, color2, strokeColor1, strokeColor2; private int gradientWidth; + private float gradientWidthScale = 1f; + private float speed = 1f; + public Paint backgroundPaint; public Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + public Paint strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private Path usePath; private Path path = new Path(); - private RectF[] rects; - private void setPathRects(RectF[] rects) { - this.rects = rects; + private Rect lastBounds; + private float[] radii = new float[8]; + + private RectF rectF = new RectF(); + + private boolean appearByGradient; + private int appearGradientWidth; + private Paint appearPaint; + private LinearGradient appearGradient; + private Matrix appearMatrix; + + private int disappearGradientWidth; + private Paint disappearPaint; + private LinearGradient disappearGradient; + private Matrix disappearMatrix; + + public void usePath(Path path) { + usePath = path; + } + + public void setGradientScale(float scale) { + gradientWidthScale = scale; + } + + public void setSpeed(float speed) { + this.speed = speed; + } + + public void setAppearByGradient(boolean enabled) { + appearByGradient = enabled; + } + + public void setRadiiDp(float allDp) { + if (usePath != null) { + paint.setPathEffect(new CornerPathEffect(AndroidUtilities.dp(allDp))); + strokePaint.setPathEffect(new CornerPathEffect(AndroidUtilities.dp(allDp))); + } else { + setRadiiDp(allDp, allDp, allDp, allDp); + } + } + + public void setRadiiDp(float topLeftDp, float topRightDp, float bottomRightDp, float bottomLeftDp) { + setRadii(AndroidUtilities.dp(topLeftDp), AndroidUtilities.dp(topRightDp), AndroidUtilities.dp(bottomRightDp), AndroidUtilities.dp(bottomLeftDp)); + } + + public void setRadii(float topLeft, float topRight, float bottomRight, float bottomLeft) { + final boolean changed = ( + radii[0] != topLeft || + radii[2] != topRight || + radii[4] != bottomRight || + radii[6] != bottomLeft + ); + radii[0] = radii[1] = topLeft; + radii[2] = radii[3] = topRight; + radii[4] = radii[5] = bottomRight; + radii[6] = radii[7] = bottomLeft; + + if (lastBounds != null && changed) { + path.rewind(); + rectF.set(lastBounds); + path.addRoundRect(rectF, radii, Path.Direction.CW); + } + } + + public void setBounds(@NonNull RectF bounds) { + super.setBounds((int) bounds.left, (int) bounds.top, (int) bounds.right, (int) bounds.bottom); + } + + public void reset() { + start = -1; + } + + public void disappear() { + if (!isDisappeared() && !isDisappearing()) { + disappearStart = SystemClock.elapsedRealtime(); + } + } + + public void resetDisappear() { + disappearStart = -1; } @Override public void draw(@NonNull Canvas canvas) { + if (isDisappeared()) { + return; + } + Rect bounds = getBounds(); if (getPaintAlpha() <= 0) { return; } - int gwidth = Math.min(AndroidUtilities.dp(400), bounds.width()); + + int width = bounds.width(); + if (width <= 0) { + width = AndroidUtilities.dp(200); + } + int gwidth = (int) (Math.min(AndroidUtilities.dp(400), width) * gradientWidthScale); int color1 = this.color1 != null ? this.color1 : Theme.getColor(colorKey1, resourcesProvider); int color2 = this.color2 != null ? this.color2 : Theme.getColor(colorKey2, resourcesProvider); - if (gradient == null || gwidth != gradientWidth || color1 != gradientColor1 || color2 != gradientColor2) { + int strokeColor1 = this.strokeColor1 != null ? this.strokeColor1 : Theme.getColor(colorKey1, resourcesProvider); + int strokeColor2 = this.strokeColor2 != null ? this.strokeColor2 : Theme.getColor(colorKey2, resourcesProvider); + if (gradient == null || gwidth != gradientWidth || color1 != gradientColor1 || color2 != gradientColor2 || strokeColor1 != gradientStrokeColor1 || strokeColor2 != gradientStrokeColor2) { gradientWidth = gwidth; + gradientColor1 = color1; gradientColor2 = color2; gradient = new LinearGradient(0, 0, gradientWidth, 0, new int[] { gradientColor1, gradientColor2, gradientColor1 }, new float[] { 0f, .67f, 1f }, Shader.TileMode.REPEAT); + gradient.setLocalMatrix(matrix); paint.setShader(gradient); + + gradientStrokeColor1 = strokeColor1; + gradientStrokeColor2 = strokeColor2; + strokeGradient = new LinearGradient(0, 0, gradientWidth, 0, new int[] { gradientStrokeColor1, gradientStrokeColor1, gradientStrokeColor2, gradientStrokeColor1 }, new float[] { 0f, .4f, .67f, 1f }, Shader.TileMode.REPEAT); + strokeGradient.setLocalMatrix(strokeMatrix); + strokePaint.setShader(strokeGradient); } long now = SystemClock.elapsedRealtime(); if (start < 0) { start = now; } - float offset = gradientWidth - (((now - start) / 4000f * AndroidUtilities.dp(2) * gradientWidth) % gradientWidth); + float t = (now - start) / 2000f; + t = (float) Math.pow(t * speed / 4, .85f) * 4f; + float offset = ((t * AndroidUtilities.density * gradientWidth) % gradientWidth); + float appearT = (now - start) / APPEAR_DURATION; + float disappearT = disappearStart > 0 ? 1f - CubicBezierInterpolator.EASE_OUT.getInterpolation(Math.min(1, (now - disappearStart) / DISAPPEAR_DURATION)) : 0; - canvas.save(); - canvas.clipRect(bounds); - canvas.translate(-offset, 0); - path.reset(); - if (rects == null) { - path.addRect(bounds.left + offset, bounds.top, bounds.right + offset, bounds.bottom, Path.Direction.CW); - } else { - for (int i = 0; i < rects.length; ++i) { - RectF r = rects[i]; - if (r != null) { - path.addRect(r.left + offset, r.top, r.right + offset, r.bottom, Path.Direction.CW); + if (isDisappearing()) { + int disappearGradientWidthNow = Math.max(AndroidUtilities.dp(200), bounds.width() / 3); + + if (disappearT < 1) { + if (disappearPaint == null) { + disappearPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + disappearGradient = new LinearGradient(0, 0, disappearGradientWidth = disappearGradientWidthNow, 0, new int[]{0xffffffff, 0x00ffffff}, new float[]{0f, 1f}, Shader.TileMode.CLAMP); + disappearMatrix = new Matrix(); + disappearGradient.setLocalMatrix(disappearMatrix); + disappearPaint.setShader(disappearGradient); + disappearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); + } else if (disappearGradientWidth != disappearGradientWidthNow) { + disappearGradient = new LinearGradient(0, 0, disappearGradientWidth = disappearGradientWidthNow, 0, new int[]{0xffffffff, 0x00ffffff}, new float[]{0f, 1f}, Shader.TileMode.CLAMP); + disappearGradient.setLocalMatrix(disappearMatrix); + disappearPaint.setShader(disappearGradient); } + + rectF.set(bounds); + rectF.inset(-strokePaint.getStrokeWidth(), -strokePaint.getStrokeWidth()); + canvas.saveLayerAlpha(rectF, 255, Canvas.ALL_SAVE_FLAG); + } + + } + if (appearByGradient) { + int appearGradientWidthNow = Math.max(AndroidUtilities.dp(200), bounds.width() / 3); + + if (appearT < 1) { + if (appearPaint == null) { + appearPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + appearGradient = new LinearGradient(0, 0, appearGradientWidth = appearGradientWidthNow, 0, new int[]{0x00ffffff, 0xffffffff}, new float[]{0f, 1f}, Shader.TileMode.CLAMP); + appearMatrix = new Matrix(); + appearGradient.setLocalMatrix(appearMatrix); + appearPaint.setShader(appearGradient); + appearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); + } else if (appearGradientWidth != appearGradientWidthNow) { + appearGradient = new LinearGradient(0, 0, appearGradientWidth = appearGradientWidthNow, 0, new int[]{0x00ffffff, 0xffffffff}, new float[]{0f, 1f}, Shader.TileMode.CLAMP); + appearGradient.setLocalMatrix(appearMatrix); + appearPaint.setShader(appearGradient); + } + + rectF.set(bounds); + rectF.inset(-strokePaint.getStrokeWidth(), -strokePaint.getStrokeWidth()); + canvas.saveLayerAlpha(rectF, 255, Canvas.ALL_SAVE_FLAG); } } - canvas.drawPath(path, paint); - canvas.translate(offset, 0); - canvas.restore(); + + matrix.setTranslate(offset, 0); + gradient.setLocalMatrix(matrix); + + strokeMatrix.setTranslate(offset, 0); + strokeGradient.setLocalMatrix(strokeMatrix); + + Path drawPath; + if (usePath != null) { + drawPath = usePath; + } else { + if (lastBounds == null || !lastBounds.equals(bounds)) { + path.rewind(); + rectF.set(lastBounds = bounds); + path.addRoundRect(rectF, radii, Path.Direction.CW); + } + drawPath = path; + } + if (backgroundPaint != null) { + canvas.drawPath(drawPath, backgroundPaint); + } + canvas.drawPath(drawPath, paint); + if (stroke) { + canvas.drawPath(drawPath, strokePaint); + } + + if (isDisappearing() && disappearT < 1) { + canvas.save(); + float appearOffset = disappearT * (disappearGradientWidth + bounds.width() + disappearGradientWidth) - disappearGradientWidth; + disappearMatrix.setTranslate(bounds.right - appearOffset, 0); + disappearGradient.setLocalMatrix(disappearMatrix); + int inset = (int) strokePaint.getStrokeWidth(); + canvas.drawRect(bounds.left - inset, bounds.top - inset, bounds.right + inset, bounds.bottom + inset, disappearPaint); + canvas.restore(); + canvas.restore(); + } + if (appearByGradient && appearT < 1) { + canvas.save(); + float appearOffset = appearT * (appearGradientWidth + bounds.width() + appearGradientWidth) - appearGradientWidth; + appearMatrix.setTranslate(bounds.left + appearOffset, 0); + appearGradient.setLocalMatrix(appearMatrix); + int inset = (int) strokePaint.getStrokeWidth(); + canvas.drawRect(bounds.left - inset, bounds.top - inset, bounds.right + inset, bounds.bottom + inset, appearPaint); + canvas.restore(); + canvas.restore(); + } invalidateSelf(); } + public void updateBounds() { + if (usePath != null) { + usePath.computeBounds(AndroidUtilities.rectTmp, false); + setBounds(AndroidUtilities.rectTmp); + } + } + public int getPaintAlpha() { return paint.getAlpha(); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/LoadingSpan.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/LoadingSpan.java index 84a7d60f6..27db8b7ca 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/LoadingSpan.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/LoadingSpan.java @@ -1,7 +1,6 @@ package org.telegram.ui.Components; import android.graphics.Canvas; -import android.graphics.CornerPathEffect; import android.graphics.Paint; import android.text.style.ReplacementSpan; import android.view.View; @@ -18,11 +17,18 @@ public class LoadingSpan extends ReplacementSpan { private View view; private LoadingDrawable drawable; + public int yOffset; + public LoadingSpan(View view, int size) { + this(view, size, AndroidUtilities.dp(2)); + } + + public LoadingSpan(View view, int size, int yOffset) { this.view = view; this.size = size; + this.yOffset = yOffset; this.drawable = new LoadingDrawable(null); - this.drawable.paint.setPathEffect(new CornerPathEffect(AndroidUtilities.dp(4))); + this.drawable.setRadiiDp(4); } public void setColorKeys(String colorKey1, String colorKey2) { @@ -47,12 +53,19 @@ public class LoadingSpan extends ReplacementSpan { @Override public int getSize(@NonNull Paint paint, CharSequence charSequence, int i, int i1, @Nullable Paint.FontMetricsInt fontMetricsInt) { + if (paint != null) { + drawable.setColors( + Theme.multAlpha(paint.getColor(), .1f), + Theme.multAlpha(paint.getColor(), .25f) + ); + drawable.setAlpha(paint.getAlpha()); + } return size; } @Override public void draw(@NonNull Canvas canvas, CharSequence charSequence, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) { - drawable.setBounds((int) x, top + AndroidUtilities.dp(2), (int) x + size, bottom - AndroidUtilities.dp(2)); + drawable.setBounds((int) x, top + yOffset, (int) x + size, bottom - AndroidUtilities.dp(2) + yOffset); drawable.draw(canvas); if (view != null) { view.invalidate(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/MediaActionDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/MediaActionDrawable.java index b2ee69010..3e9f73c14 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/MediaActionDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/MediaActionDrawable.java @@ -13,7 +13,6 @@ import android.graphics.RectF; import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.text.TextPaint; -import android.util.Log; import android.view.animation.DecelerateInterpolator; import org.telegram.messenger.AndroidUtilities; @@ -208,6 +207,9 @@ public class MediaActionDrawable extends Drawable { } public void setProgress(float value, boolean animated) { + if (downloadProgress == value) { + return; + } if (!animated) { animatedDownloadProgress = value; downloadProgressAnimationStart = value; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/MediaActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/MediaActivity.java index 7d268cf90..372598701 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/MediaActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/MediaActivity.java @@ -36,11 +36,16 @@ import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.SimpleTextView; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Components.FloatingDebug.FloatingDebugController; +import org.telegram.ui.Components.FloatingDebug.FloatingDebugProvider; +import org.telegram.ui.Components.Paint.ShapeDetector; import org.telegram.ui.ProfileActivity; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; -public class MediaActivity extends BaseFragment implements SharedMediaLayout.SharedMediaPreloaderDelegate { +public class MediaActivity extends BaseFragment implements SharedMediaLayout.SharedMediaPreloaderDelegate, FloatingDebugProvider { private SharedMediaLayout.SharedMediaPreloader sharedMediaPreloader; private TLRPC.ChatFull currentChatInfo; @@ -388,4 +393,16 @@ public class MediaActivity extends BaseFragment implements SharedMediaLayout.Sha } return ColorUtils.calculateLuminance(color) > 0.7f; } + + @Override + public List onGetDebugItems() { + return Arrays.asList( + new FloatingDebugController.DebugItem( + (ShapeDetector.isLearning(getContext()) ? "Disable" : "Enable") + " shape detector learning debug", + () -> { + ShapeDetector.setLearning(getContext(), !ShapeDetector.isLearning(getContext())); + } + ) + ); + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/MentionsContainerView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/MentionsContainerView.java index 41f3cec83..8f91b692c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/MentionsContainerView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/MentionsContainerView.java @@ -21,9 +21,7 @@ import androidx.recyclerview.widget.RecyclerView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLoader; -import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.SharedConfig; -import org.telegram.messenger.UserConfig; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Adapters.MentionsAdapter; @@ -433,9 +431,9 @@ public class MentionsContainerView extends BlurredFrameLayout { } float newTranslationY = (reversed ? -Math.max(0, listViewPadding - itemHeight) : -listViewPadding + Math.max(0, listViewPadding - itemHeight)); if (forceZeroHeight && !reversed) { - newTranslationY += listView.computeVerticalScrollOffset(); // getMeasuredHeight() - containerTop; + newTranslationY += listView.computeVerticalScrollOffset(); } - setVisibility(View.VISIBLE); + Integer updateVisibility = null; if (listViewTranslationAnimator != null) { listViewTranslationAnimator.cancel(); } @@ -447,15 +445,13 @@ public class MentionsContainerView extends BlurredFrameLayout { final float toHideT = forceZeroHeight ? 1 : 0; if (fromTranslation == toTranslation) { listViewTranslationAnimator = null; - setVisibility(forceZeroHeight ? View.GONE : View.VISIBLE); + updateVisibility = forceZeroHeight ? View.GONE : View.VISIBLE; if (switchLayoutManagerOnEnd && forceZeroHeight) { switchLayoutManagerOnEnd = false; listView.setLayoutManager(getNeededLayoutManager()); updateVisibility(shown = true); } } else { - int account = UserConfig.selectedAccount; -// animationIndex = NotificationCenter.getInstance(account).setAnimationInProgress(animationIndex, null); listViewTranslationAnimator = new SpringAnimation(new FloatValueHolder(fromTranslation)) .setSpring( @@ -463,9 +459,6 @@ public class MentionsContainerView extends BlurredFrameLayout { .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY) .setStiffness(550.0f) ); -// listViewTranslationAnimator = new SpringAnimation(listView, DynamicAnimation.TRANSLATION_Y, newTranslationY); -// listViewTranslationAnimator.getSpring().setStiffness(1500); -// listViewTranslationAnimator.getSpring().setDampingRatio(1); listViewTranslationAnimator.addUpdateListener((anm, val, vel) -> { listView.setTranslationY(val); hideT = AndroidUtilities.lerp(fromHideT, toHideT, (val - fromTranslation) / (toTranslation - fromTranslation)); @@ -492,10 +485,13 @@ public class MentionsContainerView extends BlurredFrameLayout { hideT = forceZeroHeight ? 1 : 0; listView.setTranslationY(newTranslationY); if (forceZeroHeight) { - setVisibility(View.GONE); + updateVisibility = View.GONE; // adapter.clear(true); } } + if (updateVisibility != null && getVisibility() != updateVisibility) { + setVisibility(updateVisibility); + } } public class MentionsListView extends RecyclerListView { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/MessageContainsEmojiButton.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/MessageContainsEmojiButton.java index 8ddb65897..4e6b94d20 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/MessageContainsEmojiButton.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/MessageContainsEmojiButton.java @@ -3,7 +3,6 @@ package org.telegram.ui.Components; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; -import android.graphics.CornerPathEffect; import android.graphics.Paint; import android.graphics.Rect; import android.text.Layout; @@ -152,7 +151,7 @@ public class MessageContainsEmojiButton extends FrameLayout implements Notificat loadingDrawable = new LoadingDrawable(resourcesProvider); loadingDrawable.colorKey1 = Theme.key_actionBarDefaultSubmenuBackground; loadingDrawable.colorKey2 = Theme.key_listSelector; - loadingDrawable.paint.setPathEffect(new CornerPathEffect(AndroidUtilities.dp(4))); + loadingDrawable.setRadiiDp(4); } } } @@ -168,7 +167,7 @@ public class MessageContainsEmojiButton extends FrameLayout implements Notificat private int updateLayout(int width, boolean full) { if (mainText != lastMainTextText || lastMainTextWidth != width) { if (mainText != null) { - mainTextLayout = new StaticLayout(mainText, 0, mainText.length(), textPaint, width, Layout.Alignment.ALIGN_NORMAL, 1, 0, false); + mainTextLayout = new StaticLayout(mainText, 0, mainText.length(), textPaint, Math.max(width, 0), Layout.Alignment.ALIGN_NORMAL, 1, 0, false); if (loadingDrawable != null && loadingBoundsTo == null) { int lastLine = mainTextLayout.getLineCount() - 1; lastLineMargin = (int) mainTextLayout.getPrimaryHorizontal(mainText.length()) + AndroidUtilities.dp(2); @@ -179,7 +178,7 @@ public class MessageContainsEmojiButton extends FrameLayout implements Notificat if (loadingBoundsFrom == null) { loadingBoundsFrom = new Rect(); } - loadingBoundsFrom.set(lastLineMargin, lastLineTop + AndroidUtilities.dp(1.25f), (int) (lastLineMargin + lwidth), (int) bottom + AndroidUtilities.dp(1.25f)); + loadingBoundsFrom.set(lastLineMargin, lastLineTop, (int) (lastLineMargin + lwidth), (int) bottom); loadingDrawable.setBounds(loadingBoundsFrom); loadingDrawableBoundsSet = true; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/MotionBackgroundDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/MotionBackgroundDrawable.java index a2b17eca4..f2bbb287f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/MotionBackgroundDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/MotionBackgroundDrawable.java @@ -29,6 +29,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLog; import org.telegram.messenger.GenericProvider; import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.SharedConfig; import org.telegram.messenger.Utilities; import java.lang.ref.WeakReference; @@ -251,7 +252,7 @@ public class MotionBackgroundDrawable extends Drawable { } public void switchToNextPosition(boolean fast) { - if (posAnimationProgress < 1.0f) { + if (posAnimationProgress < 1.0f || SharedConfig.getLiteMode().enabled()) { return; } rotatingPreview = false; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/NestedSizeNotifierLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/NestedSizeNotifierLayout.java new file mode 100644 index 000000000..c84173ed4 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/NestedSizeNotifierLayout.java @@ -0,0 +1,177 @@ +package org.telegram.ui.Components; + +import android.content.Context; +import android.view.View; + +import androidx.core.view.NestedScrollingParent3; +import androidx.core.view.NestedScrollingParentHelper; +import androidx.core.view.ViewCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.telegram.ui.ActionBar.BottomSheet; + +public class NestedSizeNotifierLayout extends SizeNotifierFrameLayout implements NestedScrollingParent3, View.OnLayoutChangeListener { + public NestedSizeNotifierLayout(Context context) { + super(context); + nestedScrollingParentHelper = new NestedScrollingParentHelper(this); + } + + private NestedScrollingParentHelper nestedScrollingParentHelper; + View targetListView; + ChildLayout childLayout; + BottomSheet.ContainerView bottomSheetContainerView; + + int maxTop; + boolean attached; + + private boolean childAttached() { + return childLayout != null && childLayout.isAttached() && childLayout.getListView() != null; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + updateMaxTop(); + } + + private void updateMaxTop() { + if (targetListView != null && childLayout != null) { + maxTop = targetListView.getMeasuredHeight() - targetListView.getPaddingBottom() - childLayout.getMeasuredHeight(); + } + } + + @Override + public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type, int[] consumed) { + if (target == targetListView && childAttached()) { + RecyclerListView innerListView = childLayout.getListView(); + int top = childLayout.getTop(); + if (top == maxTop) { + consumed[1] = dyUnconsumed; + innerListView.scrollBy(0, dyUnconsumed); + } + } + } + + @Override + public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) { + + } + + @Override + public boolean onNestedPreFling(View target, float velocityX, float velocityY) { + return super.onNestedPreFling(target, velocityX, velocityY); + } + + @Override + public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int type) { + if (target == targetListView && childAttached()) { + int t = childLayout.getTop(); + if (dy < 0) { + if (t <= maxTop) { + RecyclerListView innerListView = childLayout.getListView(); + LinearLayoutManager linearLayoutManager = (LinearLayoutManager) innerListView.getLayoutManager(); + int pos = linearLayoutManager.findFirstVisibleItemPosition(); + if (pos != RecyclerView.NO_POSITION) { + RecyclerView.ViewHolder holder = innerListView.findViewHolderForAdapterPosition(pos); + int top = holder != null ? holder.itemView.getTop() : -1; + int paddingTop = innerListView.getPaddingTop(); + if (top != paddingTop || pos != 0) { + consumed[1] = pos != 0 ? dy : Math.max(dy, (top - paddingTop)); + innerListView.scrollBy(0, dy); + } + } + } else if (bottomSheetContainerView != null && !targetListView.canScrollVertically(dy)) { + bottomSheetContainerView.onNestedScroll(target, 0, 0, dx, dy); + } + } else { + if (bottomSheetContainerView != null) { + bottomSheetContainerView.onNestedPreScroll(target, dx, dy, consumed); + } + } + } + } + + @Override + public boolean onStartNestedScroll(View child, View target, int axes, int type) { + return child != null && child.isAttachedToWindow() && axes == ViewCompat.SCROLL_AXIS_VERTICAL; + } + + @Override + public void onNestedScrollAccepted(View child, View target, int axes, int type) { + nestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes); + } + + @Override + public void onStopNestedScroll(View target, int type) { + nestedScrollingParentHelper.onStopNestedScroll(target); + if (bottomSheetContainerView != null) { + bottomSheetContainerView.onStopNestedScroll(target); + } + } + + @Override + public void onStopNestedScroll(View child) { + + } + + public void setTargetListView(View target) { + this.targetListView = target; + updateMaxTop(); + } + + public void setChildLayout(ChildLayout childLayout) { + if (this.childLayout != childLayout) { + this.childLayout = childLayout; + if (attached) { + childLayout.getListView().addOnLayoutChangeListener(this); + } + updateMaxTop(); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + attached = true; + if (childLayout != null) { + childLayout.addOnLayoutChangeListener(this); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + attached = false; + if (childLayout != null) { + childLayout.removeOnLayoutChangeListener(this); + } + } + + public boolean isPinnedToTop() { + return childLayout != null && childLayout.getTop() == maxTop; + } + + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + updateMaxTop(); + } + + public interface ChildLayout { + RecyclerListView getListView(); + + int getTop(); + + boolean isAttached(); + + int getMeasuredHeight(); + + void addOnLayoutChangeListener(View.OnLayoutChangeListener listener); + + void removeOnLayoutChangeListener(OnLayoutChangeListener li); + } + + public void setBottomSheetContainerView(BottomSheet.ContainerView bottomSheetContainerView) { + this.bottomSheetContainerView = bottomSheetContainerView; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/NumberPicker.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/NumberPicker.java index e68fc3409..d2e2bc6f2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/NumberPicker.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/NumberPicker.java @@ -632,10 +632,16 @@ public class NumberPicker extends LinearLayout { } public void setWrapSelectorWheel(boolean wrapSelectorWheel) { - final boolean wrappingAllowed = !(mMaxValueSet && mMinValueSet) || (mMaxValue - mMinValue) >= mSelectorIndices.length; + final boolean wrappingAllowed = !(mMaxValueSet && mMinValueSet) || allItemsCount != null && (mMaxValue - mMinValue + 1) >= allItemsCount; mWrapSelectorWheel = wrappingAllowed && (mWrapSelectorWheelSetting = wrapSelectorWheel); } + private Integer allItemsCount; + public void setAllItemsCount(int allItemsCount) { + this.allItemsCount = allItemsCount; + setWrapSelectorWheel(mWrapSelectorWheelSetting); + } + public void setOnLongPressUpdateInterval(long intervalMillis) { mLongPressUpdateInterval = intervalMillis; } @@ -649,6 +655,7 @@ public class NumberPicker extends LinearLayout { } public void setMinValue(int minValue) { + mMinValueSet = true; if (mMinValue == minValue) { return; } @@ -656,7 +663,6 @@ public class NumberPicker extends LinearLayout { throw new IllegalArgumentException("minValue must be >= 0"); } mMinValue = minValue; - mMinValueSet = true; if (mMinValue > mValue) { if (mMinValue <= mFantomValue) { mValue = mFantomValue; @@ -664,9 +670,7 @@ public class NumberPicker extends LinearLayout { mValue = mMinValue; } } - if (mWrapSelectorWheelSetting && mMaxValueSet) { - mWrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length; - } + setWrapSelectorWheel(mWrapSelectorWheelSetting); initializeSelectorWheelIndices(); updateInputTextView(); tryComputeMaxWidth(); @@ -678,6 +682,7 @@ public class NumberPicker extends LinearLayout { } public void setMaxValue(int maxValue) { + mMaxValueSet = true; if (mMaxValue == maxValue) { return; } @@ -685,7 +690,6 @@ public class NumberPicker extends LinearLayout { throw new IllegalArgumentException("maxValue must be >= 0"); } mMaxValue = maxValue; - mMaxValueSet = true; if (mMaxValue < mValue) { if (mMaxValue >= mFantomValue) { mValue = mFantomValue; @@ -693,9 +697,7 @@ public class NumberPicker extends LinearLayout { mValue = mMaxValue; } } - if (mWrapSelectorWheelSetting && mMinValueSet) { - mWrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length; - } + setWrapSelectorWheel(mWrapSelectorWheelSetting); initializeSelectorWheelIndices(); updateInputTextView(); tryComputeMaxWidth(); @@ -966,9 +968,9 @@ public class NumberPicker extends LinearLayout { } private int getWrappedSelectorIndex(int selectorIndex) { - if (selectorIndex > mMaxValue) { + if (mMaxValueSet && selectorIndex > mMaxValue && mMaxValue - mMinValue != 0) { return mMinValue + (selectorIndex - mMaxValue) % (mMaxValue - mMinValue) - 1; - } else if (selectorIndex < mMinValue) { + } else if (mMinValueSet && selectorIndex < mMinValue && mMaxValue - mMinValue != 0) { return mMaxValue - (mMinValue - selectorIndex) % (mMaxValue - mMinValue) + 1; } return selectorIndex; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/OutlineEditText.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/OutlineEditText.java new file mode 100644 index 000000000..f82238dba --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/OutlineEditText.java @@ -0,0 +1,48 @@ +package org.telegram.ui.Components; + +import android.content.Context; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.text.InputType; +import android.util.TypedValue; +import android.view.Gravity; + +import org.telegram.ui.ActionBar.Theme; + +public class OutlineEditText extends OutlineTextContainerView { + + EditTextBoldCursor editText; + + public OutlineEditText(Context context) { + super(context); + + editText = new EditTextBoldCursor(context) { + @Override + protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { + super.onFocusChanged(focused, direction, previouslyFocusedRect); + animateSelection(focused || isFocused() ? 1f : 0f); + } + }; + editText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); + editText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + editText.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + editText.setBackground(null); + editText.setSingleLine(true); + editText.setInputType(InputType.TYPE_CLASS_TEXT); + editText.setTypeface(Typeface.DEFAULT); + editText.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteInputFieldActivated)); + editText.setCursorWidth(1.5f); + attachEditText(editText); + + addView(editText, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL)); + + } + + public void setHint(String hint) { + setText(hint); + } + + public EditTextBoldCursor getEditText() { + return editText; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Brush.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Brush.java index dc633bb2f..1bcca0c18 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Brush.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Brush.java @@ -4,53 +4,97 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; -public interface Brush { +import java.util.Arrays; +import java.util.List; - float getSpacing(); - float getAlpha(); - float getAngle(); - float getScale(); - boolean isLightSaber(); - Bitmap getStamp(); +public abstract class Brush { + public static List BRUSHES_LIST = Arrays.asList( + new Radial(), + new Arrow(), + new Elliptical(), + new Neon(), + new Blurer(), + new Eraser() + ); - class Radial implements Brush { + public static final int PAINT_TYPE_BLIT = 0; + public static final int PAINT_TYPE_COMPOSITE = 1; + public static final int PAINT_TYPE_BRUSH = 2; - @Override - public float getSpacing() { - return 0.15f; + public float getSpacing() { + return 0.15f; + } + + public float getAlpha() { + return 0.85f; + } + + public float getOverrideAlpha() { + return 1f; + } + + public float getAngle() { + return 0.0f; + } + + public float getScale() { + return 1.0f; + } + + public float getPreviewScale() { + return 0.4f; + } + + public float getDefaultWeight() { + return 0.25f; + } + + public boolean isEraser() { + return false; + } + + public String getShaderName(int paintType) { + switch (paintType) { + case PAINT_TYPE_BLIT: + return "blitWithMask"; + case PAINT_TYPE_COMPOSITE: + return "compositeWithMask"; + case PAINT_TYPE_BRUSH: + return "brush"; } + return null; + } + + public int getStampResId() { + return R.drawable.paint_radial_brush; + } + + public Bitmap getStamp() { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inScaled = false; + return BitmapFactory.decodeResource(ApplicationLoader.applicationContext.getResources(), getStampResId(), options); + } + + public float getSmoothThicknessRate() { + return 1f; + } + + public int getIconRes() { + return 0; + } + + public static class Radial extends Brush { @Override - public float getAlpha() { - return 0.85f; - } - - @Override - public float getAngle() { - return 0.0f; - } - - @Override - public float getScale() { - return 1.0f; - } - - @Override - public boolean isLightSaber() { - return false; - } - - @Override - public Bitmap getStamp() { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inScaled = false; - return BitmapFactory.decodeResource(ApplicationLoader.applicationContext.getResources(), R.drawable.paint_radial_brush, options); + public int getIconRes() { + return R.raw.photo_pen; } } - class Elliptical implements Brush { + public static class Elliptical extends Brush { @Override public float getSpacing() { @@ -62,9 +106,14 @@ public interface Brush { return 0.3f; } + @Override + public float getOverrideAlpha() { + return 0.45f; + } + @Override public float getAngle() { - return (float) Math.toRadians(125.0); + return (float) Math.toRadians(0.0); } @Override @@ -73,19 +122,27 @@ public interface Brush { } @Override - public boolean isLightSaber() { - return false; + public float getPreviewScale() { + return 0.4f; } @Override - public Bitmap getStamp() { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inScaled = false; - return BitmapFactory.decodeResource(ApplicationLoader.applicationContext.getResources(), R.drawable.paint_elliptical_brush, options); + public int getStampResId() { + return R.drawable.paint_elliptical_brush; + } + + @Override + public int getIconRes() { + return R.raw.photo_marker; + } + + @Override + public float getDefaultWeight() { + return 0.5f; } } - class Neon implements Brush { + public static class Neon extends Brush { @Override public float getSpacing() { @@ -97,61 +154,300 @@ public interface Brush { return 0.7f; } - @Override - public float getAngle() { - return 0.0f; - } - @Override public float getScale() { return 1.45f; } @Override - public boolean isLightSaber() { - return true; + public float getPreviewScale() { + return 0.2f; } @Override - public Bitmap getStamp() { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inScaled = false; - return BitmapFactory.decodeResource(ApplicationLoader.applicationContext.getResources(), R.drawable.paint_neon_brush, options); + public String getShaderName(int paintType) { + switch (paintType) { + case PAINT_TYPE_BLIT: + return "blitWithMaskLight"; + case PAINT_TYPE_COMPOSITE: + return "compositeWithMaskLight"; + case PAINT_TYPE_BRUSH: + return "brushLight"; + } + return null; + } + + @Override + public int getStampResId() { + return R.drawable.paint_neon_brush; + } + + @Override + public int getIconRes() { + return R.raw.photo_neon; + } + + @Override + public float getDefaultWeight() { + return 0.5f; } } - class Arrow implements Brush { + public static class Arrow extends Brush { @Override - public float getSpacing() { - return 0.15f; + public float getSmoothThicknessRate() { + return .25f; } + @Override + public int getIconRes() { + return R.raw.photo_arrow; + } + + @Override + public float getDefaultWeight() { + return 0.25f; + } + } + + public static class Eraser extends Brush { + @Override public float getAlpha() { - return 0.85f; + return 1f; } @Override - public float getAngle() { - return 0.0f; + public float getPreviewScale() { + return 0.35f; } @Override - public float getScale() { + public float getDefaultWeight() { return 1.0f; } @Override - public boolean isLightSaber() { - return false; + public String getShaderName(int paintType) { + switch (paintType) { + case PAINT_TYPE_BLIT: + return "blitWithMaskEraser"; + case PAINT_TYPE_COMPOSITE: + return "compositeWithMaskEraser"; + case PAINT_TYPE_BRUSH: + return "brush"; + } + return null; } @Override - public Bitmap getStamp() { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inScaled = false; - return BitmapFactory.decodeResource(ApplicationLoader.applicationContext.getResources(), R.drawable.paint_radial_brush, options); + public int getIconRes() { + return R.raw.photo_eraser; + } + + @Override + public boolean isEraser() { + return true; + } + } + + public static class Blurer extends Brush { + + @Override + public float getAlpha() { + return 1f; + } + + @Override + public float getPreviewScale() { + return 0.35f; + } + + @Override + public float getDefaultWeight() { + return 1.0f; + } + + @Override + public String getShaderName(int paintType) { + switch (paintType) { + case PAINT_TYPE_BLIT: + return "blitWithMaskBlurer"; + case PAINT_TYPE_COMPOSITE: + return "compositeWithMaskBlurer"; + case PAINT_TYPE_BRUSH: + return "brush"; + } + return null; + } + + @Override + public int getIconRes() { + return R.raw.photo_blur; + } + } + + public static abstract class Shape extends Brush { + + public static final int SHAPE_TYPE_CIRCLE = 0; + public static final int SHAPE_TYPE_RECTANGLE = 1; + public static final int SHAPE_TYPE_STAR = 2; + public static final int SHAPE_TYPE_BUBBLE = 3; + public static final int SHAPE_TYPE_ARROW = 4; + + public static List SHAPES_LIST = Arrays.asList( + new Circle(), + new Rectangle(), + new Star(), + new Bubble(), + new Arrow() + ); + + public static Brush.Shape make(int type) { + if (type < 0 || type > SHAPES_LIST.size()) { + throw new IndexOutOfBoundsException("Shape type must be in range from 0 to " + (SHAPES_LIST.size() - 1) + ", but got " + type); + } + return SHAPES_LIST.get(type); + } + + @Override + public String getShaderName(int paintType) { + switch (paintType) { + case PAINT_TYPE_BLIT: + case PAINT_TYPE_COMPOSITE: + return "shape"; + case PAINT_TYPE_BRUSH: + return "brush"; + } + return null; + } + + public String getShapeName() { + return null; + } + + public int getShapeShaderType() { + return 0; + } + + public int getFilledIconRes() { + return 0; + } + + @Override + public float getAlpha() { + return 1f; + } + + public static class Circle extends Shape { + @Override + public int getShapeShaderType() { + return SHAPE_TYPE_CIRCLE; + } + + @Override + public String getShapeName() { + return LocaleController.getString(R.string.PaintCircle); + } + + @Override + public int getIconRes() { + return R.drawable.photo_circle; + } + + @Override + public int getFilledIconRes() { + return R.drawable.photo_circle_fill; + } + } + + public static class Rectangle extends Shape { + @Override + public int getShapeShaderType() { + return SHAPE_TYPE_RECTANGLE; + } + + @Override + public String getShapeName() { + return LocaleController.getString(R.string.PaintRectangle); + } + + @Override + public int getIconRes() { + return R.drawable.photo_rectangle; + } + + @Override + public int getFilledIconRes() { + return R.drawable.photo_rectangle_fill; + } + } + + public static class Star extends Shape { + @Override + public int getShapeShaderType() { + return SHAPE_TYPE_STAR; + } + + @Override + public String getShapeName() { + return LocaleController.getString(R.string.PaintStar); + } + + @Override + public int getIconRes() { + return R.drawable.photo_star; + } + + @Override + public int getFilledIconRes() { + return R.drawable.photo_star_fill; + } + } + + public static class Bubble extends Shape { + @Override + public int getShapeShaderType() { + return SHAPE_TYPE_BUBBLE; + } + + @Override + public String getShapeName() { + return LocaleController.getString(R.string.PaintBubble); + } + + @Override + public int getIconRes() { + return R.drawable.msg_msgbubble; + } + + @Override + public int getFilledIconRes() { + return R.drawable.msg_msgbubble2; + } + } + + public static class Arrow extends Shape { + @Override + public int getShapeShaderType() { + return SHAPE_TYPE_ARROW; + } + + @Override + public String getShapeName() { + return LocaleController.getString(R.string.PaintArrow); + } + + @Override + public int getIconRes() { + return R.drawable.photo_arrowshape; + } + + @Override + public int getFilledIconRes() { + return R.drawable.photo_arrowshape; + } } } } \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/ColorPickerBottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/ColorPickerBottomSheet.java new file mode 100644 index 000000000..45e83f078 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/ColorPickerBottomSheet.java @@ -0,0 +1,1077 @@ +package org.telegram.ui.Components.Paint; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +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.Shader; +import android.graphics.drawable.Drawable; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.LongSparseArray; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.core.graphics.ColorUtils; +import androidx.core.math.MathUtils; +import androidx.core.util.Consumer; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.CubicBezierInterpolator; +import org.telegram.ui.Components.EditTextBoldCursor; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.Paint.Views.PaintColorsListView; +import org.telegram.ui.Components.Paint.Views.PipettePickerView; +import org.telegram.ui.Components.ViewPagerFixed; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Pattern; + +public class ColorPickerBottomSheet extends BottomSheet { + private final static int FROM_PICKER = 0, + FROM_ALPHA = 1, + FROM_INIT = 2, + FROM_GRID = 3, + FROM_SLIDER = 4, + FROM_SLIDER_TEXT = 5; + + private final static int HORIZONTAL_SQUARES = 12; + private final static int VERTICAL_SQUARES = 10; + + private ColorPickerView pickerView; + private ImageView pipetteView; + private ImageView doneView; + private AlphaPickerView alphaPickerView; + private android.graphics.Path path = new android.graphics.Path(); + private int mColor; + private Consumer colorListener; + + private PipetteDelegate pipetteDelegate; + + private boolean initialized; + + public ColorPickerBottomSheet(Context context, Theme.ResourcesProvider resourcesProvider) { + super(context, true, resourcesProvider); + + fixNavigationBar(0xff252525); + + shadowDrawable = context.getResources().getDrawable(R.drawable.sheet_shadow_round).mutate(); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(0xff252525, PorterDuff.Mode.MULTIPLY)); + + LinearLayout linearLayout = new LinearLayout(context); + linearLayout.setOrientation(LinearLayout.VERTICAL); + linearLayout.setPadding(0, AndroidUtilities.dp(16), 0, 0); + + pipetteView = new ImageView(context); + pipetteView.setImageResource(R.drawable.picker); + pipetteView.setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN)); + pipetteView.setBackground(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + pipetteView.setOnClickListener(v -> { + if (pipetteDelegate.isPipetteVisible()) { + return; + } + + Bitmap drawingBitmap = AndroidUtilities.snapshotView(pipetteDelegate.getSnapshotDrawingView()); + + Bitmap bitmap = Bitmap.createBitmap(drawingBitmap.getWidth(), drawingBitmap.getHeight(), Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(bitmap); + c.drawColor(Color.BLACK); + + pipetteDelegate.onDrawImageOverCanvas(bitmap, c); + c.drawBitmap(drawingBitmap, 0, 0, null); + drawingBitmap.recycle(); + PipettePickerView pipette = new PipettePickerView(context, bitmap) { + @Override + protected void onStartPipette() { + pipetteDelegate.onStartColorPipette(); + } + + @Override + protected void onStopPipette() { + pipetteDelegate.onStopColorPipette(); + } + }; + pipetteDelegate.getContainerView().addView(pipette, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + pipette.setColorListener(pipetteDelegate::onColorSelected); + pipette.animateShow(); + dismiss(); + }); +// bottomLayout.addView(pipetteView, LayoutHelper.createLinear(28, 28, 0, 0, 16, 0)); + + doneView = new ImageView(context); + doneView.setImageResource(R.drawable.ic_ab_done); + doneView.setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN)); + doneView.setBackground(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + doneView.setOnClickListener(v -> { + dismiss(); + }); + + alphaPickerView = new AlphaPickerView(context); + alphaPickerView.setColor(Color.RED); + + pickerView = new ColorPickerView(context); + linearLayout.addView(pickerView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 0)); + + ScrollView scrollView = new ScrollView(context) { + { + setWillNotDraw(false); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) pickerView.getLayoutParams(); + params.height = (int) ((MeasureSpec.getSize(widthMeasureSpec) - AndroidUtilities.dp(24)) * ((float) VERTICAL_SQUARES / HORIZONTAL_SQUARES) + AndroidUtilities.dp(48 + 40)); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + float y = linearLayout.getY() + AndroidUtilities.dp(1); + int w = AndroidUtilities.dp(36); + AndroidUtilities.rectTmp.set((getMeasuredWidth() - w) / 2f, y, (getMeasuredWidth() + w) / 2f, y + AndroidUtilities.dp(4)); + int color = 0xff5B5B5B; + Theme.dialogs_onlineCirclePaint.setColor(color); + canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(2), AndroidUtilities.dp(2), Theme.dialogs_onlineCirclePaint); + } + }; + scrollView.addView(linearLayout); + setCustomView(scrollView); + } + + @Override + public void show() { + if (!pipetteDelegate.isPipetteAvailable()) { + pipetteView.setVisibility(View.GONE); + } + super.show(); + } + + public ColorPickerBottomSheet setPipetteDelegate(PipetteDelegate pipetteDelegate) { + this.pipetteDelegate = pipetteDelegate; + return this; + } + + public ColorPickerBottomSheet setColorListener(Consumer colorListener) { + this.colorListener = colorListener; + return this; + } + + public ColorPickerBottomSheet setColor(int color) { + onSetColor(color, FROM_INIT); + return this; + } + + private void onSetColor(int color, int from) { + if (!initialized) { + if (from != FROM_INIT) { + return; + } else { + initialized = true; + } + } + + if (from != FROM_SLIDER_TEXT) { + View focus = pickerView.findFocus(); + if (focus != null) { + focus.clearFocus(); + AndroidUtilities.hideKeyboard(focus); + } + } + + if (from != FROM_GRID) { + pickerView.gridPickerView.setCurrentColor(color); + } + if (from != FROM_PICKER) { + pickerView.gradientPickerView.setColor(color, from != FROM_ALPHA); + } + if (from != FROM_ALPHA) { + alphaPickerView.setColor(color); + } + pickerView.slidersPickerView.invalidateColor(); + } + + @Override + public void dismiss() { + super.dismiss(); + + if (colorListener != null) { + colorListener.accept(mColor); + } + } + + private final class ColorPickerView extends LinearLayout { + private GridPickerView gridPickerView; + private GradientPickerView gradientPickerView; + private SlidersPickerView slidersPickerView; + private ViewPagerFixed.TabsView tabsView; + + public ColorPickerView(Context context) { + super(context); + setOrientation(VERTICAL); + + gridPickerView = new GridPickerView(context); + gridPickerView.setCurrentColor(mColor); + gradientPickerView = new GradientPickerView(context); + slidersPickerView = new SlidersPickerView(context); + + ViewPagerFixed pager = new ViewPagerFixed(context, resourcesProvider) { + @Override + protected int tabMarginDp() { + return 0; + } + }; + pager.setAdapter(new ViewPagerFixed.Adapter() { + @Override + public int getItemCount() { + return 3; + } + + @Override + public String getItemTitle(int position) { + switch (position) { + default: + case 0: + return LocaleController.getString(R.string.PaintPaletteGrid).toUpperCase(); + case 1: + return LocaleController.getString(R.string.PaintPaletteSpectrum).toUpperCase(); + case 2: + return LocaleController.getString(R.string.PaintPaletteSliders).toUpperCase(); + } + } + + @Override + public View createView(int viewType) { + switch (viewType) { + default: + case 0: + return gridPickerView; + case 1: + return gradientPickerView; + case 2: + return slidersPickerView; + } + } + + @Override + public int getItemViewType(int position) { + return position; + } + + @Override + public void bindView(View view, int position, int viewType) {} + }); + + addView(pager, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 0, 1f)); + addView(alphaPickerView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48, 12, 0, 12, 0)); + + LinearLayout bottomLayout = new LinearLayout(context); + bottomLayout.setOrientation(LinearLayout.HORIZONTAL); + bottomLayout.setGravity(Gravity.CENTER_VERTICAL); + + bottomLayout.addView(pipetteView, LayoutHelper.createLinear(28, 28)); + bottomLayout.addView(tabsView = pager.createTabsView(false, 8), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 40, 1f, Gravity.CENTER_VERTICAL, 12, 0, 12, 0)); + bottomLayout.addView(doneView, LayoutHelper.createLinear(28, 28)); + + addView(bottomLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48, 14, 0, 14, 0)); + } + } + + private final class GridPickerView extends View { + private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + + private final int[] colors = { + 0xFF00A1D8, + 0xFF0060FD, + 0xFF4C1FB7, + 0xFF982BBC, + 0xFFB82C5D, + 0xFFFD3E12, + 0xFFFF6900, + 0xFFFDAB00, + 0xFFFCC700, + 0xFFFCFA43, + 0xFFD9EB37, + 0xFF76ba3f + }; + + private Paint selectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private LongSparseArray selectors = new LongSparseArray<>(); + private long selected = Long.MIN_VALUE; + private Path selectorPath = new Path(); + private float[] radii = new float[8]; + + private Map colorMap = new HashMap<>(); + + public GridPickerView(Context context) { + super(context); + + setPadding(AndroidUtilities.dp(14), AndroidUtilities.dp(3), AndroidUtilities.dp(14), AndroidUtilities.dp(3)); + + selectorPaint.setColor(0xffffffff); + selectorPaint.setStyle(Paint.Style.STROKE); + selectorPaint.setStrokeCap(Paint.Cap.ROUND); + selectorPaint.setStrokeJoin(Paint.Join.ROUND); + + for (int x = 0; x < HORIZONTAL_SQUARES; x++) { + for (int y = 0; y < VERTICAL_SQUARES; y++) { + if (y == 0) { + colorMap.put((long) (x << 16) + y, ColorUtils.blendARGB(Color.WHITE, Color.BLACK, (float) x / (HORIZONTAL_SQUARES - 1))); + } else { + int color; + int centerY = VERTICAL_SQUARES / 2 + 1; + if (y < centerY) { + color = ColorUtils.blendARGB(colors[x], Color.BLACK, (centerY - y - 1) / (VERTICAL_SQUARES / 2f - 1) * 0.5f); + } else { // y >= centerY + color = ColorUtils.blendARGB(colors[x], Color.WHITE, 0.5f - (VERTICAL_SQUARES - y - 1) / (VERTICAL_SQUARES / 2f) * 0.5f); + } + colorMap.put((long) (x << 16) + y, color); + } + } + } + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + updatePosition(event); + getParent().requestDisallowInterceptTouchEvent(true); + break; + case MotionEvent.ACTION_MOVE: + updatePosition(event); + break; + case MotionEvent.ACTION_UP: + updatePosition(event); + case MotionEvent.ACTION_CANCEL: + getParent().requestDisallowInterceptTouchEvent(false); + break; + } + return true; + } + + public void setCurrentColor(int color) { + for (Map.Entry e : colorMap.entrySet()) { + if (e.getValue() == color) { + long p = e.getKey(); + int x = (int) (p >> 16); + int y = (int) (p - (x << 16)); + setCurrentColor(x, y); + return; + } + } + + selected = Long.MIN_VALUE; + invalidate(); + } + + public void setCurrentColor(int x, int y) { + selected = ((long) x << 16) + y; + if (selectors.get(selected) == null) { + selectors.put(selected, 0f); + } + invalidate(); + } + + private void updatePosition(MotionEvent e) { + int xSize = (getWidth() - getPaddingLeft() - getPaddingRight()) / HORIZONTAL_SQUARES; + int ySize = (getHeight() - getPaddingTop() - getPaddingBottom()) / VERTICAL_SQUARES; + int x = (int) ((e.getX() - getPaddingLeft()) / xSize); + int y = (int) (e.getY() / ySize); + Integer clr = colorMap.get(((long) x << 16) + y); + if (clr != null) { + onSetColor(clr, FROM_GRID); + setCurrentColor(x, y); + } + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + AndroidUtilities.rectTmp.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()); + canvas.save(); + path.rewind(); + path.addRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(10), AndroidUtilities.dp(10), Path.Direction.CW); + canvas.clipPath(path); + + float xSize = (float) (getWidth() - getPaddingLeft() - getPaddingRight()) / HORIZONTAL_SQUARES; + float ySize = (float) (getHeight() - getPaddingTop() - getPaddingBottom()) / VERTICAL_SQUARES; + + for (int x = 0; x < HORIZONTAL_SQUARES; x++) { + for (int y = 0; y < VERTICAL_SQUARES; y++) { + Integer clr = colorMap.get((long) (x << 16) + y); + if (clr == null) { + continue; + } + paint.setColor(clr); + AndroidUtilities.rectTmp.set(getPaddingLeft() + x * xSize, getPaddingTop() + y * ySize, getPaddingLeft() + (x + 1) * xSize, getPaddingTop() + (y + 1) * ySize); + canvas.drawRect(AndroidUtilities.rectTmp, paint); + } + } + + canvas.restore(); + + for (int i = 0; i < selectors.size(); ++i) { + long p = selectors.keyAt(i); + float t = selectors.valueAt(i); + + if (selected == p) { + t = Math.min(1, t + 16f / 350f); + } else { + t = Math.max(0, t - 16f / 150f); + } + + int x = (int) (p >> 16); + int y = (int) (p - (x << 16)); + + Integer color = colorMap.get(p); + if (color != null) { + selectorPaint.setColor(AndroidUtilities.computePerceivedBrightness(color) > 0.721f ? 0xff111111 : 0xffffffff); + } + selectorPaint.setStrokeWidth(CubicBezierInterpolator.EASE_OUT_QUINT.getInterpolation(t) * AndroidUtilities.dp(3)); + + selectorPath.rewind(); + AndroidUtilities.rectTmp.set(getPaddingLeft() + x * xSize, getPaddingTop() + y * ySize, getPaddingLeft() + (x + 1) * xSize, getPaddingTop() + (y + 1) * ySize); + radii[0] = radii[1] = x == 0 && y == 0 ? AndroidUtilities.dp(10) : 0; // top left + radii[2] = radii[3] = x == HORIZONTAL_SQUARES - 1 && y == 0 ? AndroidUtilities.dp(10) : 0; // top right + radii[4] = radii[5] = x == HORIZONTAL_SQUARES - 1 && y == VERTICAL_SQUARES - 1 ? AndroidUtilities.dp(10) : 0; // bottom right + radii[6] = radii[7] = x == 0 && y == VERTICAL_SQUARES - 1 ? AndroidUtilities.dp(10) : 0; // bottom left + selectorPath.addRoundRect(AndroidUtilities.rectTmp, radii, Path.Direction.CW); + canvas.drawPath(selectorPath, selectorPaint); + + if (t <= 0 && selected != p) { + selectors.removeAt(i); + i--; + invalidate(); + continue; + } else if (t < 1) { + invalidate(); + } + + selectors.setValueAt(i, t); + } + } + } + + private final class GradientPickerView extends View { + private Paint gradientPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private Paint whiteBlackPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private Paint outlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + private float positionX, positionY; + private Drawable shadowDrawable; + + private float[] hsv = new float[3]; + + public GradientPickerView(Context context) { + super(context); + + setPadding(AndroidUtilities.dp(14), AndroidUtilities.dp(3), AndroidUtilities.dp(14), AndroidUtilities.dp(3)); + + outlinePaint.setColor(Color.WHITE); + outlinePaint.setStyle(Paint.Style.FILL_AND_STROKE); + outlinePaint.setStrokeWidth(AndroidUtilities.dp(3)); + shadowDrawable = ContextCompat.getDrawable(context, R.drawable.knob_shadow); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + gradientPaint.setShader(new LinearGradient(0, getPaddingTop(), 0, h - getPaddingBottom(), new int[]{Color.RED, Color.YELLOW, Color.GREEN, Color.CYAN, Color.BLUE, Color.MAGENTA, Color.RED}, null, android.graphics.Shader.TileMode.CLAMP)); + whiteBlackPaint.setShader(new LinearGradient(getPaddingLeft(), 0, w - getPaddingRight(), 0, new int[]{Color.WHITE, Color.TRANSPARENT, Color.TRANSPARENT, Color.BLACK}, new float[]{0.06f, 0.22f, 0.78f, 0.94f}, Shader.TileMode.MIRROR)); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + AndroidUtilities.rectTmp.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()); + canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(8), AndroidUtilities.dp(8), gradientPaint); + canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(8), AndroidUtilities.dp(8), whiteBlackPaint); + + float outlineRad = AndroidUtilities.dp(13); + float rad2 = outlineRad - outlinePaint.getStrokeWidth() / 2; + float min = AndroidUtilities.dp(16); + int w = getWidth() - getPaddingLeft() - getPaddingRight(); + int h = getHeight() - getPaddingTop() - getPaddingBottom(); + float cx = getPaddingLeft() + MathUtils.clamp(positionX * w, min, w - min); + float cy = getPaddingTop() + MathUtils.clamp(positionY * h, min, h - min); + shadowDrawable.getPadding(AndroidUtilities.rectTmp2); + shadowDrawable.setBounds( + (int) (cx - outlineRad - AndroidUtilities.rectTmp2.left), + (int) (cy - outlineRad - AndroidUtilities.rectTmp2.top), + (int) (cx + outlineRad + AndroidUtilities.rectTmp2.bottom), + (int) (cy + outlineRad + AndroidUtilities.rectTmp2.bottom) + ); + shadowDrawable.draw(canvas); + canvas.drawCircle(cx, cy, outlineRad, outlinePaint); + PaintColorsListView.drawColorCircle(canvas, cx, cy, rad2, ColorUtils.setAlphaComponent(mColor, 0xFF)); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + getParent().requestDisallowInterceptTouchEvent(true); + updatePosition(event); + break; + case MotionEvent.ACTION_MOVE: + updatePosition(event); + break; + case MotionEvent.ACTION_UP: + getParent().requestDisallowInterceptTouchEvent(false); + updatePosition(event); + break; + case MotionEvent.ACTION_CANCEL: + getParent().requestDisallowInterceptTouchEvent(false); + break; + } + return true; + } + + private void updatePosition(MotionEvent e) { + positionX = (e.getX() - getPaddingLeft()) / (getWidth() - getPaddingLeft() - getPaddingRight()); + positionY = (e.getY() - getPaddingTop()) / (getHeight() - getPaddingTop() - getPaddingBottom()); + + hsv[0] = positionY * 360f; + if (positionX <= 0.22f || positionX >= 0.78f) { + hsv[1] = positionX <= 0.22f ? AndroidUtilities.lerp(1f, 0f, 1f - positionX / 0.22f) : AndroidUtilities.lerp(1f, 0f, (positionX - 0.78f) / (1f - 0.78f)); + hsv[2] = positionX <= 0.22f ? 1f : AndroidUtilities.lerp(1f, 0f, (positionX - 0.78f) / (1f - 0.78f)); + } else { + hsv[1] = 1f; + hsv[2] = 1f; + } + mColor = Color.HSVToColor(hsv); + onSetColor(mColor, FROM_PICKER); + invalidate(); + } + + public void setColor(int color, boolean updatePosition) { + mColor = color; + Color.colorToHSV(color, hsv); + + if (updatePosition) { + positionX = 1f + hsv[1] * 0.5f - (hsv[2] <= 0.5f ? 1f - (0.78f + (1f - hsv[2]) * (1f - 0.78f)) : 1f - (1f - hsv[2]) * 0.22f); + positionY = hsv[0] / 360f; + } + invalidate(); + } + } + + private final class SlidersPickerView extends LinearLayout { + private SliderCell red; + private SliderCell green; + private SliderCell blue; + + private EditText hexEdit; + + private boolean isInvalidatingColor; + + public SlidersPickerView(Context context) { + super(context); + + setOrientation(VERTICAL); + setPadding(AndroidUtilities.dp(14), 0, AndroidUtilities.dp(14), 0); + + red = new SliderCell(context); + red.bind(ColorSliderView.MODE_RED); + addView(red, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 0, 0, 0, 0, 16)); + + green = new SliderCell(context); + green.bind(ColorSliderView.MODE_GREEN); + addView(green, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 0, 0, 0, 0, 16)); + + blue = new SliderCell(context); + blue.bind(ColorSliderView.MODE_BLUE); + addView(blue, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 0, 0, 0, 0, 16)); + + LinearLayout hexLayout = new LinearLayout(context); + hexLayout.setOrientation(HORIZONTAL); + hexLayout.setGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT); + addView(hexLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 64)); + + TextView hexTitle = new TextView(context); + hexTitle.setTextColor(0x99ffffff); + hexTitle.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + hexTitle.setText(LocaleController.getString(R.string.PaintPaletteSlidersHexColor).toUpperCase()); + hexTitle.setTypeface(AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_ROBOTO_MEDIUM)); + hexLayout.addView(hexTitle, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, 0, 0, 8, 0)); + + hexEdit = new EditTextBoldCursor(context); + hexEdit.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + hexEdit.setBackground(Theme.createRoundRectDrawable(AndroidUtilities.dp(10), 0x19ffffff)); + hexEdit.setPadding(0, 0, 0, 0); + hexEdit.setTextColor(Color.WHITE); + hexEdit.setGravity(Gravity.CENTER); + hexEdit.setSingleLine(); + hexEdit.setImeOptions(EditorInfo.IME_ACTION_DONE); + hexEdit.setImeActionLabel(LocaleController.getString(R.string.Done), EditorInfo.IME_ACTION_DONE); + hexEdit.setTypeface(AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_ROBOTO_MEDIUM)); + hexEdit.addTextChangedListener(new TextWatcher() { + private Pattern pattern = Pattern.compile("^[0-9a-fA-F]*$"); + private CharSequence previous; + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + previous = s.toString(); + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable s) { + if (isInvalidatingColor) { + return; + } + if (previous != null && s != null && !TextUtils.isEmpty(s) && !Objects.equals(previous.toString(), s.toString())) { + String str = s.toString(); + if (str.length() > 8) { + hexEdit.setText(str.substring(2, 8).toUpperCase()); + hexEdit.setSelection(8); + return; + } + + if (!pattern.matcher(s).find()) { + return; + } + int color; + switch (str.length()) { + case 3: + color = (int) Long.parseLong("FF" + str.charAt(0) + str.charAt(0) + str.charAt(1) + str.charAt(1) + str.charAt(2) + str.charAt(2), 16); + break; + case 6: + color = 0xFF000000 + (int) Long.parseLong(str, 16); + break; + case 8: + color = (int) Long.parseLong(str, 16); + break; + default: + color = mColor; + break; + } + + if (color == mColor) { + return; + } + + onSetColor(color, FROM_SLIDER_TEXT); + } + } + }); + hexEdit.setOnFocusChangeListener((v, hasFocus) -> { + if (!hasFocus && TextUtils.isEmpty(hexEdit.getText())) { + hexEdit.setText("0"); + } + }); + hexEdit.setOnEditorActionListener((v, actionId, event) -> { + if (actionId == EditorInfo.IME_ACTION_DONE) { + v.clearFocus(); + AndroidUtilities.hideKeyboard(v); + } + return false; + }); + hexLayout.addView(hexEdit, LayoutHelper.createLinear(72, 36)); + } + + public void invalidateColor() { + isInvalidatingColor = true; + + red.invalidateColor(); + green.invalidateColor(); + blue.invalidateColor(); + + if (!hexEdit.isFocused()) { + int ss = hexEdit.getSelectionStart(), se = hexEdit.getSelectionEnd(); + StringBuilder str = new StringBuilder(Integer.toHexString(mColor)); + while (str.length() < 8) { + str.insert(0, "0"); + } + hexEdit.setText(str.toString().toUpperCase().substring(2)); + hexEdit.setSelection(ss, se); + } + isInvalidatingColor = false; + } + } + + private final class SliderCell extends FrameLayout { + private TextView titleView; + private ColorSliderView sliderView; + private EditText valueView; + + private int mode; + + private boolean isInvalidatingColor; + + public SliderCell(@NonNull Context context) { + super(context); + + titleView = new TextView(context); + titleView.setTextColor(0x99ffffff); + titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + titleView.setTypeface(AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_ROBOTO_MEDIUM)); + addView(titleView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT, 8, 0, 8, 0)); + + sliderView = new ColorSliderView(context); + addView(sliderView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT, 0, 16, 78, 0)); + + valueView = new EditTextBoldCursor(context); + valueView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + valueView.setBackground(Theme.createRoundRectDrawable(AndroidUtilities.dp(10), 0x19ffffff)); + valueView.setPadding(0, 0, 0, 0); + valueView.setTextColor(Color.WHITE); + valueView.setGravity(Gravity.CENTER); + valueView.setSingleLine(); + valueView.setImeOptions(EditorInfo.IME_ACTION_DONE); + valueView.setImeActionLabel(LocaleController.getString(R.string.Done), EditorInfo.IME_ACTION_DONE); + valueView.setInputType(EditorInfo.TYPE_CLASS_NUMBER); + valueView.setTypeface(AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_ROBOTO_MEDIUM)); + valueView.addTextChangedListener(new TextWatcher() { + private CharSequence previous; + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + previous = s.toString(); + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable s) { + if (isInvalidatingColor) { + return; + } + + if (previous != null && s != null && !TextUtils.isEmpty(s) && !Objects.equals(previous.toString(), s.toString())) { + int val = Integer.parseInt(s.toString()); + val = MathUtils.clamp(val, 0, 255); + + int color; + switch (mode) { + default: + case ColorSliderView.MODE_RED: + color = Color.argb(Color.alpha(mColor), val, Color.green(mColor), Color.blue(mColor)); + break; + case ColorSliderView.MODE_GREEN: + color = Color.argb(Color.alpha(mColor), Color.red(mColor), val, Color.blue(mColor)); + break; + case ColorSliderView.MODE_BLUE: + color = Color.argb(Color.alpha(mColor), Color.red(mColor), Color.green(mColor), val); + break; + } + onSetColor(color, FROM_SLIDER_TEXT); + } + } + }); + valueView.setOnFocusChangeListener((v, hasFocus) -> { + if (!hasFocus && TextUtils.isEmpty(valueView.getText())) { + valueView.setText("0"); + } + }); + valueView.setOnEditorActionListener((v, actionId, event) -> { + if (actionId == EditorInfo.IME_ACTION_DONE) { + v.clearFocus(); + AndroidUtilities.hideKeyboard(v); + } + return false; + }); + addView(valueView, LayoutHelper.createFrame(72, 36, Gravity.BOTTOM | Gravity.RIGHT)); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(52), MeasureSpec.EXACTLY)); + } + + public void bind(int mode) { + this.mode = mode; + sliderView.setMode(mode); + switch (mode) { + case ColorSliderView.MODE_RED: + titleView.setText(LocaleController.getString(R.string.PaintPaletteSlidersRed).toUpperCase()); + break; + case ColorSliderView.MODE_GREEN: + titleView.setText(LocaleController.getString(R.string.PaintPaletteSlidersGreen).toUpperCase()); + break; + case ColorSliderView.MODE_BLUE: + titleView.setText(LocaleController.getString(R.string.PaintPaletteSlidersBlue).toUpperCase()); + break; + } + invalidateColor(); + } + + public void invalidateColor() { + isInvalidatingColor = true; + + sliderView.invalidateColor(); + int ss = valueView.getSelectionStart(), se = valueView.getSelectionEnd(); + switch (mode) { + case ColorSliderView.MODE_RED: + valueView.setText(String.valueOf(Color.red(mColor))); + break; + case ColorSliderView.MODE_GREEN: + valueView.setText(String.valueOf(Color.green(mColor))); + break; + case ColorSliderView.MODE_BLUE: + valueView.setText(String.valueOf(Color.blue(mColor))); + break; + } + valueView.setSelection(ss, se); + + isInvalidatingColor = false; + } + } + + private final class AlphaPickerView extends View { + private Paint colorPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private Paint outlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private float alpha; + + public AlphaPickerView(Context context) { + super(context); + outlinePaint.setColor(Color.WHITE); + outlinePaint.setStyle(Paint.Style.FILL_AND_STROKE); + outlinePaint.setStrokeWidth(AndroidUtilities.dp(3)); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + invalidateShader(); + } + + public void setColor(int mColor) { + alpha = Color.alpha(mColor) / (float) 0xFF; + invalidateShader(); + invalidate(); + } + + private void invalidateShader() { + colorPaint.setShader(new LinearGradient(0, 0, getWidth(), 0, new int[]{Color.TRANSPARENT, mColor}, null, Shader.TileMode.CLAMP)); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + getParent().requestDisallowInterceptTouchEvent(true); + case MotionEvent.ACTION_MOVE: + updatePosition(event.getX()); + break; + case MotionEvent.ACTION_UP: + updatePosition(event.getX()); + getParent().requestDisallowInterceptTouchEvent(false); + break; + case MotionEvent.ACTION_CANCEL: + getParent().requestDisallowInterceptTouchEvent(false); + break; + } + return true; + } + + private void updatePosition(float x) { + float rad = AndroidUtilities.dp(6); + float outlineRad = AndroidUtilities.dp(13); + float rad2 = outlineRad - outlinePaint.getStrokeWidth() / 2; + + alpha = MathUtils.clamp((x - rad + rad2) / (getWidth() - rad * 2), 0, 1); + onSetColor(ColorUtils.setAlphaComponent(mColor, (int) (alpha * 0xFF)), FROM_ALPHA); + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + float y = getHeight() / 2f; + float rad = AndroidUtilities.dp(6); + AndroidUtilities.rectTmp.set(rad, y - rad, getWidth() - rad, y + rad); + canvas.save(); + path.rewind(); + path.addRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(16), AndroidUtilities.dp(16), Path.Direction.CW); + canvas.clipPath(path); + PaintColorsListView.drawCheckerboard(canvas, AndroidUtilities.rectTmp, AndroidUtilities.dp(6)); + canvas.restore(); + AndroidUtilities.rectTmp.set(rad, y - rad, getWidth() - rad, y + rad); + canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(16), AndroidUtilities.dp(16), colorPaint); + + float outlineRad = AndroidUtilities.dp(13); + float rad2 = outlineRad - outlinePaint.getStrokeWidth() / 2; + float cx = Math.max(rad + rad2, rad + (getWidth() - rad * 2) * alpha - rad2); + canvas.drawCircle(cx, y, outlineRad, outlinePaint); + PaintColorsListView.drawColorCircle(canvas, cx, y, rad2, ColorUtils.setAlphaComponent(mColor, (int) (alpha * 0xFF))); + } + } + + private final class ColorSliderView extends View { + private Paint colorPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private Paint outlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + private final static int MODE_RED = 0, MODE_GREEN = 1, MODE_BLUE = 2; + private int mode; + + private int filledColor; + + public ColorSliderView(Context context) { + super(context); + outlinePaint.setColor(Color.WHITE); + outlinePaint.setStyle(Paint.Style.FILL_AND_STROKE); + outlinePaint.setStrokeWidth(AndroidUtilities.dp(3)); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + invalidateShader(); + } + + public void setMode(int mode) { + this.mode = mode; + } + + public void invalidateColor() { + filledColor = ColorUtils.setAlphaComponent(mColor, 0xFF); + invalidateShader(); + invalidate(); + } + + private void invalidateShader() { + int from, to; + switch (mode) { + default: + case MODE_RED: + from = Color.argb(0xFF, 0x00, Color.green(mColor), Color.blue(mColor)); + to = Color.argb(0xFF, 0xFF, Color.green(mColor), Color.blue(mColor)); + break; + case MODE_GREEN: + from = Color.argb(0xFF, Color.red(mColor), 0x00, Color.blue(mColor)); + to = Color.argb(0xFF, Color.red(mColor), 0xFF, Color.blue(mColor)); + break; + case MODE_BLUE: + from = Color.argb(0xFF, Color.red(mColor), Color.green(mColor), 0x00); + to = Color.argb(0xFF, Color.red(mColor), Color.green(mColor), 0xFF); + break; + } + colorPaint.setShader(new LinearGradient(0, 0, getWidth(), 0, new int[]{from, to}, null, Shader.TileMode.CLAMP)); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + getParent().requestDisallowInterceptTouchEvent(true); + case MotionEvent.ACTION_MOVE: + updatePosition(event.getX()); + break; + case MotionEvent.ACTION_UP: + updatePosition(event.getX()); + getParent().requestDisallowInterceptTouchEvent(false); + break; + case MotionEvent.ACTION_CANCEL: + getParent().requestDisallowInterceptTouchEvent(false); + break; + } + return true; + } + + private void updatePosition(float x) { + float rad = AndroidUtilities.dp(6); + float outlineRad = AndroidUtilities.dp(13); + float rad2 = outlineRad - outlinePaint.getStrokeWidth() / 2; + + float val = MathUtils.clamp((x - rad + rad2) / (getWidth() - rad * 2), 0, 1); + + int color; + switch (mode) { + default: + case MODE_RED: + color = Color.argb(0xFF, (int) (val * 0xFF), Color.green(mColor), Color.blue(mColor)); + break; + case MODE_GREEN: + color = Color.argb(0xFF, Color.red(mColor), (int) (val * 0xFF), Color.blue(mColor)); + break; + case MODE_BLUE: + color = Color.argb(0xFF, Color.red(mColor), Color.green(mColor), (int) (val * 0xFF)); + break; + } + + onSetColor(ColorUtils.setAlphaComponent(color, Color.alpha(mColor)), FROM_SLIDER); + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + float y = getHeight() / 2f; + float rad = AndroidUtilities.dp(6); + AndroidUtilities.rectTmp.set(rad, y - rad, getWidth() - rad, y + rad); + canvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(16), AndroidUtilities.dp(16), colorPaint); + + float val; + switch (mode) { + default: + case MODE_RED: + val = Color.red(mColor) / (float) 0xFF; + break; + case MODE_GREEN: + val = Color.green(mColor) / (float) 0xFF; + break; + case MODE_BLUE: + val = Color.blue(mColor) / (float) 0xFF; + break; + } + + float outlineRad = AndroidUtilities.dp(13); + float rad2 = outlineRad - outlinePaint.getStrokeWidth() / 2; + float cx = Math.max(rad + rad2, rad + (getWidth() - rad * 2) * val - rad2); + canvas.drawCircle(cx, y, outlineRad, outlinePaint); + PaintColorsListView.drawColorCircle(canvas, cx, y, rad2, filledColor); + } + } + + public interface PipetteDelegate { + void onStartColorPipette(); + void onStopColorPipette(); + ViewGroup getContainerView(); + View getSnapshotDrawingView(); + void onDrawImageOverCanvas(Bitmap bitmap, Canvas canvas); + boolean isPipetteVisible(); + boolean isPipetteAvailable(); + void onColorSelected(int color); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Input.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Input.java index 46f148e98..9955f695c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Input.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Input.java @@ -1,33 +1,70 @@ package org.telegram.ui.Components.Paint; +import static com.google.zxing.common.detector.MathUtils.distance; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.graphics.Matrix; import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import androidx.core.math.MathUtils; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.BotWebViewVibrationEffect; +import org.telegram.ui.Components.CubicBezierInterpolator; +import org.telegram.ui.Components.Size; import java.util.Vector; public class Input { + private final static CubicBezierInterpolator PRESSURE_INTERPOLATOR = new CubicBezierInterpolator(0, 0.5, 0, 1); private RenderView renderView; private boolean beganDrawing; private boolean isFirst; + private long drawingStart; private boolean hasMoved; private boolean clearBuffer; - private Point lastLocation; + private Point lastLocation, lastThickLocation; private double lastRemainder; + private boolean lastAngleSet; private float lastAngle; - + private float lastScale; + private boolean canFill; private Point[] points = new Point[3]; - private int pointsCount; + private int pointsCount, realPointsCount; + private double thicknessSum, thicknessCount; + + private ValueAnimator arrowAnimator; + + private final ShapeDetector detector; + + private void setShapeHelper(Shape shape) { + if (shape != null) { + shape.thickness = renderView.getCurrentWeight(); + if (thicknessSum > 0) { + shape.thickness *= (thicknessSum / thicknessCount); + } + if (shape.getType() == Brush.Shape.SHAPE_TYPE_ARROW) { + shape.arrowTriangleLength *= shape.thickness; + } + } + renderView.getPainting().setHelperShape(shape); + } private Matrix invertMatrix; private float[] tempPoint = new float[2]; - public Input(RenderView render) { - renderView = render; + private long lastVelocityUpdate; + private float velocity; + + public Input(RenderView renderView) { + this.renderView = renderView; + this.detector = new ShapeDetector(renderView.getContext(), this::setShapeHelper); } public void setMatrix(Matrix m) { @@ -35,7 +72,106 @@ public class Input { m.invert(invertMatrix); } + private ValueAnimator fillAnimator; + private void fill(Brush brush, boolean registerUndo, Runnable onDone) { + if (!canFill || lastLocation == null) { + return; + } + + if (brush == null) { + brush = renderView.getCurrentBrush(); + } + + if (brush instanceof Brush.Elliptical || + brush instanceof Brush.Neon) { + brush = new Brush.Radial(); + } + + canFill = false; + renderView.getPainting().clearStroke(); + pointsCount = 0; + realPointsCount = 0; + lastAngleSet = false; + beganDrawing = false; + if (registerUndo) { + renderView.onBeganDrawing(); + } + + Size size = renderView.getPainting().getSize(); + float R = Math.max( + Math.max( + distance((float) lastLocation.x, (float) lastLocation.y, 0, 0), + distance((float) lastLocation.x, (float) lastLocation.y, size.width, 0) + ), + Math.max( + distance((float) lastLocation.x, (float) lastLocation.y, 0, size.height), + distance((float) lastLocation.x, (float) lastLocation.y, size.width, size.height) + ) + ) / 0.84f; + + if (arrowAnimator != null) { + arrowAnimator.cancel(); + arrowAnimator = null; + } + if (fillAnimator != null) { + fillAnimator.cancel(); + fillAnimator = null; + } + final Point point = new Point(lastLocation.x, lastLocation.y, 1); + final Brush finalBrush = brush; + fillAnimator = ValueAnimator.ofFloat(0, 1); + fillAnimator.addUpdateListener(anm -> { + float t = (float) anm.getAnimatedValue(); + Path path = new Path(new Point[] { point }); + int color = finalBrush.isEraser() ? 0xffffffff : renderView.getCurrentColor(); + path.setup(color, t * R, finalBrush); + renderView.getPainting().paintStroke(path, true, true, null); + }); + fillAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + fillAnimator = null; + Path path = new Path(new Point[] { point }); + path.setup(renderView.getCurrentColor(), 1f * R, finalBrush); + int color = finalBrush.isEraser() ? 0xffffffff : renderView.getCurrentColor(); + renderView.getPainting().commitPath(path, color, registerUndo, null); + if (registerUndo) { + renderView.onFinishedDrawing(true); + } + + if (onDone != null) { + onDone.run(); + } + } + }); + fillAnimator.setDuration(450); + fillAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT); + fillAnimator.start(); + + if (registerUndo) { + BotWebViewVibrationEffect.IMPACT_HEAVY.vibrate(); + } + }; + private final Runnable fillWithCurrentBrush = () -> fill(null, true, null); + + public void clear(Runnable onDone) { + Size size = renderView.getPainting().getSize(); + lastLocation = new Point(size.width, 0, 1); + canFill = true; + fill(new Brush.Eraser(), false, onDone); + } + + private boolean ignore; + public void ignoreOnce() { + this.ignore = true; + } + + private Brush switchedBrushByStylusFrom; + public void process(MotionEvent event, float scale) { + if (fillAnimator != null || arrowAnimator != null) { + return; + } int action = event.getActionMasked(); float x = event.getX(); float y = renderView.getHeight() - event.getY(); @@ -44,96 +180,220 @@ public class Input { tempPoint[1] = y; invertMatrix.mapPoints(tempPoint); - Point location = new Point(tempPoint[0], tempPoint[1], 1.0f); + long dt = System.currentTimeMillis() - lastVelocityUpdate; + velocity = MathUtils.clamp(velocity - dt / 125f, 0.6f, 1f); + lastScale = scale; + lastVelocityUpdate = System.currentTimeMillis(); + + boolean stylusToolPressed = false; + float weight = velocity; + if (event.getToolType(event.getActionIndex()) == MotionEvent.TOOL_TYPE_STYLUS) { + weight = Math.max(.1f, PRESSURE_INTERPOLATOR.getInterpolation(event.getPressure())); + stylusToolPressed = (event.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) == MotionEvent.BUTTON_STYLUS_PRIMARY; + } + if (renderView.getCurrentBrush() != null) { + weight = 1 + (weight - 1) * AndroidUtilities.lerp(1, renderView.getCurrentBrush().getSmoothThicknessRate(), MathUtils.clamp(realPointsCount / 16f, 0, 1)); + } + Point location = new Point(tempPoint[0], tempPoint[1], weight); switch (action) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: { + if (ignore) { + return; + } if (!beganDrawing) { beganDrawing = true; hasMoved = false; isFirst = true; lastLocation = location; + drawingStart = System.currentTimeMillis(); points[0] = location; pointsCount = 1; + realPointsCount = 1; + lastAngleSet = false; clearBuffer = true; + canFill = true; + AndroidUtilities.runOnUIThread(fillWithCurrentBrush, ViewConfiguration.getLongPressTimeout()); } else { float distance = location.getDistanceTo(lastLocation); if (distance < AndroidUtilities.dp(5.0f) / scale) { return; } + if (canFill && (distance > AndroidUtilities.dp(6) / scale || pointsCount > 4)) { + canFill = false; + AndroidUtilities.cancelRunOnUIThread(fillWithCurrentBrush); + } if (!hasMoved) { renderView.onBeganDrawing(); hasMoved = true; + + if (stylusToolPressed && renderView.getCurrentBrush() instanceof Brush.Radial) { + switchedBrushByStylusFrom = renderView.getCurrentBrush(); + renderView.selectBrush(Brush.BRUSHES_LIST.get(Brush.BRUSHES_LIST.size() - 1)); + } } points[pointsCount] = location; + if ((System.currentTimeMillis() - drawingStart) > 3000) { + detector.clear(); + renderView.getPainting().setHelperShape(null); + } else if (renderView.getCurrentBrush() instanceof Brush.Radial || renderView.getCurrentBrush() instanceof Brush.Elliptical) { + detector.append(location.x, location.y, distance > AndroidUtilities.dp(6) / scale); + } pointsCount++; + realPointsCount++; if (pointsCount == 3) { - lastAngle = (float) Math.atan2(points[2].y - points[1].y, points[2].x - points[1].x); - smoothenAndPaintPoints(false); + float angle = (float) Math.atan2(points[2].y - points[1].y, points[2].x - points[1].x); + if (!lastAngleSet) { + lastAngle = angle; + lastAngleSet = true; + } else { + float f = MathUtils.clamp(distance / (AndroidUtilities.dp(16) / scale), 0, 1); + if (f > .4f) { + lastAngle = lerpAngle(lastAngle, angle, f); + } + } + smoothenAndPaintPoints(false, renderView.getCurrentBrush().getSmoothThicknessRate()); } lastLocation = location; + if (distance > AndroidUtilities.dp(8) / scale) { + lastThickLocation = location; + } + + velocity = MathUtils.clamp(velocity + dt / 75f, 0.6f, 1); } break; } case MotionEvent.ACTION_UP: { - if (!hasMoved) { - if (renderView.shouldDraw()) { - location.edge = true; - paintPath(new Path(location)); + if (ignore) { + ignore = false; + return; + } + canFill = false; + detector.clear(); + AndroidUtilities.cancelRunOnUIThread(fillWithCurrentBrush); + if (!renderView.getPainting().applyHelperShape()) { + + boolean commit = true; + if (!hasMoved) { + if (renderView.shouldDraw()) { + location.edge = true; + paintPath(new Path(location)); + } + reset(); + } else if (pointsCount > 0) { + smoothenAndPaintPoints(true, renderView.getCurrentBrush().getSmoothThicknessRate()); + + Brush brush = renderView.getCurrentBrush(); + if (brush instanceof Brush.Arrow) { + float angle = lastAngle; + final Point loc = points[pointsCount - 1]; + double z = lastThickLocation == null ? location.z : lastThickLocation.z; + float arrowLength = renderView.getCurrentWeight() * (float) z * 4.5f; + + commit = false; + if (arrowAnimator != null) { + arrowAnimator.cancel(); + } + final float[] lastT = new float[1]; + final boolean[] vibrated = new boolean[1]; + arrowAnimator = ValueAnimator.ofFloat(0, 1); + arrowAnimator.addUpdateListener(anm -> { + float t = (float) anm.getAnimatedValue(); + + paintPath(new Path(new Point[]{ + new Point(loc.x + Math.cos(angle - Math.PI / 4 * 3) * arrowLength * lastT[0], loc.y + Math.sin(angle - Math.PI / 4 * 3.2) * arrowLength * lastT[0], z), + new Point(loc.x + Math.cos(angle - Math.PI / 4 * 3) * arrowLength * t, loc.y + Math.sin(angle - Math.PI / 4 * 3.2) * arrowLength * t, z, true) + })); + paintPath(new Path(new Point[]{ + new Point(loc.x + Math.cos(angle + Math.PI / 4 * 3) * arrowLength * lastT[0], loc.y + Math.sin(angle + Math.PI / 4 * 3.2) * arrowLength * lastT[0], z), + new Point(loc.x + Math.cos(angle + Math.PI / 4 * 3) * arrowLength * t, loc.y + Math.sin(angle + Math.PI / 4 * 3.2) * arrowLength * t, z, true) + })); + + if (!vibrated[0] && t > .4f) { + vibrated[0] = true; + BotWebViewVibrationEffect.SELECTION_CHANGE.vibrate(); + } + + lastT[0] = t; + }); + arrowAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (!renderView.getCurrentBrush().isEraser() || renderView.getUndoStore().canUndo()) { + renderView.getPainting().commitPath(null, renderView.getCurrentColor()); + } + arrowAnimator = null; + } + }); + arrowAnimator.setDuration(240); + arrowAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT); + arrowAnimator.start(); + } } - reset(); - } else if (pointsCount > 0) { - smoothenAndPaintPoints(true); - Brush brush = renderView.getCurrentBrush(); - if (brush instanceof Brush.Arrow) { - float arrowLength = renderView.getCurrentWeight() * 4.5f; - float angle = lastAngle; - location = points[pointsCount - 1]; - - Point tip = new Point(location.x, location.y, 0.8f); - Point leftTip = new Point(location.x + Math.cos(angle - Math.PI / 4 * 3) * arrowLength, location.y + Math.sin(angle - Math.PI / 4 * 3.2) * arrowLength, 1.0); - leftTip.edge = true; - Path left = new Path(new Point[]{tip, leftTip}); - paintPath(left); - - Point rightTip = new Point(location.x + Math.cos(angle + Math.PI / 4 * 3) * arrowLength, location.y + Math.sin(angle + Math.PI / 4 * 3.2) * arrowLength, 1.0); - rightTip.edge = true; - Path right = new Path(new Point[]{tip, rightTip}); - paintPath(right); + if (commit && (!renderView.getCurrentBrush().isEraser() || renderView.getUndoStore().canUndo())) { + renderView.getPainting().commitPath(null, renderView.getCurrentColor(), true, () -> { + if (switchedBrushByStylusFrom != null) { + renderView.selectBrush(switchedBrushByStylusFrom); + switchedBrushByStylusFrom = null; + } + }); } } pointsCount = 0; - - renderView.getPainting().commitStroke(renderView.getCurrentColor()); + realPointsCount = 0; + lastAngleSet = false; beganDrawing = false; + thicknessCount = thicknessSum = 0; renderView.onFinishedDrawing(hasMoved); break; } case MotionEvent.ACTION_CANCEL: { + if (ignore) { + ignore = false; + return; + } + canFill = false; + detector.clear(); + renderView.getPainting().setHelperShape(null); + AndroidUtilities.cancelRunOnUIThread(fillWithCurrentBrush); renderView.getPainting().clearStroke(); pointsCount = 0; + realPointsCount = 0; + lastAngleSet = false; beganDrawing = false; + thicknessCount = thicknessSum = 0; + + if (switchedBrushByStylusFrom != null) { + renderView.selectBrush(switchedBrushByStylusFrom); + switchedBrushByStylusFrom = null; + } break; } } } + private float lerpAngle(float angleA, float angleB, float t) { +// double da = (angleB - angleA) % (Math.PI * 2); +// return (float) (angleA + (2 * da % (Math.PI * 2) - da) * t); + return (float) Math.atan2((1-t)*Math.sin(angleA) + t*Math.sin(angleB), (1-t)*Math.cos(angleA) + t*Math.cos(angleB)); + } + private void reset() { pointsCount = 0; } - private void smoothenAndPaintPoints(boolean ended) { + private void smoothenAndPaintPoints(boolean ended, float smoothThickness) { if (pointsCount > 2) { Vector points = new Vector<>(); @@ -156,12 +416,14 @@ public class Input { float step = 1.0f / (float) numberOfSegments; for (int j = 0; j < numberOfSegments; j++) { - Point point = smoothPoint(midPoint1, midPoint2, prev1, t); + Point point = smoothPoint(midPoint1, midPoint2, prev1, t, smoothThickness); if (isFirst) { point.edge = true; isFirst = false; } points.add(point); + thicknessSum += point.z; + thicknessCount++; t += step; } @@ -191,12 +453,20 @@ public class Input { } } - private Point smoothPoint(Point midPoint1, Point midPoint2, Point prev1, float t) { + private Point smoothPoint(Point midPoint1, Point midPoint2, Point prev1, float t, float smoothThickness) { double a1 = Math.pow(1.0f - t, 2); double a2 = (2.0f * (1.0f - t) * t); double a3 = t * t; - return new Point(midPoint1.x * a1 + prev1.x * a2 + midPoint2.x * a3, midPoint1.y * a1 + prev1.y * a2 + midPoint2.y * a3, 1.0f); + float t_squared = t * t; + float minus_t_squard = (1 - t) * (1 - t); + + double x = midPoint1.x * minus_t_squard + 2 * prev1.x * t * (1 - t) + midPoint2.x * t_squared; + double y = midPoint1.y * minus_t_squard + 2 * prev1.y * t * (1 - t) + midPoint2.y * t_squared; + double z = midPoint1.z * a1 + prev1.z * a2 + midPoint2.z * a3; + z = 1 + (z - 1) * AndroidUtilities.lerp(1, smoothThickness, MathUtils.clamp(realPointsCount / 16f, 0, 1)); + + return new Point(x, y, z); } private void paintPath(final Path path) { @@ -208,7 +478,7 @@ public class Input { path.remainder = lastRemainder; - renderView.getPainting().paintStroke(path, clearBuffer, () -> AndroidUtilities.runOnUIThread(() -> lastRemainder = path.remainder)); + renderView.getPainting().paintStroke(path, clearBuffer, false, () -> AndroidUtilities.runOnUIThread(() -> lastRemainder = path.remainder)); clearBuffer = false; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/PaintTypeface.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/PaintTypeface.java new file mode 100644 index 000000000..f97041e3d --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/PaintTypeface.java @@ -0,0 +1,283 @@ +package org.telegram.ui.Components.Paint; + +import android.graphics.Paint; +import android.graphics.Typeface; +import android.graphics.fonts.Font; +import android.graphics.fonts.SystemFonts; +import android.os.Build; +import android.text.TextUtils; + +import androidx.annotation.RequiresApi; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.Utilities; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +public class PaintTypeface { + private final static boolean SYSTEM_FONTS_ENABLED = true; + + public static final PaintTypeface ROBOTO_MEDIUM = new PaintTypeface("roboto", "PhotoEditorTypefaceRoboto", AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_ROBOTO_MEDIUM)); + public static final PaintTypeface ROBOTO_ITALIC = new PaintTypeface("italic", "PhotoEditorTypefaceItalic", AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_ROBOTO_MEDIUM_ITALIC)); + public static final PaintTypeface ROBOTO_SERIF = new PaintTypeface("serif", "PhotoEditorTypefaceSerif", Typeface.create("serif", Typeface.BOLD)); + public static final PaintTypeface ROBOTO_MONO = new PaintTypeface ("mono", "PhotoEditorTypefaceMono", AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_ROBOTO_MONO)); + public static final PaintTypeface MW_BOLD = new PaintTypeface("mw_bold", "PhotoEditorTypefaceMerriweather", AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_MERRIWEATHER_BOLD)); + public static final PaintTypeface COURIER_NEW_BOLD = new PaintTypeface("courier_new_bold", "PhotoEditorTypefaceCourierNew", AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_COURIER_NEW_BOLD)); + + private final static List BUILT_IN_FONTS = Arrays.asList(ROBOTO_MEDIUM, ROBOTO_ITALIC, ROBOTO_SERIF, ROBOTO_MONO, MW_BOLD, COURIER_NEW_BOLD); + + private static final List preferable = Arrays.asList( + "Google Sans", + "Dancing Script", + "Carrois Gothic SC", + "Cutive Mono", + "Droid Sans Mono", + "Coming Soon" + ); + + private final String key; + private final String nameKey; + private final String name; + private final Typeface typeface; + private final Font font; + private Paint paint; + + PaintTypeface(String key, String nameKey, Typeface typeface) { + this.key = key; + this.nameKey = nameKey; + this.name = null; + this.typeface = typeface; + this.font = null; + } + + @RequiresApi(api = Build.VERSION_CODES.Q) + PaintTypeface(Font font, String name) { + this.key = name; + this.name = name; + this.nameKey = null; + this.typeface = Typeface.createFromFile(font.getFile()); + this.font = font; + } + + public boolean supports(String text) { + if (this.font == null) { + // TODO + return true; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (paint == null) { + paint = new Paint(); + paint.setTypeface(this.typeface); + } + return paint.hasGlyph(text); + } + // TODO + return true; + } + + public String getKey() { + return key; + } + + public Typeface getTypeface() { + return typeface; + } + + public String getName() { + if (name != null) { + return name; + } + return LocaleController.getString(nameKey); + } + + private static List typefaces; + public static List get() { + if (typefaces == null) { + typefaces = new ArrayList<>(BUILT_IN_FONTS); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && SYSTEM_FONTS_ENABLED) { + Set fonts = SystemFonts.getAvailableFonts(); + Iterator i = fonts.iterator(); + HashMap families = new HashMap<>(); + while (i.hasNext()) { + Font font = i.next(); + if (font.getFile().getName().contains("Noto")) + continue; + FontData data = parseFont(font); + if (data != null) { + Family family = families.get(data.family); + if (family == null) { + family = new Family(); + families.put(family.family = data.family, family); + } + family.fonts.add(data); + } + } + +// if (BuildVars.DEBUG_PRIVATE_VERSION) { +// for (Family family : families.values()) { +// if (family != null) { +// FontData regular = family.getRegular(); +// typefaces.add(new PaintTypeface(regular.font, regular.getName())); +// } +// } +// } else { + for (String familyName : preferable) { + Family family = families.get(familyName); + if (family != null) { + FontData regular = family.getRegular(); + typefaces.add(new PaintTypeface(regular.font, regular.getName())); + } + } +// } + } + } + return typefaces; + } + + public static boolean fetched(Runnable runnable) { + if (typefaces != null || runnable == null) { + return true; + } + Utilities.themeQueue.postRunnable(() -> { + get(); + AndroidUtilities.runOnUIThread(runnable); + }); + return false; + } + + static class Family { + String family; + ArrayList fonts = new ArrayList<>(); + + public FontData getRegular() { + FontData regular = null; + for (int j = 0; j < fonts.size(); ++j) { + if ("Regular".equalsIgnoreCase(fonts.get(j).subfamily)) { + regular = fonts.get(j); + break; + } + } + if (regular == null && !fonts.isEmpty()) { + regular = fonts.get(0); + } + return regular; + } + } + + static class FontData { + Font font; + String family; + String subfamily; + + public String getName() { + if ("Regular".equals(subfamily) || TextUtils.isEmpty(subfamily)) { + return family; + } + return family + " " + subfamily; + } + } + + private static class NameRecord { + final int platformID; + final int encodingID; + final int languageID; + final int nameID; + final int nameLength; + final int stringOffset; + + public NameRecord(RandomAccessFile reader) throws IOException { + platformID = reader.readUnsignedShort(); + encodingID = reader.readUnsignedShort(); + languageID = reader.readUnsignedShort(); + nameID = reader.readUnsignedShort(); + nameLength = reader.readUnsignedShort(); + stringOffset = reader.readUnsignedShort(); + } + + public String read(RandomAccessFile reader, int offset) throws IOException { + reader.seek(offset + stringOffset); + byte[] bytes = new byte[ nameLength ]; + reader.read(bytes); + Charset charset; + if (encodingID == 1) { + charset = StandardCharsets.UTF_16BE; + } else { + charset = StandardCharsets.UTF_8; + } + return new String(bytes, charset); + } + } + + private static String parseString(RandomAccessFile reader, int offset, NameRecord nameRecord) throws IOException { + if (nameRecord == null) { + return null; + } + return nameRecord.read(reader, offset); + } + + @RequiresApi(api = Build.VERSION_CODES.Q) + public static FontData parseFont(Font font) { + if (font == null) { + return null; + } + final File file = font.getFile(); + if (file == null) { + return null; + } + RandomAccessFile reader = null; + try { + reader = new RandomAccessFile(file, "r"); + final int version = reader.readInt(); + if (version != 0x00010000 && version != 0x4F54544F) { + return null; + } + final int numTables = reader.readUnsignedShort(); + reader.skipBytes(2 + 2 + 2); + for (int i = 0; i < numTables; ++i) { + int tag = reader.readInt(); + reader.skipBytes(4); + int offset = reader.readInt(); + int length = reader.readInt(); + + if (tag == 0x6E616D65) { + reader.seek(offset + 2); + final int count = reader.readUnsignedShort(); + final int storageOffset = reader.readUnsignedShort(); + + HashMap records = new HashMap<>(); + for (int j = 0; j < count; ++j) { + NameRecord record = new NameRecord(reader); + records.put(record.nameID, record); + } + + FontData data = new FontData(); + data.font = font; + data.family = parseString(reader, offset + storageOffset, records.get(1)); + data.subfamily = parseString(reader, offset + storageOffset, records.get(2)); + return data; + } + } + } catch (Exception e) { + FileLog.e(e); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (Exception e2) {} + } + } + return null; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Painting.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Painting.java index e15b1a4a5..d3197180f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Painting.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Painting.java @@ -1,16 +1,28 @@ package org.telegram.ui.Components.Paint; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Matrix; +import android.graphics.Paint; import android.graphics.RectF; import android.opengl.GLES20; +import androidx.core.graphics.ColorUtils; + +import org.telegram.messenger.BotWebViewVibrationEffect; import org.telegram.messenger.DispatchQueue; +import org.telegram.messenger.Utilities; +import org.telegram.ui.Components.CubicBezierInterpolator; import org.telegram.ui.Components.Size; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; +import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -37,17 +49,24 @@ public class Painting { private PaintingDelegate delegate; private Path activePath; + private Shape activeShape; + private Shape helperShape; private RenderState renderState; private RenderView renderView; private Size size; private RectF activeStrokeBounds; private Brush brush; - private Texture brushTexture; + private HashMap brushTextures = new HashMap<>(); private Texture bitmapTexture; + private Texture bluredTexture; + private Bitmap imageBitmap; + private int imageBitmapRotation; + private Bitmap bluredBitmap; private ByteBuffer vertexBuffer; private ByteBuffer textureBuffer; private int reusableFramebuffer; private int paintTexture; + private int helperTexture; private Map shaders; private int suppressChangesCounter; private int[] buffers = new int[1]; @@ -59,10 +78,12 @@ public class Painting { private float[] projection; private float[] renderProjection; - public Painting(Size sz) { + public Painting(Size sz, Bitmap originalBitmap, int originalRotation) { renderState = new RenderState(); size = sz; + imageBitmap = originalBitmap; + imageBitmapRotation = originalRotation; dataBuffer = ByteBuffer.allocateDirect((int) size.width * (int) size.height * 4); @@ -133,176 +154,492 @@ public class Painting { bitmapTexture = new Texture(bitmap); } - public void paintStroke(final Path path, final boolean clearBuffer, final Runnable action) { + private boolean helperShown; + private float helperAlpha, helperApplyAlpha; + private ValueAnimator helperAnimator, helperApplyAnimator; + + public void setHelperShape(Shape shape) { + if (helperApplyAnimator != null) { + return; + } renderView.performInContext(() -> { - activePath = path; - - RectF bounds = null; - - GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, getReusableFramebuffer()); - GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, getPaintTexture(), 0); - - Utils.HasGLError(); - - int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); - if (status == GLES20.GL_FRAMEBUFFER_COMPLETE) { - GLES20.glViewport(0, 0, (int) size.width, (int) size.height); - - if (clearBuffer) { - GLES20.glClearColor(0, 0, 0, 0); - GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); - } - - if (shaders == null) { - return; - } - Shader shader = shaders.get(brush.isLightSaber() ? "brushLight" : "brush"); - if (shader == null) { - return; - } - - GLES20.glUseProgram(shader.program); - if (brushTexture == null) { - brushTexture = new Texture(brush.getStamp()); - } - GLES20.glActiveTexture(GLES20.GL_TEXTURE0); - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, brushTexture.texture()); - GLES20.glUniformMatrix4fv(shader.getUniform("mvpMatrix"), 1, false, FloatBuffer.wrap(projection)); - GLES20.glUniform1i(shader.getUniform("texture"), 0); - - renderState.viewportScale = renderView.getScaleX(); - bounds = Render.RenderPath(path, renderState); + if (shape != null && helperTexture == 0) { + helperTexture = Texture.generateTexture(size); } - GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + if (helperShown != (shape != null)) { + helperShown = shape != null; + if (helperAnimator != null) { + helperAnimator.cancel(); + helperAnimator = null; + } + helperAnimator = ValueAnimator.ofFloat(helperAlpha, helperShown ? 1f : 0f); + helperAnimator.addUpdateListener(anm -> { + renderView.performInContext(() -> { + helperAlpha = (float) anm.getAnimatedValue(); + if (delegate != null) { + delegate.contentChanged(); + } + }); + }); + helperAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + helperAnimator = null; + renderView.performInContext(() -> { + if (delegate != null) { + delegate.contentChanged(); + } + }); + } + }); + helperAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT); + helperAnimator.start(); + + helperShape = shape; + if (delegate != null) { + delegate.contentChanged(); + } + + if (helperShown) { + BotWebViewVibrationEffect.SELECTION_CHANGE.vibrate(); + } + } else if (shape != helperShape) { + helperShape = shape; + if (delegate != null) { + delegate.contentChanged(); + } + } + }); + } + + public boolean applyHelperShape() { + if (helperShape == null || !helperShown || helperTexture == 0) { + return false; + } + + if (helperApplyAnimator != null) { + helperApplyAnimator.cancel(); + } + helperApplyAnimator = ValueAnimator.ofFloat(0, 1); + helperApplyAnimator.addUpdateListener(anm -> { + renderView.performInContext(() -> { + helperApplyAlpha = (float) anm.getAnimatedValue(); + if (delegate != null) { + delegate.contentChanged(); + } + }); + }); + helperApplyAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + renderView.performInContext(() -> { + if (helperShape == null) { + helperApplyAnimator = null; + return; + } + + int color = renderView.getCurrentColor(); + paintStrokeInternal(activePath, false, false); + Slice pathSlice = commitPathInternal(activePath, color, new RectF(activeStrokeBounds)); + clearStrokeInternal(); + + Shape shape = helperShape; + shape.getBounds(activeStrokeBounds = new RectF()); + Slice shapeSlice = commitShapeInternal(shape, color, new RectF(activeStrokeBounds)); + + restoreSliceInternal(shapeSlice, false); + restoreSliceInternal(pathSlice, false); + + commitShapeInternal(shape, color, null); + + helperShape = null; + helperApplyAlpha = 0; + helperApplyAnimator = null; + }); + } + }); + helperApplyAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT); + helperApplyAnimator.setDuration(350); + helperApplyAnimator.start(); + + BotWebViewVibrationEffect.IMPACT_RIGID.vibrate(); + return true; + } + + public void paintShape(final Shape shape, Runnable action) { + if (shape == null) { + return; + } + renderView.performInContext(() -> { + activeShape = shape; + + if (activeStrokeBounds == null) { + activeStrokeBounds = new RectF(); + } + activeShape.getBounds(activeStrokeBounds); if (delegate != null) { delegate.contentChanged(); } - if (activeStrokeBounds != null) { - activeStrokeBounds.union(bounds); - } else { - activeStrokeBounds = bounds; - } - if (action != null) { action.run(); } }); } - public void commitStroke(final int color) { + public void paintStroke(final Path path, final boolean clearBuffer, final boolean clearAll, final Runnable action) { + if (helperApplyAnimator != null) { + return; + } renderView.performInContext(() -> { + paintStrokeInternal(path, clearBuffer, clearAll); - registerUndo(activeStrokeBounds); - - beginSuppressingChanges(); - - GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, getReusableFramebuffer()); - GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, getTexture(), 0); - - GLES20.glViewport(0, 0, (int) size.width, (int) size.height); - - Shader shader = shaders.get(brush.isLightSaber() ? "compositeWithMaskLight" : "compositeWithMask"); - - GLES20.glUseProgram(shader.program); - - GLES20.glUniformMatrix4fv(shader.getUniform("mvpMatrix"), 1, false, FloatBuffer.wrap(projection)); - GLES20.glUniform1i(shader.getUniform("texture"), 0); - GLES20.glUniform1i(shader.getUniform("mask"), 1); - Shader.SetColorUniform(shader.getUniform("color"), color); - - GLES20.glActiveTexture(GLES20.GL_TEXTURE0); - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTexture()); - GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); - - GLES20.glActiveTexture(GLES20.GL_TEXTURE1); - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getPaintTexture()); - - GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ZERO); - - GLES20.glVertexAttribPointer(0, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer); - GLES20.glEnableVertexAttribArray(0); - GLES20.glVertexAttribPointer(1, 2, GLES20.GL_FLOAT, false, 8, textureBuffer); - GLES20.glEnableVertexAttribArray(1); - - GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); - - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTexture()); - GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); - - GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); - - if (!isSuppressingChanges() && delegate != null) { - delegate.contentChanged(); + if (action != null) { + action.run(); } - - endSuppressingChanges(); - - renderState.reset(); - - activeStrokeBounds = null; - activePath = null; }); } - public void clearStroke() { - renderView.performInContext(() -> { - GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, getReusableFramebuffer()); - GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, getPaintTexture(), 0); + private void paintStrokeInternal(final Path path, final boolean clearBuffer, final boolean clearAll) { + activePath = path; + if (path == null) { + return; + } - Utils.HasGLError(); + RectF bounds = null; - int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); - if (status == GLES20.GL_FRAMEBUFFER_COMPLETE) { - GLES20.glViewport(0, 0, (int) size.width, (int) size.height); + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, getReusableFramebuffer()); + GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, getPaintTexture(), 0); + + Utils.HasGLError(); + + int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); + if (status == GLES20.GL_FRAMEBUFFER_COMPLETE) { + GLES20.glViewport(0, 0, (int) size.width, (int) size.height); + + if (clearBuffer) { GLES20.glClearColor(0, 0, 0, 0); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); } - GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); - - if (delegate != null) { - delegate.contentChanged(); + if (shaders == null) { + return; } - renderState.reset(); + + Brush brush = path.getBrush(); + Shader shader = shaders.get(brush.getShaderName(Brush.PAINT_TYPE_BRUSH)); + if (shader == null) { + return; + } + + GLES20.glUseProgram(shader.program); + Texture brushTexture = brushTextures.get(brush.getStampResId()); + if (brushTexture == null) { + brushTexture = new Texture(brush.getStamp()); + brushTextures.put(brush.getStampResId(), brushTexture); + } + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, brushTexture.texture()); + GLES20.glUniformMatrix4fv(shader.getUniform("mvpMatrix"), 1, false, FloatBuffer.wrap(projection)); + GLES20.glUniform1i(shader.getUniform("texture"), 0); + + if (!clearAll) { + renderState.viewportScale = renderView.getScaleX(); + } else { + renderState.viewportScale = 1f; + } + bounds = Render.RenderPath(path, renderState, clearAll); + } + + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + + if (delegate != null) { + delegate.contentChanged(); + } + + if (activeStrokeBounds != null) { + activeStrokeBounds.union(bounds); + } else { + activeStrokeBounds = bounds; + } + } + + public void commitShape(Shape shape, final int color) { + if (shape == null || shaders == null) { + return; + } + renderView.performInContext(() -> { + commitShapeInternal(shape, color, activeStrokeBounds); activeStrokeBounds = null; - activePath = null; }); } - private void registerUndo(RectF rect) { - if (rect == null) { + private Slice commitShapeInternal(Shape shape, int color, RectF bounds) { + Slice undoSlice = registerUndo(bounds); + + beginSuppressingChanges(); + + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, getReusableFramebuffer()); + GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, getTexture(), 0); + + GLES20.glViewport(0, 0, (int) size.width, (int) size.height); + + Brush brush = shape.brush; + if (brush == null) { + brush = this.brush; + } + Shader shader = shaders.get(brush.getShaderName(Brush.PAINT_TYPE_COMPOSITE)); + if (shader == null) { + return null; + } + + GLES20.glUseProgram(shader.program); + + GLES20.glUniformMatrix4fv(shader.getUniform("mvpMatrix"), 1, false, FloatBuffer.wrap(projection)); + GLES20.glUniform1i(shader.getUniform("texture"), 0); + GLES20.glUniform1i(shader.getUniform("mask"), 1); + Shader.SetColorUniform(shader.getUniform("color"), color); + + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTexture()); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); + + GLES20.glActiveTexture(GLES20.GL_TEXTURE1); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getPaintTexture()); + + if (brush instanceof Brush.Blurer && bluredTexture != null) { + GLES20.glUniform1i(shader.getUniform("blured"), 2); + GLES20.glActiveTexture(GLES20.GL_TEXTURE2); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, bluredTexture.texture()); + } + + if (brush instanceof Brush.Shape) { + GLES20.glUniform1i(shader.getUniform("type"), shape.getType()); + GLES20.glUniform2f(shader.getUniform("resolution"), size.width, size.height); + GLES20.glUniform2f(shader.getUniform("center"), shape.centerX, shape.centerY); + GLES20.glUniform2f(shader.getUniform("radius"), shape.radiusX, shape.radiusY); + GLES20.glUniform1f(shader.getUniform("thickness"), shape.thickness); + GLES20.glUniform1f(shader.getUniform("rounding"), shape.rounding); + GLES20.glUniform2f(shader.getUniform("middle"), shape.middleX, shape.middleY); + GLES20.glUniform1f(shader.getUniform("rotation"), shape.rotation); + GLES20.glUniform1i(shader.getUniform("fill"), shape.fill ? 1 : 0); + GLES20.glUniform1f(shader.getUniform("arrowTriangleLength"), shape.arrowTriangleLength); + GLES20.glUniform1i(shader.getUniform("composite"), 1); + GLES20.glUniform1i(shader.getUniform("clear"), 0); + } + + GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ZERO); + + GLES20.glVertexAttribPointer(0, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer); + GLES20.glEnableVertexAttribArray(0); + GLES20.glVertexAttribPointer(1, 2, GLES20.GL_FLOAT, false, 8, textureBuffer); + GLES20.glEnableVertexAttribArray(1); + + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTexture()); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); + + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + + if (delegate != null && !isSuppressingChanges()) { + delegate.contentChanged(); + } + + endSuppressingChanges(); + + renderState.reset(); + + helperApplyAlpha = 0f; + helperShown = false; + helperAlpha = 0f; + helperShape = null; + + activePath = null; + activeShape = null; + + return undoSlice; + } + + public void commitPath(final Path path, final int color) { + commitPath(path, color, true, null); + } + + public void commitPath(final Path path, final int color, final boolean registerUndo, Runnable action) { + if (shaders == null || brush == null) { return; } + renderView.performInContext(() -> { + commitPathInternal(path, color, registerUndo ? activeStrokeBounds : null); + + if (registerUndo) { + activeStrokeBounds = null; + } + + if (action != null) { + action.run(); + } + }); + } + + private Slice commitPathInternal(final Path path, final int color, RectF bounds) { + Slice undoSlice = registerUndo(bounds); + + beginSuppressingChanges(); + + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, getReusableFramebuffer()); + GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, getTexture(), 0); + + GLES20.glViewport(0, 0, (int) size.width, (int) size.height); + + Brush brush = this.brush; + if (path != null) { + brush = path.getBrush(); + } + + Shader shader = shaders.get(brush.getShaderName(Brush.PAINT_TYPE_COMPOSITE)); + if (shader == null) { + return null; + } + + GLES20.glUseProgram(shader.program); + + GLES20.glUniformMatrix4fv(shader.getUniform("mvpMatrix"), 1, false, FloatBuffer.wrap(projection)); + GLES20.glUniform1i(shader.getUniform("texture"), 0); + GLES20.glUniform1i(shader.getUniform("mask"), 1); + Shader.SetColorUniform(shader.getUniform("color"), ColorUtils.setAlphaComponent(color, (int) (Color.alpha(color) * brush.getOverrideAlpha()))); + + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTexture()); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); + + GLES20.glActiveTexture(GLES20.GL_TEXTURE1); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getPaintTexture()); + + if (brush instanceof Brush.Blurer && bluredTexture != null) { + GLES20.glUniform1i(shader.getUniform("blured"), 2); + GLES20.glActiveTexture(GLES20.GL_TEXTURE2); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, bluredTexture.texture()); + } + + GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ZERO); + + GLES20.glVertexAttribPointer(0, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer); + GLES20.glEnableVertexAttribArray(0); + GLES20.glVertexAttribPointer(1, 2, GLES20.GL_FLOAT, false, 8, textureBuffer); + GLES20.glEnableVertexAttribArray(1); + + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTexture()); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); + + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + + if (!isSuppressingChanges() && delegate != null) { + delegate.contentChanged(); + } + + endSuppressingChanges(); + + renderState.reset(); + + activePath = null; + activeShape = null; + + return undoSlice; + } + + public void clearStroke() { + clearStroke(null); + } + + public void clearStroke(Runnable action) { + renderView.performInContext(() -> { + clearStrokeInternal(); + + if (action != null) { + action.run(); + } + }); + } + + public void clearShape() { + renderView.performInContext(() -> { + activeShape = null; + if (delegate != null) { + delegate.contentChanged(); + } + }); + } + + private void clearStrokeInternal() { + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, getReusableFramebuffer()); + GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, getPaintTexture(), 0); + + Utils.HasGLError(); + + int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); + if (status == GLES20.GL_FRAMEBUFFER_COMPLETE) { + GLES20.glViewport(0, 0, (int) size.width, (int) size.height); + GLES20.glClearColor(0, 0, 0, 0); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + } + + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + + if (delegate != null) { + delegate.contentChanged(); + } + renderState.reset(); + activeStrokeBounds = null; + activePath = null; + + helperApplyAlpha = 0f; + } + + private Slice registerUndo(RectF rect) { + if (rect == null) { + return null; + } boolean intersect = rect.setIntersect(rect, getBounds()); if (!intersect) { - return; + return null; } - PaintingData paintingData = getPaintingData(rect, true); - ByteBuffer data = paintingData.data; - - final Slice slice = new Slice(data, rect, delegate.requestDispatchQueue()); + final Slice slice = new Slice(getPaintingData(rect, true).data, rect, delegate.requestDispatchQueue()); delegate.requestUndoStore().registerUndo(UUID.randomUUID(), () -> restoreSlice(slice)); + + return slice; } private void restoreSlice(final Slice slice) { renderView.performInContext(() -> { - ByteBuffer buffer = slice.getData(); - - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTexture()); - GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, slice.getX(), slice.getY(), slice.getWidth(), slice.getHeight(), GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buffer); - if (!isSuppressingChanges() && delegate != null) { - delegate.contentChanged(); - } - - slice.cleanResources(); + restoreSliceInternal(slice, true); }); } + private void restoreSliceInternal(final Slice slice, boolean forget) { + if (slice == null) { + return; + } + + ByteBuffer buffer = slice.getData(); + + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTexture()); + GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, slice.getX(), slice.getY(), slice.getWidth(), slice.getHeight(), GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buffer); + if (!isSuppressingChanges() && delegate != null) { + delegate.contentChanged(); + } + + if (forget) { + slice.cleanResources(); + } + } + public void setRenderProjection(float[] proj) { renderProjection = proj; } @@ -313,14 +650,31 @@ public class Painting { } if (activePath != null) { - render(getPaintTexture(), activePath.getColor()); + renderBlitPath(getPaintTexture(), activePath, 1f - .5f * helperAlpha - .5f * helperApplyAlpha); + } else if (activeShape != null) { + renderBlitShape(getTexture(), getPaintTexture(), activeShape, 1f); } else { - renderBlit(); + renderBlit(getTexture(), 1f); + } + + if (helperTexture != 0 && helperShape != null && helperAlpha > 0) { + renderBlitShape(helperTexture, getPaintTexture(), helperShape, .5f * helperAlpha + .5f * helperApplyAlpha); } } - private void render(int mask, int color) { - Shader shader = shaders.get(brush.isLightSaber() ? "blitWithMaskLight" : "blitWithMask"); + private void renderBlitShape(int toTexture, int mask, Shape shape, float alpha) { + if (shape == null) { + return; + } + Brush brush = this.brush; + if (shape.brush != null && toTexture == helperTexture) { + brush = shape.brush; + } + if (brush == null || renderView == null) { + return; + } + + Shader shader = shaders.get(brush.getShaderName(Brush.PAINT_TYPE_BLIT)); if (shader == null) { return; } @@ -330,14 +684,31 @@ public class Painting { GLES20.glUniformMatrix4fv(shader.getUniform("mvpMatrix"), 1, false, FloatBuffer.wrap(renderProjection)); GLES20.glUniform1i(shader.getUniform("texture"), 0); GLES20.glUniform1i(shader.getUniform("mask"), 1); + int color = renderView.getCurrentColor(); + color = ColorUtils.setAlphaComponent(color, (int) (Color.alpha(color) * alpha)); Shader.SetColorUniform(shader.getUniform("color"), color); GLES20.glActiveTexture(GLES20.GL_TEXTURE0); - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTexture()); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, toTexture); GLES20.glActiveTexture(GLES20.GL_TEXTURE1); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mask); + if (brush instanceof Brush.Shape) { + GLES20.glUniform1i(shader.getUniform("type"), ((Brush.Shape) brush).getShapeShaderType()); + GLES20.glUniform2f(shader.getUniform("resolution"), size.width, size.height); + GLES20.glUniform2f(shader.getUniform("center"), shape.centerX, shape.centerY); + GLES20.glUniform2f(shader.getUniform("radius"), shape.radiusX, shape.radiusY); + GLES20.glUniform1f(shader.getUniform("thickness"), shape.thickness); + GLES20.glUniform1f(shader.getUniform("rounding"), shape.rounding); + GLES20.glUniform2f(shader.getUniform("middle"), shape.middleX, shape.middleY); + GLES20.glUniform1f(shader.getUniform("rotation"), shape.rotation); + GLES20.glUniform1i(shader.getUniform("fill"), shape.fill ? 1 : 0); + GLES20.glUniform1f(shader.getUniform("arrowTriangleLength"), shape.arrowTriangleLength); + GLES20.glUniform1i(shader.getUniform("composite"), 0); + GLES20.glUniform1i(shader.getUniform("clear"), shape == helperShape ? 1 : 0); + } + GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA); GLES20.glVertexAttribPointer(0, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer); @@ -350,8 +721,16 @@ public class Painting { Utils.HasGLError(); } - private void renderBlit() { - Shader shader = shaders.get("blit"); + private void renderBlitPath(int mask, Path path, float alpha) { + if (path == null) { + return; + } + Brush brush = path.getBrush(); + if (brush == null) { + brush = this.brush; + } + + Shader shader = shaders.get(brush.getShaderName(Brush.PAINT_TYPE_BLIT)); if (shader == null) { return; } @@ -360,10 +739,50 @@ public class Painting { GLES20.glUniformMatrix4fv(shader.getUniform("mvpMatrix"), 1, false, FloatBuffer.wrap(renderProjection)); GLES20.glUniform1i(shader.getUniform("texture"), 0); + GLES20.glUniform1i(shader.getUniform("mask"), 1); + int color = path.getColor(); + color = ColorUtils.setAlphaComponent(color, (int) (Color.alpha(color) * brush.getOverrideAlpha() * alpha)); + Shader.SetColorUniform(shader.getUniform("color"), color); GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTexture()); + GLES20.glActiveTexture(GLES20.GL_TEXTURE1); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mask); + + if (brush instanceof Brush.Blurer && bluredTexture != null) { + GLES20.glUniform1i(shader.getUniform("blured"), 2); + GLES20.glActiveTexture(GLES20.GL_TEXTURE2); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, bluredTexture.texture()); + } + + GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA); + + GLES20.glVertexAttribPointer(0, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer); + GLES20.glEnableVertexAttribArray(0); + GLES20.glVertexAttribPointer(1, 2, GLES20.GL_FLOAT, false, 8, textureBuffer); + GLES20.glEnableVertexAttribArray(1); + + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + + Utils.HasGLError(); + } + + private void renderBlit(int texture, float alpha) { + Shader shader = shaders.get("blit"); + if (texture == 0 || shader == null) { + return; + } + + GLES20.glUseProgram(shader.program); + + GLES20.glUniformMatrix4fv(shader.getUniform("mvpMatrix"), 1, false, FloatBuffer.wrap(renderProjection)); + GLES20.glUniform1i(shader.getUniform("texture"), 0); + GLES20.glUniform1f(shader.getUniform("alpha"), alpha); + + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture); + GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA); GLES20.glVertexAttribPointer(0, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer); @@ -446,6 +865,8 @@ public class Painting { data = new PaintingData(bitmap, null); } + dataBuffer.rewind(); + buffers[0] = framebuffer; GLES20.glDeleteFramebuffers(1, buffers, 0); @@ -455,11 +876,51 @@ public class Painting { return data; } + private Paint imageBitmapPaint; public void setBrush(Brush value) { brush = value; - if (brushTexture != null) { - brushTexture.cleanResources(true); - brushTexture = null; + + if (value instanceof Brush.Blurer && imageBitmap != null) { + int w = imageBitmap.getWidth(), h = imageBitmap.getHeight(); + if (imageBitmapRotation == 90 || imageBitmapRotation == 270 || imageBitmapRotation == -90) { + int pH = h; + h = w; + w = pH; + } + float SCALE = 8; + if (bluredBitmap == null) { + bluredBitmap = Bitmap.createBitmap((int) (w / SCALE), (int) (h / SCALE), Bitmap.Config.ARGB_8888); + } + Canvas canvas = new Canvas(bluredBitmap); + canvas.save(); + canvas.scale(1f / SCALE, 1f / SCALE); + if (imageBitmapPaint != null) { + imageBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + } + canvas.save(); + canvas.rotate(imageBitmapRotation); + if (imageBitmapRotation == 90) { + canvas.translate(0, -w); + } else if (imageBitmapRotation == 180) { + canvas.translate(-w, -h); + } else if (imageBitmapRotation == 270) { + canvas.translate(-h, 0); + } + canvas.drawBitmap(imageBitmap, 0, 0, imageBitmapPaint); + canvas.restore(); + if (renderView != null) { + Bitmap bitmap = renderView.getResultBitmap(); + if (bitmap != null) { + canvas.scale((float) w / bitmap.getWidth(), (float) h / bitmap.getHeight()); + canvas.drawBitmap(bitmap, 0, 0, imageBitmapPaint); + bitmap.recycle(); + } + } + Utilities.stackBlurBitmap(bluredBitmap, (int) SCALE); + if (bluredTexture != null) { + bluredTexture.cleanResources(false); + } + bluredTexture = new Texture(bluredBitmap); } } @@ -501,9 +962,21 @@ public class Painting { paintTexture = 0; } - if (brushTexture != null) { - brushTexture.cleanResources(true); - brushTexture = null; + for (Texture texture : brushTextures.values()) { + if (texture != null) { + texture.cleanResources(true); + } + } + brushTextures.clear(); + + if (helperTexture != 0) { + buffers[0] = helperTexture; + GLES20.glDeleteTextures(1, buffers, 0); + helperTexture = 0; + } + + if (bluredTexture != null) { + bluredTexture.cleanResources(true); } if (shaders != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/PersistColorPalette.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/PersistColorPalette.java new file mode 100644 index 000000000..6bd1ae39c --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/PersistColorPalette.java @@ -0,0 +1,196 @@ +package org.telegram.ui.Components.Paint; + +import android.content.Context; +import android.content.SharedPreferences; + +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.UserConfig; +import org.telegram.ui.Components.Paint.Views.PaintTextOptionsView; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class PersistColorPalette { + public final static int COLORS_COUNT = 14; + + private final static List DEFAULT_COLORS = Arrays.asList( + 0xff1D99FF, + 0xff03BCD4, + 0xff39BA2B, + 0xffF9A30F, + 0xffFA6E16, + 0xffE83544, + 0xffB24DFF, + 0xffD7A07C, + 0xffAC734C, + 0xff90512C, + 0xff532E1F, + 0xff000000, + 0xff818181, + 0xffFFFFFF + ); + private final static Integer DEFAULT_MARKER_COLOR = 0xff0a84ff; + + private static PersistColorPalette[] instances = new PersistColorPalette[UserConfig.MAX_ACCOUNT_COUNT]; + + private SharedPreferences mConfig; + private List colors = new ArrayList<>(COLORS_COUNT); + private List pendingChange = new ArrayList<>(COLORS_COUNT); + private Integer markerColor; + + private int currentBrush; + private int currentAlignment; + private int currentTextType; + private float currentWeight; + private String currentTypeface; + private boolean fillShapes; + + public PersistColorPalette(int currentUser) { + mConfig = ApplicationLoader.applicationContext.getSharedPreferences("photo_color_palette_" + currentUser, Context.MODE_PRIVATE); + currentBrush = mConfig.getInt("brush", 0); + currentWeight = mConfig.getFloat("weight", .5f); + currentTypeface = mConfig.getString("typeface", "roboto"); + currentAlignment = mConfig.getInt("text_alignment", PaintTextOptionsView.ALIGN_LEFT); + currentTextType = mConfig.getInt("text_type", 0); + fillShapes = mConfig.getBoolean("fill_shapes", false); + + loadColors(); + } + + public static PersistColorPalette getInstance(int currentAccount) { + if (instances[currentAccount] == null) { + instances[currentAccount] = new PersistColorPalette(currentAccount); + } + return instances[currentAccount]; + } + + public int getCurrentTextType() { + return currentTextType; + } + + public void setCurrentTextType(int currentTextType) { + this.currentTextType = currentTextType; + mConfig.edit().putInt("text_type", currentTextType).apply(); + } + + public int getCurrentAlignment() { + return currentAlignment; + } + + public void setCurrentAlignment(int currentAlignment) { + this.currentAlignment = currentAlignment; + mConfig.edit().putInt("text_alignment", currentAlignment).apply(); + } + + public String getCurrentTypeface() { + return currentTypeface; + } + + public void setCurrentTypeface(String currentTypeface) { + this.currentTypeface = currentTypeface; + mConfig.edit().putString("typeface", currentTypeface).apply(); + } + + public float getWeight(String key, float defaultWeight) { + return mConfig.getFloat("weight_" + key, defaultWeight); + } + + public void setWeight(String key, float weight) { + mConfig.edit().putFloat("weight_" + key, weight).apply(); + } + + public float getCurrentWeight() { + return currentWeight; + } + + public void setCurrentWeight(float currentWeight) { + this.currentWeight = currentWeight; + mConfig.edit().putFloat("weight", currentWeight).apply(); + } + + public int getCurrentBrush() { + return currentBrush; + } + + public void setCurrentBrush(int currentBrush) { + this.currentBrush = currentBrush; + mConfig.edit().putInt("brush", currentBrush).apply(); + } + + public boolean getFillShapes() { + return fillShapes; + } + + public void toggleFillShapes() { + this.fillShapes = !this.fillShapes; + mConfig.edit().putBoolean("fill_shapes", fillShapes).apply(); + } + + public void cleanup() { + pendingChange.clear(); + pendingChange.addAll(DEFAULT_COLORS); + saveColors(); + } + + private void checkIndex(int i) { + if (i < 0 || i >= COLORS_COUNT) { + throw new IndexOutOfBoundsException("Color palette index should be in range 0 ... " + COLORS_COUNT); + } + } + + public int getColor(int index) { + checkIndex(index); + + return colors.get(index); + } + + public void selectColor(int color) { + int i = colors.indexOf(color); + if (i != -1) { + selectColorIndex(i); + } else { + List from = new ArrayList<>(pendingChange.isEmpty() ? colors : pendingChange); + + pendingChange.clear(); + pendingChange.add(color); + pendingChange.addAll(from); + pendingChange.remove(pendingChange.size() - 1); + } + } + + public void selectColorIndex(int index) { + int color = colors.get(index); + List from = new ArrayList<>(pendingChange.isEmpty() ? colors : pendingChange); + pendingChange.clear(); + pendingChange.add(color); + for (int i = 0; i < COLORS_COUNT; i++) { + if (from.get(i) != color) { + pendingChange.add(from.get(i)); + } + } + } + + private void loadColors() { + for (int i = 0; i < COLORS_COUNT; i++) { + colors.add((int) mConfig.getLong("color_" + i, DEFAULT_COLORS.get(i))); + } + markerColor = (int) mConfig.getLong("color_marker", DEFAULT_MARKER_COLOR); + } + + public void saveColors() { + if (pendingChange.isEmpty()) { + return; + } + + SharedPreferences.Editor editor = mConfig.edit(); + for (int i = 0; i < COLORS_COUNT; i++) { + editor.putLong("color_" + i, pendingChange.get(i)); + } + editor.apply(); + + colors.clear(); + colors.addAll(pendingChange); + pendingChange.clear(); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Point.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Point.java index 287496bf1..6fa02437f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Point.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Point.java @@ -16,6 +16,13 @@ public class Point { this.z = z; } + public Point(double x, double y, double z, boolean edge) { + this.x = x; + this.y = y; + this.z = z; + this.edge = edge; + } + public Point(Point point) { x = point.x; y = point.y; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Render.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Render.java index 412ebad7a..1ec48306b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Render.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Render.java @@ -12,9 +12,13 @@ import java.nio.FloatBuffer; public class Render { public static RectF RenderPath(Path path, RenderState state) { + return RenderPath(path, state, false); + } + + public static RectF RenderPath(Path path, RenderState state, boolean fullAlpha) { state.baseWeight = path.getBaseWeight(); state.spacing = path.getBrush().getSpacing(); - state.alpha = path.getBrush().getAlpha(); + state.alpha = fullAlpha ? 1f : path.getBrush().getAlpha(); state.angle = path.getBrush().getAngle(); state.scale = path.getBrush().getScale(); @@ -45,7 +49,7 @@ public class Render { Point unitVector = new Point(1.0f, 1.0f, 0.0f); float vectorAngle = Math.abs(state.angle) > 0.0f ? state.angle : (float) Math.atan2(vector.y, vector.x); - float brushWeight = state.baseWeight * state.scale * 1f / state.viewportScale; + float brushWeight = (float) (state.baseWeight * point.z * state.scale * 1f / state.viewportScale); double step = Math.max(1.0f, state.spacing * brushWeight); if (distance > 0.0) { @@ -99,7 +103,7 @@ public class Render { RectF dataBounds = new RectF(0, 0, 0, 0); int count = state.getCount(); - if (count == 0) { + if (count <= 0) { return dataBounds; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/RenderView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/RenderView.java index 8d387e570..5ac26e524 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/RenderView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/RenderView.java @@ -1,7 +1,11 @@ package org.telegram.ui.Components.Paint; import android.content.Context; -import android.graphics.*; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.RectF; +import android.graphics.SurfaceTexture; import android.opengl.GLES20; import android.opengl.GLUtils; import android.os.Looper; @@ -9,14 +13,6 @@ import android.view.MotionEvent; import android.view.TextureView; import android.view.View; -import com.google.android.exoplayer2.util.Log; - -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; -import javax.microedition.khronos.egl.EGLSurface; - import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.BuildVars; import org.telegram.messenger.DispatchQueue; @@ -25,6 +21,12 @@ import org.telegram.ui.Components.Size; import java.util.concurrent.CountDownLatch; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; + public class RenderView extends TextureView { public interface RenderViewDelegate { @@ -32,6 +34,8 @@ public class RenderView extends TextureView { void onFinishedDrawing(boolean moved); void onFirstDraw(); boolean shouldDraw(); + default void invalidateInputView() {} + void resetBrush(); } private RenderViewDelegate delegate; @@ -41,6 +45,7 @@ public class RenderView extends TextureView { private Painting painting; private CanvasInternal internal; private Input input; + private ShapeInput shapeInput; private Bitmap bitmap; private boolean transformedBitmap; @@ -70,7 +75,11 @@ public class RenderView extends TextureView { internal.setBufferSize(width, height); updateTransform(); - internal.requestRender(); + post(() -> { + if (internal != null) { + internal.requestRender(); + } + }); if (painting.isPaused()) { painting.onResume(); @@ -115,6 +124,11 @@ public class RenderView extends TextureView { }); input = new Input(this); + shapeInput = new ShapeInput(this, () -> { + if (delegate != null) { + delegate.invalidateInputView(); + } + }); painting.setDelegate(new Painting.PaintingDelegate() { @Override public void contentChanged() { @@ -154,10 +168,24 @@ public class RenderView extends TextureView { if (internal == null || !internal.initialized || !internal.ready) { return true; } - input.process(event, getScaleX()); + if (brush instanceof Brush.Shape) { + shapeInput.process(event, getScaleX()); + } else { + input.process(event, getScaleX()); + } return true; } + public void onDrawForInput(Canvas canvas) { + if (brush instanceof Brush.Shape) { + shapeInput.dispatchDraw(canvas); + } + } + + public void onFillShapesToggle(Canvas canvas) { + + } + public void setUndoStore(UndoStore store) { undoStore = store; } @@ -174,7 +202,7 @@ public class RenderView extends TextureView { return painting; } - private float brushWeightForSize(float size) { + public float brushWeightForSize(float size) { float paintingWidth = painting.getSize().width; return 8.0f / 2048.0f * paintingWidth + (90.0f / 2048.0f * paintingWidth) * size; } @@ -185,6 +213,9 @@ public class RenderView extends TextureView { public void setColor(int value) { color = value; + if (brush instanceof Brush.Shape) { + shapeInput.onColorChange(); + } } public float getCurrentWeight() { @@ -193,17 +224,48 @@ public class RenderView extends TextureView { public void setBrushSize(float size) { weight = brushWeightForSize(size); + if (brush instanceof Brush.Shape) { + shapeInput.onWeightChange(); + } } public Brush getCurrentBrush() { return brush; } + public UndoStore getUndoStore() { + return undoStore; + } + public void setBrush(Brush value) { - painting.setBrush(brush = value); + if (brush instanceof Brush.Shape) { + shapeInput.stop(); + } + brush = value; + updateTransform(); + painting.setBrush(brush); + if (brush instanceof Brush.Shape) { + shapeInput.start(((Brush.Shape) brush).getShapeShaderType()); + } + } + + public void resetBrush() { + if (delegate != null) { + delegate.resetBrush(); + } + input.ignoreOnce(); + } + + public void clearShape() { + if (shapeInput != null) { + shapeInput.clear(); + } } private void updateTransform() { + if (internal == null) { + return; + } Matrix matrix = new Matrix(); float scale = painting != null ? getWidth() / painting.getSize().width : 1.0f; @@ -217,7 +279,11 @@ public class RenderView extends TextureView { matrix.preScale(scale, -scale); matrix.preTranslate(-paintingSize.width / 2.0f, -paintingSize.height / 2.0f); - input.setMatrix(matrix); + if (brush instanceof Brush.Shape) { + shapeInput.setMatrix(matrix); + } else { + input.setMatrix(matrix); + } float[] proj = GLMatrix.LoadOrtho(0.0f, internal.bufferWidth, 0.0f, internal.bufferHeight, -1.0f, 1.0f); float[] effectiveProjection = GLMatrix.LoadGraphicsMatrix(matrix); @@ -255,6 +321,10 @@ public class RenderView extends TextureView { setVisibility(View.GONE); } + public void clearAll() { + input.clear(() -> painting.setBrush(brush)); + } + private class CanvasInternal extends DispatchQueue { private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; private static final int EGL_OPENGL_ES2_BIT = 4; @@ -516,6 +586,9 @@ public class RenderView extends TextureView { } public Bitmap getResultBitmap() { + if (brush instanceof Brush.Shape) { + shapeInput.stop(); + } return internal != null ? internal.getTexture() : null; } @@ -533,4 +606,6 @@ public class RenderView extends TextureView { action.run(); }); } + + protected void selectBrush(Brush brush) {} } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/ShaderSet.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/ShaderSet.java index a8da41094..24fe0a086 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/ShaderSet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/ShaderSet.java @@ -7,23 +7,305 @@ import java.util.Map; public class ShaderSet { - private static final Map> AVAILBALBE_SHADERS = createMap(); + private static final Map> AVAILABLE_SHADERS = createMap(); private static final String VERTEX = "vertex"; private static final String FRAGMENT = "fragment"; private static final String ATTRIBUTES = "attributes"; private static final String UNIFORMS = "uniforms"; - private static final String PAINT_BRUSH_VSH = "precision highp float; uniform mat4 mvpMatrix; attribute vec4 inPosition; attribute vec2 inTexcoord; attribute float alpha; varying vec2 varTexcoord; varying float varIntensity; void main (void) { gl_Position = mvpMatrix * inPosition; varTexcoord = inTexcoord; varIntensity = alpha; }"; - private static final String PAINT_BRUSH_FSH = "precision highp float; varying vec2 varTexcoord; varying float varIntensity; uniform sampler2D texture; void main (void) { gl_FragColor = vec4(1, 1, 1, varIntensity * texture2D(texture, varTexcoord.st, 0.0).r); }"; - private static final String PAINT_BRUSHLIGHT_FSH = "precision highp float; varying vec2 varTexcoord; varying float varIntensity; uniform sampler2D texture; void main (void) { vec4 f = texture2D(texture, varTexcoord.st, 0.0); gl_FragColor = vec4(f.r * varIntensity, f.g, f.b, 0.0); }"; - private static final String PAINT_BLIT_VSH = "precision highp float; uniform mat4 mvpMatrix; attribute vec4 inPosition; attribute vec2 inTexcoord; varying vec2 varTexcoord; void main (void) { gl_Position = mvpMatrix * inPosition; varTexcoord = inTexcoord; }"; - private static final String PAINT_BLIT_FSH = "precision highp float; varying vec2 varTexcoord; uniform sampler2D texture; void main (void) { gl_FragColor = texture2D(texture, varTexcoord.st, 0.0); gl_FragColor.rgb *= gl_FragColor.a; }"; - private static final String PAINT_BLITWITHMASKLIGHT_FSH = "precision highp float; varying vec2 varTexcoord; uniform sampler2D texture; uniform sampler2D mask; uniform vec4 color; void main (void) { vec4 dst = texture2D(texture, varTexcoord.st, 0.0); vec3 maskColor = texture2D(mask, varTexcoord.st, 0.0).rgb; float srcAlpha = clamp(0.78 * maskColor.r + maskColor.b + maskColor.g, 0.0, 1.0); vec3 borderColor = mix(color.rgb, vec3(1.0, 1.0, 1.0), 0.86); vec3 finalColor = mix(color.rgb, borderColor, maskColor.g); finalColor = mix(finalColor.rgb, vec3(1.0, 1.0, 1.0), maskColor.b); float outAlpha = srcAlpha + dst.a * (1.0 - srcAlpha); gl_FragColor.rgb = (finalColor * srcAlpha + dst.rgb * dst.a * (1.0 - srcAlpha)) / outAlpha; gl_FragColor.a = outAlpha; gl_FragColor.rgb *= gl_FragColor.a; }"; - private static final String PAINT_BLITWITHMASK_FSH = "precision highp float; varying vec2 varTexcoord; uniform sampler2D texture; uniform sampler2D mask; uniform vec4 color; void main (void) { vec4 dst = texture2D(texture, varTexcoord.st, 0.0); float srcAlpha = color.a * texture2D(mask, varTexcoord.st, 0.0).a; float outAlpha = srcAlpha + dst.a * (1.0 - srcAlpha); gl_FragColor.rgb = (color.rgb * srcAlpha + dst.rgb * dst.a * (1.0 - srcAlpha)) / outAlpha; gl_FragColor.a = outAlpha; gl_FragColor.rgb *= gl_FragColor.a; }"; - private static final String PAINT_COMPOSITEWITHMASK_FSH = "precision highp float; varying vec2 varTexcoord; uniform sampler2D texture; uniform sampler2D mask; uniform vec4 color; void main(void) { vec4 dst = texture2D(texture, varTexcoord.st, 0.0); float srcAlpha = color.a * texture2D(mask, varTexcoord.st, 0.0).a; float outAlpha = srcAlpha + dst.a * (1.0 - srcAlpha); gl_FragColor.rgb = (color.rgb * srcAlpha + dst.rgb * dst.a * (1.0 - srcAlpha)) / outAlpha; gl_FragColor.a = outAlpha; }"; - private static final String PAINT_COMPOSITEWITHMASKLIGHT_FSH = "precision highp float; varying vec2 varTexcoord; uniform sampler2D texture; uniform sampler2D mask; uniform vec4 color; void main(void) { vec4 dst = texture2D(texture, varTexcoord.st, 0.0); vec3 maskColor = texture2D(mask, varTexcoord.st, 0.0).rgb; float srcAlpha = clamp(0.78 * maskColor.r + maskColor.b + maskColor.g, 0.0, 1.0); vec3 borderColor = mix(color.rgb, vec3(1.0, 1.0, 1.0), 0.86); vec3 finalColor = mix(color.rgb, borderColor, maskColor.g); finalColor = mix(finalColor.rgb, vec3(1.0, 1.0, 1.0), maskColor.b); float outAlpha = srcAlpha + dst.a * (1.0 - srcAlpha); gl_FragColor.rgb = (finalColor * srcAlpha + dst.rgb * dst.a * (1.0 - srcAlpha)) / outAlpha; gl_FragColor.a = outAlpha; }"; - private static final String PAINT_NONPREMULTIPLIEDBLIT_FSH = "precision highp float; varying vec2 varTexcoord; uniform sampler2D texture; void main (void) { gl_FragColor = texture2D(texture, varTexcoord.st, 0.0); }"; + private static final String PAINT_BRUSH_VSH = + "precision highp float;" + + + "uniform mat4 mvpMatrix;" + + "attribute vec4 inPosition;" + + "attribute vec2 inTexcoord;" + + "attribute float alpha;" + + "varying vec2 varTexcoord;" + + "varying float varIntensity;" + + + "void main (void) {" + + " gl_Position = mvpMatrix * inPosition;" + + " varTexcoord = inTexcoord;" + + " varIntensity = alpha;" + + "}"; + private static final String PAINT_BRUSH_FSH = + "precision highp float;" + + + "varying vec2 varTexcoord;" + + "varying float varIntensity;" + + "uniform sampler2D texture;" + + + "void main (void) {" + + " gl_FragColor = vec4(1, 1, 1, varIntensity * texture2D(texture, varTexcoord.st, 0.0).r);" + + "}"; + private static final String PAINT_BLIT_VSH = + "precision highp float;" + + + "uniform mat4 mvpMatrix;" + + "attribute vec4 inPosition;" + + "attribute vec2 inTexcoord;" + + "varying vec2 varTexcoord;" + + + "void main (void) {" + + " gl_Position = mvpMatrix * inPosition;" + + " varTexcoord = inTexcoord;" + + "}"; + private static final String PAINT_BLIT_FSH = + "precision highp float;" + + "varying vec2 varTexcoord;" + + "uniform sampler2D texture;" + +// "uniform float alpha;" + + "void main (void) {" + + " gl_FragColor = texture2D(texture, varTexcoord.st, 0.0);" + +// " gl_FragColor.a *= alpha;" + + " gl_FragColor.rgb *= gl_FragColor.a;" + + "}"; + private static final String PAINT_BLITWITHMASK_FSH = + "precision highp float;" + + "varying vec2 varTexcoord;" + + "uniform sampler2D texture;" + + "uniform sampler2D mask;" + + "uniform vec4 color;" + + "void main (void) {" + + " vec4 dst = texture2D(texture, varTexcoord.st, 0.0);" + + " float srcAlpha = color.a * texture2D(mask, varTexcoord.st, 0.0).a;" + + " float outAlpha = srcAlpha + dst.a * (1.0 - srcAlpha);" + + " gl_FragColor.rgb = (color.rgb * srcAlpha + dst.rgb * dst.a * (1.0 - srcAlpha)) / outAlpha;" + + " gl_FragColor.a = outAlpha;" + + " gl_FragColor.rgb *= gl_FragColor.a;" + + "}"; + private static final String PAINT_COMPOSITEWITHMASK_FSH = + "precision highp float;" + + "varying vec2 varTexcoord;" + + "uniform sampler2D texture;" + + "uniform sampler2D mask;" + + "uniform vec4 color;" + + "void main(void) {" + + " vec4 dst = texture2D(texture, varTexcoord.st, 0.0);" + + " float srcAlpha = color.a * texture2D(mask, varTexcoord.st, 0.0).a;" + + " float outAlpha = srcAlpha + dst.a * (1.0 - srcAlpha);" + + " gl_FragColor.rgb = (color.rgb * srcAlpha + dst.rgb * dst.a * (1.0 - srcAlpha)) / outAlpha;" + + " gl_FragColor.a = outAlpha;" + + "}"; + private static final String PAINT_NONPREMULTIPLIEDBLIT_FSH = + "precision highp float;" + + + "varying vec2 varTexcoord;" + + "uniform sampler2D texture;" + + + "void main (void) {" + + " gl_FragColor = texture2D(texture, varTexcoord.st, 0.0);" + + "}"; + + // neon shaders + private static final String PAINT_BRUSHLIGHT_FSH = + "precision highp float;" + + + "varying vec2 varTexcoord;" + + "varying float varIntensity;" + + "uniform sampler2D texture;" + + + "void main (void) {" + + " vec4 f = texture2D(texture, varTexcoord.st, 0.0);" + + " gl_FragColor = vec4(f.r * varIntensity, f.g, f.b, 0.0);" + + "}"; + private static final String PAINT_BLITWITHMASKLIGHT_FSH = + "precision highp float;" + + "varying vec2 varTexcoord;" + + "uniform sampler2D texture;" + + "uniform sampler2D mask;" + + "uniform vec4 color;" + + "void main (void) {" + + " vec4 dst = texture2D(texture, varTexcoord.st, 0.0);" + + " vec3 maskColor = texture2D(mask, varTexcoord.st, 0.0).rgb;" + + " float srcAlpha = clamp(0.78 * maskColor.r + maskColor.b + maskColor.g, 0.0, 1.0);" + + " vec3 borderColor = mix(color.rgb, vec3(1.0, 1.0, 1.0), 0.86);" + + " vec3 finalColor = mix(color.rgb, borderColor, maskColor.g);" + + " finalColor = mix(finalColor.rgb, vec3(1.0, 1.0, 1.0), maskColor.b);" + + " float outAlpha = srcAlpha + dst.a * (1.0 - srcAlpha);" + + " gl_FragColor.rgb = (finalColor * srcAlpha + dst.rgb * dst.a * (1.0 - srcAlpha)) / outAlpha;" + + " gl_FragColor.a = outAlpha;" + + " gl_FragColor.rgb *= gl_FragColor.a;" + + "}"; + private static final String PAINT_COMPOSITEWITHMASKLIGHT_FSH = + "precision highp float;" + + "varying vec2 varTexcoord;" + + "uniform sampler2D texture;" + + "uniform sampler2D mask;" + + "uniform vec4 color;" + + "void main(void) {" + + " vec4 dst = texture2D(texture, varTexcoord.st, 0.0);" + + " vec3 maskColor = texture2D(mask, varTexcoord.st, 0.0).rgb;" + + " float srcAlpha = clamp(0.78 * maskColor.r + maskColor.b + maskColor.g, 0.0, 1.0);" + + " vec3 borderColor = mix(color.rgb, vec3(1.0, 1.0, 1.0), 0.86);" + + " vec3 finalColor = mix(color.rgb, borderColor, maskColor.g);" + + " finalColor = mix(finalColor.rgb, vec3(1.0, 1.0, 1.0), maskColor.b);" + + " float outAlpha = srcAlpha + dst.a * (1.0 - srcAlpha);" + + " gl_FragColor.rgb = (finalColor * srcAlpha + dst.rgb * dst.a * (1.0 - srcAlpha)) / outAlpha;" + + " gl_FragColor.a = outAlpha;" + + "}"; + + // eraser + private static final String PAINT_BLITWITHMASKERASER_FSH = + "precision highp float;" + + "varying vec2 varTexcoord;" + + "uniform sampler2D texture;" + + "uniform sampler2D mask;" + + "uniform vec4 color;" + + "void main (void) {" + + " vec4 dst = texture2D(texture, varTexcoord.st, 0.0);" + + " float srcAlpha = color.a * texture2D(mask, varTexcoord.st, 0.0).a;" + + " float outAlpha = dst.a * (1. - srcAlpha);" + + " gl_FragColor.rgb = dst.rgb;" + + " gl_FragColor.a = outAlpha;" + + " gl_FragColor.rgb *= gl_FragColor.a;" + + "}"; + private static final String PAINT_COMPOSITEWITHMASKERASER_FSH = + "precision highp float;" + + "varying vec2 varTexcoord;" + + "uniform sampler2D texture;" + + "uniform sampler2D mask;" + + "uniform vec4 color;" + + "void main (void) {" + + " vec4 dst = texture2D(texture, varTexcoord.st, 0.0);" + + " float srcAlpha = color.a * texture2D(mask, varTexcoord.st, 0.0).a;" + + " gl_FragColor = vec4(dst.rgb, dst.a * (1.0 - srcAlpha));" + + " if (gl_FragColor.a <= 0.) gl_FragColor.rgb = vec3(0.);" + + "}"; + + // blurer + private static final String PAINT_BLITWITHMASKBLURER_FSH = + "precision highp float;" + + "varying vec2 varTexcoord;" + + "uniform sampler2D texture;" + + "uniform sampler2D mask;" + + "uniform sampler2D blured;" + + "uniform vec4 color;" + + "void main (void) {" + + " vec4 dst = texture2D(texture, varTexcoord.st, 0.0);" + + " float srcAlpha = color.a * texture2D(mask, varTexcoord.st, 0.0).a;" + + " float outAlpha = srcAlpha + dst.a * (1.0 - srcAlpha);" + + " vec4 blurColor = texture2D(blured, varTexcoord.st, 0.0);" + + " gl_FragColor.rgb = (blurColor.rgb * srcAlpha + dst.rgb * dst.a * (1.0 - srcAlpha)) / outAlpha;" + + " gl_FragColor.a = outAlpha;" + + " gl_FragColor.rgb *= gl_FragColor.a;" + + "}"; + private static final String PAINT_COMPOSITEWITHMASKBLURER_FSH = + "precision highp float;" + + "varying vec2 varTexcoord;" + + "uniform sampler2D texture;" + + "uniform sampler2D mask;" + + "uniform sampler2D blured;" + + "uniform vec4 color;" + + "void main (void) {" + + " vec4 dst = texture2D(texture, varTexcoord.st, 0.0);" + + " float srcAlpha = color.a * texture2D(mask, varTexcoord.st, 0.0).a;" + + " float outAlpha = srcAlpha + dst.a * (1.0 - srcAlpha);" + + " vec4 blurColor = texture2D(blured, varTexcoord.st, 0.0);" + + " gl_FragColor.rgb = (blurColor.rgb * srcAlpha + dst.rgb * dst.a * (1.0 - srcAlpha)) / outAlpha;" + + " gl_FragColor.a = outAlpha;" + + "}"; + + // shapes + private static final String PAINT_SHAPE_FSH = + "precision highp float;" + + "varying vec2 varTexcoord;" + + "uniform sampler2D texture;" + + "uniform sampler2D mask;" + + "uniform bool composite;" + + + "uniform int type;" + + "uniform vec4 color;" + + "uniform vec2 resolution;" + + "uniform vec2 center;" + + "uniform vec2 radius;" + + "uniform float thickness;" + + "uniform float rounding;" + + "uniform float rotation;" + + "uniform float arrowTriangleLength;" + + "uniform vec2 middle;" + + "uniform bool fill;" + + "uniform bool clear;" + + + // iq, i <3 you! https://iquilezles.org/articles/distfunctions2d/ + "float sdTriangle( in vec2 p, in vec2 p0, in vec2 p1, in vec2 p2 ) {" + + " vec2 e0 = p1 - p0, e1 = p2 - p1, e2 = p0 - p2, v0 = p - p0, v1 = p - p1, v2 = p - p2;" + + " vec2 pq0 = v0 - e0*clamp( dot(v0,e0)/dot(e0,e0), 0.0, 1.0 ), pq1 = v1 - e1*clamp( dot(v1,e1)/dot(e1,e1), 0.0, 1.0 ), pq2 = v2 - e2*clamp( dot(v2,e2)/dot(e2,e2), 0.0, 1.0 );" + + " float s = e0.x * e2.y - e0.y * e2.x;" + + " vec2 d = min( min( vec2( dot( pq0, pq0 ), s*(v0.x*e0.y-v0.y*e0.x) )," + + " vec2( dot( pq1, pq1 ), s*(v1.x*e1.y-v1.y*e1.x) ))," + + " vec2( dot( pq2, pq2 ), s*(v2.x*e2.y-v2.y*e2.x) ));" + + " return -sqrt(d.x) * sign(d.y);" + + "}" + + + "float sdBezier(vec2 A, vec2 B, vec2 C, vec2 P) {" + + " vec2 a=B-A,b=A-B*2.+C,c=a*2.,d=A-P;" + + " vec3 k=vec3(3.*dot(a,b),2.*dot(a,a)+dot(d,b),dot(d,a))/dot(b,b);" + + " float p=k.y-k.x*k.x/3., p3=p*p*p, q=k.x*(2.*k.x*k.x-9.*k.y)/27.+k.z, D=q*q+4.*p3/27.;" + + " if (D >= 0.) {" + + " float z=sqrt(D);" + + " vec2 x=(vec2(z,-z)-q)/2., uv=sign(x)*pow(abs(x),vec2(1./3.));" + + " float r=clamp(uv.x+uv.y-k.x/3.,0.,1.);" + + " return length(d+(c+b*r)*r);" + + " } else {" + + " float v=acos(-sqrt(-27./p3)*q/2.)/3., m=cos(v), n=sin(v)*1.73205;" + + " vec3 t=clamp(vec3(m+m,-n-m,n-m)*sqrt(-p/3.)-k.x/3.,0.,1.);" + + " return min(min(length(d+(c+b*t.x)*t.x),length(d+(c+b*t.y)*t.y)),length(d+(c+b*t.z)*t.z));" + + " }" + + "}" + + + "vec4 blendOver(vec4 a, vec4 b) {" + + " float alpha = b.a + a.a * (1. - b.a);" + + " if (alpha <= 0.) return vec4(0.);" + + " return vec4((b.rgb * b.a + a.rgb * a.a * (1. - b.a)) / alpha, alpha);" + + "}" + + + "void main (void) {" + + " vec4 dst = clear ? vec4(0.) : texture2D(texture, varTexcoord.st, 0.0);" + + " vec2 p = varTexcoord.st * resolution - center;" + + " float sdf;" + + " vec2 pp = p;" + + " p *= mat2(cos(rotation), -sin(rotation), sin(rotation), cos(rotation));" + + " if (type == 0) {" + // CIRCLE + " sdf = length(p) - min(radius.x, radius.y);" + + " } else if (type == 1 || type == 3) {" + // ROUNDED RECT (+ BUBBLE) + " vec2 q = abs(p) - abs(radius) + rounding;" + + " sdf = min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - rounding;" + + " } else if (type == 2) {" + // STAR + " float n = 5.;" + + " float an = 3.141593 / float(n);" + + " vec2 acs = vec2(cos(an), sin(an)), ecs = vec2(cos(1.), sin(1.));" + + " float bn = mod(atan(p.x, -p.y), 2.0 * an) - an;" + + " p = length(p) * vec2(cos(bn), abs(sin(bn)));" + + " p -= min(radius.x, radius.y) * acs;" + + " p += ecs*clamp( -dot(p, ecs), 0.0, min(radius.x, radius.y) * acs.y / ecs.y);" + + " sdf = length(p) * sign(p.x);" + + " } else if (type == 4) {" + // ARROW + " p += center;" + + " sdf = sdBezier(center, middle, radius, p) - thickness;" + + " vec2 ba = center - middle;" + + " float a = atan(ba.y, ba.x), g = 30. / 180. * 3.14, ar = sin(g) * arrowTriangleLength;" + + " vec2 ac = center + vec2(cos(a),sin(a)) * ar / 2.;" + + " sdf = min(sdf, max(0., sdTriangle(p, ac, ac+vec2(cos(a+3.14-g), sin(a+3.14-g))*ar, ac+vec2(cos(a+3.14+g), sin(a+3.14+g))*ar)));" + + " sdf += thickness;" + + " }" + + " if (type == 3) {" + // BUBBLE + " vec2 c = middle-center;" + + " float a = atan(c.x, -c.y), r = min(radius.x, radius.y) / 2.;" + + " float k = rounding/2., bsdf = sdTriangle(pp+center, center-vec2(cos(a),sin(a))*r, center-vec2(cos(a-3.14),sin(a-3.14))*r, middle);" + + " float h = max(k-abs(sdf-bsdf), 0.)/k;" + + " sdf = min(sdf,bsdf)-h*h*h*k*(1.0/6.0);" + + " }" + + " if (fill && sdf < 0.) {" + + " sdf = 0.;" + + " }" + + " vec4 c = vec4(color.rgb, color.a * (1. - clamp((abs(sdf) - thickness), 0., 2.) / 2.));" + + " gl_FragColor = blendOver(dst, c);" + + " if (!composite) {" + + " gl_FragColor.rgb *= gl_FragColor.a;" + + " }" + + "}"; private static Map> createMap() { Map> result = new HashMap<>(); @@ -35,27 +317,13 @@ public class ShaderSet { shader.put(UNIFORMS, new String[]{"mvpMatrix", "texture"}); result.put("brush", Collections.unmodifiableMap(shader)); - shader = new HashMap<>(); - shader.put(VERTEX, PAINT_BRUSH_VSH); - shader.put(FRAGMENT, PAINT_BRUSHLIGHT_FSH); - shader.put(ATTRIBUTES, new String[]{"inPosition", "inTexcoord", "alpha"}); - shader.put(UNIFORMS, new String[]{"mvpMatrix", "texture"}); - result.put("brushLight", Collections.unmodifiableMap(shader)); - shader = new HashMap<>(); shader.put(VERTEX, PAINT_BLIT_VSH); shader.put(FRAGMENT, PAINT_BLIT_FSH); shader.put(ATTRIBUTES, new String[]{"inPosition", "inTexcoord"}); - shader.put(UNIFORMS, new String[]{"mvpMatrix", "texture"}); + shader.put(UNIFORMS, new String[]{"mvpMatrix", "texture", "alpha"}); result.put("blit", Collections.unmodifiableMap(shader)); - shader = new HashMap<>(); - shader.put(VERTEX, PAINT_BLIT_VSH); - shader.put(FRAGMENT, PAINT_BLITWITHMASKLIGHT_FSH); - shader.put(ATTRIBUTES, new String[]{"inPosition", "inTexcoord"}); - shader.put(UNIFORMS, new String[]{"mvpMatrix", "texture", "mask", "color"}); - result.put("blitWithMaskLight", Collections.unmodifiableMap(shader)); - shader = new HashMap<>(); shader.put(VERTEX, PAINT_BLIT_VSH); shader.put(FRAGMENT, PAINT_BLITWITHMASK_FSH); @@ -70,6 +338,36 @@ public class ShaderSet { shader.put(UNIFORMS, new String[]{"mvpMatrix", "texture", "mask", "color"}); result.put("compositeWithMask", Collections.unmodifiableMap(shader)); + // blurer + shader = new HashMap<>(); + shader.put(VERTEX, PAINT_BLIT_VSH); + shader.put(FRAGMENT, PAINT_BLITWITHMASKBLURER_FSH); + shader.put(ATTRIBUTES, new String[]{"inPosition", "inTexcoord"}); + shader.put(UNIFORMS, new String[]{"mvpMatrix", "texture", "mask", "blured", "color"}); + result.put("blitWithMaskBlurer", Collections.unmodifiableMap(shader)); + + shader = new HashMap<>(); + shader.put(VERTEX, PAINT_BLIT_VSH); + shader.put(FRAGMENT, PAINT_COMPOSITEWITHMASKBLURER_FSH); + shader.put(ATTRIBUTES, new String[]{"inPosition", "inTexcoord"}); + shader.put(UNIFORMS, new String[]{"mvpMatrix", "texture", "mask", "blured", "color"}); + result.put("compositeWithMaskBlurer", Collections.unmodifiableMap(shader)); + + // neon + shader = new HashMap<>(); + shader.put(VERTEX, PAINT_BRUSH_VSH); + shader.put(FRAGMENT, PAINT_BRUSHLIGHT_FSH); + shader.put(ATTRIBUTES, new String[]{"inPosition", "inTexcoord", "alpha"}); + shader.put(UNIFORMS, new String[]{"mvpMatrix", "texture"}); + result.put("brushLight", Collections.unmodifiableMap(shader)); + + shader = new HashMap<>(); + shader.put(VERTEX, PAINT_BLIT_VSH); + shader.put(FRAGMENT, PAINT_BLITWITHMASKLIGHT_FSH); + shader.put(ATTRIBUTES, new String[]{"inPosition", "inTexcoord"}); + shader.put(UNIFORMS, new String[]{"mvpMatrix", "texture", "mask", "color"}); + result.put("blitWithMaskLight", Collections.unmodifiableMap(shader)); + shader = new HashMap<>(); shader.put(VERTEX, PAINT_BLIT_VSH); shader.put(FRAGMENT, PAINT_COMPOSITEWITHMASKLIGHT_FSH); @@ -77,6 +375,22 @@ public class ShaderSet { shader.put(UNIFORMS, new String[]{"mvpMatrix", "texture", "mask", "color"}); result.put("compositeWithMaskLight", Collections.unmodifiableMap(shader)); + // eraser + shader = new HashMap<>(); + shader.put(VERTEX, PAINT_BLIT_VSH); + shader.put(FRAGMENT, PAINT_BLITWITHMASKERASER_FSH); + shader.put(ATTRIBUTES, new String[]{"inPosition", "inTexcoord"}); + shader.put(UNIFORMS, new String[]{"mvpMatrix", "texture", "mask", "color"}); + result.put("blitWithMaskEraser", Collections.unmodifiableMap(shader)); + + shader = new HashMap<>(); + shader.put(VERTEX, PAINT_BLIT_VSH); + shader.put(FRAGMENT, PAINT_COMPOSITEWITHMASKERASER_FSH); + shader.put(ATTRIBUTES, new String[]{"inPosition", "inTexcoord"}); + shader.put(UNIFORMS, new String[]{"mvpMatrix", "texture", "mask", "color"}); + result.put("compositeWithMaskEraser", Collections.unmodifiableMap(shader)); + + // undo shader = new HashMap<>(); shader.put(VERTEX, PAINT_BLIT_VSH); shader.put(FRAGMENT, PAINT_NONPREMULTIPLIEDBLIT_FSH); @@ -84,13 +398,21 @@ public class ShaderSet { shader.put(UNIFORMS, new String[]{"mvpMatrix", "texture"}); result.put("nonPremultipliedBlit", Collections.unmodifiableMap(shader)); + // shapes + shader = new HashMap<>(); + shader.put(VERTEX, PAINT_BLIT_VSH); + shader.put(FRAGMENT, PAINT_SHAPE_FSH); + shader.put(ATTRIBUTES, new String[]{"inPosition", "inTexcoord"}); + shader.put(UNIFORMS, new String[]{"mvpMatrix", "texture", "mask", "clear", "color", "type", "color", "resolution", "center", "radius", "thickness", "rounding", "fill", "rotation", "middle", "arrowTriangleLength", "composite"}); + result.put("shape", Collections.unmodifiableMap(shader)); + return Collections.unmodifiableMap(result); } public static Map setup() { Map result = new HashMap<>(); - for (Map.Entry> entry : AVAILBALBE_SHADERS.entrySet()) { + for (Map.Entry> entry : AVAILABLE_SHADERS.entrySet()) { Map value = entry.getValue(); String vertex = (String) value.get(VERTEX); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Shape.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Shape.java new file mode 100644 index 000000000..a4075af4d --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Shape.java @@ -0,0 +1,51 @@ +package org.telegram.ui.Components.Paint; + +import android.graphics.RectF; + +import androidx.annotation.NonNull; + +public class Shape { + + public Shape(@NonNull Brush.Shape brush) { + this.brush = brush; + } + + public final Brush.Shape brush; + + public float centerX, centerY; + public float radiusX, radiusY; + public float thickness; + public float rounding; + public float rotation; + + // for arrows it is middle, for bubbles it is corner + public float middleX, middleY; + + public float arrowTriangleLength; + + public boolean fill; + + public int getType() { + return brush.getShapeShaderType(); + } + + public void getBounds(RectF rect) { + if (getType() == Brush.Shape.SHAPE_TYPE_ARROW) { + rect.set(centerX - arrowTriangleLength, centerY - arrowTriangleLength, centerX + arrowTriangleLength, centerY + arrowTriangleLength); + rect.union(radiusX, radiusY); + rect.union(middleX, middleY); + } else { + float r = Math.max(Math.abs(radiusX), Math.abs(radiusY)); + rect.set( + centerX - r * 1.42f, + centerY - r * 1.42f, + centerX + r * 1.42f, + centerY + r * 1.42f + ); + if (getType() == Brush.Shape.SHAPE_TYPE_BUBBLE) { + rect.union(middleX, middleY); + } + } + rect.inset(-thickness - 3, -thickness - 3); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/ShapeDetector.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/ShapeDetector.java new file mode 100644 index 000000000..ed04b6ed4 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/ShapeDetector.java @@ -0,0 +1,549 @@ +package org.telegram.ui.Components.Paint; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.BuildVars; +import org.telegram.messenger.DispatchQueue; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.Utilities; +import org.telegram.ui.ActionBar.AlertDialog; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +public class ShapeDetector { + + private static DispatchQueue queue = new DispatchQueue("ShapeDetector"); + + private static class Point { + public double x; + public double y; + public Point(double x, double y) { + set(x, y); + } + public void set(double x, double y) { + this.x = x; + this.y = y; + } + + public double distance(double x, double y) { + return Math.sqrt(Math.pow(x - this.x, 2) + Math.pow(y - this.y, 2)); + } + public double distance(Point p) { + return distance(p.x, p.y); + } + + public static double distance(double x1, double y1, double x2, double y2) { + return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); + } + } + + private static class RectD { + public double left, top, right, bottom; + public RectD(double left, double top, double right, double bottom) { + this.left = left; + this.top = top; + this.right = right; + this.bottom = bottom; + } + public void union(double x, double y) { + if (left >= x) { + left = x; + } + if (top >= y) { + top = y; + } + if (right <= x) { + right = x; + } + if (bottom <= y) { + bottom = y; + } + } + + @Override + public String toString() { + return "RectD{" + + "left=" + left + + ", top=" + top + + ", right=" + right + + ", bottom=" + bottom + + '}'; + } + } + + private int templatesUsageScore; + private static class Template { + public int shapeType; + public ArrayList points = new ArrayList<>(); + + public int score; + } + + private final int MIN_POINTS = 8; + private final long TIMEOUT = 150; + + private ArrayList points = new ArrayList<>(); + private ArrayList