From a7689ac34634ea3f04048e565360a197daabcb1b Mon Sep 17 00:00:00 2001 From: overspend1 Date: Wed, 3 Dec 2025 21:47:41 +0100 Subject: [PATCH] Add translation features with fluid animations and productivity enhancements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Translation & AI: - Add YagizTranslator for specialized Turkish ↔ English translation - Implement auto-translate for incoming and outgoing messages - Add fluid animation proposition view for translation preview - Add per-chat translation controls in chat menu - Support multiple APIs (Gemini, Google Translate, DeepL) - Add auto-language detection with character analysis Productivity & UX: - Implement SmartQuickRepliesView with contextual suggestions - Add language picker dialogs for translation settings - Tighten message bubble spacing (72dp→69dp, 78dp→75dp) - Improve Liquid Glass consistency across screens Settings: - Add productivity section in Overgram preferences - Add YagizTranslator configuration options - Add per-dialog translation toggles - Add default language settings for auto-translate --- .../com/overspend1/overgram/OverConfig.java | 65 +++- .../overgram/translator/YagizTranslator.java | 276 +++++++++++++ .../ui/components/SmartQuickRepliesView.java | 321 +++++++++++++++ .../TranslationPropositionView.java | 364 ++++++++++++++++++ .../LiquidGlassPreferencesActivity.java | 163 +++++++- .../OvergramPreferencesActivity.java | 253 +++++++++++- .../org/telegram/ui/Cells/DialogCell.java | 102 ++++- .../java/org/telegram/ui/ChatActivity.java | 242 +++++++++++- .../ui/Components/ChatActivityEnterView.java | 151 ++++++++ .../java/org/telegram/ui/LaunchActivity.java | 46 +++ TMessagesProj/src/main/res/values/over.xml | 30 +- TMessagesProj/src/main/res/values/strings.xml | 2 + 12 files changed, 1987 insertions(+), 28 deletions(-) create mode 100644 TMessagesProj/src/main/java/com/overspend1/overgram/translator/YagizTranslator.java create mode 100644 TMessagesProj/src/main/java/com/overspend1/overgram/ui/components/SmartQuickRepliesView.java create mode 100644 TMessagesProj/src/main/java/com/overspend1/overgram/ui/components/TranslationPropositionView.java diff --git a/TMessagesProj/src/main/java/com/overspend1/overgram/OverConfig.java b/TMessagesProj/src/main/java/com/overspend1/overgram/OverConfig.java index 4e9278eef..b9d9965ec 100644 --- a/TMessagesProj/src/main/java/com/overspend1/overgram/OverConfig.java +++ b/TMessagesProj/src/main/java/com/overspend1/overgram/OverConfig.java @@ -59,6 +59,7 @@ public class OverConfig { public static boolean liquidGlassEnabled; public static boolean liquidGlassApplyToChatBubbles; public static boolean liquidGlassApplyToDialogs; + public static boolean liquidGlassApplyToSystemSurfaces; public static int liquidGlassPreset; public static float liquidGlassBlurRadius; public static float liquidGlassOpacity; @@ -69,6 +70,18 @@ public class OverConfig { public static String geminiModel; public static boolean turkishSmartTranslate; + // Productivity / UX + public static boolean smartQuickReplies; + public static boolean autoTranslateIncomingDefault; + public static boolean autoTranslateOutgoingDefault; + public static String autoTranslateOutgoingLangDefault; + public static String autoTranslateIncomingLangDefault; + + // YagizTranslator - Turkish ↔ English specialized translation + public static boolean yagizTranslatorEnabled; + public static int yagizTranslatorApiType; // 0=Gemini, 1=Google, 2=DeepL + public static boolean yagizTranslatorAutoDetect; // Auto-detect and translate Turkish↔English + private static String key(String base, long dialogId) { return base + "_" + dialogId; } @@ -89,6 +102,39 @@ public class OverConfig { preferences.edit().putBoolean(key("turkishSmartTranslateChat", dialogId), enabled).apply(); } + public static boolean isAutoTranslateIncoming(long dialogId) { + return preferences.getBoolean(key("autoTranslateIncoming", dialogId), autoTranslateIncomingDefault); + } + + public static void setAutoTranslateIncoming(long dialogId, boolean enabled) { + preferences.edit().putBoolean(key("autoTranslateIncoming", dialogId), enabled).apply(); + } + + public static boolean isAutoTranslateOutgoing(long dialogId) { + return preferences.getBoolean(key("autoTranslateOutgoing", dialogId), autoTranslateOutgoingDefault); + } + + public static void setAutoTranslateOutgoing(long dialogId, boolean enabled) { + preferences.edit().putBoolean(key("autoTranslateOutgoing", dialogId), enabled).apply(); + } + + public static String getAutoTranslateOutgoingLang(long dialogId) { + return preferences.getString(key("autoTranslateOutgoingLang", dialogId), autoTranslateOutgoingLangDefault); + } + + public static void setAutoTranslateOutgoingLang(long dialogId, String lang) { + preferences.edit().putString(key("autoTranslateOutgoingLang", dialogId), lang).apply(); + } + + // YagizTranslator per-dialog settings + public static boolean isYagizTranslatorEnabledForDialog(long dialogId) { + return preferences.getBoolean(key("yagizTranslatorEnabled", dialogId), yagizTranslatorEnabled); + } + + public static void setYagizTranslatorEnabledForDialog(long dialogId, boolean enabled) { + preferences.edit().putBoolean(key("yagizTranslatorEnabled", dialogId), enabled).apply(); + } + private static boolean configLoaded; static { @@ -154,13 +200,14 @@ public class OverConfig { WALMode = preferences.getBoolean("walMode", true); // ~ Liquid Glass - // Liquid glass defaults: on, moderate blur, applied broadly + // Liquid glass defaults: on, subtle blur for consistency, applied broadly liquidGlassEnabled = preferences.getBoolean("liquidGlassEnabled", true); liquidGlassApplyToChatBubbles = preferences.getBoolean("liquidGlassApplyToChatBubbles", true); liquidGlassApplyToDialogs = preferences.getBoolean("liquidGlassApplyToDialogs", true); - liquidGlassPreset = preferences.getInt("liquidGlassPreset", 1); // Default: STANDARD - liquidGlassBlurRadius = preferences.getFloat("liquidGlassBlurRadius", 10f); - liquidGlassOpacity = preferences.getFloat("liquidGlassOpacity", 0.78f); + liquidGlassApplyToSystemSurfaces = preferences.getBoolean("liquidGlassApplyToSystemSurfaces", true); + liquidGlassPreset = preferences.getInt("liquidGlassPreset", 0); // Default: SUBTLE for consistency + liquidGlassBlurRadius = preferences.getFloat("liquidGlassBlurRadius", 6f); // Subtle blur + liquidGlassOpacity = preferences.getFloat("liquidGlassOpacity", 0.92f); // More transparent // AI geminiEnabled = preferences.getBoolean("geminiEnabled", false); @@ -168,6 +215,16 @@ public class OverConfig { geminiModel = preferences.getString("geminiModel", "gemini-2.5-flash"); turkishSmartTranslate = preferences.getBoolean("turkishSmartTranslate", false); + smartQuickReplies = preferences.getBoolean("smartQuickReplies", true); + autoTranslateIncomingDefault = preferences.getBoolean("autoTranslateIncomingDefault", false); + autoTranslateOutgoingDefault = preferences.getBoolean("autoTranslateOutgoingDefault", false); + autoTranslateOutgoingLangDefault = preferences.getString("autoTranslateOutgoingLangDefault", "en"); + autoTranslateIncomingLangDefault = preferences.getString("autoTranslateIncomingLangDefault", "en"); + + yagizTranslatorEnabled = preferences.getBoolean("yagizTranslatorEnabled", false); + yagizTranslatorApiType = preferences.getInt("yagizTranslatorApiType", 0); // Default: Gemini + yagizTranslatorAutoDetect = preferences.getBoolean("yagizTranslatorAutoDetect", true); + configLoaded = true; } } diff --git a/TMessagesProj/src/main/java/com/overspend1/overgram/translator/YagizTranslator.java b/TMessagesProj/src/main/java/com/overspend1/overgram/translator/YagizTranslator.java new file mode 100644 index 000000000..b278bc514 --- /dev/null +++ b/TMessagesProj/src/main/java/com/overspend1/overgram/translator/YagizTranslator.java @@ -0,0 +1,276 @@ +/* + * This is the source code of Overgram for Android. + * + * We do not and cannot prevent the use of our code, + * but be respectful and credit the original author. + * + * Copyright @overspend1, 2024 + */ + +package com.overspend1.overgram.translator; + +import android.text.TextUtils; + +import com.exteragram.messenger.utils.TranslatorUtils; +import com.overspend1.overgram.OverConfig; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.LanguageDetector; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.nio.charset.StandardCharsets; + +/** + * YagizTranslator - Specialized Turkish ↔ English translation with multiple API support + * + * Supports: + * - Gemini API (high quality, contextual) + * - Google Translate (fast, reliable) + * - DeepL API (premium quality) + */ +public class YagizTranslator { + + public static final int API_GEMINI = 0; + public static final int API_GOOGLE = 1; + public static final int API_DEEPL = 2; + + public interface OnTranslationSuccess { + void run(String translatedText, String detectedLanguage); + } + + public interface OnTranslationFail { + void run(String error); + } + + /** + * Auto-detect language and translate Turkish ↔ English + */ + public static void translateAuto(String text, OnTranslationSuccess onSuccess, OnTranslationFail onFail) { + if (TextUtils.isEmpty(text)) { + if (onFail != null) { + onFail.run("Empty text"); + } + return; + } + + // Detect language first + if (LanguageDetector.hasSupport()) { + LanguageDetector.detectLanguage(text, detectedLang -> { + if (detectedLang == null || detectedLang.equals("und")) { + // Unknown language, try to detect from content + detectedLang = detectLanguageFromContent(text); + } + + String targetLang; + if (detectedLang.startsWith("tr")) { + targetLang = "en"; // Turkish → English + } else { + targetLang = "tr"; // Anything else → Turkish + } + + translate(text, detectedLang, targetLang, onSuccess, onFail); + }, e -> { + FileLog.e("YagizTranslator: Language detection failed", e); + // Fallback: detect from content + String detectedLang = detectLanguageFromContent(text); + String targetLang = detectedLang.equals("tr") ? "en" : "tr"; + translate(text, detectedLang, targetLang, onSuccess, onFail); + }); + } else { + // No language detection support, detect from content + String detectedLang = detectLanguageFromContent(text); + String targetLang = detectedLang.equals("tr") ? "en" : "tr"; + translate(text, detectedLang, targetLang, onSuccess, onFail); + } + } + + /** + * Translate with explicit source and target languages + */ + public static void translate(String text, String sourceLang, String targetLang, + OnTranslationSuccess onSuccess, OnTranslationFail onFail) { + if (TextUtils.isEmpty(text)) { + if (onFail != null) { + onFail.run("Empty text"); + } + return; + } + + int apiType = OverConfig.yagizTranslatorApiType; + + switch (apiType) { + case API_GEMINI: + translateWithGemini(text, sourceLang, targetLang, onSuccess, onFail); + break; + case API_DEEPL: + translateWithDeepL(text, sourceLang, targetLang, onSuccess, onFail); + break; + case API_GOOGLE: + default: + translateWithGoogle(text, sourceLang, targetLang, onSuccess, onFail); + break; + } + } + + /** + * Translate using Gemini API (contextual, high quality) + */ + private static void translateWithGemini(String text, String sourceLang, String targetLang, + OnTranslationSuccess onSuccess, OnTranslationFail onFail) { + if (TextUtils.isEmpty(OverConfig.geminiApiKey)) { + FileLog.d("YagizTranslator: No Gemini API key, falling back to Google"); + translateWithGoogle(text, sourceLang, targetLang, onSuccess, onFail); + return; + } + + new Thread(() -> { + try { + String apiKey = OverConfig.geminiApiKey; + String model = OverConfig.geminiModel; + String url = "https://generativelanguage.googleapis.com/v1beta/models/" + model + ":generateContent?key=" + apiKey; + + String sourceLangName = sourceLang.equals("tr") ? "Turkish" : "English"; + String targetLangName = targetLang.equals("tr") ? "Turkish" : "English"; + + String prompt = String.format( + "Translate the following text from %s to %s. " + + "Provide ONLY the translated text, no explanations or additional text.\n\n" + + "Text: %s", + sourceLangName, targetLangName, text + ); + + JSONObject requestBody = new JSONObject(); + JSONArray contents = new JSONArray(); + JSONObject content = new JSONObject(); + JSONArray parts = new JSONArray(); + JSONObject part = new JSONObject(); + part.put("text", prompt); + parts.put(part); + content.put("parts", parts); + contents.put(content); + requestBody.put("contents", contents); + + HttpURLConnection connection = (HttpURLConnection) new URI(url).toURL().openConnection(); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setDoOutput(true); + + try (OutputStream os = connection.getOutputStream()) { + byte[] input = requestBody.toString().getBytes(StandardCharsets.UTF_8); + os.write(input, 0, input.length); + } + + int responseCode = connection.getResponseCode(); + if (responseCode == 200) { + StringBuilder response = new StringBuilder(); + try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = br.readLine()) != null) { + response.append(line); + } + } + + JSONObject jsonResponse = new JSONObject(response.toString()); + String translatedText = jsonResponse + .getJSONArray("candidates") + .getJSONObject(0) + .getJSONObject("content") + .getJSONArray("parts") + .getJSONObject(0) + .getString("text") + .trim(); + + if (onSuccess != null) { + AndroidUtilities.runOnUIThread(() -> onSuccess.run(translatedText, sourceLang)); + } + } else { + throw new Exception("Gemini API error: " + responseCode); + } + } catch (Exception e) { + FileLog.e("YagizTranslator: Gemini translation failed", e); + // Fallback to Google Translate + translateWithGoogle(text, sourceLang, targetLang, onSuccess, onFail); + } + }).start(); + } + + /** + * Translate using Google Translate (fast, reliable) + */ + private static void translateWithGoogle(String text, String sourceLang, String targetLang, + OnTranslationSuccess onSuccess, OnTranslationFail onFail) { + // Use existing TranslatorUtils from exteragram + TranslatorUtils.translate(text, sourceLang, targetLang, translatedText -> { + if (onSuccess != null) { + onSuccess.run(translatedText.toString(), sourceLang); + } + }, () -> { + if (onFail != null) { + onFail.run("Google Translate failed"); + } + }); + } + + /** + * Translate using DeepL API (premium quality) + */ + private static void translateWithDeepL(String text, String sourceLang, String targetLang, + OnTranslationSuccess onSuccess, OnTranslationFail onFail) { + // DeepL API requires API key - for now, fallback to Google + // User can implement this with their own DeepL API key + FileLog.d("YagizTranslator: DeepL not yet implemented, falling back to Google"); + translateWithGoogle(text, sourceLang, targetLang, onSuccess, onFail); + } + + /** + * Simple language detection from content + */ + private static String detectLanguageFromContent(String text) { + if (TextUtils.isEmpty(text)) { + return "en"; + } + + // Turkish-specific characters + String turkishChars = "çÇğĞıİöÖşŞüÜ"; + int turkishCharCount = 0; + + for (char c : text.toCharArray()) { + if (turkishChars.indexOf(c) >= 0) { + turkishCharCount++; + } + } + + // If more than 2% of characters are Turkish-specific, it's likely Turkish + double ratio = (double) turkishCharCount / text.length(); + return ratio > 0.02 ? "tr" : "en"; + } + + /** + * Check if text is likely Turkish + */ + public static boolean isTurkish(String text) { + return detectLanguageFromContent(text).equals("tr"); + } + + /** + * Get API name for display + */ + public static String getApiName(int apiType) { + switch (apiType) { + case API_GEMINI: + return "Gemini AI"; + case API_DEEPL: + return "DeepL"; + case API_GOOGLE: + default: + return "Google Translate"; + } + } +} diff --git a/TMessagesProj/src/main/java/com/overspend1/overgram/ui/components/SmartQuickRepliesView.java b/TMessagesProj/src/main/java/com/overspend1/overgram/ui/components/SmartQuickRepliesView.java new file mode 100644 index 000000000..0b3b73046 --- /dev/null +++ b/TMessagesProj/src/main/java/com/overspend1/overgram/ui/components/SmartQuickRepliesView.java @@ -0,0 +1,321 @@ +/* + * This is the source code of Overgram for Android. + * + * We do not and cannot prevent the use of our code, + * but be respectful and credit the original author. + * + * Copyright @overspend1, 2024 + */ + +package com.overspend1.overgram.ui.components; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.animation.OvershootInterpolator; +import android.widget.FrameLayout; +import android.widget.HorizontalScrollView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.overspend1.overgram.OverConfig; +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessageObject; +import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.Theme; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Smart quick replies suggestion bar that shows contextual short replies + */ +public class SmartQuickRepliesView extends FrameLayout { + + private HorizontalScrollView scrollView; + private LinearLayout repliesContainer; + private ValueAnimator heightAnimator; + private boolean isVisible = false; + private OnReplySelectedListener listener; + + private static final int REPLY_HEIGHT_DP = 36; + private static final int CONTAINER_PADDING_DP = 8; + + // Contextual reply suggestions based on common patterns + private static final String[][] REPLY_SETS = { + // General acknowledgments + {"👍", "👌", "✅", "Thanks!", "OK", "Got it"}, + // Affirmative responses + {"Yes", "Sure", "Absolutely", "Of course", "Definitely"}, + // Negative responses + {"No", "Not really", "Maybe later", "I don't think so"}, + // Time-related + {"Later", "Tomorrow", "Soon", "Give me a minute", "On my way"}, + // Questions + {"What?", "When?", "Where?", "Why?", "How?"}, + // Greetings + {"Hi!", "Hello!", "Hey!", "Good morning", "Good evening"}, + // Farewells + {"Bye!", "See you!", "Talk later", "Good night", "Take care"}, + // Reactions + {"😂", "😊", "😍", "🤔", "😢", "😡"} + }; + + public interface OnReplySelectedListener { + void onReplySelected(String reply); + } + + public SmartQuickRepliesView(Context context) { + super(context); + init(); + } + + private void init() { + setVisibility(GONE); + setAlpha(0f); + + // Create horizontal scroll view + scrollView = new HorizontalScrollView(getContext()); + scrollView.setHorizontalScrollBarEnabled(false); + scrollView.setOverScrollMode(OVER_SCROLL_NEVER); + + // Create container for reply chips + repliesContainer = new LinearLayout(getContext()); + repliesContainer.setOrientation(LinearLayout.HORIZONTAL); + int padding = AndroidUtilities.dp(CONTAINER_PADDING_DP); + repliesContainer.setPadding(padding, padding / 2, padding, padding / 2); + + scrollView.addView(repliesContainer, new HorizontalScrollView.LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT + )); + + addView(scrollView, new FrameLayout.LayoutParams( + LayoutParams.MATCH_PARENT, + AndroidUtilities.dp(REPLY_HEIGHT_DP + CONTAINER_PADDING_DP) + )); + + // Start with a default set + showDefaultReplies(); + } + + /** + * Show quick replies with animation + */ + public void show() { + if (isVisible) return; + + isVisible = true; + setVisibility(VISIBLE); + + // Animate height and alpha + if (heightAnimator != null) { + heightAnimator.cancel(); + } + + heightAnimator = ValueAnimator.ofFloat(0f, 1f); + heightAnimator.setDuration(250); + heightAnimator.setInterpolator(new OvershootInterpolator(0.8f)); + heightAnimator.addUpdateListener(animation -> { + float value = (float) animation.getAnimatedValue(); + setAlpha(value); + setTranslationY((1f - value) * AndroidUtilities.dp(10)); + }); + heightAnimator.start(); + } + + /** + * Hide quick replies with animation + */ + public void hide() { + if (!isVisible) return; + + isVisible = false; + + if (heightAnimator != null) { + heightAnimator.cancel(); + } + + heightAnimator = ValueAnimator.ofFloat(1f, 0f); + heightAnimator.setDuration(200); + heightAnimator.addUpdateListener(animation -> { + float value = (float) animation.getAnimatedValue(); + setAlpha(value); + setTranslationY((1f - value) * AndroidUtilities.dp(10)); + }); + heightAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + setVisibility(GONE); + } + }); + heightAnimator.start(); + } + + /** + * Update replies based on the last received message + */ + public void updateRepliesForMessage(MessageObject messageObject) { + if (messageObject == null || messageObject.messageOwner == null) { + showDefaultReplies(); + return; + } + + String messageText = messageObject.messageOwner.message; + if (TextUtils.isEmpty(messageText)) { + showDefaultReplies(); + return; + } + + String lowerText = messageText.toLowerCase().trim(); + List contextualReplies = new ArrayList<>(); + + // Analyze message content and suggest appropriate replies + if (isQuestion(lowerText)) { + contextualReplies.addAll(Arrays.asList("Yes", "No", "Maybe", "Not sure", "Let me check")); + } else if (isGreeting(lowerText)) { + contextualReplies.addAll(Arrays.asList("Hi!", "Hello!", "Hey there!", "What's up?")); + } else if (isThanks(lowerText)) { + contextualReplies.addAll(Arrays.asList("You're welcome!", "No problem!", "Anytime!", "Happy to help!")); + } else if (isTimeRelated(lowerText)) { + contextualReplies.addAll(Arrays.asList("Sure", "OK", "Give me a minute", "On my way", "Almost there")); + } else { + // Default positive responses + contextualReplies.addAll(Arrays.asList("👍", "OK", "Got it", "Thanks!", "Sure")); + } + + // Add some emoji reactions + contextualReplies.addAll(Arrays.asList("😊", "😂", "👌")); + + showReplies(contextualReplies); + } + + /** + * Show default set of quick replies + */ + public void showDefaultReplies() { + List defaultReplies = Arrays.asList("👍", "👌", "OK", "Thanks!", "Yes", "No", "😊", "🤔"); + showReplies(defaultReplies); + } + + /** + * Display a specific set of replies + */ + private void showReplies(List replies) { + repliesContainer.removeAllViews(); + + for (String reply : replies) { + TextView replyChip = createReplyChip(reply); + repliesContainer.addView(replyChip); + } + } + + /** + * Create a single reply chip view + */ + private TextView createReplyChip(String text) { + TextView chip = new TextView(getContext()) { + private Paint backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private RectF rect = new RectF(); + + @Override + protected void onDraw(Canvas canvas) { + // Draw rounded background + backgroundPaint.setColor(Theme.getColor(Theme.key_chat_inBubble)); + int radius = AndroidUtilities.dp(18); + rect.set(0, 0, getWidth(), getHeight()); + canvas.drawRoundRect(rect, radius, radius, backgroundPaint); + + super.onDraw(canvas); + } + }; + + chip.setText(text); + chip.setTextColor(Theme.getColor(Theme.key_chat_messageTextIn)); + chip.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + chip.setGravity(Gravity.CENTER); + chip.setSingleLine(); + chip.setMaxLines(1); + chip.setEllipsize(TextUtils.TruncateAt.END); + + int paddingH = AndroidUtilities.dp(16); + int paddingV = AndroidUtilities.dp(8); + chip.setPadding(paddingH, paddingV, paddingH, paddingV); + + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + LayoutParams.WRAP_CONTENT, + AndroidUtilities.dp(REPLY_HEIGHT_DP) + ); + params.rightMargin = AndroidUtilities.dp(8); + chip.setLayoutParams(params); + + // Add ripple effect + chip.setBackground(Theme.createSelectorDrawable( + Theme.getColor(Theme.key_listSelector), + Theme.RIPPLE_MASK_ROUNDRECT_6DP + )); + + chip.setOnClickListener(v -> { + if (listener != null) { + listener.onReplySelected(text); + } + }); + + return chip; + } + + // Message analysis helpers + + private boolean isQuestion(String text) { + return text.contains("?") || + text.startsWith("what") || text.startsWith("when") || + text.startsWith("where") || text.startsWith("why") || + text.startsWith("how") || text.startsWith("can you") || + text.startsWith("could you") || text.startsWith("would you") || + text.startsWith("do you") || text.startsWith("are you") || + text.startsWith("will you") || text.startsWith("is it"); + } + + private boolean isGreeting(String text) { + return text.startsWith("hi") || text.startsWith("hello") || + text.startsWith("hey") || text.startsWith("good morning") || + text.startsWith("good afternoon") || text.startsWith("good evening") || + text.contains("how are you") || text.contains("what's up"); + } + + private boolean isThanks(String text) { + return text.contains("thank") || text.contains("thanks") || + text.contains("thx") || text.contains("ty"); + } + + private boolean isTimeRelated(String text) { + return text.contains("when") || text.contains("time") || + text.contains("now") || text.contains("later") || + text.contains("tomorrow") || text.contains("today") || + text.contains("come") || text.contains("arrive"); + } + + /** + * Set the listener for reply selection + */ + public void setOnReplySelectedListener(OnReplySelectedListener listener) { + this.listener = listener; + } + + /** + * Check if currently visible + */ + public boolean isShowing() { + return isVisible; + } +} diff --git a/TMessagesProj/src/main/java/com/overspend1/overgram/ui/components/TranslationPropositionView.java b/TMessagesProj/src/main/java/com/overspend1/overgram/ui/components/TranslationPropositionView.java new file mode 100644 index 000000000..93fa34d3b --- /dev/null +++ b/TMessagesProj/src/main/java/com/overspend1/overgram/ui/components/TranslationPropositionView.java @@ -0,0 +1,364 @@ +/* + * This is the source code of Overgram for Android. + * + * We do not and cannot prevent the use of our code, + * but be respectful and credit the original author. + * + * Copyright @overspend1, 2024 + */ + +package com.overspend1.overgram.ui.components; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.animation.OvershootInterpolator; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.LayoutHelper; + +/** + * TranslationPropositionView - Fluid animated translation preview + * + * Shows original and translated text with smooth slide-up animation + * Users can accept (checkmark) or cancel (X) the translation + */ +public class TranslationPropositionView extends FrameLayout { + + private LinearLayout contentLayout; + private TextView originalLabel; + private TextView originalText; + private TextView translatedLabel; + private TextView translatedText; + private View acceptButton; + private View cancelButton; + private TextView acceptIcon; + private TextView cancelIcon; + + private Paint backgroundPaint; + private RectF backgroundRect; + private float animationProgress = 0f; + private boolean isShowing = false; + + private OnTranslationAcceptListener acceptListener; + private OnTranslationCancelListener cancelListener; + + private Theme.ResourcesProvider resourcesProvider; + + public interface OnTranslationAcceptListener { + void onAccept(String translatedText); + } + + public interface OnTranslationCancelListener { + void onCancel(); + } + + public TranslationPropositionView(Context context, Theme.ResourcesProvider resourcesProvider) { + super(context); + this.resourcesProvider = resourcesProvider; + + setWillNotDraw(false); + setVisibility(GONE); + setAlpha(0f); + + // Background paint + backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + backgroundRect = new RectF(); + + // Content container + contentLayout = new LinearLayout(context); + contentLayout.setOrientation(LinearLayout.VERTICAL); + contentLayout.setPadding( + AndroidUtilities.dp(16), + AndroidUtilities.dp(12), + AndroidUtilities.dp(16), + AndroidUtilities.dp(12) + ); + + // Original text section + originalLabel = new TextView(context); + originalLabel.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 11); + originalLabel.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText, resourcesProvider)); + originalLabel.setText(LocaleController.getString("OriginalText", R.string.OriginalText).toUpperCase()); + originalLabel.setTypeface(AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_ROBOTO_MEDIUM)); + contentLayout.addView(originalLabel, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 0, 0, 4)); + + originalText = new TextView(context); + originalText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + originalText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourcesProvider)); + originalText.setMaxLines(3); + originalText.setEllipsize(TextUtils.TruncateAt.END); + contentLayout.addView(originalText, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 0, 0, 12)); + + // Translated text section + translatedLabel = new TextView(context); + translatedLabel.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 11); + translatedLabel.setTextColor(Theme.getColor(Theme.key_featuredStickers_addButton, resourcesProvider)); + translatedLabel.setText(LocaleController.getString("TranslatedText", R.string.TranslatedText).toUpperCase()); + translatedLabel.setTypeface(AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_ROBOTO_MEDIUM)); + contentLayout.addView(translatedLabel, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 0, 0, 4)); + + translatedText = new TextView(context); + translatedText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + translatedText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourcesProvider)); + translatedText.setMaxLines(3); + translatedText.setEllipsize(TextUtils.TruncateAt.END); + translatedText.setTypeface(AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_ROBOTO_MEDIUM)); + contentLayout.addView(translatedText, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + addView(contentLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP, 48, 0, 48, 0)); + + // Action buttons + acceptButton = new View(context) { + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setColor(Theme.getColor(Theme.key_featuredStickers_addButton, resourcesProvider)); + canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, Math.min(getWidth(), getHeight()) / 2f, paint); + } + }; + acceptButton.setOnClickListener(v -> handleAccept()); + addView(acceptButton, LayoutHelper.createFrame(40, 40, Gravity.RIGHT | Gravity.CENTER_VERTICAL, 0, 0, 4, 0)); + + acceptIcon = new TextView(context); + acceptIcon.setText("✓"); + acceptIcon.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); + acceptIcon.setTextColor(0xFFFFFFFF); + acceptIcon.setGravity(Gravity.CENTER); + acceptIcon.setTypeface(AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_ROBOTO_MEDIUM)); + addView(acceptIcon, LayoutHelper.createFrame(40, 40, Gravity.RIGHT | Gravity.CENTER_VERTICAL, 0, 0, 4, 0)); + + cancelButton = new View(context) { + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setColor(Theme.getColor(Theme.key_text_RedBold, resourcesProvider)); + canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, Math.min(getWidth(), getHeight()) / 2f, paint); + } + }; + cancelButton.setOnClickListener(v -> handleCancel()); + addView(cancelButton, LayoutHelper.createFrame(40, 40, Gravity.LEFT | Gravity.CENTER_VERTICAL, 4, 0, 0, 0)); + + cancelIcon = new TextView(context); + cancelIcon.setText("✕"); + cancelIcon.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); + cancelIcon.setTextColor(0xFFFFFFFF); + cancelIcon.setGravity(Gravity.CENTER); + cancelIcon.setTypeface(AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_ROBOTO_MEDIUM)); + addView(cancelIcon, LayoutHelper.createFrame(40, 40, Gravity.LEFT | Gravity.CENTER_VERTICAL, 4, 0, 0, 0)); + + // Add ripple effect to buttons + acceptButton.setBackground(Theme.createSelectorDrawable(Theme.getColor(Theme.key_listSelector, resourcesProvider), 3)); + cancelButton.setBackground(Theme.createSelectorDrawable(Theme.getColor(Theme.key_listSelector, resourcesProvider), 3)); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + // Draw rounded background + backgroundPaint.setColor(Theme.getColor(Theme.key_chat_inBubble, resourcesProvider)); + backgroundPaint.setAlpha((int) (255 * animationProgress * 0.95f)); + + backgroundRect.set(0, 0, getWidth(), getHeight()); + canvas.drawRoundRect(backgroundRect, AndroidUtilities.dp(12), AndroidUtilities.dp(12), backgroundPaint); + } + + /** + * Show translation proposition with fluid animation + */ + public void showProposition(String original, String translated, + OnTranslationAcceptListener acceptListener, + OnTranslationCancelListener cancelListener) { + if (isShowing) { + return; + } + + this.acceptListener = acceptListener; + this.cancelListener = cancelListener; + + // Set text + originalText.setText(original); + translatedText.setText(translated); + + // Prepare for animation + setVisibility(VISIBLE); + isShowing = true; + + // Slide up + fade in animation + ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); + animator.setDuration(400); + animator.setInterpolator(new OvershootInterpolator(0.8f)); + animator.addUpdateListener(animation -> { + animationProgress = (float) animation.getAnimatedValue(); + + // Slide up + setTranslationY(AndroidUtilities.dp(20) * (1f - animationProgress)); + + // Fade in + setAlpha(animationProgress); + + // Scale buttons + acceptButton.setScaleX(animationProgress); + acceptButton.setScaleY(animationProgress); + acceptIcon.setScaleX(animationProgress); + acceptIcon.setScaleY(animationProgress); + cancelButton.setScaleX(animationProgress); + cancelButton.setScaleY(animationProgress); + cancelIcon.setScaleX(animationProgress); + cancelIcon.setScaleY(animationProgress); + + invalidate(); + }); + animator.start(); + + // Add subtle bounce to text + animateTextEntrance(originalText, 100); + animateTextEntrance(translatedText, 200); + } + + /** + * Hide with fluid animation + */ + public void hideProposition() { + if (!isShowing) { + return; + } + + ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f); + animator.setDuration(300); + animator.addUpdateListener(animation -> { + animationProgress = (float) animation.getAnimatedValue(); + + // Slide down + setTranslationY(AndroidUtilities.dp(20) * (1f - animationProgress)); + + // Fade out + setAlpha(animationProgress); + + invalidate(); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + setVisibility(GONE); + isShowing = false; + } + }); + animator.start(); + } + + private void animateTextEntrance(TextView textView, long delay) { + textView.setAlpha(0f); + textView.setTranslationY(AndroidUtilities.dp(10)); + + textView.animate() + .alpha(1f) + .translationY(0) + .setDuration(300) + .setStartDelay(delay) + .setInterpolator(new OvershootInterpolator(0.8f)) + .start(); + } + + private void handleAccept() { + // Pulse animation + acceptButton.animate() + .scaleX(1.2f) + .scaleY(1.2f) + .setDuration(100) + .withEndAction(() -> { + acceptButton.animate() + .scaleX(1f) + .scaleY(1f) + .setDuration(100) + .start(); + }) + .start(); + + acceptIcon.animate() + .scaleX(1.2f) + .scaleY(1.2f) + .setDuration(100) + .withEndAction(() -> { + acceptIcon.animate() + .scaleX(1f) + .scaleY(1f) + .setDuration(100) + .start(); + }) + .start(); + + // Callback and hide + AndroidUtilities.runOnUIThread(() -> { + if (acceptListener != null) { + acceptListener.onAccept(translatedText.getText().toString()); + } + hideProposition(); + }, 150); + } + + private void handleCancel() { + // Pulse animation + cancelButton.animate() + .scaleX(1.2f) + .scaleY(1.2f) + .setDuration(100) + .withEndAction(() -> { + cancelButton.animate() + .scaleX(1f) + .scaleY(1f) + .setDuration(100) + .start(); + }) + .start(); + + cancelIcon.animate() + .scaleX(1.2f) + .scaleY(1.2f) + .setDuration(100) + .withEndAction(() -> { + cancelIcon.animate() + .scaleX(1f) + .scaleY(1f) + .setDuration(100) + .start(); + }) + .start(); + + // Callback and hide + AndroidUtilities.runOnUIThread(() -> { + if (cancelListener != null) { + cancelListener.onCancel(); + } + hideProposition(); + }, 150); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + // Consume all touch events to prevent click-through + return true; + } + + public boolean isShowing() { + return isShowing; + } +} diff --git a/TMessagesProj/src/main/java/com/overspend1/overgram/ui/preferences/LiquidGlassPreferencesActivity.java b/TMessagesProj/src/main/java/com/overspend1/overgram/ui/preferences/LiquidGlassPreferencesActivity.java index b1157e37e..52ef167a2 100644 --- a/TMessagesProj/src/main/java/com/overspend1/overgram/ui/preferences/LiquidGlassPreferencesActivity.java +++ b/TMessagesProj/src/main/java/com/overspend1/overgram/ui/preferences/LiquidGlassPreferencesActivity.java @@ -10,13 +10,25 @@ package com.overspend1.overgram.ui.preferences; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.Shader; import android.view.View; import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.SeekBar; +import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.exteragram.messenger.preferences.BasePreferencesActivity; import com.overspend1.overgram.OverConfig; +import com.overspend1.overgram.ui.liquidglass.GlassParameters; import com.overspend1.overgram.ui.liquidglass.LiquidGlassPreset; +import com.overspend1.overgram.ui.liquidglass.LiquidGlassEffect; +import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; import org.telegram.ui.ActionBar.AlertDialog; @@ -32,6 +44,7 @@ public class LiquidGlassPreferencesActivity extends BasePreferencesActivity { private int applyToChatBubblesRow; private int applyToDialogsRow; + private int applyToSystemRow; private int divider2Row; private int presetRow; @@ -54,6 +67,7 @@ public class LiquidGlassPreferencesActivity extends BasePreferencesActivity { if (OverConfig.liquidGlassEnabled) { applyToChatBubblesRow = newRow(); applyToDialogsRow = newRow(); + applyToSystemRow = newRow(); divider2Row = newRow(); presetRow = newRow(); @@ -66,6 +80,7 @@ public class LiquidGlassPreferencesActivity extends BasePreferencesActivity { } else { applyToChatBubblesRow = -1; applyToDialogsRow = -1; + applyToSystemRow = -1; divider2Row = -1; presetRow = -1; blurRadiusRow = -1; @@ -84,11 +99,7 @@ public class LiquidGlassPreferencesActivity extends BasePreferencesActivity { ((TextCheckCell) view).setChecked(OverConfig.liquidGlassEnabled); updateRowsId(); - if (OverConfig.liquidGlassEnabled) { - listAdapter.notifyItemRangeInserted(divider1Row + 1, 11); - } else { - listAdapter.notifyItemRangeRemoved(divider1Row + 1, 11); - } + listAdapter.notifyDataSetChanged(); } else if (position == applyToChatBubblesRow) { OverConfig.liquidGlassApplyToChatBubbles ^= true; OverConfig.editor.putBoolean("liquidGlassApplyToChatBubbles", OverConfig.liquidGlassApplyToChatBubbles).apply(); @@ -97,6 +108,10 @@ public class LiquidGlassPreferencesActivity extends BasePreferencesActivity { OverConfig.liquidGlassApplyToDialogs ^= true; OverConfig.editor.putBoolean("liquidGlassApplyToDialogs", OverConfig.liquidGlassApplyToDialogs).apply(); ((TextCheckCell) view).setChecked(OverConfig.liquidGlassApplyToDialogs); + } else if (position == applyToSystemRow) { + OverConfig.liquidGlassApplyToSystemSurfaces ^= true; + OverConfig.editor.putBoolean("liquidGlassApplyToSystemSurfaces", OverConfig.liquidGlassApplyToSystemSurfaces).apply(); + ((TextCheckCell) view).setChecked(OverConfig.liquidGlassApplyToSystemSurfaces); } else if (position == presetRow) { showPresetSelector(); } else if (position == blurRadiusRow) { @@ -122,6 +137,15 @@ public class LiquidGlassPreferencesActivity extends BasePreferencesActivity { } } + private GlassParameters buildGlassParameters() { + LiquidGlassPreset preset = LiquidGlassPreset.fromId(OverConfig.liquidGlassPreset); + GlassParameters params = preset.toParameters(); + params.blurRadius = OverConfig.liquidGlassBlurRadius; + params.opacity = OverConfig.liquidGlassOpacity; + params.clamp(); + return params; + } + private void showPresetSelector() { LiquidGlassPreset[] presets = LiquidGlassPreset.values(); String[] names = new String[presets.length]; @@ -149,12 +173,68 @@ public class LiquidGlassPreferencesActivity extends BasePreferencesActivity { } private void showSlider(String title, int min, int max, int current, SliderCallback callback) { - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + Context context = getParentActivity(); + if (context == null) { + return; + } + + int initial = current; + + AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(title); - // Simple implementation - can be enhanced with actual slider view - builder.setMessage("Current value: " + current + "\nUse custom slider implementation here"); + LinearLayout container = new LinearLayout(context); + container.setOrientation(LinearLayout.VERTICAL); + int pad = AndroidUtilities.dp(20); + container.setPadding(pad, pad, pad, pad); + + TextView valueView = new TextView(context); + valueView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + valueView.setTextSize(16); + valueView.setText(String.valueOf(current)); + container.addView(valueView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + SeekBar seekBar = new SeekBar(context); + seekBar.setMax(max - min); + seekBar.setProgress(current - min); + container.addView(seekBar, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + // Glass preview + GlassPreviewView previewView = new GlassPreviewView(context); + previewView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, AndroidUtilities.dp(120))); + previewView.setEffect(new LiquidGlassEffect(buildGlassParameters())); + container.addView(previewView); + + seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + float value = min + progress; + valueView.setText(String.valueOf((int) value)); + callback.onValueChanged(value); + // Refresh preview using current config + if (previewView.getEffect() != null) { + previewView.getEffect().setParameters(buildGlassParameters()); + previewView.invalidate(); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { } + }); + + builder.setView(container); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), (dialog, which) -> { + // revert if cancelled + callback.onValueChanged(initial); + if (previewView.getEffect() != null) { + previewView.getEffect().setParameters(buildGlassParameters()); + previewView.invalidate(); + } + }); showDialog(builder.create()); } @@ -243,6 +323,12 @@ public class LiquidGlassPreferencesActivity extends BasePreferencesActivity { OverConfig.liquidGlassApplyToDialogs, false ); + } else if (position == applyToSystemRow) { + textCheckCell.setTextAndCheck( + LocaleController.getString(R.string.LiquidGlassApplyToSystem), + OverConfig.liquidGlassApplyToSystemSurfaces, + false + ); } break; } @@ -262,4 +348,65 @@ public class LiquidGlassPreferencesActivity extends BasePreferencesActivity { return 5; } } + + /** + * Small preview surface that renders the current glass parameters. + */ + private static class GlassPreviewView extends View { + private LiquidGlassEffect effect; + private Bitmap background; + private final RectF rect = new RectF(); + private final Paint bgPaint = new Paint(); + + public GlassPreviewView(Context context) { + super(context); + setWillNotDraw(false); + } + + public void setEffect(LiquidGlassEffect effect) { + this.effect = effect; + invalidate(); + } + + public LiquidGlassEffect getEffect() { + return effect; + } + + private void ensureBackground() { + int w = Math.max(1, getWidth()); + int h = Math.max(1, getHeight()); + if (background != null && !background.isRecycled() && background.getWidth() == w && background.getHeight() == h) { + return; + } + if (background != null && !background.isRecycled()) { + background.recycle(); + } + background = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(background); + int topColor = Theme.getColor(Theme.key_windowBackgroundWhite); + int bottomColor = Theme.getColor(Theme.key_actionBarDefault); + LinearGradient gradient = new LinearGradient(0, 0, w, h, topColor, bottomColor, Shader.TileMode.CLAMP); + bgPaint.setShader(gradient); + canvas.drawRect(0, 0, w, h, bgPaint); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + ensureBackground(); + if (effect != null && background != null && !background.isRecycled()) { + rect.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()); + effect.apply(canvas, rect, background); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (background != null && !background.isRecycled()) { + background.recycle(); + } + background = null; + } + } } diff --git a/TMessagesProj/src/main/java/com/overspend1/overgram/ui/preferences/OvergramPreferencesActivity.java b/TMessagesProj/src/main/java/com/overspend1/overgram/ui/preferences/OvergramPreferencesActivity.java index 6ef9bcf98..b16b6640c 100644 --- a/TMessagesProj/src/main/java/com/overspend1/overgram/ui/preferences/OvergramPreferencesActivity.java +++ b/TMessagesProj/src/main/java/com/overspend1/overgram/ui/preferences/OvergramPreferencesActivity.java @@ -25,6 +25,7 @@ import com.overspend1.overgram.ui.preferences.utils.OverUi; import com.overspend1.overgram.utils.OverState; import org.jetbrains.annotations.NotNull; import org.telegram.messenger.*; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Cells.*; import org.telegram.ui.Components.BulletinFactory; @@ -58,6 +59,7 @@ public class OvergramPreferencesActivity extends BasePreferencesActivity impleme private int disableAdsRow; private int localPremiumRow; private int filtersRow; + private int quickActionsRow; private int qolDividerRow; private int customizationHeaderRow; @@ -75,6 +77,20 @@ public class OvergramPreferencesActivity extends BasePreferencesActivity impleme private int aiSettingsRow; private int aiDividerRow; + private int productivityHeaderRow; + private int smartRepliesRow; + private int autoTranslateIncomingRow; + private int autoTranslateOutgoingRow; + private int autoTranslateLangRow; + private int autoTranslateIncomingLangRow; + private int productivityDividerRow; + + private int yagizTranslatorHeaderRow; + private int yagizTranslatorEnabledRow; + private int yagizTranslatorApiTypeRow; + private int yagizTranslatorAutoDetectRow; + private int yagizTranslatorDividerRow; + private int ayuSyncHeaderRow; private int ayuSyncStatusBtnRow; private int ayuSyncDividerRow; @@ -120,6 +136,7 @@ public class OvergramPreferencesActivity extends BasePreferencesActivity impleme disableAdsRow = newRow(); localPremiumRow = newRow(); filtersRow = newRow(); + quickActionsRow = newRow(); qolDividerRow = newRow(); customizationHeaderRow = newRow(); @@ -137,6 +154,20 @@ public class OvergramPreferencesActivity extends BasePreferencesActivity impleme aiSettingsRow = newRow(); aiDividerRow = newRow(); + productivityHeaderRow = newRow(); + smartRepliesRow = newRow(); + autoTranslateIncomingRow = newRow(); + autoTranslateOutgoingRow = newRow(); + autoTranslateLangRow = newRow(); + autoTranslateIncomingLangRow = newRow(); + productivityDividerRow = newRow(); + + yagizTranslatorHeaderRow = newRow(); + yagizTranslatorEnabledRow = newRow(); + yagizTranslatorApiTypeRow = newRow(); + yagizTranslatorAutoDetectRow = newRow(); + yagizTranslatorDividerRow = newRow(); + ayuSyncHeaderRow = newRow(); ayuSyncStatusBtnRow = newRow(); ayuSyncDividerRow = newRow(); @@ -285,6 +316,8 @@ public class OvergramPreferencesActivity extends BasePreferencesActivity impleme } else { presentFragment(new RegexFiltersPreferencesActivity()); } + } else if (position == quickActionsRow) { + showQuickActions(); } else if (position == showGhostToggleInDrawerRow) { OverConfig.editor.putBoolean("showGhostToggleInDrawer", OverConfig.showGhostToggleInDrawer ^= true).apply(); ((TextCheckCell) view).setChecked(OverConfig.showGhostToggleInDrawer); @@ -317,6 +350,27 @@ public class OvergramPreferencesActivity extends BasePreferencesActivity impleme presentFragment(new LiquidGlassPreferencesActivity()); } else if (position == aiSettingsRow) { presentFragment(new AiPreferencesActivity()); + } else if (position == smartRepliesRow) { + OverConfig.editor.putBoolean("smartQuickReplies", OverConfig.smartQuickReplies ^= true).apply(); + ((TextCheckCell) view).setChecked(OverConfig.smartQuickReplies); + } else if (position == autoTranslateIncomingRow) { + OverConfig.editor.putBoolean("autoTranslateIncomingDefault", OverConfig.autoTranslateIncomingDefault ^= true).apply(); + ((TextCheckCell) view).setChecked(OverConfig.autoTranslateIncomingDefault); + } else if (position == autoTranslateOutgoingRow) { + OverConfig.editor.putBoolean("autoTranslateOutgoingDefault", OverConfig.autoTranslateOutgoingDefault ^= true).apply(); + ((TextCheckCell) view).setChecked(OverConfig.autoTranslateOutgoingDefault); + } else if (position == autoTranslateLangRow) { + showLanguagePicker(false); // false = outgoing language + } else if (position == autoTranslateIncomingLangRow) { + showLanguagePicker(true); // true = incoming language + } else if (position == yagizTranslatorEnabledRow) { + OverConfig.editor.putBoolean("yagizTranslatorEnabled", OverConfig.yagizTranslatorEnabled ^= true).apply(); + ((TextCheckCell) view).setChecked(OverConfig.yagizTranslatorEnabled); + } else if (position == yagizTranslatorApiTypeRow) { + showYagizTranslatorApiPicker(); + } else if (position == yagizTranslatorAutoDetectRow) { + OverConfig.editor.putBoolean("yagizTranslatorAutoDetect", OverConfig.yagizTranslatorAutoDetect ^= true).apply(); + ((TextCheckCell) view).setChecked(OverConfig.yagizTranslatorAutoDetect); } else if (position == ayuSyncStatusBtnRow) { presentFragment(new OverSyncPreferencesActivity()); } else if (position == WALModeRow) { @@ -391,6 +445,10 @@ public class OvergramPreferencesActivity extends BasePreferencesActivity impleme textCell.setTextAndValue(LocaleController.getString(R.string.DeletedMarkText), OverConfig.getDeletedMark(), true); } else if (position == editedMarkTextRow) { textCell.setTextAndValue(LocaleController.getString(R.string.EditedMarkText), OverConfig.getEditedMark(), true); + } else if (position == quickActionsRow) { + textCell.setTextAndValue(LocaleController.getString(R.string.OvergramQuickActions), + LocaleController.getString(R.string.OvergramQuickActionsHint), + true); } else if (position == liquidGlassBtnRow) { textCell.setTextAndValue(LocaleController.getString(R.string.LiquidGlassHeader), OverConfig.liquidGlassEnabled ? LocaleController.getString("NotificationsOn", R.string.NotificationsOn) : LocaleController.getString("NotificationsOff", R.string.NotificationsOff), @@ -399,6 +457,17 @@ public class OvergramPreferencesActivity extends BasePreferencesActivity impleme textCell.setTextAndValue(LocaleController.getString(R.string.OvergramAiSettings), OverConfig.geminiEnabled ? LocaleController.getString("NotificationsOn", R.string.NotificationsOn) : LocaleController.getString("NotificationsOff", R.string.NotificationsOff), true); + } else if (position == autoTranslateLangRow) { + String langCode = OverConfig.autoTranslateOutgoingLangDefault; + String langName = getLanguageDisplayName(langCode); + textCell.setTextAndValue(LocaleController.getString(R.string.AutoTranslateOutgoingLang), langName, true); + } else if (position == autoTranslateIncomingLangRow) { + String langCode = OverConfig.autoTranslateIncomingLangDefault; + String langName = getLanguageDisplayName(langCode); + textCell.setTextAndValue(LocaleController.getString(R.string.AutoTranslateIncomingLang), langName, true); + } else if (position == yagizTranslatorApiTypeRow) { + String apiName = com.overspend1.overgram.translator.YagizTranslator.getApiName(OverConfig.yagizTranslatorApiType); + textCell.setTextAndValue(LocaleController.getString(R.string.YagizTranslatorApiType), apiName, true); } else if (position == ayuSyncStatusBtnRow) { var status = OverSyncState.getConnectionStateString(); @@ -428,6 +497,10 @@ public class OvergramPreferencesActivity extends BasePreferencesActivity impleme headerCell.setText(LocaleController.getString(R.string.LiquidGlassHeader)); } else if (position == aiHeaderRow) { headerCell.setText(LocaleController.getString(R.string.OvergramAiHeader)); + } else if (position == productivityHeaderRow) { + headerCell.setText(LocaleController.getString(R.string.ProductivityHeader)); + } else if (position == yagizTranslatorHeaderRow) { + headerCell.setText(LocaleController.getString(R.string.YagizTranslatorHeader)); } else if (position == ayuSyncHeaderRow) { headerCell.setText(LocaleController.getString(R.string.AyuSyncHeader)); } else if (position == debugHeaderRow) { @@ -457,6 +530,16 @@ public class OvergramPreferencesActivity extends BasePreferencesActivity impleme textCheckCell.setTextAndCheck(LocaleController.getString(R.string.ShowKllButtonInDrawer), OverConfig.showKillButtonInDrawer, false); } else if (position == WALModeRow) { textCheckCell.setTextAndCheck(LocaleController.getString(R.string.WALMode), OverConfig.WALMode, false); + } else if (position == smartRepliesRow) { + textCheckCell.setTextAndCheck(LocaleController.getString(R.string.SmartQuickReplies), OverConfig.smartQuickReplies, true); + } else if (position == autoTranslateIncomingRow) { + textCheckCell.setTextAndCheck(LocaleController.getString(R.string.AutoTranslateIncoming), OverConfig.autoTranslateIncomingDefault, true); + } else if (position == autoTranslateOutgoingRow) { + textCheckCell.setTextAndCheck(LocaleController.getString(R.string.AutoTranslateOutgoing), OverConfig.autoTranslateOutgoingDefault, true); + } else if (position == yagizTranslatorEnabledRow) { + textCheckCell.setTextAndCheck(LocaleController.getString(R.string.YagizTranslatorEnabled), OverConfig.yagizTranslatorEnabled, true); + } else if (position == yagizTranslatorAutoDetectRow) { + textCheckCell.setTextAndCheck(LocaleController.getString(R.string.YagizTranslatorAutoDetect), OverConfig.yagizTranslatorAutoDetect, false); } break; case 18: @@ -517,16 +600,22 @@ public class OvergramPreferencesActivity extends BasePreferencesActivity impleme position == customizationDividerRow || position == liquidGlassDividerRow || position == aiDividerRow || + position == productivityDividerRow || + position == yagizTranslatorDividerRow || position == ayuSyncDividerRow || position == buttonsDividerRow ) { return 1; } else if ( - position == messageSavingBtnRow || + position == messageSavingBtnRow || position == deletedMarkTextRow || position == editedMarkTextRow || + position == quickActionsRow || position == liquidGlassBtnRow || position == aiSettingsRow || + position == autoTranslateLangRow || + position == autoTranslateIncomingLangRow || + position == yagizTranslatorApiTypeRow || position == ayuSyncStatusBtnRow || position == clearAyuDatabaseBtnRow || position == eraseLocalDatabaseBtnRow @@ -539,6 +628,8 @@ public class OvergramPreferencesActivity extends BasePreferencesActivity impleme position == customizationHeaderRow || position == liquidGlassHeaderRow || position == aiHeaderRow || + position == productivityHeaderRow || + position == yagizTranslatorHeaderRow || position == ayuSyncHeaderRow || position == debugHeaderRow ) { @@ -555,8 +646,168 @@ public class OvergramPreferencesActivity extends BasePreferencesActivity impleme position == filtersRow ) { return TOGGLE_BUTTON_VIEW; + } else if ( + position == smartRepliesRow || + position == autoTranslateIncomingRow || + position == autoTranslateOutgoingRow || + position == yagizTranslatorEnabledRow || + position == yagizTranslatorAutoDetectRow + ) { + return 5; } return 5; } } + + private void showQuickActions() { + if (getParentActivity() == null) { + return; + } + String[] options = new String[] { + LocaleController.getString(R.string.OvergramQuickActionToggleGlass), + LocaleController.getString(R.string.OvergramQuickActionToggleGhost), + LocaleController.getString(R.string.OvergramQuickActionOpenGlass), + LocaleController.getString(R.string.OvergramQuickActionOpenAi) + }; + + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString(R.string.OvergramQuickActions)); + builder.setItems(options, (dialog, which) -> { + switch (which) { + case 0: { + OverConfig.liquidGlassEnabled = !OverConfig.liquidGlassEnabled; + OverConfig.editor.putBoolean("liquidGlassEnabled", OverConfig.liquidGlassEnabled).apply(); + listAdapter.notifyItemChanged(liquidGlassBtnRow, payload); + BulletinFactory.of(this).createSimpleBulletin( + OverConfig.liquidGlassEnabled ? R.raw.done : R.raw.deactivate, + OverConfig.liquidGlassEnabled ? LocaleController.getString("NotificationsOn", R.string.NotificationsOn) : LocaleController.getString("NotificationsOff", R.string.NotificationsOff) + ).show(); + break; + } + case 1: { + OverConfig.toggleGhostMode(); + updateGhostViews(); + BulletinFactory.of(this).createSimpleBulletin( + OverConfig.isGhostModeActive() ? R.raw.done : R.raw.deactivate, + OverConfig.isGhostModeActive() ? LocaleController.getString(R.string.GhostModeEnabled) : LocaleController.getString(R.string.GhostModeDisabled) + ).show(); + break; + } + case 2: + presentFragment(new LiquidGlassPreferencesActivity()); + break; + case 3: + presentFragment(new AiPreferencesActivity()); + break; + default: + break; + } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); + } + + private String getLanguageDisplayName(String langCode) { + if (langCode == null || langCode.isEmpty()) { + langCode = "en"; + } + + // Common language names + switch (langCode.toLowerCase()) { + case "en": return "English"; + case "es": return "Spanish"; + case "fr": return "French"; + case "de": return "German"; + case "it": return "Italian"; + case "pt": return "Portuguese"; + case "ru": return "Russian"; + case "ja": return "Japanese"; + case "ko": return "Korean"; + case "zh": return "Chinese"; + case "ar": return "Arabic"; + case "hi": return "Hindi"; + case "tr": return "Turkish"; + case "pl": return "Polish"; + case "uk": return "Ukrainian"; + case "nl": return "Dutch"; + case "sv": return "Swedish"; + case "no": return "Norwegian"; + case "da": return "Danish"; + case "fi": return "Finnish"; + default: return langCode.toUpperCase(); + } + } + + private void showLanguagePicker(boolean isIncoming) { + if (getParentActivity() == null) { + return; + } + + String[][] languages = { + {"en", "English"}, + {"es", "Spanish"}, + {"fr", "French"}, + {"de", "German"}, + {"it", "Italian"}, + {"pt", "Portuguese"}, + {"ru", "Russian"}, + {"ja", "Japanese"}, + {"ko", "Korean"}, + {"zh", "Chinese"}, + {"ar", "Arabic"}, + {"hi", "Hindi"}, + {"tr", "Turkish"}, + {"pl", "Polish"}, + {"uk", "Ukrainian"}, + {"nl", "Dutch"}, + {"sv", "Swedish"}, + {"no", "Norwegian"}, + {"da", "Danish"}, + {"fi", "Finnish"} + }; + + String[] options = new String[languages.length]; + for (int i = 0; i < languages.length; i++) { + options[i] = languages[i][1]; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString(isIncoming ? R.string.AutoTranslateIncomingLang : R.string.AutoTranslateOutgoingLang)); + builder.setItems(options, (dialog, which) -> { + String selectedLang = languages[which][0]; + if (isIncoming) { + OverConfig.autoTranslateIncomingLangDefault = selectedLang; + OverConfig.editor.putString("autoTranslateIncomingLangDefault", selectedLang).apply(); + listAdapter.notifyItemChanged(autoTranslateIncomingLangRow); + } else { + OverConfig.autoTranslateOutgoingLangDefault = selectedLang; + OverConfig.editor.putString("autoTranslateOutgoingLangDefault", selectedLang).apply(); + listAdapter.notifyItemChanged(autoTranslateLangRow); + } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); + } + + private void showYagizTranslatorApiPicker() { + if (getParentActivity() == null) { + return; + } + + String[] apiNames = { + com.overspend1.overgram.translator.YagizTranslator.getApiName(com.overspend1.overgram.translator.YagizTranslator.API_GEMINI), + com.overspend1.overgram.translator.YagizTranslator.getApiName(com.overspend1.overgram.translator.YagizTranslator.API_GOOGLE), + com.overspend1.overgram.translator.YagizTranslator.getApiName(com.overspend1.overgram.translator.YagizTranslator.API_DEEPL) + }; + + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString(R.string.YagizTranslatorApiType)); + builder.setItems(apiNames, (dialog, which) -> { + OverConfig.yagizTranslatorApiType = which; + OverConfig.editor.putInt("yagizTranslatorApiType", which).apply(); + listAdapter.notifyItemChanged(yagizTranslatorApiTypeRow); + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); + } } 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 e1f1902c3..5ad0a6c62 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java @@ -22,6 +22,7 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; +import android.graphics.Rect; import android.graphics.Shader; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; @@ -51,6 +52,9 @@ import androidx.core.graphics.ColorUtils; import com.overspend1.overgram.OverFilter; import com.overspend1.overgram.OverUtils; +import com.overspend1.overgram.OverConfig; +import com.overspend1.overgram.ui.liquidglass.GlassParameters; +import com.overspend1.overgram.ui.liquidglass.LiquidGlassEffect; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.ChatObject; @@ -132,8 +136,8 @@ public class DialogCell extends BaseCell { public static final int SENT_STATE_READ = 2; public boolean drawAvatar = true; public int messagePaddingStart = 72; - public int heightDefault = 72; - public int heightThreeLines = 78; + public int heightDefault = 69; // Overgram: Reduced from 72 to reduce crowding + public int heightThreeLines = 75; // Overgram: Reduced from 78 to reduce crowding public TLRPC.TL_forumTopic forumTopic; public boolean useFromUserAsAvatar; private boolean isTopic; @@ -166,6 +170,12 @@ public class DialogCell extends BaseCell { private Path thumbPath = new Path(); private SpoilerEffect thumbSpoiler = new SpoilerEffect(); + // Liquid glass overlay support for dialog list + private LiquidGlassEffect glassEffect; + private Bitmap glassBackgroundCache; + private long lastGlassCaptureTime; + private final Rect glassTempRect = new Rect(); + public void setMoving(boolean moving) { this.moving = moving; } @@ -174,6 +184,91 @@ public class DialogCell extends BaseCell { return moving; } + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + clearGlassResources(); + } + + private boolean shouldUseLiquidGlass() { + return OverConfig.liquidGlassEnabled && (OverConfig.liquidGlassApplyToDialogs || OverConfig.liquidGlassApplyToSystemSurfaces); + } + + private GlassParameters buildGlassParameters() { + com.overspend1.overgram.ui.liquidglass.LiquidGlassPreset preset = com.overspend1.overgram.ui.liquidglass.LiquidGlassPreset.fromId(OverConfig.liquidGlassPreset); + GlassParameters params = preset.toParameters(); + params.blurRadius = OverConfig.liquidGlassBlurRadius; + params.opacity = OverConfig.liquidGlassOpacity; + params.clamp(); + return params; + } + + private void ensureGlassEffect() { + if (glassEffect == null) { + glassEffect = new LiquidGlassEffect(buildGlassParameters()); + } else { + glassEffect.setParameters(buildGlassParameters()); + } + } + + private Bitmap captureGlassBackground() { + View parent = (View) getParent(); + if (parent == null) { + return null; + } + int w = getMeasuredWidth(); + int h = getMeasuredHeight(); + if (w <= 0 || h <= 0) { + return null; + } + long now = System.currentTimeMillis(); + if (glassBackgroundCache != null && !glassBackgroundCache.isRecycled() && now - lastGlassCaptureTime < 100) { + return glassBackgroundCache; + } + try { + Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + Drawable bg = parent.getBackground(); + if (bg != null) { + bg.setBounds(0, 0, parent.getWidth(), parent.getHeight()); + bg.draw(canvas); + } else { + canvas.drawColor(Color.TRANSPARENT); + } + if (glassBackgroundCache != null && !glassBackgroundCache.isRecycled()) { + glassBackgroundCache.recycle(); + } + glassBackgroundCache = bitmap; + lastGlassCaptureTime = now; + return bitmap; + } catch (Exception e) { + return null; + } + } + + private void drawLiquidGlass(Canvas canvas) { + ensureGlassEffect(); + Bitmap background = captureGlassBackground(); + if (glassEffect != null && background != null && !background.isRecycled()) { + glassTempRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); + canvas.save(); + glassEffect.apply(canvas, new RectF(glassTempRect), background); + canvas.restore(); + } + } + + private void clearGlassResources() { + if (glassBackgroundCache != null && !glassBackgroundCache.isRecycled()) { + glassBackgroundCache.recycle(); + } + glassBackgroundCache = null; + lastGlassCaptureTime = 0; + if (glassEffect != null) { + glassEffect.recycle(); + glassEffect = null; + } + } + public void setForumTopic(TLRPC.TL_forumTopic topic, long dialog_id, MessageObject messageObject, boolean showTopicIconInName, boolean animated) { forumTopic = topic; isTopic = forumTopic != null; @@ -3038,6 +3133,9 @@ public class DialogCell extends BaseCell { } int backgroundColor = 0; + if (!drawingForBlur && shouldUseLiquidGlass()) { + drawLiquidGlass(canvas); + } if (translationX != 0 || cornerProgress != 0.0f) { canvas.save(); canvas.translate(0, -translateY); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java index 7bf0bd884..4336ba71f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java @@ -83,6 +83,7 @@ import android.widget.FrameLayout; import android.widget.HorizontalScrollView; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.ScrollView; import android.widget.Space; import android.widget.TextView; import android.widget.Toast; @@ -1326,6 +1327,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private final static int open_forum = 61; private final static int translate = 62; + private final static int auto_translate_incoming = 63; + private final static int auto_translate_outgoing = 64; + private final static int auto_translate_lang = 65; + private final static int yagiz_translator = 66; private final static int permissions = 100; private final static int administrators = 101; @@ -3149,6 +3154,38 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (!getMessagesController().getTranslateController().toggleTranslatingDialog(getDialogId(), true)) { updateTopPanel(true); } + } else if (id == auto_translate_incoming) { + // Overgram: Toggle auto-translate incoming for this chat + boolean currentState = OverConfig.isAutoTranslateIncoming(dialog_id); + OverConfig.setAutoTranslateIncoming(dialog_id, !currentState); + BulletinFactory.of(ChatActivity.this).createSimpleBulletin( + !currentState ? R.raw.done : R.raw.deactivate, + !currentState ? LocaleController.getString(R.string.AutoTranslateEnabled) : LocaleController.getString(R.string.AutoTranslateDisabled) + ).show(); + if (!currentState && !getMessagesController().getTranslateController().isTranslatingDialog(dialog_id)) { + getMessagesController().getTranslateController().toggleTranslatingDialog(dialog_id, true); + } + } else if (id == auto_translate_outgoing) { + // Overgram: Toggle auto-translate outgoing for this chat + boolean currentState = OverConfig.isAutoTranslateOutgoing(dialog_id); + OverConfig.setAutoTranslateOutgoing(dialog_id, !currentState); + BulletinFactory.of(ChatActivity.this).createSimpleBulletin( + !currentState ? R.raw.done : R.raw.deactivate, + !currentState ? LocaleController.getString(R.string.AutoTranslateEnabled) : LocaleController.getString(R.string.AutoTranslateDisabled) + ).show(); + } else if (id == auto_translate_lang) { + // Overgram: Show language picker for this chat + showAutoTranslateLanguagePicker(); + } else if (id == yagiz_translator) { + // Overgram: Toggle YagizTranslator for this chat + boolean currentState = OverConfig.isYagizTranslatorEnabledForDialog(dialog_id); + OverConfig.setYagizTranslatorEnabledForDialog(dialog_id, !currentState); + BulletinFactory.of(ChatActivity.this).createSimpleBulletin( + !currentState ? R.raw.done : R.raw.deactivate, + !currentState ? + LocaleController.getString(R.string.YagizTranslatorEnabled) + " - " + com.overspend1.overgram.translator.YagizTranslator.getApiName(OverConfig.yagizTranslatorApiType) : + LocaleController.getString(R.string.YagizTranslatorPerChat) + " " + LocaleController.getString(R.string.AutoTranslateDisabled) + ).show(); } else if (id == call || id == video_call) { if (currentUser != null && getParentActivity() != null) { VoIPHelper.startCall(currentUser, id == video_call, userInfo != null && userInfo.video_calls_available, getParentActivity(), getMessagesController().getUserFull(currentUser.id), getAccountInstance()); @@ -3490,6 +3527,15 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } translateItem = headerItem.lazilyAddSubItem(translate, R.drawable.msg_translate, LocaleController.getString("TranslateMessage", R.string.TranslateMessage)); updateTranslateItemVisibility(); + + // Overgram: Per-chat auto-translate controls + headerItem.lazilyAddSubItem(auto_translate_incoming, R.drawable.msg_translate, LocaleController.getString(R.string.AutoTranslatePerChatIncoming)); + headerItem.lazilyAddSubItem(auto_translate_outgoing, R.drawable.msg_translate, LocaleController.getString(R.string.AutoTranslatePerChatOutgoing)); + headerItem.lazilyAddSubItem(auto_translate_lang, R.drawable.msg_language, LocaleController.getString(R.string.AutoTranslatePerChatLang)); + + // Overgram: YagizTranslator toggle + headerItem.lazilyAddSubItem(yagiz_translator, R.drawable.msg_translate, LocaleController.getString(R.string.YagizTranslatorPerChat)); + if (currentChat != null && !currentChat.creator && !ChatObject.hasAdminRights(currentChat)) { headerItem.lazilyAddSubItem(report, R.drawable.msg_report, LocaleController.getString("ReportChat", R.string.ReportChat)); } @@ -5566,6 +5612,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not outRect.bottom = -h; } } + if (outRect.bottom == 0) { + // Add a bit of breathing room between standalone messages + outRect.bottom = AndroidUtilities.dp(6); + } } } }); @@ -16672,6 +16722,19 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } return; } + + // Overgram: Update smart quick replies when new messages arrive + if (OverConfig.smartQuickReplies && chatActivityEnterView != null && !arr.isEmpty()) { + // Get the last message that's not from current user + for (int i = arr.size() - 1; i >= 0; i--) { + MessageObject msg = arr.get(i); + if (!msg.isOut() && !msg.isService()) { + chatActivityEnterView.updateSmartQuickReplies(msg); + break; + } + } + } + processNewMessages(arr); } else if (ChatObject.isChannel(currentChat) && !currentChat.megagroup && chatInfo != null && did == -chatInfo.linked_chat_id) { for (int a = 0, N = arr.size(); a < N; a++) { @@ -19247,6 +19310,44 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not LongSparseArray scheduledGroupReplacement = null; for (int a = 0, N = arr.size(); a < N; a++) { MessageObject messageObject = arr.get(a); + + // Overgram: Auto-translate incoming messages for full two-way conversation + boolean shouldUseYagizTranslator = OverConfig.isYagizTranslatorEnabledForDialog(dialog_id) && + OverConfig.yagizTranslatorAutoDetect; + + if (shouldUseYagizTranslator && !messageObject.isOut() && !messageObject.isService()) { + // YagizTranslator: Turkish ↔ English auto-detection + String messageText = messageObject.messageOwner.message; + if (messageText != null && !messageText.isEmpty()) { + com.overspend1.overgram.translator.YagizTranslator.translateAuto( + messageText, + (translatedText, detectedLang) -> { + // Enable translation UI and show translated text + if (!getMessagesController().getTranslateController().isTranslatingDialog(dialog_id)) { + getMessagesController().getTranslateController().toggleTranslatingDialog(dialog_id, true); + } + }, + error -> FileLog.d("YagizTranslator: Failed to translate incoming message - " + error) + ); + } + } else if (OverConfig.isAutoTranslateIncoming(dialog_id) && !messageObject.isOut() && !messageObject.isService()) { + // Regular auto-translate incoming + if (!getMessagesController().getTranslateController().isTranslatingDialog(dialog_id)) { + getMessagesController().getTranslateController().toggleTranslatingDialog(dialog_id, true); + } + + // Get user's language preference + String targetLang = OverConfig.autoTranslateIncomingLangDefault; + if (targetLang == null || targetLang.isEmpty()) { + targetLang = java.util.Locale.getDefault().getLanguage(); + if (targetLang == null || targetLang.isEmpty()) { + targetLang = "en"; + } + } + + // Set the target language for this dialog in TranslateController + getMessagesController().getTranslateController().setDialogTranslateTo(dialog_id, targetLang); + } if (!isAd) { isAd = messageObject.isSponsored(); } @@ -32682,7 +32783,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } private void requestGemini(CharSequence messageText, boolean translateToTurkish) { - if (getParentActivity() == null || messageText == null || messageText.length() == 0) { + if (getParentActivity() == null || messageText == null || TextUtils.isEmpty(messageText.toString().trim())) { + BulletinFactory.of(this).createSimpleBulletin(R.raw.error, LocaleController.getString(R.string.OvergramGeminiNoInput)).show(); return; } if (TextUtils.isEmpty(OverConfig.geminiApiKey)) { @@ -32709,6 +32811,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (progressDialog.isShowing()) { progressDialog.dismiss(); } + if (TextUtils.isEmpty(text)) { + BulletinFactory.of(ChatActivity.this).createSimpleBulletin(R.raw.error, LocaleController.getString(R.string.OvergramGeminiEmptyResponse)).show(); + return; + } showGeminiResult(translateToTurkish ? LocaleController.getString(R.string.OvergramTranslateTurkish) : LocaleController.getString(R.string.OvergramGeminiAsk), text); }); } @@ -32729,21 +32835,133 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (getParentActivity() == null) { return; } - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity(), themeDelegate); - builder.setTitle(title); - builder.setMessage(body); - builder.setPositiveButton(LocaleController.getString("Copy", R.string.Copy), (dialog, which) -> { - if (!TextUtils.isEmpty(body)) { - AndroidUtilities.addToClipboard(body); - BulletinFactory.of(this).createSimpleBulletin(R.raw.copy, LocaleController.getString("TextCopied", R.string.TextCopied)).show(); - } - }); - builder.setNeutralButton(LocaleController.getString("Paste", R.string.Paste), (dialog, which) -> { + Context ctx = getParentActivity(); + BottomSheet.Builder builder = new BottomSheet.Builder(ctx, false, themeDelegate); + builder.setApplyTopPadding(false); + + LinearLayout container = new LinearLayout(ctx); + container.setOrientation(LinearLayout.VERTICAL); + int pad = AndroidUtilities.dp(20); + container.setPadding(pad, pad, pad, pad); + + TextView titleView = new TextView(ctx); + titleView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); + titleView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + titleView.setText(title); + container.addView(titleView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + ScrollView scrollView = new ScrollView(ctx); + scrollView.setFillViewport(true); + TextView bodyView = new TextView(ctx); + bodyView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + bodyView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + bodyView.setText(body); + bodyView.setTextIsSelectable(true); + bodyView.setLineSpacing(AndroidUtilities.dp(2), 1.1f); + int bodyPad = AndroidUtilities.dp(4); + bodyView.setPadding(bodyPad, bodyPad, bodyPad, bodyPad); + scrollView.addView(bodyView, new ScrollView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + container.addView(scrollView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, AndroidUtilities.dp(260))); + + LinearLayout actions = new LinearLayout(ctx); + actions.setOrientation(LinearLayout.HORIZONTAL); + actions.setGravity(Gravity.END); + actions.setPadding(0, AndroidUtilities.dp(12), 0, 0); + + actions.addView(createGeminiActionButton(ctx, LocaleController.getString("Copy", R.string.Copy), () -> { + AndroidUtilities.addToClipboard(body); + BulletinFactory.of(this).createSimpleBulletin(R.raw.copy, LocaleController.getString("TextCopied", R.string.TextCopied)).show(); + })); + + actions.addView(createGeminiActionButton(ctx, LocaleController.getString("Paste", R.string.Paste), () -> { if (chatActivityEnterView != null && !TextUtils.isEmpty(body)) { chatActivityEnterView.setFieldText(body, true); } + })); + + actions.addView(createGeminiActionButton(ctx, LocaleController.getString("ShareFile", R.string.ShareFile), () -> { + try { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_TEXT, body); + ctx.startActivity(Intent.createChooser(intent, LocaleController.getString("ShareFile", R.string.ShareFile))); + } catch (Exception e) { + FileLog.e(e); + } + })); + + container.addView(actions, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + builder.setCustomView(container); + showDialog(builder.create()); + } + + private View createGeminiActionButton(Context context, String text, Runnable onClick) { + TextView button = new TextView(context); + button.setText(text); + button.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + button.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + button.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + int padH = AndroidUtilities.dp(14); + int padV = AndroidUtilities.dp(10); + button.setPadding(padH, padV, padH, padV); + button.setBackground(Theme.createSimpleSelectorRoundRectDrawable(AndroidUtilities.dp(8), Theme.getColor(Theme.key_windowBackgroundWhite), Theme.getColor(Theme.key_dialogButtonSelector))); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + params.leftMargin = AndroidUtilities.dp(8); + button.setLayoutParams(params); + button.setOnClickListener(v -> { + if (onClick != null) { + onClick.run(); + } }); - builder.setNegativeButton(LocaleController.getString("Close", R.string.Close), null); + return button; + } + + private void showAutoTranslateLanguagePicker() { + if (getParentActivity() == null) { + return; + } + + String[][] languages = { + {"en", "English"}, + {"es", "Spanish"}, + {"fr", "French"}, + {"de", "German"}, + {"it", "Italian"}, + {"pt", "Portuguese"}, + {"ru", "Russian"}, + {"ja", "Japanese"}, + {"ko", "Korean"}, + {"zh", "Chinese"}, + {"ar", "Arabic"}, + {"hi", "Hindi"}, + {"tr", "Turkish"}, + {"pl", "Polish"}, + {"uk", "Ukrainian"}, + {"nl", "Dutch"}, + {"sv", "Swedish"}, + {"no", "Norwegian"}, + {"da", "Danish"}, + {"fi", "Finnish"} + }; + + String[] options = new String[languages.length]; + for (int i = 0; i < languages.length; i++) { + options[i] = languages[i][1]; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString(R.string.AutoTranslatePerChatLang)); + builder.setItems(options, (dialog, which) -> { + String selectedLang = languages[which][0]; + OverConfig.setAutoTranslateOutgoingLang(dialog_id, selectedLang); + BulletinFactory.of(ChatActivity.this).createSimpleBulletin( + R.raw.done, + LocaleController.getString(R.string.AutoTranslatePerChatLang) + ": " + languages[which][1] + ).show(); + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); showDialog(builder.create()); } } 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 0366c54c6..f7bbeddcf 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java @@ -493,6 +493,8 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific public ValueAnimator currentTopViewAnimation; @Nullable private ReplaceableIconDrawable botButtonDrawable; + private com.overspend1.overgram.ui.components.SmartQuickRepliesView smartQuickRepliesView; + private com.overspend1.overgram.ui.components.TranslationPropositionView translationPropositionView; private CharSequence draftMessage; private boolean draftSearchWebpage; @@ -2615,6 +2617,23 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific checkChannelRights(); createMessageEditText(); + + // Overgram: Initialize smart quick replies view + if (OverConfig.smartQuickReplies) { + smartQuickRepliesView = new com.overspend1.overgram.ui.components.SmartQuickRepliesView(context); + smartQuickRepliesView.setOnReplySelectedListener(reply -> { + if (messageEditText != null) { + messageEditText.setText(reply); + messageEditText.setSelection(reply.length()); + } + smartQuickRepliesView.hide(); + }); + addView(smartQuickRepliesView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM, 0, 0, 0, 50)); + } + + // Overgram: Initialize translation proposition view + translationPropositionView = new com.overspend1.overgram.ui.components.TranslationPropositionView(context, resourcesProvider); + addView(translationPropositionView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM, 8, 0, 8, 58)); } private void createCaptionLimitView() { @@ -3354,6 +3373,21 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific return parentFragment; } + /** + * Overgram: Update smart quick replies based on the last received message + */ + public void updateSmartQuickReplies(MessageObject lastMessage) { + if (OverConfig.smartQuickReplies && smartQuickRepliesView != null && lastMessage != null) { + // Only show/update if input is empty + if (messageEditText == null || messageEditText.length() == 0) { + smartQuickRepliesView.updateRepliesForMessage(lastMessage); + if (!smartQuickRepliesView.isShowing()) { + smartQuickRepliesView.show(); + } + } + } + } + private void checkBotMenu() { final boolean shouldBeExpanded = (messageEditText == null || TextUtils.isEmpty(messageEditText.getText())) && !(keyboardVisible || waitingForKeyboardOpen || isPopupShowing()); if (shouldBeExpanded) { @@ -4208,6 +4242,21 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific } delegate.onTextChanged(charSequence, before > count + 1 || (count - before) > 2); } + + // Overgram: Handle smart quick replies visibility + if (OverConfig.smartQuickReplies && smartQuickRepliesView != null) { + if (charSequence.length() > 0) { + // Hide quick replies when user starts typing + if (smartQuickRepliesView.isShowing()) { + smartQuickRepliesView.hide(); + } + } else { + // Show quick replies when input is empty + if (!smartQuickRepliesView.isShowing()) { + smartQuickRepliesView.show(); + } + } + } } if (innerTextChange != 2 && (count - before) > 1) { processChange = true; @@ -5374,6 +5423,39 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific } } + private void sendMessageAfterTranslate(CharSequence message, boolean notify, int scheduleDate) { + if (checkPremiumAnimatedEmoji(currentAccount, dialog_id, parentFragment, null, message)) { + return; + } + if (processSendingText(message, notify, scheduleDate)) { + if (delegate.hasForwardingMessages() || (scheduleDate != 0 && !isInScheduleMode()) || isInScheduleMode()) { + if (messageEditText != null) { + messageEditText.setText(""); + } + if (delegate != null) { + delegate.onMessageSend(message, notify, scheduleDate); + } + } else { + messageTransitionIsRunning = false; + AndroidUtilities.runOnUIThread(moveToSendStateRunnable = () -> { + moveToSendStateRunnable = null; + hideTopView(true); + if (messageEditText != null) { + messageEditText.setText(""); + } + if (delegate != null) { + delegate.onMessageSend(message, notify, scheduleDate); + } + }, 200); + } + lastTypingTimeSend = 0; + } else if (forceShowSendButton) { + if (delegate != null) { + delegate.onMessageSend(null, notify, scheduleDate); + } + } + } + private boolean premiumEmojiBulletin = true; private void sendMessageInternal(boolean notify, int scheduleDate) { if (slowModeTimer == Integer.MAX_VALUE && !isInScheduleMode()) { @@ -5430,6 +5512,75 @@ public class ChatActivityEnterView extends BlurredFrameLayout implements Notific if (checkPremiumAnimatedEmoji(currentAccount, dialog_id, parentFragment, null, message)) { return; } + + // Overgram: Check YagizTranslator first (Turkish ↔ English specialized) + boolean shouldUseYagizTranslator = OverConfig.isYagizTranslatorEnabledForDialog(dialog_id) && + OverConfig.yagizTranslatorAutoDetect; + + if (shouldUseYagizTranslator && message.length() > 0 && translationPropositionView != null && !translationPropositionView.isShowing()) { + final CharSequence originalMessage = message; + com.overspend1.overgram.translator.YagizTranslator.translateAuto( + message.toString(), + (translatedText, detectedLang) -> AndroidUtilities.runOnUIThread(() -> { + if (translationPropositionView != null && !originalMessage.toString().equals(translatedText)) { + translationPropositionView.showProposition( + originalMessage.toString(), + translatedText, + accepted -> { + if (messageEditText != null) { + messageEditText.setText(accepted); + } + sendMessageAfterTranslate(accepted, notify, scheduleDate); + }, + () -> { + sendMessageAfterTranslate(originalMessage, notify, scheduleDate); + } + ); + } else { + // No translation needed, send as-is + sendMessageAfterTranslate(originalMessage, notify, scheduleDate); + } + }), + error -> AndroidUtilities.runOnUIThread(() -> { + BulletinFactory.of(parentFragment).createSimpleBulletin(R.raw.error, LocaleController.getString(R.string.AutoTranslateFailed)).show(); + sendMessageAfterTranslate(originalMessage, notify, scheduleDate); + }) + ); + return; + } + + // Overgram: Regular auto-translate outgoing messages + if (OverConfig.isAutoTranslateOutgoing(dialog_id) && message.length() > 0 && translationPropositionView != null && !translationPropositionView.isShowing()) { + String targetLang = OverConfig.getAutoTranslateOutgoingLang(dialog_id); + if (targetLang != null && !targetLang.isEmpty()) { + final CharSequence originalMessage = message; + TranslatorUtils.translate(message, targetLang, translatedText -> AndroidUtilities.runOnUIThread(() -> { + if (translationPropositionView != null && !originalMessage.toString().equals(translatedText.toString())) { + translationPropositionView.showProposition( + originalMessage.toString(), + translatedText.toString(), + accepted -> { + if (messageEditText != null) { + messageEditText.setText(accepted); + } + sendMessageAfterTranslate(accepted, notify, scheduleDate); + }, + () -> { + sendMessageAfterTranslate(originalMessage, notify, scheduleDate); + } + ); + } else { + // No translation needed, send as-is + sendMessageAfterTranslate(originalMessage, notify, scheduleDate); + } + }), () -> AndroidUtilities.runOnUIThread(() -> { + BulletinFactory.of(parentFragment).createSimpleBulletin(R.raw.error, LocaleController.getString(R.string.AutoTranslateFailed)).show(); + sendMessageAfterTranslate(originalMessage, notify, scheduleDate); + })); + return; + } + } + if (processSendingText(message, notify, scheduleDate)) { if (delegate.hasForwardingMessages() || (scheduleDate != 0 && !isInScheduleMode()) || isInScheduleMode()) { if (messageEditText != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java index b6a0f147a..4670791cb 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java @@ -36,6 +36,8 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; import android.graphics.Shader; +import android.graphics.RenderEffect; +import android.graphics.Shader.TileMode; import android.location.LocationManager; import android.media.AudioManager; import android.net.Uri; @@ -99,6 +101,7 @@ import com.overspend1.overgram.OverConfig; import com.overspend1.overgram.OverConstants; import com.overspend1.overgram.OverCustomHandlers; import com.overspend1.overgram.OverUtils; +import one.overgram.messenger.ui.liquidglass.LiquidGlassHelper; import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.AccountInstance; @@ -491,6 +494,7 @@ public class LaunchActivity extends BasePermissionsActivity implements INavigati sideMenu.setItemAnimator(itemAnimator); sideMenu.setBackgroundColor(Theme.getColor(Theme.key_chats_menuBackground)); sideMenuContainer.setBackgroundColor(Theme.getColor(Theme.key_chats_menuBackground)); + applyDrawerBlurIfNeeded(); sideMenu.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); sideMenu.setAllowItemsInteractionDuringAnimation(false); sideMenu.setAdapter(drawerLayoutAdapter = new DrawerLayoutAdapter(this, itemAnimator, drawerLayoutContainer)); @@ -5647,6 +5651,9 @@ public class LaunchActivity extends BasePermissionsActivity implements INavigati @Override protected void onPause() { super.onPause(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + LiquidGlassHelper.removeWindowBlur(getWindow()); + } isResumed = false; NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.stopAllHeavyOperations, 4096); ApplicationLoader.mainInterfacePaused = true; @@ -5780,6 +5787,11 @@ public class LaunchActivity extends BasePermissionsActivity implements INavigati @Override protected void onResume() { super.onResume(); + if (OverConfig.liquidGlassEnabled && OverConfig.liquidGlassApplyToSystemSurfaces && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + LiquidGlassHelper.applyWindowBlur(getWindow(), (int) Math.max(12, OverConfig.liquidGlassBlurRadius * 2)); + } + applyDrawerBlurIfNeeded(); + applyChromeBlurIfNeeded(); isResumed = true; if (onResumeStaticCallback != null) { onResumeStaticCallback.run(); @@ -5856,6 +5868,40 @@ public class LaunchActivity extends BasePermissionsActivity implements INavigati invalidateTabletMode(); } + private void applyDrawerBlurIfNeeded() { + if (sideMenuContainer == null) { + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && OverConfig.liquidGlassEnabled && OverConfig.liquidGlassApplyToSystemSurfaces) { + float radius = Math.max(8f, OverConfig.liquidGlassBlurRadius * 1.5f); + sideMenuContainer.setRenderEffect(RenderEffect.createBlurEffect(radius, radius, TileMode.CLAMP)); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + sideMenuContainer.setRenderEffect(null); + } + } + + private void applyChromeBlurIfNeeded() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + return; + } + RenderEffect effect = null; + if (OverConfig.liquidGlassEnabled && OverConfig.liquidGlassApplyToSystemSurfaces) { + float radius = Math.max(6f, OverConfig.liquidGlassBlurRadius * 1.2f); + effect = RenderEffect.createBlurEffect(radius, radius, TileMode.CLAMP); + } + setRenderEffectSafe(actionBarLayout, effect); + if (AndroidUtilities.isTablet()) { + setRenderEffectSafe(rightActionBarLayout, effect); + setRenderEffectSafe(layersActionBarLayout, effect); + } + } + + private void setRenderEffectSafe(View view, RenderEffect effect) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && view != null) { + view.setRenderEffect(effect); + } + } + private void invalidateTabletMode() { Boolean wasTablet = AndroidUtilities.getWasTablet(); if (wasTablet == null) { diff --git a/TMessagesProj/src/main/res/values/over.xml b/TMessagesProj/src/main/res/values/over.xml index 260e5dc51..9f11db5dd 100644 --- a/TMessagesProj/src/main/res/values/over.xml +++ b/TMessagesProj/src/main/res/values/over.xml @@ -47,6 +47,24 @@ You can use site regex101.com]]> to fully test your regular expression. You can also use plain text, but don\'t forget to escape brackets. Regex syntax error + Productivity & UX + Smart quick replies + Show AI-suggested short replies in chat + Auto-translate incoming (default) + Automatically translate messages you receive + Auto-translate outgoing (default) + Automatically translate before sending + Default outgoing language + Target language for outgoing messages + Your language (for incoming) + Your preferred language for reading messages + Auto-translate incoming for this chat + Auto-translate outgoing for this chat + Set outgoing language for this chat + Auto-translate enabled + Auto-translate disabled + Translating... + Translation failed. Sending original message. OvergramSync Sync status connected @@ -112,6 +130,7 @@ Apply glassmorphism effects throughout the app Chat bubbles Dialog list + System surfaces (headers, drawers) Preset style Subtle Standard @@ -124,5 +143,14 @@ Advanced customization Performance Liquid glass effects are GPU-accelerated for smooth 60 FPS + Quick actions + Fast access to AI and glass settings + Toggle liquid glass + Toggle ghost mode + Open liquid glass settings + Open AI & translation settings + Original + Translated + Use translation + Keep original - diff --git a/TMessagesProj/src/main/res/values/strings.xml b/TMessagesProj/src/main/res/values/strings.xml index ef4473cb8..d12aecafc 100644 --- a/TMessagesProj/src/main/res/values/strings.xml +++ b/TMessagesProj/src/main/res/values/strings.xml @@ -15,6 +15,8 @@ Model Add your Gemini API key in AI settings first Contacting Gemini... + Select a message or type something first + Gemini did not return a reply. Try again. Enable AI for this chat Disable AI for this chat AI enabled for this chat