|
|
@ -4,10 +4,14 @@ import android.app.Service |
|
|
import android.content.Intent |
|
|
import android.content.Intent |
|
|
import android.graphics.Color |
|
|
import android.graphics.Color |
|
|
import android.graphics.PixelFormat |
|
|
import android.graphics.PixelFormat |
|
|
|
|
|
import android.graphics.Rect |
|
|
import android.graphics.Typeface |
|
|
import android.graphics.Typeface |
|
|
import android.graphics.drawable.GradientDrawable |
|
|
import android.graphics.drawable.GradientDrawable |
|
|
import android.os.Build |
|
|
import android.os.Build |
|
|
|
|
|
import android.os.Handler |
|
|
import android.os.IBinder |
|
|
import android.os.IBinder |
|
|
|
|
|
import android.os.Looper |
|
|
|
|
|
import android.provider.Settings |
|
|
import android.text.TextUtils |
|
|
import android.text.TextUtils |
|
|
import android.util.TypedValue |
|
|
import android.util.TypedValue |
|
|
import android.view.Gravity |
|
|
import android.view.Gravity |
|
|
@ -19,10 +23,8 @@ import android.widget.FrameLayout |
|
|
import android.widget.LinearLayout |
|
|
import android.widget.LinearLayout |
|
|
import android.widget.TextView |
|
|
import android.widget.TextView |
|
|
import io.flutter.plugin.common.MethodChannel |
|
|
import io.flutter.plugin.common.MethodChannel |
|
|
import java.text.SimpleDateFormat |
|
|
|
|
|
import java.util.Date |
|
|
|
|
|
import java.util.Locale |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// 悬浮窗口服务 |
|
|
class FloatingWindowService : Service() { |
|
|
class FloatingWindowService : Service() { |
|
|
companion object { |
|
|
companion object { |
|
|
const val ACTION_UPDATE_CONTENT = "UPDATE_CONTENT" |
|
|
const val ACTION_UPDATE_CONTENT = "UPDATE_CONTENT" |
|
|
@ -51,11 +53,27 @@ class FloatingWindowService : Service() { |
|
|
|
|
|
|
|
|
private var sourceTextView: TextView? = null |
|
|
private var sourceTextView: TextView? = null |
|
|
private var translatedTextView: TextView? = null |
|
|
private var translatedTextView: TextView? = null |
|
|
|
|
|
private var sourceLoadingView: TextView? = null |
|
|
|
|
|
private var translatedLoadingView: TextView? = null |
|
|
private var languageInfoView: TextView? = null |
|
|
private var languageInfoView: TextView? = null |
|
|
private var timestampView: TextView? = null |
|
|
|
|
|
private var minimizedView: View? = null |
|
|
private var minimizedView: View? = null |
|
|
private var expandedView: View? = null |
|
|
private var expandedView: View? = null |
|
|
|
|
|
private var minimizedTextView: TextView? = null |
|
|
private var closeButton: TextView? = null |
|
|
private var closeButton: TextView? = null |
|
|
|
|
|
private var expandedContainer: ViewGroup? = null |
|
|
|
|
|
private var expandedContentLayout: LinearLayout? = null |
|
|
|
|
|
private var resizeHandleView: View? = null |
|
|
|
|
|
|
|
|
|
|
|
private var baseExpandedWidth: Int = 0 |
|
|
|
|
|
private var lastEffectiveWidth: Int = 0 |
|
|
|
|
|
private var lastEffectiveHeight: Int = 0 |
|
|
|
|
|
private var uiScale: Float = 1.0f |
|
|
|
|
|
private val backgroundBaseColor: Int = Color.argb(150, 40, 40, 40) |
|
|
|
|
|
private val uiHandler: Handler = Handler(Looper.getMainLooper()) |
|
|
|
|
|
private var sourceDotsStep: Int = 0 |
|
|
|
|
|
private var translatedDotsStep: Int = 0 |
|
|
|
|
|
private var sourceDotsRunnable: Runnable? = null |
|
|
|
|
|
private var translatedDotsRunnable: Runnable? = null |
|
|
|
|
|
|
|
|
override fun onCreate() { |
|
|
override fun onCreate() { |
|
|
super.onCreate() |
|
|
super.onCreate() |
|
|
@ -76,14 +94,37 @@ class FloatingWindowService : Service() { |
|
|
override fun onBind(intent: Intent?): IBinder? = null |
|
|
override fun onBind(intent: Intent?): IBinder? = null |
|
|
|
|
|
|
|
|
override fun onDestroy() { |
|
|
override fun onDestroy() { |
|
|
|
|
|
stopAllLoading() |
|
|
|
|
|
uiHandler.removeCallbacksAndMessages(null) |
|
|
removeFloatingWindow() |
|
|
removeFloatingWindow() |
|
|
|
|
|
sourceTextView = null |
|
|
|
|
|
translatedTextView = null |
|
|
|
|
|
sourceLoadingView = null |
|
|
|
|
|
translatedLoadingView = null |
|
|
|
|
|
languageInfoView = null |
|
|
|
|
|
minimizedView = null |
|
|
|
|
|
expandedView = null |
|
|
|
|
|
minimizedTextView = null |
|
|
|
|
|
closeButton = null |
|
|
|
|
|
resizeHandleView = null |
|
|
|
|
|
expandedContentLayout = null |
|
|
|
|
|
expandedContainer = null |
|
|
|
|
|
layoutParams = null |
|
|
|
|
|
windowManager = null |
|
|
serviceInstance = null |
|
|
serviceInstance = null |
|
|
|
|
|
methodChannel = null |
|
|
super.onDestroy() |
|
|
super.onDestroy() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private fun createFloatingWindow() { |
|
|
private fun createFloatingWindow() { |
|
|
windowManager = getSystemService(WINDOW_SERVICE) as? WindowManager ?: return |
|
|
windowManager = getSystemService(WINDOW_SERVICE) as? WindowManager ?: return |
|
|
|
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) { |
|
|
|
|
|
notifyStateChanged("hidden") |
|
|
|
|
|
stopSelf() |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
floatingView = createFloatingLayout() |
|
|
floatingView = createFloatingLayout() |
|
|
initViews() |
|
|
initViews() |
|
|
setupClickListeners() |
|
|
setupClickListeners() |
|
|
@ -108,7 +149,16 @@ class FloatingWindowService : Service() { |
|
|
y = dp(100) |
|
|
y = dp(100) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
windowManager?.addView(floatingView, layoutParams) |
|
|
try { |
|
|
|
|
|
windowManager?.addView(floatingView, layoutParams) |
|
|
|
|
|
} catch (_: SecurityException) { |
|
|
|
|
|
notifyStateChanged("hidden") |
|
|
|
|
|
stopSelf() |
|
|
|
|
|
return |
|
|
|
|
|
} catch (_: Exception) { |
|
|
|
|
|
stopSelf() |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
updateViewState(true) |
|
|
updateViewState(true) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -129,52 +179,97 @@ class FloatingWindowService : Service() { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private fun createMinimizedView(): View { |
|
|
private fun createMinimizedView(): View { |
|
|
val container = FrameLayout(this) |
|
|
val container = LinearLayout(this).apply { |
|
|
val lp = FrameLayout.LayoutParams(dp(60), dp(60)) |
|
|
orientation = LinearLayout.HORIZONTAL |
|
|
|
|
|
gravity = Gravity.CENTER_VERTICAL |
|
|
|
|
|
} |
|
|
|
|
|
val lp = FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, dp(48)) |
|
|
val margin = dp(8) |
|
|
val margin = dp(8) |
|
|
lp.setMargins(margin, margin, margin, margin) |
|
|
lp.setMargins(margin, margin, margin, margin) |
|
|
container.layoutParams = lp |
|
|
container.layoutParams = lp |
|
|
|
|
|
container.setPadding(dp(10), dp(8), dp(12), dp(8)) |
|
|
|
|
|
container.minimumWidth = dp(160) |
|
|
|
|
|
|
|
|
val bg = GradientDrawable().apply { |
|
|
val bg = GradientDrawable().apply { |
|
|
setColor(0xFFE3F2FD.toInt()) |
|
|
setColor(backgroundBaseColor) |
|
|
cornerRadius = dp(30).toFloat() |
|
|
cornerRadius = dp(24).toFloat() |
|
|
|
|
|
setStroke(dp(1), Color.argb(46, 255, 255, 255)) |
|
|
} |
|
|
} |
|
|
container.background = bg |
|
|
container.background = bg |
|
|
container.elevation = dp(8).toFloat() |
|
|
container.elevation = dp(10).toFloat() |
|
|
|
|
|
|
|
|
|
|
|
val iconBg = GradientDrawable().apply { |
|
|
|
|
|
setColor(Color.argb(42, 255, 255, 255)) |
|
|
|
|
|
cornerRadius = dp(14).toFloat() |
|
|
|
|
|
} |
|
|
val icon = TextView(this).apply { |
|
|
val icon = TextView(this).apply { |
|
|
text = "译" |
|
|
text = "V" |
|
|
setTextColor(0xFF1976D2.toInt()) |
|
|
setTextColor(0xFFFFFFFF.toInt()) |
|
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f) |
|
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f) |
|
|
gravity = Gravity.CENTER |
|
|
gravity = Gravity.CENTER |
|
|
typeface = Typeface.DEFAULT_BOLD |
|
|
typeface = Typeface.DEFAULT_BOLD |
|
|
|
|
|
background = iconBg |
|
|
} |
|
|
} |
|
|
container.addView( |
|
|
|
|
|
icon, |
|
|
val iconLp = LinearLayout.LayoutParams(dp(28), dp(28)) |
|
|
FrameLayout.LayoutParams( |
|
|
container.addView(icon, iconLp) |
|
|
ViewGroup.LayoutParams.MATCH_PARENT, |
|
|
|
|
|
ViewGroup.LayoutParams.MATCH_PARENT |
|
|
minimizedTextView = TextView(this).apply { |
|
|
) |
|
|
text = "Voitrans" |
|
|
) |
|
|
setTextColor(Color.argb(235, 255, 255, 255)) |
|
|
|
|
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f) |
|
|
|
|
|
maxLines = 1 |
|
|
|
|
|
ellipsize = TextUtils.TruncateAt.END |
|
|
|
|
|
} |
|
|
|
|
|
val textLp = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f).apply { |
|
|
|
|
|
marginStart = dp(10) |
|
|
|
|
|
} |
|
|
|
|
|
container.addView(minimizedTextView, textLp) |
|
|
|
|
|
|
|
|
|
|
|
val chevron = TextView(this).apply { |
|
|
|
|
|
text = "›" |
|
|
|
|
|
setTextColor(Color.argb(150, 255, 255, 255)) |
|
|
|
|
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f) |
|
|
|
|
|
gravity = Gravity.CENTER |
|
|
|
|
|
setPadding(dp(6), 0, 0, 0) |
|
|
|
|
|
} |
|
|
|
|
|
container.addView(chevron) |
|
|
return container |
|
|
return container |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private fun createExpandedView(): View { |
|
|
private fun createExpandedView(): View { |
|
|
val container = LinearLayout(this) |
|
|
val root = FrameLayout(this) |
|
|
val lp = FrameLayout.LayoutParams(dp(320), ViewGroup.LayoutParams.WRAP_CONTENT) |
|
|
val maxWidth = dp(360) |
|
|
|
|
|
val minWidth = dp(280) |
|
|
|
|
|
val horizontalMargins = dp(8) * 2 |
|
|
|
|
|
val screenWidth = resources.displayMetrics.widthPixels |
|
|
|
|
|
val targetWidth = (screenWidth - horizontalMargins).coerceIn(minWidth, maxWidth) |
|
|
|
|
|
baseExpandedWidth = targetWidth |
|
|
|
|
|
val lp = FrameLayout.LayoutParams(targetWidth, ViewGroup.LayoutParams.WRAP_CONTENT) |
|
|
val margin = dp(8) |
|
|
val margin = dp(8) |
|
|
lp.setMargins(margin, margin, margin, margin) |
|
|
lp.setMargins(margin, margin, margin, margin) |
|
|
container.layoutParams = lp |
|
|
root.layoutParams = lp |
|
|
container.orientation = LinearLayout.VERTICAL |
|
|
root.setPadding(0, 0, 0, 0) |
|
|
container.setPadding(dp(16), dp(16), dp(16), dp(16)) |
|
|
root.visibility = View.GONE |
|
|
container.visibility = View.GONE |
|
|
root.elevation = dp(12).toFloat() |
|
|
container.elevation = dp(12).toFloat() |
|
|
expandedContainer = root |
|
|
|
|
|
|
|
|
val bg = GradientDrawable().apply { |
|
|
val bg = GradientDrawable().apply { |
|
|
setColor(Color.WHITE) |
|
|
setColor(backgroundBaseColor) |
|
|
cornerRadius = dp(16).toFloat() |
|
|
cornerRadius = dp(16).toFloat() |
|
|
|
|
|
setStroke(dp(1), Color.argb(46, 255, 255, 255)) |
|
|
} |
|
|
} |
|
|
container.background = bg |
|
|
root.background = bg |
|
|
|
|
|
|
|
|
|
|
|
val contentLayout = LinearLayout(this).apply { |
|
|
|
|
|
orientation = LinearLayout.VERTICAL |
|
|
|
|
|
setPadding(dp(16), dp(16), dp(16), dp(16)) |
|
|
|
|
|
layoutParams = FrameLayout.LayoutParams( |
|
|
|
|
|
ViewGroup.LayoutParams.MATCH_PARENT, |
|
|
|
|
|
ViewGroup.LayoutParams.WRAP_CONTENT |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
expandedContentLayout = contentLayout |
|
|
|
|
|
|
|
|
val titleRow = LinearLayout(this).apply { |
|
|
val titleRow = LinearLayout(this).apply { |
|
|
orientation = LinearLayout.HORIZONTAL |
|
|
orientation = LinearLayout.HORIZONTAL |
|
|
@ -185,63 +280,169 @@ class FloatingWindowService : Service() { |
|
|
) |
|
|
) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
val title = TextView(this).apply { |
|
|
|
|
|
text = "实时翻译" |
|
|
|
|
|
setTextColor(0xFF333333.toInt()) |
|
|
|
|
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f) |
|
|
|
|
|
typeface = Typeface.DEFAULT_BOLD |
|
|
|
|
|
layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
closeButton = TextView(this).apply { |
|
|
closeButton = TextView(this).apply { |
|
|
text = "✕" |
|
|
text = "✕" |
|
|
setTextColor(0xFF666666.toInt()) |
|
|
setTextColor(Color.argb(210, 255, 255, 255)) |
|
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f) |
|
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f) |
|
|
gravity = Gravity.CENTER |
|
|
gravity = Gravity.CENTER |
|
|
setPadding(dp(8), dp(8), dp(8), dp(8)) |
|
|
setPadding(dp(8), dp(8), dp(8), dp(8)) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
titleRow.addView(title) |
|
|
|
|
|
titleRow.addView(closeButton) |
|
|
|
|
|
|
|
|
|
|
|
languageInfoView = TextView(this).apply { |
|
|
languageInfoView = TextView(this).apply { |
|
|
text = "中文 → 英文" |
|
|
text = "src → tgt" |
|
|
setTextColor(0xFF666666.toInt()) |
|
|
setTextColor(Color.argb(235, 255, 255, 255)) |
|
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f) |
|
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f) |
|
|
setPadding(0, dp(8), 0, dp(8)) |
|
|
setPadding(dp(10), dp(6), dp(10), dp(6)) |
|
|
|
|
|
} |
|
|
|
|
|
val langBg = GradientDrawable().apply { |
|
|
|
|
|
setColor(Color.argb(36, 255, 255, 255)) |
|
|
|
|
|
cornerRadius = dp(10).toFloat() |
|
|
} |
|
|
} |
|
|
|
|
|
languageInfoView?.background = langBg |
|
|
|
|
|
val languageInRowLp = LinearLayout.LayoutParams( |
|
|
|
|
|
ViewGroup.LayoutParams.WRAP_CONTENT, |
|
|
|
|
|
ViewGroup.LayoutParams.WRAP_CONTENT |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
sourceTextView = TextView(this).apply { |
|
|
sourceTextView = TextView(this).apply { |
|
|
text = "正在识别..." |
|
|
text = "" |
|
|
setTextColor(0xFF333333.toInt()) |
|
|
setTextColor(Color.argb(235, 255, 255, 255)) |
|
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f) |
|
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f) |
|
|
setPadding(0, dp(8), 0, dp(4)) |
|
|
setPadding(0, dp(8), 0, dp(4)) |
|
|
maxLines = 3 |
|
|
maxLines = 3 |
|
|
ellipsize = TextUtils.TruncateAt.END |
|
|
ellipsize = TextUtils.TruncateAt.END |
|
|
|
|
|
setLineSpacing(dpF(2f), 1.1f) |
|
|
|
|
|
visibility = View.GONE |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
translatedTextView = TextView(this).apply { |
|
|
translatedTextView = TextView(this).apply { |
|
|
text = "翻译中..." |
|
|
text = "" |
|
|
setTextColor(0xFF1976D2.toInt()) |
|
|
setTextColor(0xFF90CAF9.toInt()) |
|
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f) |
|
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 15f) |
|
|
setPadding(0, dp(4), 0, dp(8)) |
|
|
setPadding(0, dp(4), 0, dp(8)) |
|
|
maxLines = 3 |
|
|
maxLines = 3 |
|
|
ellipsize = TextUtils.TruncateAt.END |
|
|
ellipsize = TextUtils.TruncateAt.END |
|
|
|
|
|
setLineSpacing(dpF(2f), 1.1f) |
|
|
|
|
|
visibility = View.GONE |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
timestampView = TextView(this).apply { |
|
|
sourceLoadingView = TextView(this).apply { |
|
|
text = "" |
|
|
text = "" |
|
|
setTextColor(0xFF999999.toInt()) |
|
|
setTextColor(Color.argb(235, 255, 255, 255)) |
|
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 10f) |
|
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f) |
|
|
gravity = Gravity.END |
|
|
setPadding(0, dp(8), 0, dp(4)) |
|
|
|
|
|
visibility = View.GONE |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
container.addView(titleRow) |
|
|
translatedLoadingView = TextView(this).apply { |
|
|
container.addView(languageInfoView) |
|
|
text = "" |
|
|
container.addView(sourceTextView) |
|
|
setTextColor(0xFF90CAF9.toInt()) |
|
|
container.addView(translatedTextView) |
|
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 15f) |
|
|
container.addView(timestampView) |
|
|
setPadding(0, dp(4), 0, dp(8)) |
|
|
return container |
|
|
visibility = View.GONE |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
val spacer = View(this) |
|
|
|
|
|
val spacerLp = LinearLayout.LayoutParams(0, 0, 1f) |
|
|
|
|
|
titleRow.addView(languageInfoView, languageInRowLp) |
|
|
|
|
|
titleRow.addView(spacer, spacerLp) |
|
|
|
|
|
titleRow.addView(closeButton) |
|
|
|
|
|
|
|
|
|
|
|
contentLayout.addView(titleRow) |
|
|
|
|
|
contentLayout.addView(sourceTextView) |
|
|
|
|
|
contentLayout.addView(sourceLoadingView) |
|
|
|
|
|
contentLayout.addView(translatedTextView) |
|
|
|
|
|
contentLayout.addView(translatedLoadingView) |
|
|
|
|
|
root.addView(contentLayout) |
|
|
|
|
|
|
|
|
|
|
|
val handleSize = dp(30) |
|
|
|
|
|
val resizeHandle = TextView(this).apply { |
|
|
|
|
|
text = "⤢" |
|
|
|
|
|
setTextColor(Color.argb(190, 255, 255, 255)) |
|
|
|
|
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f) |
|
|
|
|
|
gravity = Gravity.CENTER |
|
|
|
|
|
background = null |
|
|
|
|
|
setPadding(0, 0, 0, 0) |
|
|
|
|
|
scaleX = -1f |
|
|
|
|
|
} |
|
|
|
|
|
resizeHandleView = resizeHandle |
|
|
|
|
|
val handleLp = FrameLayout.LayoutParams(handleSize, handleSize, Gravity.BOTTOM or Gravity.END).apply { |
|
|
|
|
|
setMargins(0, 0, 0, 0) |
|
|
|
|
|
} |
|
|
|
|
|
root.addView(resizeHandle, handleLp) |
|
|
|
|
|
|
|
|
|
|
|
resizeHandle.setOnTouchListener(object : View.OnTouchListener { |
|
|
|
|
|
private var downRawX: Float = 0f |
|
|
|
|
|
private var downRawY: Float = 0f |
|
|
|
|
|
private var startWidth: Int = 0 |
|
|
|
|
|
private var startHeight: Int = 0 |
|
|
|
|
|
|
|
|
|
|
|
override fun onTouch(v: View, event: MotionEvent): Boolean { |
|
|
|
|
|
val expanded = expandedContainer ?: return false |
|
|
|
|
|
val rootLp = (expanded.layoutParams as? FrameLayout.LayoutParams) ?: return false |
|
|
|
|
|
val size = v.width.takeIf { it > 0 } ?: return false |
|
|
|
|
|
val localX = event.x |
|
|
|
|
|
val localY = event.y |
|
|
|
|
|
if (!isInBottomRightTriangle(localX, localY, size.toFloat())) { |
|
|
|
|
|
return false |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
when (event.actionMasked) { |
|
|
|
|
|
MotionEvent.ACTION_DOWN -> { |
|
|
|
|
|
downRawX = event.rawX |
|
|
|
|
|
downRawY = event.rawY |
|
|
|
|
|
startWidth = if (rootLp.width > 0) rootLp.width else expanded.width.takeIf { it > 0 } ?: baseExpandedWidth |
|
|
|
|
|
val resolvedHeight = if (rootLp.height > 0 && rootLp.height != ViewGroup.LayoutParams.WRAP_CONTENT) { |
|
|
|
|
|
rootLp.height |
|
|
|
|
|
} else { |
|
|
|
|
|
expanded.height.takeIf { it > 0 } ?: dp(220) |
|
|
|
|
|
} |
|
|
|
|
|
rootLp.height = resolvedHeight |
|
|
|
|
|
expanded.layoutParams = rootLp |
|
|
|
|
|
startHeight = resolvedHeight |
|
|
|
|
|
baseExpandedWidth = startWidth |
|
|
|
|
|
return true |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
MotionEvent.ACTION_MOVE -> { |
|
|
|
|
|
val dx = (event.rawX - downRawX).toInt() |
|
|
|
|
|
val dy = (event.rawY - downRawY).toInt() |
|
|
|
|
|
val minW = dp(240) |
|
|
|
|
|
val maxW = (resources.displayMetrics.widthPixels - dp(8) * 2).coerceAtLeast(minW) |
|
|
|
|
|
val minH = dp(140) |
|
|
|
|
|
val maxH = (resources.displayMetrics.heightPixels - dp(12) * 2).coerceAtLeast(minH) |
|
|
|
|
|
|
|
|
|
|
|
val nextW = (startWidth + dx).coerceIn(minW, maxW) |
|
|
|
|
|
val nextH = (startHeight + dy).coerceIn(minH, maxH) |
|
|
|
|
|
if (rootLp.width != nextW || rootLp.height != nextH) { |
|
|
|
|
|
rootLp.width = nextW |
|
|
|
|
|
rootLp.height = nextH |
|
|
|
|
|
expanded.layoutParams = rootLp |
|
|
|
|
|
baseExpandedWidth = nextW |
|
|
|
|
|
floatingView?.post { |
|
|
|
|
|
val wm = windowManager ?: return@post |
|
|
|
|
|
val floatingLp = layoutParams ?: return@post |
|
|
|
|
|
val (effectiveWidth, effectiveHeight) = getEffectiveFloatingSize() |
|
|
|
|
|
val maxX = resources.displayMetrics.widthPixels - effectiveWidth |
|
|
|
|
|
val maxY = resources.displayMetrics.heightPixels - effectiveHeight |
|
|
|
|
|
if (maxX > 0) floatingLp.x = floatingLp.x.coerceIn(0, maxX) |
|
|
|
|
|
if (maxY > 0) floatingLp.y = floatingLp.y.coerceIn(0, maxY) |
|
|
|
|
|
wm.updateViewLayout(floatingView, floatingLp) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
return true |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
MotionEvent.ACTION_UP, |
|
|
|
|
|
MotionEvent.ACTION_CANCEL -> return true |
|
|
|
|
|
|
|
|
|
|
|
else -> return false |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
applyUiScale(uiScale) |
|
|
|
|
|
return root |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private fun initViews() { |
|
|
private fun initViews() { |
|
|
@ -270,30 +471,79 @@ class FloatingWindowService : Service() { |
|
|
private var initialTouchX: Float = 0f |
|
|
private var initialTouchX: Float = 0f |
|
|
private var initialTouchY: Float = 0f |
|
|
private var initialTouchY: Float = 0f |
|
|
private var dragging: Boolean = false |
|
|
private var dragging: Boolean = false |
|
|
|
|
|
private var scaling: Boolean = false |
|
|
|
|
|
private var initialSpan: Float = 0f |
|
|
|
|
|
private var initialScale: Float = 1.0f |
|
|
|
|
|
|
|
|
override fun onTouch(v: View, event: MotionEvent): Boolean { |
|
|
override fun onTouch(v: View, event: MotionEvent): Boolean { |
|
|
val lp = layoutParams ?: return false |
|
|
val lp = layoutParams ?: return false |
|
|
val wm = windowManager ?: return false |
|
|
val wm = windowManager ?: return false |
|
|
when (event.action) { |
|
|
when (event.actionMasked) { |
|
|
MotionEvent.ACTION_DOWN -> { |
|
|
MotionEvent.ACTION_DOWN -> { |
|
|
initialX = lp.x |
|
|
initialX = lp.x |
|
|
initialY = lp.y |
|
|
initialY = lp.y |
|
|
initialTouchX = event.rawX |
|
|
initialTouchX = event.rawX |
|
|
initialTouchY = event.rawY |
|
|
initialTouchY = event.rawY |
|
|
|
|
|
|
|
|
|
|
|
if (!isMinimized && isTouchingView(closeButton, event.rawX, event.rawY) |
|
|
|
|
|
) { |
|
|
|
|
|
return false |
|
|
|
|
|
} |
|
|
|
|
|
if (!isMinimized && isTouchingView(resizeHandleView, event.rawX, event.rawY)) { |
|
|
|
|
|
return false |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
dragging = false |
|
|
dragging = false |
|
|
|
|
|
scaling = false |
|
|
|
|
|
return true |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
MotionEvent.ACTION_POINTER_DOWN -> { |
|
|
|
|
|
if (!isMinimized && event.pointerCount >= 2) { |
|
|
|
|
|
expandedContainer?.let { expanded -> |
|
|
|
|
|
val expandedLp = expanded.layoutParams as? FrameLayout.LayoutParams |
|
|
|
|
|
if (expandedLp != null && expandedLp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { |
|
|
|
|
|
val lockedHeight = expanded.height.takeIf { it > 0 } ?: dp(220) |
|
|
|
|
|
expandedLp.height = lockedHeight |
|
|
|
|
|
expanded.layoutParams = expandedLp |
|
|
|
|
|
} |
|
|
|
|
|
val width = (expanded.layoutParams as? FrameLayout.LayoutParams)?.width ?: 0 |
|
|
|
|
|
if (width > 0) baseExpandedWidth = width |
|
|
|
|
|
} |
|
|
|
|
|
scaling = true |
|
|
|
|
|
dragging = false |
|
|
|
|
|
initialSpan = pointerSpan(event) |
|
|
|
|
|
initialScale = uiScale |
|
|
|
|
|
return true |
|
|
|
|
|
} |
|
|
return true |
|
|
return true |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
MotionEvent.ACTION_MOVE -> { |
|
|
MotionEvent.ACTION_MOVE -> { |
|
|
|
|
|
if (scaling && event.pointerCount >= 2) { |
|
|
|
|
|
val span = pointerSpan(event) |
|
|
|
|
|
if (initialSpan > 0f && span > 0f) { |
|
|
|
|
|
val next = (initialScale * (span / initialSpan)).coerceIn(0.85f, 1.4f) |
|
|
|
|
|
setUiScale(next) |
|
|
|
|
|
|
|
|
|
|
|
val (effectiveWidth, effectiveHeight) = getEffectiveFloatingSize() |
|
|
|
|
|
val maxX = resources.displayMetrics.widthPixels - effectiveWidth |
|
|
|
|
|
val maxY = resources.displayMetrics.heightPixels - effectiveHeight |
|
|
|
|
|
if (maxX > 0) lp.x = lp.x.coerceIn(0, maxX) |
|
|
|
|
|
if (maxY > 0) lp.y = lp.y.coerceIn(0, maxY) |
|
|
|
|
|
wm.updateViewLayout(floatingView, lp) |
|
|
|
|
|
} |
|
|
|
|
|
return true |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
val dx = (event.rawX - initialTouchX).toInt() |
|
|
val dx = (event.rawX - initialTouchX).toInt() |
|
|
val dy = (event.rawY - initialTouchY).toInt() |
|
|
val dy = (event.rawY - initialTouchY).toInt() |
|
|
if (!dragging && (kotlin.math.abs(dx) > threshold || kotlin.math.abs(dy) > threshold)) { |
|
|
if (!dragging && (kotlin.math.abs(dx) > threshold || kotlin.math.abs(dy) > threshold)) { |
|
|
dragging = true |
|
|
dragging = true |
|
|
} |
|
|
} |
|
|
val maxX = |
|
|
val (effectiveWidth, effectiveHeight) = getEffectiveFloatingSize() |
|
|
resources.displayMetrics.widthPixels - (floatingView?.width ?: 0) |
|
|
val maxX = resources.displayMetrics.widthPixels - effectiveWidth |
|
|
val maxY = |
|
|
val maxY = resources.displayMetrics.heightPixels - effectiveHeight |
|
|
resources.displayMetrics.heightPixels - (floatingView?.height ?: 0) |
|
|
|
|
|
var nextX = initialX + dx |
|
|
var nextX = initialX + dx |
|
|
var nextY = initialY + dy |
|
|
var nextY = initialY + dy |
|
|
if (maxX > 0) nextX = nextX.coerceIn(0, maxX) |
|
|
if (maxX > 0) nextX = nextX.coerceIn(0, maxX) |
|
|
@ -304,12 +554,21 @@ class FloatingWindowService : Service() { |
|
|
return true |
|
|
return true |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
MotionEvent.ACTION_POINTER_UP -> { |
|
|
|
|
|
val remainingPointers = event.pointerCount - 1 |
|
|
|
|
|
if (scaling && remainingPointers < 2) { |
|
|
|
|
|
scaling = false |
|
|
|
|
|
return true |
|
|
|
|
|
} |
|
|
|
|
|
return false |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
MotionEvent.ACTION_UP -> { |
|
|
MotionEvent.ACTION_UP -> { |
|
|
if (!dragging) updateViewState(!isMinimized) |
|
|
if (!dragging && !scaling) updateViewState(!isMinimized) |
|
|
dragging = false |
|
|
dragging = false |
|
|
|
|
|
scaling = false |
|
|
return true |
|
|
return true |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
else -> return false |
|
|
else -> return false |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
@ -320,6 +579,13 @@ class FloatingWindowService : Service() { |
|
|
isMinimized = minimized |
|
|
isMinimized = minimized |
|
|
minimizedView?.visibility = if (minimized) View.VISIBLE else View.GONE |
|
|
minimizedView?.visibility = if (minimized) View.VISIBLE else View.GONE |
|
|
expandedView?.visibility = if (minimized) View.GONE else View.VISIBLE |
|
|
expandedView?.visibility = if (minimized) View.GONE else View.VISIBLE |
|
|
|
|
|
|
|
|
|
|
|
if (minimized) { |
|
|
|
|
|
stopAllLoading() |
|
|
|
|
|
minimizedView?.scaleX = 1.0f |
|
|
|
|
|
minimizedView?.scaleY = 1.0f |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
notifyStateChanged(if (minimized) "minimized" else "expanded") |
|
|
notifyStateChanged(if (minimized) "minimized" else "expanded") |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -328,26 +594,58 @@ class FloatingWindowService : Service() { |
|
|
val translatedText = intent.getStringExtra(EXTRA_TRANSLATED_TEXT) |
|
|
val translatedText = intent.getStringExtra(EXTRA_TRANSLATED_TEXT) |
|
|
val sourceLang = intent.getStringExtra(EXTRA_SOURCE_LANGUAGE) |
|
|
val sourceLang = intent.getStringExtra(EXTRA_SOURCE_LANGUAGE) |
|
|
val targetLang = intent.getStringExtra(EXTRA_TARGET_LANGUAGE) |
|
|
val targetLang = intent.getStringExtra(EXTRA_TARGET_LANGUAGE) |
|
|
val timestamp = intent.getLongExtra(EXTRA_TIMESTAMP, 0L) |
|
|
|
|
|
val isIntermediate = intent.getBooleanExtra(EXTRA_IS_INTERMEDIATE, false) |
|
|
val isIntermediate = intent.getBooleanExtra(EXTRA_IS_INTERMEDIATE, false) |
|
|
|
|
|
|
|
|
|
|
|
val sourceValue = sourceText ?: "" |
|
|
|
|
|
val translatedValue = translatedText ?: "" |
|
|
|
|
|
val hasSource = !TextUtils.isEmpty(sourceValue) |
|
|
|
|
|
val hasTranslated = !TextUtils.isEmpty(translatedValue) |
|
|
|
|
|
|
|
|
languageInfoView?.let { view -> |
|
|
languageInfoView?.let { view -> |
|
|
val langText = (sourceLang ?: "") + " → " + (targetLang ?: "") |
|
|
val langText = (sourceLang ?: "") + " → " + (targetLang ?: "") |
|
|
view.text = langText |
|
|
view.text = langText |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
sourceTextView?.let { view -> |
|
|
sourceTextView?.let { view -> |
|
|
val value = sourceText ?: "" |
|
|
if (hasSource) { |
|
|
view.text = if (TextUtils.isEmpty(value)) "正在识别..." else value |
|
|
view.text = sourceValue |
|
|
|
|
|
view.visibility = View.VISIBLE |
|
|
|
|
|
stopSourceLoading() |
|
|
|
|
|
} else { |
|
|
|
|
|
view.text = "" |
|
|
|
|
|
view.visibility = View.GONE |
|
|
|
|
|
if (isIntermediate) { |
|
|
|
|
|
startSourceLoading() |
|
|
|
|
|
} else { |
|
|
|
|
|
stopSourceLoading() |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
view.alpha = if (isIntermediate) 0.85f else 1.0f |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
translatedTextView?.let { view -> |
|
|
translatedTextView?.let { view -> |
|
|
val value = translatedText ?: "" |
|
|
if (hasTranslated) { |
|
|
view.text = if (TextUtils.isEmpty(value)) "翻译中..." else value |
|
|
view.text = translatedValue |
|
|
|
|
|
view.visibility = View.VISIBLE |
|
|
|
|
|
stopTranslatedLoading() |
|
|
|
|
|
} else { |
|
|
|
|
|
view.text = "" |
|
|
|
|
|
view.visibility = View.GONE |
|
|
|
|
|
if (hasSource || isIntermediate) { |
|
|
|
|
|
startTranslatedLoading() |
|
|
|
|
|
} else { |
|
|
|
|
|
stopTranslatedLoading() |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
view.alpha = if (isIntermediate) 0.7f else 1.0f |
|
|
view.alpha = if (isIntermediate) 0.7f else 1.0f |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
timestampView?.text = if (timestamp > 0) formatTimestamp(timestamp) else "" |
|
|
minimizedTextView?.let { view -> |
|
|
|
|
|
val primary = translatedText?.takeIf { !TextUtils.isEmpty(it) } |
|
|
|
|
|
val secondary = sourceText?.takeIf { !TextUtils.isEmpty(it) } |
|
|
|
|
|
view.text = primary ?: secondary ?: "Voitrans" |
|
|
|
|
|
view.alpha = if (isIntermediate) 0.85f else 1.0f |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private fun setPosition(intent: Intent) { |
|
|
private fun setPosition(intent: Intent) { |
|
|
@ -386,18 +684,157 @@ class FloatingWindowService : Service() { |
|
|
channel.invokeMethod("onFloatingWindowClicked", null) |
|
|
channel.invokeMethod("onFloatingWindowClicked", null) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private fun formatTimestamp(ts: Long): String { |
|
|
private fun dp(value: Int): Int { |
|
|
if (ts <= 0) return "" |
|
|
return (value * resources.displayMetrics.density).toInt() |
|
|
return try { |
|
|
} |
|
|
val sdf = SimpleDateFormat("HH:mm:ss", Locale.getDefault()) |
|
|
|
|
|
sdf.format(Date(ts)) |
|
|
private fun dpF(value: Float): Float { |
|
|
} catch (_: Exception) { |
|
|
return value * resources.displayMetrics.density |
|
|
"" |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun setUiScale(next: Float) { |
|
|
|
|
|
uiScale = next |
|
|
|
|
|
applyUiScale(uiScale) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun applyUiScale(scale: Float) { |
|
|
|
|
|
val container = expandedContainer ?: return |
|
|
|
|
|
if (baseExpandedWidth <= 0) return |
|
|
|
|
|
|
|
|
|
|
|
languageInfoView?.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f * scale) |
|
|
|
|
|
languageInfoView?.setPadding( |
|
|
|
|
|
(dp(10) * scale).toInt().coerceIn(dp(6), dp(16)), |
|
|
|
|
|
(dp(6) * scale).toInt().coerceIn(dp(4), dp(12)), |
|
|
|
|
|
(dp(10) * scale).toInt().coerceIn(dp(6), dp(16)), |
|
|
|
|
|
(dp(6) * scale).toInt().coerceIn(dp(4), dp(12)) |
|
|
|
|
|
) |
|
|
|
|
|
sourceTextView?.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f * scale) |
|
|
|
|
|
translatedTextView?.setTextSize(TypedValue.COMPLEX_UNIT_SP, 15f * scale) |
|
|
|
|
|
sourceLoadingView?.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f * scale) |
|
|
|
|
|
translatedLoadingView?.setTextSize(TypedValue.COMPLEX_UNIT_SP, 15f * scale) |
|
|
|
|
|
|
|
|
|
|
|
val closeTextSizeSp = (18f * scale).coerceIn(14f, 22f) |
|
|
|
|
|
closeButton?.setTextSize(TypedValue.COMPLEX_UNIT_SP, closeTextSizeSp) |
|
|
|
|
|
|
|
|
|
|
|
val closeHPad = (dp(8) * scale).toInt().coerceIn(dp(6), dp(16)) |
|
|
|
|
|
val closeVPad = (dp(8) * scale).toInt().coerceIn(dp(4), dp(12)) |
|
|
|
|
|
closeButton?.setPadding(closeHPad, closeVPad, closeHPad, closeVPad) |
|
|
|
|
|
|
|
|
|
|
|
container.setPadding(0, 0, 0, 0) |
|
|
|
|
|
val contentPadding = (dp(16) * scale).toInt().coerceIn(dp(12), dp(24)) |
|
|
|
|
|
expandedContentLayout?.setPadding(contentPadding, contentPadding, contentPadding, contentPadding) |
|
|
|
|
|
|
|
|
|
|
|
val maxLines = if (scale >= 1.15f) 4 else 3 |
|
|
|
|
|
sourceTextView?.maxLines = maxLines |
|
|
|
|
|
translatedTextView?.maxLines = maxLines |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun getEffectiveFloatingSize(): Pair<Int, Int> { |
|
|
|
|
|
val view = floatingView |
|
|
|
|
|
if (view != null) { |
|
|
|
|
|
val w = view.width |
|
|
|
|
|
val h = view.height |
|
|
|
|
|
if (w > 0 && h > 0) { |
|
|
|
|
|
lastEffectiveWidth = w |
|
|
|
|
|
lastEffectiveHeight = h |
|
|
|
|
|
return w to h |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
layoutParams?.let { lp -> |
|
|
|
|
|
val w = lp.width |
|
|
|
|
|
val h = lp.height |
|
|
|
|
|
if (w > 0 && h > 0) { |
|
|
|
|
|
lastEffectiveWidth = w |
|
|
|
|
|
lastEffectiveHeight = h |
|
|
|
|
|
return w to h |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
(expandedContainer?.layoutParams as? FrameLayout.LayoutParams)?.let { lp -> |
|
|
|
|
|
val w = lp.width |
|
|
|
|
|
val h = lp.height |
|
|
|
|
|
if (w > 0 && h > 0) { |
|
|
|
|
|
lastEffectiveWidth = w |
|
|
|
|
|
lastEffectiveHeight = h |
|
|
|
|
|
return w to h |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (lastEffectiveWidth <= 0) lastEffectiveWidth = dp(280) |
|
|
|
|
|
if (lastEffectiveHeight <= 0) lastEffectiveHeight = dp(220) |
|
|
|
|
|
return lastEffectiveWidth to lastEffectiveHeight |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private fun dp(value: Int): Int { |
|
|
private fun startSourceLoading() { |
|
|
return (value * resources.displayMetrics.density).toInt() |
|
|
val view = sourceLoadingView ?: return |
|
|
|
|
|
if (view.visibility == View.VISIBLE && sourceDotsRunnable != null) return |
|
|
|
|
|
sourceDotsStep = 0 |
|
|
|
|
|
view.visibility = View.VISIBLE |
|
|
|
|
|
val runnable = object : Runnable { |
|
|
|
|
|
override fun run() { |
|
|
|
|
|
val count = (sourceDotsStep % 3) + 1 |
|
|
|
|
|
view.text = "•".repeat(count) |
|
|
|
|
|
sourceDotsStep++ |
|
|
|
|
|
uiHandler.postDelayed(this, 350L) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
sourceDotsRunnable = runnable |
|
|
|
|
|
uiHandler.post(runnable) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun stopSourceLoading() { |
|
|
|
|
|
sourceDotsRunnable?.let { uiHandler.removeCallbacks(it) } |
|
|
|
|
|
sourceDotsRunnable = null |
|
|
|
|
|
sourceLoadingView?.visibility = View.GONE |
|
|
|
|
|
sourceLoadingView?.text = "" |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun startTranslatedLoading() { |
|
|
|
|
|
val view = translatedLoadingView ?: return |
|
|
|
|
|
if (view.visibility == View.VISIBLE && translatedDotsRunnable != null) return |
|
|
|
|
|
translatedDotsStep = 0 |
|
|
|
|
|
view.visibility = View.VISIBLE |
|
|
|
|
|
val runnable = object : Runnable { |
|
|
|
|
|
override fun run() { |
|
|
|
|
|
val count = (translatedDotsStep % 3) + 1 |
|
|
|
|
|
view.text = "•".repeat(count) |
|
|
|
|
|
translatedDotsStep++ |
|
|
|
|
|
uiHandler.postDelayed(this, 350L) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
translatedDotsRunnable = runnable |
|
|
|
|
|
uiHandler.post(runnable) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun stopTranslatedLoading() { |
|
|
|
|
|
translatedDotsRunnable?.let { uiHandler.removeCallbacks(it) } |
|
|
|
|
|
translatedDotsRunnable = null |
|
|
|
|
|
translatedLoadingView?.visibility = View.GONE |
|
|
|
|
|
translatedLoadingView?.text = "" |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun stopAllLoading() { |
|
|
|
|
|
stopSourceLoading() |
|
|
|
|
|
stopTranslatedLoading() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun isTouchingView(view: View?, x: Float, y: Float): Boolean { |
|
|
|
|
|
if (view == null || view.visibility != View.VISIBLE) return false |
|
|
|
|
|
val rect = Rect() |
|
|
|
|
|
if (!view.getGlobalVisibleRect(rect)) return false |
|
|
|
|
|
return rect.contains(x.toInt(), y.toInt()) |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private fun pointerSpan(event: MotionEvent): Float { |
|
|
|
|
|
if (event.pointerCount < 2) return 0f |
|
|
|
|
|
val dx = event.getX(0) - event.getX(1) |
|
|
|
|
|
val dy = event.getY(0) - event.getY(1) |
|
|
|
|
|
return kotlin.math.sqrt(dx * dx + dy * dy) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun isInBottomRightTriangle(x: Float, y: Float, size: Float): Boolean { |
|
|
|
|
|
if (x < 0f || y < 0f || x > size || y > size) return false |
|
|
|
|
|
return x >= size - y |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|