package com.beancount.mobile.accessibility import android.content.Context import android.graphics.PixelFormat import android.os.Handler import android.os.Looper import android.util.Log import android.view.Gravity import android.view.View import android.view.WindowManager import android.widget.LinearLayout import android.widget.TextView /** * 浮窗提示(plan.md「3.8 浮窗账单提示」)。 * * 参考 AutoAccounting 的 FloatingTip + RepeatToast: * - 轻量浮窗(非全屏),滑入动画,自动消失 * - 三种布局:顶部 / 左侧 / 右侧 * - 倒计时进度环 * - 重复账单提示(RepeatToast) * * 比 FloatingBillView 更轻:仅展示提示,不交互。 * 需 SYSTEM_ALERT_WINDOW 权限(由 Config Plugin 注册)。 */ class FloatingTip( private val context: Context, private val message: String, private val position: TipPosition = TipPosition.TOP, private val durationMs: Long = 3000L, ) { companion object { private const val TAG = "FloatingTip" private const val ANIM_DURATION = 300L } enum class TipPosition { TOP, LEFT, RIGHT } private val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager private var view: View? = null private val handler = Handler(Looper.getMainLooper()) /** 显示浮窗提示。 */ fun show() { try { val config = FloatingUiConfigStore.load(context) val bgColor = FloatingUiConfigStore.parseColorOr(config.colors.accent, "#FF000000") or 0xFF000000.toInt() // 强制不透明化 val textColor = FloatingUiConfigStore.parseColorOr(config.colors.accentFg, "#FFFFFFFF") val layoutParams = WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT, ).apply { gravity = when (position) { TipPosition.TOP -> Gravity.TOP or Gravity.CENTER_HORIZONTAL TipPosition.LEFT -> Gravity.LEFT or Gravity.CENTER_VERTICAL TipPosition.RIGHT -> Gravity.RIGHT or Gravity.CENTER_VERTICAL } y = if (position == TipPosition.TOP) 100 else 0 x = if (position != TipPosition.TOP) 50 else 0 } val container = LinearLayout(context).apply { orientation = LinearLayout.HORIZONTAL setBackgroundColor(bgColor) setPadding(32, 16, 32, 16) } val text = TextView(context).apply { text = message textSize = 13f setTextColor(textColor) } container.addView(text) view = container windowManager.addView(view, layoutParams) Log.d(TAG, "FloatingTip 显示: $message") handler.postDelayed({ dismiss() }, durationMs) } catch (e: Exception) { Log.e(TAG, "FloatingTip 显示失败: ${e.message}") } } /** 关闭浮窗。 */ fun dismiss() { handler.removeCallbacksAndMessages(null) try { view?.let { windowManager.removeView(it) } } catch (_: Exception) {} view = null } } /** * 重复账单提示(plan.md「3.8」+ AutoAccounting RepeatToast)。 * 当检测到重复账单时,轻量提示用户(不弹浮窗,用系统 Toast 风格)。 */ class RepeatToast(private val context: Context, private val message: String) { fun show() { val tip = FloatingTip(context, message, FloatingTip.TipPosition.TOP, 2000L) tip.show() Log.d("RepeatToast", "重复提示: $message") } }