text,photo,exchange end

This commit is contained in:
Simon 2024-07-16 13:48:18 +08:00
parent 4e66e7f84d
commit ea4a023519
33 changed files with 1057 additions and 637 deletions

View File

@ -56,11 +56,20 @@ dependencies {
// To recognize Korean script // To recognize Korean script
implementation("com.google.mlkit:text-recognition-korean:16.0.0") implementation("com.google.mlkit:text-recognition-korean:16.0.0")
// CameraX // CameraX core library
implementation(libs.androidx.camera.core)
// CameraX Camera2 extensions
implementation(libs.androidx.camera.camera2) implementation(libs.androidx.camera.camera2)
// CameraX Lifecycle library
implementation(libs.androidx.camera.lifecycle) implementation(libs.androidx.camera.lifecycle)
// CameraX View class
implementation(libs.androidx.camera.view) implementation(libs.androidx.camera.view)
// 文本识别 // 文本识别
// To recognize Latin script // To recognize Latin script
// implementation(libs.play.services.mlkit.text.recognition) // implementation(libs.play.services.mlkit.text.recognition)

View File

@ -19,6 +19,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
<application <application
android:name=".MyApp"
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"

View File

@ -7,58 +7,53 @@ import android.os.Build
import android.webkit.WebView import android.webkit.WebView
import com.assimilate.alltrans.common.Language import com.assimilate.alltrans.common.Language
import com.assimilate.alltrans.common.LanguagesConstants import com.assimilate.alltrans.common.LanguagesConstants
import com.assimilate.alltrans.common.Logger
import com.assimilate.alltrans.common.PreferenceLanguageUtils
class MyApp : Application() { class MyApp : Application() {
init { init {
instance = this instance = this
} }
private var sl: Language? = null private fun setSystemLanguage() {
private var tl: Language? = null
// 检查是否是第一次进入应用
if (PreferenceLanguageUtils.isFirstTime()) {
// 第一次进入应用的逻辑
val config = applicationContext().resources.configuration
val locale = config.locale.toString() // 获取完整的语言环境信息,如 "en_US" 或 "zh_CN"
Logger.d("dslocaleds", locale)
val languages = LanguagesConstants.getInstance().getLanguageByLanguageCode(locale, this)
Logger.d("dslocaledsa", languages.language)
PreferenceLanguageUtils.putString(
"language_source",
languages.language.toString().trim()
)
// 设置已经不是第一次进入应用了
PreferenceLanguageUtils.setNotFirstTime()
}
}
companion object { companion object {
private var instance: MyApp? = null private var instance: MyApp? = null
fun applicationContext(): Context { fun applicationContext(): Context {
return instance!!.applicationContext return instance!!.applicationContext
} }
fun getSourceLanguageCode(): String {
return instance?.getSourceLanguageCode() ?: "zh"
}
fun getSourceLanguage(): String {
return instance?.getSourceLanguage() ?: "English"
}
fun getSourceSpeechCode(): String {
return instance?.getSourceSpeechCode() ?: "en-GB"
}
fun getTargetLanguageCode(): String {
return instance?.getTargetLanguageCode() ?: "en"
}
fun getTargetLanguage(): String {
return instance?.getTargetLanguage() ?: "English"
}
fun getTargetSpeechCode(): String {
return instance?.getTargetSpeechCode() ?: "en-GB"
}
fun setSourceLanguage(language: Language) {
instance?.setSourceLanguage(language)
}
fun setTargetLanguage(language: Language) {
instance?.setTargetLanguage(language)
}
} }
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
initLanguage() instance = this
setSystemLanguage()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val processName = getUniqueProcessName() val processName = getUniqueProcessName()
if (processName != null && processName != packageName) { if (processName != null && processName != packageName) {
@ -81,76 +76,4 @@ class MyApp : Application() {
object Config { object Config {
const val openSusViewMode: String = "open_sus_view" const val openSusViewMode: String = "open_sus_view"
} }
private fun initLanguage() {
// 拿到最近一次的翻译情况分别设置最近一次的并赋值setSourceLanguage|setTargetLanguage
// 以下是默认情况
val languages: ArrayList<Language> = LanguagesConstants.getInstance().getList(applicationContext)
if (languages.isNotEmpty()) {
for (language in languages) {
if ("Afrikaans" == language.language) {
tl = language
break
}
}
for (language in languages) {
if ("English" == language.language) {
sl = language
break
}
}
}
}
private fun getSourceLanguageCode(): String {
if (sl == null) {
return "en"
}
return sl!!.languageCode
}
private fun getSourceLanguage(): String {
if (sl == null) {
return "English"
}
return sl!!.language
}
private fun getSourceSpeechCode(): String {
if (sl == null) {
return "en-GB"
}
return sl!!.speechCode
}
private fun getTargetLanguageCode(): String {
if (tl == null) {
return "en"
}
return tl!!.languageCode
}
private fun getTargetLanguage(): String {
if (tl == null) {
return "English"
}
return tl!!.language
}
private fun getTargetSpeechCode(): String {
if (tl == null) {
return "en-GB"
}
return tl!!.speechCode
}
private fun setSourceLanguage(language: Language) {
sl = language
}
private fun setTargetLanguage(language: Language) {
tl = language
}
} }

View File

@ -266,7 +266,7 @@ class SusService : Service() {
) )
bitmap.copyPixelsFromBuffer(buffer) bitmap.copyPixelsFromBuffer(buffer)
image.close() image.close()
bindingSubGlobal.preview.setImageBitmap(bitmap) bindingSubGlobal.susPreview.setImageBitmap(bitmap)
tryReloadAndDetectInImage(bitmap) tryReloadAndDetectInImage(bitmap)
} }
stopSelf() stopSelf()

View File

@ -52,26 +52,26 @@ public class InferenceInfoGraphic extends GraphicOverlay.Graphic {
float x = TEXT_SIZE * 0.5f; float x = TEXT_SIZE * 0.5f;
float y = TEXT_SIZE * 1.5f; float y = TEXT_SIZE * 1.5f;
canvas.drawText( // canvas.drawText(
"InputImage size: " + overlay.getImageHeight() + "x" + overlay.getImageWidth(), // "InputImage size: " + overlay.getImageHeight() + "x" + overlay.getImageWidth(),
x, // x,
y, // y,
textPaint); // textPaint);
//
if (!showLatencyInfo) { // if (!showLatencyInfo) {
return; // return;
} // }
// Draw FPS (if valid) and inference latency // // Draw FPS (if valid) and inference latency
if (framesPerSecond != null) { // if (framesPerSecond != null) {
canvas.drawText( // canvas.drawText(
"FPS: " + framesPerSecond + ", Frame latency: " + frameLatency + " ms", // "FPS: " + framesPerSecond + ", Frame latency: " + frameLatency + " ms",
x, // x,
y + TEXT_SIZE, // y + TEXT_SIZE,
textPaint); // textPaint);
} else { // } else {
canvas.drawText("Frame latency: " + frameLatency + " ms", x, y + TEXT_SIZE, textPaint); // canvas.drawText("Frame latency: " + frameLatency + " ms", x, y + TEXT_SIZE, textPaint);
} // }
canvas.drawText( // canvas.drawText(
"Detector latency: " + detectorLatency + " ms", x, y + TEXT_SIZE * 2, textPaint); // "Detector latency: " + detectorLatency + " ms", x, y + TEXT_SIZE * 2, textPaint);
} }
} }

View File

@ -22,6 +22,7 @@ import java.util.Objects;
public class LanguagesConstants { public class LanguagesConstants {
private static boolean instanced = false; private static boolean instanced = false;
private volatile static LanguagesConstants languagesConstant; private volatile static LanguagesConstants languagesConstant;
private final Gson gson = new Gson();
private final ArrayList<Language> languages; private final ArrayList<Language> languages;
@ -30,11 +31,11 @@ public class LanguagesConstants {
if (instanced) { if (instanced) {
throw new RuntimeException("Instance multiple LanguagesConstants."); throw new RuntimeException("Instance multiple LanguagesConstants.");
} else { } else {
languages = new ArrayList<Language>(); languages = new ArrayList<>();
instanced = true; instanced = true;
} }
if(languagesConstant != null) { if (languagesConstant != null) {
throw new RuntimeException("looper error(LanguagesConstants instanced)."); throw new RuntimeException("looper error(LanguagesConstants instanced).");
} }
} }
@ -58,6 +59,40 @@ public class LanguagesConstants {
return languages; return languages;
} }
// 根据语言代码获取 Language 对象
public Language getLanguageByLanguageCode(@NonNull String languageCode, @NonNull Context context) {
ArrayList<Language> languages = getList(context);
for (Language lang : languages) {
if (lang.getLanguageCode().equalsIgnoreCase(languageCode)) {
return lang;
}
}
return null;
}
// 获取 languageCode
public String getLanguageCodeByLanguage(@NonNull String language, @NonNull Context context) {
Language lang = getLanguageObjectByLanguage(language, context);
return lang != null ? lang.getLanguageCode() : "en";
}
// 获取 speechCode
public String getSpeechCodeByLanguage(@NonNull String language, @NonNull Context context) {
Language lang = getLanguageObjectByLanguage(language, context);
return lang != null ? lang.getSpeechCode() : "en";
}
// 获取 Language 对象
private Language getLanguageObjectByLanguage(@NonNull String language, @NonNull Context context) {
ArrayList<Language> languages = getList(context);
for (Language lang : languages) {
if (lang.getLanguage().equalsIgnoreCase(language)) {
return lang;
}
}
return null;
}
private void getLanguages(final Context context) { private void getLanguages(final Context context) {
InputStream inputStream = null; InputStream inputStream = null;
InputStreamReader inputStreamReader = null; InputStreamReader inputStreamReader = null;
@ -70,7 +105,7 @@ public class LanguagesConstants {
// step2. 读取json文件信息 // step2. 读取json文件信息
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
String line = null; String line;
while ((line = bufferedReader.readLine()) != null) { while ((line = bufferedReader.readLine()) != null) {
builder.append(line); builder.append(line);
} }
@ -78,8 +113,8 @@ public class LanguagesConstants {
// step3. 将json文本转换成Java对象 // step3. 将json文本转换成Java对象
final String result = builder.toString(); final String result = builder.toString();
if (!TextUtils.isEmpty(result)) { if (!TextUtils.isEmpty(result)) {
Gson gson = new Gson(); Type listType = new TypeToken<List<Language>>() {
Type listType = new TypeToken<List<Language>>(){}.getType(); }.getType();
List<Language> temp = gson.fromJson(result, listType); List<Language> temp = gson.fromJson(result, listType);
if (null != temp && !temp.isEmpty()) { if (null != temp && !temp.isEmpty()) {

View File

@ -0,0 +1,87 @@
package com.assimilate.alltrans.common
import android.content.Context
import android.content.SharedPreferences
import com.assimilate.alltrans.MyApp
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
object PreferenceLanguageUtils {
private const val SP_NAME = "sp_language"
private const val KEY_RECENT_LANGUAGES = "recent_languages"
private const val MAX_RECENT_LANGUAGES = 5
private const val PREF_KEY_FIRST_TIME = "first_time"
@Volatile
private var sharedPreferences: SharedPreferences? = null
private fun getSharedPreferences(): SharedPreferences {
return sharedPreferences ?: synchronized(this) {
val instance = MyApp.applicationContext()
.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)
sharedPreferences = instance
instance
}
}
fun putString(key: String, value: String) {
getSharedPreferences().edit().putString(key, value).apply()
}
fun getString(key: String, defValue: String = "english"): String {
val value = getSharedPreferences().getString(key, defValue) ?: defValue
return value
}
fun clear() {
getSharedPreferences().edit().clear().apply()
}
fun clearExceptReferrerUrl() {
val sharedPreferences = getSharedPreferences()
val editor = sharedPreferences.edit()
sharedPreferences.all.keys.forEach { key ->
if (key != "refe_url") {
editor.remove(key)
}
}
editor.apply()
}
fun addRecentLanguage(language: Language) {
val recentLanguages = getRecentLanguages().toMutableList()
recentLanguages.remove(language) // Remove if already exists
recentLanguages.add(0, language) // Add to the beginning
if (recentLanguages.size > MAX_RECENT_LANGUAGES) {
recentLanguages.removeAt(recentLanguages.size - 1) // Remove the oldest
}
saveRecentLanguages(recentLanguages)
}
fun getRecentLanguages(): List<Language> {
val json = getSharedPreferences().getString(KEY_RECENT_LANGUAGES, null)
return if (json != null) {
val type = object : TypeToken<List<Language>>() {}.type
Gson().fromJson(json, type)
} else {
emptyList()
}
}
private fun saveRecentLanguages(languages: List<Language>) {
val json = Gson().toJson(languages)
getSharedPreferences().edit().putString(KEY_RECENT_LANGUAGES, json).apply()
}
// 检查是否是第一次进入应用
fun isFirstTime(): Boolean {
return getSharedPreferences().getBoolean(PREF_KEY_FIRST_TIME, true)
}
// 设置已经不是第一次进入应用了
fun setNotFirstTime() {
getSharedPreferences().edit().putBoolean(PREF_KEY_FIRST_TIME, false).apply()
}
}

View File

@ -24,7 +24,7 @@ public class PreferenceUtils {
public static boolean shouldGroupRecognizedTextInBlocks(Context context) { public static boolean shouldGroupRecognizedTextInBlocks(Context context) {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
String prefKey = context.getString(R.string.pref_key_group_recognized_text_in_blocks); String prefKey = context.getString(R.string.pref_key_group_recognized_text_in_blocks);
return sharedPreferences.getBoolean(prefKey, false); return sharedPreferences.getBoolean(prefKey, true);
} }
public static boolean showLanguageTag(Context context) { public static boolean showLanguageTag(Context context) {

View File

@ -1,23 +1,23 @@
package com.assimilate.alltrans.common package com.assimilate.alltrans.common
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Color import android.graphics.Color
import android.graphics.Paint import android.graphics.Paint
import android.graphics.RectF import android.graphics.RectF
import android.os.Handler
import android.os.Looper
import android.text.TextPaint import android.text.TextPaint
import android.util.Log import android.util.Log
import com.assimilate.alltrans.MyApp
import com.assimilate.alltrans.curview.GraphicOverlay import com.assimilate.alltrans.curview.GraphicOverlay
import com.assimilate.alltrans.http.GoogleTranslator
import com.assimilate.alltrans.http.Translator
import com.google.mlkit.vision.text.Text import com.google.mlkit.vision.text.Text
import java.util.Arrays import java.util.Arrays
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
/** class TextGraphic(
* Graphic instance for rendering TextBlock position, size, and ID within an associated graphic
* overlay view.
*/
class TextGraphic
constructor(
overlay: GraphicOverlay?, overlay: GraphicOverlay?,
private val text: Text, private val text: Text,
private val shouldGroupTextInBlocks: Boolean, private val shouldGroupTextInBlocks: Boolean,
@ -28,14 +28,15 @@ constructor(
private val rectPaint: Paint = Paint() private val rectPaint: Paint = Paint()
private val textPaint: TextPaint private val textPaint: TextPaint
private val labelPaint: Paint private val labelPaint: Paint
private val handler = Handler(Looper.getMainLooper())
init { init {
prepareTranslation()
rectPaint.color = MARKER_COLOR rectPaint.color = MARKER_COLOR
rectPaint.style = Paint.Style.STROKE rectPaint.style = Paint.Style.STROKE
rectPaint.strokeWidth = STROKE_WIDTH rectPaint.strokeWidth = STROKE_WIDTH
textPaint = TextPaint() textPaint = TextPaint()
textPaint.color = TEXT_COLOR textPaint.color = TEXT_COLOR
textPaint.textSize = TEXT_SIZE
labelPaint = Paint() labelPaint = Paint()
labelPaint.color = MARKER_COLOR labelPaint.color = MARKER_COLOR
labelPaint.style = Paint.Style.FILL labelPaint.style = Paint.Style.FILL
@ -43,25 +44,70 @@ constructor(
postInvalidate() postInvalidate()
} }
/** Draws the text block annotations for position, size, and raw value on the supplied canvas. */ private var translatedTextBlocks: List<String> = listOf()
// Method to prepare translation before drawing
private fun prepareTranslation() {
Thread {
val textToTranslate = StringBuilder()
// Collect all text to be translated and append delimiter
for (textBlock in text.textBlocks) {
val textWithDelimiter = textBlock.text + DELIMITER
textToTranslate.append(textWithDelimiter)
}
val lanSourceCode = LanguagesConstants.getInstance().getLanguageCodeByLanguage(
PreferenceLanguageUtils.getString("language_source"),
MyApp.applicationContext()
)
val lanTargetCode = LanguagesConstants.getInstance().getLanguageCodeByLanguage(
PreferenceLanguageUtils.getString("language_target"),
MyApp.applicationContext()
)
// Define translation parameters
val param = HashMap<String, String>().apply {
put("sourceLanguage", lanSourceCode)
put("translationLanguage", lanTargetCode)
put("text", textToTranslate.toString())
}
val translator: Translator<GoogleTranslator.GoogleTranslateCallback> =
GoogleTranslator()
// Perform translation
translator.translate(param, GoogleTranslator.GoogleTranslateCallback { translatedText ->
// Split translated text by delimiter
translatedTextBlocks =
translatedText.split(DELIMITER.toRegex()).dropLastWhile { it.isEmpty() }
// Update UI thread
handler.post {
postInvalidate() // Notify to redraw
}
})
}.start()
}
override fun draw(canvas: Canvas) { override fun draw(canvas: Canvas) {
Log.d(TAG, "Text is: " + text.text)
for (textBlock in text.textBlocks) { // Renders the text at the bottom of the box. for ((translatedIndex, textBlock) in text.textBlocks.withIndex()) {
Log.d(TAG, "TextBlock text is: " + textBlock.text) val translatedBlockText =
Log.d(TAG, "TextBlock recognizedLanguage is: " + textBlock.recognizedLanguage) if (translatedIndex < translatedTextBlocks.size) translatedTextBlocks[translatedIndex] else textBlock.text
Log.d(TAG, "TextBlock boundingbox is: " + textBlock.boundingBox)
Log.d(TAG, "TextBlock cornerpoint is: " + Arrays.toString(textBlock.cornerPoints))
val height1 = ((textBlock.boundingBox?.bottom?.toFloat()
?: 30f) - (textBlock.boundingBox?.top?.toFloat()
?: 0f)) / textBlock.lines.size
if (shouldGroupTextInBlocks) { if (shouldGroupTextInBlocks) {
drawText( drawText(
getFormattedText( getFormattedText(
textBlock.text, translatedBlockText,
textBlock.recognizedLanguage, textBlock.recognizedLanguage,
confidence = null confidence = null
), ),
RectF(textBlock.boundingBox), RectF(textBlock.boundingBox),
TEXT_SIZE * textBlock.lines.size + 2 * STROKE_WIDTH, height1 - 3 * STROKE_WIDTH,
canvas canvas
) )
} else { } else {
@ -71,12 +117,18 @@ constructor(
Log.d(TAG, "Line cornerpoint is: " + Arrays.toString(line.cornerPoints)) Log.d(TAG, "Line cornerpoint is: " + Arrays.toString(line.cornerPoints))
Log.d(TAG, "Line confidence is: " + line.confidence) Log.d(TAG, "Line confidence is: " + line.confidence)
Log.d(TAG, "Line angle is: " + line.angle) Log.d(TAG, "Line angle is: " + line.angle)
// Draws the bounding box around the TextBlock. // Draw the bounding box around the TextBlock.
val rect = RectF(line.boundingBox) val rect = RectF(line.boundingBox)
drawText( drawText(
getFormattedText(line.text, line.recognizedLanguage, line.confidence), getFormattedText(
translatedBlockText,
line.recognizedLanguage,
line.confidence
),
rect, rect,
TEXT_SIZE + 2 * STROKE_WIDTH, ((line.boundingBox?.bottom?.toFloat()
?: 20f) - (line.boundingBox?.top?.toFloat()
?: 0f)) - 2 * STROKE_WIDTH,
canvas canvas
) )
for (element in line.elements) { for (element in line.elements) {
@ -103,79 +155,6 @@ constructor(
} }
} }
} }
val traString =
"aaaaaaaaaaa(`_`))bbbbbbbbbbb((`_`)ccccc(`_`)dhsihs(`_`)dhksskjh(`_`)dhskjfhsdkjfj(`_`)"
val delimiter = "(`_`)"
val parts = traString.split(delimiter)
for (part in parts) {
Log.d(TAG, "TextBlock aaaaa is: $part")
}
// 遍历每个TextBlock并处理分割后的部分
for ((index, textBlock) in text.textBlocks.withIndex()) { // 使用withIndex()获取索引
Log.d(TAG, "TextBlock text is: " + textBlock.text)
Log.d(TAG, "TextBlock recognizedLanguage is: " + textBlock.recognizedLanguage)
Log.d(TAG, "TextBlock boundingbox is: " + textBlock.boundingBox)
Log.d(TAG, "TextBlock cornerpoint is: " + Arrays.toString(textBlock.cornerPoints))
if (shouldGroupTextInBlocks) {
// 获取当前索引对应的part
val part = if (index < parts.size) parts[index] else ""
drawText(
getFormattedText(
part,
textBlock.recognizedLanguage,
confidence = null
),
RectF(textBlock.boundingBox),
TEXT_SIZE * textBlock.lines.size + 2 * STROKE_WIDTH,
canvas
)
} else {
for (line in textBlock.lines) {
Log.d(TAG, "Line text is: " + line.text)
Log.d(TAG, "Line boundingbox is: " + line.boundingBox)
Log.d(TAG, "Line cornerpoint is: " + Arrays.toString(line.cornerPoints))
Log.d(TAG, "Line confidence is: " + line.confidence)
Log.d(TAG, "Line angle is: " + line.angle)
// Draws the bounding box around the TextBlock.
val rect = RectF(line.boundingBox)
drawText(
getFormattedText(line.text, line.recognizedLanguage, line.confidence),
rect,
TEXT_SIZE + 2 * STROKE_WIDTH,
canvas
)
for (element in line.elements) {
Log.d(TAG, "Element text is: " + element.text)
Log.d(TAG, "Element boundingbox is: " + element.boundingBox)
Log.d(
TAG,
"Element cornerpoint is: " + Arrays.toString(element.cornerPoints)
)
Log.d(TAG, "Element language is: " + element.recognizedLanguage)
Log.d(TAG, "Element confidence is: " + element.confidence)
Log.d(TAG, "Element angle is: " + element.angle)
for (symbol in element.symbols) {
Log.d(TAG, "Symbol text is: " + symbol.text)
Log.d(TAG, "Symbol boundingbox is: " + symbol.boundingBox)
Log.d(
TAG,
"Symbol cornerpoint is: " + Arrays.toString(symbol.cornerPoints)
)
Log.d(TAG, "Symbol confidence is: " + symbol.confidence)
Log.d(TAG, "Symbol angle is: " + symbol.angle)
}
}
}
}
}
} }
private fun getFormattedText(text: String, languageTag: String, confidence: Float?): String { private fun getFormattedText(text: String, languageTag: String, confidence: Float?): String {
@ -189,35 +168,78 @@ constructor(
else res else res
} }
private fun drawText(text: String, rect: RectF, textHeight: Float, canvas: Canvas) { private fun drawText(text: String, rect: RectF, textSize: Float, canvas: Canvas) {
// If the image is flipped, the left will be translated to right, and the right to left.
val x0 = translateX(rect.left) val x0 = translateX(rect.left)
val x1 = translateX(rect.right) val x1 = translateX(rect.right)
rect.left = min(x0, x1) rect.left = min(x0, x1)
rect.right = max(x0, x1) rect.right = max(x0, x1)
rect.top = translateY(rect.top) rect.top = translateY(rect.top)
rect.bottom = translateY(rect.bottom) rect.bottom = translateY(rect.bottom)
canvas.drawRect(rect, rectPaint)
val textWidth = textPaint.measureText(text) // Set initial text size
textPaint.textSize = textSize
// Break the text into multiple lines if necessary
var lines = wrapText(text.trim(), rect.width())
// Calculate total height of the text
val totalTextHeight = textPaint.fontMetrics.descent - textPaint.fontMetrics.ascent
val totalTextHeightWithSpacing = totalTextHeight * lines.size
// Adjust the text size if the total height is greater than the rectangle height
if (totalTextHeightWithSpacing > rect.height()) {
textPaint.textSize *= rect.height() / totalTextHeightWithSpacing
lines = wrapText(text.trim(), rect.width())
} else if (totalTextHeightWithSpacing < rect.height()) {
// If the total text height is less than the rectangle height, increase text size
textPaint.textSize *= rect.height() / totalTextHeightWithSpacing
lines = wrapText(text.trim(), rect.width())
}
// Calculate new total height with adjusted text size
val finalTextHeight = textPaint.fontMetrics.descent - textPaint.fontMetrics.ascent
val finalTotalTextHeightWithSpacing = finalTextHeight * lines.size
// Calculate starting Y coordinate to center the text vertically
var textY =
rect.top + ((rect.height() - finalTotalTextHeightWithSpacing) / 2) - textPaint.fontMetrics.ascent
// Draw the background rectangle
canvas.drawRect( canvas.drawRect(
rect.left - STROKE_WIDTH, rect.left - STROKE_WIDTH,
rect.top - textHeight, rect.top - STROKE_WIDTH,
rect.left + textWidth + 2 * STROKE_WIDTH, rect.right + STROKE_WIDTH,
rect.top, rect.bottom + STROKE_WIDTH,
labelPaint labelPaint
) )
// Renders the text at the bottom of the box.
canvas.drawText(text, rect.left, rect.top - STROKE_WIDTH, textPaint) // Draw each line of text
for (line in lines) {
canvas.drawText(line, rect.left, textY, textPaint)
textY += finalTextHeight
}
}
private fun wrapText(text: String, maxWidth: Float): List<String> {
val lines = mutableListOf<String>()
var remainingText = text.trim()
while (remainingText.isNotEmpty()) {
val breakPoint = textPaint.breakText(remainingText, true, maxWidth, null)
val line = remainingText.substring(0, breakPoint)
lines.add(line)
remainingText = remainingText.substring(breakPoint)
}
return lines
} }
companion object { companion object {
private const val DELIMITER = "`0_.._0`"
private const val TAG = "TextGraphic" private const val TAG = "TextGraphic"
private const val TEXT_WITH_LANGUAGE_TAG_FORMAT = "%s:%s" private const val TEXT_WITH_LANGUAGE_TAG_FORMAT = "%s:%s"
private const val TEXT_COLOR = Color.BLACK private val TEXT_COLOR = Color.parseColor("#FF474747")
private const val MARKER_COLOR = Color.GREEN private val MARKER_COLOR = Color.parseColor("#FFD9D9D9")
private const val TEXT_SIZE = 54.0f private const val STROKE_WIDTH = 2.0f
private const val STROKE_WIDTH = 4.0f
} }
} }

View File

@ -2,8 +2,12 @@
package com.assimilate.alltrans.common package com.assimilate.alltrans.common
import android.content.Context import android.content.Context
import android.text.TextUtils
import android.util.Log import android.util.Log
import com.assimilate.alltrans.MyApp
import com.assimilate.alltrans.curview.GraphicOverlay import com.assimilate.alltrans.curview.GraphicOverlay
import com.assimilate.alltrans.http.GoogleTranslator
import com.assimilate.alltrans.http.Translator
import com.google.android.gms.tasks.Task import com.google.android.gms.tasks.Task
import com.google.mlkit.vision.common.InputImage import com.google.mlkit.vision.common.InputImage
import com.google.mlkit.vision.text.Text import com.google.mlkit.vision.text.Text
@ -31,6 +35,9 @@ class TextRecognitionProcessor(
return textRecognizer.process(image) return textRecognizer.process(image)
} }
override fun onSuccess(text: Text, graphicOverlay: GraphicOverlay) { override fun onSuccess(text: Text, graphicOverlay: GraphicOverlay) {
Log.d(TAG, "On-device Text detection successful") Log.d(TAG, "On-device Text detection successful")
logExtrasForTesting(text) logExtrasForTesting(text)

View File

@ -1,10 +1,16 @@
package com.assimilate.alltrans.common; package com.assimilate.alltrans.common;
import android.app.Activity; import android.app.Activity;
import android.graphics.Color;
import android.view.Gravity;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.google.android.material.snackbar.Snackbar;
public class Widget { public class Widget {
private static volatile Toast toast; private static volatile Toast toast;
@ -15,4 +21,18 @@ public class Widget {
toast = Toast.makeText(mActivity, msg, Toast.LENGTH_SHORT); toast = Toast.makeText(mActivity, msg, Toast.LENGTH_SHORT);
toast.show(); toast.show();
} }
public static void makeSnackbar(@NonNull final Activity mActivity, @NonNull final String msg) {
View view = mActivity.findViewById(android.R.id.content);
Snackbar snackbar = Snackbar.make(view, msg, Snackbar.LENGTH_LONG);
View snackbarView = snackbar.getView();
snackbarView.setBackgroundColor(Color.parseColor("#66000000"));
TextView textView = snackbarView.findViewById(com.google.android.material.R.id.snackbar_text);
textView.setTextColor(Color.WHITE);
textView.setTextSize(16f);
textView.setGravity(Gravity.CENTER);
snackbar.show();
}
} }

View File

@ -9,6 +9,7 @@ import android.graphics.Canvas;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.Paint; import android.graphics.Paint;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
@ -37,266 +38,294 @@ import java.util.List;
* </ol> * </ol>
*/ */
public class GraphicOverlay extends View { public class GraphicOverlay extends View {
private final Object lock = new Object(); private final Object lock = new Object();
private final List<Graphic> graphics = new ArrayList<>(); private final List<Graphic> graphics = new ArrayList<>();
// Matrix for transforming from image coordinates to overlay view coordinates. // Matrix for transforming from image coordinates to overlay view coordinates.
private final Matrix transformationMatrix = new Matrix(); private final Matrix transformationMatrix = new Matrix();
private int imageWidth; private int imageWidth;
private int imageHeight; private int imageHeight;
// The factor of overlay View size to image size. Anything in the image coordinates need to be // The factor of overlay View size to image size. Anything in the image coordinates need to be
// scaled by this amount to fit with the area of overlay View. // scaled by this amount to fit with the area of overlay View.
private float scaleFactor = 1.0f; private float scaleFactor = 1.0f;
// The number of horizontal pixels needed to be cropped on each side to fit the image with the // The number of horizontal pixels needed to be cropped on each side to fit the image with the
// area of overlay View after scaling. // area of overlay View after scaling.
private float postScaleWidthOffset; private float postScaleWidthOffset;
// The number of vertical pixels needed to be cropped on each side to fit the image with the // The number of vertical pixels needed to be cropped on each side to fit the image with the
// area of overlay View after scaling. // area of overlay View after scaling.
private float postScaleHeightOffset; private float postScaleHeightOffset;
private boolean isImageFlipped; private boolean isImageFlipped;
private boolean needUpdateTransformation = true; private boolean needUpdateTransformation = true;
/** /**
* Base class for a custom graphics object to be rendered within the graphic overlay. Subclass * Base class for a custom graphics object to be rendered within the graphic overlay. Subclass
* this and implement the {@link Graphic#draw(Canvas)} method to define the graphics element. Add * this and implement the {@link Graphic#draw(Canvas)} method to define the graphics element. Add
* instances to the overlay using {@link GraphicOverlay#add(Graphic)}. * instances to the overlay using {@link GraphicOverlay#add(Graphic)}.
*/ */
public abstract static class Graphic { public abstract static class Graphic {
private GraphicOverlay overlay; private GraphicOverlay overlay;
public Graphic(GraphicOverlay overlay) { public Graphic(GraphicOverlay overlay) {
this.overlay = overlay; this.overlay = overlay;
}
/**
* Draw the graphic on the supplied canvas. Drawing should use the following methods to convert
* to view coordinates for the graphics that are drawn:
*
* <ol>
* <li>{@link Graphic#scale(float)} adjusts the size of the supplied value from the image
* scale to the view scale.
* <li>{@link Graphic#translateX(float)} and {@link Graphic#translateY(float)} adjust the
* coordinate from the image's coordinate system to the view coordinate system.
* </ol>
*
* @param canvas drawing canvas
*/
public abstract void draw(Canvas canvas);
protected void drawRect(
Canvas canvas, float left, float top, float right, float bottom, Paint paint) {
canvas.drawRect(left, top, right, bottom, paint);
}
protected void drawText(Canvas canvas, String text, float x, float y, Paint paint) {
canvas.drawText(text, x, y, paint);
}
/**
* Adjusts the supplied value from the image scale to the view scale.
*/
public float scale(float imagePixel) {
return imagePixel * overlay.scaleFactor;
}
/**
* Returns the application context of the app.
*/
public Context getApplicationContext() {
return overlay.getContext().getApplicationContext();
}
public boolean isImageFlipped() {
return overlay.isImageFlipped;
}
/**
* Adjusts the x coordinate from the image's coordinate system to the view coordinate system.
*/
public float translateX(float x) {
if (overlay.isImageFlipped) {
return overlay.getWidth() - (scale(x) - overlay.postScaleWidthOffset);
} else {
return scale(x) - overlay.postScaleWidthOffset;
}
}
/**
* Adjusts the y coordinate from the image's coordinate system to the view coordinate system.
*/
public float translateY(float y) {
return scale(y) - overlay.postScaleHeightOffset;
}
/**
* Returns a {@link Matrix} for transforming from image coordinates to overlay view coordinates.
*/
public Matrix getTransformationMatrix() {
return overlay.transformationMatrix;
}
public void postInvalidate() {
overlay.postInvalidate();
}
/**
* Given the {@code zInImagePixel}, update the color for the passed in {@code paint}. The color will be
* more red if the {@code zInImagePixel} is smaller, or more blue ish vice versa. This is
* useful to visualize the z value of landmarks via color for features like Pose and Face Mesh.
*
* @param paint the paint to update color with
* @param canvas the canvas used to draw with paint
* @param visualizeZ if true, paint color will be changed.
* @param rescaleZForVisualization if true, re-scale the z value with zMin and zMax to make
* color more distinguishable
* @param zInImagePixel the z value used to update the paint color
* @param zMin min value of all z values going to be passed in
* @param zMax max value of all z values going to be passed in
*/
public void updatePaintColorByZValue(
Paint paint,
Canvas canvas,
boolean visualizeZ,
boolean rescaleZForVisualization,
float zInImagePixel,
float zMin,
float zMax) {
if (!visualizeZ) {
return;
}
// When visualizeZ is true, sets up the paint to different colors based on z values.
// Gets the range of z value.
float zLowerBoundInScreenPixel;
float zUpperBoundInScreenPixel;
if (rescaleZForVisualization) {
zLowerBoundInScreenPixel = min(-0.001f, scale(zMin));
zUpperBoundInScreenPixel = max(0.001f, scale(zMax));
} else {
// By default, assume the range of z value in screen pixel is [-canvasWidth, canvasWidth].
float defaultRangeFactor = 1f;
zLowerBoundInScreenPixel = -defaultRangeFactor * canvas.getWidth();
zUpperBoundInScreenPixel = defaultRangeFactor * canvas.getWidth();
}
float zInScreenPixel = scale(zInImagePixel);
if (zInScreenPixel < 0) {
// Sets up the paint to be red if the item is in front of the z origin.
// Maps values within [zLowerBoundInScreenPixel, 0) to [255, 0) and use it to control the
// color. The larger the value is, the more red it will be.
int v = (int) (zInScreenPixel / zLowerBoundInScreenPixel * 255);
v = Ints.constrainToRange(v, 0, 255);
paint.setARGB(255, 255, 255 - v, 255 - v);
} else {
// Sets up the paint to be blue if the item is behind the z origin.
// Maps values within [0, zUpperBoundInScreenPixel] to [0, 255] and use it to control the
// color. The larger the value is, the more blue it will be.
int v = (int) (zInScreenPixel / zUpperBoundInScreenPixel * 255);
v = Ints.constrainToRange(v, 0, 255);
paint.setARGB(255, 255 - v, 255 - v, 255);
}
}
}
public GraphicOverlay(Context context, AttributeSet attrs) {
super(context, attrs);
addOnLayoutChangeListener(
(view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
needUpdateTransformation = true);
} }
/** /**
* Draw the graphic on the supplied canvas. Drawing should use the following methods to convert * Removes all graphics from the overlay.
* to view coordinates for the graphics that are drawn: */
public void clear() {
synchronized (lock) {
graphics.clear();
}
postInvalidate();
}
/**
* Adds a graphic to the overlay.
*/
public void add(Graphic graphic) {
synchronized (lock) {
graphics.add(graphic);
}
}
/**
* Removes a graphic from the overlay.
*/
public void remove(Graphic graphic) {
synchronized (lock) {
graphics.remove(graphic);
}
postInvalidate();
}
/**
* Sets the source information of the image being processed by detectors, including size and
* whether it is flipped, which informs how to transform image coordinates later.
* *
* <ol> * @param imageWidth the width of the image sent to ML Kit detectors
* <li>{@link Graphic#scale(float)} adjusts the size of the supplied value from the image * @param imageHeight the height of the image sent to ML Kit detectors
* scale to the view scale. * @param isFlipped whether the image is flipped. Should set it to true when the image is from the
* <li>{@link Graphic#translateX(float)} and {@link Graphic#translateY(float)} adjust the * front camera.
* coordinate from the image's coordinate system to the view coordinate system.
* </ol>
*
* @param canvas drawing canvas
*/ */
public abstract void draw(Canvas canvas); public void setImageSourceInfo(int imageWidth, int imageHeight, boolean isFlipped) {
Preconditions.checkState(imageWidth > 0, "image width must be positive");
protected void drawRect( Preconditions.checkState(imageHeight > 0, "image height must be positive");
Canvas canvas, float left, float top, float right, float bottom, Paint paint) { synchronized (lock) {
canvas.drawRect(left, top, right, bottom, paint); this.imageWidth = imageWidth;
this.imageHeight = imageHeight;
this.isImageFlipped = isFlipped;
needUpdateTransformation = true;
}
postInvalidate();
} }
protected void drawText(Canvas canvas, String text, float x, float y, Paint paint) { public int getImageWidth() {
canvas.drawText(text, x, y, paint); return imageWidth;
} }
/** Adjusts the supplied value from the image scale to the view scale. */ public int getImageHeight() {
public float scale(float imagePixel) { return imageHeight;
return imagePixel * overlay.scaleFactor;
} }
/** Returns the application context of the app. */ private void updateTransformationIfNeeded() {
public Context getApplicationContext() { if (!needUpdateTransformation || imageWidth <= 0 || imageHeight <= 0) {
return overlay.getContext().getApplicationContext(); return;
} }
float viewAspectRatio = (float) getWidth() / getHeight();
float imageAspectRatio = (float) imageWidth / imageHeight;
postScaleWidthOffset = 0;
postScaleHeightOffset = 0;
if (viewAspectRatio > imageAspectRatio) {
// The image needs to be vertically cropped to be displayed in this view.
scaleFactor = (float) getWidth() / imageWidth;
postScaleHeightOffset = ((float) getWidth() / imageAspectRatio - getHeight()) / 2;
} else {
// The image needs to be horizontally cropped to be displayed in this view.
scaleFactor = (float) getHeight() / imageHeight;
postScaleWidthOffset = ((float) getHeight() * imageAspectRatio - getWidth()) / 2;
}
public boolean isImageFlipped() { transformationMatrix.reset();
return overlay.isImageFlipped; transformationMatrix.setScale(scaleFactor, scaleFactor);
transformationMatrix.postTranslate(-postScaleWidthOffset, -postScaleHeightOffset);
if (isImageFlipped) {
transformationMatrix.postScale(-1f, 1f, getWidth() / 2f, getHeight() / 2f);
}
needUpdateTransformation = false;
} }
/** /**
* Adjusts the x coordinate from the image's coordinate system to the view coordinate system. * Draws the overlay with its associated graphic objects.
*/ */
public float translateX(float x) { @Override
if (overlay.isImageFlipped) { protected void onDraw(Canvas canvas) {
return overlay.getWidth() - (scale(x) - overlay.postScaleWidthOffset); super.onDraw(canvas);
} else {
return scale(x) - overlay.postScaleWidthOffset; synchronized (lock) {
} updateTransformationIfNeeded();
for (Graphic graphic : graphics) {
graphic.draw(canvas);
}
}
} }
/** @Override
* Adjusts the y coordinate from the image's coordinate system to the view coordinate system. public boolean onTouchEvent(MotionEvent event) {
*/ switch (event.getAction()) {
public float translateY(float y) { case MotionEvent.ACTION_DOWN:
return scale(y) - overlay.postScaleHeightOffset;
setVisibility(View.INVISIBLE);
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
setVisibility(View.VISIBLE);
return true;
}
return super.onTouchEvent(event);
} }
/**
* Returns a {@link Matrix} for transforming from image coordinates to overlay view coordinates.
*/
public Matrix getTransformationMatrix() {
return overlay.transformationMatrix;
}
public void postInvalidate() {
overlay.postInvalidate();
}
/**
* Given the {@code zInImagePixel}, update the color for the passed in {@code paint}. The color will be
* more red if the {@code zInImagePixel} is smaller, or more blue ish vice versa. This is
* useful to visualize the z value of landmarks via color for features like Pose and Face Mesh.
*
* @param paint the paint to update color with
* @param canvas the canvas used to draw with paint
* @param visualizeZ if true, paint color will be changed.
* @param rescaleZForVisualization if true, re-scale the z value with zMin and zMax to make
* color more distinguishable
* @param zInImagePixel the z value used to update the paint color
* @param zMin min value of all z values going to be passed in
* @param zMax max value of all z values going to be passed in
*/
public void updatePaintColorByZValue(
Paint paint,
Canvas canvas,
boolean visualizeZ,
boolean rescaleZForVisualization,
float zInImagePixel,
float zMin,
float zMax) {
if (!visualizeZ) {
return;
}
// When visualizeZ is true, sets up the paint to different colors based on z values.
// Gets the range of z value.
float zLowerBoundInScreenPixel;
float zUpperBoundInScreenPixel;
if (rescaleZForVisualization) {
zLowerBoundInScreenPixel = min(-0.001f, scale(zMin));
zUpperBoundInScreenPixel = max(0.001f, scale(zMax));
} else {
// By default, assume the range of z value in screen pixel is [-canvasWidth, canvasWidth].
float defaultRangeFactor = 1f;
zLowerBoundInScreenPixel = -defaultRangeFactor * canvas.getWidth();
zUpperBoundInScreenPixel = defaultRangeFactor * canvas.getWidth();
}
float zInScreenPixel = scale(zInImagePixel);
if (zInScreenPixel < 0) {
// Sets up the paint to be red if the item is in front of the z origin.
// Maps values within [zLowerBoundInScreenPixel, 0) to [255, 0) and use it to control the
// color. The larger the value is, the more red it will be.
int v = (int) (zInScreenPixel / zLowerBoundInScreenPixel * 255);
v = Ints.constrainToRange(v, 0, 255);
paint.setARGB(255, 255, 255 - v, 255 - v);
} else {
// Sets up the paint to be blue if the item is behind the z origin.
// Maps values within [0, zUpperBoundInScreenPixel] to [0, 255] and use it to control the
// color. The larger the value is, the more blue it will be.
int v = (int) (zInScreenPixel / zUpperBoundInScreenPixel * 255);
v = Ints.constrainToRange(v, 0, 255);
paint.setARGB(255, 255 - v, 255 - v, 255);
}
}
}
public GraphicOverlay(Context context, AttributeSet attrs) {
super(context, attrs);
addOnLayoutChangeListener(
(view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
needUpdateTransformation = true);
}
/** Removes all graphics from the overlay. */
public void clear() {
synchronized (lock) {
graphics.clear();
}
postInvalidate();
}
/** Adds a graphic to the overlay. */
public void add(Graphic graphic) {
synchronized (lock) {
graphics.add(graphic);
}
}
/** Removes a graphic from the overlay. */
public void remove(Graphic graphic) {
synchronized (lock) {
graphics.remove(graphic);
}
postInvalidate();
}
/**
* Sets the source information of the image being processed by detectors, including size and
* whether it is flipped, which informs how to transform image coordinates later.
*
* @param imageWidth the width of the image sent to ML Kit detectors
* @param imageHeight the height of the image sent to ML Kit detectors
* @param isFlipped whether the image is flipped. Should set it to true when the image is from the
* front camera.
*/
public void setImageSourceInfo(int imageWidth, int imageHeight, boolean isFlipped) {
Preconditions.checkState(imageWidth > 0, "image width must be positive");
Preconditions.checkState(imageHeight > 0, "image height must be positive");
synchronized (lock) {
this.imageWidth = imageWidth;
this.imageHeight = imageHeight;
this.isImageFlipped = isFlipped;
needUpdateTransformation = true;
}
postInvalidate();
}
public int getImageWidth() {
return imageWidth;
}
public int getImageHeight() {
return imageHeight;
}
private void updateTransformationIfNeeded() {
if (!needUpdateTransformation || imageWidth <= 0 || imageHeight <= 0) {
return;
}
float viewAspectRatio = (float) getWidth() / getHeight();
float imageAspectRatio = (float) imageWidth / imageHeight;
postScaleWidthOffset = 0;
postScaleHeightOffset = 0;
if (viewAspectRatio > imageAspectRatio) {
// The image needs to be vertically cropped to be displayed in this view.
scaleFactor = (float) getWidth() / imageWidth;
postScaleHeightOffset = ((float) getWidth() / imageAspectRatio - getHeight()) / 2;
} else {
// The image needs to be horizontally cropped to be displayed in this view.
scaleFactor = (float) getHeight() / imageHeight;
postScaleWidthOffset = ((float) getHeight() * imageAspectRatio - getWidth()) / 2;
}
transformationMatrix.reset();
transformationMatrix.setScale(scaleFactor, scaleFactor);
transformationMatrix.postTranslate(-postScaleWidthOffset, -postScaleHeightOffset);
if (isImageFlipped) {
transformationMatrix.postScale(-1f, 1f, getWidth() / 2f, getHeight() / 2f);
}
needUpdateTransformation = false;
}
/** Draws the overlay with its associated graphic objects. */
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
synchronized (lock) {
updateTransformationIfNeeded();
for (Graphic graphic : graphics) {
graphic.draw(canvas);
}
}
}
} }

View File

@ -122,7 +122,7 @@ public class HistoryActivity extends AppCompatActivity {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (ids.isEmpty()) { if (ids.isEmpty()) {
Widget.makeToast(HistoryActivity.this, "Noting to remove."); Widget.makeToast(HistoryActivity.this, "Noting to remove.");
return; return;
} }

View File

@ -7,64 +7,102 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.assimilate.alltrans.MyApp
import com.assimilate.alltrans.R import com.assimilate.alltrans.R
import com.assimilate.alltrans.adapters.LanguageAdapter import com.assimilate.alltrans.adapters.LanguageAdapter
import com.assimilate.alltrans.common.Language import com.assimilate.alltrans.common.Language
import com.assimilate.alltrans.common.LanguagesConstants import com.assimilate.alltrans.common.LanguagesConstants
import com.assimilate.alltrans.common.PreferenceLanguageUtils
import com.assimilate.alltrans.databinding.ActivityLanguageChangeBinding import com.assimilate.alltrans.databinding.ActivityLanguageChangeBinding
class LanguageChangeActivity : AppCompatActivity() { class LanguageChangeActivity : AppCompatActivity() {
private lateinit var binding: ActivityLanguageChangeBinding private lateinit var binding: ActivityLanguageChangeBinding
private var lastTranslateLanguage = false private var lastTranslateLanguage = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityLanguageChangeBinding.inflate(layoutInflater) binding = ActivityLanguageChangeBinding.inflate(layoutInflater)
enableEdgeToEdge() enableEdgeToEdge()
setContentView(binding.root) setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets insets
} }
initView()
initList() initList()
initClick() initClick()
} }
private fun initView() {
binding.tvChangeSource.text = PreferenceLanguageUtils.getString("language_source")
binding.tvChangeTarget.text = PreferenceLanguageUtils.getString("language_target")
updateRecentLanguages()
}
private fun updateRecentLanguages() {
val recentLanguages = PreferenceLanguageUtils.getRecentLanguages()
if (recentLanguages.isNotEmpty()) {
val recentAdapter = LanguageAdapter(this, ArrayList(recentLanguages)) { _, language ->
Log.d("LanguageChange", language.language)
if (lastTranslateLanguage) {
PreferenceLanguageUtils.putString("language_target", language.language)
onBackPressed()
} else {
PreferenceLanguageUtils.putString("language_source", language.language)
}
PreferenceLanguageUtils.addRecentLanguage(language)
binding.tvChangeSource.text = PreferenceLanguageUtils.getString("language_source")
binding.tvChangeTarget.text = PreferenceLanguageUtils.getString("language_target")
updateRecentLanguages()
}
binding.listLanCommon5.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
binding.listLanCommon5.adapter = recentAdapter
}
}
private fun initClick() { private fun initClick() {
binding.tvChangeSource.setOnClickListener { binding.tvChangeSource.setOnClickListener {
lastTranslateLanguage = false lastTranslateLanguage = false
} }
binding.tvChangeTarget.setOnClickListener { binding.tvChangeTarget.setOnClickListener {
lastTranslateLanguage = true lastTranslateLanguage = true
}
binding.tvExchange.setOnClickListener {
binding.tvExchange.setOnClickListener {
val currentSourceLanguage = PreferenceLanguageUtils.getString("language_source")
val currentTargetLanguage = PreferenceLanguageUtils.getString("language_target")
// 交换源语言和目标语言
PreferenceLanguageUtils.putString("language_source", currentTargetLanguage)
PreferenceLanguageUtils.putString("language_target", currentSourceLanguage)
// 更新界面显示
binding.tvChangeSource.text = currentTargetLanguage
binding.tvChangeTarget.text = currentSourceLanguage
onBackPressed()
}
} }
} }
private fun initList() { private fun initList() {
val languages: ArrayList<Language> = LanguagesConstants.getInstance().getList( val languages: ArrayList<Language> = LanguagesConstants.getInstance().getList(this)
this
)
if (languages.isNotEmpty()) { if (languages.isNotEmpty()) {
val adapter = val adapter = LanguageAdapter(this, languages) { _, language ->
LanguageAdapter( Log.d("LanguageChange", language.language)
this, languages if (lastTranslateLanguage) {
) { _, language -> PreferenceLanguageUtils.putString("language_target", language.language)
onBackPressed()
Log.d("fsdafsdfd", language.language) } else {
if (lastTranslateLanguage) { PreferenceLanguageUtils.putString("language_source", language.language)
MyApp.setTargetLanguage(language)
onBackPressed()
} else {
MyApp.setSourceLanguage(language)
}
binding.tvChangeSource.text = MyApp.getSourceLanguage()
binding.tvChangeTarget.text = MyApp.getTargetLanguage()
} }
val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) PreferenceLanguageUtils.addRecentLanguage(language)
binding.listLanguages.setLayoutManager(layoutManager) binding.tvChangeSource.text = PreferenceLanguageUtils.getString("language_source")
binding.listLanguages.setAdapter(adapter) binding.tvChangeTarget.text = PreferenceLanguageUtils.getString("language_target")
updateRecentLanguages()
}
binding.listLanguages.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
binding.listLanguages.adapter = adapter
} }
} }
} }

View File

@ -13,6 +13,7 @@ import android.speech.RecognizerIntent
import android.text.Editable import android.text.Editable
import android.text.TextUtils import android.text.TextUtils
import android.text.TextWatcher import android.text.TextWatcher
import android.util.Log
import android.view.View import android.view.View
import android.widget.EditText import android.widget.EditText
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
@ -25,6 +26,8 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.assimilate.alltrans.MyApp import com.assimilate.alltrans.MyApp
import com.assimilate.alltrans.R import com.assimilate.alltrans.R
import com.assimilate.alltrans.allservice.SusService import com.assimilate.alltrans.allservice.SusService
import com.assimilate.alltrans.common.LanguagesConstants
import com.assimilate.alltrans.common.PreferenceLanguageUtils
import com.assimilate.alltrans.common.Widget import com.assimilate.alltrans.common.Widget
import com.assimilate.alltrans.databinding.ActivityMainBinding import com.assimilate.alltrans.databinding.ActivityMainBinding
@ -101,8 +104,8 @@ class MainActivity : AppCompatActivity() {
private fun initView() { private fun initView() {
binding.chSourceLanguage.text = MyApp.getSourceLanguage() binding.chSourceLanguage.text = PreferenceLanguageUtils.getString("language_source")
binding.chTargetLanguage.text = MyApp.getTargetLanguage() binding.chTargetLanguage.text = PreferenceLanguageUtils.getString("language_target")
} }
private fun initSet() { private fun initSet() {
@ -187,6 +190,25 @@ class MainActivity : AppCompatActivity() {
} }
} }
binding.ivMainExChange.setOnClickListener {
// 读取当前的源语言和目标语言
val currentSourceLanguage = PreferenceLanguageUtils.getString("language_source")
val currentTargetLanguage = PreferenceLanguageUtils.getString("language_target")
// 交换源语言和目标语言
PreferenceLanguageUtils.putString("language_source", currentTargetLanguage)
PreferenceLanguageUtils.putString("language_target", currentSourceLanguage)
// 更新界面显示
binding.chSourceLanguage.text = currentTargetLanguage
binding.chTargetLanguage.text = currentSourceLanguage
// 打印日志,验证交换后的语言设置
Log.d("fdhash_su", PreferenceLanguageUtils.getString("language_source"))
Log.d("fdhash_ta", PreferenceLanguageUtils.getString("language_target"))
}
} }
private fun toTextTransResult() { private fun toTextTransResult() {
@ -200,6 +222,7 @@ class MainActivity : AppCompatActivity() {
binding.etText.text = null binding.etText.text = null
} }
// 语音转文本 // 语音转文本
private fun voiceToText() { private fun voiceToText() {
val speechIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH) val speechIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
@ -212,18 +235,22 @@ class MainActivity : AppCompatActivity() {
5000 5000
) // 设置5秒的可能完全静默时间 ) // 设置5秒的可能完全静默时间
speechIntent.putExtra(
"android.speech.extra.LANGUAGE_MODEL", // speechIntent.putExtra(
MyApp.getSourceLanguage() // "android.speech.extra.LANGUAGE_MODEL",
) // MyApp.getSourceLanguage()
// )
val languageCode = LanguagesConstants.getInstance()
.getLanguageCodeByLanguage(PreferenceLanguageUtils.getString("language_source"), this)
speechIntent.putExtra( speechIntent.putExtra(
"android.speech.extra.LANGUAGE", "android.speech.extra.LANGUAGE",
MyApp.getSourceLanguageCode() languageCode
)
speechIntent.putExtra(
"android.speech.extra.LANGUAGE_PREFERENCE",
MyApp.getSourceLanguage()
) )
// speechIntent.putExtra(
// "android.speech.extra.LANGUAGE_PREFERENCE",
//
// )
try { try {
launcher?.launch(speechIntent) launcher?.launch(speechIntent)
} catch (ea: ActivityNotFoundException) { } catch (ea: ActivityNotFoundException) {
@ -250,7 +277,6 @@ class MainActivity : AppCompatActivity() {
} }
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
initView() initView()
@ -264,5 +290,4 @@ class MainActivity : AppCompatActivity() {
} }
} }
} }

View File

@ -13,34 +13,43 @@ import android.os.Bundle
import android.provider.MediaStore import android.provider.MediaStore
import android.util.Log import android.util.Log
import android.util.Pair import android.util.Pair
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.AdapterView.OnItemSelectedListener import android.widget.AdapterView.OnItemSelectedListener
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.ImageView import android.widget.ImageView
import android.widget.PopupMenu
import android.widget.Spinner import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.assimilate.alltrans.R import com.assimilate.alltrans.R
import com.assimilate.alltrans.common.BitmapUtils import com.assimilate.alltrans.common.BitmapUtils
import com.assimilate.alltrans.common.TextRecognitionProcessor import com.assimilate.alltrans.common.TextRecognitionProcessor
import com.assimilate.alltrans.common.VisionImageProcessor import com.assimilate.alltrans.common.VisionImageProcessor
import com.assimilate.alltrans.common.Widget
import com.assimilate.alltrans.curview.GraphicOverlay import com.assimilate.alltrans.curview.GraphicOverlay
import com.google.android.gms.common.annotation.KeepName import com.google.android.gms.common.annotation.KeepName
import com.google.common.util.concurrent.ListenableFuture
import com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions import com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions
import com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions import com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions
import com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions import com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions
import com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions import com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions
import com.google.mlkit.vision.text.latin.TextRecognizerOptions import com.google.mlkit.vision.text.latin.TextRecognizerOptions
import java.io.File
import java.io.IOException import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Locale
/** Activity demonstrating different image detector features with a still image from camera. */ /** 演示使用相机拍摄静态图像进行不同图像检测功能的活动。 */
@KeepName @KeepName
class StillImageActivity : AppCompatActivity() { class StillImageActivity : AppCompatActivity() {
private var preview: ImageView? = null private var preview: ImageView? = null
@ -49,17 +58,16 @@ class StillImageActivity : AppCompatActivity() {
private var selectedSize: String? = SIZE_SCREEN private var selectedSize: String? = SIZE_SCREEN
private var isLandScape = false private var isLandScape = false
private var imageUri: Uri? = null private var imageUri: Uri? = null
// Max width (portrait mode)
private var imageMaxWidth = 0 private var imageMaxWidth = 0
// Max height (portrait mode)
private var imageMaxHeight = 0 private var imageMaxHeight = 0
private var imageProcessor: VisionImageProcessor? = null private var imageProcessor: VisionImageProcessor? = null
private lateinit var imageCapture: ImageCapture
private lateinit var outputDirectory: File
private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
private var isFlashOn = false
private val REQUEST_CAMERA_PERMISSION = 100 private val REQUEST_CAMERA_PERMISSION = 100
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_still_image) setContentView(R.layout.activity_still_image)
@ -74,31 +82,13 @@ class StillImageActivity : AppCompatActivity() {
REQUEST_CAMERA_PERMISSION REQUEST_CAMERA_PERMISSION
) )
} }
Widget.makeSnackbar(this, "Photographing text for translation")
findViewById<View>(R.id.select_image_button).setOnClickListener { view: View ->
// Menu for selecting either: a) take new photo b) select from existing
val popup = PopupMenu(this@StillImageActivity, view)
popup.setOnMenuItemClickListener { menuItem: MenuItem ->
val itemId = menuItem.itemId
if (itemId == R.id.select_images_from_local) {
startChooseImageIntentForResult()
return@setOnMenuItemClickListener true
} else if (itemId == R.id.take_photo_using_camera) {
startCameraIntentForResult()
return@setOnMenuItemClickListener true
}
false
}
val inflater = popup.menuInflater
inflater.inflate(R.menu.camera_button_menu, popup.menu)
popup.show()
}
preview = findViewById(R.id.preview) preview = findViewById(R.id.preview)
graphicOverlay = findViewById(R.id.graphic_overlay) graphicOverlay = findViewById(R.id.graphic_overlay)
populateFeatureSelector() populateFeatureSelector()
populateSizeSelector()
isLandScape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE isLandScape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
if (savedInstanceState != null) { if (savedInstanceState != null) {
imageUri = savedInstanceState.getParcelable(KEY_IMAGE_URI) imageUri = savedInstanceState.getParcelable(KEY_IMAGE_URI)
@ -121,12 +111,107 @@ class StillImageActivity : AppCompatActivity() {
} }
) )
val settingsButton = findViewById<ImageView>(R.id.settings_button) // 初始化相机
settingsButton.setOnClickListener { startCamera()
// val intent = Intent(applicationContext, SettingsActivity::class.java) outputDirectory = getOutputDirectory()
// intent.putExtra(SettingsActivity.EXTRA_LAUNCH_SOURCE, LaunchSource.STILL_IMAGE) initClick()
// startActivity(intent) }
private fun initClick() {
findViewById<ImageView>(R.id.iv_still_pic).setOnClickListener { startChooseImageIntentForResult() }
findViewById<ImageView>(R.id.iv_still_take).setOnClickListener { takePhoto() }
findViewById<ImageView>(R.id.iv_still_back).setOnClickListener { onBackPressed() }
findViewById<ImageView>(R.id.iv_still_buling).setOnClickListener {
toggleFlash()
updateFlashButtonUI()
} }
findViewById<TextView>(R.id.still_source_language).setOnClickListener { }
findViewById<TextView>(R.id.still_target_language).setOnClickListener { }
findViewById<TextView>(R.id.still_exChange).setOnClickListener { }
}
private fun toggleFlash() {
if (isFlashOn) {
imageCapture.flashMode = ImageCapture.FLASH_MODE_OFF
} else {
imageCapture.flashMode = ImageCapture.FLASH_MODE_ON
}
isFlashOn = !isFlashOn
}
private fun updateFlashButtonUI() {
if (isFlashOn) {
findViewById<ImageView>(R.id.iv_still_buling).setImageResource(R.drawable.ic_still_bulibuli)
} else {
findViewById<ImageView>(R.id.iv_still_buling).setImageResource(R.drawable.ic_still_notbuli)
}
}
private fun startCamera() {
val previewView = findViewById<PreviewView>(R.id.photo_preview)
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener(Runnable {
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
imageCapture = ImageCapture.Builder()
.setFlashMode(ImageCapture.FLASH_MODE_OFF) // 默认关闭闪光灯
.build()
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(this))
}
private fun takePhoto() {
val imageCapture = imageCapture
val photoFile = File(
outputDirectory,
SimpleDateFormat(FILENAME_FORMAT, Locale.US).format(System.currentTimeMillis()) + ".jpg"
)
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
imageCapture.takePicture(
outputOptions,
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val savedUri = Uri.fromFile(photoFile)
imageUri = savedUri
val msg = "Photo capture succeeded: $savedUri"
Log.d(TAG, msg)
tryReloadAndDetectInImage()
}
}
)
}
private fun getOutputDirectory(): File {
val mediaDir = externalMediaDirs.firstOrNull()?.let {
File(it, resources.getString(R.string.app_name)).apply { mkdirs() }
}
return if (mediaDir != null && mediaDir.exists())
mediaDir else filesDir
} }
@ -169,7 +254,12 @@ class StillImageActivity : AppCompatActivity() {
public override fun onDestroy() { public override fun onDestroy() {
super.onDestroy() super.onDestroy()
imageProcessor?.run { this.stop() } imageProcessor?.run { this.stop() }
// 释放相机资源
if (::cameraProviderFuture.isInitialized) {
cameraProviderFuture.get().unbindAll()
}
} }
private fun populateFeatureSelector() { private fun populateFeatureSelector() {
@ -207,37 +297,6 @@ class StillImageActivity : AppCompatActivity() {
} }
} }
private fun populateSizeSelector() {
val sizeSpinner = findViewById<Spinner>(R.id.size_selector)
val options: MutableList<String> = ArrayList()
options.add(SIZE_SCREEN)
options.add(SIZE_1024_768)
options.add(SIZE_640_480)
options.add(SIZE_ORIGINAL)
// Creating adapter for featureSpinner
val dataAdapter = ArrayAdapter(this, R.layout.spinner_style, options)
// Drop down layout style - list view with radio button
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
// attaching data adapter to spinner
sizeSpinner.adapter = dataAdapter
sizeSpinner.onItemSelectedListener =
object : OnItemSelectedListener {
override fun onItemSelected(
parentView: AdapterView<*>,
selectedItemView: View?,
pos: Int,
id: Long
) {
if (pos >= 0) {
selectedSize = parentView.getItemAtPosition(pos).toString()
tryReloadAndDetectInImage()
}
}
override fun onNothingSelected(arg0: AdapterView<*>?) {}
}
}
public override fun onSaveInstanceState(outState: Bundle) { public override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putParcelable(KEY_IMAGE_URI, imageUri) outState.putParcelable(KEY_IMAGE_URI, imageUri)
@ -247,6 +306,7 @@ class StillImageActivity : AppCompatActivity() {
} }
private fun startCameraIntentForResult() { private fun startCameraIntentForResult() {
// Ensure permission is still granted before starting camera intent // Ensure permission is still granted before starting camera intent
if (ContextCompat.checkSelfPermission( if (ContextCompat.checkSelfPermission(
this, this,
@ -299,12 +359,10 @@ class StillImageActivity : AppCompatActivity() {
if (imageUri == null) { if (imageUri == null) {
return return
} }
if (SIZE_SCREEN == selectedSize && imageMaxWidth == 0) { if (SIZE_SCREEN == selectedSize && imageMaxWidth == 0) {
// UI layout has not finished yet, will reload once it's ready. // UI layout has not finished yet, will reload once it's ready.
return return
} }
val imageBitmap = val imageBitmap =
BitmapUtils.getBitmapFromContentUri(contentResolver, imageUri) ?: return BitmapUtils.getBitmapFromContentUri(contentResolver, imageUri) ?: return
// Clear the overlay first // Clear the overlay first
@ -443,5 +501,6 @@ class StillImageActivity : AppCompatActivity() {
private const val KEY_SELECTED_SIZE = "com.google.mlkit.vision.demo.KEY_SELECTED_SIZE" private const val KEY_SELECTED_SIZE = "com.google.mlkit.vision.demo.KEY_SELECTED_SIZE"
private const val REQUEST_IMAGE_CAPTURE = 1001 private const val REQUEST_IMAGE_CAPTURE = 1001
private const val REQUEST_CHOOSE_IMAGE = 1002 private const val REQUEST_CHOOSE_IMAGE = 1002
private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
} }
} }

View File

@ -7,13 +7,16 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.speech.tts.TextToSpeech import android.speech.tts.TextToSpeech
import android.text.TextUtils import android.text.TextUtils
import android.util.Log
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import com.assimilate.alltrans.MyApp import com.assimilate.alltrans.MyApp
import com.assimilate.alltrans.R import com.assimilate.alltrans.R
import com.assimilate.alltrans.common.LanguagesConstants
import com.assimilate.alltrans.common.Logger import com.assimilate.alltrans.common.Logger
import com.assimilate.alltrans.common.PreferenceLanguageUtils
import com.assimilate.alltrans.common.Widget import com.assimilate.alltrans.common.Widget
import com.assimilate.alltrans.databinding.ActivityTextResultBinding import com.assimilate.alltrans.databinding.ActivityTextResultBinding
import com.assimilate.alltrans.http.GoogleTranslator import com.assimilate.alltrans.http.GoogleTranslator
@ -67,8 +70,20 @@ class TextResultActivity : AppCompatActivity() {
} }
binding.ivTrCopy.setOnClickListener { copyToClipboard() } binding.ivTrCopy.setOnClickListener { copyToClipboard() }
binding.ivSourceClear.setOnClickListener { onBackPressed() } binding.ivSourceClear.setOnClickListener { onBackPressed() }
binding.ivSourceTts.setOnClickListener { readText(binding.tvTrSource.text.toString()) } binding.ivSourceTts.setOnClickListener {
binding.ivTargetTts.setOnClickListener { readText(binding.tvTrTarget.text.toString()) } readText(
binding.tvTrSource.text.toString(),
PreferenceLanguageUtils.getString("language_source"),
this
)
}
binding.ivTargetTts.setOnClickListener {
readText(
binding.tvTrTarget.text.toString(),
PreferenceLanguageUtils.getString("language_target"),
this
)
}
binding.ivTrTargetShare.setOnClickListener { shareText(binding.tvTrTarget.text.toString()) } binding.ivTrTargetShare.setOnClickListener { shareText(binding.tvTrTarget.text.toString()) }
binding.ivTrCollect.setOnClickListener { addCollect() } binding.ivTrCollect.setOnClickListener { addCollect() }
@ -81,9 +96,19 @@ class TextResultActivity : AppCompatActivity() {
} }
translating = true translating = true
val lanSourceCode = LanguagesConstants.getInstance().getLanguageCodeByLanguage(
PreferenceLanguageUtils.getString("language_source"),
MyApp.applicationContext()
)
val lanTargetCode = LanguagesConstants.getInstance().getLanguageCodeByLanguage(
PreferenceLanguageUtils.getString("language_target"),
MyApp.applicationContext()
)
val param = HashMap<String, String>().apply { val param = HashMap<String, String>().apply {
put("sourceLanguage", MyApp.getSourceLanguageCode()) put("sourceLanguage", lanSourceCode)
put("translationLanguage", MyApp.getTargetLanguageCode()) put("translationLanguage", lanTargetCode)
put("text", text) put("text", text)
} }
@ -104,9 +129,9 @@ class TextResultActivity : AppCompatActivity() {
private fun addHistory(transResult: String) { private fun addHistory(transResult: String) {
val dbTranslation = DbTranslation(this) val dbTranslation = DbTranslation(this)
val translations = Translations( val translations = Translations(
MyApp.getSourceLanguage(), PreferenceLanguageUtils.getString("language_source"),
binding.tvTrSource.text.toString(), binding.tvTrSource.text.toString(),
MyApp.getTargetLanguage(), PreferenceLanguageUtils.getString("language_target"),
transResult transResult
) )
@ -131,17 +156,29 @@ class TextResultActivity : AppCompatActivity() {
} }
} }
private fun readText(text: String) { // readText 方法,整合获取语言代码和朗读文本功能
private fun readText(text: String, targetLanguage: String, context: Context) {
val speech: String = text.trim() val speech: String = text.trim()
if (!TextUtils.isEmpty(speech)) { if (!TextUtils.isEmpty(speech)) {
if (TextToSpeech.LANG_NOT_SUPPORTED != tts.isLanguageAvailable(Locale.getDefault()) // 获取目标语言的语言代码
) { val languageCode =
tts.speak(speech, 0, null, null) LanguagesConstants.getInstance().getLanguageCodeByLanguage(targetLanguage, context)
Log.d("LanguageCode", "Language Code for $targetLanguage: $languageCode")
// 创建语言 Locale 对象
val locale = Locale(languageCode)
// 判断语言是否支持
if (TextToSpeech.LANG_NOT_SUPPORTED != tts.isLanguageAvailable(locale)) {
tts.language = locale
tts.speak(speech, TextToSpeech.QUEUE_FLUSH, null, null)
} else {
Widget.makeToast(this, getString(R.string.tr_tts_error))
} }
} }
} }
// 复制到粘贴板 // 复制到粘贴板
private fun copyToClipboard() { private fun copyToClipboard() {
val tip = "Copied to clipboard!" val tip = "Copied to clipboard!"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="20dp"/> <corners android:radius="20dp" />
<solid android:color="#FF2F2F2F"/> <solid android:color="#FF2F2F2F" />
</shape> </shape>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M2.89941 12H20.8994"
android:strokeColor="#FFFFFF"
android:strokeWidth="2"
android:strokeLineCap="round"
android:strokeLineJoin="round"/>
<path
android:pathData="M8.8994 18L2.89941 12L8.8994 6"
android:strokeColor="#FFFFFF"
android:strokeWidth="2"
android:strokeLineCap="round"
android:strokeLineJoin="round"/>
</vector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M9.99991 10.9763L14.1247 6.85156L15.3032 8.03007L9.99991 13.3334L4.69666 8.03007L5.87516 6.85156L9.99991 10.9763Z" />
</vector>

View File

@ -31,15 +31,17 @@
android:background="@drawable/button_r20_white_bg" android:background="@drawable/button_r20_white_bg"
android:drawablePadding="25dp" android:drawablePadding="25dp"
android:gravity="center" android:gravity="center"
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingEnd="12dp" android:paddingEnd="12dp"
android:text="@string/text_source_language"
android:textColor="@color/main_text_ff1f1724" android:textColor="@color/main_text_ff1f1724"
android:textSize="16sp" android:textSize="16sp"
android:textStyle="bold" android:textStyle="bold"
app:drawableEndCompat="@drawable/ic_down_choose" /> app:drawableEndCompat="@drawable/ic_down_choose" />
<ImageView <ImageView
android:id="@+id/tv_exchange"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
@ -62,7 +64,7 @@
android:gravity="center" android:gravity="center"
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingEnd="12dp" android:paddingEnd="12dp"
android:text="@string/text_target_language"
android:textColor="@color/main_text_ff1f1724" android:textColor="@color/main_text_ff1f1724"
android:textSize="16sp" android:textSize="16sp"
android:textStyle="bold" android:textStyle="bold"
@ -103,6 +105,7 @@
android:textStyle="bold" /> android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_lan_common5"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"

View File

@ -83,6 +83,7 @@
app:drawableEndCompat="@drawable/ic_down_choose" /> app:drawableEndCompat="@drawable/ic_down_choose" />
<ImageView <ImageView
android:id="@+id/iv_main_exChange"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"

View File

@ -6,12 +6,22 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:keepScreenOn="true"> android:keepScreenOn="true">
<androidx.camera.view.PreviewView
android:id="@+id/photo_preview"
android:layout_width="0dp"
android:layout_height="0dp"
android:adjustViewBounds="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/control"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView <ImageView
android:id="@+id/preview" android:id="@+id/preview"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:adjustViewBounds="true" android:adjustViewBounds="true"
app:layout_constraintBottom_toTopOf="@+id/control" app:layout_constraintBaseline_toTopOf="@id/control"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -26,20 +36,48 @@
app:layout_constraintTop_toTopOf="@id/preview" /> app:layout_constraintTop_toTopOf="@id/preview" />
<LinearLayout <LinearLayout
android:id="@id/control" android:id="@+id/control"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="#000" android:background="#000"
android:orientation="vertical" android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"> app:layout_constraintBottom_toBottomOf="parent">
<Button <LinearLayout
android:id="@+id/select_image_button" android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_marginTop="16dp"
android:layout_margin="12dp" android:orientation="horizontal">
android:text="@string/select_image" />
<ImageView
android:id="@+id/iv_still_pic"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_weight="1"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:src="@drawable/ic_still_pic" />
<ImageView
android:id="@+id/iv_still_take"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:src="@drawable/ic_still_take" />
<ImageView
android:id="@+id/iv_still_buling"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_weight="1"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:src="@drawable/ic_still_notbuli" />
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/control2" android:id="@+id/control2"
@ -49,28 +87,85 @@
android:orientation="horizontal" android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"> app:layout_constraintBottom_toBottomOf="parent">
<Spinner
android:id="@+id/size_selector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1" />
<Spinner <Spinner
android:id="@+id/feature_selector" android:id="@+id/feature_selector"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center" />
android:layout_weight="1" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<include <LinearLayout
android:id="@+id/settings_button" android:id="@+id/change_language"
layout="@layout/settings_style" android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="23dp"
android:layout_marginBottom="16dp"
android:elevation="2dp"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:layout_constraintBottom_toTopOf="@id/control"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="@+id/still_source_language"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/button_r20_black_bg"
android:drawablePadding="25dp"
android:gravity="center"
android:maxLines="1"
android:paddingStart="16dp"
android:paddingEnd="12dp"
android:text="@string/text_source_language"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
app:drawableEndCompat="@drawable/ic_down_choose_white" />
<ImageView
android:id="@+id/still_exChange"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:background="@drawable/button_r20_black_bg"
android:paddingStart="12dp"
android:paddingTop="6dp"
android:paddingEnd="12dp"
android:paddingBottom="6dp"
android:src="@drawable/ic_exchage" />
<TextView
android:id="@+id/still_target_language"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/button_r20_black_bg"
android:drawablePadding="25dp"
android:gravity="center"
android:maxLines="1"
android:paddingStart="16dp"
android:paddingEnd="12dp"
android:text="@string/text_target_language"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
app:drawableEndCompat="@drawable/ic_down_choose_white" />
</LinearLayout>
<ImageView
android:id="@+id/iv_still_back"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintRight_toRightOf="@id/root" android:padding="16dp"
android:src="@drawable/ic_back_white"
app:layout_constraintStart_toStartOf="@id/root"
app:layout_constraintTop_toTopOf="@id/root" /> app:layout_constraintTop_toTopOf="@id/root" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -153,15 +153,10 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:src="@drawable/tr_photo" /> android:src="@drawable/tr_photo" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/tr_voice" />
<View <View
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="1dp" android:layout_height="1dp"

View File

@ -12,7 +12,7 @@
/> />
<ImageView <ImageView
android:id="@+id/preview" android:id="@+id/sus_preview"
android:layout_width="150dp" android:layout_width="150dp"
android:layout_height="150dp" android:layout_height="150dp"
android:adjustViewBounds="true" /> android:adjustViewBounds="true" />

View File

@ -71,8 +71,8 @@
}, },
{ {
"language": "Chinese, Simplified", "language": "Chinese, Simplified",
"languageCode": "zh", "languageCode": "zh_CN",
"speechCode": "zh" "speechCode": "zh_CN"
}, },
{ {
"language": "Chinese, Traditional", "language": "Chinese, Traditional",

View File

@ -18,8 +18,10 @@
<color name="bg_ff2f2f2f">#FF2F2F2F</color> <color name="bg_ff2f2f2f">#FF2F2F2F</color>
<!-- change_language--> <!-- change_language-->
<color name="bg_ff605c62">#FF605C62</color> <color name="bg_ff605c62">#FF605C62</color>
<!-- his_page--> <!-- his_page-->
<color name="text_ffa5a5a5">#FFA5A5A5</color> <color name="text_ffa5a5a5">#FFA5A5A5</color>
<!-- still-page-->
<color name="bg_53514c">#FF53514C</color>
</resources> </resources>

View File

@ -25,6 +25,7 @@
<string name="tr_add_new">New Translate</string> <string name="tr_add_new">New Translate</string>
<string name="tr_common">Common</string> <string name="tr_common">Common</string>
<string name="tr_other">other</string> <string name="tr_other">other</string>
<string name="tr_tts_error">Speech in this language is temporarily not supported.</string>
<!--settings_page--> <!--settings_page-->
<string name="settings">Settings</string> <string name="settings">Settings</string>
<string name="languages">Languages</string> <string name="languages">Languages</string>

View File

@ -1,6 +1,7 @@
[versions] [versions]
agp = "8.4.1" agp = "8.4.1"
cameraCamera2 = "1.3.4" cameraCamera2 = "1.3.4"
cameraCore = "1.3.4"
converterGson = "2.9.0" converterGson = "2.9.0"
glide = "4.16.0" glide = "4.16.0"
gson = "2.10.1" gson = "2.10.1"
@ -19,9 +20,10 @@ playServicesMlkitTextRecognition = "19.0.0"
playServicesMlkitTextRecognitionChinese = "16.0.0" playServicesMlkitTextRecognitionChinese = "16.0.0"
[libraries] [libraries]
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraCamera2" } androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraCore" }
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "cameraCamera2" } androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "cameraCore" }
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "cameraCamera2" } androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "cameraCore" }
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "cameraCore" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "converterGson" } converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "converterGson" }
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" } glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }