diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index b268ef3..33dccf9 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -4,6 +4,14 @@ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2479661..8cc1855 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,7 +8,6 @@ - @@ -18,6 +17,7 @@ + = Build.VERSION_CODES.P) { diff --git a/app/src/main/java/com/assimilate/alltrans/adapters/LanguageAdapter.java b/app/src/main/java/com/assimilate/alltrans/adapters/LanguageAdapter.java index 9931619..455413b 100644 --- a/app/src/main/java/com/assimilate/alltrans/adapters/LanguageAdapter.java +++ b/app/src/main/java/com/assimilate/alltrans/adapters/LanguageAdapter.java @@ -5,6 +5,8 @@ import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.Filter; +import android.widget.Filterable; import android.widget.ImageView; import androidx.annotation.NonNull; @@ -17,24 +19,21 @@ import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; import java.util.ArrayList; +import java.util.List; -public class LanguageAdapter extends RecyclerView.Adapter { +public class LanguageAdapter extends RecyclerView.Adapter implements Filterable { private final Activity mActivity; private final ArrayList languages; + private final ArrayList filteredLanguages; private final OnClickListener listener; private final RequestOptions options; - private boolean sorting = false; - public LanguageAdapter(@NonNull final Activity activity, @NonNull final ArrayList languageList, final OnClickListener onClickListener) { this.mActivity = activity; - this.languages = new ArrayList(); + this.languages = new ArrayList<>(languageList); + this.filteredLanguages = new ArrayList<>(languageList); this.listener = onClickListener; - if (!languageList.isEmpty()) { - languages.addAll(languageList); - } - options = new RequestOptions() .placeholder(R.mipmap.ic_launcher) .error(R.mipmap.ic_launcher) @@ -49,9 +48,9 @@ public class LanguageAdapter extends RecyclerView.Adapter { + final int pos = holder.getAdapterPosition(); + if (listener != null) + listener.click(pos, currentLanguage); }); } @Override public int getItemCount() { - return languages.size(); + return filteredLanguages.size(); } private void drawImage(@NonNull final String url, @NonNull final ImageView view) { @@ -90,6 +86,37 @@ public class LanguageAdapter extends RecyclerView.Adapter 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) results.values); + notifyDataSetChanged(); + } + }; + } + public static class LanguageHolder extends RecyclerView.ViewHolder { public LanguageItemLayoutBinding mBinding; diff --git a/app/src/main/java/com/assimilate/alltrans/allservice/SusService.kt b/app/src/main/java/com/assimilate/alltrans/allservice/SusService.kt index ada6964..e30f3ab 100644 --- a/app/src/main/java/com/assimilate/alltrans/allservice/SusService.kt +++ b/app/src/main/java/com/assimilate/alltrans/allservice/SusService.kt @@ -1,34 +1,47 @@ 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.Intent import android.graphics.Bitmap import android.graphics.PixelFormat import android.hardware.display.DisplayManager import android.hardware.display.VirtualDisplay +import android.media.Image import android.media.ImageReader import android.media.projection.MediaProjection 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.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 com.assimilate.alltrans.R import com.assimilate.alltrans.common.TextRecognitionProcessor import com.assimilate.alltrans.common.VisionImageProcessor -import com.assimilate.alltrans.databinding.SusControlViewBinding import com.assimilate.alltrans.databinding.LayoutSusGlobalBinding +import com.assimilate.alltrans.databinding.SusControlViewBinding import com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions import java.io.IOException class SusService : Service() { private var imageProcessor: VisionImageProcessor? = null private lateinit var mediaProjectionManager: MediaProjectionManager - private lateinit var mediaProjection: MediaProjection - private lateinit var virtualDisplay: VirtualDisplay + private var mediaProjection: MediaProjection? = null + private var virtualDisplay: VirtualDisplay? = null private lateinit var displayMetrics: DisplayMetrics + private lateinit var imageReader: ImageReader private lateinit var windowManager: WindowManager private lateinit var floatingView: View @@ -37,34 +50,79 @@ class SusService : Service() { private lateinit var bindingSusControl: SusControlViewBinding 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? { 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 { - val resultCode = intent?.getIntExtra("resultCode", Activity.RESULT_OK) - val data: Intent? = intent?.getParcelableExtra("data") - if (resultCode != null && data != null) { - startScreenshot(resultCode, data) + if (intent == null) { + return START_NOT_STICKY } + + val resultCode = intent.getIntExtra(mpResultCode, Activity.RESULT_CANCELED) + val data = intent.getParcelableExtra(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 } - private fun initSusView() { + override fun onCreate() { + super.onCreate() + displayMetrics = DisplayMetrics() 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)) floatingView = bindingSusControl.root @@ -85,31 +143,7 @@ class SusService : Service() { windowManager.addView(floatingView, layoutParams) - // 设置点击事件 - bindingSusControl.tvSusGlobal.setOnClickListener { - // 处理全局翻译点击事件 - addGlobalView() - } - - bindingSusControl.tvSusCopy.setOnClickListener { - // 处理复制文本点击事件 - } - - bindingSusControl.tvSusPhoto.setOnClickListener { - // 处理照片翻译点击事件 - } - - bindingSusControl.tvSusDistrict.setOnClickListener { - // 处理地区翻译点击事件 - } - - bindingSusControl.ivSusHome.setOnClickListener { - // 处理返回主页点击事件 - } - - bindingSusControl.ivSusMove.setOnClickListener { - // 处理移动窗口点击事件 - } + initControlClick() bindingSusControl.ivSusMove.setOnTouchListener(object : View.OnTouchListener { private var startX = 0f @@ -122,9 +156,9 @@ class SusService : Service() { MotionEvent.ACTION_DOWN -> { startX = layoutParams.x.toFloat() startY = layoutParams.y.toFloat() - touchX = event.rawX touchY = event.rawY + handler.removeCallbacks(hideRunnable) return true } @@ -135,26 +169,78 @@ class SusService : Service() { 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 } }) + + 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() { - windowManager = getSystemService(WINDOW_SERVICE) as WindowManager - bindingSubGlobal = LayoutSusGlobalBinding.inflate(LayoutInflater.from(this)) globalView = bindingSubGlobal.root - imageProcessor = TextRecognitionProcessor( - this, - ChineseTextRecognizerOptions.Builder().build() - ) val layoutParams = WindowManager.LayoutParams( - WindowManager.LayoutParams.WRAP_CONTENT, - WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY } else { @@ -165,22 +251,113 @@ class SusService : Service() { ) layoutParams.gravity = Gravity.TOP or Gravity.LEFT layoutParams.x = 0 - layoutParams.y = 100 + layoutParams.y = 0 windowManager.addView(globalView, layoutParams) + + initGlobalClick() + } - override fun onDestroy() { - super.onDestroy() - if (floatingView.isAttachedToWindow) { - windowManager.removeView(floatingView) - } - if (::globalView.isInitialized && globalView.isAttachedToWindow) { + private fun initGlobalClick() { + bindingSubGlobal.susGlobalClose.setOnClickListener { 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 { + Log.d("SusService", "Creating notification") val notificationChannelId = "FOREGROUND_SERVICE_CHANNEL" if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -206,7 +383,6 @@ class SusService : Service() { private fun tryReloadAndDetectInImage(bitmap: Bitmap) { try { - // Clear the overlay first bindingSubGlobal.susGraphicOverlay.clear() if (imageProcessor != null) { @@ -223,53 +399,20 @@ class SusService : Service() { ) } } catch (e: IOException) { - Log.e("SusService", "Error retrieving saved image") + Log.e("SusService", "Error retrieving saved image", e) } } - fun startScreenshot(resultCode: Int, data: Intent?) { - mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data!!) - val imageReader = ImageReader.newInstance( - displayMetrics.widthPixels, - displayMetrics.heightPixels, - PixelFormat.RGBA_8888, - 2 - ) - - virtualDisplay = mediaProjection.createVirtualDisplay( - "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) + override fun onDestroy() { + super.onDestroy() + Log.d("SusService", "Service onDestroy") + if (floatingView.isAttachedToWindow) { + windowManager.removeView(floatingView) + } + if (::globalView.isInitialized && globalView.isAttachedToWindow) { + windowManager.removeView(globalView) + } + stopProjection() } + } diff --git a/app/src/main/java/com/assimilate/alltrans/common/LanguagesConstants.java b/app/src/main/java/com/assimilate/alltrans/common/LanguagesConstants.java index 458e699..c53eac1 100644 --- a/app/src/main/java/com/assimilate/alltrans/common/LanguagesConstants.java +++ b/app/src/main/java/com/assimilate/alltrans/common/LanguagesConstants.java @@ -62,6 +62,11 @@ public class LanguagesConstants { // 根据语言代码获取 Language 对象 public Language getLanguageByLanguageCode(@NonNull String languageCode, @NonNull Context context) { ArrayList languages = getList(context); + + if (languageCode.equals("zh")) { + languageCode = "zh_CN"; + } + for (Language lang : languages) { if (lang.getLanguageCode().equalsIgnoreCase(languageCode)) { return lang; diff --git a/app/src/main/java/com/assimilate/alltrans/common/TextGraphic.kt b/app/src/main/java/com/assimilate/alltrans/common/TextGraphic.kt index 8433f2b..0a856a6 100644 --- a/app/src/main/java/com/assimilate/alltrans/common/TextGraphic.kt +++ b/app/src/main/java/com/assimilate/alltrans/common/TextGraphic.kt @@ -4,8 +4,11 @@ import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.RectF +import android.graphics.Typeface import android.os.Handler import android.os.Looper +import android.text.Layout +import android.text.StaticLayout import android.text.TextPaint import android.util.Log 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.Translator import com.google.mlkit.vision.text.Text -import java.util.Arrays +import java.util.concurrent.Executors import kotlin.math.max import kotlin.math.min @@ -27,8 +30,10 @@ class TextGraphic( private val rectPaint: Paint = Paint() private val textPaint: TextPaint + private val labelPaint: Paint private val handler = Handler(Looper.getMainLooper()) + private val executor = Executors.newSingleThreadExecutor() init { prepareTranslation() @@ -37,6 +42,9 @@ class TextGraphic( rectPaint.strokeWidth = STROKE_WIDTH textPaint = TextPaint() textPaint.color = TEXT_COLOR + textPaint.textSize = TEXT_SIZE + textPaint.typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD) + textPaint.isFakeBoldText = true labelPaint = Paint() labelPaint.color = MARKER_COLOR labelPaint.style = Paint.Style.FILL @@ -48,7 +56,10 @@ class TextGraphic( // Method to prepare translation before drawing private fun prepareTranslation() { - Thread { + + + executor.execute { + val textToTranslate = StringBuilder() // Collect all text to be translated and append delimiter @@ -64,13 +75,22 @@ class TextGraphic( PreferenceLanguageUtils.getString("language_target"), MyApp.applicationContext() ) + + if (lanTargetCode == null || lanSourceCode == null || textToTranslate.toString() + .isEmpty() + ) { + return@execute + } + // Define translation parameters val param = HashMap().apply { put("sourceLanguage", lanSourceCode) put("translationLanguage", lanTargetCode) put("text", textToTranslate.toString()) } - + Log.d("fdasfas_sou", lanSourceCode) + Log.d("fdasfas_tar", lanTargetCode) + Log.d("fdasfas_tex", textToTranslate.toString()) val translator: Translator = GoogleTranslator() @@ -78,27 +98,23 @@ class TextGraphic( translator.translate(param, GoogleTranslator.GoogleTranslateCallback { translatedText -> // Split translated text by delimiter translatedTextBlocks = - translatedText.split(DELIMITER.toRegex()).dropLastWhile { it.isEmpty() } + translatedText.split(DELIMITER.toRegex()).filter { it.isNotEmpty() } // Update UI thread handler.post { - postInvalidate() // Notify to redraw + postInvalidate() } }) - }.start() + } } override fun draw(canvas: Canvas) { - Log.d(TAG, "Text is: " + text.text) + for ((translatedIndex, textBlock) in text.textBlocks.withIndex()) { val translatedBlockText = if (translatedIndex < translatedTextBlocks.size) translatedTextBlocks[translatedIndex] else textBlock.text - val height1 = ((textBlock.boundingBox?.bottom?.toFloat() - ?: 30f) - (textBlock.boundingBox?.top?.toFloat() - ?: 0f)) / textBlock.lines.size - if (shouldGroupTextInBlocks) { drawText( getFormattedText( @@ -107,53 +123,16 @@ class TextGraphic( confidence = null ), RectF(textBlock.boundingBox), - height1 - 3 * STROKE_WIDTH, canvas ) - } else { - for (line in textBlock.lines) { - Log.d(TAG, "Line text is: " + line.text) - Log.d(TAG, "Line boundingbox is: " + line.boundingBox) - Log.d(TAG, "Line cornerpoint is: " + Arrays.toString(line.cornerPoints)) - Log.d(TAG, "Line confidence is: " + line.confidence) - Log.d(TAG, "Line angle is: " + line.angle) - // 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 + Log.d( + "fdgfsdfsdfas", getFormattedText( + translatedBlockText, + textBlock.recognizedLanguage, + confidence = null ) - 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 } - 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 x1 = translateX(rect.right) rect.left = min(x0, x1) rect.right = max(x0, x1) rect.top = translateY(rect.top) rect.bottom = translateY(rect.bottom) + canvas.drawRect(rect, 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 - val totalTextHeight = textPaint.fontMetrics.descent - textPaint.fontMetrics.ascent - val totalTextHeightWithSpacing = totalTextHeight * lines.size - - // Adjust the text size if the total height is greater than the rectangle height - if (totalTextHeightWithSpacing > rect.height()) { - textPaint.textSize *= rect.height() / totalTextHeightWithSpacing - lines = wrapText(text.trim(), rect.width()) - } else if (totalTextHeightWithSpacing < rect.height()) { - // If the total text height is less than the rectangle height, increase text size - textPaint.textSize *= rect.height() / totalTextHeightWithSpacing - lines = wrapText(text.trim(), rect.width()) + if (textLayout.height <= availableHeight) { + break + } + textSize -= 1 } - // Calculate new total height with adjusted text size - val finalTextHeight = textPaint.fontMetrics.descent - textPaint.fontMetrics.ascent - val finalTotalTextHeightWithSpacing = finalTextHeight * lines.size + // 使用 StaticLayout 绘制文本 + textLayout = + 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 - var textY = - rect.top + ((rect.height() - finalTotalTextHeightWithSpacing) / 2) - textPaint.fontMetrics.ascent - - // 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 { - val lines = mutableListOf() - 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 + canvas.save() + canvas.translate(rect.left, rect.top) + textLayout.draw(canvas) + canvas.restore() } companion object { - private const val DELIMITER = "`0_.._0`" + private const val DELIMITER = "0`~`0" private const val TAG = "TextGraphic" private const val TEXT_WITH_LANGUAGE_TAG_FORMAT = "%s:%s" private val TEXT_COLOR = Color.parseColor("#FF474747") private val MARKER_COLOR = Color.parseColor("#FFD9D9D9") private const val STROKE_WIDTH = 2.0f + private const val TEXT_SIZE = 44.0f } } diff --git a/app/src/main/java/com/assimilate/alltrans/common/TextRecognitionProcessor.kt b/app/src/main/java/com/assimilate/alltrans/common/TextRecognitionProcessor.kt index ac1e6f4..df2b24e 100644 --- a/app/src/main/java/com/assimilate/alltrans/common/TextRecognitionProcessor.kt +++ b/app/src/main/java/com/assimilate/alltrans/common/TextRecognitionProcessor.kt @@ -1,13 +1,9 @@ - package com.assimilate.alltrans.common import android.content.Context -import android.text.TextUtils import android.util.Log import com.assimilate.alltrans.MyApp import com.assimilate.alltrans.curview.GraphicOverlay -import com.assimilate.alltrans.http.GoogleTranslator -import com.assimilate.alltrans.http.Translator import com.google.android.gms.tasks.Task import com.google.mlkit.vision.common.InputImage import com.google.mlkit.vision.text.Text @@ -17,99 +13,134 @@ import com.google.mlkit.vision.text.TextRecognizerOptionsInterface /** Processor for the text detector demo. */ class TextRecognitionProcessor( - private val context: Context, - textRecognizerOptions: TextRecognizerOptionsInterface + private val context: Context, + textRecognizerOptions: TextRecognizerOptionsInterface ) : VisionProcessorBase(context) { - private val textRecognizer: TextRecognizer = TextRecognition.getClient(textRecognizerOptions) - private val shouldGroupRecognizedTextInBlocks: Boolean = - PreferenceUtils.shouldGroupRecognizedTextInBlocks(context) - private val showLanguageTag: Boolean = PreferenceUtils.showLanguageTag(context) - private val showConfidence: Boolean = PreferenceUtils.shouldShowTextConfidence(context) + private val textRecognizer: TextRecognizer = TextRecognition.getClient(textRecognizerOptions) + private val shouldGroupRecognizedTextInBlocks: Boolean = + PreferenceUtils.shouldGroupRecognizedTextInBlocks(context) + private val showLanguageTag: Boolean = PreferenceUtils.showLanguageTag(context) + private val showConfidence: Boolean = PreferenceUtils.shouldShowTextConfidence(context) - override fun stop() { - super.stop() - textRecognizer.close() - } - - override fun detectInImage(image: InputImage): Task { - return textRecognizer.process(image) - } - - - - - override fun onSuccess(text: Text, graphicOverlay: GraphicOverlay) { - Log.d(TAG, "On-device Text detection successful") - logExtrasForTesting(text) - graphicOverlay.add( - TextGraphic( - graphicOverlay, - text, - shouldGroupRecognizedTextInBlocks, - showLanguageTag, - showConfidence - ) - ) - } - - override fun onFailure(e: Exception) { - Log.w(TAG, "Text detection failed.$e") - } - - companion object { - private const val TAG = "TextRecProcessor" - private fun logExtrasForTesting(text: Text?) { - if (text != null) { - 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) { - val lines = text.textBlocks[i].lines - Log.v( - MANUAL_TESTING_LOG, - String.format("Detected text block %d has %d lines", i, lines.size) - ) - for (j in lines.indices) { - 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] - 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 - ) - ) - } - } - } - } - } + override fun stop() { + super.stop() + textRecognizer.close() + } + + override fun detectInImage(image: InputImage): Task { + return textRecognizer.process(image) + } + + + override fun onSuccess(text: Text, graphicOverlay: GraphicOverlay) { + PreferenceLanguageUtils.putString("language_source", getMostFrequentLanguage(text)) + + Log.d(TAG, "On-device Text detection successful") + logExtrasForTesting(text) + graphicOverlay.add( + TextGraphic( + graphicOverlay, + text, + shouldGroupRecognizedTextInBlocks, + showLanguageTag, + showConfidence + ) + ) + } + + override fun onFailure(e: Exception) { + Log.w(TAG, "Text detection failed.$e") + } + + + // 推测最可能的语言 + private fun getMostFrequentLanguage(text: Text): String { + val languageCount = mutableMapOf() + for (textBlock in text.textBlocks) { + for (line in textBlock.lines) { + for (element in line.elements) { + val language = element.recognizedLanguage + if (language != "und-Latn") { + if (languageCount.containsKey(language)) { + languageCount[language] = languageCount[language]!! + 1 + } else { + languageCount[language] = 1 + } + } + } + } + } + + val maxCode = languageCount.maxByOrNull { it.value }?.key ?: "zh_CN" + + val lanByCode = LanguagesConstants.getInstance() + .getLanguageByLanguageCode(maxCode, MyApp.applicationContext()) + + + return if (lanByCode != null) { + lanByCode.language + } else { + "English" + } + } + + companion object { + private const val TAG = "TextRecProcessor" + private fun logExtrasForTesting(text: Text?) { + if (text != null) { + 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) { + val lines = text.textBlocks[i].lines + Log.v( + MANUAL_TESTING_LOG, + String.format("Detected text block %d has %d lines", i, lines.size) + ) + for (j in lines.indices) { + 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] + 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 + ) + ) + } + } + } + } + } + } } - } } diff --git a/app/src/main/java/com/assimilate/alltrans/curview/SusView.kt b/app/src/main/java/com/assimilate/alltrans/curview/SusView.kt index 10ba01c..39383db 100644 --- a/app/src/main/java/com/assimilate/alltrans/curview/SusView.kt +++ b/app/src/main/java/com/assimilate/alltrans/curview/SusView.kt @@ -14,7 +14,7 @@ class SusView : View.OnClickListener { override fun onClick(v: View?) { - TODO("Not yet implemented") + // TODO("Not yet implemented") } diff --git a/app/src/main/java/com/assimilate/alltrans/viewui/HistoryActivity.kt b/app/src/main/java/com/assimilate/alltrans/viewui/HistoryActivity.kt index 52d2358..fa94139 100644 --- a/app/src/main/java/com/assimilate/alltrans/viewui/HistoryActivity.kt +++ b/app/src/main/java/com/assimilate/alltrans/viewui/HistoryActivity.kt @@ -1,172 +1,166 @@ -package com.assimilate.alltrans.viewui; +package com.assimilate.alltrans.viewui -import android.content.Context; -import android.os.Bundle; -import android.speech.tts.TextToSpeech; -import android.view.View; +import android.content.Context +import android.os.Bundle +import android.speech.tts.TextToSpeech +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; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; -import androidx.recyclerview.widget.LinearLayoutManager; +class HistoryActivity : AppCompatActivity() { + private var tts: TextToSpeech? = null + private var binding: ActivityHistoryBinding? = null + private var ids: HashSet? = null // 通过id 删除数据库文件 + private var items: HashSet? = null // 通许index 删除界面上面的数据 + private var operationCollection = false -import com.assimilate.alltrans.R; -import com.assimilate.alltrans.adapters.TranslationAdapter; -import com.assimilate.alltrans.common.Widget; -import com.assimilate.alltrans.databinding.ActivityHistoryBinding; -import com.assimilate.alltrans.mydb.DbTranslation; -import com.assimilate.alltrans.mydb.Translations; + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + binding = ActivityHistoryBinding.inflate(layoutInflater) + setContentView(binding!!.root) + 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; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.Locale; + ids = HashSet() + items = HashSet() -public class HistoryActivity extends AppCompatActivity { - public final static String COMMAND = "remove"; - public final static String COMMAND_COLLECTION = "remove-collection"; - public final static String COMMAND_HISTORY = "remove-history"; - private TextToSpeech tts; - private ActivityHistoryBinding binding; - private HashSet ids; // 通过id 删除数据库文件 - private HashSet items; // 通许index 删除界面上面的数据 - private boolean operationCollection = false; + val extra = intent.getStringExtra(COMMAND) + operationCollection = COMMAND_COLLECTION == extra - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - 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; - }); + tts = TextToSpeech(this) { status -> + if (null != tts && TextToSpeech.SUCCESS == status) tts!!.setLanguage( + Locale.getDefault() + ) + } - ids = new HashSet<>(); - items = new HashSet<>(); - - 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 = new ArrayList<>(); - if (operationCollection) { + val translations = ArrayList() + if (!operationCollection) { // 查出收藏的翻译记录 - binding.tvFuncTrans.setText("Collect"); - binding.ivFuncTrans.setImageResource(R.drawable.ic_add); - ArrayList list = new DbTranslation(this).getTranslations(true); + binding!!.tvFuncTrans.text = getString(R.string.favor_title) + // binding.ivFuncTrans.setImageResource(R.drawable.ic_add); + val list = DbTranslation(this).getTranslations(true) if (null != list && !list.isEmpty()) { - translations.addAll(list); + translations.addAll(list) } } else { // 查出所有的翻译记录 - binding.tvFuncTrans.setText("History"); - binding.ivFuncTrans.setImageResource(R.drawable.ic_add); - ArrayList list = new DbTranslation(this).getTranslations(false); - if (null != list && !list.isEmpty()) { - translations.addAll(list); + binding!!.tvFuncTrans.text = getString(R.string.his_title) + // binding.ivFuncTrans.setImageResource(R.drawable.ic_add); + val list = DbTranslation(this).getTranslations(false) + if (null != list && list.isNotEmpty()) { + translations.addAll(list) } } - final TranslationAdapter adapter = new TranslationAdapter(translations, new TranslationAdapter.TranslationItemCallback() { - @Override - public void updateList(TranslationAdapter.Operation operation, long id, int position) { + val adapter = TranslationAdapter(translations, object : TranslationItemCallback { + override fun updateList( + operation: TranslationAdapter.Operation, + id: Long, + position: Int + ) { if (TranslationAdapter.Operation.ADD == operation) { - add(id, position); + add(id, position) } else if (TranslationAdapter.Operation.REMOVE == operation) { - remove(id, position); + remove(id, position) } - updateBtn(); + updateBtn() } - @Override - public void speech(String value) { + override fun speech(value: String) { if (null != tts - && TextToSpeech.LANG_NOT_SUPPORTED != tts.isLanguageAvailable(Locale.getDefault())) { - tts.speak(value, 0, null, null); + && TextToSpeech.LANG_NOT_SUPPORTED != tts!!.isLanguageAvailable(Locale.getDefault()) + ) { + tts!!.speak(value, 0, null, null) } } - private void add(long id, int index) { - ids.add(id); - items.add(index); + private fun add(id: Long, index: Int) { + ids!!.add(id) + items!!.add(index) } - private void remove(long id, int index) { - ids.remove(id); - items.remove(index); + private fun remove(id: Long, index: Int) { + ids!!.remove(id) + items!!.remove(index) } - private void updateBtn() { - if (ids.isEmpty()) { - binding.remove.setVisibility(View.INVISIBLE); + private fun updateBtn() { + if (ids!!.isEmpty()) { + binding!!.remove.visibility = View.INVISIBLE } 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() { - private DbTranslation dbTranslation; + binding!!.remove.setOnClickListener(object : View.OnClickListener { + private var dbTranslation: DbTranslation? = null - @Override - public void onClick(View v) { - if (ids.isEmpty()) { - Widget.makeToast(HistoryActivity.this, "Noting to remove."); - return; + override fun onClick(v: View) { + if (ids!!.isEmpty()) { + Widget.makeToast(this@HistoryActivity, "Noting to remove.") + return } - if (!ids.isEmpty()) { - ArrayList longArrayList = new ArrayList<>(ids); + if (!ids!!.isEmpty()) { + val longArrayList = ArrayList(ids) if (operationCollection) { - getDbTranslation(HistoryActivity.this).removeCollectTranslations(longArrayList); + getDbTranslation(this@HistoryActivity).removeCollectTranslations( + longArrayList + ) } else { - getDbTranslation(HistoryActivity.this).removeTranslations(longArrayList); + getDbTranslation(this@HistoryActivity).removeTranslations(longArrayList) } - ids.clear(); + ids!!.clear() } - if (!items.isEmpty()) { - ArrayList integerArrayList = new ArrayList<>(items); - Collections.sort(integerArrayList, new Comparator() { - @Override - public int compare(Integer o1, Integer o2) { - return o2.compareTo(o1); - } - }); - adapter.updateSet(integerArrayList); - items.clear(); + if (!items!!.isEmpty()) { + val integerArrayList = ArrayList(items) + Collections.sort(integerArrayList) { o1, o2 -> 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) { - dbTranslation = new DbTranslation(context); + dbTranslation = DbTranslation(context) } - return dbTranslation; + return dbTranslation!! } - }); + }) - binding.histories.setLayoutManager(layoutManager); - binding.histories.setAdapter(adapter); + binding!!.histories.layoutManager = layoutManager + binding!!.histories.adapter = adapter } - @Override - public void onBackPressed() { - super.onBackPressed(); + override fun onBackPressed() { + super.onBackPressed() } - public void clickBack(View view) { - onBackPressed(); + fun clickBack(view: View?) { + onBackPressed() + } + + companion object { + const val COMMAND: String = "remove" + const val COMMAND_COLLECTION: String = "remove_collection" + const val COMMAND_HISTORY: String = "remove_history" } } \ No newline at end of file diff --git a/app/src/main/java/com/assimilate/alltrans/viewui/LanguageChangeActivity.kt b/app/src/main/java/com/assimilate/alltrans/viewui/LanguageChangeActivity.kt index e497cac..81c6524 100644 --- a/app/src/main/java/com/assimilate/alltrans/viewui/LanguageChangeActivity.kt +++ b/app/src/main/java/com/assimilate/alltrans/viewui/LanguageChangeActivity.kt @@ -31,10 +31,26 @@ class LanguageChangeActivity : AppCompatActivity() { } initView() + initSearch() // 添加这一行 initList() 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() { binding.tvChangeSource.text = PreferenceLanguageUtils.getString("language_source") binding.tvChangeTarget.text = PreferenceLanguageUtils.getString("language_target") @@ -57,32 +73,38 @@ class LanguageChangeActivity : AppCompatActivity() { binding.tvChangeTarget.text = PreferenceLanguageUtils.getString("language_target") updateRecentLanguages() } - binding.listLanCommon5.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) + binding.listLanCommon5.layoutManager = + LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) binding.listLanCommon5.adapter = recentAdapter } } private fun initClick() { + binding.ivChBack.setOnClickListener { onBackPressed() } binding.tvChangeSource.setOnClickListener { lastTranslateLanguage = false + binding.tvChangeSource.setTextColor(getColor(R.color.main_text_ff0e8ce8)) + binding.tvChangeTarget.setTextColor(getColor(R.color.main_text_ff1f1724)) } binding.tvChangeTarget.setOnClickListener { 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() { @@ -101,8 +123,10 @@ class LanguageChangeActivity : AppCompatActivity() { binding.tvChangeTarget.text = PreferenceLanguageUtils.getString("language_target") updateRecentLanguages() } - binding.listLanguages.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) + binding.listLanguages.layoutManager = + LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) binding.listLanguages.adapter = adapter } } + } diff --git a/app/src/main/java/com/assimilate/alltrans/viewui/MainActivity.kt b/app/src/main/java/com/assimilate/alltrans/viewui/MainActivity.kt index 3f42926..1975bcd 100644 --- a/app/src/main/java/com/assimilate/alltrans/viewui/MainActivity.kt +++ b/app/src/main/java/com/assimilate/alltrans/viewui/MainActivity.kt @@ -7,8 +7,10 @@ import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.media.projection.MediaProjectionManager +import android.net.Uri import android.os.Build import android.os.Bundle +import android.provider.Settings import android.speech.RecognizerIntent import android.text.Editable import android.text.TextUtils @@ -23,7 +25,6 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.localbroadcastmanager.content.LocalBroadcastManager -import com.assimilate.alltrans.MyApp import com.assimilate.alltrans.R import com.assimilate.alltrans.allservice.SusService import com.assimilate.alltrans.common.LanguagesConstants @@ -33,16 +34,16 @@ import com.assimilate.alltrans.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { - private var launcher: ActivityResultLauncher? = null - private lateinit var binding: ActivityMainBinding private lateinit var lcm: LocalBroadcastManager 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?) { super.onCreate(savedInstanceState) @@ -59,7 +60,18 @@ class MainActivity : AppCompatActivity() { initClick() 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() { @@ -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?) { super.onActivityResult(requestCode, resultCode, data) - if (requestCode == REQUEST_CODE_SCREEN_CAPTURE && resultCode == Activity.RESULT_OK) { - startSusService(resultCode, data) + if (requestCode == REQUEST_CODE) { + 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() { binding.chSourceLanguage.text = PreferenceLanguageUtils.getString("language_source") binding.chTargetLanguage.text = PreferenceLanguageUtils.getString("language_target") @@ -120,6 +125,14 @@ class MainActivity : AppCompatActivity() { } 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的内容显示或隐藏粘贴按钮 if (s.isNullOrEmpty()) { binding.tvMainTrans.visibility = View.GONE @@ -127,6 +140,10 @@ class MainActivity : AppCompatActivity() { } else { 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 { + intent = Intent(this, HistoryActivity::class.java) + intent.putExtra("remove", "remove_collection") startActivity( - Intent(this, HistoryActivity::class.java) + intent ) } binding.llQuickSet.setOnClickListener { @@ -177,17 +196,12 @@ class MainActivity : AppCompatActivity() { ) } 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 { @@ -203,9 +217,6 @@ class MainActivity : AppCompatActivity() { binding.chSourceLanguage.text = currentTargetLanguage 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 } val intent = Intent(this, TextResultActivity::class.java) - // 将字符串数据添加到Intent中 intent.putExtra("source_text", binding.etText.text.toString()) startActivity(intent) binding.etText.text = null @@ -228,11 +238,11 @@ class MainActivity : AppCompatActivity() { val speechIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH) speechIntent.putExtra( RecognizerIntent.EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS, - 5000 + 4000 ) // 设置5秒的静默时间 speechIntent.putExtra( RecognizerIntent.EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS, - 5000 + 4000 ) // 设置5秒的可能完全静默时间 @@ -254,7 +264,7 @@ class MainActivity : AppCompatActivity() { try { launcher?.launch(speechIntent) } catch (ea: ActivityNotFoundException) { - Widget.makeToast(this, "Something went wrong.") + Widget.makeToast(this, getString(R.string.main_voice_to_text)) } } diff --git a/app/src/main/java/com/assimilate/alltrans/viewui/SettingsActivity.kt b/app/src/main/java/com/assimilate/alltrans/viewui/SettingsActivity.kt index 161ae97..b84f96c 100644 --- a/app/src/main/java/com/assimilate/alltrans/viewui/SettingsActivity.kt +++ b/app/src/main/java/com/assimilate/alltrans/viewui/SettingsActivity.kt @@ -1,5 +1,6 @@ package com.assimilate.alltrans.viewui +import android.content.Intent import android.os.Bundle import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity @@ -44,5 +45,11 @@ class SettingsActivity bottomSheetDialog.show() } + binding.llFavorite.setOnClickListener { + val intent = Intent(this, HistoryActivity::class.java) + intent.putExtra("remove", "remove_history") + + startActivity(intent) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/assimilate/alltrans/viewui/StillImageActivity.kt b/app/src/main/java/com/assimilate/alltrans/viewui/StillImageActivity.kt index 0b4aa45..c6a4b90 100644 --- a/app/src/main/java/com/assimilate/alltrans/viewui/StillImageActivity.kt +++ b/app/src/main/java/com/assimilate/alltrans/viewui/StillImageActivity.kt @@ -9,19 +9,23 @@ import android.content.pm.PackageManager import android.content.res.Configuration import android.graphics.Bitmap import android.net.Uri +import android.os.Build import android.os.Bundle import android.provider.MediaStore import android.util.Log import android.util.Pair +import android.view.MenuItem import android.view.View import android.view.ViewTreeObserver import android.widget.AdapterView -import android.widget.AdapterView.OnItemSelectedListener import android.widget.ArrayAdapter import android.widget.ImageView +import android.widget.PopupMenu +import android.widget.SearchView import android.widget.Spinner import android.widget.TextView import android.widget.Toast +import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import androidx.camera.core.CameraSelector import androidx.camera.core.ImageCapture @@ -31,14 +35,24 @@ import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.view.PreviewView import androidx.core.app.ActivityCompat 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.adapters.LanguageAdapter 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.VisionImageProcessor import com.assimilate.alltrans.common.Widget import com.assimilate.alltrans.curview.GraphicOverlay 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.mlkit.common.model.LocalModel import com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions import com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions 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.IOException import java.text.SimpleDateFormat +import java.util.ArrayList 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 class StillImageActivity : AppCompatActivity() { private var preview: ImageView? = null @@ -67,56 +84,120 @@ class StillImageActivity : AppCompatActivity() { private var isFlashOn = false private val REQUEST_CAMERA_PERMISSION = 100 + private lateinit var bottomSheetDialog: BottomSheetDialog + private var chooseLanguage :Boolean = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + enableEdgeToEdge() 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 + } - // 检查并请求相机权限 - if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) - != PackageManager.PERMISSION_GRANTED + Widget.makeSnackbar(this, "Photographing text for translation") + + 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(R.id.root) + rootView.viewTreeObserver.addOnGlobalLayoutListener(object : + ViewTreeObserver.OnGlobalLayoutListener { + override fun onGlobalLayout() { + rootView.viewTreeObserver.removeOnGlobalLayoutListener(this) + imageMaxWidth = rootView.width + imageMaxHeight = rootView.height - findViewById(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( this, arrayOf(Manifest.permission.CAMERA), 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(R.id.root) - rootView.viewTreeObserver.addOnGlobalLayoutListener( - object : ViewTreeObserver.OnGlobalLayoutListener { - override fun onGlobalLayout() { - rootView.viewTreeObserver.removeOnGlobalLayoutListener(this) - imageMaxWidth = rootView.width - imageMaxHeight = rootView.height - findViewById(R.id.control).height - if (SIZE_SCREEN == selectedSize) { - tryReloadAndDetectInImage() - } - } - } - ) - - // 初始化相机 - startCamera() - outputDirectory = getOutputDirectory() - initClick() } + private fun initView() { + findViewById(R.id.still_source_language).text = + PreferenceLanguageUtils.getString("language_source") + findViewById(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(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(R.id.list_languages)?.adapter as LanguageAdapter).filter.filter( + newText + ) + + return true + } + }) + + initList() + } + + private fun initList() { + val languages: ArrayList = 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(R.id.still_source_language).text = + PreferenceLanguageUtils.getString("language_source") + findViewById(R.id.still_target_language).text = + PreferenceLanguageUtils.getString("language_target") + } + bottomSheetDialog.findViewById(R.id.list_languages)?.layoutManager = + LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) + bottomSheetDialog.findViewById(R.id.list_languages)?.adapter = adapter + } + } + + + private fun initClick() { findViewById(R.id.iv_still_pic).setOnClickListener { startChooseImageIntentForResult() } findViewById(R.id.iv_still_take).setOnClickListener { takePhoto() } @@ -125,46 +206,40 @@ class StillImageActivity : AppCompatActivity() { toggleFlash() updateFlashButtonUI() } - findViewById(R.id.still_source_language).setOnClickListener { } - findViewById(R.id.still_target_language).setOnClickListener { } - findViewById(R.id.still_exChange).setOnClickListener { } + findViewById(R.id.still_source_language).setOnClickListener { + chooseLanguage = false + bottomSheetDialog.show() + } + findViewById(R.id.still_target_language).setOnClickListener { + chooseLanguage = true + bottomSheetDialog.show() + } + findViewById(R.id.still_exChange).setOnClickListener { + } } private fun toggleFlash() { - if (isFlashOn) { - imageCapture.flashMode = ImageCapture.FLASH_MODE_OFF - } else { - imageCapture.flashMode = ImageCapture.FLASH_MODE_ON - } + imageCapture.flashMode = + if (isFlashOn) ImageCapture.FLASH_MODE_OFF else ImageCapture.FLASH_MODE_ON isFlashOn = !isFlashOn } private fun updateFlashButtonUI() { - if (isFlashOn) { - findViewById(R.id.iv_still_buling).setImageResource(R.drawable.ic_still_bulibuli) - } else { - findViewById(R.id.iv_still_buling).setImageResource(R.drawable.ic_still_notbuli) - } + findViewById(R.id.iv_still_buling).setImageResource(if (isFlashOn) R.drawable.ic_still_bulibuli else R.drawable.ic_still_notbuli) } - private fun startCamera() { val previewView = findViewById(R.id.photo_preview) cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener(Runnable { val cameraProvider = cameraProviderFuture.get() - val preview = Preview.Builder() - .build() - .also { - it.setSurfaceProvider(previewView.surfaceProvider) - } - - imageCapture = ImageCapture.Builder() - .setFlashMode(ImageCapture.FLASH_MODE_OFF) // 默认关闭闪光灯 - .build() + val preview = Preview.Builder().build().also { + it.setSurfaceProvider(previewView.surfaceProvider) + } + imageCapture = ImageCapture.Builder().setFlashMode(ImageCapture.FLASH_MODE_OFF).build() val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA try { @@ -177,14 +252,10 @@ class StillImageActivity : AppCompatActivity() { } private fun takePhoto() { - - val imageCapture = imageCapture - val photoFile = File( outputDirectory, SimpleDateFormat(FILENAME_FORMAT, Locale.US).format(System.currentTimeMillis()) + ".jpg" ) - val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() imageCapture.takePicture( @@ -196,25 +267,20 @@ class StillImageActivity : AppCompatActivity() { } override fun onImageSaved(output: ImageCapture.OutputFileResults) { - val savedUri = Uri.fromFile(photoFile) - imageUri = savedUri - val msg = "Photo capture succeeded: $savedUri" - Log.d(TAG, msg) + imageUri = Uri.fromFile(photoFile) + Log.d(TAG, "Photo capture succeeded: $imageUri") tryReloadAndDetectInImage() } - } - ) + }) } private fun getOutputDirectory(): File { val mediaDir = externalMediaDirs.firstOrNull()?.let { File(it, resources.getString(R.string.app_name)).apply { mkdirs() } } - return if (mediaDir != null && mediaDir.exists()) - mediaDir else filesDir + return if (mediaDir != null && mediaDir.exists()) mediaDir else filesDir } - override fun onRequestPermissionsResult( requestCode: Int, permissions: Array, @@ -224,80 +290,43 @@ class StillImageActivity : AppCompatActivity() { when (requestCode) { REQUEST_CAMERA_PERMISSION -> { if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { - // Permission is granted, proceed with camera - startCameraIntentForResult() + startCamera() } else { - // Permission denied, show a message to the user Toast.makeText( this, "Camera permission is required to use the camera", Toast.LENGTH_SHORT ).show() } - return } } } - - public override fun onResume() { + override fun onResume() { super.onResume() Log.d(TAG, "onResume") + + createImageProcessor() tryReloadAndDetectInImage() + + } - public override fun onPause() { + override fun onPause() { super.onPause() - imageProcessor?.run { this.stop() } + imageProcessor?.stop() } - public override fun onDestroy() { + override fun onDestroy() { super.onDestroy() - - imageProcessor?.run { this.stop() } - // 释放相机资源 + imageProcessor?.stop() if (::cameraProviderFuture.isInitialized) { cameraProviderFuture.get().unbindAll() } } - private fun populateFeatureSelector() { - val featureSpinner = findViewById(R.id.feature_selector) - val options: MutableList = 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) { + override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putParcelable(KEY_IMAGE_URI, imageUri) outState.putInt(KEY_IMAGE_MAX_WIDTH, imageMaxWidth) @@ -305,104 +334,71 @@ class StillImageActivity : AppCompatActivity() { 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 - if (ContextCompat.checkSelfPermission( - this, - Manifest.permission.CAMERA - ) != PackageManager.PERMISSION_GRANTED - ) { + "Hindi", "Marathi", "Nepali", "Sanskrit" -> TextRecognitionProcessor( + this, + DevanagariTextRecognizerOptions.Builder().build() + ) + + 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( - this, - "Camera permission is required to use the camera", - Toast.LENGTH_SHORT + applicationContext, + "Can not create image processor: " + e.message, + Toast.LENGTH_LONG ).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() { - Log.d(TAG, "Try reload and detect image") try { - if (imageUri == null) { - return - } - if (SIZE_SCREEN == selectedSize && imageMaxWidth == 0) { - // UI layout has not finished yet, will reload once it's ready. - return - } + if (imageUri == null) return + if (SIZE_SCREEN == selectedSize && imageMaxWidth == 0) return + val imageBitmap = BitmapUtils.getBitmapFromContentUri(contentResolver, imageUri) ?: return - // Clear the overlay first - graphicOverlay!!.clear() - val resizedBitmap: Bitmap - resizedBitmap = - if (selectedSize == SIZE_ORIGINAL) { - imageBitmap - } else { - // Get the dimensions of the image view - val targetedSize: Pair = 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 + if (selectedSize == SIZE_SCREEN) { + val targetedSize = targetedWidthHeight + val scaleFactor = max( + imageBitmap.width.toFloat() / targetedSize.first.toFloat(), + imageBitmap.height.toFloat() / targetedSize.second.toFloat() ) - imageProcessor!!.processBitmap(resizedBitmap, graphicOverlay) + val resizedBitmap = Bitmap.createScaledBitmap( + imageBitmap, + (imageBitmap.width / scaleFactor).toInt(), + (imageBitmap.height / scaleFactor).toInt(), + true + ) + preview?.setImageBitmap(resizedBitmap) + processImage(resizedBitmap) } else { - Log.e( - TAG, - "Null imageProcessor, please check adb logs for imageProcessor creation error" - ) + preview?.setImageBitmap(imageBitmap) + processImage(imageBitmap) } } catch (e: IOException) { Log.e(TAG, "Error retrieving saved image") @@ -412,95 +408,52 @@ class StillImageActivity : AppCompatActivity() { private val targetedWidthHeight: Pair get() { - val targetWidth: Int - val targetHeight: Int - when (selectedSize) { - SIZE_SCREEN -> { - targetWidth = imageMaxWidth - targetHeight = 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") - } + val targetWidth = if (isLandScape) max(imageMaxWidth, imageMaxHeight) else min( + imageMaxWidth, + imageMaxHeight + ) + val targetHeight = if (isLandScape) min(imageMaxWidth, imageMaxHeight) else max( + imageMaxWidth, + imageMaxHeight + ) return Pair(targetWidth, targetHeight) } - private fun createImageProcessor() { - try { - when (selectedMode) { - TEXT_RECOGNITION_LATIN -> - imageProcessor = - TextRecognitionProcessor(this, TextRecognizerOptions.Builder().build()) + private fun processImage(bitmap: Bitmap) { + graphicOverlay?.clear() + imageProcessor?.processBitmap(bitmap, graphicOverlay) + } - TEXT_RECOGNITION_CHINESE -> - imageProcessor = - TextRecognitionProcessor( - this, - ChineseTextRecognizerOptions.Builder().build() - ) + private fun startChooseImageIntentForResult() { + val intent = Intent().apply { + type = "image/*" + action = Intent.ACTION_GET_CONTENT + } + startActivityForResult(Intent.createChooser(intent, "Select Picture"), REQUEST_CHOOSE_IMAGE) + } - TEXT_RECOGNITION_DEVANAGARI -> - imageProcessor = - TextRecognitionProcessor( - this, - DevanagariTextRecognizerOptions.Builder().build() - ) - - 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() + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == REQUEST_CHOOSE_IMAGE && resultCode == Activity.RESULT_OK && data != null && data.data != null) { + imageUri = data.data + tryReloadAndDetectInImage() } } companion object { private const val TAG = "StillImageActivity" - private const val TEXT_RECOGNITION_LATIN = "Text Recognition Latin" - private const val TEXT_RECOGNITION_CHINESE = "Text Recognition Chinese" - private const val TEXT_RECOGNITION_DEVANAGARI = "Text Recognition Devanagari" - private const val TEXT_RECOGNITION_JAPANESE = "Text Recognition Japanese" - private const val TEXT_RECOGNITION_KOREAN = "Text Recognition Korean" - - private const val SIZE_SCREEN = "w:screen" // Match screen width - private const val SIZE_1024_768 = "w:1024" // ~1024*768 in a normal ratio - private const val SIZE_640_480 = "w:640" // ~640*480 in a normal ratio - private const val SIZE_ORIGINAL = "w:original" // Original image size - 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 KEY_IMAGE_URI = "com.google.mlkit.demo.stillImage.KEY_IMAGE_URI" + private const val KEY_IMAGE_MAX_WIDTH = + "com.google.mlkit.demo.stillImage.KEY_IMAGE_MAX_WIDTH" + private const val KEY_IMAGE_MAX_HEIGHT = + "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" + private const val TEXT_RECOGNITION_CHINESE = "Chinese, Simplified" + private const val TEXT_RECOGNITION_JAPANESE = "Japanese" + private const val TEXT_RECOGNITION_KOREAN = "Korean" private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" + private const val REQUEST_CHOOSE_IMAGE = 1001 } } + diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml deleted file mode 100644 index 08e87cc..0000000 --- a/app/src/main/res/drawable/ic_close.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/layout/activity_history.xml b/app/src/main/res/layout/activity_history.xml index 6f610d8..04fcaae 100644 --- a/app/src/main/res/layout/activity_history.xml +++ b/app/src/main/res/layout/activity_history.xml @@ -1,5 +1,5 @@ - + + android:padding="16dp" + android:src="@drawable/ic_back" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="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" /> - + - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_language_change.xml b/app/src/main/res/layout/activity_language_change.xml index a50a52c..b2cd7c1 100644 --- a/app/src/main/res/layout/activity_language_change.xml +++ b/app/src/main/res/layout/activity_language_change.xml @@ -9,19 +9,42 @@ android:paddingTop="16dp" tools:context=".viewui.LanguageChangeActivity"> + + + + + app:layout_constraintTop_toBottomOf="@id/iv_ch_back"> @@ -85,6 +106,7 @@ app:layout_constraintTop_toBottomOf="@id/change_language"> diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index 6ed939e..70beaed 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -228,7 +228,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:drawablePadding="12dp" - android:text="1.11" + android:text="1.0.1" android:textColor="@color/main_text_ff1f1724" android:textSize="14sp" app:drawableEndCompat="@drawable/ic_next" /> diff --git a/app/src/main/res/layout/activity_still_image.xml b/app/src/main/res/layout/activity_still_image.xml index 3790b1f..14f07b6 100755 --- a/app/src/main/res/layout/activity_still_image.xml +++ b/app/src/main/res/layout/activity_still_image.xml @@ -4,24 +4,25 @@ android:id="@+id/root" android:layout_width="match_parent" android:layout_height="match_parent" + android:background="@color/bg_53514c" android:keepScreenOn="true"> + app:layout_constraintTop_toTopOf="parent" /> @@ -30,6 +31,7 @@ android:id="@+id/graphic_overlay" android:layout_width="0dp" android:layout_height="0dp" + android:background="@color/bg_40_000000" app:layout_constraintBottom_toBottomOf="@id/preview" app:layout_constraintLeft_toLeftOf="@id/preview" app:layout_constraintRight_toRightOf="@id/preview" @@ -46,7 +48,7 @@ diff --git a/app/src/main/res/layout/activity_text_result.xml b/app/src/main/res/layout/activity_text_result.xml index e30f17c..b3acf7e 100644 --- a/app/src/main/res/layout/activity_text_result.xml +++ b/app/src/main/res/layout/activity_text_result.xml @@ -19,6 +19,18 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + android:padding="12dp" + android:src="@drawable/ic_tr_close" /> + + + - /> - - \ No newline at end of file diff --git a/app/src/main/res/layout/sus_control_view.xml b/app/src/main/res/layout/sus_control_view.xml index 06bbe68..5aaf019 100644 --- a/app/src/main/res/layout/sus_control_view.xml +++ b/app/src/main/res/layout/sus_control_view.xml @@ -1,14 +1,14 @@ - - diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index b576e5b..958195e 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,6 +3,8 @@ #FF000000 #FFFFFFFF + + #66000000 #FFF9F9F9 #FFE2EFFF diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e15a553..4a1fd72 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,19 +13,25 @@ translate Chinese English - enter text + Enter text Paste + Your device may not support speech-to-text. Translate Quick Translate settings Photo Translation Dictionary - 0/5000 + %1$d/1800 + + + Languages + New Translate Common other Speech in this language is temporarily not supported. + Translator Settings Languages @@ -38,6 +44,7 @@ 透明度 悬浮球显示时的透明度 自动折叠时间 + Favorite Global Translation Copy Text @@ -45,6 +52,8 @@ District Translation Delete + History record + \ No newline at end of file