Sfoglia il codice sorgente

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

hezihao 7 mesi fa
parent
commit
c3f0ff2238

BIN
assets/images/icon_keyboard_guide_overlay1.webp


+ 2 - 3
lib/data/consts/constants.dart

@@ -1,5 +1,3 @@
-
-
 import '../../utils/mmkv_util.dart';
 
 class Constants {
@@ -44,7 +42,8 @@ class Constants {
   // 首次进入应用
   static const String isFirstIntro = 'isFirstIntro';
 
-
+  // 是否首次显示键盘引导
+  static const String isFirstShowKeyboardGuide = 'isFirstShowKeyboardGuide';
 }
 
 String getBaseUrl() {

+ 51 - 0
lib/module/keyboard_guide/guide_overlay/keyboard_guide_overlay_dialog.dart

@@ -0,0 +1,51 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
+import 'package:keyboard/resource/assets.gen.dart';
+
+import '../../../resource/colors.gen.dart';
+import '../../../utils/widget_location_util.dart';
+
+/// 键盘引导-引导覆盖弹窗
+class KeyboardGuideOverlayDialog {
+  static final tag = "KeyboardGuideOverlayDialog";
+
+  /// 显示
+  /// [targetWidgetKey] 目标组件的key
+  /// [onFinishCallback] 结束引导时回调
+  static void show(
+    GlobalKey targetWidgetKey, {
+    required VoidCallback onFinishCallback,
+  }) {
+    // 获取组件的位置信息
+    var targetWidgetInfo = WidgetLocationUtil.getWidgetLocation(
+      targetWidgetKey,
+    );
+    SmartDialog.show(
+      tag: tag,
+      backType: SmartBackType.normal,
+      clickMaskDismiss: true,
+      maskColor: ColorName.black70,
+      onDismiss: () {
+        onFinishCallback();
+      },
+      builder: (_) {
+        return SizedBox(
+          width: double.infinity,
+          height: double.infinity,
+          child: Stack(
+            children: [
+              Positioned(
+                left: targetWidgetInfo.position.dx,
+                top: targetWidgetInfo.position.dy,
+                child: Assets.images.iconKeyboardGuideOverlay1.image(
+                  width: 320.w,
+                ),
+              ),
+            ],
+          ),
+        );
+      },
+    );
+  }
+}

+ 26 - 0
lib/module/keyboard_guide/keyboard_guide_controller.dart

@@ -6,11 +6,19 @@ import 'package:keyboard/resource/string.gen.dart';
 
 import '../../base/base_controller.dart';
 import '../../data/bean/keyboard_guide_msg.dart';
+import '../../utils/keyboard_guide_record_util.dart';
 import '../../utils/toast_util.dart';
+import 'guide_overlay/keyboard_guide_overlay_dialog.dart';
 
 /// 键盘引导页面Controller
 @injectable
 class KeyboardGuidePageController extends BaseController {
+  /// 引导消息的GlobalKey
+  final GlobalKey guideMsgGlobalKey = GlobalKey();
+
+  /// 第几条消息为引导消息
+  final Rx<int> guideMsgIndex = 1.obs;
+
   /// TextField操作控制器
   final TextEditingController editingController = TextEditingController();
 
@@ -142,4 +150,22 @@ class KeyboardGuidePageController extends BaseController {
       });
     }
   }
+
+  /// 显示引导覆盖层弹窗
+  void showGuideOverlayDialog() {
+    // 显示过引导弹窗,则不显示
+    if (!KeyboardGuideRecordUtil.isFirstShowKeyboardGuide()) {
+      return;
+    }
+    // 首帧结束后,再显示
+    WidgetsBinding.instance.addPostFrameCallback((_) {
+      KeyboardGuideOverlayDialog.show(
+        guideMsgGlobalKey,
+        onFinishCallback: () {
+          // 设置未非首次显示
+          KeyboardGuideRecordUtil.setFirstShowKeyboardGuide(false);
+        },
+      );
+    });
+  }
 }

+ 70 - 52
lib/module/keyboard_guide/keyboard_guide_page.dart

@@ -12,6 +12,7 @@ import '../../resource/colors.gen.dart';
 import '../../resource/string.gen.dart';
 import '../../utils/clipboard_util.dart';
 import '../../utils/url_launcher_util.dart';
+import '../../widget/delegate_lifecycle_widget.dart';
 import 'enums/keyboard_guide_msg_type.dart';
 
 /// 键盘引导页面
@@ -31,26 +32,32 @@ class KeyboardGuidePage extends BasePage<KeyboardGuidePageController> {
   Widget buildBody(BuildContext context) {
     return Scaffold(
       backgroundColor: backgroundColor(),
-      body: Column(
-        children: [
-          // 标题栏
-          _buildTitleBar(),
-          // 消息列表
-          Expanded(
-            flex: 1,
-            child: Obx(() {
-              return ListView.builder(
-                controller: controller.scrollController,
-                itemCount: controller.msgList.length,
-                itemBuilder: (BuildContext context, int index) {
-                  return _buildMsgItem(controller.msgList[index]);
-                },
-              );
-            }),
-          ),
-          // 底部输入栏
-          _buildBottomInput(),
-        ],
+      body: DelegateLifecycleWidget(
+        onCreateCallback: () {
+          controller.showGuideOverlayDialog();
+        },
+        child: Column(
+          children: [
+            // 标题栏
+            _buildTitleBar(),
+            // 消息列表
+            Expanded(
+              flex: 1,
+              child: Obx(() {
+                return ListView.builder(
+                  controller: controller.scrollController,
+                  itemCount: controller.msgList.length,
+                  itemBuilder: (BuildContext context, int index) {
+                    KeyboardGuideMsg msg = controller.msgList[index];
+                    return _buildMsgItem(msg, index);
+                  },
+                );
+              }),
+            ),
+            // 底部输入栏
+            _buildBottomInput(),
+          ],
+        ),
       ),
     );
   }
@@ -291,45 +298,56 @@ class KeyboardGuidePage extends BasePage<KeyboardGuidePageController> {
   }
 
   /// 构建聊天消息列表项
-  Widget _buildMsgItem(KeyboardGuideMsg msg) {
-    Widget content;
-    // 自己发的
-    if (msg.isMe) {
-      content = Row(
-        // 如果是自己发的,则在右边
-        mainAxisAlignment: MainAxisAlignment.end,
-        // 顶部对齐
-        crossAxisAlignment: CrossAxisAlignment.start,
-        children: [
-          // 聊天气泡
-          _buildMsgBubble(msg),
-          // 头像
-          _buildAvatar(msg),
-        ],
-      );
-    } else {
-      // 对方发的
-      content = Row(
-        // 如果是自己发的,则在右边
-        mainAxisAlignment: MainAxisAlignment.start,
-        // 顶部对齐
-        crossAxisAlignment: CrossAxisAlignment.start,
-        children: [
-          // 头像
-          _buildAvatar(msg),
-          // 聊天气泡
-          _buildMsgBubble(msg),
-        ],
+  Widget _buildMsgItem(KeyboardGuideMsg msg, int index) {
+    return Obx(() {
+      Widget content;
+      // 自己发的
+      if (msg.isMe) {
+        content = Row(
+          // 如果是自己发的,则在右边
+          mainAxisAlignment: MainAxisAlignment.end,
+          // 顶部对齐
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            // 聊天气泡
+            _buildMsgBubble(msg),
+            // 头像
+            _buildAvatar(msg),
+          ],
+        );
+      } else {
+        // 对方发的
+        content = Row(
+          // 如果是自己发的,则在右边
+          mainAxisAlignment: MainAxisAlignment.start,
+          // 顶部对齐
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            // 头像
+            _buildAvatar(msg),
+            // 聊天气泡
+            _buildMsgBubble(msg),
+          ],
+        );
+      }
+
+      bool isTargetGuildMsg = controller.guideMsgIndex.value == index;
+
+      return Container(
+        margin: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.0.h),
+        child:
+            isTargetGuildMsg
+                ? Container(key: controller.guideMsgGlobalKey, child: content)
+                : content,
       );
-    }
-    return Container(padding: const EdgeInsets.all(8.0), child: content);
+    });
   }
 
   /// 构建头像
   Widget _buildAvatar(KeyboardGuideMsg msg) {
     double avatarSize = 36.0;
     return Container(
-      margin: const EdgeInsets.symmetric(horizontal: 9.0),
+      margin: const EdgeInsets.only(right: 9.0),
       child: CircleAvatar(
         radius: 20,
         child:

+ 5 - 0
lib/resource/assets.gen.dart

@@ -676,6 +676,10 @@ class $AssetsImagesGen {
   AssetGenImage get iconKeyboardExplosivePlay =>
       const AssetGenImage('assets/images/icon_keyboard_explosive_play.webp');
 
+  /// File path: assets/images/icon_keyboard_guide_overlay1.webp
+  AssetGenImage get iconKeyboardGuideOverlay1 =>
+      const AssetGenImage('assets/images/icon_keyboard_guide_overlay1.webp');
+
   /// File path: assets/images/icon_keyboard_hit_play.webp
   AssetGenImage get iconKeyboardHitPlay =>
       const AssetGenImage('assets/images/icon_keyboard_hit_play.webp');
@@ -1279,6 +1283,7 @@ class $AssetsImagesGen {
     iconKeyboardCurrentGo,
     iconKeyboardDefaultAvatar,
     iconKeyboardExplosivePlay,
+    iconKeyboardGuideOverlay1,
     iconKeyboardHitPlay,
     iconKeyboardInitmacyTitle,
     iconKeyboardIntimacyLogo,

+ 15 - 0
lib/utils/keyboard_guide_record_util.dart

@@ -0,0 +1,15 @@
+import '../data/consts/constants.dart';
+import 'mmkv_util.dart';
+
+/// 键盘引导记录工具类
+class KeyboardGuideRecordUtil {
+  /// 是否第一次显示键盘引导
+  static bool isFirstShowKeyboardGuide() {
+    return KVUtil.getBool(Constants.isFirstShowKeyboardGuide, true);
+  }
+
+  /// 设置是否第一次显示键盘引导
+  static void setFirstShowKeyboardGuide(bool isFirst) {
+    KVUtil.putBool(Constants.isFirstShowKeyboardGuide, isFirst);
+  }
+}

+ 39 - 0
lib/utils/widget_location_util.dart

@@ -0,0 +1,39 @@
+import 'package:flutter/cupertino.dart';
+
+/// 组件位置工具类
+class WidgetLocationUtil {
+  /// 获取组件的位置信息
+  static WidgetInfo getWidgetLocation(GlobalKey key) {
+    final context = key.currentContext;
+
+    if (context == null) {
+      throw Exception('Context is null');
+    }
+
+    final renderObject = context.findRenderObject();
+    if (renderObject == null || renderObject is! RenderBox) {
+      throw Exception('RenderObject is null or not a RenderBox');
+    }
+
+    final offset = renderObject.localToGlobal(Offset.zero);
+    final size = renderObject.size;
+
+    return WidgetInfo(offset, size);
+  }
+}
+
+/// 组件信息
+class WidgetInfo {
+  // 位置
+  final Offset position;
+
+  // 大小
+  final Size size;
+
+  WidgetInfo(this.position, this.size);
+
+  @override
+  String toString() {
+    return 'WidgetInfo{position: $position, size: $size}';
+  }
+}