Browse Source

refactor(android): 移除重复的悬浮窗和覆盖权限相关功能代码;(相关功能实现已经转移到本地插件floating_ui_plugin)

newdev_shunjiawei
lxm 9 months ago
parent
commit
12213cd7e1
  1. 133
      android/app/src/main/kotlin/com/saitong/voitrans/FloatingWindowPlugin.kt
  2. 378
      android/app/src/main/kotlin/com/saitong/voitrans/FloatingWindowService.kt
  3. 99
      android/app/src/main/kotlin/com/saitong/voitrans/OverlayPermissionPlugin.kt

133
android/app/src/main/kotlin/com/saitong/voitrans/FloatingWindowPlugin.kt

@ -1,133 +0,0 @@
package com.saitong.voitrans
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.provider.Settings
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
class FloatingWindowPlugin: FlutterPlugin, MethodCallHandler {
private lateinit var channel: MethodChannel
private lateinit var context: Context
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "floating_window")
channel.setMethodCallHandler(this)
// 设置回调通道
FloatingWindowService.methodChannel = channel
}
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"enableFloatingWindow" -> {
enableFloatingWindow(result)
}
"disableFloatingWindow" -> {
disableFloatingWindow(result)
}
"showFloatingWindow" -> {
showFloatingWindow(result)
}
"hideFloatingWindow" -> {
hideFloatingWindow(result)
}
"updateContent" -> {
updateContent(call, result)
}
"setPosition" -> {
setPosition(call, result)
}
else -> {
result.notImplemented()
}
}
}
private fun enableFloatingWindow(result: Result) {
try {
val intent = Intent(context, FloatingWindowService::class.java)
context.startService(intent)
result.success(true)
} catch (e: Exception) {
result.error("ENABLE_ERROR", "Failed to enable floating window: ${e.message}", null)
}
}
private fun disableFloatingWindow(result: Result) {
try {
val intent = Intent(context, FloatingWindowService::class.java)
context.stopService(intent)
result.success(true)
} catch (e: Exception) {
result.error("DISABLE_ERROR", "Failed to disable floating window: ${e.message}", null)
}
}
private fun showFloatingWindow(result: Result) {
try {
// 服务已经在运行,不需要额外操作
result.success(true)
} catch (e: Exception) {
result.error("SHOW_ERROR", "Failed to show floating window: ${e.message}", null)
}
}
private fun hideFloatingWindow(result: Result) {
try {
FloatingWindowService.serviceInstance?.stopSelf()
result.success(true)
} catch (e: Exception) {
result.error("HIDE_ERROR", "Failed to hide floating window: ${e.message}", null)
}
}
private fun updateContent(call: MethodCall, result: Result) {
try {
val args = call.arguments as? Map<String, Any>
if (args != null) {
val intent = Intent(context, FloatingWindowService::class.java).apply {
action = FloatingWindowService.ACTION_UPDATE_CONTENT
putExtra(FloatingWindowService.EXTRA_SOURCE_TEXT, args["sourceText"] as? String ?: "")
putExtra(FloatingWindowService.EXTRA_TRANSLATED_TEXT, args["translatedText"] as? String ?: "")
putExtra(FloatingWindowService.EXTRA_SOURCE_LANGUAGE, args["sourceLanguage"] as? String ?: "")
putExtra(FloatingWindowService.EXTRA_TARGET_LANGUAGE, args["targetLanguage"] as? String ?: "")
putExtra(FloatingWindowService.EXTRA_TIMESTAMP, (args["timestamp"] as? Number)?.toLong() ?: 0L)
putExtra(FloatingWindowService.EXTRA_IS_INTERMEDIATE, args["isIntermediate"] as? Boolean ?: false)
}
context.startService(intent)
}
result.success(true)
} catch (e: Exception) {
result.error("UPDATE_ERROR", "Failed to update content: ${e.message}", null)
}
}
private fun setPosition(call: MethodCall, result: Result) {
try {
val args = call.arguments as? Map<String, Any>
if (args != null) {
val intent = Intent(context, FloatingWindowService::class.java).apply {
action = FloatingWindowService.ACTION_SET_POSITION
putExtra(FloatingWindowService.EXTRA_X, (args["x"] as? Number)?.toInt() ?: 0)
putExtra(FloatingWindowService.EXTRA_Y, (args["y"] as? Number)?.toInt() ?: 0)
}
context.startService(intent)
}
result.success(true)
} catch (e: Exception) {
result.error("POSITION_ERROR", "Failed to set position: ${e.message}", null)
}
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
FloatingWindowService.methodChannel = null
}
}

378
android/app/src/main/kotlin/com/saitong/voitrans/FloatingWindowService.kt

@ -1,378 +0,0 @@
package com.saitong.voitrans
import android.app.Service
import android.content.Intent
import android.graphics.PixelFormat
import android.os.Build
import android.os.IBinder
import android.view.*
import android.widget.TextView
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.FrameLayout
import androidx.cardview.widget.CardView
import io.flutter.plugin.common.MethodChannel
import java.text.SimpleDateFormat
import java.util.*
class FloatingWindowService : Service() {
private var windowManager: WindowManager? = null
private var floatingView: View? = null
private var isMinimized = true
private lateinit var layoutParams: WindowManager.LayoutParams
private lateinit var sourceTextView: TextView
private lateinit var translatedTextView: TextView
private lateinit var languageInfoView: TextView
private lateinit var timestampView: TextView
private lateinit var minimizedView: CardView
private lateinit var expandedView: CardView
companion object {
var methodChannel: MethodChannel? = null
var serviceInstance: FloatingWindowService? = null
const val ACTION_UPDATE_CONTENT = "UPDATE_CONTENT"
const val ACTION_SET_POSITION = "SET_POSITION"
const val EXTRA_SOURCE_TEXT = "source_text"
const val EXTRA_TRANSLATED_TEXT = "translated_text"
const val EXTRA_SOURCE_LANGUAGE = "source_language"
const val EXTRA_TARGET_LANGUAGE = "target_language"
const val EXTRA_TIMESTAMP = "timestamp"
const val EXTRA_IS_INTERMEDIATE = "is_intermediate"
const val EXTRA_X = "x"
const val EXTRA_Y = "y"
}
override fun onCreate() {
super.onCreate()
serviceInstance = this
createFloatingWindow()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
ACTION_UPDATE_CONTENT -> updateContent(intent)
ACTION_SET_POSITION -> setPosition(intent)
}
return START_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
private fun createFloatingWindow() {
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
// 创建布局
floatingView = createFloatingLayout()
// 初始化视图组件
initViews()
// 设置点击事件
setupClickListeners()
// 设置窗口参数
layoutParams = WindowManager.LayoutParams().apply {
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
WindowManager.LayoutParams.TYPE_PHONE
}
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
format = PixelFormat.TRANSLUCENT
gravity = Gravity.TOP or Gravity.START
x = 100
y = 100
}
windowManager?.addView(floatingView, layoutParams)
// 默认显示最小化状态
updateViewState(true)
}
private fun createFloatingLayout(): View {
val context = this
val density = resources.displayMetrics.density
// 创建根布局
val rootLayout = FrameLayout(context).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
}
// 创建最小化视图
val minimizedCard = CardView(context).apply {
id = View.generateViewId()
layoutParams = FrameLayout.LayoutParams(
(60 * density).toInt(),
(60 * density).toInt()
).apply {
setMargins((8 * density).toInt(), (8 * density).toInt(), (8 * density).toInt(), (8 * density).toInt())
}
radius = 30 * density
cardElevation = 8 * density
setCardBackgroundColor(0xFFE3F2FD.toInt())
}
val minimizedContent = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
gravity = Gravity.CENTER
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
val icon = TextView(context).apply {
text = "译"
textSize = 14f
setTextColor(0xFF1976D2.toInt())
gravity = Gravity.CENTER
}
minimizedContent.addView(icon)
minimizedCard.addView(minimizedContent)
// 创建展开视图
val expandedCard = CardView(context).apply {
id = View.generateViewId()
layoutParams = FrameLayout.LayoutParams(
(320 * density).toInt(),
ViewGroup.LayoutParams.WRAP_CONTENT
).apply {
setMargins((8 * density).toInt(), (8 * density).toInt(), (8 * density).toInt(), (8 * density).toInt())
}
radius = 16 * density
cardElevation = 12 * density
setCardBackgroundColor(0xFFFFFFFF.toInt())
visibility = View.GONE
}
val expandedContent = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
setPadding(
(16 * density).toInt(),
(16 * density).toInt(),
(16 * density).toInt(),
(16 * density).toInt()
)
}
// 标题栏
val titleLayout = LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
}
val titleText = TextView(context).apply {
text = "实时翻译"
textSize = 16f
setTextColor(0xFF333333.toInt())
layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)
}
val closeButton = TextView(context).apply {
id = View.generateViewId()
text = "✕"
textSize = 18f
setTextColor(0xFF666666.toInt())
gravity = Gravity.CENTER
setPadding((8 * density).toInt(), (8 * density).toInt(), (8 * density).toInt(), (8 * density).toInt())
}
titleLayout.addView(titleText)
titleLayout.addView(closeButton)
// 语言信息
val languageInfo = TextView(context).apply {
id = View.generateViewId()
text = "中文 → 英文"
textSize = 12f
setTextColor(0xFF666666.toInt())
setPadding(0, (8 * density).toInt(), 0, (8 * density).toInt())
}
// 源文本
val sourceText = TextView(context).apply {
id = View.generateViewId()
text = "正在识别..."
textSize = 14f
setTextColor(0xFF333333.toInt())
setPadding(0, (8 * density).toInt(), 0, (4 * density).toInt())
maxLines = 3
}
// 翻译文本
val translatedText = TextView(context).apply {
id = View.generateViewId()
text = "翻译中..."
textSize = 14f
setTextColor(0xFF1976D2.toInt())
setPadding(0, (4 * density).toInt(), 0, (8 * density).toInt())
maxLines = 3
}
// 时间戳
val timestamp = TextView(context).apply {
id = View.generateViewId()
text = ""
textSize = 10f
setTextColor(0xFF999999.toInt())
gravity = Gravity.END
}
expandedContent.addView(titleLayout)
expandedContent.addView(languageInfo)
expandedContent.addView(sourceText)
expandedContent.addView(translatedText)
expandedContent.addView(timestamp)
expandedCard.addView(expandedContent)
rootLayout.addView(minimizedCard)
rootLayout.addView(expandedCard)
// 保存引用
minimizedView = minimizedCard
expandedView = expandedCard
sourceTextView = sourceText
translatedTextView = translatedText
languageInfoView = languageInfo
timestampView = timestamp
return rootLayout
}
private fun initViews() {
// Views are already initialized in createFloatingLayout
}
private fun setupClickListeners() {
// 长按关闭
floatingView!!.setOnLongClickListener {
closeFloatingWindow()
true
}
// 拖拽功能
setupDragListener()
}
private fun setupDragListener() {
var initialX = 0
var initialY = 0
var initialTouchX = 0f
var initialTouchY = 0f
var dragging = false
val threshold = (4 * resources.displayMetrics.density).toInt()
floatingView!!.setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
initialX = layoutParams.x
initialY = layoutParams.y
initialTouchX = event.rawX
initialTouchY = event.rawY
dragging = false
true
}
MotionEvent.ACTION_MOVE -> {
val dx = (event.rawX - initialTouchX).toInt()
val dy = (event.rawY - initialTouchY).toInt()
if (Math.abs(dx) > threshold || Math.abs(dy) > threshold) dragging = true
val dm = resources.displayMetrics
val maxX = dm.widthPixels - (floatingView?.width ?: 0)
val maxY = dm.heightPixels - (floatingView?.height ?: 0)
layoutParams.x = (initialX + dx).coerceIn(0, maxX)
layoutParams.y = (initialY + dy).coerceIn(0, maxY)
windowManager?.updateViewLayout(floatingView, layoutParams)
true
}
MotionEvent.ACTION_UP -> {
if (!dragging) {
isMinimized = !isMinimized
updateViewState(isMinimized)
notifyStateChange(if (isMinimized) "minimized" else "expanded")
}
dragging = false
true
}
else -> true
}
}
}
private fun updateViewState(minimized: Boolean) {
if (minimized) {
minimizedView.visibility = View.VISIBLE
expandedView.visibility = View.GONE
} else {
minimizedView.visibility = View.GONE
expandedView.visibility = View.VISIBLE
}
}
private fun updateContent(intent: Intent) {
val sourceText = intent.getStringExtra(EXTRA_SOURCE_TEXT) ?: ""
val translatedText = intent.getStringExtra(EXTRA_TRANSLATED_TEXT) ?: ""
val sourceLanguage = intent.getStringExtra(EXTRA_SOURCE_LANGUAGE) ?: ""
val targetLanguage = intent.getStringExtra(EXTRA_TARGET_LANGUAGE) ?: ""
val timestamp = intent.getLongExtra(EXTRA_TIMESTAMP, 0)
val isIntermediate = intent.getBooleanExtra(EXTRA_IS_INTERMEDIATE, false)
// 更新UI
sourceTextView.text = if (sourceText.isNotEmpty()) sourceText else "正在识别..."
translatedTextView.text = if (translatedText.isNotEmpty()) translatedText else "翻译中..."
languageInfoView.text = "$sourceLanguage → $targetLanguage"
if (timestamp > 0) {
val dateFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
timestampView.text = dateFormat.format(Date(timestamp))
}
// 如果是中间结果,可以添加一些视觉提示
if (isIntermediate) {
translatedTextView.alpha = 0.7f
} else {
translatedTextView.alpha = 1.0f
}
}
private fun setPosition(intent: Intent) {
val x = intent.getIntExtra(EXTRA_X, 0)
val y = intent.getIntExtra(EXTRA_Y, 0)
floatingView?.let {
layoutParams.x = x
layoutParams.y = y
windowManager?.updateViewLayout(it, layoutParams)
}
}
private fun closeFloatingWindow() {
stopSelf()
notifyFloatingWindowClosed()
}
private fun notifyStateChange(state: String) {
methodChannel?.invokeMethod("onFloatingWindowStateChanged", mapOf("state" to state))
}
private fun notifyFloatingWindowClosed() {
methodChannel?.invokeMethod("onFloatingWindowClosed", null)
}
override fun onDestroy() {
floatingView?.let { windowManager?.removeView(it) }
serviceInstance = null
methodChannel = null
super.onDestroy()
}
}

99
android/app/src/main/kotlin/com/saitong/voitrans/OverlayPermissionPlugin.kt

@ -1,99 +0,0 @@
package com.saitong.voitrans
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.content.pm.PackageManager
import android.provider.Settings
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
class OverlayPermissionPlugin: FlutterPlugin, MethodCallHandler {
private lateinit var channel: MethodChannel
private lateinit var context: Context
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "overlay_permission")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"hasOverlayPermission" -> {
result.success(hasOverlayPermission())
}
"requestOverlayPermission" -> {
requestOverlayPermission(result)
}
else -> {
result.notImplemented()
}
}
}
private fun hasOverlayPermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Settings.canDrawOverlays(context)
} else {
true
}
}
private fun requestOverlayPermission(result: Result) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (hasOverlayPermission()) {
result.success(true)
return
}
val openedBySystem = startActivitySafe(
Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION).apply {
data = Uri.parse("package:${context.packageName}")
}
)
if (openedBySystem) {
result.success(true)
return
}
val openedByAppDetails = startActivitySafe(
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.parse("package:${context.packageName}")
}
)
if (openedByAppDetails) {
result.success(true)
return
}
result.success(false)
} else {
result.success(true)
}
} catch (e: Exception) {
result.error("REQUEST_ERROR", "Failed to request overlay permission: ${e.message}", null)
}
}
private fun startActivitySafe(intent: Intent): Boolean {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val pm: PackageManager = context.packageManager
if (intent.resolveActivity(pm) == null) return false
return try {
context.startActivity(intent)
true
} catch (_: Exception) {
false
}
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}
Loading…
Cancel
Save