Procházet zdrojové kódy

[feat]分析亲密度,报告分析页,增加生成定制人设按钮和弹窗,对接使用报告生成人设接口

hezihao před 7 měsíci
rodič
revize
80c47f3398

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

@@ -348,6 +348,13 @@
     <string name="intimacy_intimacy_chat_strategy">聊天策略</string>
     <string name="intimacy_intimacy_summary">总结</string>
 
+    <string name="intimacy_generate_character_title">定制专属人设</string>
+    <string name="intimacy_generate_character_input_hint">输入人设名称</string>
+    <string name="intimacy_generate_character_input_tip">请输入人设名称</string>
+    <string name="intimacy_generate_character_add_keyboard">添加到键盘</string>
+    <string name="intimacy_generate_character">生成定制人设</string>
+    <string name="intimacy_generate_character_add_success">添加成功</string>
+
     <string name="preview">预览</string>
 
     <string name="retry">再试试</string>

+ 7 - 0
lib/data/api/atmob_api.dart

@@ -14,6 +14,7 @@ import 'package:keyboard/data/api/request/chat_super_speak_request.dart';
 import 'package:keyboard/data/api/request/complaint_submit_request.dart';
 import 'package:keyboard/data/api/request/config_request.dart';
 import 'package:keyboard/data/api/request/intimacy_analyze_request.dart';
+import 'package:keyboard/data/api/request/intimacy_generate_character_request.dart';
 import 'package:keyboard/data/api/request/intimacy_reply_analyze_request.dart';
 import 'package:keyboard/data/api/request/intimacy_reply_chat_request.dart';
 import 'package:keyboard/data/api/request/keyboard_character_list_request.dart';
@@ -290,6 +291,12 @@ abstract class AtmobApi {
     @Body() IntimacyAnalyzeRequest request,
   );
 
+  /// 生成亲密度人设
+  @POST("/project/keyboard/v1/intimacy/character/generate")
+  Future<BaseResponse> intimacyCharacterGenerate(
+    @Body() IntimacyGenerateCharacterRequest request,
+  );
+
   /// 获取对话分析配置
   @POST("/project/keyboard/v1/intimacy/chat/config")
   Future<BaseResponse<IntimacyAnalyzeChatConfigResponse>>

+ 33 - 0
lib/data/api/atmob_api.g.dart

@@ -1304,6 +1304,39 @@ class _AtmobApi implements AtmobApi {
   }
 
   @override
+  Future<BaseResponse<dynamic>> intimacyCharacterGenerate(
+    IntimacyGenerateCharacterRequest request,
+  ) async {
+    final _extra = <String, dynamic>{};
+    final queryParameters = <String, dynamic>{};
+    final _headers = <String, dynamic>{};
+    final _data = <String, dynamic>{};
+    _data.addAll(request.toJson());
+    final _options = _setStreamType<BaseResponse<dynamic>>(
+      Options(method: 'POST', headers: _headers, extra: _extra)
+          .compose(
+            _dio.options,
+            '/project/keyboard/v1/intimacy/character/generate',
+            queryParameters: queryParameters,
+            data: _data,
+          )
+          .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
+    );
+    final _result = await _dio.fetch<Map<String, dynamic>>(_options);
+    late BaseResponse<dynamic> _value;
+    try {
+      _value = BaseResponse<dynamic>.fromJson(
+        _result.data!,
+        (json) => json as dynamic,
+      );
+    } on Object catch (e, s) {
+      errorLogger?.logError(e, s, _options);
+      rethrow;
+    }
+    return _value;
+  }
+
+  @override
   Future<BaseResponse<IntimacyAnalyzeChatConfigResponse>>
   getIntimacyAnalyzeChatConfig(AppBaseRequest request) async {
     final _extra = <String, dynamic>{};

+ 29 - 0
lib/data/api/request/intimacy_generate_character_request.dart

@@ -0,0 +1,29 @@
+import 'package:json_annotation/json_annotation.dart';
+import 'package:keyboard/base/app_base_request.dart';
+
+part 'intimacy_generate_character_request.g.dart';
+
+/// 生成亲密度人设的请求
+@JsonSerializable()
+class IntimacyGenerateCharacterRequest extends AppBaseRequest {
+  /// 键盘id
+  @JsonKey(name: "keyboardId")
+  String keyboardId;
+
+  /// 名称
+  @JsonKey(name: "name")
+  String name;
+
+  /// 聊天策略
+  @JsonKey(name: "chatStrategy")
+  String chatStrategy;
+
+  IntimacyGenerateCharacterRequest(
+      this.keyboardId,
+      this.name,
+      this.chatStrategy,
+      );
+
+  @override
+  Map<String, dynamic> toJson() => _$IntimacyGenerateCharacterRequestToJson(this);
+}

+ 76 - 0
lib/data/api/request/intimacy_generate_character_request.g.dart

@@ -0,0 +1,76 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'intimacy_generate_character_request.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+IntimacyGenerateCharacterRequest _$IntimacyGenerateCharacterRequestFromJson(
+  Map<String, dynamic> json,
+) =>
+    IntimacyGenerateCharacterRequest(
+        json['keyboardId'] as String,
+        json['name'] as String,
+        json['chatStrategy'] as String,
+      )
+      ..appPlatform = (json['appPlatform'] as num).toInt()
+      ..os = json['os'] as String
+      ..osVersion = json['osVersion'] as String
+      ..packageName = json['packageName'] as String?
+      ..appVersionName = json['appVersionName'] as String?
+      ..appVersionCode = (json['appVersionCode'] as num?)?.toInt()
+      ..channelName = json['channelName'] as String?
+      ..appId = (json['appId'] as num?)?.toInt()
+      ..tgPlatform = (json['tgPlatform'] as num?)?.toInt()
+      ..oaid = json['oaid'] as String?
+      ..aaid = json['aaid'] as String?
+      ..androidId = json['androidId'] as String?
+      ..imei = json['imei'] as String?
+      ..simImei0 = json['simImei0'] as String?
+      ..simImei1 = json['simImei1'] as String?
+      ..mac = json['mac'] as String?
+      ..idfa = json['idfa'] as String?
+      ..idfv = json['idfv'] as String?
+      ..machineId = json['machineId'] as String?
+      ..brand = json['brand'] as String?
+      ..model = json['model'] as String?
+      ..wifiName = json['wifiName'] as String?
+      ..region = json['region'] as String?
+      ..locLng = (json['locLng'] as num?)?.toDouble()
+      ..locLat = (json['locLat'] as num?)?.toDouble()
+      ..authToken = json['authToken'] as String?;
+
+Map<String, dynamic> _$IntimacyGenerateCharacterRequestToJson(
+  IntimacyGenerateCharacterRequest instance,
+) => <String, dynamic>{
+  'appPlatform': instance.appPlatform,
+  'os': instance.os,
+  'osVersion': instance.osVersion,
+  'packageName': instance.packageName,
+  'appVersionName': instance.appVersionName,
+  'appVersionCode': instance.appVersionCode,
+  'channelName': instance.channelName,
+  'appId': instance.appId,
+  'tgPlatform': instance.tgPlatform,
+  'oaid': instance.oaid,
+  'aaid': instance.aaid,
+  'androidId': instance.androidId,
+  'imei': instance.imei,
+  'simImei0': instance.simImei0,
+  'simImei1': instance.simImei1,
+  'mac': instance.mac,
+  'idfa': instance.idfa,
+  'idfv': instance.idfv,
+  'machineId': instance.machineId,
+  'brand': instance.brand,
+  'model': instance.model,
+  'wifiName': instance.wifiName,
+  'region': instance.region,
+  'locLng': instance.locLng,
+  'locLat': instance.locLat,
+  'authToken': instance.authToken,
+  'keyboardId': instance.keyboardId,
+  'name': instance.name,
+  'chatStrategy': instance.chatStrategy,
+};

+ 60 - 46
lib/data/repository/intimacy_analyze_repository.dart

@@ -14,6 +14,7 @@ import '../../utils/sse_parse_util.dart';
 import '../api/atmob_api.dart';
 import '../api/atmob_stream_api.dart';
 import '../api/request/intimacy_analyze_request.dart';
+import '../api/request/intimacy_generate_character_request.dart';
 import '../api/request/intimacy_reply_analyze_request.dart';
 import '../api/request/intimacy_reply_chat_request.dart';
 import '../api/response/intimacy_analyze_chat_config_response.dart';
@@ -107,34 +108,36 @@ class IntimacyAnalyzeRepository {
   }
 
   /// 对话分析(SSE流式)
-  Future<Stream<Message>> intimacyChatAnalyze(IntimacyChatAnalyzeRequest request) {
+  Future<Stream<Message>> intimacyChatAnalyze(
+    IntimacyChatAnalyzeRequest request,
+  ) {
     return atmobStreamApi
         .intimacyChatAnalyze(request)
         .then((response) async {
-      List<String>? contentType = response.headers['Content-Type'];
-      if (contentType != null) {
-        for (var value in contentType) {
-          if (value.contains('text/event-stream')) {
-            return response.stream;
-          } else if (value.contains('application/json')) {
-            BaseResponse<String> baseResponse = BaseResponse.fromJson(
-              jsonDecode(
-                await response.stream
-                    .map((bytes) => utf8.decoder.convert(bytes))
-                    .toList()
-                    .then((value) => value.join()),
-              ),
+          List<String>? contentType = response.headers['Content-Type'];
+          if (contentType != null) {
+            for (var value in contentType) {
+              if (value.contains('text/event-stream')) {
+                return response.stream;
+              } else if (value.contains('application/json')) {
+                BaseResponse<String> baseResponse = BaseResponse.fromJson(
+                  jsonDecode(
+                    await response.stream
+                        .map((bytes) => utf8.decoder.convert(bytes))
+                        .toList()
+                        .then((value) => value.join()),
+                  ),
                   (json) => json as String,
-            );
-            throw ServerErrorException(
-              baseResponse.code,
-              baseResponse.message,
-            );
+                );
+                throw ServerErrorException(
+                  baseResponse.code,
+                  baseResponse.message,
+                );
+              }
+            }
           }
-        }
-      }
-      throw Exception('Invalid content type');
-    })
+          throw Exception('Invalid content type');
+        })
         .then((stream) => SSEParseUtil.parse(stream));
   }
 
@@ -153,35 +156,46 @@ class IntimacyAnalyzeRepository {
     return atmobApi.getIntimacyAnalyze(request).then(HttpHandler.handle(true));
   }
 
+  /// 生成亲密度人设
+  Future<void> intimacyCharacterGenerate(
+    IntimacyGenerateCharacterRequest request,
+  ) {
+    return atmobApi
+        .intimacyCharacterGenerate(request)
+        .then(HttpHandler.handle(true));
+  }
+
   /// 识图回复(SSE流式)
-  Future<Stream<Message>> intimacyReplyAnalyze(IntimacyReplyAnalyzeRequest request) {
+  Future<Stream<Message>> intimacyReplyAnalyze(
+    IntimacyReplyAnalyzeRequest request,
+  ) {
     return atmobStreamApi
         .intimacyReplyAnalyze(request)
         .then((response) async {
-      List<String>? contentType = response.headers['Content-Type'];
-      if (contentType != null) {
-        for (var value in contentType) {
-          if (value.contains('text/event-stream')) {
-            return response.stream;
-          } else if (value.contains('application/json')) {
-            BaseResponse<String> baseResponse = BaseResponse.fromJson(
-              jsonDecode(
-                await response.stream
-                    .map((bytes) => utf8.decoder.convert(bytes))
-                    .toList()
-                    .then((value) => value.join()),
-              ),
+          List<String>? contentType = response.headers['Content-Type'];
+          if (contentType != null) {
+            for (var value in contentType) {
+              if (value.contains('text/event-stream')) {
+                return response.stream;
+              } else if (value.contains('application/json')) {
+                BaseResponse<String> baseResponse = BaseResponse.fromJson(
+                  jsonDecode(
+                    await response.stream
+                        .map((bytes) => utf8.decoder.convert(bytes))
+                        .toList()
+                        .then((value) => value.join()),
+                  ),
                   (json) => json as String,
-            );
-            throw ServerErrorException(
-              baseResponse.code,
-              baseResponse.message,
-            );
+                );
+                throw ServerErrorException(
+                  baseResponse.code,
+                  baseResponse.message,
+                );
+              }
+            }
           }
-        }
-      }
-      throw Exception('Invalid content type');
-    })
+          throw Exception('Invalid content type');
+        })
         .then((stream) => SSEParseUtil.parse(stream));
   }
 }

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

@@ -55,6 +55,8 @@ import '../module/intimacy_analyse/image_viewer/image_viewer_controller.dart'
 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/dialog/intimacy_generate_character/intimacy_generate_character_edit_controller.dart'
+    as _i873;
 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'
@@ -122,6 +124,9 @@ extension GetItInjectableX on _i174.GetIt {
       () => _i415.KeyboardMethodHandler(),
     );
     gh.factory<_i973.SplashController>(() => _i973.SplashController());
+    gh.factory<_i873.IntimacyGenerateCharacterEditController>(
+      () => _i873.IntimacyGenerateCharacterEditController(),
+    );
     gh.lazySingleton<_i495.WechatLoginService>(
       () => _i495.WechatLoginService(),
     );

+ 1 - 42
lib/module/intimacy_analyse/intimacy_analyse_upload/dialog/direction/custom_direction_edit_view.dart

@@ -8,6 +8,7 @@ import 'package:keyboard/resource/colors.gen.dart';
 import 'package:keyboard/widget/gradient_btn.dart';
 
 import '../../../../../resource/string.gen.dart';
+import '../../../../../widget/delegate_lifecycle_widget.dart';
 
 /// 保存时回调
 typedef OnSaveCallback = void Function(String customDirection);
@@ -164,45 +165,3 @@ class CustomDirectionEditView extends BaseView<CustomDirectionEditController> {
   }
 }
 
-/// 由于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;
-  }
-}

+ 60 - 0
lib/module/intimacy_analyse/intimacy_analyse_upload/dialog/intimacy_generate_character/intimacy_generate_character_edit_controller.dart

@@ -0,0 +1,60 @@
+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/toast_util.dart';
+import 'intimacy_generate_character_edit_view.dart';
+
+/// 生成亲密度人设(生成键盘)的编辑弹窗的Controller
+@injectable
+class IntimacyGenerateCharacterEditController extends BaseController {
+  /// TextField操作控制器
+  final TextEditingController editingController = TextEditingController();
+
+  /// 输入框焦点
+  final FocusNode inputFocusNode = FocusNode();
+
+  IntimacyGenerateCharacterEditController();
+
+  @override
+  void onClose() {
+    inputFocusNode.dispose();
+    editingController.dispose();
+    super.onClose();
+  }
+
+  /// 保存
+  void doOnSave(OnSaveCallback onSaveCallback) {
+    // 必须输入内容
+    String userInput = editingController.text.trim();
+
+    if (userInput.isEmpty) {
+      ToastUtil.show(StringName.intimacyGenerateCharacterInputTip);
+      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/intimacy_generate_character/intimacy_generate_character_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 'intimacy_generate_character_edit_view.dart';
+
+/// 生成亲密度人设(生成键盘)
+class IntimacyGenerateCharacterEditDialog {
+  static const String _tag = "IntimacyGenerateCharacterEditDialog";
+
+  /// 显示弹窗
+  /// [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: IntimacyGenerateCharacterEditView(
+              onSaveCallback: (String customDirection) {
+                onSaveCallback(customDirection);
+                SmartDialog.dismiss(tag: _tag);
+              },
+              onCloseCallback: () {
+                // 关闭弹窗
+                SmartDialog.dismiss(tag: _tag);
+              },
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}

+ 160 - 0
lib/module/intimacy_analyse/intimacy_analyse_upload/dialog/intimacy_generate_character/intimacy_generate_character_edit_view.dart

@@ -0,0 +1,160 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:keyboard/base/base_view.dart';
+import 'package:keyboard/resource/assets.gen.dart';
+import 'package:keyboard/resource/colors.gen.dart';
+import 'package:keyboard/widget/gradient_btn.dart';
+
+import '../../../../../resource/string.gen.dart';
+import '../../../../../widget/delegate_lifecycle_widget.dart';
+import 'intimacy_generate_character_edit_controller.dart';
+
+/// 保存时回调
+typedef OnSaveCallback = void Function(String customDirection);
+
+/// 关闭弹窗时回调
+typedef OnCloseCallback = void Function();
+
+/// 生成亲密度人设(生成键盘)的编辑弹窗的内容
+class IntimacyGenerateCharacterEditView extends BaseView<IntimacyGenerateCharacterEditController> {
+  final OnSaveCallback onSaveCallback;
+  final OnCloseCallback onCloseCallback;
+
+  const IntimacyGenerateCharacterEditView({
+    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.intimacyGenerateCharacterTitle,
+      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,
+      // 文字居中显示
+      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.intimacyGenerateCharacterInputHint,
+        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 SizedBox(
+      width: double.infinity,
+      child: GradientTextBtn(
+        StringName.intimacyGenerateCharacterAddKeyboard,
+        onPressed: () {
+          controller.doOnSave(onSaveCallback);
+        },
+      ),
+    );
+  }
+}
+

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

@@ -12,6 +12,7 @@ import 'package:keyboard/utils/error_handler.dart';
 import 'package:keyboard/utils/toast_util.dart';
 import 'package:keyboard/utils/upload/upload_scene_type.dart';
 import 'package:wechat_assets_picker/wechat_assets_picker.dart';
+import '../../../data/api/request/intimacy_generate_character_request.dart';
 import '../../../data/api/response/intimacy_analyze_config_response.dart';
 import '../../../data/api/response/intimacy_analyze_response.dart';
 import '../../../data/api/response/user_info_response.dart';
@@ -32,6 +33,7 @@ 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';
+import 'dialog/intimacy_generate_character/intimacy_generate_character_edit_dialog.dart';
 
 /// 亲密度分析上传页Controller
 @injectable
@@ -390,4 +392,21 @@ class IntimacyAnalyseUploadController extends BaseController {
     AtmobLog.d(tag, "switchTaTest result:=> ${keyboardInfo?.toJson() ?? ""}");
     currentKeyboardInfo.value = keyboardInfo;
   }
+
+  /// 点击生成定制人设按钮
+  void clickGenerateCharacterBtn() {
+    // 键盘Id
+    String keyboardId = currentKeyboardInfo.value?.id ?? "";
+    // 聊天策略
+    String chatStrategy = intimacyAnalyzeResult.value?.chatStrategy ?? "";
+
+    // 弹出预测方向的编辑弹窗
+    IntimacyGenerateCharacterEditDialog.show((String name) async {
+      // 调用生成接口
+      await intimacyAnalyzeRepository.intimacyCharacterGenerate(
+        IntimacyGenerateCharacterRequest(keyboardId, name, chatStrategy),
+      );
+      ToastUtil.show(StringName.intimacyGenerateCharacterAddSuccess);
+    });
+  }
 }

+ 48 - 6
lib/module/intimacy_analyse/intimacy_analyse_upload/intimacy_analyse_upload_page.dart

@@ -1,4 +1,3 @@
-import 'package:cached_network_image/cached_network_image.dart';
 import 'package:dotted_border/dotted_border.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
@@ -645,7 +644,16 @@ class IntimacyAnalyseUploadPage
           );
         } else {
           // 已解锁,生成定制人设按钮
-          return SizedBox();
+          return Container(
+            width: double.maxFinite,
+            margin: EdgeInsets.only(
+              left: 13.w,
+              top: 8.h,
+              right: 13.w,
+              bottom: 20.h,
+            ),
+            child: _buildGenerateCharacterBtn(),
+          );
         }
       }
     }
@@ -756,11 +764,45 @@ class IntimacyAnalyseUploadPage
     });
   }
 
+  /// 生成定制人设按钮
+  Widget _buildGenerateCharacterBtn() {
+    return Container(
+      padding: EdgeInsets.only(top: 7.h, bottom: 6.h),
+      child: GradientTextBtn(
+        StringName.intimacyGenerateCharacter,
+        color: ColorName.colorBrand,
+        onPressed: () {
+          controller.clickGenerateCharacterBtn();
+        },
+      ),
+    );
+  }
+
   /// 构建卡片列表
   Widget _buildCardList() {
-    // 是否已解锁
-    bool isUnlock = controller.memberInfo.value?.isMember ?? false;
     return Obx(() {
+      // 是否已解锁
+      bool isUnlock = controller.memberInfo.value?.isMember ?? false;
+
+      // 列表,距离底部的距离
+      double listBottomSpace;
+      // 上传阶段
+      if (controller.isUploadPage.value) {
+        listBottomSpace = 90.h;
+      } else {
+        // 报告已生成
+        if (controller.intimacyAnalyzeResult.value != null) {
+          listBottomSpace = 90.h;
+        } else {
+          // 未生成报告,未解锁
+          if (isUnlock) {
+            listBottomSpace = 20.h;
+          } else {
+            listBottomSpace = 90.h;
+          }
+        }
+      }
+
       // 上传页
       if (controller.isUploadPage.value) {
         return Column(
@@ -769,7 +811,7 @@ class IntimacyAnalyseUploadPage
             _buildUploadStepCard(),
             // 预测方向卡片
             _buildPredictionDirectionStepCard(),
-            SizedBox(height: 90.h),
+            SizedBox(height: listBottomSpace),
           ],
         );
       } else {
@@ -781,7 +823,7 @@ class IntimacyAnalyseUploadPage
             // 报告结果卡片
             _buildAnalysisReport(),
             // 内容距离底部的距离
-            isUnlock ? SizedBox(height: 20.h) : SizedBox(height: 90.h),
+            SizedBox(height: listBottomSpace),
           ],
         );
       }

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

@@ -249,6 +249,12 @@ class StringName {
   static final String intimacyintimacyEmotionNeed = 'intimacy_intimacy_emotion_need'.tr; // 情感需求
   static final String intimacyintimacyChatStrategy = 'intimacy_intimacy_chat_strategy'.tr; // 聊天策略
   static final String intimacyintimacySummary = 'intimacy_intimacy_summary'.tr; // 总结
+  static final String intimacyGenerateCharacterTitle = 'intimacy_generate_character_title'.tr; // 定制专属人设
+  static final String intimacyGenerateCharacterInputHint = 'intimacy_generate_character_input_hint'.tr; // 输入人设名称
+  static final String intimacyGenerateCharacterInputTip = 'intimacy_generate_character_input_tip'.tr; // 请输入人设名称
+  static final String intimacyGenerateCharacterAddKeyboard = 'intimacy_generate_character_add_keyboard'.tr; // 添加到键盘
+  static final String intimacyGenerateCharacter = 'intimacy_generate_character'.tr; // 生成定制人设
+  static final String intimacyGenerateCharacterAddSuccess = 'intimacy_generate_character_add_success'.tr; // 添加成功
   static final String preview = 'preview'.tr; // 预览
   static final String retry = 'retry'.tr; // 再试试
   static final String nextStep = 'next_step'.tr; // 下一步
@@ -557,6 +563,12 @@ class StringMultiSource {
       'intimacy_intimacy_emotion_need': '情感需求',
       'intimacy_intimacy_chat_strategy': '聊天策略',
       'intimacy_intimacy_summary': '总结',
+      'intimacy_generate_character_title': '定制专属人设',
+      'intimacy_generate_character_input_hint': '输入人设名称',
+      'intimacy_generate_character_input_tip': '请输入人设名称',
+      'intimacy_generate_character_add_keyboard': '添加到键盘',
+      'intimacy_generate_character': '生成定制人设',
+      'intimacy_generate_character_add_success': '添加成功',
       'preview': '预览',
       'retry': '再试试',
       'next_step': '下一步',

+ 3 - 1
lib/router/app_pages.dart

@@ -48,6 +48,7 @@ import '../module/intimacy_analyse/analyse_report/intimacy_analyse_report_view_c
 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/dialog/intimacy_generate_character/intimacy_generate_character_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';
@@ -60,7 +61,6 @@ import '../module/login/login_page.dart';
 import '../module/main/main_controller.dart';
 import '../module/main/main_page.dart';
 import '../module/new_user/new_user_page.dart';
-import '../module/new_user/step/gender/step_gender_logic.dart';
 import '../module/profile/edit/profile_edit_page.dart';
 import '../module/splash/splash_page.dart';
 import '../module/store/discount/discount_controller.dart';
@@ -157,6 +157,8 @@ class AppBinding extends Bindings {
     lazyPut(() => getIt.get<CustomDirectionEditController>());
     lazyPut(() => getIt.get<UserProfileController>());
     lazyPut(() => getIt.get<NewDiscountController>());
+
+    lazyPut(() => getIt.get<IntimacyGenerateCharacterEditController>());
   }
 
   void lazyPut<S>(InstanceBuilderCallback<S> builder) {

+ 44 - 0
lib/widget/delegate_lifecycle_widget.dart

@@ -0,0 +1,44 @@
+import 'package:flutter/cupertino.dart';
+
+/// 由于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;
+  }
+}