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
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)
// CameraX Lifecycle library
implementation(libs.androidx.camera.lifecycle)
// CameraX View class
implementation(libs.androidx.camera.view)
// 文本识别
// To recognize Latin script
// implementation(libs.play.services.mlkit.text.recognition)

View File

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

View File

@ -7,58 +7,53 @@ import android.os.Build
import android.webkit.WebView
import com.assimilate.alltrans.common.Language
import com.assimilate.alltrans.common.LanguagesConstants
import com.assimilate.alltrans.common.Logger
import com.assimilate.alltrans.common.PreferenceLanguageUtils
class MyApp : Application() {
init {
instance = this
}
private var sl: Language? = null
private var tl: Language? = null
private fun setSystemLanguage() {
// 检查是否是第一次进入应用
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 {
private var instance: MyApp? = null
fun applicationContext(): Context {
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() {
super.onCreate()
initLanguage()
instance = this
setSystemLanguage()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val processName = getUniqueProcessName()
if (processName != null && processName != packageName) {
@ -81,76 +76,4 @@ class MyApp : Application() {
object Config {
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)
image.close()
bindingSubGlobal.preview.setImageBitmap(bitmap)
bindingSubGlobal.susPreview.setImageBitmap(bitmap)
tryReloadAndDetectInImage(bitmap)
}
stopSelf()

View File

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

View File

@ -22,6 +22,7 @@ import java.util.Objects;
public class LanguagesConstants {
private static boolean instanced = false;
private volatile static LanguagesConstants languagesConstant;
private final Gson gson = new Gson();
private final ArrayList<Language> languages;
@ -30,7 +31,7 @@ public class LanguagesConstants {
if (instanced) {
throw new RuntimeException("Instance multiple LanguagesConstants.");
} else {
languages = new ArrayList<Language>();
languages = new ArrayList<>();
instanced = true;
}
@ -58,6 +59,40 @@ public class LanguagesConstants {
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) {
InputStream inputStream = null;
InputStreamReader inputStreamReader = null;
@ -70,7 +105,7 @@ public class LanguagesConstants {
// step2. 读取json文件信息
final StringBuilder builder = new StringBuilder();
String line = null;
String line;
while ((line = bufferedReader.readLine()) != null) {
builder.append(line);
}
@ -78,8 +113,8 @@ public class LanguagesConstants {
// step3. 将json文本转换成Java对象
final String result = builder.toString();
if (!TextUtils.isEmpty(result)) {
Gson gson = new Gson();
Type listType = new TypeToken<List<Language>>(){}.getType();
Type listType = new TypeToken<List<Language>>() {
}.getType();
List<Language> temp = gson.fromJson(result, listType);
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) {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
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) {

View File

@ -1,23 +1,23 @@
package com.assimilate.alltrans.common
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.os.Handler
import android.os.Looper
import android.text.TextPaint
import android.util.Log
import com.assimilate.alltrans.MyApp
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 java.util.Arrays
import kotlin.math.max
import kotlin.math.min
/**
* Graphic instance for rendering TextBlock position, size, and ID within an associated graphic
* overlay view.
*/
class TextGraphic
constructor(
class TextGraphic(
overlay: GraphicOverlay?,
private val text: Text,
private val shouldGroupTextInBlocks: Boolean,
@ -28,14 +28,15 @@ constructor(
private val rectPaint: Paint = Paint()
private val textPaint: TextPaint
private val labelPaint: Paint
private val handler = Handler(Looper.getMainLooper())
init {
prepareTranslation()
rectPaint.color = MARKER_COLOR
rectPaint.style = Paint.Style.STROKE
rectPaint.strokeWidth = STROKE_WIDTH
textPaint = TextPaint()
textPaint.color = TEXT_COLOR
textPaint.textSize = TEXT_SIZE
labelPaint = Paint()
labelPaint.color = MARKER_COLOR
labelPaint.style = Paint.Style.FILL
@ -43,25 +44,70 @@ constructor(
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) {
Log.d(TAG, "Text is: " + text.text)
for (textBlock in text.textBlocks) { // Renders the text at the bottom of the box.
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))
for ((translatedIndex, textBlock) in text.textBlocks.withIndex()) {
val translatedBlockText =
if (translatedIndex < translatedTextBlocks.size) translatedTextBlocks[translatedIndex] else textBlock.text
val height1 = ((textBlock.boundingBox?.bottom?.toFloat()
?: 30f) - (textBlock.boundingBox?.top?.toFloat()
?: 0f)) / textBlock.lines.size
if (shouldGroupTextInBlocks) {
drawText(
getFormattedText(
textBlock.text,
translatedBlockText,
textBlock.recognizedLanguage,
confidence = null
),
RectF(textBlock.boundingBox),
TEXT_SIZE * textBlock.lines.size + 2 * STROKE_WIDTH,
height1 - 3 * STROKE_WIDTH,
canvas
)
} else {
@ -71,12 +117,18 @@ constructor(
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.
// Draw the bounding box around the TextBlock.
val rect = RectF(line.boundingBox)
drawText(
getFormattedText(line.text, line.recognizedLanguage, line.confidence),
getFormattedText(
translatedBlockText,
line.recognizedLanguage,
line.confidence
),
rect,
TEXT_SIZE + 2 * STROKE_WIDTH,
((line.boundingBox?.bottom?.toFloat()
?: 20f) - (line.boundingBox?.top?.toFloat()
?: 0f)) - 2 * STROKE_WIDTH,
canvas
)
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 {
@ -189,35 +168,78 @@ constructor(
else res
}
private fun drawText(text: String, rect: RectF, textHeight: Float, canvas: Canvas) {
// If the image is flipped, the left will be translated to right, and the right to left.
private fun drawText(text: String, rect: RectF, textSize: Float, canvas: Canvas) {
val x0 = translateX(rect.left)
val x1 = translateX(rect.right)
rect.left = min(x0, x1)
rect.right = max(x0, x1)
rect.top = translateY(rect.top)
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(
rect.left - STROKE_WIDTH,
rect.top - textHeight,
rect.left + textWidth + 2 * STROKE_WIDTH,
rect.top,
rect.top - STROKE_WIDTH,
rect.right + STROKE_WIDTH,
rect.bottom + STROKE_WIDTH,
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 {
private const val DELIMITER = "`0_.._0`"
private const val TAG = "TextGraphic"
private const val TEXT_WITH_LANGUAGE_TAG_FORMAT = "%s:%s"
private const val TEXT_COLOR = Color.BLACK
private const val MARKER_COLOR = Color.GREEN
private const val TEXT_SIZE = 54.0f
private const val STROKE_WIDTH = 4.0f
private val TEXT_COLOR = Color.parseColor("#FF474747")
private val MARKER_COLOR = Color.parseColor("#FFD9D9D9")
private const val STROKE_WIDTH = 2.0f
}
}

View File

@ -2,8 +2,12 @@
package com.assimilate.alltrans.common
import android.content.Context
import android.text.TextUtils
import android.util.Log
import com.assimilate.alltrans.MyApp
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.mlkit.vision.common.InputImage
import com.google.mlkit.vision.text.Text
@ -31,6 +35,9 @@ class TextRecognitionProcessor(
return textRecognizer.process(image)
}
override fun onSuccess(text: Text, graphicOverlay: GraphicOverlay) {
Log.d(TAG, "On-device Text detection successful")
logExtrasForTesting(text)

View File

@ -1,10 +1,16 @@
package com.assimilate.alltrans.common;
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 androidx.annotation.NonNull;
import com.google.android.material.snackbar.Snackbar;
public class Widget {
private static volatile Toast toast;
@ -15,4 +21,18 @@ public class Widget {
toast = Toast.makeText(mActivity, msg, Toast.LENGTH_SHORT);
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.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.google.common.base.Preconditions;
@ -92,12 +93,16 @@ public class GraphicOverlay extends View {
canvas.drawText(text, x, y, paint);
}
/** Adjusts the supplied value from the image scale to the view scale. */
/**
* 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. */
/**
* Returns the application context of the app.
*/
public Context getApplicationContext() {
return overlay.getContext().getApplicationContext();
}
@ -203,7 +208,9 @@ public class GraphicOverlay extends View {
needUpdateTransformation = true);
}
/** Removes all graphics from the overlay. */
/**
* Removes all graphics from the overlay.
*/
public void clear() {
synchronized (lock) {
graphics.clear();
@ -211,14 +218,18 @@ public class GraphicOverlay extends View {
postInvalidate();
}
/** Adds a graphic to the overlay. */
/**
* Adds a graphic to the overlay.
*/
public void add(Graphic graphic) {
synchronized (lock) {
graphics.add(graphic);
}
}
/** Removes a graphic from the overlay. */
/**
* Removes a graphic from the overlay.
*/
public void remove(Graphic graphic) {
synchronized (lock) {
graphics.remove(graphic);
@ -284,7 +295,9 @@ public class GraphicOverlay extends View {
needUpdateTransformation = false;
}
/** Draws the overlay with its associated graphic objects. */
/**
* Draws the overlay with its associated graphic objects.
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
@ -298,5 +311,21 @@ public class GraphicOverlay extends View {
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
setVisibility(View.INVISIBLE);
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
setVisibility(View.VISIBLE);
return true;
}
return super.onTouchEvent(event);
}
}

View File

@ -7,64 +7,102 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.recyclerview.widget.LinearLayoutManager
import com.assimilate.alltrans.MyApp
import com.assimilate.alltrans.R
import com.assimilate.alltrans.adapters.LanguageAdapter
import com.assimilate.alltrans.common.Language
import com.assimilate.alltrans.common.LanguagesConstants
import com.assimilate.alltrans.common.PreferenceLanguageUtils
import com.assimilate.alltrans.databinding.ActivityLanguageChangeBinding
class LanguageChangeActivity : AppCompatActivity() {
private lateinit var binding: ActivityLanguageChangeBinding
private var lastTranslateLanguage = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLanguageChangeBinding.inflate(layoutInflater)
enableEdgeToEdge()
setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
initView()
initList()
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() {
binding.tvChangeSource.setOnClickListener {
lastTranslateLanguage = false
}
binding.tvChangeTarget.setOnClickListener {
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() {
val languages: ArrayList<Language> = LanguagesConstants.getInstance().getList(
this
)
val languages: ArrayList<Language> = LanguagesConstants.getInstance().getList(this)
if (languages.isNotEmpty()) {
val adapter =
LanguageAdapter(
this, languages
) { _, language ->
Log.d("fsdafsdfd", language.language)
val adapter = LanguageAdapter(this, languages) { _, language ->
Log.d("LanguageChange", language.language)
if (lastTranslateLanguage) {
MyApp.setTargetLanguage(language)
PreferenceLanguageUtils.putString("language_target", language.language)
onBackPressed()
} else {
MyApp.setSourceLanguage(language)
PreferenceLanguageUtils.putString("language_source", language.language)
}
binding.tvChangeSource.text = MyApp.getSourceLanguage()
binding.tvChangeTarget.text = MyApp.getTargetLanguage()
PreferenceLanguageUtils.addRecentLanguage(language)
binding.tvChangeSource.text = PreferenceLanguageUtils.getString("language_source")
binding.tvChangeTarget.text = PreferenceLanguageUtils.getString("language_target")
updateRecentLanguages()
}
val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
binding.listLanguages.setLayoutManager(layoutManager)
binding.listLanguages.setAdapter(adapter)
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.TextUtils
import android.text.TextWatcher
import android.util.Log
import android.view.View
import android.widget.EditText
import androidx.activity.enableEdgeToEdge
@ -25,6 +26,8 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.assimilate.alltrans.MyApp
import com.assimilate.alltrans.R
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.databinding.ActivityMainBinding
@ -101,8 +104,8 @@ class MainActivity : AppCompatActivity() {
private fun initView() {
binding.chSourceLanguage.text = MyApp.getSourceLanguage()
binding.chTargetLanguage.text = MyApp.getTargetLanguage()
binding.chSourceLanguage.text = PreferenceLanguageUtils.getString("language_source")
binding.chTargetLanguage.text = PreferenceLanguageUtils.getString("language_target")
}
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() {
@ -200,6 +222,7 @@ class MainActivity : AppCompatActivity() {
binding.etText.text = null
}
// 语音转文本
private fun voiceToText() {
val speechIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
@ -212,18 +235,22 @@ class MainActivity : AppCompatActivity() {
5000
) // 设置5秒的可能完全静默时间
speechIntent.putExtra(
"android.speech.extra.LANGUAGE_MODEL",
MyApp.getSourceLanguage()
)
// speechIntent.putExtra(
// "android.speech.extra.LANGUAGE_MODEL",
// MyApp.getSourceLanguage()
// )
val languageCode = LanguagesConstants.getInstance()
.getLanguageCodeByLanguage(PreferenceLanguageUtils.getString("language_source"), this)
speechIntent.putExtra(
"android.speech.extra.LANGUAGE",
MyApp.getSourceLanguageCode()
)
speechIntent.putExtra(
"android.speech.extra.LANGUAGE_PREFERENCE",
MyApp.getSourceLanguage()
languageCode
)
// speechIntent.putExtra(
// "android.speech.extra.LANGUAGE_PREFERENCE",
//
// )
try {
launcher?.launch(speechIntent)
} catch (ea: ActivityNotFoundException) {
@ -250,7 +277,6 @@ class MainActivity : AppCompatActivity() {
}
}
override fun onResume() {
super.onResume()
initView()
@ -264,5 +290,4 @@ class MainActivity : AppCompatActivity() {
}
}
}

View File

@ -13,34 +13,43 @@ import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import android.util.Pair
import android.view.MenuItem
import android.view.View
import android.view.ViewTreeObserver
import android.widget.AdapterView
import android.widget.AdapterView.OnItemSelectedListener
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.PopupMenu
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
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.content.ContextCompat
import com.assimilate.alltrans.R
import com.assimilate.alltrans.common.BitmapUtils
import com.assimilate.alltrans.common.TextRecognitionProcessor
import com.assimilate.alltrans.common.VisionImageProcessor
import com.assimilate.alltrans.common.Widget
import com.assimilate.alltrans.curview.GraphicOverlay
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.devanagari.DevanagariTextRecognizerOptions
import com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions
import com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions
import com.google.mlkit.vision.text.latin.TextRecognizerOptions
import java.io.File
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
class StillImageActivity : AppCompatActivity() {
private var preview: ImageView? = null
@ -49,17 +58,16 @@ class StillImageActivity : AppCompatActivity() {
private var selectedSize: String? = SIZE_SCREEN
private var isLandScape = false
private var imageUri: Uri? = null
// Max width (portrait mode)
private var imageMaxWidth = 0
// Max height (portrait mode)
private var imageMaxHeight = 0
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
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_still_image)
@ -74,31 +82,13 @@ class StillImageActivity : AppCompatActivity() {
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)
graphicOverlay = findViewById(R.id.graphic_overlay)
populateFeatureSelector()
populateSizeSelector()
isLandScape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
if (savedInstanceState != null) {
imageUri = savedInstanceState.getParcelable(KEY_IMAGE_URI)
@ -121,12 +111,107 @@ class StillImageActivity : AppCompatActivity() {
}
)
val settingsButton = findViewById<ImageView>(R.id.settings_button)
settingsButton.setOnClickListener {
// val intent = Intent(applicationContext, SettingsActivity::class.java)
// intent.putExtra(SettingsActivity.EXTRA_LAUNCH_SOURCE, LaunchSource.STILL_IMAGE)
// startActivity(intent)
// 初始化相机
startCamera()
outputDirectory = getOutputDirectory()
initClick()
}
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() {
super.onDestroy()
imageProcessor?.run { this.stop() }
// 释放相机资源
if (::cameraProviderFuture.isInitialized) {
cameraProviderFuture.get().unbindAll()
}
}
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) {
super.onSaveInstanceState(outState)
outState.putParcelable(KEY_IMAGE_URI, imageUri)
@ -247,6 +306,7 @@ class StillImageActivity : AppCompatActivity() {
}
private fun startCameraIntentForResult() {
// Ensure permission is still granted before starting camera intent
if (ContextCompat.checkSelfPermission(
this,
@ -299,12 +359,10 @@ class StillImageActivity : AppCompatActivity() {
if (imageUri == null) {
return
}
if (SIZE_SCREEN == selectedSize && imageMaxWidth == 0) {
// UI layout has not finished yet, will reload once it's ready.
return
}
val imageBitmap =
BitmapUtils.getBitmapFromContentUri(contentResolver, imageUri) ?: return
// 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 REQUEST_IMAGE_CAPTURE = 1001
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.speech.tts.TextToSpeech
import android.text.TextUtils
import android.util.Log
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.assimilate.alltrans.MyApp
import com.assimilate.alltrans.R
import com.assimilate.alltrans.common.LanguagesConstants
import com.assimilate.alltrans.common.Logger
import com.assimilate.alltrans.common.PreferenceLanguageUtils
import com.assimilate.alltrans.common.Widget
import com.assimilate.alltrans.databinding.ActivityTextResultBinding
import com.assimilate.alltrans.http.GoogleTranslator
@ -67,8 +70,20 @@ class TextResultActivity : AppCompatActivity() {
}
binding.ivTrCopy.setOnClickListener { copyToClipboard() }
binding.ivSourceClear.setOnClickListener { onBackPressed() }
binding.ivSourceTts.setOnClickListener { readText(binding.tvTrSource.text.toString()) }
binding.ivTargetTts.setOnClickListener { readText(binding.tvTrTarget.text.toString()) }
binding.ivSourceTts.setOnClickListener {
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.ivTrCollect.setOnClickListener { addCollect() }
@ -81,9 +96,19 @@ class TextResultActivity : AppCompatActivity() {
}
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 {
put("sourceLanguage", MyApp.getSourceLanguageCode())
put("translationLanguage", MyApp.getTargetLanguageCode())
put("sourceLanguage", lanSourceCode)
put("translationLanguage", lanTargetCode)
put("text", text)
}
@ -104,9 +129,9 @@ class TextResultActivity : AppCompatActivity() {
private fun addHistory(transResult: String) {
val dbTranslation = DbTranslation(this)
val translations = Translations(
MyApp.getSourceLanguage(),
PreferenceLanguageUtils.getString("language_source"),
binding.tvTrSource.text.toString(),
MyApp.getTargetLanguage(),
PreferenceLanguageUtils.getString("language_target"),
transResult
)
@ -131,16 +156,28 @@ class TextResultActivity : AppCompatActivity() {
}
}
private fun readText(text: String) {
// readText 方法,整合获取语言代码和朗读文本功能
private fun readText(text: String, targetLanguage: String, context: Context) {
val speech: String = text.trim()
if (!TextUtils.isEmpty(speech)) {
if (TextToSpeech.LANG_NOT_SUPPORTED != tts.isLanguageAvailable(Locale.getDefault())
) {
tts.speak(speech, 0, null, null)
// 获取目标语言的语言代码
val languageCode =
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() {

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

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

View File

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

View File

@ -6,12 +6,22 @@
android:layout_height="match_parent"
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
android:id="@+id/preview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
app:layout_constraintBottom_toTopOf="@+id/control"
app:layout_constraintBaseline_toTopOf="@id/control"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@ -26,20 +36,48 @@
app:layout_constraintTop_toTopOf="@id/preview" />
<LinearLayout
android:id="@id/control"
android:id="@+id/control"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#000"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent">
<Button
android:id="@+id/select_image_button"
android:layout_width="wrap_content"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="horizontal">
<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_margin="12dp"
android:text="@string/select_image" />
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
android:id="@+id/control2"
@ -49,28 +87,85 @@
android:orientation="horizontal"
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
android:id="@+id/feature_selector"
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1" />
android:layout_gravity="center" />
</LinearLayout>
</LinearLayout>
<include
android:id="@+id/settings_button"
layout="@layout/settings_style"
<LinearLayout
android:id="@+id/change_language"
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"
app:layout_constraintRight_toRightOf="@id/root"
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_height="wrap_content"
android:padding="16dp"
android:src="@drawable/ic_back_white"
app:layout_constraintStart_toStartOf="@id/root"
app:layout_constraintTop_toTopOf="@id/root" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -153,15 +153,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
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
android:layout_width="0dp"
android:layout_height="1dp"

View File

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

View File

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

View File

@ -20,6 +20,8 @@
<color name="bg_ff605c62">#FF605C62</color>
<!-- his_page-->
<color name="text_ffa5a5a5">#FFA5A5A5</color>
<!-- still-page-->
<color name="bg_53514c">#FF53514C</color>
</resources>

View File

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

View File

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