Ver Fonte

[feat]亲密度分析,增加添加预测方向弹窗

hezihao há 7 meses atrás
pai
commit
0dbf49e879

BIN
assets/images/icon_custom_direction_edit_close.webp


+ 3 - 0
assets/string/base/string.xml

@@ -241,6 +241,9 @@
     <string name="keyboard_guide_wechat_not_install">未安装微信</string>
     <string name="keyboard_guide_input_hint">选择粘贴TA的话,选择人设风格回复</string>
 
+    <string name="input_common_hint">输入你的内容</string>
+    <string name="input_min_input_tip">输入的内容至少%s个字</string>
+
     <string name="keyboard_guide_ta_reply1">👋
         欢迎使用【追爱小键盘】\n复制任意一句对话,点击人设体验回复
     </string>

+ 7 - 0
lib/di/get_it.config.dart

@@ -53,6 +53,8 @@ import '../module/intimacy_analyse/analyse_report/intimacy_analyse_report_view_c
 import '../module/intimacy_analyse/image_viewer/image_viewer_controller.dart'
     as _i1071;
 import '../module/intimacy_analyse/intimacy_analyse_controller.dart' as _i977;
+import '../module/intimacy_analyse/intimacy_analyse_upload/dialog/direction/custom_direction_edit_controller.dart'
+    as _i278;
 import '../module/intimacy_analyse/intimacy_analyse_upload/intimacy_analyse_upload_controller.dart'
     as _i666;
 import '../module/intimacy_analyse/screenshot_reply/conversation_analysis/conversation_analysis_controller.dart'
@@ -315,6 +317,11 @@ extension GetItInjectableX on _i174.GetIt {
         gh<_i428.UploadFileManager>(),
       ),
     );
+    gh.factory<_i278.CustomDirectionEditController>(
+      () => _i278.CustomDirectionEditController(
+        gh<_i738.IntimacyAnalyzeConfigHelper>(),
+      ),
+    );
     gh.factory<_i666.IntimacyAnalyseUploadController>(
       () => _i666.IntimacyAnalyseUploadController(
         gh<_i283.IntimacyAnalyzeRepository>(),

+ 83 - 0
lib/module/intimacy_analyse/intimacy_analyse_upload/dialog/direction/custom_direction_edit_controller.dart

@@ -0,0 +1,83 @@
+import 'package:flutter/cupertino.dart';
+import 'package:injectable/injectable.dart';
+import 'package:keyboard/base/base_controller.dart';
+import 'package:keyboard/resource/string.gen.dart';
+import 'package:keyboard/utils/string_format_util.dart';
+import 'package:keyboard/utils/toast_util.dart';
+
+import '../../../../../utils/intimacy_analyze_config_helper.dart';
+import 'custom_direction_edit_view.dart';
+
+/// 自定义预测方向的编辑弹窗的Controller
+@injectable
+class CustomDirectionEditController extends BaseController {
+  /// 亲密度分析的帮助类
+  final IntimacyAnalyzeConfigHelper intimacyAnalyzeConfigHelper;
+
+  /// TextField操作控制器
+  final TextEditingController editingController = TextEditingController();
+
+  /// 输入框焦点
+  final FocusNode inputFocusNode = FocusNode();
+
+  CustomDirectionEditController(this.intimacyAnalyzeConfigHelper);
+
+  @override
+  void onClose() {
+    inputFocusNode.dispose();
+    editingController.dispose();
+    super.onClose();
+  }
+
+  /// 获取自定义预测方向,最大自定义字符数
+  int getMaxCustomDirectionWords() {
+    return intimacyAnalyzeConfigHelper.getMaxCustomDirectionWords();
+  }
+
+  /// 保存
+  void doOnSave(OnSaveCallback onSaveCallback) {
+    // 必须输入内容
+    String userInput = editingController.text.trim();
+
+    if (userInput.isEmpty) {
+      ToastUtil.show(StringName.inputCommonHint);
+      return;
+    }
+
+    // 限制最小保存字符
+    int minCustomDirectionWords =
+        intimacyAnalyzeConfigHelper.getMinCustomDirectionWords();
+    if (userInput.length < minCustomDirectionWords) {
+      String msg = StringFormatUtil.formatStr(
+        StringName.inputMininputTip,
+        minCustomDirectionWords.toString(),
+      );
+      ToastUtil.show(msg);
+      return;
+    }
+
+    onSaveCallback(userInput);
+
+    // 清空输入框
+    editingController.text = "";
+  }
+
+  /// 关闭
+  void doClose(OnCloseCallback onCloseCallback) {
+    // 清空输入框
+    editingController.text = "";
+    onCloseCallback();
+  }
+
+  /// 让输入框获取焦点
+  void doRequestInputFocus() {
+    Future.delayed(const Duration(milliseconds: 300), () {
+      inputFocusNode.requestFocus();
+    });
+  }
+
+  /// 让输入框失去焦点
+  void doInputUnFocus() {
+    inputFocusNode.unfocus();
+  }
+}

+ 47 - 0
lib/module/intimacy_analyse/intimacy_analyse_upload/dialog/direction/custom_direction_edit_dialog.dart

@@ -0,0 +1,47 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
+
+import 'custom_direction_edit_view.dart';
+
+/// 自定义预测方向的编辑弹窗
+class CustomDirectionEditDialog {
+  static const String _tag = "CustomDirectionEditDialog";
+
+  /// 显示弹窗
+  /// [onSaveCallback] 保存时回调
+  static void show(OnSaveCallback onSaveCallback) {
+    SmartDialog.show(
+      tag: _tag,
+      // 点击遮罩,不关闭弹窗
+      clickMaskDismiss: false,
+      // 内容居中显示
+      alignment: Alignment.center,
+      // 动画类型
+      animationType: SmartAnimationType.fade,
+      builder:
+          (BuildContext context) => Container(
+            margin: EdgeInsets.only(
+              // 动态添加底部间距,避免软键盘弹起时,遮挡弹窗
+              bottom: MediaQuery.of(context).viewInsets.bottom,
+            ),
+            // 高度,包裹内容
+            child: IntrinsicHeight(
+              child: Container(
+                padding: EdgeInsets.symmetric(horizontal: 32.w),
+                child: CustomDirectionEditView(
+                  onSaveCallback: (String customDirection) {
+                    onSaveCallback(customDirection);
+                    SmartDialog.dismiss(tag: _tag);
+                  },
+                  onCloseCallback: () {
+                    // 关闭弹窗
+                    SmartDialog.dismiss(tag: _tag);
+                  },
+                ),
+              ),
+            ),
+          ),
+    );
+  }
+}

+ 228 - 0
lib/module/intimacy_analyse/intimacy_analyse_upload/dialog/direction/custom_direction_edit_view.dart

@@ -0,0 +1,228 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:keyboard/base/base_view.dart';
+import 'package:keyboard/module/intimacy_analyse/intimacy_analyse_upload/dialog/direction/custom_direction_edit_controller.dart';
+import 'package:keyboard/resource/assets.gen.dart';
+import 'package:keyboard/resource/colors.gen.dart';
+
+import '../../../../../resource/string.gen.dart';
+
+/// 保存时回调
+typedef OnSaveCallback = void Function(String customDirection);
+
+/// 关闭弹窗时回调
+typedef OnCloseCallback = void Function();
+
+/// 自定义预测方向的编辑弹窗的内容
+class CustomDirectionEditView extends BaseView<CustomDirectionEditController> {
+  final OnSaveCallback onSaveCallback;
+  final OnCloseCallback onCloseCallback;
+
+  const CustomDirectionEditView({
+    super.key,
+    required this.onSaveCallback,
+    required this.onCloseCallback,
+  });
+
+  @override
+  backgroundColor() => Colors.transparent;
+
+  @override
+  Widget buildBody(BuildContext context) {
+    return DelegateLifecycleWidget(
+      onCreateCallback: () {
+        // 进入页面,就获取输入框焦点
+        controller.doRequestInputFocus();
+      },
+      child: Stack(
+        children: [
+          Container(
+            padding: EdgeInsets.symmetric(vertical: 24.h, horizontal: 16.w),
+            decoration: BoxDecoration(
+              color: ColorName.white,
+              borderRadius: BorderRadius.all(Radius.circular(16.r)),
+            ),
+            child: Column(
+              // 包裹内容
+              mainAxisSize: MainAxisSize.min,
+              children: [
+                // 标题
+                _buildTitle(),
+                SizedBox(height: 16.h),
+                // 输入框
+                _buildInput(),
+                SizedBox(height: 24.h),
+                _buildSaveBtn(),
+              ],
+            ),
+          ),
+          Positioned(
+            top: 0,
+            right: 0,
+            child: InkWell(
+              onTap: () {
+                controller.doClose(onCloseCallback);
+              },
+              splashColor: ColorName.transparent,
+              child: Container(
+                padding: EdgeInsets.all(14.w),
+                child: Assets.images.iconCustomDirectionEditClose.image(
+                  width: 24.w,
+                  height: 24.w,
+                ),
+              ),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  /// 标题
+  Widget _buildTitle() {
+    return Text(
+      StringName.intimacyAnalyseAddPredictionDirection,
+      style: TextStyle(
+        fontSize: 16.sp,
+        color: ColorName.black80,
+        fontWeight: FontWeight.bold,
+      ),
+    );
+  }
+
+  /// 输入框
+  Widget _buildInput() {
+    return TextField(
+      // 点击输入框外部,关闭软键盘
+      onTapUpOutside: (event) {
+        controller.doInputUnFocus();
+      },
+      // 焦点控制
+      focusNode: controller.inputFocusNode,
+      // 输入框控制
+      controller: controller.editingController,
+      inputFormatters: [
+        // 限定最大字符长度
+        LengthLimitingTextInputFormatter(
+          controller.getMaxCustomDirectionWords(),
+        ),
+      ],
+      // 文字居中显示
+      textAlign: TextAlign.center,
+      // 单行
+      maxLines: 1,
+      // 设置光标颜色
+      cursorColor: ColorName.inputCursor,
+      // 光标宽度
+      cursorWidth: 2.w,
+      // 光标圆角
+      cursorRadius: Radius.circular(2.r),
+      // 输入框的边框样式
+      decoration: InputDecoration(
+        // 启用背景填充,背景才会生效
+        filled: true,
+        // 设置背景颜色
+        fillColor: Color(0xFFF6F5FA),
+        // 调整内边距
+        contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 10),
+        // 设置边框样式
+        border: OutlineInputBorder(
+          // 圆角半径
+          borderRadius: BorderRadius.circular(31.0),
+          // 隐藏边框线
+          borderSide: BorderSide.none,
+        ),
+        // 未输入时的提示文字
+        hintText: StringName.inputCommonHint,
+        hintStyle: TextStyle(
+          color: Colors.black45,
+          fontSize: 14.sp,
+          fontWeight: FontWeight.w400,
+        ),
+      ),
+      style: TextStyle(
+        color: ColorName.black80,
+        fontSize: 14.sp,
+        fontWeight: FontWeight.w400,
+      ),
+    );
+  }
+
+  /// 保存按钮
+  Widget _buildSaveBtn() {
+    return GestureDetector(
+      onTap: () {
+        controller.doOnSave(onSaveCallback);
+      },
+      child: Container(
+        padding: EdgeInsets.symmetric(vertical: 14.h),
+        width: double.maxFinite,
+        decoration: ShapeDecoration(
+          // 渐变背景
+          gradient: LinearGradient(
+            colors: [ColorName.purpleGradient3, ColorName.purpleGradient4],
+            begin: Alignment.centerLeft,
+            end: Alignment.centerRight,
+          ),
+          shape: RoundedRectangleBorder(
+            borderRadius: BorderRadius.circular(50.r),
+          ),
+        ),
+        child: Center(
+          child: Text(
+            StringName.save,
+            style: TextStyle(
+              color: ColorName.white,
+              fontSize: 16.sp,
+              fontWeight: FontWeight.w500,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+/// 由于Getx的Controller用了单例,导致无法监听到组件的生命周期,所以用一个StatefulWidget来获取Widget的生命周期
+class DelegateLifecycleWidget extends StatefulWidget {
+  final Widget child;
+
+  final Function? onCreateCallback;
+  final Function? onDestroyCallback;
+
+  const DelegateLifecycleWidget({
+    super.key,
+    required this.child,
+    this.onCreateCallback,
+    this.onDestroyCallback,
+  });
+
+  @override
+  State<StatefulWidget> createState() {
+    return _DelegateLifecycleWidgetState();
+  }
+}
+
+class _DelegateLifecycleWidgetState extends State<DelegateLifecycleWidget> {
+  @override
+  void initState() {
+    super.initState();
+    if (widget.onCreateCallback != null) {
+      widget.onCreateCallback!();
+    }
+  }
+
+  @override
+  void dispose() {
+    if (widget.onDestroyCallback != null) {
+      widget.onDestroyCallback!();
+    }
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return widget.child;
+  }
+}

+ 5 - 0
lib/module/intimacy_analyse/intimacy_analyse_upload/intimacy_analyse_upload_controller.dart

@@ -25,6 +25,7 @@ import '../../../utils/intimacy_analyze_config_helper.dart';
 import '../../../utils/upload/upload_file_manager.dart';
 import '../../profile/profile_page.dart';
 import '../../store/store_page.dart';
+import 'dialog/direction/custom_direction_edit_dialog.dart';
 
 /// 亲密度分析上传页Controller
 @injectable
@@ -257,6 +258,10 @@ class IntimacyAnalyseUploadController extends BaseController {
   /// 添加预测方向
   void addDirection() {
     // 弹出预测方向的编辑弹窗
+    CustomDirectionEditDialog.show((String customDirection) {
+      // 调用接口,新增,并添加一行到页面上
+      ToastUtil.show("保存成功");
+    });
   }
 
   /// 预测方向,选中选项

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

@@ -425,6 +425,11 @@ class $AssetsImagesGen {
   AssetGenImage get iconCustomDialogClose =>
       const AssetGenImage('assets/images/icon_custom_dialog_close.webp');
 
+  /// File path: assets/images/icon_custom_direction_edit_close.webp
+  AssetGenImage get iconCustomDirectionEditClose => const AssetGenImage(
+    'assets/images/icon_custom_direction_edit_close.webp',
+  );
+
   /// File path: assets/images/icon_default_avatar.webp
   AssetGenImage get iconDefaultAvatar =>
       const AssetGenImage('assets/images/icon_default_avatar.webp');
@@ -1060,6 +1065,7 @@ class $AssetsImagesGen {
     iconCopy,
     iconCustomCharacterAddMarket,
     iconCustomDialogClose,
+    iconCustomDirectionEditClose,
     iconDefaultAvatar,
     iconDefaultAvatarFemale,
     iconDefaultAvatarMale,

+ 4 - 0
lib/resource/string.gen.dart

@@ -177,6 +177,8 @@ class StringName {
   static final String keyboardGuideGoWechat = 'keyboard_guide_go_wechat'.tr; // 去微信体验
   static final String keyboardGuideWechatNotInstall = 'keyboard_guide_wechat_not_install'.tr; // 未安装微信
   static final String keyboardGuideInputHint = 'keyboard_guide_input_hint'.tr; // 选择粘贴TA的话,选择人设风格回复
+  static final String inputCommonHint = 'input_common_hint'.tr; // 输入你的内容
+  static final String inputMininputTip = 'input_min_input_tip'.tr; // 输入的内容至少%s个字
   static final String keyboardGuideTaReply1 = 'keyboard_guide_ta_reply1'.tr; // 👋 欢迎使用【追爱小键盘】\n复制任意一句对话,点击人设体验回复
   static final String keyboardGuideTaReply2 = 'keyboard_guide_ta_reply2'.tr; // 你睡了吗?
   static final String keyboardGuideTaReply3 = 'keyboard_guide_ta_reply3'.tr; // 我先去吃饭了,一会聊
@@ -416,6 +418,8 @@ class StringMultiSource {
       'keyboard_guide_go_wechat': '去微信体验',
       'keyboard_guide_wechat_not_install': '未安装微信',
       'keyboard_guide_input_hint': '选择粘贴TA的话,选择人设风格回复',
+      'input_common_hint': '输入你的内容',
+      'input_min_input_tip': '输入的内容至少%s个字',
       'keyboard_guide_ta_reply1': '👋 欢迎使用【追爱小键盘】\n复制任意一句对话,点击人设体验回复',
       'keyboard_guide_ta_reply2': '你睡了吗?',
       'keyboard_guide_ta_reply3': '我先去吃饭了,一会聊',

+ 2 - 0
lib/router/app_pages.dart

@@ -42,6 +42,7 @@ import '../module/feedback/feedback_page.dart';
 import '../module/intimacy_analyse/analyse_report/intimacy_analyse_report_view_controller.dart';
 import '../module/intimacy_analyse/image_viewer/image_viewer_controller.dart';
 import '../module/intimacy_analyse/image_viewer/image_viewer_page.dart';
+import '../module/intimacy_analyse/intimacy_analyse_upload/dialog/direction/custom_direction_edit_controller.dart';
 import '../module/intimacy_analyse/intimacy_analyse_upload/intimacy_analyse_upload_controller.dart';
 import '../module/intimacy_analyse/intimacy_analyse_upload/intimacy_analyse_upload_page.dart';
 import '../module/intimacy_analyse/screenshot_reply/conversation_analysis/conversation_analysis_controller.dart';
@@ -133,6 +134,7 @@ class AppBinding extends Bindings {
     lazyPut(() => getIt.get<LoginDialogController>());
     lazyPut(() => getIt.get<ImageViewerController>());
     lazyPut(() => getIt.get<UserInfoController>());
+    lazyPut(() => getIt.get<CustomDirectionEditController>());
   }
 
   void lazyPut<S>(InstanceBuilderCallback<S> builder) {

+ 28 - 1
lib/utils/intimacy_analyze_config_helper.dart

@@ -16,6 +16,33 @@ class IntimacyAnalyzeConfigHelper {
 
   IntimacyAnalyzeConfigHelper(this.intimacyAnalyzeRepository);
 
+  /// 是否可以自定义预测方向
+  bool isCanCustomDirection() {
+    var config = intimacyAnalyzeConfig.value;
+    if (config == null) {
+      return false;
+    }
+    return config.custom ?? false;
+  }
+
+  /// 获取自定义预测方向,最大自定义字符数
+  int getMaxCustomDirectionWords() {
+    var config = intimacyAnalyzeConfig.value;
+    if (config == null) {
+      return 0;
+    }
+    return config.maxCustomWords ?? 0;
+  }
+
+  /// 获取自定义预测方向,最小自定义字符数
+  int getMinCustomDirectionWords() {
+    var config = intimacyAnalyzeConfig.value;
+    if (config == null) {
+      return 0;
+    }
+    return config.minCustomWords ?? 0;
+  }
+
   /// 最多,可以选多少张图片
   int getMaxAssetsCount() {
     var config = intimacyAnalyzeConfig.value;
@@ -29,4 +56,4 @@ class IntimacyAnalyzeConfigHelper {
     // 最少选多少张
     return config.minImageCount ?? 1;
   }
-}
+}