Selaa lähdekoodia

[feat]增加兴趣,人设关键词页面,增加定制人设编辑,档案编辑,选择生日页增加12星座

云天逵 7 kuukautta sitten
vanhempi
commit
f6c8d8f92b
59 muutettua tiedostoa jossa 2548 lisäystä ja 647 poistoa
  1. BIN
      assets/images/icon_aquarius.webp
  2. BIN
      assets/images/icon_aries.webp
  3. BIN
      assets/images/icon_cancer.webp
  4. BIN
      assets/images/icon_capricorn.webp
  5. BIN
      assets/images/icon_change_birthday_gemini.webp
  6. BIN
      assets/images/icon_change_character_empty.webp
  7. BIN
      assets/images/icon_change_character_unselect.webp
  8. BIN
      assets/images/icon_change_hobbies_unselect.webp
  9. BIN
      assets/images/icon_character_custom_detail_female.webp
  10. BIN
      assets/images/icon_gemini.webp
  11. BIN
      assets/images/icon_leo.webp
  12. BIN
      assets/images/icon_libra.webp
  13. BIN
      assets/images/icon_pisces.webp
  14. BIN
      assets/images/icon_sagittarius.webp
  15. BIN
      assets/images/icon_scorpio.webp
  16. BIN
      assets/images/icon_taurus.webp
  17. BIN
      assets/images/icon_virgo.webp
  18. 3 0
      assets/string/base/string.xml
  19. 8 0
      lib/data/api/atmob_api.dart
  20. 34 0
      lib/data/api/atmob_api.g.dart
  21. 30 0
      lib/data/api/request/keyboard_generate_request.dart
  22. 80 0
      lib/data/api/request/keyboard_generate_request.g.dart
  23. 20 0
      lib/data/api/response/keyboard_generate_response.dart
  24. 27 0
      lib/data/api/response/keyboard_generate_response.g.dart
  25. 4 4
      lib/data/bean/keyboard_info.dart
  26. 0 2
      lib/data/bean/keyboard_info.g.dart
  27. 27 0
      lib/data/bean/user_info.dart
  28. 19 0
      lib/data/bean/user_info.g.dart
  29. 4 6
      lib/data/repository/config_repository.dart
  30. 30 5
      lib/data/repository/keyboard_repository.dart
  31. 19 5
      lib/di/get_it.config.dart
  32. 107 98
      lib/dialog/custom_label_dialog.dart
  33. 1 1
      lib/module/about/about_page.dart
  34. 4 1
      lib/module/change/birthday/change_birthday_controller.dart
  35. 52 6
      lib/module/change/birthday/change_birthday_page.dart
  36. 83 0
      lib/module/change/character/change_character_controller.dart
  37. 349 0
      lib/module/change/character/change_character_page.dart
  38. 131 0
      lib/module/change/hobbies/change_hobbies_controller.dart
  39. 336 0
      lib/module/change/hobbies/change_hobbies_page.dart
  40. 6 0
      lib/module/change/nickname/change_nickname_page.dart
  41. 1 1
      lib/module/character_custom/character_custom_controller.dart
  42. 223 31
      lib/module/character_custom/detail/character_custom_detail_controller.dart
  43. 175 122
      lib/module/character_custom/detail/character_custom_detail_page.dart
  44. 24 5
      lib/module/character_custom/list/character_custom_list_controller.dart
  45. 2 2
      lib/module/character_custom/list/character_custom_list_page.dart
  46. 59 40
      lib/module/keyboard/keyboard_view.dart
  47. 118 108
      lib/module/keyboard_manage/keyboard_manage_controller.dart
  48. 13 28
      lib/module/keyboard_manage/keyboard_manage_page.dart
  49. 1 1
      lib/module/mine/mine_controller.dart
  50. 3 5
      lib/module/mine/mine_view.dart
  51. 135 6
      lib/module/profile/edit/profile_edit_controller.dart
  52. 216 132
      lib/module/profile/edit/profile_edit_page.dart
  53. 17 11
      lib/module/profile/profile_controller.dart
  54. 2 2
      lib/module/profile/profile_page.dart
  55. 80 4
      lib/resource/assets.gen.dart
  56. 4 0
      lib/resource/string.gen.dart
  57. 12 0
      lib/router/app_pages.dart
  58. 86 18
      lib/utils/age_zodiac_sign_util.dart
  59. 3 3
      lib/utils/styles.dart

BIN
assets/images/icon_aquarius.webp


BIN
assets/images/icon_aries.webp


BIN
assets/images/icon_cancer.webp


BIN
assets/images/icon_capricorn.webp


BIN
assets/images/icon_change_birthday_gemini.webp


BIN
assets/images/icon_change_character_empty.webp


BIN
assets/images/icon_change_character_unselect.webp


BIN
assets/images/icon_change_hobbies_unselect.webp


BIN
assets/images/icon_character_custom_detail_female.webp


BIN
assets/images/icon_gemini.webp


BIN
assets/images/icon_leo.webp


BIN
assets/images/icon_libra.webp


BIN
assets/images/icon_pisces.webp


BIN
assets/images/icon_sagittarius.webp


BIN
assets/images/icon_scorpio.webp


BIN
assets/images/icon_taurus.webp


BIN
assets/images/icon_virgo.webp


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

@@ -286,4 +286,7 @@
     <string name="next_step">下一步</string>
     <string name="recently">最近</string>
 
+    <string name="add_hobbies">添加爱好</string>
+    <string name="save">保存</string>
+
 </resources>

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

@@ -16,6 +16,7 @@ import 'package:keyboard/data/api/request/config_request.dart';
 import 'package:keyboard/data/api/request/keyboard_character_list_request.dart';
 import 'package:keyboard/data/api/request/keyboard_character_update_request.dart';
 import 'package:keyboard/data/api/request/keyboard_choose_request.dart';
+import 'package:keyboard/data/api/request/keyboard_generate_request.dart';
 import 'package:keyboard/data/api/request/keyboard_list_request.dart';
 import 'package:keyboard/data/api/request/keyboard_update_request.dart';
 import 'package:keyboard/data/api/request/login_request.dart';
@@ -38,6 +39,7 @@ import 'package:keyboard/data/api/response/config_response.dart';
 import 'package:keyboard/data/api/response/item_list_response.dart';
 import 'package:keyboard/data/api/response/item_retention_response.dart';
 import 'package:keyboard/data/api/response/keyboard_character_list_response.dart';
+import 'package:keyboard/data/api/response/keyboard_generate_response.dart';
 import 'package:keyboard/data/api/response/keyboard_home_info_response.dart'
     show KeyboardHomeInfoResponse;
 import 'package:keyboard/data/api/response/keyboard_list_response.dart';
@@ -178,6 +180,12 @@ abstract class AtmobApi {
     @Body() KeyboardCharacterUpdateRequest request,
   );
 
+  // 生成键盘
+  @POST("/project/keyboard/v1/keyboard/generate")
+  Future<BaseResponse<KeyboardGenerateResponse>> keyboardGenerate(
+    @Body() KeyboardGenerateRequest request,
+  );
+
   // 获取键盘列表
   @POST("/project/keyboard/v1/keyboard/list")
   Future<BaseResponse<KeyboardListResponse>> getKeyboardList(

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

@@ -737,6 +737,40 @@ class _AtmobApi implements AtmobApi {
   }
 
   @override
+  Future<BaseResponse<KeyboardGenerateResponse>> keyboardGenerate(
+    KeyboardGenerateRequest 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<KeyboardGenerateResponse>>(
+      Options(method: 'POST', headers: _headers, extra: _extra)
+          .compose(
+            _dio.options,
+            '/project/keyboard/v1/keyboard/generate',
+            queryParameters: queryParameters,
+            data: _data,
+          )
+          .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
+    );
+    final _result = await _dio.fetch<Map<String, dynamic>>(_options);
+    late BaseResponse<KeyboardGenerateResponse> _value;
+    try {
+      _value = BaseResponse<KeyboardGenerateResponse>.fromJson(
+        _result.data!,
+        (json) =>
+            KeyboardGenerateResponse.fromJson(json as Map<String, dynamic>),
+      );
+    } on Object catch (e, s) {
+      errorLogger?.logError(e, s, _options);
+      rethrow;
+    }
+    return _value;
+  }
+
+  @override
   Future<BaseResponse<KeyboardListResponse>> getKeyboardList(
     KeyboardListRequest request,
   ) async {

+ 30 - 0
lib/data/api/request/keyboard_generate_request.dart

@@ -0,0 +1,30 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import '../../../base/app_base_request.dart';
+
+part 'keyboard_generate_request.g.dart';
+
+@JsonSerializable()
+class KeyboardGenerateRequest extends AppBaseRequest {
+
+
+  @JsonKey(name: "intimacy")
+  int intimacy;
+
+  @JsonKey(name: "name")
+  String name;
+
+  @JsonKey(name: "gender")
+  int gender;
+
+  @JsonKey(name: "birthday")
+  String birthday;
+
+  @JsonKey(name: "imageUrl")
+  String imageUrl;
+
+  KeyboardGenerateRequest({required this.intimacy,required this.name,required this.gender,required this.birthday,required this.imageUrl,});
+
+  @override
+  Map<String, dynamic> toJson() => _$KeyboardGenerateRequestToJson(this);
+}

+ 80 - 0
lib/data/api/request/keyboard_generate_request.g.dart

@@ -0,0 +1,80 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'keyboard_generate_request.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+KeyboardGenerateRequest _$KeyboardGenerateRequestFromJson(
+  Map<String, dynamic> json,
+) =>
+    KeyboardGenerateRequest(
+        intimacy: (json['intimacy'] as num).toInt(),
+        name: json['name'] as String,
+        gender: (json['gender'] as num).toInt(),
+        birthday: json['birthday'] as String,
+        imageUrl: json['imageUrl'] 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> _$KeyboardGenerateRequestToJson(
+  KeyboardGenerateRequest 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,
+  'intimacy': instance.intimacy,
+  'name': instance.name,
+  'gender': instance.gender,
+  'birthday': instance.birthday,
+  'imageUrl': instance.imageUrl,
+};

+ 20 - 0
lib/data/api/response/keyboard_generate_response.dart

@@ -0,0 +1,20 @@
+import 'package:json_annotation/json_annotation.dart';
+import 'package:keyboard/data/bean/keyboard_info.dart';
+
+import '../../bean/user_info.dart';
+
+part 'keyboard_generate_response.g.dart';
+
+@JsonSerializable()
+class KeyboardGenerateResponse {
+  @JsonKey(name: "keyboardInfo")
+   KeyboardInfo? keyboardInfo;
+
+  @JsonKey(name: "userInfo")
+   UserInfo? userInfo;
+
+  KeyboardGenerateResponse({this.keyboardInfo, this.userInfo});
+
+  factory KeyboardGenerateResponse.fromJson(Map<String, dynamic> json) =>
+      _$KeyboardGenerateResponseFromJson(json);
+}

+ 27 - 0
lib/data/api/response/keyboard_generate_response.g.dart

@@ -0,0 +1,27 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'keyboard_generate_response.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+KeyboardGenerateResponse _$KeyboardGenerateResponseFromJson(
+  Map<String, dynamic> json,
+) => KeyboardGenerateResponse(
+  keyboardInfo:
+      json['keyboardInfo'] == null
+          ? null
+          : KeyboardInfo.fromJson(json['keyboardInfo'] as Map<String, dynamic>),
+  userInfo:
+      json['userInfo'] == null
+          ? null
+          : UserInfo.fromJson(json['userInfo'] as Map<String, dynamic>),
+);
+
+Map<String, dynamic> _$KeyboardGenerateResponseToJson(
+  KeyboardGenerateResponse instance,
+) => <String, dynamic>{
+  'keyboardInfo': instance.keyboardInfo,
+  'userInfo': instance.userInfo,
+};

+ 4 - 4
lib/data/bean/keyboard_info.dart

@@ -32,9 +32,9 @@ class KeyboardInfo {
   @JsonKey(name: 'imageUrl')
   String? imageUrl;
 
-  // 展示头像时用到
-  @JsonKey(name: 'avatar')
-  String? avatar;
+  // // 展示头像时用到
+  // @JsonKey(name: 'avatar')
+  // String? avatar;
 
   @JsonKey(name: 'isChoose')
   bool? isChoose;
@@ -52,7 +52,7 @@ class KeyboardInfo {
     this.intimacy,
     this.imageUrl,
     this.isChoose,
-    this.avatar,
+    // this.avatar,
     this.isSelect,
   });
 

+ 0 - 2
lib/data/bean/keyboard_info.g.dart

@@ -15,7 +15,6 @@ KeyboardInfo _$KeyboardInfoFromJson(Map<String, dynamic> json) => KeyboardInfo(
   intimacy: (json['intimacy'] as num?)?.toInt(),
   imageUrl: json['imageUrl'] as String?,
   isChoose: json['isChoose'] as bool?,
-  avatar: json['avatar'] as String?,
   isSelect: json['isSelect'] as bool?,
 );
 
@@ -28,7 +27,6 @@ Map<String, dynamic> _$KeyboardInfoToJson(KeyboardInfo instance) =>
       'birthday': instance.birthday,
       'intimacy': instance.intimacy,
       'imageUrl': instance.imageUrl,
-      'avatar': instance.avatar,
       'isChoose': instance.isChoose,
       'isSelect': instance.isSelect,
     };

+ 27 - 0
lib/data/bean/user_info.dart

@@ -0,0 +1,27 @@
+
+import 'package:json_annotation/json_annotation.dart';
+
+part 'user_info.g.dart';
+// 在生成键盘出使用
+@JsonSerializable()
+class UserInfo {
+  @JsonKey(name: "name")
+  String? name;
+  @JsonKey(name: "birthday")
+  String? birthday;
+  @JsonKey(name: "gender")
+  int? gender;
+
+  UserInfo({
+    this.name,
+    this.birthday,
+    this.gender,
+
+  });
+
+
+  Map<String, dynamic> toJson() => _$UserInfoToJson(this);
+
+  factory UserInfo.fromJson(Map<String, dynamic> json) =>
+      _$UserInfoFromJson(json);
+}

+ 19 - 0
lib/data/bean/user_info.g.dart

@@ -0,0 +1,19 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'user_info.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+UserInfo _$UserInfoFromJson(Map<String, dynamic> json) => UserInfo(
+  name: json['name'] as String?,
+  birthday: json['birthday'] as String?,
+  gender: (json['gender'] as num?)?.toInt(),
+);
+
+Map<String, dynamic> _$UserInfoToJson(UserInfo instance) => <String, dynamic>{
+  'name': instance.name,
+  'birthday': instance.birthday,
+  'gender': instance.gender,
+};

+ 4 - 6
lib/data/repository/config_repository.dart

@@ -22,12 +22,12 @@ class ConfigRepository {
 
   final Rxn<CustomConfigInfo> _characterCustomConfig = Rxn<CustomConfigInfo>();
 
-  CustomConfigInfo? get characterCustomConfig => _characterCustomConfig.value;
+  // 里面包含人设头像
+  Rxn<CustomConfigInfo> get characterCustomConfig => _characterCustomConfig;
 
+  // 人物和键盘的头像
   final Rxn<DefaultAvatarInfo> defaultAvatarInfo = Rxn<DefaultAvatarInfo>();
 
-
-
   ConfigRepository(this.atmobApi) {
     AtmobLog.d(tag, '$tag....init');
     refreshConfig();
@@ -50,10 +50,8 @@ class ConfigRepository {
           if (config.confCode == 'intimacy') {
             AtmobLog.d(tag, '获取亲密度配置: ${config.value}');
           } else if (config.confCode == 'default_avatar') {
-
             defaultAvatarInfo.value = DefaultAvatarInfo.fromJson(config.value);
-            AtmobLog.d(
-                tag, '获取默认头像配置: ${defaultAvatarInfo.value?.toJson()}');
+            AtmobLog.d(tag, '获取默认头像配置: ${defaultAvatarInfo.value?.toJson()}');
           }
         }
       }

+ 30 - 5
lib/data/repository/keyboard_repository.dart

@@ -2,7 +2,9 @@ import 'package:get/get.dart';
 import 'package:injectable/injectable.dart';
 import 'package:keyboard/base/app_base_request.dart';
 import 'package:keyboard/data/api/request/keyboard_character_update_request.dart';
+import 'package:keyboard/data/api/request/keyboard_generate_request.dart';
 import 'package:keyboard/data/api/request/keyboard_list_request.dart';
+import 'package:keyboard/data/api/response/keyboard_generate_response.dart';
 import 'package:keyboard/data/api/response/keyboard_love_index_response.dart';
 import 'package:keyboard/data/api/response/keyboard_prologue_list_response.dart';
 import 'package:keyboard/utils/atmob_log.dart';
@@ -30,8 +32,7 @@ class KeyboardRepository {
   final Rxn<KeyboardLoveIndexResponse> _homeLoveIndex =
       Rxn<KeyboardLoveIndexResponse>();
 
-  Rxn<KeyboardLoveIndexResponse> get homeLoveIndex =>
-      _homeLoveIndex;
+  Rxn<KeyboardLoveIndexResponse> get homeLoveIndex => _homeLoveIndex;
 
   final Rxn<KeyboardHomeInfoResponse> _homeInfo =
       Rxn<KeyboardHomeInfoResponse>();
@@ -48,7 +49,7 @@ class KeyboardRepository {
 
     await Future.delayed(const Duration(milliseconds: 500));
     // 延迟为了保证首页数据能够正常获取,不然保存的时候,获取太快了,导致还是拉到旧的数值
-    getKeyboardHomeInfo();
+    await getKeyboardHomeInfo();
     getKeyboardLoveIndex();
   }
 
@@ -118,7 +119,7 @@ class KeyboardRepository {
             gender: gender,
           ),
         )
-        .then(HttpHandler.handle(true));
+        .then(HttpHandler.handle(false));
   }
 
   // 选择键盘
@@ -150,12 +151,36 @@ class KeyboardRepository {
   Future<KeyboardLoveIndexResponse> getKeyboardLoveIndex() {
     return atmobApi
         .getKeyboardLoveIndex(AppBaseRequest())
-        .then(HttpHandler.handle(true))
+        .then(HttpHandler.handle(false))
         .then((response) {
           _homeLoveIndex.value = response;
           return response;
         });
   }
 
+  // 生成键盘
+  Future<KeyboardGenerateResponse> getKeyboardGenerate({
+    required String name,
+    required String imageUrl,
+    required String birthday,
+    required int intimacy,
+    required int gender,
+  }) {
+    return atmobApi
+        .keyboardGenerate(
+          KeyboardGenerateRequest(
+            intimacy: intimacy,
+            name: name,
+            gender: gender,
+            birthday: birthday,
+            imageUrl: imageUrl,
+          ),
+        )
+        .then(HttpHandler.handle(true))
+        .then((response) {
+          return response;
+        });
+  }
+
   static KeyboardRepository getInstance() => getIt.get<KeyboardRepository>();
 }

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

@@ -30,7 +30,9 @@ import '../dialog/custom_character/custom_character_add_controller.dart'
 import '../module/about/about_controller.dart' as _i256;
 import '../module/browser/browser_controller.dart' as _i923;
 import '../module/change/birthday/change_birthday_controller.dart' as _i1057;
+import '../module/change/character/change_character_controller.dart' as _i84;
 import '../module/change/gender/change_gender_controller.dart' as _i315;
+import '../module/change/hobbies/change_hobbies_controller.dart' as _i765;
 import '../module/change/nickname/change_nickname_controller.dart' as _i859;
 import '../module/character/character_controller.dart' as _i888;
 import '../module/character/content/character_group_content_controller.dart'
@@ -161,6 +163,12 @@ extension GetItInjectableX on _i174.GetIt {
         gh<_i243.AtmobApi>(),
       ),
     );
+    gh.factory<_i922.KeyboardManageController>(
+      () => _i922.KeyboardManageController(
+        gh<_i274.KeyboardRepository>(),
+        gh<_i83.AccountRepository>(),
+      ),
+    );
     gh.factory<_i244.ProfileController>(
       () => _i244.ProfileController(
         gh<_i274.KeyboardRepository>(),
@@ -179,12 +187,15 @@ extension GetItInjectableX on _i174.GetIt {
     gh.lazySingleton<_i779.PaymentStatusManager>(
       () => _i779.PaymentStatusManager(gh<_i987.StoreRepository>()),
     );
+    gh.factory<_i344.ProfileEditController>(
+      () => _i344.ProfileEditController(
+        gh<_i50.ConfigRepository>(),
+        gh<_i274.KeyboardRepository>(),
+      ),
+    );
     gh.factory<_i161.KeyBoardController>(
       () => _i161.KeyBoardController(gh<_i274.KeyboardRepository>()),
     );
-    gh.factory<_i922.KeyboardManageController>(
-      () => _i922.KeyboardManageController(gh<_i274.KeyboardRepository>()),
-    );
     gh.factory<_i970.CharacterGroupContentController>(
       () => _i970.CharacterGroupContentController(
         gh<_i421.CharactersRepository>(),
@@ -222,8 +233,11 @@ extension GetItInjectableX on _i174.GetIt {
     gh.factory<_i15.CharacterCustomController>(
       () => _i15.CharacterCustomController(gh<_i50.ConfigRepository>()),
     );
-    gh.factory<_i344.ProfileEditController>(
-      () => _i344.ProfileEditController(gh<_i50.ConfigRepository>()),
+    gh.factory<_i765.ChangeHobbiesController>(
+      () => _i765.ChangeHobbiesController(gh<_i50.ConfigRepository>()),
+    );
+    gh.factory<_i84.ChangeCharacterController>(
+      () => _i84.ChangeCharacterController(gh<_i50.ConfigRepository>()),
     );
     gh.factory<_i888.CharacterController>(
       () => _i888.CharacterController(

+ 107 - 98
lib/dialog/custom_label_dialog.dart

@@ -15,6 +15,7 @@ class CustomLabelDialog {
   static const String tag = 'CustomLabelDialog';
 
   static void show({
+    String? title,
     required Function(String) clickCallback,
     required String hintText,
     required int maxLength,
@@ -28,116 +29,124 @@ class CustomLabelDialog {
       builder: (_) {
         return Scaffold(
           backgroundColor: Colors.transparent,
-            body: Column(
-          crossAxisAlignment: CrossAxisAlignment.center,
-          mainAxisAlignment: MainAxisAlignment.center,
-          children: [Container(
-            width: double.infinity,
-            margin: EdgeInsets.symmetric(horizontal: 31.w),
-            decoration: ShapeDecoration(
-              color: Colors.white,
-              shape: RoundedRectangleBorder(
-                borderRadius: BorderRadius.circular(20.r),
-              ),
-            ),
-            child: Stack(
-              children: [
-                Container(
-                  padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 24.h),
-                  child: Column(
-                    mainAxisSize: MainAxisSize.min,
-                    crossAxisAlignment: CrossAxisAlignment.center,
-                    mainAxisAlignment: MainAxisAlignment.center,
-                    children: [
-                      Text(
-                        StringName.customLabel,
-                        style: TextStyle(
-                          color: Colors.black.withAlpha(204),
-                          fontSize: 16.sp,
-                          fontWeight: FontWeight.w500,
-                        ),
+          body: Column(
+            crossAxisAlignment: CrossAxisAlignment.center,
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: [
+              Container(
+                width: double.infinity,
+                margin: EdgeInsets.symmetric(horizontal: 31.w),
+                decoration: ShapeDecoration(
+                  color: Colors.white,
+                  shape: RoundedRectangleBorder(
+                    borderRadius: BorderRadius.circular(20.r),
+                  ),
+                ),
+                child: Stack(
+                  children: [
+                    Container(
+                      padding: EdgeInsets.symmetric(
+                        horizontal: 16.w,
+                        vertical: 24.h,
                       ),
-                      SizedBox(height: 16.h),
-                      Container(
-                        height: 48.h,
-
-                        alignment: Alignment.center,
-                        decoration: ShapeDecoration(
-                          color: const Color(0xFFF5F4F9),
-                          shape: RoundedRectangleBorder(
-                            borderRadius: BorderRadius.circular(31.r),
+                      child: Column(
+                        mainAxisSize: MainAxisSize.min,
+                        crossAxisAlignment: CrossAxisAlignment.center,
+                        mainAxisAlignment: MainAxisAlignment.center,
+                        children: [
+                          Text(
+                            title ?? StringName.customLabel,
+                            style: TextStyle(
+                              color: Colors.black.withAlpha(204),
+                              fontSize: 16.sp,
+                              fontWeight: FontWeight.w500,
+                            ),
                           ),
-                        ),
-                        child: TextField(
-                          controller: textController,
-                          // maxLength: maxLength,
-                          maxLines: null,
-                          expands: true,
-                          textAlign: TextAlign.center,
-                          textAlignVertical: TextAlignVertical.center,
-                          decoration: InputDecoration(
-                            counterText: "",
-                            hintText: hintText,
-                            hintStyle: TextStyle(
-                              color: Colors.black.withAlpha(66),
+                          SizedBox(height: 16.h),
+                          Container(
+                            height: 48.h,
+
+                            alignment: Alignment.center,
+                            decoration: ShapeDecoration(
+                              color: const Color(0xFFF5F4F9),
+                              shape: RoundedRectangleBorder(
+                                borderRadius: BorderRadius.circular(31.r),
+                              ),
                             ),
-                            border: OutlineInputBorder(
-                              borderRadius: BorderRadius.circular(10.r),
-                              borderSide: BorderSide.none, // 移除边框线
+                            child: TextField(
+                              controller: textController,
+                              // maxLength: maxLength,
+                              maxLines: null,
+                              expands: true,
+                              textAlign: TextAlign.center,
+                              textAlignVertical: TextAlignVertical.center,
+                              decoration: InputDecoration(
+                                counterText: "",
+                                hintText: hintText,
+                                hintStyle: TextStyle(
+                                  color: Colors.black.withAlpha(66),
+                                ),
+                                border: OutlineInputBorder(
+                                  borderRadius: BorderRadius.circular(10.r),
+                                  borderSide: BorderSide.none, // 移除边框线
+                                ),
+                                filled: true,
+                                fillColor: const Color(0xFFF5F4F9),
+                              ),
                             ),
-                            filled: true,
-                            fillColor: const Color(0xFFF5F4F9),
                           ),
-                        ),
-                      ),
-                      SizedBox(height: 24.h),
-                      Container(
-                        height: 48.h,
-                        width: double.infinity,
-                        decoration: Styles.getActivateButtonDecoration(31.r),
-                        child: TextButton(
-                          onPressed: () {
-                            if (textController.text.isEmpty) {
-                              return ToastUtil.show(hintText);
-                            }
-                            if (textController.text.length > maxLength) {
-                              return ToastUtil.show('最多$maxLength个字哦~');
-                            }
+                          SizedBox(height: 24.h),
+                          Container(
+                            height: 48.h,
+                            width: double.infinity,
+                            decoration: Styles.getActivateButtonDecoration(
+                              31.r,
+                            ),
+                            child: TextButton(
+                              onPressed: () {
+                                if (textController.text.isEmpty) {
+                                  return ToastUtil.show(hintText);
+                                }
+                                if (textController.text.length > maxLength) {
+                                  return ToastUtil.show('最多$maxLength个字哦~');
+                                }
 
-                            clickCallback(textController.text.trim());
-                            SmartDialog.dismiss();
-                          },
-                          child: Text(
-                            StringName.customLabelSave,
-                            style: TextStyle(
-                              color: Colors.white,
-                              fontSize: 16.sp,
-                              fontWeight: FontWeight.w500,
+                                clickCallback(textController.text.trim());
+                                SmartDialog.dismiss();
+                              },
+                              child: Text(
+                                StringName.customLabelSave,
+                                style: TextStyle(
+                                  color: Colors.white,
+                                  fontSize: 16.sp,
+                                  fontWeight: FontWeight.w500,
+                                ),
+                              ),
                             ),
                           ),
-                        ),
+                        ],
                       ),
-                    ],
-                  ),
-                ),
-                Positioned(
-                  right: 14,
-                  top: 14,
+                    ),
+                    Positioned(
+                      right: 14,
+                      top: 14,
 
-                  child: GestureDetector(
-                    onTap: () {
-                      SmartDialog.dismiss();
-                    },
-                    child: Assets.images.iconCustomDialogClose.image(
-                      width: 24.w,
-                      height: 24.h,
+                      child: GestureDetector(
+                        onTap: () {
+                          SmartDialog.dismiss();
+                        },
+                        child: Assets.images.iconCustomDialogClose.image(
+                          width: 24.w,
+                          height: 24.h,
+                        ),
+                      ),
                     ),
-                  ),
+                  ],
                 ),
-              ],
-            ),
-          ),]
-        ));
+              ),
+            ],
+          ),
+        );
       },
     );
   }

+ 1 - 1
lib/module/about/about_page.dart

@@ -155,7 +155,7 @@ class AboutPage extends BasePage<AboutController> {
       decoration: ShapeDecoration(
         shape: RoundedRectangleBorder(
           side: BorderSide(
-            width: 1,
+            width: 0.5.w,
             strokeAlign: BorderSide.strokeAlignCenter,
             color: Color(0xFFF5F4F9),
           ),

+ 4 - 1
lib/module/change/birthday/change_birthday_controller.dart

@@ -5,6 +5,8 @@ import 'package:keyboard/utils/age_zodiac_sign_util.dart';
 import 'package:intl/intl.dart';
 import 'package:keyboard/utils/toast_util.dart';
 
+import '../../../resource/assets.gen.dart';
+
 @injectable
 class ChangeBirthdayController extends BaseController {
   final tag = "ChangeBirthdayController";
@@ -15,7 +17,7 @@ class ChangeBirthdayController extends BaseController {
   late String currentDate;
 
   //星座
-  var constellation = "".obs;
+  Rx<Zodiac> constellation = Zodiac(name: '', image: AssetGenImage('')).obs;
   var age = 0.obs;
 
   ChangeBirthdayController();
@@ -57,4 +59,5 @@ class ChangeBirthdayController extends BaseController {
     }
     Get.back(result: currentDate);
   }
+
 }

+ 52 - 6
lib/module/change/birthday/change_birthday_page.dart

@@ -13,7 +13,10 @@ class ChangeBirthdayPage extends BasePage<ChangeBirthdayController> {
   const ChangeBirthdayPage({super.key});
 
   static Future start({String? birthday}) async {
-    return Get.toNamed(RoutePath.changeBirthday, arguments: {"birthday": birthday});
+    return Get.toNamed(
+      RoutePath.changeBirthday,
+      arguments: {"birthday": birthday},
+    );
   }
 
   @override
@@ -52,7 +55,7 @@ class ChangeBirthdayPage extends BasePage<ChangeBirthdayController> {
                       onTap: () {
                         controller.clickSave();
                       },
-                      isEnable: controller.constellation.value.isNotEmpty,
+                      isEnable: controller.constellation.value.name.isNotEmpty,
                     );
                   }),
                 ),
@@ -97,12 +100,55 @@ class ChangeBirthdayPage extends BasePage<ChangeBirthdayController> {
           return Row(
             mainAxisAlignment: MainAxisAlignment.center,
             children: [
-              Assets.images.iconChangeBirthdayGemini.image(
-                width: 26.w,
-                height: 26.w,
+
+              // 星座
+              Container(
+                width: 26.r,
+                height: 26.r,
+                decoration: ShapeDecoration(
+                  color: Colors.white,
+                  shape: OvalBorder(
+                    side: BorderSide(width: 1.r, color: Colors.white),
+                  ),
+                  shadows: [
+                    BoxShadow(
+                      color: Color(0x30A46EFE),
+                      blurRadius: 2.r,
+                      offset: Offset(0, 2),
+                      spreadRadius: 0,
+                    ),
+                  ],
+                ),
+                child: Container(
+                    margin: EdgeInsets.all(2.r),
+                    width: 22.r,
+                    height: 22.r,
+                    decoration: ShapeDecoration(
+                      gradient: LinearGradient(
+                        begin: Alignment.centerLeft,
+                        end: Alignment.centerRight,
+                        colors: [
+                          const Color(0xFF7D46FC),
+                          const Color(0xFFBC87FF),
+                        ],
+                      ),
+                      shape: OvalBorder(),
+                      shadows: [
+                        BoxShadow(
+                          color: Color(0x30A46EFE),
+                          blurRadius: 2.r,
+                          offset: Offset(0, 2.08),
+                          spreadRadius: 0,
+                        ),
+                      ],
+                    ),
+                    child: Center(child: controller.constellation.value.image.image(width: 14.r,height: 14.r,fit: BoxFit.contain),)
+                ),
               ),
+              SizedBox(width: 7.w,),
+
               Text(
-                controller.constellation.value,
+                controller.constellation.value.name,
                 style: Styles.getTextStyleBlack204W500(14.sp),
               ),
             ],

+ 83 - 0
lib/module/change/character/change_character_controller.dart

@@ -0,0 +1,83 @@
+import 'package:injectable/injectable.dart';
+import 'package:keyboard/base/base_controller.dart';
+import 'package:get/get.dart';
+
+import '../../../data/bean/custom_config_info.dart';
+import '../../../data/repository/config_repository.dart';
+import '../../../utils/atmob_log.dart';
+import '../../../utils/toast_util.dart';
+
+@injectable
+class ChangeCharacterController extends BaseController {
+  final String tag = "ChangeCharacterController";
+  final ConfigRepository configRepository;
+
+  CustomConfigInfo? get currentCharacterCustomConfig =>
+      configRepository.characterCustomConfig.value;
+
+  final RxList<CharactersList> characterLabelsList = <CharactersList>[].obs;
+
+  final RxList<CharactersList> characterSelectLabels = <CharactersList>[].obs;
+
+  ChangeCharacterController(this.configRepository);
+
+  @override
+  void onInit() {
+    super.onInit();
+    initData();
+
+    final List<CharactersList>? characters =
+        Get.arguments["characters"] as List<CharactersList>?;
+    if (characters != null) {
+      characterSelectLabels.assignAll(characters);}
+  }
+  //  初始化数据
+  void initData() {
+    AtmobLog.d(tag, "initData");
+    characterLabelsList.value = currentCharacterCustomConfig?.characters ?? [];
+  }
+
+  void selectCharacter(CharactersList name) {
+    handleSelection(
+      name: name,
+      selectedList: characterSelectLabels,
+      max: currentCharacterCustomConfig?.maxCharacterNum ?? 3,
+      errorMessage:
+          "最多选择${currentCharacterCustomConfig?.maxCharacterNum ?? 3}个关键词",
+    );
+  }
+
+  ///标签选择处理
+  void handleSelection({
+    required dynamic name,
+    required RxList<dynamic> selectedList,
+    required int max,
+    required String errorMessage,
+  }) {
+    if (selectedList.contains(name)) {
+      selectedList.remove(name);
+    } else if (selectedList.length < max) {
+      selectedList.add(name);
+    } else {
+      ToastUtil.show(errorMessage);
+    }
+  }
+
+  void clickBack() {
+    Get.back();
+  }
+
+  void clickSave() {
+    if (characterSelectLabels.isEmpty) {
+      ToastUtil.show(
+        "至少选择${currentCharacterCustomConfig?.minCharacterNum ?? 1}个关键词",
+      );
+      return;
+    }
+    AtmobLog.d(
+      tag,
+      "clickSave characterSelectLabels: ${characterSelectLabels.toJson()}",
+    );
+    Get.back(result: characterSelectLabels);
+  }
+}

+ 349 - 0
lib/module/change/character/change_character_page.dart

@@ -0,0 +1,349 @@
+import 'package:dotted_border/dotted_border.dart';
+import 'package:flutter/src/widgets/framework.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:keyboard/module/change/character/change_character_controller.dart';
+
+import '../../../base/base_page.dart';
+import '../../../data/bean/custom_config_info.dart';
+import 'package:get/get.dart';
+import 'package:flutter/material.dart';
+
+import '../../../resource/assets.gen.dart';
+import '../../../resource/string.gen.dart';
+import '../../../router/app_pages.dart';
+import '../../../utils/styles.dart';
+
+class ChangeCharacterPage extends BasePage<ChangeCharacterController> {
+  const ChangeCharacterPage({super.key});
+
+  static Future start({List<CharactersList>? characters}) async {
+    return Get.toNamed(
+      RoutePath.changeCharacters,
+      arguments: {"characters": characters},
+    );
+  }
+
+  @override
+  bool immersive() {
+    return true;
+  }
+
+  @override
+  Color backgroundColor() {
+    return const Color(0xFFF6F5FA);
+  }
+
+  @override
+  Widget buildBody(BuildContext context) {
+    return Stack(
+      children: [
+        IgnorePointer(child: Assets.images.bgMine.image(width: 360.w)),
+        SafeArea(
+          child: Stack(
+            children: [
+              Column(
+                children: [
+                  _buildTitle(),
+                  SizedBox(height: 40.h),
+                  Text(
+                    StringName.characterKeywords,
+                    style: Styles.getTextStyleBlack204W500(22.sp),
+                  ),
+                  SizedBox(height: 50.h),
+                  _buildCurrentCharacterLabels(),
+                  SizedBox(height: 20.h),
+                  _buildDivider(),
+                  Expanded(
+                    child: SingleChildScrollView(
+                      child: _buildSelectionPage(
+                        items: controller.characterLabelsList,
+                        selectedLabels: controller.characterSelectLabels,
+                        onSelected: (item) {
+                          controller.selectCharacter(item);
+                        },
+                        isCustomEnabled: true,
+
+                        isShowEmoji: true,
+                      ),
+                    ),
+                  ),
+
+                  SizedBox(height: 100.h),
+                ],
+              ),
+              Align(
+                alignment: Alignment.bottomCenter,
+                child: Container(
+                  margin: EdgeInsets.only(bottom: 32.h),
+                  child: Obx(() {
+                    return _buildSaveButton(
+                      onTap: () {
+                        controller.clickSave();
+                      },
+                      isEnable: controller.characterSelectLabels.isNotEmpty,
+                    );
+                  }),
+                ),
+              ),
+            ],
+          ),
+        ),
+      ],
+    );
+  }
+
+  _buildTitle() {
+    return Container(
+      alignment: Alignment.centerLeft,
+      padding: EdgeInsets.only(top: 12.h, left: 16.w, right: 16.w),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.start,
+        children: [
+          GestureDetector(
+            onTap: controller.clickBack,
+            child: Assets.images.iconMineBackArrow.image(
+              width: 24.w,
+              height: 24.w,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildSaveButton({required VoidCallback onTap, required isEnable}) {
+    return GestureDetector(
+      onTap: () {
+        onTap();
+      },
+      child: Container(
+        width: 260.w,
+        height: 48.h,
+        decoration:
+            isEnable
+                ? Styles.getActivateButtonDecoration(31.r)
+                : Styles.getInactiveButtonDecoration(31.r),
+        child: Center(
+          child: Text(
+            '保存',
+            style: TextStyle(
+              color: Colors.white,
+              fontSize: 16.sp,
+              fontWeight: FontWeight.w500,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+
+  Widget _buildSelectionPage({
+    required List<dynamic> items,
+    required RxList<dynamic> selectedLabels,
+    required Function(dynamic) onSelected,
+    required bool isCustomEnabled,
+
+    required bool isShowEmoji,
+  }) {
+    return Obx(() {
+      return Column(
+        children: [
+          Container(
+            margin: EdgeInsets.only(top: 20.h, left: 18.w, right: 18.w),
+            alignment: Alignment.center,
+            child: GridView.count(
+              physics: NeverScrollableScrollPhysics(),
+              shrinkWrap: true,
+              crossAxisCount: 3,
+              crossAxisSpacing: 6.r,
+              mainAxisSpacing: 10.r,
+              childAspectRatio: 2.6,
+
+              children: [
+                ...items.map((item) {
+                  final emoji = item.emoji ?? "";
+                  final name = item.name ?? "";
+                  return Obx(() {
+                    bool isSelected = selectedLabels.contains(item);
+                    return ChoiceChip(
+                      padding: EdgeInsets.zero,
+                      label: SizedBox(
+                        width: 140.w,
+
+                        child: Center(
+                          child: Text(
+                            isShowEmoji ? "$emoji$name" : name,
+                            overflow: TextOverflow.ellipsis,
+                            maxLines: 1,
+                            style: TextStyle(
+                              color:
+                                  isSelected
+                                      ? Color(0xFF7D46FC)
+                                      : Colors.black.withValues(alpha: 0.8),
+                              fontSize: 14.sp,
+                              fontWeight: FontWeight.w400,
+                            ),
+                          ),
+                        ),
+                      ),
+                      showCheckmark: false,
+                      selected: isSelected,
+                      selectedColor: Color(0xFFEDE8FF),
+                      backgroundColor: Colors.white,
+                      shape: RoundedRectangleBorder(
+                        side: BorderSide(width: 0.w, color: Colors.transparent),
+                        borderRadius: BorderRadius.circular(31.r),
+                      ),
+                      onSelected: (selected) {
+                        onSelected(item);
+                      },
+                    );
+                  });
+                }),
+              ],
+            ),
+          ),
+        ],
+      );
+    });
+  }
+
+  Widget _buildDivider() {
+    return Container(
+      margin: EdgeInsets.only(top: 8.h, bottom: 8.h),
+      width: 304.w,
+      decoration: ShapeDecoration(
+        shape: RoundedRectangleBorder(
+          side: BorderSide(
+            width: 0.5.w,
+            strokeAlign: BorderSide.strokeAlignCenter,
+            color: const Color(0xffDDDCE1),
+          ),
+        ),
+      ),
+    );
+  }
+
+  // 添加爱好
+  Widget _buildCustom(VoidCallback onCustomClick) {
+    return GestureDetector(
+      onTap: onCustomClick,
+      child: DottedBorder(
+        color: const Color(0xFFC9C2DB),
+        strokeWidth: 1.0.w,
+        padding: EdgeInsets.symmetric(horizontal: 11.w, vertical: 8.h),
+        borderType: BorderType.RRect,
+        radius: Radius.circular(70.r),
+        child: Container(
+          alignment: Alignment.center,
+          child: Row(
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: [
+              Assets.images.iconCharacterCustomPlus.image(
+                width: 18.w,
+                height: 18.w,
+              ),
+              Text(
+                StringName.characterKeywords,
+                style: TextStyle(
+                  color: const Color(0xFFC9C2DB),
+                  fontSize: 14.sp,
+                  fontWeight: FontWeight.w500,
+                ),
+              ),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+
+  _buildCurrentCharacterLabels() {
+    return Obx(() {
+      return Container(
+        padding: EdgeInsets.only(top: 14.h, bottom: 14.h),
+        width: 326.w,
+        decoration: ShapeDecoration(
+          color: Colors.white,
+          shape: RoundedRectangleBorder(
+            borderRadius: BorderRadius.circular(38.50.r),
+          ),
+        ),
+        child: Row(
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: List.generate(3, (index) {
+            final character =
+                (index < controller.characterSelectLabels.length)
+                    ? controller.characterSelectLabels[index]
+                    : null;
+            return Padding(
+              padding: EdgeInsets.symmetric(horizontal: 4.w),
+              child: _buildCharacterItem(character),
+            );
+          }),
+        ),
+      );
+    });
+  }
+
+  //当个选中的标签
+  Widget _buildCharacterItem(CharactersList? character) {
+    final hasEmoji = character?.emoji != null;
+    final hasName = character?.name != null;
+    return Stack(
+      children: [
+        Column(
+          children: [
+            SizedBox(height: 2.h),
+            Container(
+              width: 92.6.w,
+              height: 40.h,
+              alignment: Alignment.center,
+              decoration: ShapeDecoration(
+                color: const Color(0xFFF6F5FF),
+                shape: RoundedRectangleBorder(
+                  borderRadius: BorderRadius.circular(70.r),
+                ),
+              ),
+              child:
+                  hasEmoji
+                      ? Text(character!.emoji!)
+                      : Assets.images.iconChangeCharacterEmpty.image(
+                        width: 14.w,
+                        height: 18.w,
+                      ),
+            ),
+            if (hasName)
+              Padding(
+                padding: EdgeInsets.only(top: 13.h),
+                child: Text(
+                  character!.name!,
+                  style: TextStyle(
+                    color: Colors.black.withOpacity(0.8),
+                    fontSize: 14.sp,
+                    fontWeight: FontWeight.w400,
+                  ),
+                ),
+              ),
+          ],
+        ),
+
+        if (hasEmoji)
+          Positioned(
+            right: 0,
+            top: 0,
+            child: InkWell(
+
+              onTap: () {
+                controller.characterSelectLabels.remove(character);
+              },
+              child: Assets.images.iconChangeCharacterUnselect.image(
+                width: 17.w,
+                height: 17.w,
+              )
+            ),
+          ),
+      ],
+    );
+  }
+}

+ 131 - 0
lib/module/change/hobbies/change_hobbies_controller.dart

@@ -0,0 +1,131 @@
+import 'package:injectable/injectable.dart';
+import 'package:keyboard/base/base_controller.dart';
+import 'package:get/get.dart';
+import 'package:keyboard/utils/atmob_log.dart';
+
+import '../../../data/bean/custom_config_info.dart';
+import '../../../data/repository/config_repository.dart';
+import '../../../dialog/custom_label_dialog.dart';
+import '../../../resource/string.gen.dart';
+import '../../../utils/toast_util.dart';
+
+@injectable
+class ChangeHobbiesController extends BaseController {
+  final String tag = 'ChangeHobbiesController';
+  final ConfigRepository configRepository;
+
+  CustomConfigInfo? get currentCharacterCustomConfig =>
+      configRepository.characterCustomConfig.value;
+
+  final RxList<Hobbies> hobbiesLabelsList = <Hobbies>[].obs;
+
+  final RxList<Hobbies> hobbiesSelectLabels = <Hobbies>[].obs;
+
+  ChangeHobbiesController(this.configRepository);
+
+  @override
+  void onInit() {
+    super.onInit();
+    initData();
+    final List<Hobbies>? hobbies = Get.arguments["hobbies"];
+
+    if (hobbies != null) {
+      for (var hobby in hobbies) {
+        final exists = hobbiesLabelsList.any((e) => e.name == hobby.name);
+        if (!exists) {
+          hobbiesLabelsList.add(hobby);
+        }
+      }
+
+      hobbiesSelectLabels.assignAll(hobbies);
+      AtmobLog.d(
+        tag,
+        "hobbiesSelectLabels: ${hobbiesSelectLabels.map((e) => e.name)}",
+      );
+    }
+  }
+
+  //  初始化数据
+  void initData() {
+    AtmobLog.d(tag, "initData");
+    hobbiesLabelsList.value = currentCharacterCustomConfig?.hobbies ?? [];
+  }
+
+  @override
+  void onClose() {
+    super.onClose();
+  }
+
+  void clickBack() {
+    Get.back();
+  }
+
+  void clickSave() {
+    if (hobbiesSelectLabels.isEmpty) {
+      ToastUtil.show(
+        "至少选择${currentCharacterCustomConfig?.minHobbyNum ?? 1}个爱好",
+      );
+      return;
+    }
+    AtmobLog.d(
+      tag,
+      "clickSave hobbiesSelectLabels: ${hobbiesSelectLabels.toJson()}",
+    );
+    Get.back(result: hobbiesSelectLabels);
+  }
+
+  /// 选择爱好标签
+  void selectHobby(Hobbies hobby) {
+    handleSelection(
+      name: hobby,
+      selectedList: hobbiesSelectLabels,
+      max: currentCharacterCustomConfig?.maxHobbyNum ?? 3,
+      errorMessage: "最多选择${currentCharacterCustomConfig?.maxHobbyNum ?? 3}个爱好",
+    );
+  }
+
+  ///标签选择处理
+  void handleSelection({
+    required dynamic name,
+    required RxList<dynamic> selectedList,
+    required int max,
+    required String errorMessage,
+  }) {
+    if (selectedList.contains(name)) {
+      selectedList.remove(name);
+    } else if (selectedList.length < max) {
+      selectedList.add(name);
+    } else {
+      ToastUtil.show(errorMessage);
+    }
+  }
+
+  void clickHobbiesCustom() {
+    AtmobLog.d(tag, "clickHobbiesCustom");
+    CustomLabelDialog.show(
+      title: StringName.addHobbies,
+      maxLength: currentCharacterCustomConfig?.maxHobbyWords ?? 10,
+      hintText: StringName.customLabelHobbiesHint,
+      clickCallback: (value) {
+        final isExist = hobbiesLabelsList.map((e) => e.name).contains(value);
+        if (isExist) {
+          ToastUtil.show("添加失败,标签 $value 已存在");
+          return;
+        }
+
+        final newHobby = Hobbies(name: value);
+        hobbiesLabelsList.add(newHobby);
+
+        // 如果当前已选未超限,自动选中
+        if (hobbiesSelectLabels.length <
+            (currentCharacterCustomConfig?.maxHobbyNum ?? 3)) {
+          hobbiesSelectLabels.add(newHobby);
+        } else {
+          ToastUtil.show(
+            "最多选择${currentCharacterCustomConfig?.maxHobbyNum ?? 3}个爱好",
+          );
+        }
+      },
+    );
+  }
+}

+ 336 - 0
lib/module/change/hobbies/change_hobbies_page.dart

@@ -0,0 +1,336 @@
+import 'package:dotted_border/dotted_border.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:keyboard/base/base_page.dart';
+import 'package:keyboard/module/change/hobbies/change_hobbies_controller.dart';
+import 'package:get/get.dart';
+
+import '../../../data/bean/custom_config_info.dart';
+import '../../../resource/assets.gen.dart';
+import '../../../resource/string.gen.dart';
+import '../../../router/app_pages.dart';
+import '../../../utils/styles.dart';
+
+class ChangeHobbiesPage extends BasePage<ChangeHobbiesController> {
+  const ChangeHobbiesPage({super.key});
+
+  static Future start({List<Hobbies>? hobbies}) async {
+    return Get.toNamed(
+      RoutePath.changeHobbies,
+      arguments: {"hobbies": hobbies},
+    );
+  }
+
+  @override
+  bool immersive() {
+    return true;
+  }
+
+  @override
+  Color backgroundColor() {
+    return const Color(0xFFF6F5FA);
+  }
+
+  @override
+  Widget buildBody(BuildContext context) {
+    return Stack(
+      children: [
+        IgnorePointer(child: Assets.images.bgMine.image(width: 360.w)),
+        SafeArea(
+          child: Stack(
+            children: [
+              Column(
+                children: [
+                  _buildTitle(),
+                  SizedBox(height: 40.h),
+                  Text('兴趣爱好', style: Styles.getTextStyleBlack204W500(22.sp)),
+                  SizedBox(height: 50.h),
+                  _buildCurrentHobbies(),
+                  SizedBox(height: 20.h),
+                  _buildDivider(),
+                  Expanded(
+                    child: SingleChildScrollView(
+                      child: _buildSelectionPage(
+                        items: controller.hobbiesLabelsList,
+                        selectedLabels: controller.hobbiesSelectLabels,
+                        onSelected: (item) {
+                          controller.selectHobby(item);
+                        },
+                        isCustomEnabled: true,
+
+                        isShowEmoji: true,
+                      ),
+                    ),
+                  ),
+
+                  SizedBox(height: 100.h),
+                ],
+              ),
+              Align(
+                alignment: Alignment.bottomCenter,
+                child: Container(
+                  margin: EdgeInsets.only(bottom: 32.h),
+                  child: Obx(() {
+                    return _buildSaveButton(
+                      onTap: () {
+                        controller.clickSave();
+                      },
+                      isEnable: controller.hobbiesSelectLabels.isNotEmpty,
+                    );
+                  }),
+                ),
+              ),
+            ],
+          ),
+        ),
+      ],
+    );
+  }
+
+  _buildTitle() {
+    return Container(
+      alignment: Alignment.centerLeft,
+      padding: EdgeInsets.only(top: 12.h, left: 16.w, right: 16.w),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.start,
+        children: [
+          GestureDetector(
+            onTap: controller.clickBack,
+            child: Assets.images.iconMineBackArrow.image(
+              width: 24.w,
+              height: 24.w,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildSaveButton({required VoidCallback onTap, required isEnable}) {
+    return GestureDetector(
+      onTap: () {
+        onTap();
+      },
+      child: Container(
+        width: 260.w,
+        height: 48.h,
+        decoration:
+            isEnable
+                ? Styles.getActivateButtonDecoration(31.r)
+                : Styles.getInactiveButtonDecoration(31.r),
+        child: Center(
+          child: Text(
+            '保存',
+            style: TextStyle(
+              color: Colors.white,
+              fontSize: 16.sp,
+              fontWeight: FontWeight.w500,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+
+  Widget _buildSelectionPage({
+    required List<dynamic> items,
+    required RxList<dynamic> selectedLabels,
+    required Function(dynamic) onSelected,
+    required bool isCustomEnabled,
+
+    required bool isShowEmoji,
+  }) {
+    return Obx(() {
+      return Column(
+        children: [
+          Container(
+            margin: EdgeInsets.only(top: 20.h, left: 18.w, right: 18.w),
+            alignment: Alignment.center,
+            child: GridView.count(
+              physics: NeverScrollableScrollPhysics(),
+              shrinkWrap: true,
+              crossAxisCount: 3,
+              crossAxisSpacing: 6.r,
+              mainAxisSpacing: 10.r,
+              childAspectRatio: 2.6,
+
+              children: [
+                ...items.map((item) {
+                  final emoji = item.emoji ?? "";
+                  final name = item.name ?? "";
+                  return Obx(() {
+                    bool isSelected = selectedLabels.contains(item);
+                    return ChoiceChip(
+                      padding: EdgeInsets.zero,
+                      label: SizedBox(
+                        width: 140.w,
+
+                        child: Center(
+                          child: Text(
+                            isShowEmoji ? "$emoji$name" : name,
+                            overflow: TextOverflow.ellipsis,
+                            maxLines: 1,
+                            style: TextStyle(
+                              color:
+                                  isSelected
+                                      ? Color(0xFF7D46FC)
+                                      : Colors.black.withValues(alpha: 0.8),
+                              fontSize: 14.sp,
+                              fontWeight: FontWeight.w400,
+                            ),
+                          ),
+                        ),
+                      ),
+                      showCheckmark: false,
+                      selected: isSelected,
+                      selectedColor: Color(0xFFEDE8FF),
+                      backgroundColor: Colors.white,
+                      shape: RoundedRectangleBorder(
+                        side: BorderSide(width: 0.w, color: Colors.transparent),
+                        borderRadius: BorderRadius.circular(31.r),
+                      ),
+                      onSelected: (selected) {
+                        onSelected(item);
+                      },
+                    );
+                  });
+                }),
+              ],
+            ),
+          ),
+        ],
+      );
+    });
+  }
+
+  Widget _buildDivider() {
+    return Container(
+      margin: EdgeInsets.only(top: 8.h, bottom: 8.h),
+      width: 304.w,
+      decoration: ShapeDecoration(
+        shape: RoundedRectangleBorder(
+          side: BorderSide(
+            width: 0.5.w,
+            strokeAlign: BorderSide.strokeAlignCenter,
+            color: const Color(0xffDDDCE1),
+          ),
+        ),
+      ),
+    );
+  }
+
+  // 添加爱好
+  Widget _buildCustom(VoidCallback onCustomClick) {
+    return GestureDetector(
+      onTap: onCustomClick,
+      child: DottedBorder(
+        color: const Color(0xFFC9C2DB),
+        strokeWidth: 1.0.w,
+        padding: EdgeInsets.symmetric(horizontal: 11.w, vertical: 8.h),
+        borderType: BorderType.RRect,
+        radius: Radius.circular(70.r),
+        child: Container(
+          alignment: Alignment.center,
+          child: Row(
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: [
+              Assets.images.iconCharacterCustomPlus.image(
+                width: 18.w,
+                height: 18.w,
+              ),
+              Text(
+                StringName.addHobbies,
+                style: TextStyle(
+                  color: const Color(0xFFC9C2DB),
+                  fontSize: 14.sp,
+                  fontWeight: FontWeight.w500,
+                ),
+              ),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+
+  _buildCurrentHobbies() {
+    return Row(
+      children: [
+        Obx(() {
+          return Expanded(
+            child: SingleChildScrollView(
+              scrollDirection: Axis.horizontal,
+              child: Padding(
+                padding: EdgeInsets.only(left: 16.w),
+                child: Row(
+                  children: [
+                    _buildCustom(() {
+                      controller.clickHobbiesCustom();
+                    }),
+                    ...controller.hobbiesSelectLabels.map((item) {
+                      return Container(
+                        margin: EdgeInsets.only(left: 8.w),
+                        child: Obx(() {
+                          final isSelected = controller.hobbiesLabelsList
+                              .contains(item);
+                          return GestureDetector(
+                            onTap: () => controller.selectHobby(item),
+                            child: Container(
+                              padding: EdgeInsets.symmetric(
+                                horizontal: 11.w,
+                                vertical: 8.h,
+                              ),
+                              decoration: BoxDecoration(
+                                gradient:
+                                    isSelected
+                                        ? LinearGradient(
+                                          colors: [
+                                            Color(0xFF7D46FC),
+                                            Color(0xFFBC87FF),
+                                          ],
+                                          begin: Alignment.centerLeft,
+                                          end: Alignment.centerRight,
+                                        )
+                                        : null,
+                                color: isSelected ? null : Colors.white,
+                                borderRadius: BorderRadius.circular(70.r),
+                                border: Border.all(
+                                  color: Colors.transparent,
+                                  width: 0,
+                                ),
+                              ),
+                              child: Row(
+                                children: [
+                                  Text(
+                                    "${item.emoji ?? ""}${item.name}",
+                                    style: TextStyle(
+                                      color:
+                                          isSelected
+                                              ? Colors.white
+                                              : Color(0xFF755BAB),
+                                      fontSize: 14.sp,
+                                      fontWeight: FontWeight.w400,
+                                    ),
+                                  ),
+                                  Assets.images.iconChangeHobbiesUnselect.image(
+                                    width: 14.w,
+                                    height: 14.h,
+                                  ),
+                                ],
+                              ),
+                            ),
+                          );
+                        }),
+                      );
+                    }),
+                  ],
+                ),
+              ),
+            ),
+          );
+        }),
+      ],
+    );
+  }
+}

+ 6 - 0
lib/module/change/nickname/change_nickname_page.dart

@@ -126,6 +126,12 @@ class ChangeNicknamePage extends BasePage<ChangeNicknameController> {
                     width: 1.w,
                   ),
                 ),
+                enabledBorder: UnderlineInputBorder(
+                  borderSide: BorderSide(
+                    color: Colors.black.withAlpha(26),
+                    width: 1.w,
+                  ),
+                ),
                 filled: true,
                 fillColor: Colors.transparent,
               ),

+ 1 - 1
lib/module/character_custom/character_custom_controller.dart

@@ -30,7 +30,7 @@ class CharacterCustomController extends BaseController {
   final ConfigRepository configRepository;
 
   CustomConfigInfo? get currentCharacterCustomConfig =>
-      configRepository.characterCustomConfig;
+      configRepository.characterCustomConfig.value;
 
   final RxList<Hobbies> hobbiesLabelsList = <Hobbies>[].obs;
 

+ 223 - 31
lib/module/character_custom/detail/character_custom_detail_controller.dart

@@ -1,5 +1,6 @@
 import 'package:injectable/injectable.dart';
 import 'package:keyboard/base/base_controller.dart';
+import 'package:keyboard/data/bean/character_info.dart';
 import 'package:keyboard/data/bean/custom_config_info.dart';
 import 'package:keyboard/data/repository/characters_repository.dart';
 import 'package:get/get.dart';
@@ -7,10 +8,24 @@ import 'package:keyboard/data/repository/config_repository.dart';
 import 'package:keyboard/module/store/store_page.dart';
 import 'package:keyboard/utils/atmob_log.dart';
 
+import '../../../resource/assets.gen.dart';
+import '../../../utils/age_zodiac_sign_util.dart';
+import '../../../utils/error_handler.dart';
 import '../../../utils/http_handler.dart';
 import '../../../utils/toast_util.dart';
+import '../../change/birthday/change_birthday_page.dart';
+import '../../change/character/change_character_page.dart';
+import '../../change/gender/change_gender_page.dart';
+import '../../change/hobbies/change_hobbies_page.dart';
+import '../../change/nickname/change_nickname_page.dart';
 
 // 定制人设详情页
+
+enum CharacterCustomEditMode {
+  add, // 新增
+  edit, // 编辑
+}
+
 @injectable
 class CharacterCustomDetailController extends BaseController {
   final String tag = 'CharacterCustomDetailController';
@@ -20,24 +35,28 @@ class CharacterCustomDetailController extends BaseController {
   final ConfigRepository configRepository;
 
   CustomConfigInfo? get currentCharacterCustomConfig =>
-      configRepository.characterCustomConfig;
+      configRepository.characterCustomConfig.value;
 
   final RxList<Hobbies> _hobbiesSelectLabels = <Hobbies>[].obs;
 
-  List<Hobbies> get hobbiesSelectLabels => _hobbiesSelectLabels.toList();
+  RxList<Hobbies> get hobbiesSelectLabels => _hobbiesSelectLabels;
 
   final RxList<CharactersList> _characterSelectLabels = <CharactersList>[].obs;
 
-  List<CharactersList> get characterSelectLabels =>
-      _characterSelectLabels.toList();
+  RxList<CharactersList> get characterSelectLabels => _characterSelectLabels;
 
-  final RxString _characterCustomName = "".obs;
+  final Rx<CharacterInfo> _currentCharacterInfo = CharacterInfo().obs;
 
-  String get characterCustomName => _characterCustomName.value;
+  final RxString _currentNickname = "".obs;
 
-  final RxInt _gender = 1.obs;
+  String get currentNickname => _currentNickname.value;
 
-  final RxString _birthday = "".obs;
+  final RxnInt _currentGender = RxnInt(null);
+
+  final RxnString _currentBirthday = RxnString(null);
+
+  String? get currentBirthday =>
+      AgeZodiacSignUtil.formatBirthdayFromString(_currentBirthday.value);
 
   final RxString _avatarUrl = "".obs;
 
@@ -47,6 +66,16 @@ class CharacterCustomDetailController extends BaseController {
 
   final List<String> _girlAvatars = [];
 
+  late final CharacterCustomEditMode mode;
+
+  bool get isEditMode => mode == CharacterCustomEditMode.edit;
+
+  bool get isAddMode => mode == CharacterCustomEditMode.add;
+
+  final RxList<Hobbies> hobbiesLabelsList = <Hobbies>[].obs;
+
+  final RxList<CharactersList> characterLabelsList = <CharactersList>[].obs;
+
   CharacterCustomDetailController(
     this.charactersRepository,
     this.configRepository,
@@ -54,38 +83,75 @@ class CharacterCustomDetailController extends BaseController {
 
   @override
   void onInit() {
-    _getArgs();
-
     super.onInit();
+
     initData();
+    _getArgs();
   }
 
   void initData() {
     AtmobLog.d(tag, "initData");
     _boyAvatars.addAll(currentCharacterCustomConfig?.boyAvatars ?? []);
     _girlAvatars.addAll(currentCharacterCustomConfig?.girlAvatars ?? []);
-    if (_gender.value == 1) {
+    hobbiesLabelsList.value = currentCharacterCustomConfig?.hobbies ?? [];
+    characterLabelsList.value = currentCharacterCustomConfig?.characters ?? [];
+    if (_currentGender.value == 1) {
       _avatarUrl.value = _boyAvatars[0];
     } else {
       _avatarUrl.value = _girlAvatars[0];
     }
   }
 
-  void nextAvatar() {
-    AtmobLog.d(tag, "nextAvatar");
-
-    if (_gender.value == 1) {
-      int currentIndex = _boyAvatars.indexOf(_avatarUrl.value);
-      _avatarUrl.value = _boyAvatars[(currentIndex + 1) % _boyAvatars.length];
-    } else {
-      int currentIndex = _girlAvatars.indexOf(_avatarUrl.value);
-      _avatarUrl.value = _girlAvatars[(currentIndex + 1) % _girlAvatars.length];
-    }
-  }
-
   void _getArgs() {
     final arguments = Get.arguments as Map<String, dynamic>?;
+    if (arguments?['currentCharacterInfo'] == null) {
+      AtmobLog.i(tag, '没有传递 currentCharacterInfo 参数');
+    } else {
+      mode = CharacterCustomEditMode.edit;
+      _currentCharacterInfo.value = arguments?['currentCharacterInfo'];
+
+      List<String>? hobbies = _currentCharacterInfo.value.hobbies;
+      print("hobbies: $hobbies");
+      if (hobbies != null) {
+        for (var hobby in hobbies) {
+          final exists = hobbiesLabelsList.any((e) => e.name == hobby);
+          if (!exists) {
+            hobbiesLabelsList.add(Hobbies(name: hobby));
+          }
+          final selectedHobby = hobbiesLabelsList.firstWhere(
+            (e) => e.name == hobby,
+            orElse: () => Hobbies(name: hobby),
+          );
+          if (selectedHobby.name != null) {
+            _hobbiesSelectLabels.add(selectedHobby);
+          }
+        }
+      }
 
+      List<String>? characters = _currentCharacterInfo.value.characters;
+      if (characters != null) {
+        for (var character in characters) {
+          final exists = characterLabelsList.any((e) => e.name == character);
+          if (!exists) {
+            characterLabelsList.add(CharactersList(name: character));
+          }
+          final selectedCharacter = characterLabelsList.firstWhere(
+            (e) => e.name == character,
+            orElse: () => CharactersList(name: character),
+          );
+          if (selectedCharacter.name != null) {
+            _characterSelectLabels.add(selectedCharacter);
+          }
+        }
+      }
+      _avatarUrl.value =
+          _currentCharacterInfo.value.imageUrl ?? _avatarUrl.value;
+      _currentNickname.value = _currentCharacterInfo.value.name ?? '';
+      _currentGender.value = _currentCharacterInfo.value.gender;
+      _currentBirthday.value = _currentCharacterInfo.value.birthday;
+      return;
+    }
+    mode = CharacterCustomEditMode.add;
     if (arguments?['hobbiesSelectLabels'] == null) {
       AtmobLog.i(tag, '没有传递 hobbiesSelectLabels 参数');
     } else {
@@ -103,10 +169,22 @@ class CharacterCustomDetailController extends BaseController {
     }
 
     if (arguments?['characterCustomName'] == null) {
-      AtmobLog.i(tag, '警告: 没有传递 characterCustomName 参数');
+      AtmobLog.i(tag, ' 没有传递 characterCustomName 参数');
     } else {
-      _characterCustomName(arguments?['characterCustomName'] ?? '');
-      AtmobLog.i(tag, "characterCustomName: $characterCustomName");
+      _currentNickname(arguments?['characterCustomName'] ?? '');
+      AtmobLog.i(tag, "characterCustomName: $currentNickname");
+    }
+  }
+
+  void nextAvatar() {
+    AtmobLog.d(tag, "nextAvatar");
+
+    if (_currentGender.value == 1) {
+      int currentIndex = _boyAvatars.indexOf(_avatarUrl.value);
+      _avatarUrl.value = _boyAvatars[(currentIndex + 1) % _boyAvatars.length];
+    } else {
+      int currentIndex = _girlAvatars.indexOf(_avatarUrl.value);
+      _avatarUrl.value = _girlAvatars[(currentIndex + 1) % _girlAvatars.length];
     }
   }
 
@@ -124,17 +202,87 @@ class CharacterCustomDetailController extends BaseController {
     Get.back();
   }
 
+  void clickNickname() async {
+    AtmobLog.d(tag, 'clickNickname');
+    final result = await ChangeNicknamePage.start(
+      nickName: _currentNickname.value,
+    );
+    if (result != null) {
+      _currentNickname.value = result;
+    }
+  }
+
   void clickUnlockButton() {
     AtmobLog.d(tag, "点击解锁按钮,生成专属人设");
-    generateCharacterCustom();
+
+    if (isEditMode) {
+      updateCharacterCustom();
+    } else {
+      generateCharacterCustom();
+    }
+  }
+
+  void clickGender() async {
+    AtmobLog.d(tag, 'clickGender');
+    final result = await ChangeGenderPage.start(gender: _currentGender.value);
+    if (result != null) {
+      _currentGender.value = result;
+    }
+  }
+
+  String get genderText {
+    if (_currentGender.value == 1) return '男';
+    if (_currentGender.value == 2) return '女';
+    return '请选择';
+  }
+
+  AssetGenImage? get genderImage {
+    if (_currentGender.value == 1) {
+      return Assets.images.iconCharacterCustomDetailMale;
+    }
+    if (_currentGender.value == 2) {
+      return Assets.images.iconCharacterCustomDetailFemale;
+    }
+    return null;
+  }
+
+  void clickBirthday() async {
+    AtmobLog.d(tag, 'clickBirthday');
+    final result = await ChangeBirthdayPage.start(
+      birthday: _currentBirthday.value,
+    );
+    if (result != null) {
+      AtmobLog.d(tag, 'clickBirthday result: $result');
+      _currentBirthday.value = result;
+    }
+  }
+
+  void clickHobbies() async {
+    AtmobLog.d(tag, 'clickHobbies');
+
+    var result = await ChangeHobbiesPage.start(hobbies: _hobbiesSelectLabels);
+    if (result is List<Hobbies>) {
+      _hobbiesSelectLabels.assignAll(result);
+    }
+  }
+
+  void clickCharacter() async {
+    AtmobLog.d(tag, 'clickCharacter');
+
+    var result = await ChangeCharacterPage.start(
+      characters: _characterSelectLabels,
+    );
+    if (result is List<CharactersList>) {
+      _characterSelectLabels.assignAll(result);
+    }
   }
 
   // 生成定制人设
   Future<void> generateCharacterCustom() async {
     try {
       await charactersRepository.generateCharacterCustom(
-        name: _characterCustomName.value,
-        gender: 1,
+        name: _currentNickname.value,
+        gender: _currentGender.value,
         hobbies:
             _hobbiesSelectLabels
                 .map((hobby) => hobby.name)
@@ -145,7 +293,7 @@ class CharacterCustomDetailController extends BaseController {
                 .map((character) => character.name)
                 .whereType<String>()
                 .toList(),
-        birthday: _birthday.value,
+        birthday: _currentBirthday.value,
         imageUrl: _avatarUrl.value,
       );
     } catch (error) {
@@ -154,7 +302,51 @@ class CharacterCustomDetailController extends BaseController {
         StorePage.start();
       }
       if (error is ServerErrorException) {
-        ToastUtil.show(error.message);
+        ErrorHandler.toastError(error);
+      }
+    }
+  }
+
+  //   更新专属人设
+  Future<void> updateCharacterCustom() async {
+    try {
+      if (_currentCharacterInfo.value.id == null) {
+        ToastUtil.show("当前人设为空");
+        return;
+      }
+      await charactersRepository.characterCustomUpdate(
+        id: _currentCharacterInfo.value.id!,
+        name: _currentNickname.value,
+        gender: 1,
+        hobbies:
+            _hobbiesSelectLabels
+                .map((hobby) => hobby.name)
+                .whereType<String>()
+                .toList(),
+        characters:
+            _characterSelectLabels
+                .map((character) => character.name)
+                .whereType<String>()
+                .toList(),
+        birthday: _currentBirthday.value,
+        imgUrl: _avatarUrl.value,
+      );
+      ToastUtil.show("更新成功");
+      _currentCharacterInfo.value.name = _currentNickname.value;
+      _currentCharacterInfo.value.birthday = _currentBirthday.value;
+      _currentCharacterInfo.value.gender = _currentGender.value;
+      _currentCharacterInfo.value.hobbies =
+          _hobbiesSelectLabels.map((e) => e.name!).toList();
+      _currentCharacterInfo.value.characters =
+          _characterSelectLabels.map((e) => e.name!).toList();
+      Get.back(result: _currentCharacterInfo.value);
+    } catch (error) {
+      if (error is ServerErrorException && error.code == 1005) {
+        ToastUtil.show('请开通会员解锁权益~');
+        StorePage.start();
+      }
+      if (error is ServerErrorException) {
+        ErrorHandler.toastError(error);
       }
     }
   }

+ 175 - 122
lib/module/character_custom/detail/character_custom_detail_page.dart

@@ -1,12 +1,16 @@
 import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/cupertino.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
 import 'package:keyboard/base/base_page.dart';
 import 'package:flutter/material.dart';
+import 'package:keyboard/data/bean/character_info.dart';
 import '../../../data/bean/custom_config_info.dart';
 import '../../../resource/assets.gen.dart';
 import '../../../resource/string.gen.dart';
 import '../../../router/app_pages.dart';
+import '../../../utils/age_zodiac_sign_util.dart';
 import '../../../utils/styles.dart';
+import '../../../widget/avatar/avatar_image_widget.dart';
 import 'character_custom_detail_controller.dart';
 import 'package:get/get.dart';
 
@@ -14,17 +18,19 @@ class CharacterCustomDetailPage
     extends BasePage<CharacterCustomDetailController> {
   const CharacterCustomDetailPage({super.key});
 
-  static void start({
+  static Future start({
     List<Hobbies>? hobbiesSelectLabels,
     List<CharactersList>? characterSelectLabels,
     String? characterCustomName,
-  }) {
-    Get.toNamed(
+    CharacterInfo? currentCharacterInfo,
+  }) async {
+    return Get.toNamed(
       RoutePath.characterCustomDetail,
       arguments: {
         'hobbiesSelectLabels': hobbiesSelectLabels,
         'characterSelectLabels': characterSelectLabels,
         'characterCustomName': characterCustomName,
+        'currentCharacterInfo': currentCharacterInfo,
       },
     );
   }
@@ -110,42 +116,61 @@ class CharacterCustomDetailPage
   }
 
   _buildNameCard() {
-    return Container(
-      padding: EdgeInsets.only(left: 104.w, top: 14.h),
-      child: Row(
-        crossAxisAlignment: CrossAxisAlignment.end,
-        children: [
-          Text(
-            controller.characterCustomName,
-            textAlign: TextAlign.center,
-            style: TextStyle(
-              color: Colors.black.withAlpha(204),
-              fontSize: 18.sp,
-              fontWeight: FontWeight.w500,
-            ),
-          ),
-          Container(
-            child: Assets.images.iconCharacterCustomDetailEdit.image(
-              width: 20.r,
-              height: 20.r,
+    return GestureDetector(
+      onTap: () {
+        controller.clickNickname();
+      },
+      child: Container(
+        padding: EdgeInsets.only(left: 104.w, top: 14.h),
+        child: Row(
+          crossAxisAlignment: CrossAxisAlignment.end,
+          children: [
+            Obx(() {
+              return Text(
+                controller.currentNickname ?? "请输入昵称",
+                textAlign: TextAlign.center,
+                style: TextStyle(
+                  color: Colors.black.withAlpha(204),
+                  fontSize: 18.sp,
+                  fontWeight: FontWeight.w500,
+                ),
+              );
+            }),
+            Container(
+              child: Assets.images.iconCharacterCustomDetailEdit.image(
+                width: 20.r,
+                height: 20.r,
+              ),
             ),
-          ),
-        ],
+          ],
+        ),
       ),
     );
   }
 
   _buildAvatar() {
     return GestureDetector(
-        onTap: controller.nextAvatar,
-        child: Container(
-      width: 72.r,
-      height: 72.r,
-      decoration: ShapeDecoration(
-        shape: OvalBorder(side: BorderSide(width: 2, color: Colors.white)),
-      ),
-      child:CachedNetworkImage(imageUrl: controller.avatarUrl,width: 72.r,height: 72.r,),
-    ));
+      onTap: controller.nextAvatar,
+      child: Obx(() {
+        return Container(
+          width: 72.r,
+          height: 72.r,
+          child:
+              controller.avatarUrl.isNotEmpty
+                  ? CircleAvatarWidget(
+                    image: Assets.images.iconKeyboardDefaultAvatar.provider(),
+                    imageUrl: controller.avatarUrl,
+                    size: 72.w,
+                    borderColor: Colors.white,
+                    borderWidth: 2.r,
+                    placeholder: (_, __) {
+                      return const CupertinoActivityIndicator();
+                    },
+                  )
+                  : SizedBox(),
+        );
+      }),
+    );
   }
 
   _buildAvatarSwitch() {
@@ -165,65 +190,81 @@ class CharacterCustomDetailPage
   // 性别
   Widget _buildGenderCard() {
     return _buildListItem(
-      onTap: () {
-        debugPrint('点击了性别');
+      onBottomTap: () {
+        controller.clickGender();
       },
       firstWidget: Text('性别', style: Styles.getTextStyleBlack204W400(14.sp)),
-      bottomWidget: Row(
-        children: [
-          Assets.images.iconCharacterCustomDetailMale.image(
-            width: 24.w,
-            height: 24.w,
-          ),
-          SizedBox(width: 6.w),
-          Text('男', style: Styles.getTextStyleBlack204W400(14.sp)),
-          Spacer(),
-          Assets.images.iconArrowRight.image(width: 24.w, height: 24.w),
-        ],
-      ),
+      bottomWidget: Obx(() {
+        return Row(
+          children: [
+            controller.genderImage != null
+                ? controller.genderImage!.image(width: 24.w, height: 24.w)
+                : const SizedBox(),
+            SizedBox(width: 6.w),
+            Text(
+              controller.genderText,
+              style: Styles.getTextStyleBlack204W400(14.sp),
+            ),
+            Spacer(),
+            Assets.images.iconArrowRight.image(width: 24.w, height: 24.w),
+          ],
+        );
+      }),
     );
   }
 
   Widget _buildBirthdayCard() {
     return _buildListItem(
-      onTap: () {
-        debugPrint('点击了生日');
+      onBottomTap: () {
+        controller.clickBirthday();
       },
       firstWidget: Text('出生日期', style: Styles.getTextStyleBlack204W400(14.sp)),
-      bottomWidget: Row(
-        children: [
-          Text('1998-12-16', style: Styles.getTextStyleBlack204W400(14.sp)),
-          SizedBox(width: 12.w),
-          Text('22岁', style: Styles.getTextStyleBlack204W400(14.sp)),
-          Spacer(),
-          Assets.images.iconArrowRight.image(width: 24.w, height: 24.w),
-        ],
-      ),
+      bottomWidget: Obx(() {
+        return Row(
+          children: [
+            Text(
+              controller.currentBirthday ?? "请选择",
+              style: Styles.getTextStyleBlack204W400(14.sp),
+            ),
+            SizedBox(width: 12.w),
+            Text(
+              controller.currentBirthday != null
+                  ? '${AgeZodiacSignUtil.calculateAgeFromString(controller.currentBirthday!).toString()}岁'
+                  : "",
+              style: Styles.getTextStyleBlack204W400(14.sp),
+            ),
+            Spacer(),
+            Assets.images.iconArrowRight.image(width: 24.w, height: 24.w),
+          ],
+        );
+      }),
     );
   }
 
   Widget _hobbiesCard() {
-
-    return _buildTagCard(
-      title: '兴趣爱好',
-      labels: controller.hobbiesSelectLabels,
-      isShowEmoji: true,
-      onTap:  () {
-        debugPrint('点击了兴趣爱好');
-      },
-    );
+    return Obx(() {
+      return _buildTagCard(
+        title: StringName.hobbies,
+        labels: controller.hobbiesSelectLabels,
+        isShowEmoji: true,
+        onTap: () {
+          controller.clickHobbies();
+        },
+      );
+    });
   }
 
   Widget _characterCard() {
-
-    return _buildTagCard(
-      title: '性格',
-      labels: controller.characterSelectLabels,
-      isShowEmoji: false,
-      onTap:  () {
-        debugPrint('点击了性格');
-      },
-    );
+    return Obx(() {
+      return _buildTagCard(
+        title: StringName.characterKeywords,
+        labels: controller.characterSelectLabels,
+        isShowEmoji: true,
+        onTap: () {
+          controller.clickCharacter();
+        },
+      );
+    });
   }
 
   Widget _buildUnlockButton() {
@@ -242,20 +283,27 @@ class CharacterCustomDetailPage
             borderRadius: BorderRadius.circular(50.r),
           ),
         ),
-        child: Row(
-          mainAxisAlignment: MainAxisAlignment.center,
-          children: [
-            Assets.images.iconCharacterCustomDetailLock.image(
-              width: 22.w,
-              height: 22.w,
-            ),
-            Text(
-              StringName.unlockExclusiveCharacter,
-              textAlign: TextAlign.center,
-              style: Styles.getTextStyleWhiteW500(16.sp),
-            ),
-          ],
-        ),
+        child:
+            controller.isEditMode
+                ? Text(
+                  StringName.save,
+                  textAlign: TextAlign.center,
+                  style: Styles.getTextStyleWhiteW500(16.sp),
+                )
+                : Row(
+                  mainAxisAlignment: MainAxisAlignment.center,
+                  children: [
+                    Assets.images.iconCharacterCustomDetailLock.image(
+                      width: 22.w,
+                      height: 22.w,
+                    ),
+                    Text(
+                      StringName.unlockExclusiveCharacter,
+                      textAlign: TextAlign.center,
+                      style: Styles.getTextStyleWhiteW500(16.sp),
+                    ),
+                  ],
+                ),
       ),
     );
   }
@@ -264,7 +312,8 @@ class CharacterCustomDetailPage
   Widget _buildListItem({
     required Widget firstWidget,
     required Widget bottomWidget,
-    VoidCallback? onTap,
+    VoidCallback? onBottomTap,
+    VoidCallback? onFirstTap,
   }) {
     return Container(
       padding: EdgeInsets.only(
@@ -285,11 +334,15 @@ class CharacterCustomDetailPage
         crossAxisAlignment: CrossAxisAlignment.start,
         mainAxisAlignment: MainAxisAlignment.center,
         children: [
-          firstWidget,
+          GestureDetector(
+            behavior: HitTestBehavior.opaque,
+            onTap: onFirstTap,
+            child: firstWidget,
+          ),
           _buildDivider(),
           GestureDetector(
             behavior: HitTestBehavior.opaque,
-            onTap: onTap,
+            onTap: onBottomTap,
             child: bottomWidget,
           ),
         ],
@@ -305,7 +358,7 @@ class CharacterCustomDetailPage
       decoration: ShapeDecoration(
         shape: RoundedRectangleBorder(
           side: BorderSide(
-            width: 1.r,
+            width: 0.5.r,
             strokeAlign: BorderSide.strokeAlignCenter,
             color: const Color(0xFFF5F4F9),
           ),
@@ -314,15 +367,14 @@ class CharacterCustomDetailPage
     );
   }
 
-
   Widget _buildTagCard({
     required String title,
     required List<dynamic> labels,
     required bool isShowEmoji,
-     VoidCallback? onTap,
+    VoidCallback? onTap,
   }) {
     return _buildListItem(
-      onTap: onTap,
+      onBottomTap: onTap,
       firstWidget: Text(title, style: Styles.getTextStyleBlack204W400(14.sp)),
       bottomWidget: Row(
         children: [
@@ -330,32 +382,33 @@ class CharacterCustomDetailPage
             child: Wrap(
               spacing: 8.w,
               runSpacing: 8.h,
-              children: labels.asMap().entries.map((entry) {
-                int index = entry.key;
-                var label = entry.value;
+              children:
+                  labels.asMap().entries.map((entry) {
+                    int index = entry.key;
+                    var label = entry.value;
 
-                // 根据索引设置不同的颜色
-                Color color;
-                switch (index) {
-                  case 0:
-                    color = Color(0xFFFEECE0);
-                    break;
-                  case 1:
-                    color = Color(0xFFE8E4FC);
-                    break;
-                  case 2:
-                    color = Color(0xFFE8F8E0);
-                    break;
-                  default:
-                    color = Color(0xFFFEECE0);
-                }
-                return _buildColorTag(
-                  color: color,
-                  name: label.name,
-                  emoji: label.emoji,
-                  isShowEmoji: isShowEmoji,
-                );
-              }).toList(),
+                    // 根据索引设置不同的颜色
+                    Color color;
+                    switch (index) {
+                      case 0:
+                        color = Color(0xFFFEECE0);
+                        break;
+                      case 1:
+                        color = Color(0xFFE8E4FC);
+                        break;
+                      case 2:
+                        color = Color(0xFFE8F8E0);
+                        break;
+                      default:
+                        color = Color(0xFFFEECE0);
+                    }
+                    return _buildColorTag(
+                      color: color,
+                      name: label.name,
+                      emoji: label.emoji,
+                      isShowEmoji: isShowEmoji,
+                    );
+                  }).toList(),
             ),
           ),
           SizedBox(width: 8.w),
@@ -384,7 +437,7 @@ class CharacterCustomDetailPage
         mainAxisAlignment: MainAxisAlignment.center,
         crossAxisAlignment: CrossAxisAlignment.center,
         children: [
-          if (emoji != null&&isShowEmoji)
+          if (emoji != null && isShowEmoji)
             Text(emoji, style: Styles.getTextStyleBlack204W400(14.sp)),
           if (emoji != null && name != null) SizedBox(width: 4.w),
           if (name != null)

+ 24 - 5
lib/module/character_custom/list/character_custom_list_controller.dart

@@ -14,6 +14,7 @@ import '../../../dialog/character_details_dialog.dart';
 import '../../../resource/string.gen.dart';
 import '../../../utils/atmob_log.dart';
 import '../../../utils/http_handler.dart';
+import '../detail/character_custom_detail_page.dart';
 
 @injectable
 class CharacterCustomListController extends BaseController {
@@ -91,7 +92,7 @@ class CharacterCustomListController extends BaseController {
     }
   }
 
-  void itemButtonClick(CharacterInfo characterInfo) async {
+  void itemAddButtonClick(CharacterInfo characterInfo) async {
     AtmobLog.d(tag, 'characterInfo ${characterInfo.toJson()} ');
     try {
       if (characterInfo.id != null) {
@@ -100,7 +101,6 @@ class CharacterCustomListController extends BaseController {
           keyboardId: _currentKeyboardInfo.value.id.toString(),
         );
       }
-
       ToastUtil.show("使用成功");
       Get.back(result: characterInfo);
     } catch (error) {
@@ -110,12 +110,31 @@ class CharacterCustomListController extends BaseController {
     }
   }
 
+  void itemEditClick(CharacterInfo characterInfo) async {
+    var result = await CharacterCustomDetailPage.start(
+      currentCharacterInfo: characterInfo,
+    );
+    if (result != null) {
+      if (result is CharacterInfo) {
+        // 更新列表
+        int index = _characterList.indexWhere(
+          (element) => element.id == result.id,
+        );
+        if (index != -1) {
+          _characterList[index] = result;
+        } else {
+          _characterList.add(result);
+        }
+      }
+    }
+  }
+
   // 删除定制人设
   void itemDeleteClick(CharacterInfo characterInfo) async {
     AtmobLog.d(tag, 'characterInfo ${characterInfo.toJson()} ');
-    if (characterInfo.isAdd == true) {
-      ToastUtil.show("使用中,不可删除");
-    }
+    // if (characterInfo.isAdd == true) {
+    //   ToastUtil.show("使用中,不可删除");
+    // }
     String? characterId = characterInfo.id;
     if (characterId != null) {
       try {

+ 2 - 2
lib/module/character_custom/list/character_custom_list_page.dart

@@ -125,7 +125,7 @@ class CharacterCustomListPage extends BasePage<CharacterCustomListController> {
   Widget _buildListItem({required CharacterInfo characterInfo}) {
     return GestureDetector(
       onTap: () {
-        // controller.itemButtonClick(characterInfo);
+        controller.itemEditClick(characterInfo);
       },
       child: Container(
         margin: EdgeInsets.symmetric(horizontal: 16.w),
@@ -255,7 +255,7 @@ class CharacterCustomListPage extends BasePage<CharacterCustomListController> {
   Widget _buildActionButton(CharacterInfo characterInfo) {
     return InkWell(
       onTap: () {
-        controller.itemButtonClick(characterInfo);
+        controller.itemAddButtonClick(characterInfo);
       },
       child: Container(
         width: 72.w,

+ 59 - 40
lib/module/keyboard/keyboard_view.dart

@@ -20,40 +20,53 @@ class KeyBoardView extends BaseView<KeyBoardController> {
   Widget buildBody(BuildContext context) {
     return Stack(
       children: [
-        IgnorePointer(child: Assets.images.bgKeyboard.image(width: 360.w)),
+        IgnorePointer(
+          child: Assets.images.bgKeyboard.image(width: 360.w, fit: BoxFit.fill),
+        ),
         SafeArea(
-          child: Column(
+          child: Stack(
             children: [
-              _buildTitle(),
-              Expanded(
-                child: SingleChildScrollView(
-                  child: Column(
-                    children: [
-                      _buildAvatarCard(),
-                      SizedBox(height: 10.h),
-                      _buildLoveIndexCard(),
-                      SizedBox(height: 10.h),
-                      Container(
-                        padding: EdgeInsets.only(top: 16.h),
-                        decoration: BoxDecoration(
-                          color: Colors.white,
-                          borderRadius: BorderRadius.only(
-                            topLeft: Radius.circular(16.r),
-                            topRight: Radius.circular(16.r),
+              Column(
+                children: [
+                  Expanded(
+                    child: SingleChildScrollView(
+                      child: Column(
+                        children: [
+                          _buildTitle(),
+                          _buildAvatarCard(),
+                          Container(height: 10.h, color: Colors.transparent),
+                          _buildLoveIndexCard(),
+                          SizedBox(height: 10.h),
+                          Container(
+                            padding: EdgeInsets.only(top: 16.h),
+                            decoration: BoxDecoration(
+                              color: Colors.white,
+                              borderRadius: BorderRadius.only(
+                                topLeft: Radius.circular(16.r),
+                                topRight: Radius.circular(16.r),
+                              ),
+                            ),
+                            child: Column(
+                              children: [
+                                _buildHitCard(),
+                                SizedBox(height: 10.h),
+                                _buildKeyboardSettings(),
+                                SizedBox(height: 90.h),
+                              ],
+                            ),
                           ),
-                        ),
-                        child: Column(
-                          children: [
-                            _buildHitCard(),
-                            SizedBox(height: 10.h),
-                            _buildKeyboardSettings(),
-                            _buildBanner(),
-                          ],
-                        ),
+                        ],
                       ),
-                    ],
+                    ),
                   ),
-                ),
+                ],
+              ),
+
+              Positioned(
+                bottom: 0,
+                left: 16.w,
+                right: 16.w,
+                child: _buildBanner(),
               ),
             ],
           ),
@@ -61,10 +74,16 @@ class KeyBoardView extends BaseView<KeyBoardController> {
       ],
     );
   }
+
   // 顶部标题栏
   Widget _buildTitle() {
     return Container(
-      padding: EdgeInsets.only(left: 16.w, right: 16.w, bottom: 12.h),
+      padding: EdgeInsets.only(
+        top: 12.h,
+        left: 16.w,
+        right: 16.w,
+        bottom: 25.h,
+      ),
       color: Colors.transparent,
       child: Row(
         children: [
@@ -187,13 +206,13 @@ class KeyBoardView extends BaseView<KeyBoardController> {
                               ProgressBar(
                                 title: StringName.keyboardPassion,
                                 value: controller.loveIndex.value?.passion,
-                                color: Color(0xFFFF637D),
+                                color: Color(0XFFFF637D),
                               ),
                               SizedBox(height: 6.h),
                               ProgressBar(
                                 title: StringName.keyboardRapport,
                                 value: controller.loveIndex.value?.rapport,
-                                color: Color(0xFFF5E8FC),
+                                color: Color(0XFFCE63FF),
                               ),
                             ],
                           ),
@@ -213,7 +232,7 @@ class KeyBoardView extends BaseView<KeyBoardController> {
                               ProgressBar(
                                 title: StringName.keyboardPromise,
                                 value: controller.loveIndex.value?.promise,
-                                color: Color(0xFF6382FF),
+                                color: Color(0XFF6382FF),
                               ),
                             ],
                           ),
@@ -416,12 +435,13 @@ class KeyBoardView extends BaseView<KeyBoardController> {
         Padding(
           padding: EdgeInsets.only(left: 12.w, right: 12.w),
           child: Row(
+            mainAxisAlignment: MainAxisAlignment.spaceBetween,
             children: [
               GestureDetector(
                 onTap: controller.clickEasyReply,
                 child: Container(
                   width: 166.w,
-                  height: 155.h,
+                  height: 155.11.w,
                   decoration: BoxDecoration(
                     boxShadow: [
                       BoxShadow(
@@ -433,7 +453,7 @@ class KeyBoardView extends BaseView<KeyBoardController> {
                     ],
                   ),
                   child: Assets.images.bgKeyboardEasyReply.image(
-                    fit: BoxFit.fill,
+                    fit: BoxFit.contain,
                   ),
                 ),
               ),
@@ -652,16 +672,16 @@ class KeyBoardView extends BaseView<KeyBoardController> {
   Widget _buildBanner() {
     return Obx(() {
       if (!controller.isShowBanner.value) {
-        return SizedBox(width: 328.w, height: 74.h);
+        return SizedBox(width: 328.w, height: 84.h);
       }
       return SizedBox(
         width: 328.w,
-        height: 74.h,
+        height: 84.h,
         child: Stack(
           clipBehavior: Clip.none,
           children: [
             Positioned(
-              top: 10.h,
+              top: 20.h,
               child: Assets.images.iconKeyboardBanner.image(
                 width: 328.w,
                 height: 64.h,
@@ -682,9 +702,8 @@ class KeyBoardView extends BaseView<KeyBoardController> {
               ),
             ),
             Positioned(
-              right: 10.w,
+              right: 6.w,
               top: 0.h,
-
               child: GestureDetector(
                 onTap: controller.clickCloseBanner,
                 child: Assets.images.iconKeyboardBannerClose.image(

+ 118 - 108
lib/module/keyboard_manage/keyboard_manage_controller.dart

@@ -4,6 +4,7 @@ import 'package:get/get.dart';
 import 'package:injectable/injectable.dart';
 import 'package:keyboard/base/base_controller.dart';
 import 'package:keyboard/data/bean/character_info.dart';
+import 'package:keyboard/data/repository/account_repository.dart';
 import 'package:keyboard/data/repository/keyboard_repository.dart';
 import 'package:keyboard/dialog/character_add_dialog.dart';
 import 'package:keyboard/dialog/custom_character/custom_character_add_dialog.dart';
@@ -13,6 +14,8 @@ import 'package:keyboard/utils/atmob_log.dart';
 import 'package:keyboard/utils/toast_util.dart';
 
 import '../../data/bean/keyboard_info.dart';
+import '../../utils/error_handler.dart';
+import '../../utils/http_handler.dart';
 
 enum KeyboardType {
   system, //通用键盘
@@ -24,6 +27,10 @@ class KeyboardManageController extends BaseController
     with GetTickerProviderStateMixin {
   final String tag = 'KeyboardManageController';
 
+  final AccountRepository accountRepository;
+
+  bool get isLogin => accountRepository.isLogin.value;
+
   // 自定义键盘列表
   final RxList<KeyboardInfo> _customKeyboardInfoList = RxList();
 
@@ -32,7 +39,8 @@ class KeyboardManageController extends BaseController
   // 当前自定义键盘
   final Rx<KeyboardInfo> _currentCustomKeyboardInfo = KeyboardInfo().obs;
 
-  KeyboardInfo get currentCustomKeyboardInfo => _currentCustomKeyboardInfo.value;
+  KeyboardInfo get currentCustomKeyboardInfo =>
+      _currentCustomKeyboardInfo.value;
 
   //当前自定义键盘人设列表
   final RxList<CharacterInfo> _currentCustomKeyboardCharacterList = RxList();
@@ -110,11 +118,17 @@ class KeyboardManageController extends BaseController
   // 键盘管理页面的pageController,用于控制通用键盘和自定义键盘的切换
   late PageController pageController;
 
-  KeyboardManageController(this.keyboardRepository);
+  KeyboardManageController(this.keyboardRepository, this.accountRepository);
 
   @override
   void onInit() {
     super.onInit();
+
+    final args = Get.arguments;
+    if (args is Map && args["customKeyboardInfo"] is KeyboardInfo) {
+      _currentCustomKeyboardInfo.value = args["customKeyboardInfo"];
+    }
+
     _dataLoad();
   }
 
@@ -155,10 +169,13 @@ class KeyboardManageController extends BaseController
 
       //检查是否是选择的键盘,如果没有选择的键盘,默认选择第一个
       if (_customKeyboardInfoList.isNotEmpty) {
-        _currentCustomKeyboardInfo.value = _customKeyboardInfoList.firstWhere(
-          (element) => element.isChoose == true,
-          orElse: () => _customKeyboardInfoList.first,
-        );
+        if (_currentCustomKeyboardInfo.value.id == null) {
+          _currentCustomKeyboardInfo.value = _customKeyboardInfoList.firstWhere(
+                (element) => element.isChoose == true,
+            orElse: () => _customKeyboardInfoList.first,
+          );
+        }
+
         _currentCustomIntimacy.value =
             _currentCustomKeyboardInfo.value.intimacy ?? 0;
         _currentCustomIntimacy.listen((intimacy) {
@@ -353,109 +370,88 @@ class KeyboardManageController extends BaseController
     isCustom
         ? saveCustomKeyboardCharacterList()
         : saveGeneralKeyboardCharacterList();
+
+
   }
 
   void saveCustomKeyboardCharacterList() {
-    if (_customIntimacyChanged.value) {
-      AtmobLog.i(tag, 'clickSave intimacyChanged');
-      _currentCustomKeyboardInfo.value.intimacy = currentCustomIntimacy;
-      String? keyboardId = _currentCustomKeyboardInfo.value.id;
-      if (keyboardId != null) {
-        keyboardRepository
-            .updateKeyboardInfo(
-              keyboardId: keyboardId,
-              intimacy: currentCustomIntimacy,
-            )
-            .then((value) {
-              ToastUtil.show(StringName.keyboardSaveSuccess);
-
-              CharacterGroupContentController characterGroupContentController =
-                  Get.find<CharacterGroupContentController>();
-              characterGroupContentController.refreshData();
-            })
-            .catchError((error) {
-              ToastUtil.show(StringName.keyboardSaveFailed);
-              _customIntimacyChanged.value = false;
-            });
-      }
-    }
-    if (_customKeyboardCharacterListChanged.value) {
-      AtmobLog.i(tag, 'clickSave keyboardChanged');
-      String? keyboardId = _currentCustomKeyboardInfo.value.id;
-      if (keyboardId != null) {
-        List<String> characterIds =
-            _currentCustomKeyboardCharacterList
-                .map((e) => e.id)
-                .toList()
-                .cast<String>();
-        keyboardRepository
-            .keyboardCharacterUpdate(
-              characterIds: characterIds,
-              keyboardId: keyboardId,
-            )
-            .then((value) {
-              _oldCustomCharacterList = List<CharacterInfo>.from(
-                _currentCustomKeyboardCharacterList,
-              );
-
-              CharacterGroupContentController characterGroupContentController =
-                  Get.find<CharacterGroupContentController>();
-              characterGroupContentController.refreshData();
-              ToastUtil.show(StringName.keyboardSaveSuccess);
-            })
-            .catchError((error) {
-              ToastUtil.show(StringName.keyboardSaveFailed);
-              _customKeyboardCharacterListChanged.value = false;
-            });
-      }
-    }
+    _saveKeyboardInfo(
+      intimacyChanged: _customIntimacyChanged,
+      currentIntimacy: currentCustomIntimacy,
+      currentKeyboardInfo: _currentCustomKeyboardInfo,
+      characterListChanged: _customKeyboardCharacterListChanged,
+      currentCharacterList: _currentCustomKeyboardCharacterList,
+      onUpdateSuccess: () {
+        _oldCustomCharacterList = List<CharacterInfo>.from(
+          _currentCustomKeyboardCharacterList,
+        );
+        Get.find<CharacterGroupContentController>().refreshData();
+        saveSuccessGetBack();
+      },
+    );
   }
 
   void saveGeneralKeyboardCharacterList() {
-    if (_generalIntimacyChanged.value) {
+    _saveKeyboardInfo(
+      intimacyChanged: _generalIntimacyChanged,
+      currentIntimacy: currentGeneralIntimacy,
+      currentKeyboardInfo: _currentGeneralKeyboardInfo,
+      characterListChanged: _generalKeyboardCharacterListChanged,
+      currentCharacterList: _currentGeneralKeyboardCharacterList,
+      onUpdateSuccess: () {
+        _oldGeneralCharacterList = List<CharacterInfo>.from(
+          _currentGeneralKeyboardCharacterList,
+        );
+      },
+    );
+  }
+
+  void _saveKeyboardInfo({
+    required RxBool intimacyChanged,
+    required int currentIntimacy,
+    required Rx<KeyboardInfo> currentKeyboardInfo,
+    required RxBool characterListChanged,
+    required List<CharacterInfo> currentCharacterList,
+    required VoidCallback onUpdateSuccess,
+  }) {
+    String? keyboardId = currentKeyboardInfo.value.id;
+
+    if (intimacyChanged.value && keyboardId != null) {
       AtmobLog.i(tag, 'clickSave intimacyChanged');
-      _currentGeneralKeyboardInfo.value.intimacy = currentGeneralIntimacy;
-      String? keyboardId = _currentGeneralKeyboardInfo.value.id;
-      if (keyboardId != null) {
-        keyboardRepository
-            .updateKeyboardInfo(
-              keyboardId: keyboardId,
-              intimacy: currentGeneralIntimacy,
-            )
-            .then((value) {
-              ToastUtil.show(StringName.keyboardSaveSuccess);
-            })
-            .catchError((error) {
-              ToastUtil.show(StringName.keyboardSaveFailed);
-              _generalIntimacyChanged.value = false;
-            });
-      }
+      currentKeyboardInfo.value.intimacy = currentIntimacy;
+      keyboardRepository
+          .updateKeyboardInfo(keyboardId: keyboardId, intimacy: currentIntimacy)
+          .then((_) => ToastUtil.show(StringName.keyboardSaveSuccess))
+          .catchError((error) {
+            if (error is ServerErrorException) {
+              ErrorHandler.toastError(error);
+            }
+            ToastUtil.show(StringName.keyboardSaveFailed);
+          })
+          .whenComplete(() => intimacyChanged.value = false);
     }
-    if (_generalKeyboardCharacterListChanged.value) {
+
+    if (characterListChanged.value && keyboardId != null) {
       AtmobLog.i(tag, 'clickSave keyboardChanged');
-      String? keyboardId = _currentGeneralKeyboardInfo.value.id;
-      if (keyboardId != null) {
-        List<String> characterIds =
-            _currentGeneralKeyboardCharacterList
-                .map((e) => e.id)
-                .toList()
-                .cast<String>();
-        keyboardRepository
-            .keyboardCharacterUpdate(
-              characterIds: characterIds,
-              keyboardId: keyboardId,
-            )
-            .then((value) {
-              _oldGeneralCharacterList = List<CharacterInfo>.from(
-                _currentGeneralKeyboardCharacterList,
-              );
-              ToastUtil.show(StringName.keyboardSaveSuccess);
-            })
-            .catchError((error) {
-              ToastUtil.show(StringName.keyboardSaveFailed);
-              _generalKeyboardCharacterListChanged.value = false;
-            });
-      }
+      List<String> characterIds =
+          currentCharacterList.map((e) => e.id).cast<String>().toList();
+      keyboardRepository
+          .keyboardCharacterUpdate(
+            keyboardId: keyboardId,
+            characterIds: characterIds,
+          )
+          .then((_) {
+            onUpdateSuccess();
+            ToastUtil.show(StringName.keyboardSaveSuccess);
+
+          })
+          .catchError((error) {
+            if (error is ServerErrorException) {
+              ErrorHandler.toastError(error);
+            }
+            ToastUtil.show(StringName.keyboardSaveFailed);
+          })
+          .whenComplete(() => characterListChanged.value = false);
     }
   }
 
@@ -479,6 +475,10 @@ class KeyboardManageController extends BaseController
   }
 
   clickAddCharacter({required bool isCustom}) {
+    if (!isLogin) {
+      ToastUtil.show("请先登录");
+      return;
+    }
     if (isCustom) {
       CharacterAddDialog.show(
         currentKeyboardInfo: currentCustomKeyboardInfo,
@@ -487,7 +487,6 @@ class KeyboardManageController extends BaseController
             keyboardId: _currentCustomKeyboardInfo.value.id ?? "",
             isCustom: true,
           );
-
         },
       );
     } else {
@@ -501,14 +500,20 @@ class KeyboardManageController extends BaseController
   }
 
   clickCustomCharacter() {
+    if (!isLogin) {
+      ToastUtil.show("请先登录");
+      return;
+    }
     AtmobLog.i(tag, 'clickCustomCharacter');
-    CustomCharacterAddDialog.show(currentKeyboardInfo: currentCustomKeyboardInfo, clickCallback: (){
-      getKeyboardCharacterList(
-        keyboardId: _currentCustomKeyboardInfo.value.id ?? "",
-        isCustom: true,
-      );
-
-    });
+    CustomCharacterAddDialog.show(
+      currentKeyboardInfo: currentCustomKeyboardInfo,
+      clickCallback: () {
+        getKeyboardCharacterList(
+          keyboardId: _currentCustomKeyboardInfo.value.id ?? "",
+          isCustom: true,
+        );
+      },
+    );
   }
 
   @override
@@ -517,4 +522,9 @@ class KeyboardManageController extends BaseController
     pageController.dispose();
     super.onClose();
   }
+
+ void saveSuccessGetBack(){
+
+   Get.back(result: _currentCustomKeyboardCharacterList);
+ }
 }

+ 13 - 28
lib/module/keyboard_manage/keyboard_manage_page.dart

@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
 import 'package:get/get.dart';
 import 'package:keyboard/base/base_page.dart';
+import 'package:keyboard/data/bean/keyboard_info.dart';
 import 'package:keyboard/module/keyboard_manage/keyboard_manage_controller.dart';
 import 'package:keyboard/resource/string.gen.dart';
 import 'package:reorderables/reorderables.dart';
@@ -21,8 +22,11 @@ class KeyboardManagePage extends BasePage<KeyboardManageController> {
     return true;
   }
 
-  static start() {
-    Get.toNamed(RoutePath.keyboardManage);
+  static Future start({KeyboardInfo? customKeyboardInfo}) async {
+    return Get.toNamed(
+      RoutePath.keyboardManage,
+      arguments: {"customKeyboardInfo": customKeyboardInfo},
+    );
   }
 
   @override
@@ -59,8 +63,8 @@ class KeyboardManagePage extends BasePage<KeyboardManageController> {
                       controller.switchPageKeyboardType(index);
                     },
                     children: [
-                      _buildCustomKeyboardSettings(),
-                      _buildGeneralKeyboardSettings(),
+                      _buildKeyboardSettings(isCustom: true),
+                      _buildKeyboardSettings(isCustom: false),
                     ],
                   ),
                 ),
@@ -113,27 +117,7 @@ class KeyboardManagePage extends BasePage<KeyboardManageController> {
     );
   }
 
-  Widget _buildCustomKeyboardSettings() {
-    return Column(
-      children: [
-        Expanded(
-          child: SingleChildScrollView(
-            child: Column(
-              crossAxisAlignment: CrossAxisAlignment.start,
-              children: [
-                _buildDropdownButton(), // 下拉框
-                _buildIntimacySlider(isCustom: true), // 亲密度模块
-                _buildKeyboardCharacter(isCustom: true), // 键盘人设
-              ],
-            ),
-          ),
-        ),
-        _buildSaveButton(isCustom: true),
-      ],
-    );
-  }
-
-  Widget _buildGeneralKeyboardSettings() {
+  Widget _buildKeyboardSettings({required bool isCustom}) {
     return Column(
       children: [
         Expanded(
@@ -141,13 +125,14 @@ class KeyboardManagePage extends BasePage<KeyboardManageController> {
             child: Column(
               crossAxisAlignment: CrossAxisAlignment.start,
               children: [
-                _buildIntimacySlider(isCustom: false), // 亲密度模块
-                _buildKeyboardCharacter(isCustom: false), // 键盘人设
+                if (isCustom) _buildDropdownButton(), // 只有自定义显示下拉
+                _buildIntimacySlider(isCustom: isCustom),
+                _buildKeyboardCharacter(isCustom: isCustom),
               ],
             ),
           ),
         ),
-        _buildSaveButton(isCustom: false),
+        _buildSaveButton(isCustom: isCustom),
       ],
     );
   }

+ 1 - 1
lib/module/mine/mine_controller.dart

@@ -62,7 +62,7 @@ class MineController extends BaseController {
     debugPrint('clickOnlineCustomerService');
     isTest?
     accountRepository
-        .loginUserLogin("11223344553", "1122")
+        .loginUserLogin("11223344551", "1122")
         .then((data) {
           Get.back();
           ToastUtil.show(StringName.loginSuccess);

+ 3 - 5
lib/module/mine/mine_view.dart

@@ -92,11 +92,9 @@ class MineView extends BaseView<MineController> {
         children: [
           controller.isLogin
               ? Assets.images.iconMineUserLogged.image(
-              width: 56.r, height: 56.r)
-              : Assets.images.iconMineUserNoLogin.image(
-            width: 56.r,
-            height: 56.r,
-          ),
+              width: 56.r, height: 56.r,fit: BoxFit.contain,)
+              : Assets.images.iconMineUserLogged.image(
+              width: 56.r, height: 56.r,fit: BoxFit.contain),
           SizedBox(width: 12.r),
           Text(
             controller.getUserName(),

+ 135 - 6
lib/module/profile/edit/profile_edit_controller.dart

@@ -3,12 +3,21 @@ import 'package:injectable/injectable.dart';
 import 'package:keyboard/base/base_controller.dart';
 import 'package:keyboard/data/bean/default_avatar_info.dart';
 import 'package:keyboard/data/bean/keyboard_info.dart';
+import 'package:keyboard/data/repository/keyboard_repository.dart';
+import 'package:keyboard/dialog/loading_dialog.dart';
 import 'package:keyboard/module/change/gender/change_gender_page.dart';
+import 'package:keyboard/utils/age_zodiac_sign_util.dart';
 import 'package:keyboard/utils/atmob_log.dart';
+import 'package:keyboard/utils/toast_util.dart';
 
+import '../../../data/bean/character_info.dart';
 import '../../../data/repository/config_repository.dart';
+import '../../../resource/assets.gen.dart';
+import '../../../utils/error_handler.dart';
+import '../../../utils/http_handler.dart';
 import '../../change/birthday/change_birthday_page.dart';
 import '../../change/nickname/change_nickname_page.dart';
+import '../../keyboard_manage/keyboard_manage_page.dart';
 
 enum ProfileEditMode {
   add, // 新增
@@ -21,6 +30,8 @@ class ProfileEditController extends BaseController {
 
   final ConfigRepository configRepository;
 
+  final KeyboardRepository keyboardRepository;
+
   final RxnInt _currentGender = RxnInt(null);
 
   final RxString _avatarUrl = "".obs;
@@ -32,13 +43,18 @@ class ProfileEditController extends BaseController {
 
   late final ProfileEditMode mode;
 
+  bool get isEditMode => mode == ProfileEditMode.edit;
+
+  bool get isAddMode => mode == ProfileEditMode.add;
+
   final Rx<KeyboardInfo> _currentCustomKeyboardInfo = KeyboardInfo().obs;
 
   Rx<KeyboardInfo> get currentCustomKeyboardInfo => _currentCustomKeyboardInfo;
 
   final RxnString _currentBirthday = RxnString(null);
 
-  String? get currentBirthday => _currentBirthday.value;
+  String? get currentBirthday =>
+      AgeZodiacSignUtil.formatBirthdayFromString(_currentBirthday.value);
 
   // 当前昵称
   final RxnString _currentNickname = RxnString(null);
@@ -59,7 +75,13 @@ class ProfileEditController extends BaseController {
 
   final RxList<String> _girlAvatars = <String>[].obs;
 
-  ProfileEditController(this.configRepository);
+  // 当前定制人设列表
+  final RxList<CharacterInfo> _currentCustomKeyboardCharacterList = RxList();
+
+  RxList<CharacterInfo> get currentCustomKeyboardCharacterList =>
+      _currentCustomKeyboardCharacterList;
+
+  ProfileEditController(this.configRepository, this.keyboardRepository);
 
   @override
   void onInit() {
@@ -68,11 +90,12 @@ class ProfileEditController extends BaseController {
     if (keyboardInfo != null) {
       mode = ProfileEditMode.edit;
       _currentCustomKeyboardInfo.value = keyboardInfo;
-
       _currentNickname.value = keyboardInfo.name;
       _currentGender.value = keyboardInfo.gender;
       _currentCustomIntimacy.value = keyboardInfo.intimacy ?? 30;
-      _avatarUrl.value = keyboardInfo.avatar ?? "";
+      _currentBirthday.value = keyboardInfo.birthday;
+      _avatarUrl.value = keyboardInfo.imageUrl ?? "";
+      getKeyboardCharacterList(keyboardId: keyboardInfo.id!);
     } else {
       mode = ProfileEditMode.add;
       _currentGender.value = null;
@@ -101,8 +124,27 @@ class ProfileEditController extends BaseController {
     }
   }
 
-  clickSaveButton() {
+  clickSaveButton() async {
     AtmobLog.d(tag, 'clickSaveButton');
+    if (!_validateForm()) return;
+
+    AtmobLog.d(tag, 'generateKeyboard');
+
+    try {
+      LoadingDialog.show(isEditMode ? "更新键盘数据中" : "生成键盘中");
+      if (isEditMode) {
+        await _updateKeyboard();
+        ToastUtil.show("更新键盘成功");
+      } else {
+        await _generateKeyboard();
+        ToastUtil.show("生成键盘成功");
+      }
+      Get.back(result: true);
+    } catch (error) {
+      _handleError(error);
+    } finally {
+      LoadingDialog.hide();
+    }
   }
 
   void nextAvatar() {
@@ -136,7 +178,7 @@ class ProfileEditController extends BaseController {
 
   void clickGender() async {
     AtmobLog.d(tag, 'clickGender');
-    final result = await ChangeGenderPage.start();
+    final result = await ChangeGenderPage.start(gender: _currentGender.value);
     if (result != null) {
       _currentGender.value = result;
     }
@@ -153,9 +195,96 @@ class ProfileEditController extends BaseController {
     }
   }
 
+  void clickRelationship() {
+    AtmobLog.d(tag, "clickRelationship");
+  }
+
+  void clickGoKeyboardManage() async{
+   var result  = await KeyboardManagePage.start(customKeyboardInfo: _currentCustomKeyboardInfo.value);
+   if (result != null && result is List<CharacterInfo>) {
+     AtmobLog.d(tag, 'clickGoKeyboardManage result: $result');
+     _currentCustomKeyboardCharacterList.assignAll(result);
+   }
+  }
+
   String get genderText {
     if (_currentGender.value == 1) return '男';
     if (_currentGender.value == 2) return '女';
     return '请选择';
   }
+  AssetGenImage? get genderImage {
+    if (_currentGender.value == 1) return Assets.images.iconCharacterCustomDetailMale;
+    if (_currentGender.value == 2) return Assets.images.iconCharacterCustomDetailFemale;
+    return null;
+  }
+
+  bool _validateForm() {
+    if (_currentNickname.value?.isEmpty ?? true) {
+      ToastUtil.show("请输入昵称");
+      return false;
+    }
+    if (_currentGender.value == null) {
+      ToastUtil.show("请选择性别");
+      return false;
+    }
+    if (_currentBirthday.value == null) {
+      ToastUtil.show("请选择生日");
+      return false;
+    }
+    if (_avatarUrl.value.isEmpty) {
+      ToastUtil.show("请选择头像");
+      return false;
+    }
+    if (isEditMode && _currentCustomKeyboardInfo.value.id == null) {
+      ToastUtil.show("请先选择键盘");
+      return false;
+    }
+    return true;
+  }
+
+  Future<void> _updateKeyboard() async {
+    await keyboardRepository.updateKeyboardInfo(
+      keyboardId: _currentCustomKeyboardInfo.value.id!,
+      name: _currentNickname.value,
+      imageUrl: _avatarUrl.value,
+      birthday: _currentBirthday.value,
+      gender: _currentGender.value,
+      intimacy: _currentCustomIntimacy.value,
+    );
+  }
+
+  Future<void> _generateKeyboard() async {
+    await keyboardRepository.getKeyboardGenerate(
+      name: _currentNickname.value!,
+      gender: _currentGender.value!,
+      imageUrl: _avatarUrl.value,
+      birthday: _currentBirthday.value!,
+      intimacy: _currentCustomIntimacy.value,
+    );
+  }
+
+  void _handleError(dynamic error) {
+    if (error is ServerErrorException) {
+      ErrorHandler.toastError(error);
+      AtmobLog.d(tag, 'clickSaveButton error: ${error.message}');
+    } else {
+      ErrorHandler.toastError(error);
+      AtmobLog.d(tag, 'clickSaveButton error: $error');
+    }
+  }
+
+  // 获取当前键盘人设列表
+  void getKeyboardCharacterList({required String keyboardId}) {
+    keyboardRepository.getKeyboardCharacterList(keyboardId: keyboardId).then((
+      keyboardCharacterListResponse,
+    ) {
+      AtmobLog.i(
+        tag,
+        'keyboardCharacterListResponse: ${keyboardCharacterListResponse.characterInfos.toString()}',
+      );
+      _currentCustomKeyboardCharacterList.assignAll(
+        keyboardCharacterListResponse.characterInfos,
+      );
+    });
+  }
 }

+ 216 - 132
lib/module/profile/edit/profile_edit_page.dart

@@ -8,10 +8,12 @@ import 'package:flutter/material.dart';
 import 'package:get/get.dart';
 import 'package:keyboard/utils/age_zodiac_sign_util.dart';
 
+import '../../../data/bean/character_info.dart';
 import '../../../data/bean/keyboard_info.dart';
 import '../../../resource/assets.gen.dart';
 import '../../../resource/string.gen.dart';
 import '../../../router/app_pages.dart';
+import '../../../utils/intimacy_util.dart';
 import '../../../utils/styles.dart';
 import '../../../widget/avatar/avatar_image_widget.dart';
 import '../../../widget/gradient_rect_slider_track_shape.dart';
@@ -19,8 +21,8 @@ import '../../../widget/gradient_rect_slider_track_shape.dart';
 class ProfileEditPage extends BasePage<ProfileEditController> {
   const ProfileEditPage({super.key});
 
-  static start({KeyboardInfo? keyboardInfo}) {
-    Get.toNamed(
+  static Future start({KeyboardInfo? keyboardInfo}) async {
+    return Get.toNamed(
       RoutePath.profileEdit,
       arguments: {"keyboardInfo": keyboardInfo},
     );
@@ -67,24 +69,37 @@ class ProfileEditPage extends BasePage<ProfileEditController> {
                             ),
                           ),
                         ),
-                        child: SingleChildScrollView(
-                          child: Column(
-                            children: [
-                              _buildNameCard(),
-                              SizedBox(height: 20.h),
-                              _buildIntimacySlider(),
-                              SizedBox(height: 10.h),
-                              _buildGenderCard(),
-                              SizedBox(height: 10.h),
-                              _buildBirthdayCard(),
-                              SizedBox(height: 10.h),
-                              // _buildRelationshipCard(),
-                              // SizedBox(height: 10.h),
-                            ],
-                          ),
+                        child: Column(
+                          children: [
+                            _buildNameCard(),
+                            SizedBox(height: 20.h),
+                            Expanded(
+                              child: SingleChildScrollView(
+                                child: Column(
+                                  children: [
+                                    _buildIntimacySlider(),
+                                    SizedBox(height: 10.h),
+                                    _buildGenderCard(),
+                                    SizedBox(height: 10.h),
+                                    _buildBirthdayCard(),
+                                    SizedBox(height: 10.h),
+                                    controller.isEditMode
+                                        ? _buildRelationshipCard()
+                                        : SizedBox(),
+                                    SizedBox(height: 10.h),
+                                    controller.isEditMode
+                                        ? _buildCharacterList()
+                                        : SizedBox(),
+                                    SizedBox(height: 20.h),
+                                  ],
+                                ),
+                              ),
+                            ),
+                          ],
                         ),
                       ),
                     ),
+
                     Container(
                       color: Color(0xFFF6F5FA),
                       child: _buildSaveButton(),
@@ -124,7 +139,7 @@ class ProfileEditPage extends BasePage<ProfileEditController> {
           children: [
             Obx(() {
               return Text(
-                controller.currentNickname??"请输入昵称",
+                controller.currentNickname ?? "请输入昵称",
                 textAlign: TextAlign.center,
                 style: TextStyle(
                   color: Colors.black.withAlpha(204),
@@ -153,18 +168,18 @@ class ProfileEditPage extends BasePage<ProfileEditController> {
           width: 72.r,
           height: 72.r,
           child:
-          controller.avatarUrl.isNotEmpty
-              ? CircleAvatarWidget(
-            image: Assets.images.iconKeyboardDefaultAvatar.provider(),
-            imageUrl: controller.avatarUrl,
-            size: 72.w,
-            borderColor: Colors.white,
-            borderWidth: 2.r,
-            placeholder: (_, __) {
-              return const CupertinoActivityIndicator();
-            },
-          )
-              : SizedBox(),
+              controller.avatarUrl.isNotEmpty
+                  ? CircleAvatarWidget(
+                    image: Assets.images.iconKeyboardDefaultAvatar.provider(),
+                    imageUrl: controller.avatarUrl,
+                    size: 72.w,
+                    borderColor: Colors.white,
+                    borderWidth: 2.r,
+                    placeholder: (_, __) {
+                      return const CupertinoActivityIndicator();
+                    },
+                  )
+                  : SizedBox(),
         );
       }),
     );
@@ -187,17 +202,16 @@ class ProfileEditPage extends BasePage<ProfileEditController> {
   // 性别
   Widget _buildGenderCard() {
     return _buildListItem(
-      onTap: () {
+      onBottomTap: () {
         controller.clickGender();
       },
       firstWidget: Text('性别', style: Styles.getTextStyleBlack204W400(14.sp)),
       bottomWidget: Obx(() {
         return Row(
           children: [
-            Assets.images.iconCharacterCustomDetailMale.image(
-              width: 24.w,
-              height: 24.w,
-            ),
+            controller.genderImage != null
+                ? controller.genderImage!.image(width: 24.w, height: 24.w)
+                : const SizedBox(),
             SizedBox(width: 6.w),
             Text(
               controller.genderText,
@@ -213,11 +227,10 @@ class ProfileEditPage extends BasePage<ProfileEditController> {
 
   Widget _buildBirthdayCard() {
     return _buildListItem(
-      onTap: () {
+      onBottomTap: () {
         controller.clickBirthday();
       },
-      firstWidget: Text(
-          '出生日期', style: Styles.getTextStyleBlack204W400(14.sp)),
+      firstWidget: Text('出生日期', style: Styles.getTextStyleBlack204W400(14.sp)),
       bottomWidget: Obx(() {
         return Row(
           children: [
@@ -240,16 +253,88 @@ class ProfileEditPage extends BasePage<ProfileEditController> {
     );
   }
 
+  Widget _buildCharacterList() {
+    return _buildListItem(
+      onFirstTap: () {
+        controller.clickGoKeyboardManage();
+      },
+      firstWidget: Row(
+        children: [
+          Text('键盘人设', style: Styles.getTextStyleBlack204W400(14.sp)),
+          Spacer(),
+          Assets.images.iconArrowRight.image(width: 24.w, height: 24.w),
+        ],
+      ),
+      bottomWidget: Column(
+        children: [
+          SizedBox(height: 7.h),
+          Obx(() {
+            final list = controller.currentCustomKeyboardCharacterList;
+            if (list.isEmpty) {
+              return const Center(child: CircularProgressIndicator());
+            }
+            final showList = list.take(9).toList();
+
+            return SizedBox(
+              height: 32.h * 3 + 8.h * 2,
+              child: GridView.builder(
+                padding: EdgeInsets.zero,
+                physics: const NeverScrollableScrollPhysics(),
+
+                itemCount: showList.length,
+                gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
+                  crossAxisCount: 3, // 每行3个
+                  mainAxisSpacing: 8.h,
+                  crossAxisSpacing: 8.w,
+                  childAspectRatio: 96.w / 32.h, // 控制宽高比
+                ),
+                itemBuilder: (context, index) {
+                  return _buildCharacterItem(showList[index]);
+                },
+              ),
+            );
+          }),
+          // Spacer(),
+          // Assets.images.iconArrowRight.image(width: 24.w, height: 24.w),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildCharacterItem(CharacterInfo character) {
+    return Container(
+      alignment: Alignment.center,
+      decoration: ShapeDecoration(
+        color: const Color(0xFFF5F4F9),
+        shape: RoundedRectangleBorder(
+          borderRadius: BorderRadius.circular(31.r),
+        ),
+      ),
+      child: Text(
+        '${character.emoji}${character.name}',
+        style: Styles.getTextStyleBlack204W400(12.sp),
+        maxLines: 1,
+        overflow: TextOverflow.fade,
+      ),
+    );
+  }
+
   Widget _buildRelationshipCard() {
     return _buildListItem(
-      onTap: () {
-        debugPrint('点击了关系');
+      onBottomTap: () {
+        controller.clickRelationship();
       },
       firstWidget: Text('关系', style: Styles.getTextStyleBlack204W400(14.sp)),
       bottomWidget: Row(
         children: [
-          Spacer(),
-          Assets.images.iconArrowRight.image(width: 24.w, height: 24.w),
+          Obx(() {
+            return Text(
+              IntimacyUtil.getIntimacyName(controller.currentCustomIntimacy),
+              style: Styles.getTextStyleBlack204W400(14.sp),
+            );
+          }),
+          // Spacer(),
+          // Assets.images.iconArrowRight.image(width: 24.w, height: 24.w),
         ],
       ),
     );
@@ -265,12 +350,7 @@ class ProfileEditPage extends BasePage<ProfileEditController> {
         margin: EdgeInsets.only(left: 16.w, right: 16.w, bottom: 16.h),
         height: 48.h,
         alignment: Alignment.center,
-        decoration: ShapeDecoration(
-          color: const Color(0xFF7D46FC),
-          shape: RoundedRectangleBorder(
-            borderRadius: BorderRadius.circular(50.r),
-          ),
-        ),
+        decoration: Styles.getActivateButtonDecoration(50.r),
         child: Text(
           StringName.profileEditSave,
           textAlign: TextAlign.center,
@@ -284,7 +364,8 @@ class ProfileEditPage extends BasePage<ProfileEditController> {
   Widget _buildListItem({
     required Widget firstWidget,
     required Widget bottomWidget,
-    VoidCallback? onTap,
+    VoidCallback? onBottomTap,
+    VoidCallback? onFirstTap,
   }) {
     return Container(
       padding: EdgeInsets.only(
@@ -305,11 +386,15 @@ class ProfileEditPage extends BasePage<ProfileEditController> {
         crossAxisAlignment: CrossAxisAlignment.start,
         mainAxisAlignment: MainAxisAlignment.center,
         children: [
-          firstWidget,
+          GestureDetector(
+            behavior: HitTestBehavior.opaque,
+            onTap: onFirstTap,
+            child: firstWidget,
+          ),
           _buildDivider(),
           GestureDetector(
             behavior: HitTestBehavior.opaque,
-            onTap: onTap,
+            onTap: onBottomTap,
             child: bottomWidget,
           ),
         ],
@@ -325,7 +410,7 @@ class ProfileEditPage extends BasePage<ProfileEditController> {
       decoration: ShapeDecoration(
         shape: RoundedRectangleBorder(
           side: BorderSide(
-            width: 1.r,
+            width: 0.5.r,
             strokeAlign: BorderSide.strokeAlignCenter,
             color: const Color(0xFFF5F4F9),
           ),
@@ -336,91 +421,90 @@ class ProfileEditPage extends BasePage<ProfileEditController> {
 
   Widget _buildIntimacySlider() {
     return // 亲密度模块
-      Container(
-        margin: EdgeInsets.only(left: 16.w, top: 24.h, right: 16.w),
-        padding: EdgeInsets.only(
-          left: 16.w,
-          top: 23.h,
-          right: 16.w,
-          bottom: 26.h,
-        ),
-        decoration: BoxDecoration(
-          image: DecorationImage(
-            image: Assets.images.bgProfileEditIntimacy.provider(),
-            fit: BoxFit.fill,
-          ),
-          borderRadius: BorderRadius.circular(10.r),
+    Container(
+      margin: EdgeInsets.only(left: 16.w, top: 24.h, right: 16.w),
+      padding: EdgeInsets.only(
+        left: 16.w,
+        top: 23.h,
+        right: 16.w,
+        bottom: 26.h,
+      ),
+      decoration: BoxDecoration(
+        image: DecorationImage(
+          image: Assets.images.bgProfileEditIntimacy.provider(),
+          fit: BoxFit.fill,
         ),
-        child: Column(
-          children: [
-            // 亲密度
-            Row(
-              crossAxisAlignment: CrossAxisAlignment.center,
-              children: [
-                Assets.images.iconKeyboardManageFavorite.image(
-                  width: 20.w,
-                  height: 20.w,
-                ),
-                Assets.images.iconKeyboardManageIntimacyText.image(
-                  width: 48.w,
-                  height: 19.h,
-                ),
-                const Spacer(),
-                Container(
-                  alignment: Alignment.center,
-                  width: 81.w,
-                  height: 28.h,
-                  decoration: ShapeDecoration(
-                    color: const Color(0xFFE1E0E7),
-                    shape: RoundedRectangleBorder(
-                      borderRadius: BorderRadius.circular(16.r),
-                    ),
+        borderRadius: BorderRadius.circular(10.r),
+      ),
+      child: Column(
+        children: [
+          // 亲密度
+          Row(
+            crossAxisAlignment: CrossAxisAlignment.center,
+            children: [
+              Assets.images.iconKeyboardManageFavorite.image(
+                width: 20.w,
+                height: 20.w,
+              ),
+              Assets.images.iconKeyboardManageIntimacyText.image(
+                width: 48.w,
+                height: 19.h,
+              ),
+              const Spacer(),
+              Container(
+                alignment: Alignment.center,
+                width: 81.w,
+                height: 28.h,
+                decoration: ShapeDecoration(
+                  color: const Color(0xFFE1E0E7),
+                  shape: RoundedRectangleBorder(
+                    borderRadius: BorderRadius.circular(16.r),
                   ),
-                  child: Obx(() {
-                    return Text(
-                      '${StringName.intimacy}${controller
-                          .currentCustomIntimacy}%',
-                      textAlign: TextAlign.right,
-                      style: TextStyle(
-                        color: Colors.black.withAlpha(204),
-                        fontSize: 12.sp,
-                        fontWeight: FontWeight.w400,
-                      ),
-                    );
-                  }),
                 ),
-              ],
-            ),
-            SizedBox(height: 19.h),
-            Builder(
-              builder: (context) {
-                return SliderTheme(
-                  data: SliderTheme.of(context).copyWith(
-                    trackShape: const GradientRectSliderTrackShape(),
-                    trackHeight: 8.h,
-                    thumbColor: Colors.white,
-                    thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7.r),
-                    overlayShape: const RoundSliderOverlayShape(
-                      overlayRadius: 16,
+                child: Obx(() {
+                  return Text(
+                    '${StringName.intimacy}${controller.currentCustomIntimacy}%',
+                    textAlign: TextAlign.right,
+                    style: TextStyle(
+                      color: Colors.black.withAlpha(204),
+                      fontSize: 12.sp,
+                      fontWeight: FontWeight.w400,
                     ),
+                  );
+                }),
+              ),
+            ],
+          ),
+          SizedBox(height: 19.h),
+          Builder(
+            builder: (context) {
+              return SliderTheme(
+                data: SliderTheme.of(context).copyWith(
+                  trackShape: const GradientRectSliderTrackShape(),
+                  trackHeight: 8.h,
+                  thumbColor: Colors.white,
+                  thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7.r),
+                  overlayShape: const RoundSliderOverlayShape(
+                    overlayRadius: 16,
                   ),
-                  child: Obx(() {
-                    return Slider(
-                      value: controller.currentCustomIntimacy.toDouble(),
+                ),
+                child: Obx(() {
+                  return Slider(
+                    value: controller.currentCustomIntimacy.toDouble(),
 
-                      divisions: 100,
-                      min: 0,
-                      max: 100,
-                      onChanged: (value) {
-                        controller.updateIntimacy(value.toInt());
-                      },
-                    );
-                  }),
-                );
-              },
-            ),
-          ],
-        ),
-      );
+                    divisions: 100,
+                    min: 0,
+                    max: 100,
+                    onChanged: (value) {
+                      controller.updateIntimacy(value.toInt());
+                    },
+                  );
+                }),
+              );
+            },
+          ),
+        ],
+      ),
+    );
   }
 }

+ 17 - 11
lib/module/profile/profile_controller.dart

@@ -44,7 +44,7 @@ class ProfileController extends BaseController {
   }
 
   //   获取定制键盘
-  void getCustomKeyboard() {
+  Future<void> getCustomKeyboard() async {
     AtmobLog.i(tag, 'getCustomKeyboard');
     keyboardRepository.getKeyboardList(type: KeyboardType.custom.name).then((
       keyboardListResponse,
@@ -75,12 +75,15 @@ class ProfileController extends BaseController {
     Get.back();
   }
 
-  clickAddButton() {
+  clickAddButton() async {
     AtmobLog.d(tag, "clickAddButton");
-    ProfileEditPage.start();
+    var result = await ProfileEditPage.start();
+    if (result == true) {
+      await getCustomKeyboard();
+    }
   }
 
-  clickSaveButton() {
+  clickSaveButton() async {
     AtmobLog.d(tag, "clickSaveButton");
     final keyboardInfo = _currentCustomKeyboardInfo.value;
     if (_currentCustomKeyboardInfo.value.isChoose == true) {
@@ -90,24 +93,27 @@ class ProfileController extends BaseController {
     }
     if (keyboardInfo.id?.isNotEmpty == true) {
       try {
-        keyboardRepository.keyboardChoose(keyboardId: keyboardInfo.id!);
+        await keyboardRepository.keyboardChoose(keyboardId: keyboardInfo.id!);
         keyboardRepository.refreshData();
         ToastUtil.show("当前键盘已选择");
         clickBack();
       } catch (error) {
         if (error is ServerErrorException) {
-          ToastUtil.show(error.message);
-        } else {
           ErrorHandler.toastError(error);
+        } else {
+          AtmobLog.d(tag, " $error");
         }
       }
     }
   }
 
-  clickAvatar({required bool isUser}) {
+  clickAvatar({required bool isUser, KeyboardInfo? keyboardInfo}) async {
     AtmobLog.d(tag, "clickAvatar");
+    if (!isUser) {
+      var result = await ProfileEditPage.start(keyboardInfo: keyboardInfo);
+      if (result == true) {
+        await getCustomKeyboard();
+      }
+    }
   }
-
-
-
 }

+ 2 - 2
lib/module/profile/profile_page.dart

@@ -186,7 +186,7 @@ class ProfilePage extends BasePage<ProfileController> {
   }) {
     final String title = hasKeyboard ? '我&${keyboardInfo?.name ?? ""}' : '我';
     final int? intimacy = hasKeyboard ? keyboardInfo?.intimacy : null;
-    final String? keyboardAvatar = hasKeyboard ? keyboardInfo?.avatar : null;
+    final String? keyboardAvatar = hasKeyboard ? keyboardInfo?.imageUrl : null;
     final int gender = hasKeyboard ? (keyboardInfo?.gender ?? 1) : 1;
 
     return GestureDetector(
@@ -286,7 +286,7 @@ class ProfilePage extends BasePage<ProfileController> {
                             ? _buildProfileAvatar(
                           imageUrl: keyboardAvatar,
                           gender: gender,
-                          onTap: () => controller.clickAvatar(isUser: false),
+                          onTap: () => controller.clickAvatar(isUser: false,keyboardInfo:keyboardInfo),
                           genderIconAlignment: Alignment.topLeft,
                         )
                             : _buildKeyboardListEmptyAvatar(

+ 80 - 4
lib/resource/assets.gen.dart

@@ -231,6 +231,14 @@ class $AssetsImagesGen {
   AssetGenImage get iconAlipayScanPayment =>
       const AssetGenImage('assets/images/icon_alipay_scan_payment.webp');
 
+  /// File path: assets/images/icon_aquarius.webp
+  AssetGenImage get iconAquarius =>
+      const AssetGenImage('assets/images/icon_aquarius.webp');
+
+  /// File path: assets/images/icon_aries.webp
+  AssetGenImage get iconAries =>
+      const AssetGenImage('assets/images/icon_aries.webp');
+
   /// File path: assets/images/icon_arrow_right.webp
   AssetGenImage get iconArrowRight =>
       const AssetGenImage('assets/images/icon_arrow_right.webp');
@@ -239,9 +247,21 @@ class $AssetsImagesGen {
   AssetGenImage get iconBlackBack =>
       const AssetGenImage('assets/images/icon_black_back.webp');
 
-  /// File path: assets/images/icon_change_birthday_gemini.webp
-  AssetGenImage get iconChangeBirthdayGemini =>
-      const AssetGenImage('assets/images/icon_change_birthday_gemini.webp');
+  /// File path: assets/images/icon_cancer.webp
+  AssetGenImage get iconCancer =>
+      const AssetGenImage('assets/images/icon_cancer.webp');
+
+  /// File path: assets/images/icon_capricorn.webp
+  AssetGenImage get iconCapricorn =>
+      const AssetGenImage('assets/images/icon_capricorn.webp');
+
+  /// File path: assets/images/icon_change_character_empty.webp
+  AssetGenImage get iconChangeCharacterEmpty =>
+      const AssetGenImage('assets/images/icon_change_character_empty.webp');
+
+  /// File path: assets/images/icon_change_character_unselect.webp
+  AssetGenImage get iconChangeCharacterUnselect =>
+      const AssetGenImage('assets/images/icon_change_character_unselect.webp');
 
   /// File path: assets/images/icon_change_gender_female_logo.webp
   AssetGenImage get iconChangeGenderFemaleLogo =>
@@ -270,6 +290,10 @@ class $AssetsImagesGen {
     'assets/images/icon_change_gender_male_unselect.webp',
   );
 
+  /// File path: assets/images/icon_change_hobbies_unselect.webp
+  AssetGenImage get iconChangeHobbiesUnselect =>
+      const AssetGenImage('assets/images/icon_change_hobbies_unselect.webp');
+
   /// File path: assets/images/icon_character_arrow_down.webp
   AssetGenImage get iconCharacterArrowDown =>
       const AssetGenImage('assets/images/icon_character_arrow_down.webp');
@@ -295,6 +319,11 @@ class $AssetsImagesGen {
     'assets/images/icon_character_custom_detail_edit.webp',
   );
 
+  /// File path: assets/images/icon_character_custom_detail_female.webp
+  AssetGenImage get iconCharacterCustomDetailFemale => const AssetGenImage(
+    'assets/images/icon_character_custom_detail_female.webp',
+  );
+
   /// File path: assets/images/icon_character_custom_detail_lock.webp
   AssetGenImage get iconCharacterCustomDetailLock => const AssetGenImage(
     'assets/images/icon_character_custom_detail_lock.webp',
@@ -459,6 +488,10 @@ class $AssetsImagesGen {
   AssetGenImage get iconEmojiPercent =>
       const AssetGenImage('assets/images/icon_emoji_percent.webp');
 
+  /// File path: assets/images/icon_gemini.webp
+  AssetGenImage get iconGemini =>
+      const AssetGenImage('assets/images/icon_gemini.webp');
+
   /// File path: assets/images/icon_goods_info_title.webp
   AssetGenImage get iconGoodsInfoTitle =>
       const AssetGenImage('assets/images/icon_goods_info_title.webp');
@@ -611,6 +644,14 @@ class $AssetsImagesGen {
   AssetGenImage get iconKeyboardVipLogo =>
       const AssetGenImage('assets/images/icon_keyboard_vip_logo.webp');
 
+  /// File path: assets/images/icon_leo.webp
+  AssetGenImage get iconLeo =>
+      const AssetGenImage('assets/images/icon_leo.webp');
+
+  /// File path: assets/images/icon_libra.webp
+  AssetGenImage get iconLibra =>
+      const AssetGenImage('assets/images/icon_libra.webp');
+
   /// File path: assets/images/icon_lock.webp
   AssetGenImage get iconLock =>
       const AssetGenImage('assets/images/icon_lock.webp');
@@ -680,6 +721,10 @@ class $AssetsImagesGen {
   AssetGenImage get iconModeSwitchArrow =>
       const AssetGenImage('assets/images/icon_mode_switch_arrow.webp');
 
+  /// File path: assets/images/icon_pisces.webp
+  AssetGenImage get iconPisces =>
+      const AssetGenImage('assets/images/icon_pisces.webp');
+
   /// File path: assets/images/icon_profile_add.webp
   AssetGenImage get iconProfileAdd =>
       const AssetGenImage('assets/images/icon_profile_add.webp');
@@ -700,6 +745,14 @@ class $AssetsImagesGen {
   AssetGenImage get iconProfilePlus =>
       const AssetGenImage('assets/images/icon_profile_plus.webp');
 
+  /// File path: assets/images/icon_sagittarius.webp
+  AssetGenImage get iconSagittarius =>
+      const AssetGenImage('assets/images/icon_sagittarius.webp');
+
+  /// File path: assets/images/icon_scorpio.webp
+  AssetGenImage get iconScorpio =>
+      const AssetGenImage('assets/images/icon_scorpio.webp');
+
   /// File path: assets/images/icon_store_agree_privacy.webp
   AssetGenImage get iconStoreAgreePrivacy =>
       const AssetGenImage('assets/images/icon_store_agree_privacy.webp');
@@ -812,6 +865,10 @@ class $AssetsImagesGen {
   AssetGenImage get iconTabMineUnselect =>
       const AssetGenImage('assets/images/icon_tab_mine_unselect.webp');
 
+  /// File path: assets/images/icon_taurus.webp
+  AssetGenImage get iconTaurus =>
+      const AssetGenImage('assets/images/icon_taurus.webp');
+
   /// File path: assets/images/icon_ticket_dialog_button.webp
   AssetGenImage get iconTicketDialogButton =>
       const AssetGenImage('assets/images/icon_ticket_dialog_button.webp');
@@ -837,6 +894,10 @@ class $AssetsImagesGen {
   AssetGenImage get iconUploading =>
       const AssetGenImage('assets/images/icon_uploading.webp');
 
+  /// File path: assets/images/icon_virgo.webp
+  AssetGenImage get iconVirgo =>
+      const AssetGenImage('assets/images/icon_virgo.webp');
+
   /// File path: assets/images/icon_wechat.webp
   AssetGenImage get iconWechat =>
       const AssetGenImage('assets/images/icon_wechat.webp');
@@ -901,21 +962,28 @@ class $AssetsImagesGen {
     iconAiModel,
     iconAlipayPayment,
     iconAlipayScanPayment,
+    iconAquarius,
+    iconAries,
     iconArrowRight,
     iconBlackBack,
-    iconChangeBirthdayGemini,
+    iconCancer,
+    iconCapricorn,
+    iconChangeCharacterEmpty,
+    iconChangeCharacterUnselect,
     iconChangeGenderFemaleLogo,
     iconChangeGenderFemaleSelect,
     iconChangeGenderFemaleUnselect,
     iconChangeGenderMaleLogo,
     iconChangeGenderMaleSelect,
     iconChangeGenderMaleUnselect,
+    iconChangeHobbiesUnselect,
     iconCharacterArrowDown,
     iconCharacterArrowRight,
     iconCharacterCustomButton,
     iconCharacterCustomClose,
     iconCharacterCustomDelete,
     iconCharacterCustomDetailEdit,
+    iconCharacterCustomDetailFemale,
     iconCharacterCustomDetailLock,
     iconCharacterCustomDetailMale,
     iconCharacterCustomDetailSwitch,
@@ -955,6 +1023,7 @@ class $AssetsImagesGen {
     iconEmojiLike,
     iconEmojiLove,
     iconEmojiPercent,
+    iconGemini,
     iconGoodsInfoTitle,
     iconIntimacyAnalyseArrow,
     iconIntimacyAnalyseLove,
@@ -989,6 +1058,8 @@ class $AssetsImagesGen {
     iconKeyboardTitle,
     iconKeyboardTriangle,
     iconKeyboardVipLogo,
+    iconLeo,
+    iconLibra,
     iconLock,
     iconMemberRetainClose,
     iconMineAbout,
@@ -1006,11 +1077,14 @@ class $AssetsImagesGen {
     iconMineVipDescArrow,
     iconMineVipOrderArrow,
     iconModeSwitchArrow,
+    iconPisces,
     iconProfileAdd,
     iconProfileEdit,
     iconProfileFemale,
     iconProfileMale,
     iconProfilePlus,
+    iconSagittarius,
+    iconScorpio,
     iconStoreAgreePrivacy,
     iconStoreBack,
     iconStoreBanner1,
@@ -1039,12 +1113,14 @@ class $AssetsImagesGen {
     iconTabKeyboardUnselect,
     iconTabMineSelected,
     iconTabMineUnselect,
+    iconTaurus,
     iconTicketDialogButton,
     iconUploadAddSymbol,
     iconUploadDelete,
     iconUploadFail,
     iconUploadScreenshotSampleImage,
     iconUploading,
+    iconVirgo,
     iconWechat,
     iconWechatPayment,
     iconWechatScanPayment,

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

@@ -200,6 +200,8 @@ class StringName {
   static final String intimacyAnalyseSampleImage = 'intimacy_analyse_sample_image'.tr; // 示例图
   static final String nextStep = 'next_step'.tr; // 下一步
   static final String recently = 'recently'.tr; // 最近
+  static final String addHobbies = 'add_hobbies'.tr; // 添加爱好
+  static final String save = 'save'.tr; // 保存
 }
 class StringMultiSource {
   StringMultiSource._();
@@ -403,6 +405,8 @@ class StringMultiSource {
       'intimacy_analyse_sample_image': '示例图',
       'next_step': '下一步',
       'recently': '最近',
+      'add_hobbies': '添加爱好',
+      'save': '保存',
     },
   };
 }

+ 12 - 0
lib/router/app_pages.dart

@@ -3,7 +3,9 @@ import 'package:keyboard/module/about/about_controller.dart';
 import 'package:keyboard/module/browser/browser_controller.dart';
 import 'package:keyboard/module/change/birthday/change_birthday_controller.dart';
 import 'package:keyboard/module/change/birthday/change_birthday_page.dart';
+import 'package:keyboard/module/change/character/change_character_controller.dart';
 import 'package:keyboard/module/change/gender/change_gender_controller.dart';
+import 'package:keyboard/module/change/hobbies/change_hobbies_controller.dart';
 import 'package:keyboard/module/change/nickname/change_nickname_controller.dart';
 import 'package:keyboard/module/character/content/character_group_content_controller.dart';
 import 'package:keyboard/module/character_custom/character_custom_controller.dart';
@@ -25,7 +27,9 @@ import 'package:keyboard/module/store/store_page.dart';
 import '../di/get_it.dart';
 import '../module/about/about_page.dart';
 import '../module/browser/browser_page.dart';
+import '../module/change/character/change_character_page.dart';
 import '../module/change/gender/change_gender_page.dart';
+import '../module/change/hobbies/change_hobbies_page.dart';
 import '../module/change/nickname/change_nickname_page.dart';
 import '../module/character/character_controller.dart';
 import '../module/character_custom/character_custom_page.dart';
@@ -80,6 +84,9 @@ abstract class RoutePath {
   static const changeNickname = '/changeNickname';
   static const changeGender = '/changeGender';
   static const changeBirthday = '/changeBirthday';
+  static const changeHobbies ='/changeHobbies';
+  static const changeCharacters = '/changeCharacters';
+
 }
 
 class AppBinding extends Bindings {
@@ -113,6 +120,9 @@ class AppBinding extends Bindings {
     lazyPut(() => getIt.get<ChangeBirthdayController>());
     lazyPut(() => getIt.get<ConversationAnalysisController>());
     lazyPut(() => getIt.get<ScanImageReplyController>());
+    lazyPut(()=>getIt.get<ChangeHobbiesController>());
+    lazyPut(() => getIt.get<ChangeCharacterController>());
+
   }
 
   void lazyPut<S>(InstanceBuilderCallback<S> builder) {
@@ -149,4 +159,6 @@ final generalPages = [
   GetPage(name: RoutePath.changeNickname, page: () => ChangeNicknamePage()),
   GetPage(name: RoutePath.changeGender, page: () => ChangeGenderPage()),
   GetPage(name: RoutePath.changeBirthday, page: () => ChangeBirthdayPage()),
+  GetPage(name: RoutePath.changeHobbies, page: () => ChangeHobbiesPage()),
+  GetPage(name: RoutePath.changeCharacters, page: () => ChangeCharacterPage()),
 ];

+ 86 - 18
lib/utils/age_zodiac_sign_util.dart

@@ -1,40 +1,65 @@
 import 'package:intl/intl.dart';
 
+import '../resource/assets.gen.dart';
+
 class AgeZodiacSignUtil {
   AgeZodiacSignUtil._();
 
+  static List<Zodiac> zodiacs = [
+    Zodiac(name: '白羊座', image: Assets.images.iconAries),
+    Zodiac(name: '金牛座', image: Assets.images.iconTaurus),
+    Zodiac(name: '双子座', image: Assets.images.iconGemini),
+    Zodiac(name: '巨蟹座', image: Assets.images.iconCancer),
+    Zodiac(name: '狮子座', image: Assets.images.iconLeo),
+    Zodiac(name: '处女座', image: Assets.images.iconVirgo),
+    Zodiac(name: '天秤座', image: Assets.images.iconLibra),
+    Zodiac(name: '天蝎座', image: Assets.images.iconScorpio),
+    Zodiac(name: '射手座', image: Assets.images.iconSagittarius),
+    Zodiac(name: '摩羯座', image: Assets.images.iconCapricorn),
+    Zodiac(name: '水瓶座', image: Assets.images.iconAquarius),
+    Zodiac(name: '双鱼座', image: Assets.images.iconPisces),
+  ];
+
   /// 计算星座(传入 DateTime)
-  static String getZodiacSign(DateTime date) {
+  /// 通过生日获取 Zodiac 对象(包含名称和图片)
+  static Zodiac getZodiacSign(DateTime date) {
     int month = date.month;
     int day = date.day;
 
+    String sign;
+
     if ((month == 3 && day >= 21) || (month == 4 && day <= 19)) {
-      return '白羊座';
+      sign = '白羊座';
     } else if ((month == 4 && day >= 20) || (month == 5 && day <= 20)) {
-      return '金牛座';
+      sign = '金牛座';
     } else if ((month == 5 && day >= 21) || (month == 6 && day <= 20)) {
-      return '双子座';
+      sign = '双子座';
     } else if ((month == 6 && day >= 21) || (month == 7 && day <= 22)) {
-      return '巨蟹座';
+      sign = '巨蟹座';
     } else if ((month == 7 && day >= 23) || (month == 8 && day <= 22)) {
-      return '狮子座';
+      sign = '狮子座';
     } else if ((month == 8 && day >= 23) || (month == 9 && day <= 22)) {
-      return '处女座';
+      sign = '处女座';
     } else if ((month == 9 && day >= 23) || (month == 10 && day <= 22)) {
-      return '天秤座';
+      sign = '天秤座';
     } else if ((month == 10 && day >= 23) || (month == 11 && day <= 21)) {
-      return '天蝎座';
+      sign = '天蝎座';
     } else if ((month == 11 && day >= 22) || (month == 12 && day <= 21)) {
-      return '射手座';
+      sign = '射手座';
     } else if ((month == 12 && day >= 22) || (month == 1 && day <= 19)) {
-      return '摩羯座';
+      sign = '摩羯座';
     } else if ((month == 1 && day >= 20) || (month == 2 && day <= 18)) {
-      return '水瓶座';
+      sign = '水瓶座';
     } else if ((month == 2 && day >= 19) || (month == 3 && day <= 20)) {
-      return '双鱼座';
+      sign = '双鱼座';
     } else {
-      return '未知星座';
+      return const Zodiac(name: '未知星座', image: AssetGenImage('')); // 空图片占位
     }
+
+    return zodiacs.firstWhere(
+      (zodiac) => zodiac.name == sign,
+      orElse: () => const Zodiac(name: '未知星座', image: AssetGenImage('')),
+    );
   }
 
   /// 计算年龄(传入 DateTime)
@@ -42,24 +67,31 @@ class AgeZodiacSignUtil {
     DateTime currentDate = DateTime.now();
     int age = currentDate.year - birthDate.year;
     if (currentDate.month < birthDate.month ||
-        (currentDate.month == birthDate.month && currentDate.day < birthDate.day)) {
+        (currentDate.month == birthDate.month &&
+            currentDate.day < birthDate.day)) {
       age--;
     }
     return age;
   }
 
   /// 从字符串计算星座
-  static String getZodiacSignFromString(String dateStr, {String format = 'yyyy-MM-dd'}) {
+  static Zodiac getZodiacSignFromString(
+    String dateStr, {
+    String format = 'yyyy-MM-dd',
+  }) {
     try {
       DateTime date = DateFormat(format).parse(dateStr);
       return getZodiacSign(date);
     } catch (e) {
-      return '无效日期';
+      return const Zodiac(name: '未知星座', image: AssetGenImage(''));
     }
   }
 
   /// 从字符串计算年龄
-  static int? calculateAgeFromString(String dateStr, {String format = 'yyyy-MM-dd'}) {
+  static int? calculateAgeFromString(
+    String dateStr, {
+    String format = 'yyyy-MM-dd',
+  }) {
     try {
       DateTime date = DateFormat(format).parse(dateStr);
       return calculateAge(date);
@@ -67,4 +99,40 @@ class AgeZodiacSignUtil {
       return null;
     }
   }
+
+  /// 格式化生日(传入字符串)
+  static String? formatBirthdayFromString(
+    String? dateStr, {
+    String outputFormat = 'yyyy-MM-dd',
+  }) {
+    if (dateStr == null || dateStr.isEmpty) return null;
+
+    final possibleFormats = [
+      'yyyy-MM-dd',
+      'yyyy/MM/dd',
+      'yyyy-MM-dd HH:mm:ss',
+      'yyyy/MM/dd HH:mm:ss',
+      "yyyy-MM-dd'T'HH:mm:ss'Z'",
+      "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
+      'yyyy-MM-dd HH:mm:sss',
+    ];
+
+    for (var format in possibleFormats) {
+      try {
+        DateTime date = DateFormat(format).parseStrict(dateStr);
+        return DateFormat(outputFormat).format(date);
+      } catch (e) {
+        // 忽略失败,尝试下一个格式
+      }
+    }
+
+    return null;
+  }
+}
+
+class Zodiac {
+  final String name;
+  final AssetGenImage image;
+
+  const Zodiac({required this.name, required this.image});
 }

+ 3 - 3
lib/utils/styles.dart

@@ -6,8 +6,8 @@ class Styles {
   static Decoration getActivateButtonDecoration(double radius) {
     return ShapeDecoration(
       gradient: LinearGradient(
-        begin: Alignment(0.04, 0.21),
-        end: Alignment(0.98, 0.76),
+        begin: Alignment.centerLeft,
+        end: Alignment.centerRight,
         colors: [const Color(0xFF7D46FC), const Color(0xFFBC87FF)],
       ),
 
@@ -19,7 +19,7 @@ class Styles {
 
   static Decoration getInactiveButtonDecoration(double radius) {
     return ShapeDecoration(
-      color: const Color(0xFF7D46FC).withAlpha(100),
+      color: const Color(0xFFCAB3FB),
       shape: RoundedRectangleBorder(
         borderRadius: BorderRadius.circular(radius),
       ),