Browse Source

[feat]键盘插件,增加键盘引导弹窗

hezihao 7 months ago
parent
commit
8105b1a02a
15 changed files with 413 additions and 16 deletions
  1. 72 5
      plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/component/child/impl/AiKeyboardCommonPanelComponent.kt
  2. 4 4
      plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/component/child/impl/AiKeyboardProloguePanelComponent.kt
  3. 6 3
      plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/component/item/AiKeyboardKeyViewBinder.kt
  4. 10 0
      plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/constant/Constants.kt
  5. 9 0
      plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/keyboard/CustomKeyboardService.kt
  6. 8 1
      plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/keyboard/ICustomKeyboardService.kt
  7. 10 0
      plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/ui/core/IViewFinder.java
  8. 24 3
      plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/ui/popup/base/BasePopupDialog.kt
  9. 147 0
      plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/ui/popup/guide/KeyboardGuideDialog.kt
  10. 34 0
      plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/util/KeyboardGuideDialogUtil.kt
  11. 26 0
      plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/util/KeyboardGuideRecordUtil.kt
  12. 23 0
      plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/util/ViewLocationUtil.kt
  13. 38 0
      plugins/keyboard_android/android/src/main/res/layout/layout_keyboard_guide.xml
  14. BIN
      plugins/keyboard_android/android/src/main/res/mipmap-xxxhdpi/ic_keyboard_plugin_guide_overlay1.webp
  15. 2 0
      plugins/keyboard_android/android/src/main/res/values/colors.xml

+ 72 - 5
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/component/child/impl/AiKeyboardCommonPanelComponent.kt

@@ -2,6 +2,8 @@ package com.atmob.keyboard_android.component.child.impl
 
 
 import android.annotation.SuppressLint
 import android.annotation.SuppressLint
 import android.content.Context
 import android.content.Context
+import android.os.Handler
+import android.os.Looper
 import android.util.AttributeSet
 import android.util.AttributeSet
 import android.view.View
 import android.view.View
 import androidx.recyclerview.widget.GridLayoutManager
 import androidx.recyclerview.widget.GridLayoutManager
@@ -22,7 +24,10 @@ import com.atmob.keyboard_android.ext.setGone
 import com.atmob.keyboard_android.ext.setVisible
 import com.atmob.keyboard_android.ext.setVisible
 import com.atmob.keyboard_android.model.AddCharacterModel
 import com.atmob.keyboard_android.model.AddCharacterModel
 import com.atmob.keyboard_android.model.AiKeyboardKeyModel
 import com.atmob.keyboard_android.model.AiKeyboardKeyModel
+import com.atmob.keyboard_android.ui.popup.guide.KeyboardGuideDialog
 import com.atmob.keyboard_android.util.InputMethodUtil
 import com.atmob.keyboard_android.util.InputMethodUtil
+import com.atmob.keyboard_android.util.KeyboardGuideDialogUtil
+import com.atmob.keyboard_android.util.KeyboardGuideRecordUtil
 import com.atmob.keyboard_android.util.KeyboardHolder
 import com.atmob.keyboard_android.util.KeyboardHolder
 import com.atmob.keyboard_android.util.LogUtil
 import com.atmob.keyboard_android.util.LogUtil
 import com.atmob.keyboard_android.util.UserInfoHelper
 import com.atmob.keyboard_android.util.UserInfoHelper
@@ -59,6 +64,16 @@ class AiKeyboardCommonPanelComponent @JvmOverloads constructor(
     private lateinit var mKeyListAdapter: MultiTypeAdapter
     private lateinit var mKeyListAdapter: MultiTypeAdapter
 
 
     /**
     /**
+     * 键盘引导Handler
+     */
+    private val mKeyboardGuideHandler = Handler(Looper.getMainLooper())
+
+    /**
+     * 键盘引导,是否正在显示
+     */
+    private var mKeyboardGuideShowing = false
+
+    /**
      * 当前点击的条目,用于点击重新生成时,重新生成内容
      * 当前点击的条目,用于点击重新生成时,重新生成内容
      */
      */
     private var mCurrentClickItem: AiKeyboardKeyModel? = null
     private var mCurrentClickItem: AiKeyboardKeyModel? = null
@@ -98,9 +113,9 @@ class AiKeyboardCommonPanelComponent @JvmOverloads constructor(
             mKeyListItems = Items()
             mKeyListItems = Items()
             mKeyListAdapter = MultiTypeAdapter(mKeyListItems).apply {
             mKeyListAdapter = MultiTypeAdapter(mKeyListItems).apply {
                 // 键盘按键条目
                 // 键盘按键条目
-                register(AiKeyboardKeyModel::class.java, AiKeyboardKeyViewBinder {
+                register(AiKeyboardKeyModel::class.java, AiKeyboardKeyViewBinder { itemView, item ->
                     // 当前点击的条目,正在加载中,忽略点击
                     // 当前点击的条目,正在加载中,忽略点击
-                    if (it.isLoading) {
+                    if (item.isLoading) {
                         return@AiKeyboardKeyViewBinder
                         return@AiKeyboardKeyViewBinder
                     }
                     }
                     // 其他条目正在加载中,忽略点击
                     // 其他条目正在加载中,忽略点击
@@ -112,15 +127,15 @@ class AiKeyboardCommonPanelComponent @JvmOverloads constructor(
                         return@AiKeyboardKeyViewBinder
                         return@AiKeyboardKeyViewBinder
                     }
                     }
                     // 点击键盘按键,打开AI生成内容面板
                     // 点击键盘按键,打开AI生成内容面板
-                    if (it.isVip) {
+                    if (item.isVip) {
                         // 检查是否VIP
                         // 检查是否VIP
                         UserInfoHelper.checkVip {
                         UserInfoHelper.checkVip {
-                            handleItemClickLogic(it)
+                            handleItemClickLogic(item)
                             LogUtil.d("Ai键盘,已经是VIP,打开Ai内容面板")
                             LogUtil.d("Ai键盘,已经是VIP,打开Ai内容面板")
                         }
                         }
                     } else {
                     } else {
                         // 不需要VIP,直接打开
                         // 不需要VIP,直接打开
-                        handleItemClickLogic(it)
+                        handleItemClickLogic(item)
                         LogUtil.d("Ai键盘,不需要VIP,直接进行下一步")
                         LogUtil.d("Ai键盘,不需要VIP,直接进行下一步")
                     }
                     }
                 })
                 })
@@ -295,12 +310,64 @@ class AiKeyboardCommonPanelComponent @JvmOverloads constructor(
                 // 添加人设按钮
                 // 添加人设按钮
                 mKeyListItems.add(AddCharacterModel())
                 mKeyListItems.add(AddCharacterModel())
                 mKeyListAdapter.notifyDataSetChanged()
                 mKeyListAdapter.notifyDataSetChanged()
+
+                // 显示键盘引导页
+                tryShowKeyboardGuide()
             }, onFail = {
             }, onFail = {
                 ToastUtils.showShort(it)
                 ToastUtils.showShort(it)
             })
             })
     }
     }
 
 
     /**
     /**
+     * 显示键盘引导
+     */
+    private fun tryShowKeyboardGuide() {
+        val action = Runnable {
+            // 正在显示,忽略
+            if (mKeyboardGuideShowing) {
+                return@Runnable
+            }
+
+            // 不是第一次引导,忽略
+            if (!KeyboardGuideRecordUtil.isFirstShowKeyboardGuide()) {
+                return@Runnable
+            }
+
+            val targetIndex = Constants.KEYBOARD_GUIDE_KEY_POSITION
+
+            // 获取所有按键数据
+            val keyList = mKeyListItems.filterIsInstance<AiKeyboardKeyModel>().toList()
+            if (keyList.isEmpty()) {
+                return@Runnable
+            }
+            // 目标超出范围,忽略
+            if (targetIndex > keyList.size - 1) {
+                return@Runnable
+            }
+
+            // 条目数据
+            val itemData = keyList[targetIndex]
+            // 通过position,获取ViewHolder
+            val viewHolder =
+                vKeyList.findViewHolderForAdapterPosition(targetIndex)
+
+            // 找到目标条目,则显示引导
+            if (viewHolder != null) {
+                val itemView = viewHolder.itemView
+                KeyboardGuideDialogUtil.showGuideDialog(itemView, itemData, object :
+                    KeyboardGuideDialog.OnActionCallback {
+                    override fun onFinishGuide() {
+                        mKeyboardGuideShowing = false
+                        KeyboardGuideRecordUtil.setFirstShowKeyboardGuide(false)
+                    }
+                })
+                mKeyboardGuideShowing = true
+            }
+        }
+        mKeyboardGuideHandler.postDelayed(action, 350)
+    }
+
+    /**
      * 控制Ai生成内容面板,是否显示
      * 控制Ai生成内容面板,是否显示
      */
      */
     private fun controlAiChatPageShowing(isShow: Boolean) {
     private fun controlAiChatPageShowing(isShow: Boolean) {

+ 4 - 4
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/component/child/impl/AiKeyboardProloguePanelComponent.kt

@@ -175,17 +175,17 @@ class AiKeyboardProloguePanelComponent @JvmOverloads constructor(
         vKeyList.apply {
         vKeyList.apply {
             mKeyListItems = Items()
             mKeyListItems = Items()
             mKeyListAdapter = MultiTypeAdapter(mKeyListItems).apply {
             mKeyListAdapter = MultiTypeAdapter(mKeyListItems).apply {
-                register(AiKeyboardKeyModel::class.java, AiKeyboardKeyViewBinder {
+                register(AiKeyboardKeyModel::class.java, AiKeyboardKeyViewBinder { itemView, item ->
                     // 点击键盘按键,打开AI生成内容面板
                     // 点击键盘按键,打开AI生成内容面板
-                    if (it.isVip) {
+                    if (item.isVip) {
                         // 检查是否VIP
                         // 检查是否VIP
                         UserInfoHelper.checkVip {
                         UserInfoHelper.checkVip {
-                            handleItemClickLogic(it)
+                            handleItemClickLogic(item)
                             LogUtil.d("Ai键盘,已经是VIP,打开Ai内容面板")
                             LogUtil.d("Ai键盘,已经是VIP,打开Ai内容面板")
                         }
                         }
                     } else {
                     } else {
                         // 不需要VIP,直接打开
                         // 不需要VIP,直接打开
-                        handleItemClickLogic(it)
+                        handleItemClickLogic(item)
                         LogUtil.d("Ai键盘,不需要VIP,直接进行下一步")
                         LogUtil.d("Ai键盘,不需要VIP,直接进行下一步")
                     }
                     }
                 })
                 })

+ 6 - 3
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/component/item/AiKeyboardKeyViewBinder.kt

@@ -18,7 +18,7 @@ import me.drakeet.multitype.ItemViewBinder
  * AI键盘按键条目
  * AI键盘按键条目
  */
  */
 class AiKeyboardKeyViewBinder(
 class AiKeyboardKeyViewBinder(
-    private val onItemClick: (item: AiKeyboardKeyModel) -> Unit
+    private val onItemClick: (itemView: View, item: AiKeyboardKeyModel) -> Unit
 ) :
 ) :
     ItemViewBinder<AiKeyboardKeyModel, AiKeyboardKeyViewBinder.InnerViewHolder>() {
     ItemViewBinder<AiKeyboardKeyModel, AiKeyboardKeyViewBinder.InnerViewHolder>() {
     override fun onCreateViewHolder(
     override fun onCreateViewHolder(
@@ -36,7 +36,10 @@ class AiKeyboardKeyViewBinder(
         if (item.isLoading) {
         if (item.isLoading) {
             holder.vLoveFlyLoadingLottieView.setVisible()
             holder.vLoveFlyLoadingLottieView.setVisible()
             holder.vKeyText.setGone()
             holder.vKeyText.setGone()
-            LottieAnimationUtil.playLottieAnimation(holder.vLoveFlyLoadingLottieView, "lottie/anim_keyboard_love_fly.json")
+            LottieAnimationUtil.playLottieAnimation(
+                holder.vLoveFlyLoadingLottieView,
+                "lottie/anim_keyboard_love_fly.json"
+            )
         } else {
         } else {
             // 隐藏Loading,显示文字
             // 隐藏Loading,显示文字
             holder.vLoveFlyLoadingLottieView.setGone()
             holder.vLoveFlyLoadingLottieView.setGone()
@@ -48,7 +51,7 @@ class AiKeyboardKeyViewBinder(
             LottieAnimationUtil.stopLottieAnimation(holder.vLoveFlyLoadingLottieView)
             LottieAnimationUtil.stopLottieAnimation(holder.vLoveFlyLoadingLottieView)
         }
         }
         holder.itemView.click {
         holder.itemView.click {
-            onItemClick.invoke(item)
+            onItemClick.invoke(holder.itemView, item)
         }
         }
     }
     }
 
 

+ 10 - 0
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/constant/Constants.kt

@@ -24,5 +24,15 @@ interface Constants {
          * 组件动画,是否开启
          * 组件动画,是否开启
          */
          */
         const val COMPONENT_ANIMATOR_ENABLE = false
         const val COMPONENT_ANIMATOR_ENABLE = false
+
+        /**
+         * SP,是否第一次显示键盘引导
+         */
+        const val IS_FIRST_SHOW_KEYBOARD_GUIDE = "isFirstShowKeyboardGuide"
+
+        /**
+         * 键盘引导,要显示在哪个按键的数据索引,从0开始
+         */
+        const val KEYBOARD_GUIDE_KEY_POSITION = 1
     }
     }
 }
 }

+ 9 - 0
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/keyboard/CustomKeyboardService.kt

@@ -2,6 +2,7 @@ package com.atmob.keyboard_android.keyboard
 
 
 import android.inputmethodservice.InputMethodService
 import android.inputmethodservice.InputMethodService
 import android.view.View
 import android.view.View
+import android.view.ViewGroup
 import android.view.Window
 import android.view.Window
 import android.view.inputmethod.EditorInfo
 import android.view.inputmethod.EditorInfo
 import com.atmob.keyboard_android.R
 import com.atmob.keyboard_android.R
@@ -54,6 +55,10 @@ class CustomKeyboardService : InputMethodLifecycleService(), ICustomKeyboardServ
         return window!!.window!!
         return window!!.window!!
     }
     }
 
 
+    override fun getRootView(): ViewGroup {
+        return window.window?.decorView!! as ViewGroup
+    }
+
     override fun getKeyboardViewModel(): KeyboardViewModel {
     override fun getKeyboardViewModel(): KeyboardViewModel {
         return mKeyboardViewModel
         return mKeyboardViewModel
     }
     }
@@ -65,4 +70,8 @@ class CustomKeyboardService : InputMethodLifecycleService(), ICustomKeyboardServ
     override fun asInputMethodService(): InputMethodService {
     override fun asInputMethodService(): InputMethodService {
         return this
         return this
     }
     }
+
+    override fun findViewById(viewId: Int): View? {
+        return getRootView().findViewById(viewId)
+    }
 }
 }

+ 8 - 1
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/keyboard/ICustomKeyboardService.kt

@@ -1,19 +1,26 @@
 package com.atmob.keyboard_android.keyboard
 package com.atmob.keyboard_android.keyboard
 
 
 import android.inputmethodservice.InputMethodService
 import android.inputmethodservice.InputMethodService
+import android.view.ViewGroup
 import android.view.Window
 import android.view.Window
 import com.atmob.keyboard_android.mvvm.viewmodel.KeyboardViewModel
 import com.atmob.keyboard_android.mvvm.viewmodel.KeyboardViewModel
+import com.atmob.keyboard_android.ui.core.IViewFinder
 
 
 /**
 /**
  * 自定义键盘Service接口,定义对外暴露的API方法
  * 自定义键盘Service接口,定义对外暴露的API方法
  */
  */
-interface ICustomKeyboardService {
+interface ICustomKeyboardService : IViewFinder {
     /**
     /**
      * 获取自定义键盘的Window
      * 获取自定义键盘的Window
      */
      */
     fun getKeyboardWindow(): Window
     fun getKeyboardWindow(): Window
 
 
     /**
     /**
+     * 获取根View
+     */
+    fun getRootView(): ViewGroup
+
+    /**
      * 获取键盘ViewModel
      * 获取键盘ViewModel
      */
      */
     fun getKeyboardViewModel(): KeyboardViewModel
     fun getKeyboardViewModel(): KeyboardViewModel

+ 10 - 0
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/ui/core/IViewFinder.java

@@ -0,0 +1,10 @@
+package com.atmob.keyboard_android.ui.core;
+
+import android.view.View;
+
+/**
+ * View查找者接口
+ */
+public interface IViewFinder {
+    View findViewById(int viewId);
+}

+ 24 - 3
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/ui/popup/base/BasePopupDialog.kt

@@ -7,7 +7,6 @@ import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.LayoutInflater
 import android.view.View
 import android.view.View
 import android.view.Window
 import android.view.Window
-import android.view.WindowManager
 import android.widget.PopupWindow
 import android.widget.PopupWindow
 import com.atmob.keyboard_android.ui.core.LayoutCallback
 import com.atmob.keyboard_android.ui.core.LayoutCallback
 
 
@@ -24,11 +23,14 @@ abstract class BasePopupDialog(context: Context, private val hostWindow: Window)
         // 填充布局
         // 填充布局
         val contentView = LayoutInflater.from(context).inflate(onInflaterViewId(), null, false)
         val contentView = LayoutInflater.from(context).inflate(onInflaterViewId(), null, false)
 
 
+        // 获取弹窗的宽高
+        val frameSize = setupFrameSize()
+
         // 配置 PopupWindow
         // 配置 PopupWindow
         mPopupWindow = PopupWindow(
         mPopupWindow = PopupWindow(
             contentView,
             contentView,
-            WindowManager.LayoutParams.MATCH_PARENT,
-            WindowManager.LayoutParams.MATCH_PARENT
+            frameSize[0],
+            frameSize[1]
         ).apply {
         ).apply {
             // 不获取焦点,否则获取焦点会将软键盘关闭
             // 不获取焦点,否则获取焦点会将软键盘关闭
             isFocusable = false
             isFocusable = false
@@ -46,6 +48,14 @@ abstract class BasePopupDialog(context: Context, private val hostWindow: Window)
     }
     }
 
 
     /**
     /**
+     * 设置大小
+     */
+    private fun setupFrameSize(): IntArray {
+        val decorView = hostWindow.decorView
+        return onSetupDialogFrameSize(decorView.context, decorView.width, decorView.height)
+    }
+
+    /**
      * 显示Loading
      * 显示Loading
      */
      */
     fun show() {
     fun show() {
@@ -75,6 +85,17 @@ abstract class BasePopupDialog(context: Context, private val hostWindow: Window)
     }
     }
 
 
     /**
     /**
+     * 获取弹窗的宽和高,默认铺满整个Window
+     */
+    protected open fun onSetupDialogFrameSize(
+        context: Context,
+        windowWidth: Int,
+        windowHeight: Int
+    ): IntArray {
+        return intArrayOf(windowWidth, windowHeight)
+    }
+
+    /**
      * 是否允许点击外部区域,关闭弹窗
      * 是否允许点击外部区域,关闭弹窗
      */
      */
     protected open fun isCanOutsideTouchable(): Boolean {
     protected open fun isCanOutsideTouchable(): Boolean {

+ 147 - 0
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/ui/popup/guide/KeyboardGuideDialog.kt

@@ -0,0 +1,147 @@
+package com.atmob.keyboard_android.ui.popup.guide
+
+import android.content.Context
+import android.view.View
+import android.view.Window
+import android.widget.TextView
+import com.atmob.keyboard_android.R
+import com.atmob.keyboard_android.ext.click
+import com.atmob.keyboard_android.ext.measureView
+import com.atmob.keyboard_android.model.AiKeyboardKeyModel
+import com.atmob.keyboard_android.ui.popup.base.BasePopupDialog
+import com.atmob.keyboard_android.util.ViewLocationUtil
+import com.blankj.utilcode.util.ConvertUtils
+
+/**
+ * 键盘引导弹窗
+ */
+class KeyboardGuideDialog(context: Context, hostWindow: Window) :
+    BasePopupDialog(context, hostWindow) {
+    private lateinit var vRoot: View
+    private lateinit var vFakeTargetView: View
+    private lateinit var vKeyText: TextView
+
+    /**
+     * 目标View的位置、大小信息
+     */
+    private var mTargetViewInfo: TargetViewInfo? = null
+
+    /**
+     * 操作回调
+     */
+    private var mOnActionCallback: OnActionCallback? = null
+
+    override fun onInflaterViewId(): Int {
+        return R.layout.layout_keyboard_guide
+    }
+
+    override fun onInflaterViewAfter(view: View) {
+        super.onInflaterViewAfter(view)
+        vRoot = view.findViewById(R.id.root)
+        vFakeTargetView = view.findViewById(R.id.fake_target_view)
+        vKeyText = view.findViewById(R.id.key_text)
+    }
+
+    override fun onBindView(view: View) {
+        super.onBindView(view)
+        vRoot.click {
+            // 关闭弹窗
+            dismiss()
+            // 结束引导
+            mOnActionCallback?.onFinishGuide()
+        }
+        render()
+    }
+
+    override fun onSetupDialogFrameSize(
+        context: Context,
+        windowWidth: Int,
+        windowHeight: Int
+    ): IntArray {
+        return intArrayOf(
+            windowWidth,
+            // 键盘的高度
+            context.resources.getDimensionPixelSize(R.dimen.keyboard_height)
+        )
+    }
+
+    private fun render() {
+        // 更新内容文字
+        mTargetViewInfo?.let {
+            vKeyText.text = it.dataModel.text
+        }
+        // 更新位置
+        updateFakeTargetViewLocation()
+    }
+
+    /**
+     * 更新假的目标View的位置
+     */
+    private fun updateFakeTargetViewLocation() {
+        mTargetViewInfo?.let {
+            // 设置假目标View的位置
+            vFakeTargetView.translationX = it.x.toFloat()
+            vFakeTargetView.translationY = it.y.toFloat()
+            // 设置假目标View的大小
+            vFakeTargetView.apply {
+                layoutParams.apply {
+                    // 不知道为什么,获取到的实际的宽度,会比显示的小一点点,高度却是正常的,所以故意加回一点宽度来处理
+                    width = it.width + ConvertUtils.dp2px(6f)
+                    height = it.height
+                }
+                requestLayout()
+            }
+        }
+    }
+
+    /**
+     * 目标View的信息
+     */
+    data class TargetViewInfo(
+        val x: Int,
+        val y: Int,
+        val width: Int,
+        val height: Int,
+        // 数据信息
+        val dataModel: AiKeyboardKeyModel
+    )
+
+    interface OnActionCallback {
+        /**
+         * 结束引导时回调
+         */
+        fun onFinishGuide()
+    }
+
+    /**
+     * 绑定目标View
+     */
+    fun bindTargetView(targetView: View, dataModel: AiKeyboardKeyModel): KeyboardGuideDialog {
+        // View在Window中的位置
+        val viewLocationInWindow = ViewLocationUtil.getViewLocationInWindow(targetView)
+        // View的宽高
+        val viewSize = measureView(targetView)
+
+        // 封装数据
+        mTargetViewInfo = TargetViewInfo(
+            x = viewLocationInWindow[0],
+            y = viewLocationInWindow[1],
+            width = viewSize.first,
+            height = viewSize.second,
+            dataModel = dataModel
+        )
+
+        // 重新渲染
+        render()
+
+        return this
+    }
+
+    /**
+     * 绑定操作回调
+     */
+    fun bindOnActionCallback(callback: OnActionCallback): KeyboardGuideDialog {
+        this.mOnActionCallback = callback
+        return this
+    }
+}

+ 34 - 0
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/util/KeyboardGuideDialogUtil.kt

@@ -0,0 +1,34 @@
+package com.atmob.keyboard_android.util
+
+import android.view.View
+import com.atmob.keyboard_android.model.AiKeyboardKeyModel
+import com.atmob.keyboard_android.ui.popup.guide.KeyboardGuideDialog
+import com.atmob.keyboard_android.ui.popup.guide.KeyboardGuideDialog.OnActionCallback
+
+/**
+ * 键盘引导弹窗工具类
+ */
+class KeyboardGuideDialogUtil {
+    companion object {
+        /**
+         * 显示权限弹窗
+         */
+        fun showGuideDialog(
+            targetView: View,
+            dataModel: AiKeyboardKeyModel,
+            actionCallback: OnActionCallback
+        ) {
+            val keyboardService = KeyboardHolder.getKeyboardService()
+            if (keyboardService == null) {
+                return
+            }
+
+            val keyboardWindow = keyboardService.getKeyboardWindow()
+
+            KeyboardGuideDialog(targetView.context, keyboardWindow)
+                .bindTargetView(targetView, dataModel)
+                .bindOnActionCallback(actionCallback)
+                .show()
+        }
+    }
+}

+ 26 - 0
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/util/KeyboardGuideRecordUtil.kt

@@ -0,0 +1,26 @@
+package com.atmob.keyboard_android.util
+
+import com.atmob.keyboard_android.constant.Constants
+import com.blankj.utilcode.util.SPUtils
+
+
+/**
+ * 键盘引导记录工具类
+ */
+class KeyboardGuideRecordUtil private constructor() {
+    companion object {
+        /**
+         * 是否第一次显示键盘引导
+         */
+        fun isFirstShowKeyboardGuide(): Boolean {
+            return SPUtils.getInstance().getBoolean(Constants.IS_FIRST_SHOW_KEYBOARD_GUIDE, true)
+        }
+
+        /**
+         * 设置是否第一次显示键盘引导
+         */
+        fun setFirstShowKeyboardGuide(isFirst: Boolean) {
+            SPUtils.getInstance().put(Constants.IS_FIRST_SHOW_KEYBOARD_GUIDE, isFirst)
+        }
+    }
+}

+ 23 - 0
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/util/ViewLocationUtil.kt

@@ -0,0 +1,23 @@
+package com.atmob.keyboard_android.util
+
+import android.view.View
+
+/**
+ * View的位置工具类
+ */
+class ViewLocationUtil private constructor() {
+    companion object {
+        /**
+         * 获取View在Window中的坐标
+         */
+        fun getViewLocationInWindow(view: View): IntArray {
+            val location = IntArray(2)
+            view.getLocationInWindow(location)
+            // View 左上角的 X 坐标
+            val x = location[0]
+            // View 左上角的 Y 坐标
+            val y = location[1]
+            return intArrayOf(x, y)
+        }
+    }
+}

+ 38 - 0
plugins/keyboard_android/android/src/main/res/layout/layout_keyboard_guide.xml

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/bg_dialog_dim2"
+    android:clipChildren="false">
+
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="end"
+        android:layout_marginTop="28dp"
+        android:layout_marginEnd="43dp"
+        android:src="@mipmap/ic_keyboard_plugin_guide_overlay1" />
+
+    <FrameLayout
+        android:id="@+id/fake_target_view"
+        android:layout_width="86dp"
+        android:layout_height="46dp"
+        android:background="@drawable/bg_ai_keyboard_key">
+
+        <TextView
+            android:id="@+id/key_text"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            android:ellipsize="end"
+            android:gravity="center"
+            android:maxLines="1"
+            android:paddingStart="3dp"
+            android:paddingEnd="3dp"
+            android:textColor="@color/text_color_primary"
+            android:textSize="14sp"
+            tools:text="哄女友" />
+    </FrameLayout>
+</FrameLayout>

BIN
plugins/keyboard_android/android/src/main/res/mipmap-xxxhdpi/ic_keyboard_plugin_guide_overlay1.webp


+ 2 - 0
plugins/keyboard_android/android/src/main/res/values/colors.xml

@@ -37,6 +37,8 @@
     <color name="text_permission_tip">#FFA616</color>
     <color name="text_permission_tip">#FFA616</color>
     <!-- 弹窗背景阴暗颜色 -->
     <!-- 弹窗背景阴暗颜色 -->
     <color name="bg_dialog_dim">#26000000</color>
     <color name="bg_dialog_dim">#26000000</color>
+    <!-- 弹窗背景阴暗颜色2 -->
+    <color name="bg_dialog_dim2">#B2000000</color>
     <!-- 弹窗的操作按钮背景颜色 -->
     <!-- 弹窗的操作按钮背景颜色 -->
     <color name="bg_dialog_action_btn_1">#F6F5FA</color>
     <color name="bg_dialog_action_btn_1">#F6F5FA</color>
 </resources>
 </resources>