Add translation features with fluid animations and productivity enhancements

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
This commit is contained in:
2025-12-03 21:47:41 +01:00
parent 4bbe025d2e
commit a7689ac346
12 changed files with 1987 additions and 28 deletions

View File

@@ -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;
}
}

View File

@@ -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";
}
}
}

View File

@@ -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<String> 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<String> defaultReplies = Arrays.asList("👍", "👌", "OK", "Thanks!", "Yes", "No", "😊", "🤔");
showReplies(defaultReplies);
}
/**
* Display a specific set of replies
*/
private void showReplies(List<String> 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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());
}
}

View File

@@ -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);

View File

@@ -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<Long> 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());
}
}

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -47,6 +47,24 @@
<string name="RegexFiltersAddDescription">You can use site <![CDATA[<a href="https://regex101.com">regex101.com</a>]]> to fully test your regular expression.
You can also use plain text, but don\'t forget to escape brackets.</string>
<string name="RegexFiltersAddError">Regex syntax error</string>
<string name="ProductivityHeader">Productivity &amp; UX</string>
<string name="SmartQuickReplies">Smart quick replies</string>
<string name="SmartQuickRepliesHint">Show AI-suggested short replies in chat</string>
<string name="AutoTranslateIncoming">Auto-translate incoming (default)</string>
<string name="AutoTranslateIncomingHint">Automatically translate messages you receive</string>
<string name="AutoTranslateOutgoing">Auto-translate outgoing (default)</string>
<string name="AutoTranslateOutgoingHint">Automatically translate before sending</string>
<string name="AutoTranslateOutgoingLang">Default outgoing language</string>
<string name="AutoTranslateOutgoingLangHint">Target language for outgoing messages</string>
<string name="AutoTranslateIncomingLang">Your language (for incoming)</string>
<string name="AutoTranslateIncomingLangHint">Your preferred language for reading messages</string>
<string name="AutoTranslatePerChatIncoming">Auto-translate incoming for this chat</string>
<string name="AutoTranslatePerChatOutgoing">Auto-translate outgoing for this chat</string>
<string name="AutoTranslatePerChatLang">Set outgoing language for this chat</string>
<string name="AutoTranslateEnabled">Auto-translate enabled</string>
<string name="AutoTranslateDisabled">Auto-translate disabled</string>
<string name="AutoTranslatingMessage">Translating...</string>
<string name="AutoTranslateFailed">Translation failed. Sending original message.</string>
<string name="AyuSyncHeader">OvergramSync</string>
<string name="AyuSyncStatusTitle">Sync status</string>
<string name="AyuSyncStatusOk">connected</string>
@@ -112,6 +130,7 @@
<string name="LiquidGlassEnableHint">Apply glassmorphism effects throughout the app</string>
<string name="LiquidGlassApplyToChatBubbles">Chat bubbles</string>
<string name="LiquidGlassApplyToDialogs">Dialog list</string>
<string name="LiquidGlassApplyToSystem">System surfaces (headers, drawers)</string>
<string name="LiquidGlassPreset">Preset style</string>
<string name="LiquidGlassPresetSubtle">Subtle</string>
<string name="LiquidGlassPresetStandard">Standard</string>
@@ -124,5 +143,14 @@
<string name="LiquidGlassCustomization">Advanced customization</string>
<string name="LiquidGlassPerformance">Performance</string>
<string name="LiquidGlassPerformanceHint">Liquid glass effects are GPU-accelerated for smooth 60 FPS</string>
<string name="OvergramQuickActions">Quick actions</string>
<string name="OvergramQuickActionsHint">Fast access to AI and glass settings</string>
<string name="OvergramQuickActionToggleGlass">Toggle liquid glass</string>
<string name="OvergramQuickActionToggleGhost">Toggle ghost mode</string>
<string name="OvergramQuickActionOpenGlass">Open liquid glass settings</string>
<string name="OvergramQuickActionOpenAi">Open AI &amp; translation settings</string>
<string name="OriginalText">Original</string>
<string name="TranslatedText">Translated</string>
<string name="AcceptTranslation">Use translation</string>
<string name="CancelTranslation">Keep original</string>
</resources>

View File

@@ -15,6 +15,8 @@
<string name="OvergramGeminiModelTitle">Model</string>
<string name="OvergramGeminiMissingKey">Add your Gemini API key in AI settings first</string>
<string name="OvergramGeminiWorking">Contacting Gemini...</string>
<string name="OvergramGeminiNoInput">Select a message or type something first</string>
<string name="OvergramGeminiEmptyResponse">Gemini did not return a reply. Try again.</string>
<string name="OvergramAiEnableChat">Enable AI for this chat</string>
<string name="OvergramAiDisableChat">Disable AI for this chat</string>
<string name="OvergramAiEnabledChat">AI enabled for this chat</string>