update history+photo trans

This commit is contained in:
Simon 2024-07-19 18:39:46 +08:00
parent 67c17356d9
commit c4c3998f65
25 changed files with 1102 additions and 917 deletions

View File

@ -4,6 +4,14 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2024-07-19T10:26:54.102874Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=bd95a93d" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState> </SelectionState>
</selectionStates> </selectionStates>
</component> </component>

View File

@ -8,7 +8,6 @@
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@ -18,6 +17,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<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:name=".MyApp"
android:allowBackup="true" android:allowBackup="true"
@ -27,6 +27,7 @@
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:largeHeap="true"
android:theme="@style/Theme.Alltrans" android:theme="@style/Theme.Alltrans"
tools:targetApi="31"> tools:targetApi="31">
<service <service

View File

@ -9,18 +9,16 @@ 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.Logger
import com.assimilate.alltrans.common.PreferenceLanguageUtils import com.assimilate.alltrans.common.PreferenceLanguageUtils
import com.assimilate.alltrans.common.ScreenUtils
class MyApp : Application() { class MyApp : Application() {
init { init {
instance = this instance = this
} }
private fun setSystemLanguage() { private fun setSystemLanguage() {
// 检查是否是第一次进入应用 // 检查是否是第一次进入应用
if (PreferenceLanguageUtils.isFirstTime()) { if (PreferenceLanguageUtils.isFirstTime()) {
// 第一次进入应用的逻辑 // 第一次进入应用的逻辑
@ -52,6 +50,7 @@ class MyApp : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
instance = this instance = this
// ScreenUtils.init(this)
setSystemLanguage() setSystemLanguage()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {

View File

@ -5,6 +5,8 @@ import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView; import android.widget.ImageView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -17,24 +19,21 @@ import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.RequestOptions;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
public class LanguageAdapter extends RecyclerView.Adapter<LanguageAdapter.LanguageHolder> { public class LanguageAdapter extends RecyclerView.Adapter<LanguageAdapter.LanguageHolder> implements Filterable {
private final Activity mActivity; private final Activity mActivity;
private final ArrayList<Language> languages; private final ArrayList<Language> languages;
private final ArrayList<Language> filteredLanguages;
private final OnClickListener listener; private final OnClickListener listener;
private final RequestOptions options; private final RequestOptions options;
private boolean sorting = false;
public LanguageAdapter(@NonNull final Activity activity, @NonNull final ArrayList<Language> languageList, final OnClickListener onClickListener) { public LanguageAdapter(@NonNull final Activity activity, @NonNull final ArrayList<Language> languageList, final OnClickListener onClickListener) {
this.mActivity = activity; this.mActivity = activity;
this.languages = new ArrayList<Language>(); this.languages = new ArrayList<>(languageList);
this.filteredLanguages = new ArrayList<>(languageList);
this.listener = onClickListener; this.listener = onClickListener;
if (!languageList.isEmpty()) {
languages.addAll(languageList);
}
options = new RequestOptions() options = new RequestOptions()
.placeholder(R.mipmap.ic_launcher) .placeholder(R.mipmap.ic_launcher)
.error(R.mipmap.ic_launcher) .error(R.mipmap.ic_launcher)
@ -49,9 +48,9 @@ public class LanguageAdapter extends RecyclerView.Adapter<LanguageAdapter.Langua
@Override @Override
public void onBindViewHolder(@NonNull LanguageHolder holder, int position) { public void onBindViewHolder(@NonNull LanguageHolder holder, int position) {
final Language currentLanguage = languages.get(position); final Language currentLanguage = filteredLanguages.get(position);
if (null != currentLanguage) { if (currentLanguage != null) {
drawImage(currentLanguage.mFlagUrl, holder.mBinding.languageFlag); drawImage(currentLanguage.mFlagUrl, holder.mBinding.languageFlag);
if (!TextUtils.isEmpty(currentLanguage.getLanguage())) { if (!TextUtils.isEmpty(currentLanguage.getLanguage())) {
holder.mBinding.language.setText(currentLanguage.getLanguage()); holder.mBinding.language.setText(currentLanguage.getLanguage());
@ -60,19 +59,16 @@ public class LanguageAdapter extends RecyclerView.Adapter<LanguageAdapter.Langua
} }
} }
holder.mBinding.getRoot().setOnClickListener(new View.OnClickListener() { holder.mBinding.getRoot().setOnClickListener(view -> {
@Override final int pos = holder.getAdapterPosition();
public void onClick(View view) { if (listener != null)
final int position = holder.getAdapterPosition(); listener.click(pos, currentLanguage);
if (null != listener)
listener.click(position, currentLanguage);
}
}); });
} }
@Override @Override
public int getItemCount() { public int getItemCount() {
return languages.size(); return filteredLanguages.size();
} }
private void drawImage(@NonNull final String url, @NonNull final ImageView view) { private void drawImage(@NonNull final String url, @NonNull final ImageView view) {
@ -90,6 +86,37 @@ public class LanguageAdapter extends RecyclerView.Adapter<LanguageAdapter.Langua
} }
} }
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
List<Language> filteredList = new ArrayList<>();
if (TextUtils.isEmpty(constraint)) {
filteredList.addAll(languages);
} else {
String filterPattern = constraint.toString().toLowerCase().trim();
for (Language language : languages) {
if (language.getLanguage().toLowerCase().contains(filterPattern)) {
filteredList.add(language);
}
}
}
FilterResults results = new FilterResults();
results.values = filteredList;
return results;
}
@Override
@SuppressWarnings("unchecked")
protected void publishResults(CharSequence constraint, FilterResults results) {
filteredLanguages.clear();
filteredLanguages.addAll((List<Language>) results.values);
notifyDataSetChanged();
}
};
}
public static class LanguageHolder extends RecyclerView.ViewHolder { public static class LanguageHolder extends RecyclerView.ViewHolder {
public LanguageItemLayoutBinding mBinding; public LanguageItemLayoutBinding mBinding;

View File

@ -1,34 +1,47 @@
package com.assimilate.alltrans.allservice package com.assimilate.alltrans.allservice
import android.app.* import android.app.Activity
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.PixelFormat import android.graphics.PixelFormat
import android.hardware.display.DisplayManager import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay import android.hardware.display.VirtualDisplay
import android.media.Image
import android.media.ImageReader import android.media.ImageReader
import android.media.projection.MediaProjection import android.media.projection.MediaProjection
import android.media.projection.MediaProjectionManager import android.media.projection.MediaProjectionManager
import android.os.* import android.os.Build
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.util.Log import android.util.Log
import android.view.* import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import com.assimilate.alltrans.R import com.assimilate.alltrans.R
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.databinding.SusControlViewBinding
import com.assimilate.alltrans.databinding.LayoutSusGlobalBinding import com.assimilate.alltrans.databinding.LayoutSusGlobalBinding
import com.assimilate.alltrans.databinding.SusControlViewBinding
import com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions import com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions
import java.io.IOException import java.io.IOException
class SusService : Service() { class SusService : Service() {
private var imageProcessor: VisionImageProcessor? = null private var imageProcessor: VisionImageProcessor? = null
private lateinit var mediaProjectionManager: MediaProjectionManager private lateinit var mediaProjectionManager: MediaProjectionManager
private lateinit var mediaProjection: MediaProjection private var mediaProjection: MediaProjection? = null
private lateinit var virtualDisplay: VirtualDisplay private var virtualDisplay: VirtualDisplay? = null
private lateinit var displayMetrics: DisplayMetrics private lateinit var displayMetrics: DisplayMetrics
private lateinit var imageReader: ImageReader
private lateinit var windowManager: WindowManager private lateinit var windowManager: WindowManager
private lateinit var floatingView: View private lateinit var floatingView: View
@ -37,34 +50,79 @@ class SusService : Service() {
private lateinit var bindingSusControl: SusControlViewBinding private lateinit var bindingSusControl: SusControlViewBinding
private lateinit var bindingSubGlobal: LayoutSusGlobalBinding private lateinit var bindingSubGlobal: LayoutSusGlobalBinding
private var mResultCode = 0
private var mResultData: Intent? = null
private val mpResultCode = "mpResultCode"
private val mpData = "mpData"
private val ball_time = 5000L
private val handler = Handler(Looper.getMainLooper())
private val hideRunnable = Runnable {
// floatingView.visibility = View.GONE
// val layoutParams = floatingView.layoutParams as WindowManager.LayoutParams
// val iconLayoutParams = WindowManager.LayoutParams(
// 100, 100, // Set the size of the icon as needed
// layoutParams.type,
// WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
// PixelFormat.TRANSLUCENT
// )
// iconLayoutParams.gravity = layoutParams.gravity
// iconLayoutParams.x = layoutParams.x
// iconLayoutParams.y = layoutParams.y
//
// val iconView = ImageView(this).apply {
// setImageResource(R.drawable.ic_like_yes) // Set your icon drawable here
// setOnClickListener {
// floatingView.visibility = View.VISIBLE
// windowManager.removeView(this)
// windowManager.addView(floatingView, layoutParams)
// startHideTimer()
// }
// }
// windowManager.addView(iconView, iconLayoutParams)
}
override fun onBind(intent: Intent?): IBinder? { override fun onBind(intent: Intent?): IBinder? {
return null return null
} }
override fun onCreate() {
super.onCreate()
mediaProjectionManager =
getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
displayMetrics = DisplayMetrics()
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
windowManager.defaultDisplay?.getMetrics(displayMetrics)
initSusView()
startForeground(1, createNotification())
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val resultCode = intent?.getIntExtra("resultCode", Activity.RESULT_OK) if (intent == null) {
val data: Intent? = intent?.getParcelableExtra("data") return START_NOT_STICKY
if (resultCode != null && data != null) {
startScreenshot(resultCode, data)
} }
val resultCode = intent.getIntExtra(mpResultCode, Activity.RESULT_CANCELED)
val data = intent.getParcelableExtra<Intent>(mpData)
if (resultCode == Activity.RESULT_OK && data != null) {
mResultCode = resultCode
mResultData = data
startProjection()
} else {
Log.e("SusService", "Intent or data is null")
}
return START_NOT_STICKY return START_NOT_STICKY
} }
private fun initSusView() { override fun onCreate() {
super.onCreate()
displayMetrics = DisplayMetrics()
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
initControlView()
imageProcessor = TextRecognitionProcessor(
this,
ChineseTextRecognizerOptions.Builder().build()
)
startForeground(1, createNotification())
}
private fun initControlView() {
bindingSusControl = SusControlViewBinding.inflate(LayoutInflater.from(this)) bindingSusControl = SusControlViewBinding.inflate(LayoutInflater.from(this))
floatingView = bindingSusControl.root floatingView = bindingSusControl.root
@ -85,31 +143,7 @@ class SusService : Service() {
windowManager.addView(floatingView, layoutParams) windowManager.addView(floatingView, layoutParams)
// 设置点击事件 initControlClick()
bindingSusControl.tvSusGlobal.setOnClickListener {
// 处理全局翻译点击事件
addGlobalView()
}
bindingSusControl.tvSusCopy.setOnClickListener {
// 处理复制文本点击事件
}
bindingSusControl.tvSusPhoto.setOnClickListener {
// 处理照片翻译点击事件
}
bindingSusControl.tvSusDistrict.setOnClickListener {
// 处理地区翻译点击事件
}
bindingSusControl.ivSusHome.setOnClickListener {
// 处理返回主页点击事件
}
bindingSusControl.ivSusMove.setOnClickListener {
// 处理移动窗口点击事件
}
bindingSusControl.ivSusMove.setOnTouchListener(object : View.OnTouchListener { bindingSusControl.ivSusMove.setOnTouchListener(object : View.OnTouchListener {
private var startX = 0f private var startX = 0f
@ -122,9 +156,9 @@ class SusService : Service() {
MotionEvent.ACTION_DOWN -> { MotionEvent.ACTION_DOWN -> {
startX = layoutParams.x.toFloat() startX = layoutParams.x.toFloat()
startY = layoutParams.y.toFloat() startY = layoutParams.y.toFloat()
touchX = event.rawX touchX = event.rawX
touchY = event.rawY touchY = event.rawY
handler.removeCallbacks(hideRunnable)
return true return true
} }
@ -135,26 +169,78 @@ class SusService : Service() {
return true return true
} }
MotionEvent.ACTION_UP -> return true MotionEvent.ACTION_UP -> {
val screenWidth = resources.displayMetrics.widthPixels
if (layoutParams.x + floatingView.width / 2 <= screenWidth / 2) {
layoutParams.x = 0
} else {
layoutParams.x = screenWidth - floatingView.width
}
v.performClick() // 手动触发点击事件
windowManager.updateViewLayout(floatingView, layoutParams)
startHideTimer()
return true
}
} }
return false return false
} }
}) })
startHideTimer() // Start the timer when the view is initialized
}
private fun startHideTimer() {
handler.removeCallbacks(hideRunnable)
handler.postDelayed(hideRunnable, ball_time)
}
private fun initControlClick() {
bindingSusControl.ivSusMove.setOnClickListener {
bindingSusControl.ivSusMove.setImageResource(R.drawable.ic_little_ball)
bindingSusControl.susLl1.visibility = View.GONE
bindingSusControl.susLl2.visibility = View.GONE
bindingSusControl.ivSusHome.visibility = View.GONE
}
bindingSusControl.tvSusGlobal.setOnClickListener {
bindingSusControl.susControlRoot.visibility = View.INVISIBLE
Handler(Looper.getMainLooper()).postDelayed({
addGlobalView()
captureScreenshot()
}, 555)
}
bindingSusControl.tvSusCopy.setOnClickListener {
Log.d("SusService", "Copy text clicked")
}
bindingSusControl.tvSusPhoto.setOnClickListener {
Log.d("SusService", "Photo translation clicked")
}
bindingSusControl.tvSusDistrict.setOnClickListener {
Log.d("SusService", "District translation clicked")
}
bindingSusControl.ivSusHome.setOnClickListener {
Log.d("SusService", "Home clicked")
}
} }
private fun addGlobalView() { private fun addGlobalView() {
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
bindingSubGlobal = LayoutSusGlobalBinding.inflate(LayoutInflater.from(this)) bindingSubGlobal = LayoutSusGlobalBinding.inflate(LayoutInflater.from(this))
globalView = bindingSubGlobal.root globalView = bindingSubGlobal.root
imageProcessor = TextRecognitionProcessor(
this,
ChineseTextRecognizerOptions.Builder().build()
)
val layoutParams = WindowManager.LayoutParams( val layoutParams = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.MATCH_PARENT,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else { } else {
@ -165,22 +251,113 @@ class SusService : Service() {
) )
layoutParams.gravity = Gravity.TOP or Gravity.LEFT layoutParams.gravity = Gravity.TOP or Gravity.LEFT
layoutParams.x = 0 layoutParams.x = 0
layoutParams.y = 100 layoutParams.y = 0
windowManager.addView(globalView, layoutParams) windowManager.addView(globalView, layoutParams)
initGlobalClick()
} }
override fun onDestroy() { private fun initGlobalClick() {
super.onDestroy() bindingSubGlobal.susGlobalClose.setOnClickListener {
if (floatingView.isAttachedToWindow) {
windowManager.removeView(floatingView)
}
if (::globalView.isInitialized && globalView.isAttachedToWindow) {
windowManager.removeView(globalView) windowManager.removeView(globalView)
bindingSusControl.susControlRoot.visibility = View.VISIBLE
} }
}
private fun startProjection() {
if (mResultData == null) {
Log.e("SusService", "mResultData is null, cannot start projection")
return
}
if (mediaProjection == null) {
mediaProjectionManager =
getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
mediaProjection =
mediaProjectionManager.getMediaProjection(mResultCode, mResultData!!)
mediaProjection?.registerCallback(object : MediaProjection.Callback() {}, null)
Handler(Looper.getMainLooper()).postDelayed({
try {
val displayMetrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(displayMetrics)
val density = displayMetrics.densityDpi
val width = displayMetrics.widthPixels
val height = displayMetrics.heightPixels
imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 2)
virtualDisplay = mediaProjection?.createVirtualDisplay(
"ScreenCapture",
width,
height,
density,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
imageReader.surface,
null,
null
)
} catch (e: Exception) {
Log.e("SusService", "Error starting projection", e)
}
}, 1234)
}
}
private fun stopProjection() {
// Release the virtual display
try {
virtualDisplay?.release()
virtualDisplay = null
} catch (e: Exception) {
Log.e("SusService", "Error releasing virtual display", e)
}
// Stop the media projection
try {
mediaProjection?.stop()
mediaProjection = null
} catch (e: Exception) {
Log.e("SusService", "Error stopping media projection", e)
}
}
private fun captureScreenshot() {
val image: Image? = imageReader.acquireLatestImage()
image?.let {
val bitmap = imageToBitmap(it)
image.close()
bindingSubGlobal.susPreview.setImageBitmap(bitmap)
tryReloadAndDetectInImage(bitmap)
}
}
private fun imageToBitmap(image: Image): Bitmap {
val planes = image.planes
val buffer = planes[0].buffer
val pixelStride = planes[0].pixelStride
val rowStride = planes[0].rowStride
val rowPadding = rowStride - pixelStride * image.width
val bitmap = Bitmap.createBitmap(
image.width + rowPadding / pixelStride,
image.height,
Bitmap.Config.ARGB_8888
)
bitmap.copyPixelsFromBuffer(buffer)
return Bitmap.createBitmap(bitmap, 0, 0, image.width, image.height)
} }
private fun createNotification(): Notification { private fun createNotification(): Notification {
Log.d("SusService", "Creating notification")
val notificationChannelId = "FOREGROUND_SERVICE_CHANNEL" val notificationChannelId = "FOREGROUND_SERVICE_CHANNEL"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -206,7 +383,6 @@ class SusService : Service() {
private fun tryReloadAndDetectInImage(bitmap: Bitmap) { private fun tryReloadAndDetectInImage(bitmap: Bitmap) {
try { try {
// Clear the overlay first
bindingSubGlobal.susGraphicOverlay.clear() bindingSubGlobal.susGraphicOverlay.clear()
if (imageProcessor != null) { if (imageProcessor != null) {
@ -223,53 +399,20 @@ class SusService : Service() {
) )
} }
} catch (e: IOException) { } catch (e: IOException) {
Log.e("SusService", "Error retrieving saved image") Log.e("SusService", "Error retrieving saved image", e)
} }
} }
fun startScreenshot(resultCode: Int, data: Intent?) { override fun onDestroy() {
mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data!!) super.onDestroy()
val imageReader = ImageReader.newInstance( Log.d("SusService", "Service onDestroy")
displayMetrics.widthPixels, if (floatingView.isAttachedToWindow) {
displayMetrics.heightPixels, windowManager.removeView(floatingView)
PixelFormat.RGBA_8888, }
2 if (::globalView.isInitialized && globalView.isAttachedToWindow) {
) windowManager.removeView(globalView)
}
virtualDisplay = mediaProjection.createVirtualDisplay( stopProjection()
"Screenshot",
displayMetrics.widthPixels,
displayMetrics.heightPixels,
displayMetrics.densityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
imageReader.surface,
null,
null
)
// 初始化 globalView
addGlobalView()
Handler(Looper.getMainLooper()).postDelayed({
val image = imageReader.acquireLatestImage()
if (image != null) {
val planes = image.planes
val buffer = planes[0].buffer
val pixelStride = planes[0].pixelStride
val rowStride = planes[0].rowStride
val rowPadding = rowStride - pixelStride * displayMetrics.widthPixels
val bitmap = Bitmap.createBitmap(
displayMetrics.widthPixels + rowPadding / pixelStride,
displayMetrics.heightPixels,
Bitmap.Config.ARGB_8888
)
bitmap.copyPixelsFromBuffer(buffer)
image.close()
bindingSubGlobal.susPreview.setImageBitmap(bitmap)
tryReloadAndDetectInImage(bitmap)
}
stopSelf()
}, 1000)
} }
} }

View File

@ -62,6 +62,11 @@ public class LanguagesConstants {
// 根据语言代码获取 Language 对象 // 根据语言代码获取 Language 对象
public Language getLanguageByLanguageCode(@NonNull String languageCode, @NonNull Context context) { public Language getLanguageByLanguageCode(@NonNull String languageCode, @NonNull Context context) {
ArrayList<Language> languages = getList(context); ArrayList<Language> languages = getList(context);
if (languageCode.equals("zh")) {
languageCode = "zh_CN";
}
for (Language lang : languages) { for (Language lang : languages) {
if (lang.getLanguageCode().equalsIgnoreCase(languageCode)) { if (lang.getLanguageCode().equalsIgnoreCase(languageCode)) {
return lang; return lang;

View File

@ -4,8 +4,11 @@ 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.graphics.Typeface
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.text.Layout
import android.text.StaticLayout
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.MyApp
@ -13,7 +16,7 @@ import com.assimilate.alltrans.curview.GraphicOverlay
import com.assimilate.alltrans.http.GoogleTranslator import com.assimilate.alltrans.http.GoogleTranslator
import com.assimilate.alltrans.http.Translator 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.concurrent.Executors
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@ -27,8 +30,10 @@ class TextGraphic(
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()) private val handler = Handler(Looper.getMainLooper())
private val executor = Executors.newSingleThreadExecutor()
init { init {
prepareTranslation() prepareTranslation()
@ -37,6 +42,9 @@ class TextGraphic(
rectPaint.strokeWidth = STROKE_WIDTH rectPaint.strokeWidth = STROKE_WIDTH
textPaint = TextPaint() textPaint = TextPaint()
textPaint.color = TEXT_COLOR textPaint.color = TEXT_COLOR
textPaint.textSize = TEXT_SIZE
textPaint.typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
textPaint.isFakeBoldText = true
labelPaint = Paint() labelPaint = Paint()
labelPaint.color = MARKER_COLOR labelPaint.color = MARKER_COLOR
labelPaint.style = Paint.Style.FILL labelPaint.style = Paint.Style.FILL
@ -48,7 +56,10 @@ class TextGraphic(
// Method to prepare translation before drawing // Method to prepare translation before drawing
private fun prepareTranslation() { private fun prepareTranslation() {
Thread {
executor.execute {
val textToTranslate = StringBuilder() val textToTranslate = StringBuilder()
// Collect all text to be translated and append delimiter // Collect all text to be translated and append delimiter
@ -64,13 +75,22 @@ class TextGraphic(
PreferenceLanguageUtils.getString("language_target"), PreferenceLanguageUtils.getString("language_target"),
MyApp.applicationContext() MyApp.applicationContext()
) )
if (lanTargetCode == null || lanSourceCode == null || textToTranslate.toString()
.isEmpty()
) {
return@execute
}
// Define translation parameters // Define translation parameters
val param = HashMap<String, String>().apply { val param = HashMap<String, String>().apply {
put("sourceLanguage", lanSourceCode) put("sourceLanguage", lanSourceCode)
put("translationLanguage", lanTargetCode) put("translationLanguage", lanTargetCode)
put("text", textToTranslate.toString()) put("text", textToTranslate.toString())
} }
Log.d("fdasfas_sou", lanSourceCode)
Log.d("fdasfas_tar", lanTargetCode)
Log.d("fdasfas_tex", textToTranslate.toString())
val translator: Translator<GoogleTranslator.GoogleTranslateCallback> = val translator: Translator<GoogleTranslator.GoogleTranslateCallback> =
GoogleTranslator() GoogleTranslator()
@ -78,27 +98,23 @@ class TextGraphic(
translator.translate(param, GoogleTranslator.GoogleTranslateCallback { translatedText -> translator.translate(param, GoogleTranslator.GoogleTranslateCallback { translatedText ->
// Split translated text by delimiter // Split translated text by delimiter
translatedTextBlocks = translatedTextBlocks =
translatedText.split(DELIMITER.toRegex()).dropLastWhile { it.isEmpty() } translatedText.split(DELIMITER.toRegex()).filter { it.isNotEmpty() }
// Update UI thread // Update UI thread
handler.post { handler.post {
postInvalidate() // Notify to redraw postInvalidate()
} }
}) })
}.start() }
} }
override fun draw(canvas: Canvas) { override fun draw(canvas: Canvas) {
Log.d(TAG, "Text is: " + text.text)
for ((translatedIndex, textBlock) in text.textBlocks.withIndex()) { for ((translatedIndex, textBlock) in text.textBlocks.withIndex()) {
val translatedBlockText = val translatedBlockText =
if (translatedIndex < translatedTextBlocks.size) translatedTextBlocks[translatedIndex] else textBlock.text 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) { if (shouldGroupTextInBlocks) {
drawText( drawText(
getFormattedText( getFormattedText(
@ -107,53 +123,16 @@ class TextGraphic(
confidence = null confidence = null
), ),
RectF(textBlock.boundingBox), RectF(textBlock.boundingBox),
height1 - 3 * STROKE_WIDTH,
canvas canvas
) )
} else { Log.d(
for (line in textBlock.lines) { "fdgfsdfsdfas", getFormattedText(
Log.d(TAG, "Line text is: " + line.text) translatedBlockText,
Log.d(TAG, "Line boundingbox is: " + line.boundingBox) textBlock.recognizedLanguage,
Log.d(TAG, "Line cornerpoint is: " + Arrays.toString(line.cornerPoints)) confidence = null
Log.d(TAG, "Line confidence is: " + line.confidence)
Log.d(TAG, "Line angle is: " + line.angle)
// Draw the bounding box around the TextBlock.
val rect = RectF(line.boundingBox)
drawText(
getFormattedText(
translatedBlockText,
line.recognizedLanguage,
line.confidence
),
rect,
((line.boundingBox?.bottom?.toFloat()
?: 20f) - (line.boundingBox?.top?.toFloat()
?: 0f)) - 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)
}
}
}
}
} }
} }
@ -168,78 +147,60 @@ class TextGraphic(
else res else res
} }
private fun drawText(text: String, rect: RectF, textSize: Float, canvas: Canvas) { private fun drawText(text: String, rect: RectF, canvas: Canvas) {
// 如果图像是翻转的,将左边翻译到右边,右边翻译到左边。
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, labelPaint)
// Set initial text size // 设置文本大小以适应矩形
textPaint.textSize = textSize val textPaintCopy = TextPaint(textPaint)
val availableWidth = rect.width().toInt()
val availableHeight = rect.height().toInt()
var textSize = textPaintCopy.textSize
// Break the text into multiple lines if necessary // 调整文本大小以适应矩形
var lines = wrapText(text.trim(), rect.width()) var textLayout: StaticLayout
while (textSize > 0) {
textPaintCopy.textSize = textSize
textLayout =
StaticLayout.Builder.obtain(text, 0, text.length, textPaintCopy, availableWidth)
.setAlignment(Layout.Alignment.ALIGN_NORMAL)
.setLineSpacing(0.0f, 1.0f)
.setIncludePad(false)
.build()
// Calculate total height of the text if (textLayout.height <= availableHeight) {
val totalTextHeight = textPaint.fontMetrics.descent - textPaint.fontMetrics.ascent break
val totalTextHeightWithSpacing = totalTextHeight * lines.size }
textSize -= 1
// 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 // 使用 StaticLayout 绘制文本
val finalTextHeight = textPaint.fontMetrics.descent - textPaint.fontMetrics.ascent textLayout =
val finalTotalTextHeightWithSpacing = finalTextHeight * lines.size StaticLayout.Builder.obtain(text, 0, text.length, textPaintCopy, availableWidth)
.setAlignment(Layout.Alignment.ALIGN_NORMAL)
.setLineSpacing(0.0f, 1.0f)
.setIncludePad(false)
.build()
// Calculate starting Y coordinate to center the text vertically canvas.save()
var textY = canvas.translate(rect.left, rect.top)
rect.top + ((rect.height() - finalTotalTextHeightWithSpacing) / 2) - textPaint.fontMetrics.ascent textLayout.draw(canvas)
canvas.restore()
// Draw the background rectangle
canvas.drawRect(
rect.left - STROKE_WIDTH,
rect.top - STROKE_WIDTH,
rect.right + STROKE_WIDTH,
rect.bottom + STROKE_WIDTH,
labelPaint
)
// 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 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 val TEXT_COLOR = Color.parseColor("#FF474747") private val TEXT_COLOR = Color.parseColor("#FF474747")
private val MARKER_COLOR = Color.parseColor("#FFD9D9D9") private val MARKER_COLOR = Color.parseColor("#FFD9D9D9")
private const val STROKE_WIDTH = 2.0f private const val STROKE_WIDTH = 2.0f
private const val TEXT_SIZE = 44.0f
} }
} }

View File

@ -1,13 +1,9 @@
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.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
@ -17,99 +13,134 @@ import com.google.mlkit.vision.text.TextRecognizerOptionsInterface
/** Processor for the text detector demo. */ /** Processor for the text detector demo. */
class TextRecognitionProcessor( class TextRecognitionProcessor(
private val context: Context, private val context: Context,
textRecognizerOptions: TextRecognizerOptionsInterface textRecognizerOptions: TextRecognizerOptionsInterface
) : VisionProcessorBase<Text>(context) { ) : VisionProcessorBase<Text>(context) {
private val textRecognizer: TextRecognizer = TextRecognition.getClient(textRecognizerOptions) private val textRecognizer: TextRecognizer = TextRecognition.getClient(textRecognizerOptions)
private val shouldGroupRecognizedTextInBlocks: Boolean = private val shouldGroupRecognizedTextInBlocks: Boolean =
PreferenceUtils.shouldGroupRecognizedTextInBlocks(context) PreferenceUtils.shouldGroupRecognizedTextInBlocks(context)
private val showLanguageTag: Boolean = PreferenceUtils.showLanguageTag(context) private val showLanguageTag: Boolean = PreferenceUtils.showLanguageTag(context)
private val showConfidence: Boolean = PreferenceUtils.shouldShowTextConfidence(context) private val showConfidence: Boolean = PreferenceUtils.shouldShowTextConfidence(context)
override fun stop() { override fun stop() {
super.stop() super.stop()
textRecognizer.close() textRecognizer.close()
} }
override fun detectInImage(image: InputImage): Task<Text> { override fun detectInImage(image: InputImage): Task<Text> {
return textRecognizer.process(image) return textRecognizer.process(image)
} }
override fun onSuccess(text: Text, graphicOverlay: GraphicOverlay) {
PreferenceLanguageUtils.putString("language_source", getMostFrequentLanguage(text))
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)
graphicOverlay.add( graphicOverlay.add(
TextGraphic( TextGraphic(
graphicOverlay, graphicOverlay,
text, text,
shouldGroupRecognizedTextInBlocks, shouldGroupRecognizedTextInBlocks,
showLanguageTag, showLanguageTag,
showConfidence showConfidence
) )
) )
} }
override fun onFailure(e: Exception) { override fun onFailure(e: Exception) {
Log.w(TAG, "Text detection failed.$e") Log.w(TAG, "Text detection failed.$e")
} }
companion object {
private const val TAG = "TextRecProcessor" // 推测最可能的语言
private fun logExtrasForTesting(text: Text?) { private fun getMostFrequentLanguage(text: Text): String {
if (text != null) { val languageCount = mutableMapOf<String, Int>()
Log.v(MANUAL_TESTING_LOG, "text context is : " + text.text) for (textBlock in text.textBlocks) {
Log.v(MANUAL_TESTING_LOG, "Detected text has : " + text.textBlocks.size + " blocks") for (line in textBlock.lines) {
for (i in text.textBlocks.indices) { for (element in line.elements) {
val lines = text.textBlocks[i].lines val language = element.recognizedLanguage
Log.v( if (language != "und-Latn") {
MANUAL_TESTING_LOG, if (languageCount.containsKey(language)) {
String.format("Detected text block %d has %d lines", i, lines.size) languageCount[language] = languageCount[language]!! + 1
) } else {
for (j in lines.indices) { languageCount[language] = 1
val elements = lines[j].elements }
Log.v( }
MANUAL_TESTING_LOG, }
String.format("Detected text line %d has %d elements", j, elements.size) }
) }
for (k in elements.indices) {
val element = elements[k] val maxCode = languageCount.maxByOrNull { it.value }?.key ?: "zh_CN"
Log.v(
MANUAL_TESTING_LOG, val lanByCode = LanguagesConstants.getInstance()
String.format("Detected text element %d says: %s", k, element.text) .getLanguageByLanguageCode(maxCode, MyApp.applicationContext())
)
Log.v(
MANUAL_TESTING_LOG, return if (lanByCode != null) {
String.format( lanByCode.language
"Detected text element %d has a bounding box: %s", } else {
k, "English"
element.boundingBox!!.flattenToString() }
) }
)
Log.v( companion object {
MANUAL_TESTING_LOG, private const val TAG = "TextRecProcessor"
String.format( private fun logExtrasForTesting(text: Text?) {
"Expected corner point size is 4, get %d", if (text != null) {
element.cornerPoints!!.size Log.v(MANUAL_TESTING_LOG, "text context is : " + text.text)
) Log.v(MANUAL_TESTING_LOG, "Detected text has : " + text.textBlocks.size + " blocks")
) for (i in text.textBlocks.indices) {
for (point in element.cornerPoints!!) { val lines = text.textBlocks[i].lines
Log.v( Log.v(
MANUAL_TESTING_LOG, MANUAL_TESTING_LOG,
String.format( String.format("Detected text block %d has %d lines", i, lines.size)
"Corner point for element %d is located at: x - %d, y = %d", )
k, for (j in lines.indices) {
point.x, val elements = lines[j].elements
point.y
) Log.v(
) MANUAL_TESTING_LOG,
} String.format("Detected text line %d has %d elements", j, elements.size)
} )
} for (k in elements.indices) {
} val element = elements[k]
} Log.v("推测是什么语言", element.recognizedLanguage)
Log.v(
MANUAL_TESTING_LOG,
String.format("Detected text element %d says: %s", k, element.text)
)
Log.v(
MANUAL_TESTING_LOG,
String.format(
"Detected text element %d has a bounding box: %s",
k,
element.boundingBox!!.flattenToString()
)
)
Log.v(
MANUAL_TESTING_LOG,
String.format(
"Expected corner point size is 4, get %d",
element.cornerPoints!!.size
)
)
for (point in element.cornerPoints!!) {
Log.v(
MANUAL_TESTING_LOG,
String.format(
"Corner point for element %d is located at: x - %d, y = %d",
k,
point.x,
point.y
)
)
}
}
}
}
}
}
} }
}
} }

View File

@ -14,7 +14,7 @@ class SusView : View.OnClickListener {
override fun onClick(v: View?) { override fun onClick(v: View?) {
TODO("Not yet implemented") // TODO("Not yet implemented")
} }

View File

@ -1,172 +1,166 @@
package com.assimilate.alltrans.viewui; package com.assimilate.alltrans.viewui
import android.content.Context; import android.content.Context
import android.os.Bundle; import android.os.Bundle
import android.speech.tts.TextToSpeech; import android.speech.tts.TextToSpeech
import android.view.View; import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.recyclerview.widget.LinearLayoutManager
import com.assimilate.alltrans.R
import com.assimilate.alltrans.adapters.TranslationAdapter
import com.assimilate.alltrans.adapters.TranslationAdapter.TranslationItemCallback
import com.assimilate.alltrans.common.Widget
import com.assimilate.alltrans.databinding.ActivityHistoryBinding
import com.assimilate.alltrans.mydb.DbTranslation
import com.assimilate.alltrans.mydb.Translations
import java.util.Collections
import java.util.Locale
import androidx.appcompat.app.AppCompatActivity; class HistoryActivity : AppCompatActivity() {
import androidx.core.graphics.Insets; private var tts: TextToSpeech? = null
import androidx.core.view.ViewCompat; private var binding: ActivityHistoryBinding? = null
import androidx.core.view.WindowInsetsCompat; private var ids: HashSet<Long>? = null // 通过id 删除数据库文件
import androidx.recyclerview.widget.LinearLayoutManager; private var items: HashSet<Int>? = null // 通许index 删除界面上面的数据
private var operationCollection = false
import com.assimilate.alltrans.R; override fun onCreate(savedInstanceState: Bundle?) {
import com.assimilate.alltrans.adapters.TranslationAdapter; super.onCreate(savedInstanceState)
import com.assimilate.alltrans.common.Widget; enableEdgeToEdge()
import com.assimilate.alltrans.databinding.ActivityHistoryBinding; binding = ActivityHistoryBinding.inflate(layoutInflater)
import com.assimilate.alltrans.mydb.DbTranslation; setContentView(binding!!.root)
import com.assimilate.alltrans.mydb.Translations; ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v: View, insets: WindowInsetsCompat ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(26, systemBars.top, 26, systemBars.bottom)
insets
}
import java.util.ArrayList; ids = HashSet()
import java.util.Collections; items = HashSet()
import java.util.Comparator;
import java.util.HashSet;
import java.util.Locale;
public class HistoryActivity extends AppCompatActivity { val extra = intent.getStringExtra(COMMAND)
public final static String COMMAND = "remove"; operationCollection = COMMAND_COLLECTION == extra
public final static String COMMAND_COLLECTION = "remove-collection";
public final static String COMMAND_HISTORY = "remove-history";
private TextToSpeech tts;
private ActivityHistoryBinding binding;
private HashSet<Long> ids; // 通过id 删除数据库文件
private HashSet<Integer> items; // 通许index 删除界面上面的数据
private boolean operationCollection = false;
@Override tts = TextToSpeech(this) { status ->
protected void onCreate(Bundle savedInstanceState) { if (null != tts && TextToSpeech.SUCCESS == status) tts!!.setLanguage(
super.onCreate(savedInstanceState); Locale.getDefault()
binding = ActivityHistoryBinding.inflate(getLayoutInflater()); )
setContentView(binding.getRoot()); }
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(26, systemBars.top, 26, systemBars.bottom);
return insets;
});
ids = new HashSet<>(); val translations = ArrayList<Translations>()
items = new HashSet<>(); if (!operationCollection) {
String extra = getIntent().getStringExtra(COMMAND);
operationCollection = COMMAND_COLLECTION.equals(extra);
tts = new TextToSpeech(this, new TextToSpeech.OnInitListener() {
@Override
public void onInit(int status) {
if (null != tts && TextToSpeech.SUCCESS == status)
tts.setLanguage(Locale.getDefault());
}
});
ArrayList<Translations> translations = new ArrayList<>();
if (operationCollection) {
// 查出收藏的翻译记录 // 查出收藏的翻译记录
binding.tvFuncTrans.setText("Collect"); binding!!.tvFuncTrans.text = getString(R.string.favor_title)
binding.ivFuncTrans.setImageResource(R.drawable.ic_add); // binding.ivFuncTrans.setImageResource(R.drawable.ic_add);
ArrayList<Translations> list = new DbTranslation(this).getTranslations(true); val list = DbTranslation(this).getTranslations(true)
if (null != list && !list.isEmpty()) { if (null != list && !list.isEmpty()) {
translations.addAll(list); translations.addAll(list)
} }
} else { } else {
// 查出所有的翻译记录 // 查出所有的翻译记录
binding.tvFuncTrans.setText("History"); binding!!.tvFuncTrans.text = getString(R.string.his_title)
binding.ivFuncTrans.setImageResource(R.drawable.ic_add); // binding.ivFuncTrans.setImageResource(R.drawable.ic_add);
ArrayList<Translations> list = new DbTranslation(this).getTranslations(false); val list = DbTranslation(this).getTranslations(false)
if (null != list && !list.isEmpty()) { if (null != list && list.isNotEmpty()) {
translations.addAll(list); translations.addAll(list)
} }
} }
final TranslationAdapter adapter = new TranslationAdapter(translations, new TranslationAdapter.TranslationItemCallback() { val adapter = TranslationAdapter(translations, object : TranslationItemCallback {
@Override override fun updateList(
public void updateList(TranslationAdapter.Operation operation, long id, int position) { operation: TranslationAdapter.Operation,
id: Long,
position: Int
) {
if (TranslationAdapter.Operation.ADD == operation) { if (TranslationAdapter.Operation.ADD == operation) {
add(id, position); add(id, position)
} else if (TranslationAdapter.Operation.REMOVE == operation) { } else if (TranslationAdapter.Operation.REMOVE == operation) {
remove(id, position); remove(id, position)
} }
updateBtn(); updateBtn()
} }
@Override override fun speech(value: String) {
public void speech(String value) {
if (null != tts if (null != tts
&& TextToSpeech.LANG_NOT_SUPPORTED != tts.isLanguageAvailable(Locale.getDefault())) { && TextToSpeech.LANG_NOT_SUPPORTED != tts!!.isLanguageAvailable(Locale.getDefault())
tts.speak(value, 0, null, null); ) {
tts!!.speak(value, 0, null, null)
} }
} }
private void add(long id, int index) { private fun add(id: Long, index: Int) {
ids.add(id); ids!!.add(id)
items.add(index); items!!.add(index)
} }
private void remove(long id, int index) { private fun remove(id: Long, index: Int) {
ids.remove(id); ids!!.remove(id)
items.remove(index); items!!.remove(index)
} }
private void updateBtn() { private fun updateBtn() {
if (ids.isEmpty()) { if (ids!!.isEmpty()) {
binding.remove.setVisibility(View.INVISIBLE); binding!!.remove.visibility = View.INVISIBLE
} else { } else {
binding.remove.setVisibility(View.VISIBLE); binding!!.remove.visibility = View.VISIBLE
} }
} }
}); })
final LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
binding.remove.setOnClickListener(new View.OnClickListener() { binding!!.remove.setOnClickListener(object : View.OnClickListener {
private DbTranslation dbTranslation; private var dbTranslation: DbTranslation? = null
@Override override fun onClick(v: View) {
public void onClick(View v) { if (ids!!.isEmpty()) {
if (ids.isEmpty()) { Widget.makeToast(this@HistoryActivity, "Noting to remove.")
Widget.makeToast(HistoryActivity.this, "Noting to remove."); return
return;
} }
if (!ids.isEmpty()) { if (!ids!!.isEmpty()) {
ArrayList<Long> longArrayList = new ArrayList<>(ids); val longArrayList = ArrayList(ids)
if (operationCollection) { if (operationCollection) {
getDbTranslation(HistoryActivity.this).removeCollectTranslations(longArrayList); getDbTranslation(this@HistoryActivity).removeCollectTranslations(
longArrayList
)
} else { } else {
getDbTranslation(HistoryActivity.this).removeTranslations(longArrayList); getDbTranslation(this@HistoryActivity).removeTranslations(longArrayList)
} }
ids.clear(); ids!!.clear()
} }
if (!items.isEmpty()) { if (!items!!.isEmpty()) {
ArrayList<Integer> integerArrayList = new ArrayList<>(items); val integerArrayList = ArrayList(items)
Collections.sort(integerArrayList, new Comparator<Integer>() { Collections.sort(integerArrayList) { o1, o2 -> o2.compareTo(o1) }
@Override adapter.updateSet(integerArrayList)
public int compare(Integer o1, Integer o2) { items!!.clear()
return o2.compareTo(o1);
}
});
adapter.updateSet(integerArrayList);
items.clear();
} }
binding.remove.setVisibility(View.VISIBLE); binding!!.remove.visibility = View.VISIBLE
} }
private DbTranslation getDbTranslation(Context context) { private fun getDbTranslation(context: Context): DbTranslation {
if (null == dbTranslation) { if (null == dbTranslation) {
dbTranslation = new DbTranslation(context); dbTranslation = DbTranslation(context)
} }
return dbTranslation; return dbTranslation!!
} }
}); })
binding.histories.setLayoutManager(layoutManager); binding!!.histories.layoutManager = layoutManager
binding.histories.setAdapter(adapter); binding!!.histories.adapter = adapter
} }
@Override override fun onBackPressed() {
public void onBackPressed() { super.onBackPressed()
super.onBackPressed();
} }
public void clickBack(View view) { fun clickBack(view: View?) {
onBackPressed(); onBackPressed()
}
companion object {
const val COMMAND: String = "remove"
const val COMMAND_COLLECTION: String = "remove_collection"
const val COMMAND_HISTORY: String = "remove_history"
} }
} }

View File

@ -31,10 +31,26 @@ class LanguageChangeActivity : AppCompatActivity() {
} }
initView() initView()
initSearch() // 添加这一行
initList() initList()
initClick() initClick()
} }
private fun initSearch() {
binding.chSearch.setOnQueryTextListener(object :
androidx.appcompat.widget.SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
(binding.listLanguages.adapter as LanguageAdapter).filter.filter(newText)
return true
}
})
}
private fun initView() { private fun initView() {
binding.tvChangeSource.text = PreferenceLanguageUtils.getString("language_source") binding.tvChangeSource.text = PreferenceLanguageUtils.getString("language_source")
binding.tvChangeTarget.text = PreferenceLanguageUtils.getString("language_target") binding.tvChangeTarget.text = PreferenceLanguageUtils.getString("language_target")
@ -57,32 +73,38 @@ class LanguageChangeActivity : AppCompatActivity() {
binding.tvChangeTarget.text = PreferenceLanguageUtils.getString("language_target") binding.tvChangeTarget.text = PreferenceLanguageUtils.getString("language_target")
updateRecentLanguages() updateRecentLanguages()
} }
binding.listLanCommon5.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) binding.listLanCommon5.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
binding.listLanCommon5.adapter = recentAdapter binding.listLanCommon5.adapter = recentAdapter
} }
} }
private fun initClick() { private fun initClick() {
binding.ivChBack.setOnClickListener { onBackPressed() }
binding.tvChangeSource.setOnClickListener { binding.tvChangeSource.setOnClickListener {
lastTranslateLanguage = false lastTranslateLanguage = false
binding.tvChangeSource.setTextColor(getColor(R.color.main_text_ff0e8ce8))
binding.tvChangeTarget.setTextColor(getColor(R.color.main_text_ff1f1724))
} }
binding.tvChangeTarget.setOnClickListener { binding.tvChangeTarget.setOnClickListener {
lastTranslateLanguage = true lastTranslateLanguage = true
binding.tvChangeSource.setTextColor(getColor(R.color.main_text_ff1f1724))
binding.tvChangeTarget.setTextColor(getColor(R.color.main_text_ff0e8ce8))
} }
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()
}
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() {
@ -101,8 +123,10 @@ class LanguageChangeActivity : AppCompatActivity() {
binding.tvChangeTarget.text = PreferenceLanguageUtils.getString("language_target") binding.tvChangeTarget.text = PreferenceLanguageUtils.getString("language_target")
updateRecentLanguages() updateRecentLanguages()
} }
binding.listLanguages.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) binding.listLanguages.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
binding.listLanguages.adapter = adapter binding.listLanguages.adapter = adapter
} }
} }
} }

View File

@ -7,8 +7,10 @@ import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.media.projection.MediaProjectionManager import android.media.projection.MediaProjectionManager
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.Settings
import android.speech.RecognizerIntent import android.speech.RecognizerIntent
import android.text.Editable import android.text.Editable
import android.text.TextUtils import android.text.TextUtils
@ -23,7 +25,6 @@ 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.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
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.LanguagesConstants
@ -33,16 +34,16 @@ import com.assimilate.alltrans.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private var launcher: ActivityResultLauncher<Intent>? = null private var launcher: ActivityResultLauncher<Intent>? = null
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
private lateinit var lcm: LocalBroadcastManager private lateinit var lcm: LocalBroadcastManager
private lateinit var mediaProjectionManager: MediaProjectionManager private lateinit var mediaProjectionManager: MediaProjectionManager
private val REQUEST_CODE_SCREEN_CAPTURE = 1001 private val REQUEST_CODE = 1000
private val mpResultCode = "mpResultCode"
private val mpData = "mpData"
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -59,7 +60,18 @@ class MainActivity : AppCompatActivity() {
initClick() initClick()
registerResult() registerResult()
// 初始化
mediaProjectionManager =
getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
// 检查并请求悬浮窗权限
if (!Settings.canDrawOverlays(this)) {
val intent = Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:$packageName")
)
startActivityForResult(intent, REQUEST_CODE)
}
} }
private fun registerResult() { private fun registerResult() {
@ -77,32 +89,25 @@ class MainActivity : AppCompatActivity() {
} }
} }
private fun startScreenCapture() {
val captureIntent = mediaProjectionManager.createScreenCaptureIntent()
startActivityForResult(captureIntent, REQUEST_CODE_SCREEN_CAPTURE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_SCREEN_CAPTURE && resultCode == Activity.RESULT_OK) { if (requestCode == REQUEST_CODE) {
startSusService(resultCode, data) if (resultCode == Activity.RESULT_OK && data != null) {
val serviceIntent = Intent(this, SusService::class.java).apply {
putExtra(mpResultCode, resultCode)
putExtra(mpData, data)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent)
} else {
startService(serviceIntent)
}
} else {
Log.e("MainActivity", "Screen capture permission denied")
}
} }
} }
private fun startSusService(resultCode: Int, data: Intent?) {
val serviceIntent = Intent(this, SusService::class.java).apply {
putExtra("resultCode", resultCode)
putExtra("data", data)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent)
} else {
startService(serviceIntent)
}
}
private fun initView() { private fun initView() {
binding.chSourceLanguage.text = PreferenceLanguageUtils.getString("language_source") binding.chSourceLanguage.text = PreferenceLanguageUtils.getString("language_source")
binding.chTargetLanguage.text = PreferenceLanguageUtils.getString("language_target") binding.chTargetLanguage.text = PreferenceLanguageUtils.getString("language_target")
@ -120,6 +125,14 @@ class MainActivity : AppCompatActivity() {
} }
override fun afterTextChanged(s: Editable?) { override fun afterTextChanged(s: Editable?) {
// 统计字符数并显示在 TextView 中
val charCount = s?.length ?: 0
// 截断逻辑
if (charCount > 1800) {
val truncatedText = s?.subSequence(0, 1800)
binding.etText.setText(truncatedText)
binding.etText.setSelection(1800) // 设置光标位置到文本末尾
}
// 根据EditText的内容显示或隐藏粘贴按钮 // 根据EditText的内容显示或隐藏粘贴按钮
if (s.isNullOrEmpty()) { if (s.isNullOrEmpty()) {
binding.tvMainTrans.visibility = View.GONE binding.tvMainTrans.visibility = View.GONE
@ -127,6 +140,10 @@ class MainActivity : AppCompatActivity() {
} else { } else {
binding.tvMainTrans.visibility = View.VISIBLE binding.tvMainTrans.visibility = View.VISIBLE
} }
// 更新字符计数显示
val displayCharCount = if (charCount > 1800) 1800 else charCount
binding.tvMainLimitText.text = getString(R.string.main_limit_num, displayCharCount)
} }
}) })
@ -167,8 +184,10 @@ class MainActivity : AppCompatActivity() {
) )
} }
binding.ivMainHistory.setOnClickListener { binding.ivMainHistory.setOnClickListener {
intent = Intent(this, HistoryActivity::class.java)
intent.putExtra("remove", "remove_collection")
startActivity( startActivity(
Intent(this, HistoryActivity::class.java) intent
) )
} }
binding.llQuickSet.setOnClickListener { binding.llQuickSet.setOnClickListener {
@ -177,17 +196,12 @@ class MainActivity : AppCompatActivity() {
) )
} }
binding.ivQuickStart.setOnClickListener { binding.ivQuickStart.setOnClickListener {
// 启动截图
startActivityForResult(
mediaProjectionManager.createScreenCaptureIntent(),
REQUEST_CODE
)
mediaProjectionManager =
getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
startScreenCapture()
val intent = Intent(this, SusService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
} }
binding.ivMainExChange.setOnClickListener { binding.ivMainExChange.setOnClickListener {
@ -203,9 +217,6 @@ class MainActivity : AppCompatActivity() {
binding.chSourceLanguage.text = currentTargetLanguage binding.chSourceLanguage.text = currentTargetLanguage
binding.chTargetLanguage.text = currentSourceLanguage binding.chTargetLanguage.text = currentSourceLanguage
// 打印日志,验证交换后的语言设置
Log.d("fdhash_su", PreferenceLanguageUtils.getString("language_source"))
Log.d("fdhash_ta", PreferenceLanguageUtils.getString("language_target"))
} }
@ -216,7 +227,6 @@ class MainActivity : AppCompatActivity() {
return return
} }
val intent = Intent(this, TextResultActivity::class.java) val intent = Intent(this, TextResultActivity::class.java)
// 将字符串数据添加到Intent中
intent.putExtra("source_text", binding.etText.text.toString()) intent.putExtra("source_text", binding.etText.text.toString())
startActivity(intent) startActivity(intent)
binding.etText.text = null binding.etText.text = null
@ -228,11 +238,11 @@ class MainActivity : AppCompatActivity() {
val speechIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH) val speechIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
speechIntent.putExtra( speechIntent.putExtra(
RecognizerIntent.EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS, RecognizerIntent.EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS,
5000 4000
) // 设置5秒的静默时间 ) // 设置5秒的静默时间
speechIntent.putExtra( speechIntent.putExtra(
RecognizerIntent.EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS, RecognizerIntent.EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS,
5000 4000
) // 设置5秒的可能完全静默时间 ) // 设置5秒的可能完全静默时间
@ -254,7 +264,7 @@ class MainActivity : AppCompatActivity() {
try { try {
launcher?.launch(speechIntent) launcher?.launch(speechIntent)
} catch (ea: ActivityNotFoundException) { } catch (ea: ActivityNotFoundException) {
Widget.makeToast(this, "Something went wrong.") Widget.makeToast(this, getString(R.string.main_voice_to_text))
} }
} }

View File

@ -1,5 +1,6 @@
package com.assimilate.alltrans.viewui package com.assimilate.alltrans.viewui
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@ -44,5 +45,11 @@ class SettingsActivity
bottomSheetDialog.show() bottomSheetDialog.show()
} }
binding.llFavorite.setOnClickListener {
val intent = Intent(this, HistoryActivity::class.java)
intent.putExtra("remove", "remove_history")
startActivity(intent)
}
} }
} }

View File

@ -9,19 +9,23 @@ import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Bitmap import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle 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.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.ImageView import android.widget.ImageView
import android.widget.PopupMenu
import android.widget.SearchView
import android.widget.Spinner import android.widget.Spinner
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.CameraSelector import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageCapture import androidx.camera.core.ImageCapture
@ -31,14 +35,24 @@ import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView 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 androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.assimilate.alltrans.R import com.assimilate.alltrans.R
import com.assimilate.alltrans.adapters.LanguageAdapter
import com.assimilate.alltrans.common.BitmapUtils import com.assimilate.alltrans.common.BitmapUtils
import com.assimilate.alltrans.common.Language
import com.assimilate.alltrans.common.LanguagesConstants
import com.assimilate.alltrans.common.PreferenceLanguageUtils
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.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.android.material.bottomsheet.BottomSheetDialog
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import com.google.mlkit.common.model.LocalModel
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
@ -47,9 +61,12 @@ import com.google.mlkit.vision.text.latin.TextRecognizerOptions
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.ArrayList
import java.util.Locale import java.util.Locale
import kotlin.math.max
import kotlin.math.min
/** 演示使用相机拍摄静态图像进行不同图像检测功能的活动。 */ /** 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
@ -67,56 +84,120 @@ class StillImageActivity : AppCompatActivity() {
private var isFlashOn = false private var isFlashOn = false
private val REQUEST_CAMERA_PERMISSION = 100 private val REQUEST_CAMERA_PERMISSION = 100
private lateinit var bottomSheetDialog: BottomSheetDialog
private var chooseLanguage :Boolean = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_still_image) setContentView(R.layout.activity_still_image)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.root)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(26, systemBars.top + 26, 26, systemBars.bottom)
insets
}
// 检查并请求相机权限 Widget.makeSnackbar(this, "Photographing text for translation")
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED preview = findViewById(R.id.preview)
graphicOverlay = findViewById(R.id.graphic_overlay)
isLandScape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
savedInstanceState?.let {
imageUri = it.getParcelable(KEY_IMAGE_URI)
imageMaxWidth = it.getInt(KEY_IMAGE_MAX_WIDTH)
imageMaxHeight = it.getInt(KEY_IMAGE_MAX_HEIGHT)
selectedSize = it.getString(KEY_SELECTED_SIZE)
}
val rootView = findViewById<View>(R.id.root)
rootView.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
rootView.viewTreeObserver.removeOnGlobalLayoutListener(this)
imageMaxWidth = rootView.width
imageMaxHeight = rootView.height - findViewById<View>(R.id.control).height
if (SIZE_SCREEN == selectedSize) {
tryReloadAndDetectInImage()
}
}
})
outputDirectory = getOutputDirectory()
// 检查并启动相机
checkCameraPermission()
initView()
initClick()
}
private fun checkCameraPermission() {
if (ContextCompat.checkSelfPermission(
this, Manifest.permission.CAMERA
) != PackageManager.PERMISSION_GRANTED
) { ) {
ActivityCompat.requestPermissions( ActivityCompat.requestPermissions(
this, this,
arrayOf(Manifest.permission.CAMERA), arrayOf(Manifest.permission.CAMERA),
REQUEST_CAMERA_PERMISSION REQUEST_CAMERA_PERMISSION
) )
} else {
startCamera()
} }
Widget.makeSnackbar(this, "Photographing text for translation")
preview = findViewById(R.id.preview)
graphicOverlay = findViewById(R.id.graphic_overlay)
populateFeatureSelector()
isLandScape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
if (savedInstanceState != null) {
imageUri = savedInstanceState.getParcelable(KEY_IMAGE_URI)
imageMaxWidth = savedInstanceState.getInt(KEY_IMAGE_MAX_WIDTH)
imageMaxHeight = savedInstanceState.getInt(KEY_IMAGE_MAX_HEIGHT)
selectedSize = savedInstanceState.getString(KEY_SELECTED_SIZE)
}
val rootView = findViewById<View>(R.id.root)
rootView.viewTreeObserver.addOnGlobalLayoutListener(
object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
rootView.viewTreeObserver.removeOnGlobalLayoutListener(this)
imageMaxWidth = rootView.width
imageMaxHeight = rootView.height - findViewById<View>(R.id.control).height
if (SIZE_SCREEN == selectedSize) {
tryReloadAndDetectInImage()
}
}
}
)
// 初始化相机
startCamera()
outputDirectory = getOutputDirectory()
initClick()
} }
private fun initView() {
findViewById<TextView>(R.id.still_source_language).text =
PreferenceLanguageUtils.getString("language_source")
findViewById<TextView>(R.id.still_target_language).text =
PreferenceLanguageUtils.getString("language_target")
bottomSheetDialog = BottomSheetDialog(this)
bottomSheetDialog.setContentView(R.layout.bottomsheet_still_lan)
bottomSheetDialog.dismissWithAnimation = true
bottomSheetDialog.findViewById<androidx.appcompat.widget.SearchView>(R.id.ph_search)
?.setOnQueryTextListener(object :
androidx.appcompat.widget.SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
(bottomSheetDialog.findViewById<RecyclerView>(R.id.list_languages)?.adapter as LanguageAdapter).filter.filter(
newText
)
return true
}
})
initList()
}
private fun initList() {
val languages: ArrayList<Language> = LanguagesConstants.getInstance().getList(this)
if (languages.isNotEmpty()) {
val adapter = LanguageAdapter(this, languages) { _, language ->
if (!chooseLanguage) {
PreferenceLanguageUtils.putString("language_source", language.language)
bottomSheetDialog.dismiss()
} else {
PreferenceLanguageUtils.putString("language_target", language.language)
bottomSheetDialog.dismiss()
}
findViewById<TextView>(R.id.still_source_language).text =
PreferenceLanguageUtils.getString("language_source")
findViewById<TextView>(R.id.still_target_language).text =
PreferenceLanguageUtils.getString("language_target")
}
bottomSheetDialog.findViewById<RecyclerView>(R.id.list_languages)?.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
bottomSheetDialog.findViewById<RecyclerView>(R.id.list_languages)?.adapter = adapter
}
}
private fun initClick() { private fun initClick() {
findViewById<ImageView>(R.id.iv_still_pic).setOnClickListener { startChooseImageIntentForResult() } findViewById<ImageView>(R.id.iv_still_pic).setOnClickListener { startChooseImageIntentForResult() }
findViewById<ImageView>(R.id.iv_still_take).setOnClickListener { takePhoto() } findViewById<ImageView>(R.id.iv_still_take).setOnClickListener { takePhoto() }
@ -125,46 +206,40 @@ class StillImageActivity : AppCompatActivity() {
toggleFlash() toggleFlash()
updateFlashButtonUI() updateFlashButtonUI()
} }
findViewById<TextView>(R.id.still_source_language).setOnClickListener { } findViewById<TextView>(R.id.still_source_language).setOnClickListener {
findViewById<TextView>(R.id.still_target_language).setOnClickListener { } chooseLanguage = false
findViewById<TextView>(R.id.still_exChange).setOnClickListener { } bottomSheetDialog.show()
}
findViewById<TextView>(R.id.still_target_language).setOnClickListener {
chooseLanguage = true
bottomSheetDialog.show()
}
findViewById<ImageView>(R.id.still_exChange).setOnClickListener {
}
} }
private fun toggleFlash() { private fun toggleFlash() {
if (isFlashOn) { imageCapture.flashMode =
imageCapture.flashMode = ImageCapture.FLASH_MODE_OFF if (isFlashOn) ImageCapture.FLASH_MODE_OFF else ImageCapture.FLASH_MODE_ON
} else {
imageCapture.flashMode = ImageCapture.FLASH_MODE_ON
}
isFlashOn = !isFlashOn isFlashOn = !isFlashOn
} }
private fun updateFlashButtonUI() { private fun updateFlashButtonUI() {
if (isFlashOn) { findViewById<ImageView>(R.id.iv_still_buling).setImageResource(if (isFlashOn) R.drawable.ic_still_bulibuli else R.drawable.ic_still_notbuli)
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() { private fun startCamera() {
val previewView = findViewById<PreviewView>(R.id.photo_preview) val previewView = findViewById<PreviewView>(R.id.photo_preview)
cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener(Runnable { cameraProviderFuture.addListener(Runnable {
val cameraProvider = cameraProviderFuture.get() val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder() val preview = Preview.Builder().build().also {
.build() it.setSurfaceProvider(previewView.surfaceProvider)
.also { }
it.setSurfaceProvider(previewView.surfaceProvider)
}
imageCapture = ImageCapture.Builder()
.setFlashMode(ImageCapture.FLASH_MODE_OFF) // 默认关闭闪光灯
.build()
imageCapture = ImageCapture.Builder().setFlashMode(ImageCapture.FLASH_MODE_OFF).build()
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try { try {
@ -177,14 +252,10 @@ class StillImageActivity : AppCompatActivity() {
} }
private fun takePhoto() { private fun takePhoto() {
val imageCapture = imageCapture
val photoFile = File( val photoFile = File(
outputDirectory, outputDirectory,
SimpleDateFormat(FILENAME_FORMAT, Locale.US).format(System.currentTimeMillis()) + ".jpg" SimpleDateFormat(FILENAME_FORMAT, Locale.US).format(System.currentTimeMillis()) + ".jpg"
) )
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
imageCapture.takePicture( imageCapture.takePicture(
@ -196,25 +267,20 @@ class StillImageActivity : AppCompatActivity() {
} }
override fun onImageSaved(output: ImageCapture.OutputFileResults) { override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val savedUri = Uri.fromFile(photoFile) imageUri = Uri.fromFile(photoFile)
imageUri = savedUri Log.d(TAG, "Photo capture succeeded: $imageUri")
val msg = "Photo capture succeeded: $savedUri"
Log.d(TAG, msg)
tryReloadAndDetectInImage() tryReloadAndDetectInImage()
} }
} })
)
} }
private fun getOutputDirectory(): File { private fun getOutputDirectory(): File {
val mediaDir = externalMediaDirs.firstOrNull()?.let { val mediaDir = externalMediaDirs.firstOrNull()?.let {
File(it, resources.getString(R.string.app_name)).apply { mkdirs() } File(it, resources.getString(R.string.app_name)).apply { mkdirs() }
} }
return if (mediaDir != null && mediaDir.exists()) return if (mediaDir != null && mediaDir.exists()) mediaDir else filesDir
mediaDir else filesDir
} }
override fun onRequestPermissionsResult( override fun onRequestPermissionsResult(
requestCode: Int, requestCode: Int,
permissions: Array<String>, permissions: Array<String>,
@ -224,80 +290,43 @@ class StillImageActivity : AppCompatActivity() {
when (requestCode) { when (requestCode) {
REQUEST_CAMERA_PERMISSION -> { REQUEST_CAMERA_PERMISSION -> {
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
// Permission is granted, proceed with camera startCamera()
startCameraIntentForResult()
} else { } else {
// Permission denied, show a message to the user
Toast.makeText( Toast.makeText(
this, this,
"Camera permission is required to use the camera", "Camera permission is required to use the camera",
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
} }
return
} }
} }
} }
override fun onResume() {
public override fun onResume() {
super.onResume() super.onResume()
Log.d(TAG, "onResume") Log.d(TAG, "onResume")
createImageProcessor() createImageProcessor()
tryReloadAndDetectInImage() tryReloadAndDetectInImage()
} }
public override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
imageProcessor?.run { this.stop() } imageProcessor?.stop()
} }
public override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
imageProcessor?.stop()
imageProcessor?.run { this.stop() }
// 释放相机资源
if (::cameraProviderFuture.isInitialized) { if (::cameraProviderFuture.isInitialized) {
cameraProviderFuture.get().unbindAll() cameraProviderFuture.get().unbindAll()
} }
} }
private fun populateFeatureSelector() { override fun onSaveInstanceState(outState: Bundle) {
val featureSpinner = findViewById<Spinner>(R.id.feature_selector)
val options: MutableList<String> = ArrayList()
options.add(TEXT_RECOGNITION_CHINESE); // 识别中文文本
options.add(TEXT_RECOGNITION_LATIN); // 识别拉丁文本
options.add(TEXT_RECOGNITION_DEVANAGARI); // 识别梵文文本
options.add(TEXT_RECOGNITION_JAPANESE); // 识别日文文本
options.add(TEXT_RECOGNITION_KOREAN); // 识别韩文文本
// 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
featureSpinner.adapter = dataAdapter
featureSpinner.onItemSelectedListener =
object : OnItemSelectedListener {
override fun onItemSelected(
parentView: AdapterView<*>,
selectedItemView: View?,
pos: Int,
id: Long
) {
if (pos >= 0) {
selectedMode = parentView.getItemAtPosition(pos).toString()
createImageProcessor()
tryReloadAndDetectInImage()
}
}
override fun onNothingSelected(arg0: AdapterView<*>?) {}
}
}
public override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putParcelable(KEY_IMAGE_URI, imageUri) outState.putParcelable(KEY_IMAGE_URI, imageUri)
outState.putInt(KEY_IMAGE_MAX_WIDTH, imageMaxWidth) outState.putInt(KEY_IMAGE_MAX_WIDTH, imageMaxWidth)
@ -305,104 +334,71 @@ class StillImageActivity : AppCompatActivity() {
outState.putString(KEY_SELECTED_SIZE, selectedSize) outState.putString(KEY_SELECTED_SIZE, selectedSize)
} }
private fun startCameraIntentForResult() { private fun createImageProcessor() {
imageProcessor?.stop()
try {
imageProcessor = when (selectedMode) {
TEXT_RECOGNITION_CHINESE -> TextRecognitionProcessor(
this,
ChineseTextRecognizerOptions.Builder().build()
)
// Ensure permission is still granted before starting camera intent "Hindi", "Marathi", "Nepali", "Sanskrit" -> TextRecognitionProcessor(
if (ContextCompat.checkSelfPermission( this,
this, DevanagariTextRecognizerOptions.Builder().build()
Manifest.permission.CAMERA )
) != PackageManager.PERMISSION_GRANTED
) { TEXT_RECOGNITION_JAPANESE -> TextRecognitionProcessor(
this,
JapaneseTextRecognizerOptions.Builder().build()
)
TEXT_RECOGNITION_KOREAN -> TextRecognitionProcessor(
this,
KoreanTextRecognizerOptions.Builder().build()
)
else -> TextRecognitionProcessor(
this,
TextRecognizerOptions.Builder().build()
)
}
} catch (e: Exception) {
Log.e(TAG, "Can not create image processor: $selectedMode", e)
Toast.makeText( Toast.makeText(
this, applicationContext,
"Camera permission is required to use the camera", "Can not create image processor: " + e.message,
Toast.LENGTH_SHORT Toast.LENGTH_LONG
).show() ).show()
return
}
imageUri = null
preview!!.setImageBitmap(null)
val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
if (takePictureIntent.resolveActivity(packageManager) != null) {
val values = ContentValues()
values.put(MediaStore.Images.Media.TITLE, "New Picture")
values.put(MediaStore.Images.Media.DESCRIPTION, "From Camera")
imageUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
}
}
private fun startChooseImageIntentForResult() {
val intent = Intent()
intent.type = "image/*"
intent.action = Intent.ACTION_GET_CONTENT
startActivityForResult(Intent.createChooser(intent, "Select Picture"), REQUEST_CHOOSE_IMAGE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK) {
tryReloadAndDetectInImage()
} else if (requestCode == REQUEST_CHOOSE_IMAGE && resultCode == Activity.RESULT_OK) {
// In this case, imageUri is returned by the chooser, save it.
imageUri = data!!.data
tryReloadAndDetectInImage()
} else {
super.onActivityResult(requestCode, resultCode, data)
} }
} }
private fun tryReloadAndDetectInImage() { private fun tryReloadAndDetectInImage() {
Log.d(TAG, "Try reload and detect image")
try { try {
if (imageUri == null) { if (imageUri == null) return
return if (SIZE_SCREEN == selectedSize && imageMaxWidth == 0) return
}
if (SIZE_SCREEN == selectedSize && imageMaxWidth == 0) {
// UI layout has not finished yet, will reload once it's ready.
return
}
val imageBitmap = val imageBitmap =
BitmapUtils.getBitmapFromContentUri(contentResolver, imageUri) ?: return BitmapUtils.getBitmapFromContentUri(contentResolver, imageUri) ?: return
// Clear the overlay first
graphicOverlay!!.clear()
val resizedBitmap: Bitmap if (selectedSize == SIZE_SCREEN) {
resizedBitmap = val targetedSize = targetedWidthHeight
if (selectedSize == SIZE_ORIGINAL) { val scaleFactor = max(
imageBitmap imageBitmap.width.toFloat() / targetedSize.first.toFloat(),
} else { imageBitmap.height.toFloat() / targetedSize.second.toFloat()
// Get the dimensions of the image view
val targetedSize: Pair<Int, Int> = targetedWidthHeight
// Determine how much to scale down the image
val scaleFactor =
Math.max(
imageBitmap.width.toFloat() / targetedSize.first.toFloat(),
imageBitmap.height.toFloat() / targetedSize.second.toFloat()
)
Bitmap.createScaledBitmap(
imageBitmap,
(imageBitmap.width / scaleFactor).toInt(),
(imageBitmap.height / scaleFactor).toInt(),
true
)
}
preview!!.setImageBitmap(resizedBitmap)
if (imageProcessor != null) {
graphicOverlay!!.setImageSourceInfo(
resizedBitmap.width,
resizedBitmap.height,
/* isFlipped= */ false
) )
imageProcessor!!.processBitmap(resizedBitmap, graphicOverlay) val resizedBitmap = Bitmap.createScaledBitmap(
imageBitmap,
(imageBitmap.width / scaleFactor).toInt(),
(imageBitmap.height / scaleFactor).toInt(),
true
)
preview?.setImageBitmap(resizedBitmap)
processImage(resizedBitmap)
} else { } else {
Log.e( preview?.setImageBitmap(imageBitmap)
TAG, processImage(imageBitmap)
"Null imageProcessor, please check adb logs for imageProcessor creation error"
)
} }
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, "Error retrieving saved image") Log.e(TAG, "Error retrieving saved image")
@ -412,95 +408,52 @@ class StillImageActivity : AppCompatActivity() {
private val targetedWidthHeight: Pair<Int, Int> private val targetedWidthHeight: Pair<Int, Int>
get() { get() {
val targetWidth: Int val targetWidth = if (isLandScape) max(imageMaxWidth, imageMaxHeight) else min(
val targetHeight: Int imageMaxWidth,
when (selectedSize) { imageMaxHeight
SIZE_SCREEN -> { )
targetWidth = imageMaxWidth val targetHeight = if (isLandScape) min(imageMaxWidth, imageMaxHeight) else max(
targetHeight = imageMaxHeight imageMaxWidth,
} imageMaxHeight
)
SIZE_640_480 -> {
targetWidth = if (isLandScape) 640 else 480
targetHeight = if (isLandScape) 480 else 640
}
SIZE_1024_768 -> {
targetWidth = if (isLandScape) 1024 else 768
targetHeight = if (isLandScape) 768 else 1024
}
else -> throw IllegalStateException("Unknown size")
}
return Pair(targetWidth, targetHeight) return Pair(targetWidth, targetHeight)
} }
private fun createImageProcessor() { private fun processImage(bitmap: Bitmap) {
try { graphicOverlay?.clear()
when (selectedMode) { imageProcessor?.processBitmap(bitmap, graphicOverlay)
TEXT_RECOGNITION_LATIN -> }
imageProcessor =
TextRecognitionProcessor(this, TextRecognizerOptions.Builder().build())
TEXT_RECOGNITION_CHINESE -> private fun startChooseImageIntentForResult() {
imageProcessor = val intent = Intent().apply {
TextRecognitionProcessor( type = "image/*"
this, action = Intent.ACTION_GET_CONTENT
ChineseTextRecognizerOptions.Builder().build() }
) startActivityForResult(Intent.createChooser(intent, "Select Picture"), REQUEST_CHOOSE_IMAGE)
}
TEXT_RECOGNITION_DEVANAGARI -> override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
imageProcessor = super.onActivityResult(requestCode, resultCode, data)
TextRecognitionProcessor( if (requestCode == REQUEST_CHOOSE_IMAGE && resultCode == Activity.RESULT_OK && data != null && data.data != null) {
this, imageUri = data.data
DevanagariTextRecognizerOptions.Builder().build() tryReloadAndDetectInImage()
)
TEXT_RECOGNITION_JAPANESE ->
imageProcessor =
TextRecognitionProcessor(
this,
JapaneseTextRecognizerOptions.Builder().build()
)
TEXT_RECOGNITION_KOREAN ->
imageProcessor =
TextRecognitionProcessor(
this,
KoreanTextRecognizerOptions.Builder().build()
)
else -> Log.e(TAG, "Unknown selectedMode: $selectedMode")
}
} catch (e: Exception) {
Log.e(TAG, "Can not create image processor: $selectedMode", e)
Toast.makeText(
applicationContext,
"Can not create image processor: " + e.message,
Toast.LENGTH_LONG
)
.show()
} }
} }
companion object { companion object {
private const val TAG = "StillImageActivity" private const val TAG = "StillImageActivity"
private const val TEXT_RECOGNITION_LATIN = "Text Recognition Latin" private const val KEY_IMAGE_URI = "com.google.mlkit.demo.stillImage.KEY_IMAGE_URI"
private const val TEXT_RECOGNITION_CHINESE = "Text Recognition Chinese" private const val KEY_IMAGE_MAX_WIDTH =
private const val TEXT_RECOGNITION_DEVANAGARI = "Text Recognition Devanagari" "com.google.mlkit.demo.stillImage.KEY_IMAGE_MAX_WIDTH"
private const val TEXT_RECOGNITION_JAPANESE = "Text Recognition Japanese" private const val KEY_IMAGE_MAX_HEIGHT =
private const val TEXT_RECOGNITION_KOREAN = "Text Recognition Korean" "com.google.mlkit.demo.stillImage.KEY_IMAGE_MAX_HEIGHT"
private const val KEY_SELECTED_SIZE = "com.google.mlkit.demo.stillImage.KEY_SELECTED_SIZE"
private const val SIZE_SCREEN = "w:screen" // Match screen width private const val SIZE_SCREEN = "w:screen"
private const val SIZE_1024_768 = "w:1024" // ~1024*768 in a normal ratio private const val TEXT_RECOGNITION_CHINESE = "Chinese, Simplified"
private const val SIZE_640_480 = "w:640" // ~640*480 in a normal ratio private const val TEXT_RECOGNITION_JAPANESE = "Japanese"
private const val SIZE_ORIGINAL = "w:original" // Original image size private const val TEXT_RECOGNITION_KOREAN = "Korean"
private const val KEY_IMAGE_URI = "com.google.mlkit.vision.demo.KEY_IMAGE_URI"
private const val KEY_IMAGE_MAX_WIDTH = "com.google.mlkit.vision.demo.KEY_IMAGE_MAX_WIDTH"
private const val KEY_IMAGE_MAX_HEIGHT = "com.google.mlkit.vision.demo.KEY_IMAGE_MAX_HEIGHT"
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" private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
private const val REQUEST_CHOOSE_IMAGE = 1001
} }
} }

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="#C6C6CB"
android:pathData="M9.99999 18.3333C14.6024 18.3333 18.3333 14.6024 18.3333 10C18.3333 5.39762 14.6024 1.66667 9.99999 1.66667C5.39761 1.66667 1.66666 5.39762 1.66666 10C1.66666 14.6024 5.39761 18.3333 9.99999 18.3333Z"
android:strokeWidth="1.66667"
android:strokeColor="#C6C6CB"
android:strokeLineJoin="round" />
<path
android:pathData="M12.357 7.643L7.64291 12.357"
android:strokeWidth="1.66667"
android:strokeColor="@color/white"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:pathData="M7.64307 7.643L12.3571 12.357"
android:strokeWidth="1.66667"
android:strokeColor="@color/white"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main" android:id="@+id/main"
@ -8,61 +8,60 @@
android:background="@color/main_text_ffffffff" android:background="@color/main_text_ffffffff"
tools:context=".viewui.HistoryActivity"> tools:context=".viewui.HistoryActivity">
<ImageView <ImageView
android:layout_width="44dp" android:layout_width="wrap_content"
android:layout_height="44dp" android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="16dp"
android:onClick="clickBack" android:onClick="clickBack"
android:padding="12dp" android:padding="16dp"
android:src="@drawable/ic_back" /> android:src="@drawable/ic_back"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout <ImageView
android:id="@+id/iv_func_trans"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:layout_marginEnd="76dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_func_trans"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="19dp"
android:text="@string/his_title"
android:textColor="@color/main_text_ff1f1724"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/histories"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="0dp"
android:layout_marginTop="13dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_func_trans" />
<ImageView <TextView
android:id="@+id/iv_func_trans" android:id="@+id/remove"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="15dp" android:layout_margin="18dp"
android:layout_marginEnd="76dp" android:drawableStart="@drawable/ic_remove"
android:src="@drawable/ic_close" android:drawablePadding="6dp"
app:layout_constraintEnd_toEndOf="parent" android:padding="12dp"
app:layout_constraintTop_toTopOf="parent" /> android:text="@string/his_delete"
android:textColor="#FFFF5F5F"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView </androidx.constraintlayout.widget.ConstraintLayout>
android:id="@+id/tv_func_trans"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:text="@string/main_dictionary"
android:textColor="@color/white"
app:layout_constraintBottom_toBottomOf="@id/iv_func_trans"
app:layout_constraintEnd_toStartOf="@id/iv_func_trans"
app:layout_constraintTop_toTopOf="@id/iv_func_trans" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/histories"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHeight_percent="0.9"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/remove"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="18dp"
android:drawableStart="@drawable/ic_remove"
android:drawablePadding="6dp"
android:padding="12dp"
android:textColor="#FFFF5F5F"
android:text="@string/his_delete"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -9,19 +9,42 @@
android:paddingTop="16dp" android:paddingTop="16dp"
tools:context=".viewui.LanguageChangeActivity"> tools:context=".viewui.LanguageChangeActivity">
<ImageView
android:id="@+id/iv_ch_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:src="@drawable/ic_back"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ch_title"
android:textColor="@color/main_text_ff1f1724"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/iv_ch_back"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/iv_ch_back" />
<LinearLayout <LinearLayout
android:id="@+id/change_language" android:id="@+id/change_language"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="23dp" android:layout_marginTop="13dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:elevation="2dp" android:elevation="2dp"
android:orientation="horizontal" android:orientation="horizontal"
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_toBottomOf="@id/iv_ch_back">
<TextView <TextView
android:id="@+id/tv_change_source" android:id="@+id/tv_change_source"
@ -31,11 +54,9 @@
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:textColor="@color/main_text_ff0e8ce8"
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" />
@ -85,6 +106,7 @@
app:layout_constraintTop_toBottomOf="@id/change_language"> app:layout_constraintTop_toBottomOf="@id/change_language">
<androidx.appcompat.widget.SearchView <androidx.appcompat.widget.SearchView
android:id="@+id/ch_search"
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

@ -146,7 +146,7 @@
android:id="@+id/tv_main_limit_text" android:id="@+id/tv_main_limit_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/main_limit_num" android:text="0/1800"
android:textColor="@color/main_text_ff0e8ce8" android:textColor="@color/main_text_ff0e8ce8"
android:textSize="12sp" /> android:textSize="12sp" />

View File

@ -228,7 +228,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:drawablePadding="12dp" android:drawablePadding="12dp"
android:text="1.11" android:text="1.0.1"
android:textColor="@color/main_text_ff1f1724" android:textColor="@color/main_text_ff1f1724"
android:textSize="14sp" android:textSize="14sp"
app:drawableEndCompat="@drawable/ic_next" /> app:drawableEndCompat="@drawable/ic_next" />

View File

@ -4,24 +4,25 @@
android:id="@+id/root" android:id="@+id/root"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/bg_53514c"
android:keepScreenOn="true"> android:keepScreenOn="true">
<androidx.camera.view.PreviewView <androidx.camera.view.PreviewView
android:id="@+id/photo_preview" android:id="@+id/photo_preview"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="wrap_content"
android:adjustViewBounds="true" android:adjustViewBounds="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/control" app:layout_constraintBottom_toTopOf="@id/control"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<ImageView <ImageView
android:id="@+id/preview" android:id="@+id/preview"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:adjustViewBounds="true" android:adjustViewBounds="true"
app:layout_constraintBaseline_toTopOf="@id/control" app:layout_constraintBottom_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" />
@ -30,6 +31,7 @@
android:id="@+id/graphic_overlay" android:id="@+id/graphic_overlay"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:background="@color/bg_40_000000"
app:layout_constraintBottom_toBottomOf="@id/preview" app:layout_constraintBottom_toBottomOf="@id/preview"
app:layout_constraintLeft_toLeftOf="@id/preview" app:layout_constraintLeft_toLeftOf="@id/preview"
app:layout_constraintRight_toRightOf="@id/preview" app:layout_constraintRight_toRightOf="@id/preview"
@ -46,7 +48,7 @@
<LinearLayout <LinearLayout
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="19dp"
android:orientation="horizontal"> android:orientation="horizontal">
<ImageView <ImageView
@ -115,16 +117,14 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1" android:layout_weight="1"
android:background="@drawable/button_r20_black_bg" android:background="@drawable/button_r20_99000000_bg"
android:drawablePadding="25dp" android:drawablePadding="25dp"
android:gravity="center" android:gravity="center"
android:maxLines="1" android:maxLines="1"
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingEnd="12dp" android:paddingEnd="12dp"
android:text="@string/text_source_language"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="16sp" android:textSize="16sp"
android:textStyle="bold"
app:drawableEndCompat="@drawable/ic_down_choose_white" /> app:drawableEndCompat="@drawable/ic_down_choose_white" />
<ImageView <ImageView
@ -134,7 +134,7 @@
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
android:layout_marginEnd="12dp" android:layout_marginEnd="12dp"
android:background="@drawable/button_r20_black_bg" android:background="@drawable/button_r20_99000000_bg"
android:paddingStart="12dp" android:paddingStart="12dp"
android:paddingTop="6dp" android:paddingTop="6dp"
android:paddingEnd="12dp" android:paddingEnd="12dp"
@ -146,16 +146,14 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1" android:layout_weight="1"
android:background="@drawable/button_r20_black_bg" android:background="@drawable/button_r20_99000000_bg"
android:drawablePadding="25dp" android:drawablePadding="25dp"
android:gravity="center" android:gravity="center"
android:maxLines="1" android:maxLines="1"
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingEnd="12dp" android:paddingEnd="12dp"
android:text="@string/text_target_language"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="16sp" android:textSize="16sp"
android:textStyle="bold"
app:drawableEndCompat="@drawable/ic_down_choose_white" /> app:drawableEndCompat="@drawable/ic_down_choose_white" />
</LinearLayout> </LinearLayout>

View File

@ -19,6 +19,18 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tr_title"
android:textColor="@color/main_text_ff1f1724"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/iv_tr_back"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/iv_tr_back" />
<LinearLayout <LinearLayout
android:id="@+id/ll_main_enter_text" android:id="@+id/ll_main_enter_text"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -59,8 +71,8 @@
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="12dp" android:padding="12dp"
android:src="@drawable/ic_close" /> android:src="@drawable/ic_tr_close" />
</LinearLayout> </LinearLayout>
<ImageView <ImageView

View File

@ -4,23 +4,29 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@android:color/transparent"> android:background="@android:color/transparent">
<ImageView
android:id="@+id/sus_preview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top"
android:adjustViewBounds="true" />
<com.assimilate.alltrans.curview.GraphicOverlay <com.assimilate.alltrans.curview.GraphicOverlay
android:id="@+id/sus_graphic_overlay" android:id="@+id/sus_graphic_overlay"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="bottom"
android:background="@color/bg_40_000000" />
/>
<ImageView
android:id="@+id/sus_preview"
android:layout_width="150dp"
android:layout_height="150dp"
android:adjustViewBounds="true" />
<ImageView <ImageView
android:id="@+id/sus_global_close"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end" android:layout_gravity="end"
android:padding="20dp" android:paddingStart="1dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:src="@drawable/ic_close" /> android:src="@drawable/ic_close" />
</FrameLayout> </FrameLayout>

View File

@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/sus_control_root"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/button_r20_black_bg" android:background="@drawable/button_r20_black_bg"
android:orientation="vertical"> android:orientation="vertical">
<!-- 第一行 --> <!-- 第一行 -->
<LinearLayout <LinearLayout
android:id="@+id/sus_ll1"
android:layout_width="180dp" android:layout_width="180dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
@ -46,6 +46,7 @@
</LinearLayout> </LinearLayout>
<!-- 第二行 --> <!-- 第二行 -->
<LinearLayout <LinearLayout
android:id="@+id/sus_ll2"
android:layout_width="180dp" android:layout_width="180dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
@ -84,6 +85,7 @@
</LinearLayout> </LinearLayout>
<!-- 分隔线 --> <!-- 分隔线 -->
<View <View
android:id="@+id/sus_view1"
android:layout_width="110dp" android:layout_width="110dp"
android:layout_height="5dp" android:layout_height="5dp"
android:layout_gravity="center" android:layout_gravity="center"
@ -103,7 +105,6 @@
<ImageView <ImageView
android:id="@+id/iv_sus_home" android:id="@+id/iv_sus_home"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
@ -121,5 +122,4 @@
android:gravity="center" android:gravity="center"
android:src="@drawable/sus_trans_move" /> android:src="@drawable/sus_trans_move" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -3,6 +3,8 @@
<color name="black">#FF000000</color> <color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color> <color name="white">#FFFFFFFF</color>
<!-- text_bg-->
<color name="bg_40_000000">#66000000</color>
<!--main_page--> <!--main_page-->
<color name="main_bg_fff9f9f9">#FFF9F9F9</color> <color name="main_bg_fff9f9f9">#FFF9F9F9</color>
<color name="main_bg_ffe2efff">#FFE2EFFF</color> <color name="main_bg_ffe2efff">#FFE2EFFF</color>

View File

@ -13,19 +13,25 @@
<string name="text_main_title">translate</string> <string name="text_main_title">translate</string>
<string name="text_source_language">Chinese</string> <string name="text_source_language">Chinese</string>
<string name="text_target_language">English</string> <string name="text_target_language">English</string>
<string name="main_text_enter">enter text</string> <string name="main_text_enter">Enter text</string>
<string name="main_paste_text">Paste</string> <string name="main_paste_text">Paste</string>
<string name="main_voice_to_text">Your device may not support speech-to-text.</string>
<string name="main_try_text">Translate</string> <string name="main_try_text">Translate</string>
<string name="main_quick_text">Quick Translate</string> <string name="main_quick_text">Quick Translate</string>
<string name="main_quick_set">settings</string> <string name="main_quick_set">settings</string>
<string name="main_photo_trans">Photo Translation</string> <string name="main_photo_trans">Photo Translation</string>
<string name="main_dictionary">Dictionary</string> <string name="main_dictionary">Dictionary</string>
<string name="main_limit_num">0/5000</string> <string name="main_limit_num">%1$d/1800</string>
<!-- change_page-->
<string name="ch_title">Languages</string>
<!--text_re_page--> <!--text_re_page-->
<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> <string name="tr_tts_error">Speech in this language is temporarily not supported.</string>
<string name="tr_title">Translator</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>
@ -38,6 +44,7 @@
<string name="quick_set_touming">透明度</string> <string name="quick_set_touming">透明度</string>
<string name="quick_set_touming_descri">悬浮球显示时的透明度</string> <string name="quick_set_touming_descri">悬浮球显示时的透明度</string>
<string name="quick_set_zd_time">自动折叠时间</string> <string name="quick_set_zd_time">自动折叠时间</string>
<string name="favor_title">Favorite</string>
<!-- sus_view--> <!-- sus_view-->
<string name="global_translation">Global Translation</string> <string name="global_translation">Global Translation</string>
<string name="copy_text">Copy Text</string> <string name="copy_text">Copy Text</string>
@ -45,6 +52,8 @@
<string name="district_translation">District Translation</string> <string name="district_translation">District Translation</string>
<!-- his_page--> <!-- his_page-->
<string name="his_delete">Delete</string> <string name="his_delete">Delete</string>
<string name="his_title">History record</string>
</resources> </resources>