Преглед изворни кода

Merge remote-tracking branch 'origin/v1.0.0' into v1.0.0

hezihao пре 7 месеци
родитељ
комит
015a3adb8a
36 измењених фајлова са 2107 додато и 643 уклоњено
  1. BIN
      assets/images/icon_change_birthday_gemini.webp
  2. BIN
      assets/images/icon_change_gender_female_logo.webp
  3. BIN
      assets/images/icon_change_gender_female_select.webp
  4. BIN
      assets/images/icon_change_gender_female_unselect.webp
  5. BIN
      assets/images/icon_change_gender_male_logo.webp
  6. BIN
      assets/images/icon_change_gender_male_select.webp
  7. BIN
      assets/images/icon_change_gender_male_unselect.webp
  8. BIN
      assets/images/icon_profile_plus.webp
  9. 7 0
      assets/string/base/string.xml
  10. 9 1
      lib/data/api/atmob_api.dart
  11. 34 0
      lib/data/api/atmob_api.g.dart
  12. 19 0
      lib/data/bean/default_avatar_info.dart
  13. 21 0
      lib/data/bean/default_avatar_info.g.dart
  14. 1 1
      lib/data/bean/keyboard_info.dart
  15. 7 2
      lib/data/repository/config_repository.dart
  16. 20 0
      lib/data/repository/keyboard_repository.dart
  17. 14 2
      lib/di/get_it.config.dart
  18. 60 0
      lib/module/change/birthday/change_birthday_controller.dart
  19. 157 0
      lib/module/change/birthday/change_birthday_page.dart
  20. 33 0
      lib/module/change/gender/change_gender_controller.dart
  21. 285 0
      lib/module/change/gender/change_gender_page.dart
  22. 39 0
      lib/module/change/nickname/change_nickname_controller.dart
  23. 164 0
      lib/module/change/nickname/change_nickname_page.dart
  24. 23 4
      lib/module/keyboard/keyboard_controller.dart
  25. 179 155
      lib/module/keyboard/keyboard_view.dart
  26. 101 30
      lib/module/profile/edit/profile_edit_controller.dart
  27. 178 143
      lib/module/profile/edit/profile_edit_page.dart
  28. 7 0
      lib/module/profile/profile_controller.dart
  29. 292 259
      lib/module/profile/profile_page.dart
  30. 43 0
      lib/resource/assets.gen.dart
  31. 14 0
      lib/resource/string.gen.dart
  32. 17 0
      lib/router/app_pages.dart
  33. 70 0
      lib/utils/age_zodiac_sign_util.dart
  34. 11 0
      lib/utils/styles.dart
  35. 188 0
      lib/widget/birthday_date_picker.dart
  36. 114 46
      lib/widget/pargress_bar.dart

BIN
assets/images/icon_change_birthday_gemini.webp


BIN
assets/images/icon_change_gender_female_logo.webp


BIN
assets/images/icon_change_gender_female_select.webp


BIN
assets/images/icon_change_gender_female_unselect.webp


BIN
assets/images/icon_change_gender_male_logo.webp


BIN
assets/images/icon_change_gender_male_select.webp


BIN
assets/images/icon_change_gender_male_unselect.webp


BIN
assets/images/icon_profile_plus.webp


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

@@ -200,6 +200,9 @@
 
     <string name="profile_save">完成</string>
     <string name="profile_edit_save">保存</string>
+    <string name="profile_list">档案列表</string>
+    <string name="profile_add">添加</string>
+    <string name="profile_edit">编辑</string>
 
 
     <string name="keyboard_member_open">开通会员</string>
@@ -208,6 +211,10 @@
     <string name="keyboard_go_to_manage">去管理</string>
     <string name="keyboard_add">添加</string>
     <string name="keyboard_no_login">自己</string>
+    <string name="keyboard_rapport">默契</string>
+    <string name="keyboard_passion">激情</string>
+    <string name="keyboard_fetter">羁绊</string>
+    <string name="keyboard_promise">承诺</string>
 
     <!-- 键盘引导页 -->
     <string name="keyboard_guide_go_wechat">去微信体验</string>

+ 9 - 1
lib/data/api/atmob_api.dart

@@ -38,8 +38,10 @@ 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_home_info_response.dart' show KeyboardHomeInfoResponse;
+import 'package:keyboard/data/api/response/keyboard_home_info_response.dart'
+    show KeyboardHomeInfoResponse;
 import 'package:keyboard/data/api/response/keyboard_list_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/data/api/response/login_response.dart';
 import 'package:keyboard/data/api/response/new_user_get_character_response.dart';
@@ -158,6 +160,12 @@ abstract class AtmobApi {
     @Body() AppBaseRequest request,
   );
 
+  // 获取当天恋爱指数
+  @POST("/project/keyboard/v1/keyboard/love/index")
+  Future<BaseResponse<KeyboardLoveIndexResponse>> getKeyboardLoveIndex(
+    @Body() AppBaseRequest request,
+  );
+
   // 获取键盘人设列表
   @POST("/project/keyboard/v1/character/list")
   Future<BaseResponse<KeyboardCharacterListResponse>> getKeyboardCharacterList(

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

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

+ 19 - 0
lib/data/bean/default_avatar_info.dart

@@ -0,0 +1,19 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'default_avatar_info.g.dart';
+
+@JsonSerializable()
+class DefaultAvatarInfo {
+  @JsonKey(name: 'maleAvatars')
+  List<String>? maleAvatars;
+
+  @JsonKey(name: 'femaleAvatars')
+  List<String>? femaleAvatars;
+
+  DefaultAvatarInfo(this.maleAvatars, this.femaleAvatars);
+
+  factory DefaultAvatarInfo.fromJson(Map<String, dynamic> json) =>
+      _$DefaultAvatarInfoFromJson(json);
+
+  Map<String, dynamic> toJson() => _$DefaultAvatarInfoToJson(this);
+}

+ 21 - 0
lib/data/bean/default_avatar_info.g.dart

@@ -0,0 +1,21 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'default_avatar_info.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+DefaultAvatarInfo _$DefaultAvatarInfoFromJson(Map<String, dynamic> json) =>
+    DefaultAvatarInfo(
+      (json['maleAvatars'] as List<dynamic>?)?.map((e) => e as String).toList(),
+      (json['femaleAvatars'] as List<dynamic>?)
+          ?.map((e) => e as String)
+          .toList(),
+    );
+
+Map<String, dynamic> _$DefaultAvatarInfoToJson(DefaultAvatarInfo instance) =>
+    <String, dynamic>{
+      'maleAvatars': instance.maleAvatars,
+      'femaleAvatars': instance.femaleAvatars,
+    };

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

@@ -1,5 +1,5 @@
 import 'package:json_annotation/json_annotation.dart';
-
+import 'package:get/get.dart';
 part 'keyboard_info.g.dart';
 
 @JsonSerializable()

+ 7 - 2
lib/data/repository/config_repository.dart

@@ -12,6 +12,7 @@ import '../api/response/config_response.dart';
 import 'package:get/get.dart';
 
 import '../bean/custom_config_info.dart';
+import '../bean/default_avatar_info.dart';
 
 @lazySingleton
 class ConfigRepository {
@@ -23,6 +24,8 @@ class ConfigRepository {
 
   CustomConfigInfo? get characterCustomConfig => _characterCustomConfig.value;
 
+  final Rxn<DefaultAvatarInfo> defaultAvatarInfo = Rxn<DefaultAvatarInfo>();
+
 
 
   ConfigRepository(this.atmobApi) {
@@ -47,11 +50,13 @@ class ConfigRepository {
           if (config.confCode == 'intimacy') {
             AtmobLog.d(tag, '获取亲密度配置: ${config.value}');
           } else if (config.confCode == 'default_avatar') {
-            AtmobLog.d(tag, '获取默认头像配置: ${config.value}');
+
+            defaultAvatarInfo.value = DefaultAvatarInfo.fromJson(config.value);
+            AtmobLog.d(
+                tag, '获取默认头像配置: ${defaultAvatarInfo.value?.toJson()}');
           }
         }
       }
-
     });
   }
 

+ 20 - 0
lib/data/repository/keyboard_repository.dart

@@ -3,6 +3,7 @@ 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_list_request.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';
 import 'dart:convert';
@@ -26,6 +27,12 @@ class KeyboardRepository {
 
   RxList<KeyboardInfo> get keyboardInfoList => _keyboardInfoList;
 
+  final Rxn<KeyboardLoveIndexResponse> _homeLoveIndex =
+      Rxn<KeyboardLoveIndexResponse>();
+
+  Rxn<KeyboardLoveIndexResponse> get homeLoveIndex =>
+      _homeLoveIndex;
+
   final Rxn<KeyboardHomeInfoResponse> _homeInfo =
       Rxn<KeyboardHomeInfoResponse>();
 
@@ -42,11 +49,13 @@ class KeyboardRepository {
     await Future.delayed(const Duration(milliseconds: 500));
     // 延迟为了保证首页数据能够正常获取,不然保存的时候,获取太快了,导致还是拉到旧的数值
     getKeyboardHomeInfo();
+    getKeyboardLoveIndex();
   }
 
   Future cleanData() async {
     _keyboardInfoList.clear();
     getKeyboardHomeInfo();
+    getKeyboardLoveIndex();
   }
 
   Future refreshKeyboardList() async {
@@ -137,5 +146,16 @@ class KeyboardRepository {
         });
   }
 
+  // 获取恋爱指数
+  Future<KeyboardLoveIndexResponse> getKeyboardLoveIndex() {
+    return atmobApi
+        .getKeyboardLoveIndex(AppBaseRequest())
+        .then(HttpHandler.handle(true))
+        .then((response) {
+          _homeLoveIndex.value = response;
+          return response;
+        });
+  }
+
   static KeyboardRepository getInstance() => getIt.get<KeyboardRepository>();
 }

+ 14 - 2
lib/di/get_it.config.dart

@@ -29,6 +29,9 @@ import '../dialog/custom_character/custom_character_add_controller.dart'
     as _i122;
 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/gender/change_gender_controller.dart' as _i315;
+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'
     as _i970;
@@ -76,6 +79,9 @@ extension GetItInjectableX on _i174.GetIt {
     gh.factory<_i977.IntimacyAnalyseController>(
       () => _i977.IntimacyAnalyseController(),
     );
+    gh.factory<_i666.IntimacyAnalyseUploadController>(
+      () => _i666.IntimacyAnalyseUploadController(),
+    );
     gh.factory<_i279.IntimacyAnalyseScreenshotReplyController>(
       () => _i279.IntimacyAnalyseScreenshotReplyController(),
     );
@@ -88,8 +94,14 @@ extension GetItInjectableX on _i174.GetIt {
     gh.factory<_i415.KeyboardMethodHandler>(
       () => _i415.KeyboardMethodHandler(),
     );
-    gh.factory<_i666.IntimacyAnalyseUploadController>(
-      () => _i666.IntimacyAnalyseUploadController(),
+    gh.factory<_i1057.ChangeBirthdayController>(
+      () => _i1057.ChangeBirthdayController(),
+    );
+    gh.factory<_i315.ChangeGenderController>(
+      () => _i315.ChangeGenderController(),
+    );
+    gh.factory<_i859.ChangeNicknameController>(
+      () => _i859.ChangeNicknameController(),
     );
     gh.singleton<_i361.Dio>(
       () => networkModule.createStreamDio(),

+ 60 - 0
lib/module/change/birthday/change_birthday_controller.dart

@@ -0,0 +1,60 @@
+import 'package:injectable/injectable.dart';
+import 'package:keyboard/base/base_controller.dart';
+import 'package:get/get.dart';
+import 'package:keyboard/utils/age_zodiac_sign_util.dart';
+import 'package:intl/intl.dart';
+import 'package:keyboard/utils/toast_util.dart';
+
+@injectable
+class ChangeBirthdayController extends BaseController {
+  final tag = "ChangeBirthdayController";
+
+  late DateTime initialDate;
+  // 最小日期
+  var minimumDate = DateTime.parse("1921-01-01");
+  late String currentDate;
+
+  //星座
+  var constellation = "".obs;
+  var age = 0.obs;
+
+  ChangeBirthdayController();
+
+  @override
+  void onInit() {
+    super.onInit();
+    final String? birthday = Get.arguments?["birthday"];
+    if (birthday != null) {
+      initialDate = DateTime.parse(birthday);
+      currentDate = birthday;
+      constellation.value = AgeZodiacSignUtil.getZodiacSign(initialDate);
+      age.value = AgeZodiacSignUtil.calculateAge(initialDate);
+    } else {
+      initialDate = DateTime.now();
+      currentDate = DateFormat('yyyy-MM-dd').format(initialDate);
+      constellation.value = AgeZodiacSignUtil.getZodiacSign(initialDate);
+      age.value = AgeZodiacSignUtil.calculateAge(initialDate);
+    }
+  }
+
+  updateConstellation(DateTime date) {
+    constellation.value = AgeZodiacSignUtil.getZodiacSign(date);
+    age.value = AgeZodiacSignUtil.calculateAge(date);
+    print("星座:${constellation.value}");
+    print("年龄:${age.value}");
+    print("日期:${date}");
+    currentDate = DateFormat('yyyy-MM-dd').format(date);
+  }
+
+  void clickBack() {
+    Get.back();
+  }
+
+  void clickSave() {
+    if (currentDate.isEmpty) {
+      ToastUtil.show("请选择日期");
+      return;
+    }
+    Get.back(result: currentDate);
+  }
+}

+ 157 - 0
lib/module/change/birthday/change_birthday_page.dart

@@ -0,0 +1,157 @@
+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/module/change/birthday/change_birthday_controller.dart';
+import 'package:keyboard/utils/styles.dart';
+import 'package:keyboard/widget/birthday_date_picker.dart';
+
+import '../../../resource/assets.gen.dart';
+import '../../../router/app_pages.dart';
+
+class ChangeBirthdayPage extends BasePage<ChangeBirthdayController> {
+  const ChangeBirthdayPage({super.key});
+
+  static Future start({String? birthday}) async {
+    return Get.toNamed(RoutePath.changeBirthday, arguments: {"birthday": birthday});
+  }
+
+  @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: [
+              SingleChildScrollView(
+                child: Column(
+                  children: [
+                    _buildTitle(),
+                    SizedBox(height: 40.h),
+                    _buildContent(context),
+                  ],
+                ),
+              ),
+              Align(
+                alignment: Alignment.bottomCenter,
+                child: Container(
+                  margin: EdgeInsets.only(bottom: 32.h),
+                  child: Obx(() {
+                    return _buildSaveButton(
+                      onTap: () {
+                        controller.clickSave();
+                      },
+                      isEnable: controller.constellation.value.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,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  _buildContent(BuildContext context) {
+    return Column(
+      children: [
+        Text('选择生日', style: Styles.getTextStyleBlack204W500(22.sp)),
+        Container(
+          margin: EdgeInsets.only(top: 119.h),
+          alignment: Alignment.center,
+          child: Container(child: _datePicker(context)),
+        ),
+        SizedBox(height: 18.h),
+        Obx(() {
+          return Row(
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: [
+              Assets.images.iconChangeBirthdayGemini.image(
+                width: 26.w,
+                height: 26.w,
+              ),
+              Text(
+                controller.constellation.value,
+                style: Styles.getTextStyleBlack204W500(14.sp),
+              ),
+            ],
+          );
+        }),
+      ],
+    );
+  }
+
+  SizedBox _datePicker(BuildContext context) {
+    var widget = SizedBox(
+      height: 150.h,
+      width: 320.w,
+      // child: DatePickerWidget(),
+      child: BirthdayDatePicker(
+        initialDate: controller.initialDate,
+        minimumDate: controller.minimumDate,
+        maximumDate: DateTime.now(),
+        onDateChanged: (date) {
+          controller.updateConstellation(date);
+        },
+      ),
+    );
+    return widget;
+  }
+
+  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,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}

+ 33 - 0
lib/module/change/gender/change_gender_controller.dart

@@ -0,0 +1,33 @@
+import 'package:injectable/injectable.dart';
+import 'package:keyboard/base/base_controller.dart';
+import 'package:get/get.dart';
+
+import '../../../utils/toast_util.dart';
+
+@injectable
+class ChangeGenderController extends BaseController {
+  final tag = "ChangeGenderController";
+
+  final RxnInt currentGender = RxnInt(null);
+
+  ChangeGenderController();
+
+  @override
+  void onInit() {
+    // TODO: implement onInit
+    super.onInit();
+    currentGender.value = Get.arguments?["gender"];
+  }
+
+  void clickBack() {
+    Get.back();
+  }
+
+  void clickSave() {
+    if (currentGender.value == null) {
+      ToastUtil.show("请选择性别");
+      return;
+    }
+    Get.back(result: currentGender.value);
+  }
+}

+ 285 - 0
lib/module/change/gender/change_gender_page.dart

@@ -0,0 +1,285 @@
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:keyboard/base/base_page.dart';
+import 'package:get/get.dart';
+import 'package:keyboard/module/change/gender/change_gender_controller.dart';
+import 'package:flutter/material.dart';
+import '../../../resource/assets.gen.dart';
+import '../../../router/app_pages.dart';
+import '../../../utils/styles.dart';
+
+class ChangeGenderPage extends BasePage<ChangeGenderController> {
+  const ChangeGenderPage({super.key});
+
+  static Future start({int? gender}) async {
+    return Get.toNamed(RoutePath.changeGender, arguments: {"gender": gender});
+  }
+
+  @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: [
+              SingleChildScrollView(
+                child: Column(
+                  children: [
+                    _buildTitle(),
+                    SizedBox(height: 40.h),
+                    _buildContent(),
+                  ],
+                ),
+              ),
+              Align(
+                alignment: Alignment.bottomCenter,
+                child: Container(
+                  margin: EdgeInsets.only(bottom: 32.h),
+                  child: Obx(() {
+                    return _buildSaveButton(
+                      onTap: () {
+                        controller.clickSave();
+                      },
+                      isEnable: controller.currentGender.value != null,
+                    );
+                  }),
+                ),
+              ),
+            ],
+          ),
+        ),
+      ],
+    );
+  }
+
+  _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,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+
+  _buildContent() {
+    return Column(
+      children: [
+        Text('选择性别', style: Styles.getTextStyleBlack204W500(22.sp)),
+        SizedBox(height: 119.h),
+        _buildFemale(),
+        SizedBox(height: 25.h),
+        _buildMale(),
+      ],
+    );
+  }
+
+  _buildFemale() {
+    return GestureDetector(
+      onTap: () {
+        if (controller.currentGender.value == 2) {
+          controller.currentGender.value = 1;
+        } else {
+          controller.currentGender.value = 2;
+        }
+      },
+      child: Obx(() {
+        return SizedBox(
+          width: 320.w,
+          height: 115.h,
+          child: Stack(
+            clipBehavior: Clip.none,
+            children: [
+              controller.currentGender.value == 2
+                  ? Assets.images.iconChangeGenderFemaleSelect.image(
+                    width: 320.w,
+                    fit: BoxFit.fill,
+                  )
+                  : Assets.images.iconChangeGenderFemaleUnselect.image(
+                    width: 320.w,
+                    fit: BoxFit.cover,
+                  ),
+              Container(
+                alignment: Alignment.centerRight,
+                padding: EdgeInsets.only(right: 40.w),
+                child: Column(
+                  mainAxisAlignment: MainAxisAlignment.center,
+                  children: [
+                    Row(
+                      mainAxisSize: MainAxisSize.min,
+                      children: [
+                        Assets.images.iconChangeGenderFemaleLogo.image(
+                          width: 20.w,
+                          height: 20.w,
+                        ),
+                        SizedBox(width: 4.w),
+                        Text(
+                          '女生',
+                          textAlign: TextAlign.center,
+                          style: TextStyle(
+                            color:
+                                controller.currentGender.value == 2
+                                    ? Colors.black.withAlpha(204)
+                                    : Colors.black
+                                        .withAlpha(204)
+                                        .withValues(alpha: 0.5),
+                            fontSize: 22.sp,
+                            fontWeight: FontWeight.w700,
+                          ),
+                        ),
+                      ],
+                    ),
+                    SizedBox(height: 4.w),
+                    Row(
+                      mainAxisSize: MainAxisSize.min,
+                      children: [
+                        SizedBox(width: 20.w, height: 20.w),
+                        SizedBox(width: 4.w),
+                        Text(
+                          'Girl',
+                          textAlign: TextAlign.center,
+                          style: TextStyle(
+                            color: Colors.black.withAlpha(128),
+                            fontSize: 14.sp,
+                            fontWeight: FontWeight.w400,
+                          ),
+                        ),
+                      ],
+                    ),
+                  ],
+                ),
+              ),
+            ],
+          ),
+        );
+      }),
+    );
+  }
+
+  _buildMale() {
+    return GestureDetector(
+      onTap: () {
+        if (controller.currentGender.value == 1) {
+          controller.currentGender.value = 2;
+        } else {
+          controller.currentGender.value = 1;
+        }
+      },
+      child: Obx(() {
+        return SizedBox(
+          width: 320.w,
+          height: 115.h,
+          child: Stack(
+            clipBehavior: Clip.none,
+            children: [
+              controller.currentGender.value == 1
+                  ? Assets.images.iconChangeGenderMaleSelect.image(
+                    width: 320.w,
+                    fit: BoxFit.fill,
+                  )
+                  : Assets.images.iconChangeGenderMaleUnselect.image(
+                    width: 320.w,
+                    fit: BoxFit.cover,
+                  ),
+              Container(
+                alignment: Alignment.centerLeft,
+                padding: EdgeInsets.only(left: 40.w),
+                child: Column(
+                  mainAxisAlignment: MainAxisAlignment.center,
+                  children: [
+                    Row(
+                      mainAxisSize: MainAxisSize.min,
+                      children: [
+                        Text(
+                          '男生',
+                          textAlign: TextAlign.center,
+                          style: TextStyle(
+                            color:
+                                controller.currentGender.value == 1
+                                    ? Colors.black.withAlpha(204)
+                                    : Colors.black
+                                        .withAlpha(204)
+                                        .withValues(alpha: 0.5),
+                            fontSize: 22.sp,
+                            fontWeight: FontWeight.w700,
+                          ),
+                        ),
+                        SizedBox(width: 4.w),
+                        Assets.images.iconChangeGenderMaleLogo.image(
+                          width: 20.w,
+                          height: 20.w,
+                        ),
+                      ],
+                    ),
+                    SizedBox(height: 4.w),
+                    Row(
+                      mainAxisSize: MainAxisSize.min,
+                      children: [
+                        Text(
+                          'Boy',
+                          textAlign: TextAlign.center,
+                          style: TextStyle(
+                            color: Colors.black.withAlpha(128),
+                            fontSize: 14.sp,
+                            fontWeight: FontWeight.w400,
+                          ),
+                        ),
+                        SizedBox(width: 4.w),
+                        SizedBox(width: 20.w, height: 20.w),
+                      ],
+                    ),
+                  ],
+                ),
+              ),
+            ],
+          ),
+        );
+      }),
+    );
+  }
+}

+ 39 - 0
lib/module/change/nickname/change_nickname_controller.dart

@@ -0,0 +1,39 @@
+import 'package:flutter/cupertino.dart';
+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 'package:keyboard/utils/toast_util.dart';
+
+@injectable
+class ChangeNicknameController extends BaseController {
+  final tag = "ChangeNicknameController";
+
+  final RxString nickname = "".obs;
+
+  ChangeNicknameController();
+
+  TextEditingController textEditingController = TextEditingController();
+
+  @override
+  onInit() {
+    super.onInit();
+    final String? nickName = Get.arguments?["nickName"];
+    if (nickName != null) {
+      nickname.value = nickName;
+      textEditingController.text = nickName;
+    }
+  }
+
+  clickBack() {
+    Get.back();
+  }
+
+  clickSave() {
+    if (nickname.value.isEmpty) {
+      ToastUtil.show("请输入昵称");
+      return;
+    }
+    Get.back(result: nickname.value);
+  }
+}

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

@@ -0,0 +1,164 @@
+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/module/change/nickname/change_nickname_controller.dart';
+import 'package:keyboard/utils/styles.dart';
+
+import '../../../resource/assets.gen.dart';
+import '../../../router/app_pages.dart';
+
+class ChangeNicknamePage extends BasePage<ChangeNicknameController> {
+  const ChangeNicknamePage({super.key});
+
+  static Future start({String? nickName}) async {
+    return Get.toNamed(
+      RoutePath.changeNickname,
+      arguments: {"nickName": nickName},
+    );
+  }
+
+  @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: [
+              SingleChildScrollView(
+                child: Column(
+                  children: [
+                    _buildTitle(),
+                    SizedBox(height: 40.h),
+                    _buildContent(),
+                  ],
+                ),
+              ),
+              Align(
+                alignment: Alignment.bottomCenter,
+                child: Container(
+                  margin: EdgeInsets.only(bottom: 32.h),
+                  child: Obx(() {
+                    return _buildSaveButton(
+                      onTap: () {
+                        controller.clickSave();
+                      },
+                      isEnable: controller.nickname.value.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,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  _buildContent() {
+    return Column(
+      children: [
+        Text('修改昵称', style: Styles.getTextStyleBlack204W500(22.sp)),
+        Container(
+          margin: EdgeInsets.only(top: 119.h, left: 27.w, right: 27.w),
+          alignment: Alignment.center,
+          child: Container(
+            height: 48.h,
+            alignment: Alignment.center,
+            child: TextField(
+              controller: controller.textEditingController,
+              // maxLength: maxLength,
+              maxLines: 1,
+
+              onChanged: (value) {
+                controller.nickname.value = value;
+              },
+              cursorColor: Color(0xFF7B7DFF),
+              textAlign: TextAlign.center,
+              textAlignVertical: TextAlignVertical.center,
+              decoration: InputDecoration(
+                counterText: "",
+                hintText: "请输入你的昵称",
+                hintStyle: TextStyle(
+                  color: Colors.black.withAlpha(77),
+                  fontSize: 18.sp,
+                  fontWeight: FontWeight.w500,
+                ),
+                border: UnderlineInputBorder(
+                  borderSide: BorderSide(
+                    color: Colors.black.withAlpha(26),
+                    width: 1.w,
+                  ),
+                ),
+                focusedBorder: UnderlineInputBorder(
+                  borderSide: BorderSide(
+                    color: Colors.black.withAlpha(26),
+                    width: 1.w,
+                  ),
+                ),
+                filled: true,
+                fillColor: Colors.transparent,
+              ),
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+
+  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,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}

+ 23 - 4
lib/module/keyboard/keyboard_controller.dart

@@ -9,6 +9,10 @@ import 'package:keyboard/data/repository/keyboard_repository.dart';
 import 'package:keyboard/module/keyboard_manage/keyboard_manage_page.dart';
 import 'package:keyboard/module/store/store_page.dart';
 
+import '../../data/api/response/keyboard_love_index_response.dart';
+import '../../utils/atmob_log.dart';
+import '../profile/profile_page.dart';
+
 @injectable
 class KeyBoardController extends BaseController {
   final tag = "KeyBoardController";
@@ -16,21 +20,23 @@ class KeyBoardController extends BaseController {
   final KeyboardRepository keyboardRepository;
 
   KeyboardHomeInfoResponse? get homeInfo => keyboardRepository.homeInfo.value;
+
+  Rxn<KeyboardLoveIndexResponse> get loveIndex =>
+      keyboardRepository.homeLoveIndex;
   static const int countdownTime = 10 * 60 * 100;
   final RxInt timeLeft = countdownTime.obs;
   Timer? _timer;
 
+  final RxBool isShowBanner = true.obs;
+
   // 各项指标数据
-  final moodPercentage = 20.obs;
-  final appearancePercentage = 50.obs;
-  final wealthPercentage = 30.obs;
-  final cleanlinessPercentage = 50.obs;
 
   KeyBoardController(this.keyboardRepository);
 
   @override
   void onInit() {
     super.onInit();
+
     startCountdown();
   }
 
@@ -54,6 +60,19 @@ class KeyBoardController extends BaseController {
     KeyboardManagePage.start();
   }
 
+  void clickCloseBanner() {
+    isShowBanner.value = false;
+
+    debugPrint("click close banner");
+  }
+
+  void clickAvatar(bool isUser) {
+    debugPrint("click avatar");
+    if (!isUser) {
+      ProfilePage.start();
+    }
+  }
+
   void startCountdown() {
     _timer = Timer.periodic(const Duration(milliseconds: 10), (timer) {
       if (timeLeft.value > 0) {

+ 179 - 155
lib/module/keyboard/keyboard_view.dart

@@ -9,6 +9,7 @@ import 'package:keyboard/resource/string.gen.dart';
 
 import '../../resource/assets.gen.dart';
 import '../../utils/styles.dart';
+import '../../widget/avatar/avatar_image_widget.dart';
 import '../../widget/pargress_bar.dart';
 import 'keyboard_controller.dart';
 
@@ -26,7 +27,6 @@ class KeyBoardView extends BaseView<KeyBoardController> {
               _buildTitle(),
               Expanded(
                 child: SingleChildScrollView(
-                  physics: NeverScrollableScrollPhysics(),
                   child: Column(
                     children: [
                       _buildAvatarCard(),
@@ -61,7 +61,6 @@ class KeyBoardView extends BaseView<KeyBoardController> {
       ],
     );
   }
-
   // 顶部标题栏
   Widget _buildTitle() {
     return Container(
@@ -127,11 +126,11 @@ class KeyBoardView extends BaseView<KeyBoardController> {
             Row(
               mainAxisAlignment: MainAxisAlignment.spaceBetween,
               children: [
-                _buildUserAvatar(true),
+                _buildAvatar(true),
                 const SizedBox(width: 16),
                 _buildLovePercentage(),
                 const SizedBox(width: 16),
-                _buildUserAvatar(false),
+                _buildAvatar(false),
               ],
             ),
           ],
@@ -170,55 +169,59 @@ class KeyBoardView extends BaseView<KeyBoardController> {
               ),
               SizedBox(width: 10.w),
               Expanded(
-                child: Container(
-                  padding: EdgeInsets.symmetric(
-                    horizontal: 10.w,
-                    vertical: 8.h,
-                  ),
-                  decoration: BoxDecoration(
-                    color: Color(0xFFFAFAFC),
-                    borderRadius: BorderRadius.circular(12.r),
-                  ),
-                  child: Row(
-                    children: [
-                      Expanded(
-                        child: Column(
-                          children: [
-                            ProgressBar(
-                              title: "激情",
-                              value: controller.appearancePercentage,
-                              color: Color(0xFFFF637D),
-                            ),
-                            SizedBox(height: 6.h),
-                            ProgressBar(
-                              title: "默契",
-                              value: controller.appearancePercentage,
-                              color: Color(0xFFF5E8FC),
-                            ),
-                          ],
+                child: Obx(() {
+                  return Container(
+                    padding: EdgeInsets.symmetric(
+                      horizontal: 10.w,
+                      vertical: 8.h,
+                    ),
+                    decoration: BoxDecoration(
+                      color: Color(0xFFFAFAFC),
+                      borderRadius: BorderRadius.circular(12.r),
+                    ),
+                    child: Row(
+                      children: [
+                        Expanded(
+                          child: Column(
+                            children: [
+                              ProgressBar(
+                                title: StringName.keyboardPassion,
+                                value: controller.loveIndex.value?.passion,
+                                color: Color(0xFFFF637D),
+                              ),
+                              SizedBox(height: 6.h),
+                              ProgressBar(
+                                title: StringName.keyboardRapport,
+                                value: controller.loveIndex.value?.rapport,
+                                color: Color(0xFFF5E8FC),
+                              ),
+                            ],
+                          ),
                         ),
-                      ),
-                      SizedBox(width: 21.w),
-                      Expanded(
-                        child: Column(
-                          children: [
-                            ProgressBar(
-                              title: "羁绊",
-                              value: controller.appearancePercentage,
-                              color: Color(0xFFFFC954),
-                            ),
-                            SizedBox(height: 6.h),
-                            ProgressBar(
-                              title: "承诺",
-                              value: controller.appearancePercentage,
-                              color: Color(0xFF6382FF),
-                            ),
-                          ],
+                        SizedBox(width: 21.w),
+                        Expanded(
+                          child: Column(
+                            children: [
+                              Obx(() {
+                                return ProgressBar(
+                                  title: StringName.keyboardFetter,
+                                  value: controller.loveIndex.value?.fetter,
+                                  color: Color(0xFFFFC954),
+                                );
+                              }),
+                              SizedBox(height: 6.h),
+                              ProgressBar(
+                                title: StringName.keyboardPromise,
+                                value: controller.loveIndex.value?.promise,
+                                color: Color(0xFF6382FF),
+                              ),
+                            ],
+                          ),
                         ),
-                      ),
-                    ],
-                  ),
-                ),
+                      ],
+                    ),
+                  );
+                }),
               ),
             ],
           ),
@@ -228,79 +231,75 @@ class KeyBoardView extends BaseView<KeyBoardController> {
   }
 
   // 用户头像
-  Widget _buildUserAvatar(bool isUser) {
-    return Column(
-      children: [
-        Stack(
-          alignment: Alignment.bottomCenter,
-          children: [
-            Column(
-              children: [
-                Container(
-                  width: 98.r,
-                  height: 98.r,
-                  decoration: ShapeDecoration(
-                    shape: RoundedRectangleBorder(
-                      borderRadius: BorderRadius.circular(50.r),
+  Widget _buildAvatar(bool isUser) {
+    return GestureDetector(
+      onTap: () {
+        controller.clickAvatar(isUser);
+      },
+      child: Column(
+        children: [
+          Stack(
+            alignment: Alignment.bottomCenter,
+            children: [
+              Column(
+                children: [
+                  Container(
+                    width: 98.r,
+                    height: 98.r,
+                    decoration: ShapeDecoration(
+                      shape: RoundedRectangleBorder(
+                        borderRadius: BorderRadius.circular(50.r),
+                      ),
+                    ),
+                    child: CircleAvatarWidget(
+                      imageUrl:
+                          isUser
+                              ? controller.homeInfo?.imageUrl
+                              : controller.homeInfo?.targetImageUrl,
+                      placeholderImage:
+                          Assets.images.iconKeyboardDefaultAvatar.provider(),
+                      borderColor: Colors.white,
+                      borderWidth: 2.r,
+                      placeholder: (_, __) {
+                        return const CupertinoActivityIndicator();
+                      },
                     ),
                   ),
-                  child: CachedNetworkImage(
-                    imageUrl:
-                        isUser
-                            ? controller.homeInfo?.imageUrl ?? ""
-                            : controller.homeInfo?.targetImageUrl ?? "",
-                    placeholder: (_, __) => const CupertinoActivityIndicator(),
-                    errorWidget:
-                        (context, url, error) => CircleAvatar(
-                          backgroundImage:
-                              Assets.images.iconKeyboardDefaultAvatar
-                                  .provider(),
-                        ),
-                    imageBuilder:
-                        (context, imageProvider) => Container(
-                          decoration: ShapeDecoration(
-                            shape: RoundedRectangleBorder(
-                              side: BorderSide(width: 2.r, color: Colors.white),
-                              borderRadius: BorderRadius.circular(50.r),
-                            ),
-                          ),
-                          child: CircleAvatar(backgroundImage: imageProvider),
-                        ),
-                  ),
+                  SizedBox(height: 10.h),
+                ],
+              ),
+              Container(
+                padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 4.w),
+                decoration: BoxDecoration(
+                  color: Colors.white,
+                  borderRadius: BorderRadius.circular(22.r),
                 ),
-                SizedBox(height: 10.h),
-              ],
-            ),
-            Container(
-              padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 4.w),
-              decoration: BoxDecoration(
-                color: Colors.white,
-                borderRadius: BorderRadius.circular(22.r),
+                child:
+                    isUser
+                        ? Text(
+                          controller.homeInfo?.name ??
+                              StringName.keyboardNoLogin,
+                          style: Styles.getTextStyleBlack204W400(14.sp),
+                        )
+                        : Text(
+                          controller.homeInfo?.targetName ??
+                              StringName.keyboardAdd,
+                          style:
+                              controller.homeInfo?.targetName != null
+                                  ? Styles.getTextStyleBlack204W400(14.sp)
+                                  : TextStyle(
+                                    color: const Color(0xFF8651FF),
+                                    fontSize: 14.sp,
+                                    fontWeight: FontWeight.w400,
+                                  ),
+                        ),
               ),
-              child:
-                  isUser
-                      ? Text(
-                        controller.homeInfo?.name ?? StringName.keyboardNoLogin,
-                        style: Styles.getTextStyleBlack204W400(14.sp),
-                      )
-                      : Text(
-                        controller.homeInfo?.targetName ??
-                            StringName.keyboardAdd,
-                        style:
-                            controller.homeInfo?.targetName != null
-                                ? Styles.getTextStyleBlack204W400(14.sp)
-                                : TextStyle(
-                                  color: const Color(0xFF8651FF),
-                                  fontSize: 14.sp,
-                                  fontWeight: FontWeight.w500,
-                                ),
-                      ),
-            ),
-          ],
-        ),
-        const SizedBox(height: 4),
-        // Text(name, style: const TextStyle(fontSize: 12)),
-      ],
+            ],
+          ),
+          const SizedBox(height: 4),
+          // Text(name, style: const TextStyle(fontSize: 12)),
+        ],
+      ),
     );
   }
 
@@ -545,7 +544,6 @@ class KeyBoardView extends BaseView<KeyBoardController> {
   // 当前键盘人设信息
   Widget _buildKeyboardSettings() {
     return GestureDetector(
-      onTap: controller.clickGoKeyboardManage,
       child: Container(
         margin: EdgeInsets.symmetric(horizontal: 16.w),
         padding: EdgeInsets.only(
@@ -577,21 +575,24 @@ class KeyBoardView extends BaseView<KeyBoardController> {
                   height: 20.h,
                   fit: BoxFit.cover,
                 ),
-                Row(
-                  children: [
-                    Text(
-                      StringName.keyboardGoToManage,
-                      style: TextStyle(
-                        color: Colors.black.withAlpha(102),
-                        fontSize: 12.sp,
-                        fontWeight: FontWeight.w500,
+                GestureDetector(
+                  onTap: controller.clickGoKeyboardManage,
+                  child: Row(
+                    children: [
+                      Text(
+                        StringName.keyboardGoToManage,
+                        style: TextStyle(
+                          color: Colors.black.withAlpha(102),
+                          fontSize: 12.sp,
+                          fontWeight: FontWeight.w500,
+                        ),
                       ),
-                    ),
-                    Assets.images.iconKeyboardCurrentGo.image(
-                      width: 7.w,
-                      height: 7.w,
-                    ),
-                  ],
+                      Assets.images.iconKeyboardCurrentGo.image(
+                        width: 7.w,
+                        height: 7.w,
+                      ),
+                    ],
+                  ),
                 ),
               ],
             ),
@@ -649,30 +650,53 @@ class KeyBoardView extends BaseView<KeyBoardController> {
 
   // 活动banner
   Widget _buildBanner() {
-    return Container(
-      width: 328.w,
-      height: 64.h,
-      child: Stack(
-        children: [
-          Assets.images.iconKeyboardBanner.image(width: 328.w, height: 64.h),
+    return Obx(() {
+      if (!controller.isShowBanner.value) {
+        return SizedBox(width: 328.w, height: 74.h);
+      }
+      return SizedBox(
+        width: 328.w,
+        height: 74.h,
+        child: Stack(
+          clipBehavior: Clip.none,
+          children: [
+            Positioned(
+              top: 10.h,
+              child: Assets.images.iconKeyboardBanner.image(
+                width: 328.w,
+                height: 64.h,
+              ),
+            ),
+            Positioned(
+              right: 53.w,
+              bottom: 18.h,
+              child: Obx(
+                () => Text(
+                  controller.formattedTime,
+                  style: TextStyle(
+                    color: Colors.white,
+                    fontSize: 12.sp,
+                    fontWeight: FontWeight.w500,
+                  ),
+                ),
+              ),
+            ),
+            Positioned(
+              right: 10.w,
+              top: 0.h,
 
-          Positioned(
-            right: 53.w,
-            bottom: 18.h,
-            child: Obx(
-              () => Text(
-                controller.formattedTime,
-                style: TextStyle(
-                  color: Colors.white,
-                  fontSize: 12.sp,
-                  fontWeight: FontWeight.w500,
+              child: GestureDetector(
+                onTap: controller.clickCloseBanner,
+                child: Assets.images.iconKeyboardBannerClose.image(
+                  width: 14.w,
+                  height: 22.h,
                 ),
               ),
             ),
-          ),
-        ],
-      ),
-    );
+          ],
+        ),
+      );
+    });
   }
 
   @override

+ 101 - 30
lib/module/profile/edit/profile_edit_controller.dart

@@ -1,11 +1,19 @@
+import 'package:get/get.dart';
 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/module/change/gender/change_gender_page.dart';
 import 'package:keyboard/utils/atmob_log.dart';
-import 'package:get/get.dart';
 
-import '../../../data/bean/custom_config_info.dart';
 import '../../../data/repository/config_repository.dart';
+import '../../change/birthday/change_birthday_page.dart';
+import '../../change/nickname/change_nickname_page.dart';
+
+enum ProfileEditMode {
+  add, // 新增
+  edit, // 编辑
+}
 
 @injectable
 class ProfileEditController extends BaseController {
@@ -13,22 +21,32 @@ class ProfileEditController extends BaseController {
 
   final ConfigRepository configRepository;
 
-  final RxInt _gender = 1.obs;
+  final RxnInt _currentGender = RxnInt(null);
 
   final RxString _avatarUrl = "".obs;
 
   String get avatarUrl => _avatarUrl.value;
 
-  CustomConfigInfo? get currentCharacterCustomConfig =>
-      configRepository.characterCustomConfig;
+  Rxn<DefaultAvatarInfo> get currentDefaultAvatarInfo =>
+      configRepository.defaultAvatarInfo;
+
+  late final ProfileEditMode mode;
 
   final Rx<KeyboardInfo> _currentCustomKeyboardInfo = KeyboardInfo().obs;
 
-  KeyboardInfo get currentCustomKeyboardInfo =>
-      _currentCustomKeyboardInfo.value;
+  Rx<KeyboardInfo> get currentCustomKeyboardInfo => _currentCustomKeyboardInfo;
+
+  final RxnString _currentBirthday = RxnString(null);
+
+  String? get currentBirthday => _currentBirthday.value;
+
+  // 当前昵称
+  final RxnString _currentNickname = RxnString(null);
+
+  String? get currentNickname => _currentNickname.value;
 
   // 当前自定义键盘亲密度
-  final RxInt _currentCustomIntimacy = 0.obs;
+  final RxInt _currentCustomIntimacy = 30.obs;
 
   int get currentCustomIntimacy => _currentCustomIntimacy.value;
 
@@ -37,28 +55,50 @@ class ProfileEditController extends BaseController {
 
   bool get customIntimacyChanged => _customIntimacyChanged.value;
 
-  final List<String> _boyAvatars = [];
+  final RxList<String> _boyAvatars = <String>[].obs;
 
-  final List<String> _girlAvatars = [];
+  final RxList<String> _girlAvatars = <String>[].obs;
 
   ProfileEditController(this.configRepository);
 
   @override
   void onInit() {
     super.onInit();
-    initData();
+    final KeyboardInfo? keyboardInfo = Get.arguments?["keyboardInfo"];
+    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 ?? "";
+    } else {
+      mode = ProfileEditMode.add;
+      _currentGender.value = null;
+      _currentCustomIntimacy.value = 30;
+      _avatarUrl.value = "";
+    }
+
+    ever<DefaultAvatarInfo?>(currentDefaultAvatarInfo, (info) {
+      updateAvatarListsAndSelectFirst(info);
+    });
+  }
+
+  @override
+  void onReady() {
+    super.onReady();
+    updateAvatarListsAndSelectFirst(currentDefaultAvatarInfo.value);
   }
 
-  void initData() {
-    AtmobLog.d(tag, "initData");
-    _boyAvatars.addAll(currentCharacterCustomConfig?.boyAvatars ?? []);
-    _girlAvatars.addAll(currentCharacterCustomConfig?.girlAvatars ?? []);
-    if (_gender.value == 1) {
+  void updateAvatarListsAndSelectFirst(DefaultAvatarInfo? info) {
+    _boyAvatars.assignAll(info?.maleAvatars ?? []);
+    _girlAvatars.assignAll(info?.femaleAvatars ?? []);
+    if (_currentGender.value == 1 && _boyAvatars.isNotEmpty) {
       _avatarUrl.value = _boyAvatars[0];
-    } else {
+    } else if (_girlAvatars.isNotEmpty) {
       _avatarUrl.value = _girlAvatars[0];
     }
-
   }
 
   clickSaveButton() {
@@ -66,25 +106,56 @@ class ProfileEditController extends BaseController {
   }
 
   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];
-    }
+    List<String> avatars =
+        _currentGender.value == 1 ? _boyAvatars : _girlAvatars;
+    if (avatars.isEmpty) return;
+    int currentIndex = avatars.indexOf(_avatarUrl.value);
+    _avatarUrl.value = avatars[(currentIndex + 1) % avatars.length];
+    AtmobLog.d(tag, 'nextAvatar: ${_avatarUrl.value}');
   }
+
   // 更新亲密度
   void updateIntimacy(int intimacy) {
-
-      _currentCustomIntimacy.value = intimacy;
-
+    _currentCustomIntimacy.value = intimacy;
   }
 
   clickBack() {
     AtmobLog.d(tag, 'clickBackButton');
     Get.back();
   }
+
+  void clickNickname() async {
+    AtmobLog.d(tag, 'clickNickname');
+    final result = await ChangeNicknamePage.start(
+      nickName: _currentNickname.value,
+    );
+    if (result != null) {
+      _currentNickname.value = result;
+    }
+  }
+
+  void clickGender() async {
+    AtmobLog.d(tag, 'clickGender');
+    final result = await ChangeGenderPage.start();
+    if (result != null) {
+      _currentGender.value = result;
+    }
+  }
+
+  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;
+    }
+  }
+
+  String get genderText {
+    if (_currentGender.value == 1) return '男';
+    if (_currentGender.value == 2) return '女';
+    return '请选择';
+  }
 }

+ 178 - 143
lib/module/profile/edit/profile_edit_page.dart

@@ -1,22 +1,29 @@
 import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/cupertino.dart';
 import 'package:flutter/src/widgets/framework.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
 import 'package:keyboard/base/base_page.dart';
 import 'package:keyboard/module/profile/edit/profile_edit_controller.dart';
 import 'package:flutter/material.dart';
 import 'package:get/get.dart';
+import 'package:keyboard/utils/age_zodiac_sign_util.dart';
 
+import '../../../data/bean/keyboard_info.dart';
 import '../../../resource/assets.gen.dart';
 import '../../../resource/string.gen.dart';
 import '../../../router/app_pages.dart';
 import '../../../utils/styles.dart';
+import '../../../widget/avatar/avatar_image_widget.dart';
 import '../../../widget/gradient_rect_slider_track_shape.dart';
 
 class ProfileEditPage extends BasePage<ProfileEditController> {
   const ProfileEditPage({super.key});
 
-  static start() {
-    Get.toNamed(RoutePath.profileEdit);
+  static start({KeyboardInfo? keyboardInfo}) {
+    Get.toNamed(
+      RoutePath.profileEdit,
+      arguments: {"keyboardInfo": keyboardInfo},
+    );
   }
 
   @override
@@ -71,8 +78,8 @@ class ProfileEditPage extends BasePage<ProfileEditController> {
                               SizedBox(height: 10.h),
                               _buildBirthdayCard(),
                               SizedBox(height: 10.h),
-                              _buildRelationshipCard(),
-                              SizedBox(height: 10.h),
+                              // _buildRelationshipCard(),
+                              // SizedBox(height: 10.h),
                             ],
                           ),
                         ),
@@ -106,27 +113,34 @@ class ProfileEditPage extends BasePage<ProfileEditController> {
   }
 
   _buildNameCard() {
-    return Container(
-      padding: EdgeInsets.only(left: 104.w, top: 14.h),
-      child: Row(
-        crossAxisAlignment: CrossAxisAlignment.end,
-        children: [
-          Text(
-            controller.currentCustomKeyboardInfo.name ?? '请输入昵称',
-            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,
+              ),
             ),
-          ),
-        ],
+          ],
+        ),
       ),
     );
   }
@@ -134,21 +148,25 @@ class ProfileEditPage extends BasePage<ProfileEditController> {
   _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:
-            controller.avatarUrl.isNotEmpty
-                ? CachedNetworkImage(
-                  imageUrl: controller.avatarUrl,
-                  width: 72.r,
-                  height: 72.r,
-                )
-                : SizedBox(),
-      ),
+      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(),
+        );
+      }),
     );
   }
 
@@ -170,39 +188,55 @@ class ProfileEditPage extends BasePage<ProfileEditController> {
   Widget _buildGenderCard() {
     return _buildListItem(
       onTap: () {
-        debugPrint('点击了性别');
+        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: [
+            Assets.images.iconCharacterCustomDetailMale.image(
+              width: 24.w,
+              height: 24.w,
+            ),
+            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('点击了生日');
+        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),
-        ],
-      ),
+      firstWidget: Text(
+          '出生日期', style: Styles.getTextStyleBlack204W400(14.sp)),
+      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),
+          ],
+        );
+      }),
     );
   }
 
@@ -214,13 +248,13 @@ class ProfileEditPage extends BasePage<ProfileEditController> {
       firstWidget: Text('关系', style: Styles.getTextStyleBlack204W400(14.sp)),
       bottomWidget: Row(
         children: [
-
           Spacer(),
           Assets.images.iconArrowRight.image(width: 24.w, height: 24.w),
         ],
       ),
     );
   }
+
   Widget _buildSaveButton() {
     return GestureDetector(
       onTap: () {
@@ -302,90 +336,91 @@ 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,
+      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,
         ),
-        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),
-                  ),
+        decoration: BoxDecoration(
+          image: DecorationImage(
+            image: Assets.images.bgProfileEditIntimacy.provider(),
+            fit: BoxFit.fill,
+          ),
+          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,
                 ),
-                child: Obx(() {
-                  return Text(
-                    '${StringName.intimacy}${controller.currentCustomIntimacy}%',
-                    textAlign: TextAlign.right,
-                    style: TextStyle(
-                      color: Colors.black.withAlpha(204),
-                      fontSize: 12.sp,
-                      fontWeight: FontWeight.w400,
+                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),
                     ),
-                  );
-                }),
-              ),
-            ],
-          ),
-          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,
+                      ),
+                    );
+                  }),
                 ),
-                child: Obx(() {
-                  return Slider(
-                    value: controller.currentCustomIntimacy.toDouble(),
+              ],
+            ),
+            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(),
 
-                    divisions: 100,
-                    min: 0,
-                    max: 100,
-                    onChanged: (value) {
-                      controller.updateIntimacy(value.toInt());
-                    },
-                  );
-                }),
-              );
-            },
-          ),
-        ],
-      ),
-    );
+                      divisions: 100,
+                      min: 0,
+                      max: 100,
+                      onChanged: (value) {
+                        controller.updateIntimacy(value.toInt());
+                      },
+                    );
+                  }),
+                );
+              },
+            ),
+          ],
+        ),
+      );
   }
 }

+ 7 - 0
lib/module/profile/profile_controller.dart

@@ -103,4 +103,11 @@ class ProfileController extends BaseController {
       }
     }
   }
+
+  clickAvatar({required bool isUser}) {
+    AtmobLog.d(tag, "clickAvatar");
+  }
+
+
+
 }

+ 292 - 259
lib/module/profile/profile_page.dart

@@ -1,5 +1,7 @@
-import 'package:cached_network_image/cached_network_image.dart';
+import 'package:dotted_border/dotted_border.dart';
+import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
 import 'package:get/get.dart';
 import 'package:keyboard/base/base_page.dart';
@@ -11,6 +13,7 @@ import '../../resource/assets.gen.dart';
 import '../../resource/string.gen.dart';
 import '../../router/app_pages.dart';
 import '../../utils/styles.dart';
+import '../../widget/avatar/avatar_image_widget.dart';
 
 class ProfilePage extends BasePage<ProfileController> {
   const ProfilePage({super.key});
@@ -47,7 +50,11 @@ class ProfilePage extends BasePage<ProfileController> {
               // 键盘列表
               Obx(() {
                 if (controller.keyboardInfoList.isEmpty) {
-                  return const SliverToBoxAdapter(child: SizedBox());
+                  return  SliverToBoxAdapter(child:  _buildKeyboardListItem(
+                    keyboardInfo: null,
+                    isChosen: false,
+                    hasKeyboard: false,
+                  ));
                 }
                 return SliverList(
                   delegate: SliverChildBuilderDelegate((context, index) {
@@ -55,9 +62,9 @@ class ProfilePage extends BasePage<ProfileController> {
                         controller.keyboardInfoList[index];
                     return Obx(() {
                       return _buildKeyboardListItem(
-                        keyboardInfo,
-                        keyboardInfo.id ==
-                            controller.currentCustomKeyboardInfo.id,
+                        keyboardInfo: keyboardInfo,
+                        isChosen: keyboardInfo.id == controller.currentCustomKeyboardInfo.id,
+                        hasKeyboard: true,
                       );
                     });
                   }, childCount: controller.keyboardInfoList.length),
@@ -91,94 +98,150 @@ class ProfilePage extends BasePage<ProfileController> {
     );
   }
 
-  _buildKeyboardListItem(KeyboardInfo keyboardInfo, bool isChoose) {
+
+
+  // 爱心
+  _buildLoveIndex(int? intimacy) {
+    return Container(
+      width: 87.w,
+      height: 71.h,
+      decoration: BoxDecoration(
+        image: DecorationImage(image: Assets.images.bgProfileLove.provider()),
+      ),
+      child: Column(
+        children: [
+          SizedBox(height: 45.h),
+          Container(
+            width: 47.w,
+            height: 19.h,
+            decoration: ShapeDecoration(
+              color: Colors.white,
+              shape: RoundedRectangleBorder(
+                side: BorderSide(width: 1.18.w, color: const Color(0xFFFD649B)),
+                borderRadius: BorderRadius.circular(12.36.r),
+              ),
+            ),
+            child:
+                (intimacy != null)
+                    ? Center(
+                      child: Text(
+                        IntimacyUtil.getIntimacyName(intimacy),
+                        style: TextStyle(
+                          color: const Color(0xFFFF73E0),
+                          fontSize: 11.sp,
+                          fontWeight: FontWeight.w500,
+                        ),
+                      ),
+                    )
+                    : Center(
+                      child: Text(
+                        "?",
+                        style: TextStyle(
+                          color: const Color(0xFFFF73E0),
+                          fontSize: 11.sp,
+                          fontWeight: FontWeight.w500,
+                        ),
+                      ),
+                    ),
+          ),
+        ],
+      ),
+    );
+  }
+
+
+  _buildTitle() {
+    return Container(
+      alignment: Alignment.centerLeft,
+      padding: EdgeInsets.only(top: 12.h, left: 16.w, right: 16.w),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: [
+          GestureDetector(
+            onTap: () => controller.clickBack(),
+            child: Assets.images.iconMineBackArrow.image(
+              width: 24.w,
+              height: 24.w,
+            ),
+          ),
+          Text(
+            StringName.profileList,
+            style: Styles.getTextStyleBlack204W500(17.sp),
+          ),
+          GestureDetector(
+            onTap: () => controller.clickAddButton(),
+            child: Assets.images.iconProfileAdd.image(
+              width: 24.w,
+              height: 24.w,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+  Widget _buildKeyboardListItem({
+    KeyboardInfo? keyboardInfo,
+    bool isChosen = false,
+    required bool hasKeyboard,
+  }) {
+    final String title = hasKeyboard ? '我&${keyboardInfo?.name ?? ""}' : '我';
+    final int? intimacy = hasKeyboard ? keyboardInfo?.intimacy : null;
+    final String? keyboardAvatar = hasKeyboard ? keyboardInfo?.avatar : null;
+    final int gender = hasKeyboard ? (keyboardInfo?.gender ?? 1) : 1;
+
     return GestureDetector(
-      onTap: () {
-        controller.clickOnChangeKeyboard(keyboardInfo);
-      },
+      onTap: hasKeyboard ? () => controller.clickOnChangeKeyboard(keyboardInfo!) : null,
       child: Container(
         height: 164.h,
         margin: EdgeInsets.only(left: 16.w, right: 16.w, bottom: 12.h),
-        decoration:
-            isChoose
-                ? ShapeDecoration(
-                  gradient: LinearGradient(
-                    begin: Alignment(0.02, 0.04),
-                    end: Alignment(1.00, 1.00),
-                    colors: [const Color(0xFFE7A0FF), const Color(0xFFAB8FFA)],
-                  ),
-                  shape: RoundedRectangleBorder(
-                    borderRadius: BorderRadius.circular(22.r),
-                  ),
-                  shadows: [
-                    BoxShadow(
-                      color: Color(0x1CD6C1FF),
-                      blurRadius: 4.r,
-                      offset: Offset(0, 4.r),
-                      spreadRadius: 0,
-                    ),
-                  ],
-                )
-                : ShapeDecoration(
-                  color: Colors.white,
-                  shape: RoundedRectangleBorder(
-                    borderRadius: BorderRadius.circular(20.r),
-                  ),
-                ),
-
+        decoration: isChosen
+            ? ShapeDecoration(
+          gradient: LinearGradient(
+            begin: Alignment(0.02, 0.04),
+            end: Alignment(1.00, 1.00),
+            colors: [const Color(0xFFE7A0FF), const Color(0xFFAB8FFA)],
+          ),
+          shape: RoundedRectangleBorder(
+            borderRadius: BorderRadius.circular(22.r),
+          ),
+          shadows: [
+            BoxShadow(
+              color: Color(0x1CD6C1FF),
+              blurRadius: 4.r,
+              offset: Offset(0, 4.r),
+              spreadRadius: 0,
+            ),
+          ],
+        )
+            : ShapeDecoration(
+          color: Colors.white,
+          shape: RoundedRectangleBorder(
+            borderRadius: BorderRadius.circular(20.r),
+          ),
+        ),
         child: Stack(
           children: [
-            isChoose
-                ? Opacity(
-                  opacity: 0.1,
-                  child: Assets.images.bgProfileSelected.image(),
-                )
-                : SizedBox(),
+            if (isChosen)
+              Opacity(
+                opacity: 0.1,
+                child: Assets.images.bgProfileSelected.image(),
+              ),
             Column(
               crossAxisAlignment: CrossAxisAlignment.center,
               children: [
                 Padding(
                   padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 10.h),
                   child: Row(
-                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                    mainAxisAlignment: MainAxisAlignment.start,
                     children: [
                       Text(
-                        '我&${keyboardInfo.name}',
+                        title,
                         style: TextStyle(
                           color: const Color(0xFF202020),
                           fontSize: 16.sp,
                           fontWeight: FontWeight.w700,
                         ),
                       ),
-                      Container(
-                        height: 26.h,
-                        width: 55.w,
-                        decoration: ShapeDecoration(
-                          color:
-                              isChoose ? Colors.white : const Color(0xFFF1F1F1),
-                          shape: RoundedRectangleBorder(
-                            borderRadius: BorderRadius.circular(50.r),
-                          ),
-                        ),
-                        child: Row(
-                          mainAxisAlignment: MainAxisAlignment.center,
-                          children: [
-                            Assets.images.iconProfileEdit.image(
-                              width: 10.w,
-                              height: 10.w,
-                            ),
-                            SizedBox(width: 4.w),
-                            Text(
-                              '编辑',
-                              style: TextStyle(
-                                color: Colors.black.withAlpha(178),
-                                fontSize: 11,
-                                fontWeight: FontWeight.w500,
-                              ),
-                            ),
-                          ],
-                        ),
-                      ),
                     ],
                   ),
                 ),
@@ -187,19 +250,12 @@ class ProfilePage extends BasePage<ProfileController> {
                   padding: EdgeInsets.symmetric(horizontal: 16.w),
                   child: Divider(
                     height: 1.h,
-                    color: isChoose ? Colors.transparent : Color(0xffF5F5F5),
+                    color: isChosen ? Colors.transparent : Color(0xffF5F5F5),
                   ),
                 ),
                 Expanded(
                   child: Container(
-                    margin:
-                        isChoose
-                            ? EdgeInsets.only(
-                              left: 4.w,
-                              right: 4.w,
-                              bottom: 4.w,
-                            )
-                            : EdgeInsets.all(0),
+                    margin: isChosen ? EdgeInsets.symmetric(horizontal: 4.w, vertical: 4.w) : EdgeInsets.zero,
                     decoration: ShapeDecoration(
                       color: Colors.white,
                       shape: RoundedRectangleBorder(
@@ -217,168 +273,25 @@ class ProfilePage extends BasePage<ProfileController> {
                     child: Row(
                       mainAxisAlignment: MainAxisAlignment.center,
                       children: [
-                        Stack(
-                          children: [
-                            Container(
-                              width: 78.w,
-                              height: 78.w,
-                              decoration: ShapeDecoration(
-                                color:
-                                    controller.userGender == 1
-                                        ? Color(0xFFB7B6FF)
-                                        : null,
-                                gradient:
-                                    controller.userGender == 1
-                                        ? null
-                                        : LinearGradient(
-                                          begin: Alignment(0.50, -0.00),
-                                          end: Alignment(0.50, 1.00),
-                                          colors: [
-                                            const Color(0xFFEBE6FF),
-                                            const Color(0xFFFFE6FE),
-                                          ],
-                                        ),
-                                shape: RoundedRectangleBorder(
-                                  side: BorderSide(
-                                    width: 1.5.w,
-                                    color: Colors.white,
-                                  ),
-                                  borderRadius: BorderRadius.circular(40.r),
-                                ),
-                              ),
-                              child:
-                                  controller.userInfo?.imageUrl?.isNotEmpty ==
-                                          true
-                                      ? ClipRRect(
-                                        borderRadius: BorderRadius.circular(
-                                          40.r,
-                                        ),
-                                        child: CachedNetworkImage(
-                                          width: 78.w,
-                                          height: 78.w,
-                                          imageUrl:
-                                              controller.userInfo?.imageUrl ??
-                                              "",
-                                        ),
-                                      )
-                                      : SizedBox(),
-                            ),
-                            Positioned(
-                              top: 0,
-                              right: 0,
-                              child:
-                                  controller.userGender == 1
-                                      ? Assets.images.iconProfileMale.image(
-                                        width: 20.w,
-                                        height: 20.w,
-                                      )
-                                      : Assets.images.iconProfileFemale.image(
-                                        width: 20.w,
-                                        height: 20.w,
-                                      ),
-                            ),
-                          ],
+                        _buildProfileAvatar(
+                          imageUrl: controller.userInfo?.imageUrl,
+                          gender: controller.userGender,
+                          onTap: () => controller.clickAvatar(isUser: true),
+                          genderIconAlignment: Alignment.topRight,
                         ),
                         SizedBox(width: 8.w),
-                        // 爱心
-                        Container(
-                          width: 87.w,
-                          height: 71.h,
-                          decoration: BoxDecoration(
-                            image: DecorationImage(
-                              image: Assets.images.bgProfileLove.provider(),
-                            ),
-                          ),
-                          child: Column(
-                            children: [
-                              SizedBox(height: 45.h),
-                              Container(
-                                width: 47.w,
-                                height: 19.h,
-                                decoration: ShapeDecoration(
-                                  color: Colors.white,
-                                  shape: RoundedRectangleBorder(
-                                    side: BorderSide(
-                                      width: 1.18.w,
-                                      color: const Color(0xFFFD649B),
-                                    ),
-                                    borderRadius: BorderRadius.circular(
-                                      12.36.r,
-                                    ),
-                                  ),
-                                ),
-                                child:
-                                    (keyboardInfo.intimacy != null)
-                                        ? Center(
-                                          child: Text(
-                                            IntimacyUtil.getIntimacyName(
-                                              keyboardInfo.intimacy!,
-                                            ),
-                                            style: TextStyle(
-                                              color: const Color(0xFFFF73E0),
-                                              fontSize: 11.sp,
-                                              fontWeight: FontWeight.w500,
-                                            ),
-                                          ),
-                                        )
-                                        : SizedBox(),
-                              ),
-                            ],
-                          ),
-                        ),
+                        _buildLoveIndex(intimacy),
                         SizedBox(width: 8.w),
-                        keyboardInfo.avatar?.isNotEmpty == true
-                            ? Stack(
-                              children: [
-                                Container(
-                                  width: 78.w,
-                                  height: 78.w,
-                                  decoration: ShapeDecoration(
-                                    color:
-                                        keyboardInfo.gender == 1
-                                            ? Color(0xFFB7B6FF)
-                                            : null,
-                                    gradient:
-                                        keyboardInfo.gender == 1
-                                            ? null
-                                            : LinearGradient(
-                                              begin: Alignment(0.50, -0.00),
-                                              end: Alignment(0.50, 1.00),
-                                              colors: [
-                                                const Color(0xFFEBE6FF),
-                                                const Color(0xFFFFE6FE),
-                                              ],
-                                            ),
-                                    shape: RoundedRectangleBorder(
-                                      side: BorderSide(
-                                        width: 1.5.w,
-                                        color: Colors.white,
-                                      ),
-                                      borderRadius: BorderRadius.circular(40.r),
-                                    ),
-                                  ),
-                                  child: ClipRRect(
-                                    borderRadius: BorderRadius.circular(40.r),
-                                    child: CachedNetworkImage(
-                                      imageUrl: keyboardInfo.avatar!,
-                                    ),
-                                  ),
-                                ),
-                                Positioned(
-                                  top: 0,
-                                  left: 0,
-                                  child:
-                                      keyboardInfo.gender == 1
-                                          ? Assets.images.iconProfileMale.image(
-                                            width: 20.w,
-                                            height: 20.w,
-                                          )
-                                          : Assets.images.iconProfileFemale
-                                              .image(width: 20.w, height: 20.w),
-                                ),
-                              ],
-                            )
-                            : SizedBox(width: 78.w, height: 78.w),
+                        hasKeyboard
+                            ? _buildProfileAvatar(
+                          imageUrl: keyboardAvatar,
+                          gender: gender,
+                          onTap: () => controller.clickAvatar(isUser: false),
+                          genderIconAlignment: Alignment.topLeft,
+                        )
+                            : _buildKeyboardListEmptyAvatar(
+                          onTap: () => controller.clickAddButton(),
+                        ),
                       ],
                     ),
                   ),
@@ -390,32 +303,152 @@ class ProfilePage extends BasePage<ProfileController> {
       ),
     );
   }
+  // 头像
+  Widget _buildProfileAvatar({
+    required String? imageUrl,
+    required int gender,
+    required VoidCallback? onTap,
 
-  _buildTitle() {
-    return Container(
-      alignment: Alignment.centerLeft,
-      padding: EdgeInsets.only(top: 12.h, left: 16.w, right: 16.w),
-      child: Row(
-        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+    Alignment genderIconAlignment = Alignment.topRight,
+  }) {
+    return GestureDetector(
+      onTap: onTap,
+      child: Stack(
+        alignment: Alignment.bottomCenter,
         children: [
-          GestureDetector(
-            onTap: controller.clickBack,
-            child: Assets.images.iconMineBackArrow.image(
-              width: 24.w,
-              height: 24.w,
+          Container(
+            margin: EdgeInsets.only(bottom: 10.h),
+            width: 78.w,
+            height: 78.w,
+            decoration: ShapeDecoration(
+              color: gender == 1 ? const Color(0xFFB7B6FF) : null,
+              gradient:
+              gender == 1
+                  ? null
+                  : const LinearGradient(
+                begin: Alignment(0.5, 0),
+                end: Alignment(0.5, 1.0),
+                colors: [Color(0xFFEBE6FF), Color(0xFFFFE6FE)],
+              ),
+              shape: RoundedRectangleBorder(
+                side: BorderSide(width: 1.5.w, color: Colors.white),
+                borderRadius: BorderRadius.circular(40.r),
+              ),
+            ),
+            child: CircleAvatarWidget(
+              image: Assets.images.iconKeyboardDefaultAvatar.provider(),
+              imageUrl: imageUrl,
+              size: 78.w,
+              borderWidth: 0.r,
+              placeholder: (_, __) {
+                return const CupertinoActivityIndicator();
+              },
             ),
           ),
-          Text("我", style: Styles.getTextStyleBlack204W500(17.sp)),
-          SizedBox(),
-          GestureDetector(
-            onTap: controller.clickAddButton,
-            child: Assets.images.iconProfileAdd.image(
-              width: 24.w,
-              height: 24.w,
+
+          Positioned(
+            top: 0,
+            left: genderIconAlignment == Alignment.topLeft ? 0 : null,
+            right: genderIconAlignment == Alignment.topRight ? 0 : null,
+            child:
+            gender == 1
+                ? Assets.images.iconProfileMale.image(
+              width: 20.w,
+              height: 20.w,
+            )
+                : Assets.images.iconProfileFemale.image(
+              width: 20.w,
+              height: 20.w,
             ),
           ),
+          Positioned(
+            child: Container(
+              width: 58.w,
+              height: 28.h,
+              decoration: ShapeDecoration(
+                color: Colors.white,
+                shape: RoundedRectangleBorder(
+                  side: BorderSide(width: 1, color: const Color(0x6BE1E1E1)),
+                  borderRadius: BorderRadius.circular(50.r),
+                ),
+                shadows: [
+                  BoxShadow(
+                    color: Color(0x56E6D9FF),
+                    blurRadius: 4,
+                    offset: Offset(0, 3),
+                    spreadRadius: 0,
+                  ),
+                ],
+              ),
+              child: Row(
+                mainAxisAlignment: MainAxisAlignment.center,
+                children: [
+                  Assets.images.iconProfileEdit.image(
+                    width: 12.w,
+                    height: 12.w,
+                  ),
+                  Text(
+                    StringName.profileEdit,
+                    style: Styles.getTextStyleBlack178W500(11.sp),
+                  ),
+                ],
+              ),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+
+  //   没有Keyboard时
+  _buildKeyboardListEmptyAvatar({required VoidCallback? onTap}) {
+    return GestureDetector(
+      onTap: onTap,
+      child: Stack(
+        alignment: Alignment.bottomCenter,
+        children: [
+          Container(
+            margin: EdgeInsets.only(bottom: 10.h),
+            width: 78.w,
+            height: 78.w,
+            child:    DottedBorder(
+              color: const Color(0xFFA595C8),
+              strokeWidth: 1.0.w,
+              borderType: BorderType.Circle,
+              child: Container(
+                width: 78.w,
+                height: 78.w,
+                margin: EdgeInsets.only(bottom: 10.h),
+                alignment: Alignment.center,
+                child: Column(
+                  crossAxisAlignment: CrossAxisAlignment.center,
+                  mainAxisAlignment: MainAxisAlignment.end,
+                  children: [
+                    Assets.images.iconProfilePlus.image(
+                      width: 22.5.w,
+                      height: 22.5.w,
+                      fit: BoxFit.cover,
+                    ),
+                    SizedBox(height: 2.h),
+                    Text(
+                      StringName.profileAdd,
+                      style: TextStyle(
+                        color: const Color(0xB2755BAB),
+                        fontSize: 12.sp,
+                        fontWeight: FontWeight.w400,
+                      ),
+                    ),
+                  ],
+                ),
+              ),
+            )
+          ),
+
         ],
       ),
     );
+
+
   }
 }

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

@@ -231,6 +231,37 @@ 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_change_gender_female_logo.webp
+  AssetGenImage get iconChangeGenderFemaleLogo =>
+      const AssetGenImage('assets/images/icon_change_gender_female_logo.webp');
+
+  /// File path: assets/images/icon_change_gender_female_select.webp
+  AssetGenImage get iconChangeGenderFemaleSelect => const AssetGenImage(
+    'assets/images/icon_change_gender_female_select.webp',
+  );
+
+  /// File path: assets/images/icon_change_gender_female_unselect.webp
+  AssetGenImage get iconChangeGenderFemaleUnselect => const AssetGenImage(
+    'assets/images/icon_change_gender_female_unselect.webp',
+  );
+
+  /// File path: assets/images/icon_change_gender_male_logo.webp
+  AssetGenImage get iconChangeGenderMaleLogo =>
+      const AssetGenImage('assets/images/icon_change_gender_male_logo.webp');
+
+  /// File path: assets/images/icon_change_gender_male_select.webp
+  AssetGenImage get iconChangeGenderMaleSelect =>
+      const AssetGenImage('assets/images/icon_change_gender_male_select.webp');
+
+  /// File path: assets/images/icon_change_gender_male_unselect.webp
+  AssetGenImage get iconChangeGenderMaleUnselect => const AssetGenImage(
+    'assets/images/icon_change_gender_male_unselect.webp',
+  );
+
   /// File path: assets/images/icon_character_arrow_down.webp
   AssetGenImage get iconCharacterArrowDown =>
       const AssetGenImage('assets/images/icon_character_arrow_down.webp');
@@ -604,6 +635,10 @@ class $AssetsImagesGen {
   AssetGenImage get iconProfileMale =>
       const AssetGenImage('assets/images/icon_profile_male.webp');
 
+  /// File path: assets/images/icon_profile_plus.webp
+  AssetGenImage get iconProfilePlus =>
+      const AssetGenImage('assets/images/icon_profile_plus.webp');
+
   /// File path: assets/images/icon_store_agree_privacy.webp
   AssetGenImage get iconStoreAgreePrivacy =>
       const AssetGenImage('assets/images/icon_store_agree_privacy.webp');
@@ -800,6 +835,13 @@ class $AssetsImagesGen {
     iconAlipayScanPayment,
     iconArrowRight,
     iconBlackBack,
+    iconChangeBirthdayGemini,
+    iconChangeGenderFemaleLogo,
+    iconChangeGenderFemaleSelect,
+    iconChangeGenderFemaleUnselect,
+    iconChangeGenderMaleLogo,
+    iconChangeGenderMaleSelect,
+    iconChangeGenderMaleUnselect,
     iconCharacterArrowDown,
     iconCharacterArrowRight,
     iconCharacterCustomButton,
@@ -889,6 +931,7 @@ class $AssetsImagesGen {
     iconProfileEdit,
     iconProfileFemale,
     iconProfileMale,
+    iconProfilePlus,
     iconStoreAgreePrivacy,
     iconStoreBack,
     iconStoreBanner1,

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

@@ -143,12 +143,19 @@ class StringName {
   static final String discountDialogSocial = 'discount_dialog_social'.tr; // 扩大社交
   static final String profileSave = 'profile_save'.tr; // 完成
   static final String profileEditSave = 'profile_edit_save'.tr; // 保存
+  static final String profileList = 'profile_list'.tr; // 档案列表
+  static final String profileAdd = 'profile_add'.tr; // 添加
+  static final String profileEdit = 'profile_edit'.tr; // 编辑
   static final String keyboardMemberOpen = 'keyboard_member_open'.tr; // 开通会员
   static final String keyboardIntimacySubtitle = 'keyboard_intimacy_subtitle'.tr; // 探索人格/情感特质
   static final String keyboardScreenshotSubtitle = 'keyboard_screenshot_subtitle'.tr; // 上下文语义分析
   static final String keyboardGoToManage = 'keyboard_go_to_manage'.tr; // 去管理
   static final String keyboardAdd = 'keyboard_add'.tr; // 添加
   static final String keyboardNoLogin = 'keyboard_no_login'.tr; // 自己
+  static final String keyboardRapport = 'keyboard_rapport'.tr; // 默契
+  static final String keyboardPassion = 'keyboard_passion'.tr; // 激情
+  static final String keyboardFetter = 'keyboard_fetter'.tr; // 羁绊
+  static final String keyboardPromise = 'keyboard_promise'.tr; // 承诺
   static final String keyboardGuideGoWechat = 'keyboard_guide_go_wechat'.tr; // 去微信体验
   static final String keyboardGuideWechatNotInstall = 'keyboard_guide_wechat_not_install'.tr; // 未安装微信
   static final String keyboardGuideInputHint = 'keyboard_guide_input_hint'.tr; // 选择粘贴TA的话,选择人设风格回复
@@ -319,12 +326,19 @@ class StringMultiSource {
       'discount_dialog_social': '扩大社交',
       'profile_save': '完成',
       'profile_edit_save': '保存',
+      'profile_list': '档案列表',
+      'profile_add': '添加',
+      'profile_edit': '编辑',
       'keyboard_member_open': '开通会员',
       'keyboard_intimacy_subtitle': '探索人格/情感特质',
       'keyboard_screenshot_subtitle': '上下文语义分析',
       'keyboard_go_to_manage': '去管理',
       'keyboard_add': '添加',
       'keyboard_no_login': '自己',
+      'keyboard_rapport': '默契',
+      'keyboard_passion': '激情',
+      'keyboard_fetter': '羁绊',
+      'keyboard_promise': '承诺',
       'keyboard_guide_go_wechat': '去微信体验',
       'keyboard_guide_wechat_not_install': '未安装微信',
       'keyboard_guide_input_hint': '选择粘贴TA的话,选择人设风格回复',

+ 17 - 0
lib/router/app_pages.dart

@@ -1,6 +1,10 @@
 import 'package:get/get.dart';
 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/gender/change_gender_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';
 import 'package:keyboard/module/character_custom/detail/character_custom_detail_page.dart';
@@ -21,6 +25,8 @@ 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/gender/change_gender_page.dart';
+import '../module/change/nickname/change_nickname_page.dart';
 import '../module/character/character_controller.dart';
 import '../module/character_custom/character_custom_page.dart';
 import '../module/character_custom/detail/character_custom_detail_controller.dart';
@@ -68,6 +74,10 @@ abstract class RoutePath {
 
   // 亲密度分析上传页
   static const intimacyAnalyseUpload = '/intimacyAnalyseUpload';
+
+  static const changeNickname = '/changeNickname';
+  static const changeGender = '/changeGender';
+  static const changeBirthday = '/changeBirthday';
 }
 
 class AppBinding extends Bindings {
@@ -96,6 +106,9 @@ class AppBinding extends Bindings {
     lazyPut(() => getIt.get<IntimacyAnalyseReportController>());
     lazyPut(() => getIt.get<IntimacyAnalyseScreenshotReplyController>());
     lazyPut(() => getIt.get<IntimacyAnalyseUploadController>());
+    lazyPut(() => getIt.get<ChangeNicknameController>());
+    lazyPut(() => getIt.get<ChangeGenderController>());
+    lazyPut(() => getIt.get<ChangeBirthdayController>());
   }
 
   void lazyPut<S>(InstanceBuilderCallback<S> builder) {
@@ -128,4 +141,8 @@ final generalPages = [
   GetPage(name: RoutePath.intimacyAnalyse, page: () => IntimacyAnalysePage()),
   // 亲密度分析上传页
   GetPage(name: RoutePath.intimacyAnalyseUpload, page: () => IntimacyAnalyseUploadPage()),
+
+  GetPage(name: RoutePath.changeNickname, page: () => ChangeNicknamePage()),
+  GetPage(name: RoutePath.changeGender, page: () => ChangeGenderPage()),
+  GetPage(name: RoutePath.changeBirthday, page: () => ChangeBirthdayPage()),
 ];

+ 70 - 0
lib/utils/age_zodiac_sign_util.dart

@@ -0,0 +1,70 @@
+import 'package:intl/intl.dart';
+
+class AgeZodiacSignUtil {
+  AgeZodiacSignUtil._();
+
+  /// 计算星座(传入 DateTime)
+  static String getZodiacSign(DateTime date) {
+    int month = date.month;
+    int day = date.day;
+
+    if ((month == 3 && day >= 21) || (month == 4 && day <= 19)) {
+      return '白羊座';
+    } else if ((month == 4 && day >= 20) || (month == 5 && day <= 20)) {
+      return '金牛座';
+    } else if ((month == 5 && day >= 21) || (month == 6 && day <= 20)) {
+      return '双子座';
+    } else if ((month == 6 && day >= 21) || (month == 7 && day <= 22)) {
+      return '巨蟹座';
+    } else if ((month == 7 && day >= 23) || (month == 8 && day <= 22)) {
+      return '狮子座';
+    } else if ((month == 8 && day >= 23) || (month == 9 && day <= 22)) {
+      return '处女座';
+    } else if ((month == 9 && day >= 23) || (month == 10 && day <= 22)) {
+      return '天秤座';
+    } else if ((month == 10 && day >= 23) || (month == 11 && day <= 21)) {
+      return '天蝎座';
+    } else if ((month == 11 && day >= 22) || (month == 12 && day <= 21)) {
+      return '射手座';
+    } else if ((month == 12 && day >= 22) || (month == 1 && day <= 19)) {
+      return '摩羯座';
+    } else if ((month == 1 && day >= 20) || (month == 2 && day <= 18)) {
+      return '水瓶座';
+    } else if ((month == 2 && day >= 19) || (month == 3 && day <= 20)) {
+      return '双鱼座';
+    } else {
+      return '未知星座';
+    }
+  }
+
+  /// 计算年龄(传入 DateTime)
+  static int calculateAge(DateTime birthDate) {
+    DateTime currentDate = DateTime.now();
+    int age = currentDate.year - birthDate.year;
+    if (currentDate.month < birthDate.month ||
+        (currentDate.month == birthDate.month && currentDate.day < birthDate.day)) {
+      age--;
+    }
+    return age;
+  }
+
+  /// 从字符串计算星座
+  static String getZodiacSignFromString(String dateStr, {String format = 'yyyy-MM-dd'}) {
+    try {
+      DateTime date = DateFormat(format).parse(dateStr);
+      return getZodiacSign(date);
+    } catch (e) {
+      return '无效日期';
+    }
+  }
+
+  /// 从字符串计算年龄
+  static int? calculateAgeFromString(String dateStr, {String format = 'yyyy-MM-dd'}) {
+    try {
+      DateTime date = DateFormat(format).parse(dateStr);
+      return calculateAge(date);
+    } catch (e) {
+      return null;
+    }
+  }
+}

+ 11 - 0
lib/utils/styles.dart

@@ -42,6 +42,8 @@ class Styles {
     );
   }
 
+
+
   static TextStyle getTextStyleBlack153W400(double? sp) {
     return TextStyle(
       color: Colors.black.withAlpha(153),
@@ -50,6 +52,15 @@ class Styles {
     );
   }
 
+
+  static TextStyle getTextStyleBlack178W500(double?sp){
+    return TextStyle(
+      color: Colors.black.withAlpha(178),
+      fontSize: sp,
+      fontWeight: FontWeight.w500,
+    );
+  }
+
   static TextStyle getTextStyleBlack102W500(double? sp) {
     return TextStyle(
       color: Colors.black.withAlpha(102),

+ 188 - 0
lib/widget/birthday_date_picker.dart

@@ -0,0 +1,188 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+
+class BirthdayDatePicker extends StatefulWidget {
+  final DateTime initialDate;
+  final DateTime minimumDate;
+  final DateTime maximumDate;
+  final void Function(DateTime) onDateChanged;
+
+  const BirthdayDatePicker({
+    super.key,
+    required this.initialDate,
+    required this.minimumDate,
+    required this.maximumDate,
+    required this.onDateChanged,
+  });
+
+  @override
+  State<BirthdayDatePicker> createState() => _BirthdayDatePickerState();
+}
+
+class _BirthdayDatePickerState extends State<BirthdayDatePicker> {
+  late int selectedYear;
+  late int selectedMonth;
+  late int selectedDay;
+
+  @override
+  void initState() {
+    super.initState();
+    selectedYear = widget.initialDate.year;
+    selectedMonth = widget.initialDate.month;
+    selectedDay = widget.initialDate.day;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      height: 150.h,
+      child: Stack(
+        children: [
+          Positioned(
+            bottom: 57.h, // Position at the bottom
+            left: 0,
+            right: 0,
+            top: 57.h,
+            child: Container(
+              height: 36.h,
+              decoration: BoxDecoration(
+                color: Colors.white.withValues(alpha: 0.6), // Background color
+                borderRadius: BorderRadius.circular(14.r),
+              ),
+            ),
+          ),
+          Row(
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: [
+              _buildPickerBuilder(
+                count: widget.maximumDate.year - widget.minimumDate.year + 1,
+                initialIndex: selectedYear - widget.minimumDate.year,
+                itemBuilder: (index) => '${widget.minimumDate.year + index}年',
+                onSelectedItemChanged: (index) {
+                  setState(() {
+                    selectedYear = widget.minimumDate.year + index;
+                    _adjustMonthAndDay();
+                    _notify();
+                  });
+                },
+              ),
+              _buildPickerBuilder(
+                count: _getMaxMonth(),
+                initialIndex: selectedMonth - 1,
+                itemBuilder: (index) => '${index + 1}月',
+                onSelectedItemChanged: (index) {
+                  setState(() {
+                    selectedMonth = index + 1;
+                    _adjustDayForMonth();
+                    _notify();
+                  });
+                },
+              ),
+              _buildPickerBuilder(
+                count: _getMaxDay(),
+                initialIndex: selectedDay - 1,
+                itemBuilder: (index) => '${index + 1}日',
+                onSelectedItemChanged: (index) {
+                  setState(() {
+                    selectedDay = index + 1;
+                    _notify();
+                  });
+                },
+              ),
+            ],
+          ),
+
+          // Positioned Stack item (will be at the bottom of the Row)
+        ],
+      ),
+    );
+  }
+
+  Widget _buildPickerBuilder({
+    required int count,
+    required int initialIndex,
+    required String Function(int) itemBuilder,
+    required Function(int) onSelectedItemChanged,
+  }) {
+    return Expanded(
+      child: ListWheelScrollView.useDelegate(
+        controller: FixedExtentScrollController(initialItem: initialIndex),
+        itemExtent: 36.h,
+        onSelectedItemChanged: onSelectedItemChanged,
+          overAndUnderCenterOpacity: 0.35,
+        childDelegate: ListWheelChildBuilderDelegate(
+          builder: (context, index) {
+            // 判断选中项
+            bool isSelected = index == initialIndex;
+            return Container(
+              alignment: Alignment.center,
+              color: Colors.transparent,
+              child: Text(
+                itemBuilder(index),
+                style: TextStyle(
+                  fontSize: 16.sp,
+                  fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
+                  color: isSelected ? Colors.black : Colors.black,
+                ),
+              ),
+            );
+          },
+          childCount: count,
+        ),
+      ),
+    );
+  }
+
+  void _adjustMonthAndDay() {
+    if (selectedYear == widget.maximumDate.year &&
+        selectedMonth > widget.maximumDate.month) {
+      selectedMonth = widget.maximumDate.month;
+    }
+    _adjustDayForMonth();
+  }
+
+  void _adjustDayForMonth() {
+    int maxDay = _getMaxDay();
+    if (selectedDay > maxDay) {
+      selectedDay = maxDay;
+    }
+  }
+
+  int _getMaxMonth() {
+    if (selectedYear == widget.maximumDate.year) {
+      return widget.maximumDate.month;
+    }
+    return 12;
+  }
+
+  int _getMaxDay() {
+    if (selectedYear == widget.maximumDate.year &&
+        selectedMonth == widget.maximumDate.month) {
+      return widget.maximumDate.day;
+    }
+    return _getDaysInMonth(selectedYear, selectedMonth);
+  }
+
+  int _getDaysInMonth(int year, int month) {
+    switch (month) {
+      case 2:
+        if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
+          return 29;
+        }
+        return 28;
+      case 4:
+      case 6:
+      case 9:
+      case 11:
+        return 30;
+      default:
+        return 31;
+    }
+  }
+
+  void _notify() {
+    final selectedDate = DateTime(selectedYear, selectedMonth, selectedDay);
+    widget.onDateChanged(selectedDate);
+  }
+}

+ 114 - 46
lib/widget/pargress_bar.dart

@@ -1,10 +1,12 @@
+import 'package:flutter/material.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
-import 'package:get/get.dart';
+
 import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
 
-class ProgressBar extends StatelessWidget {
+class ProgressBar extends StatefulWidget {
   final String title;
-  final RxInt value;
+  final int? value;
   final Color color;
 
   const ProgressBar({
@@ -15,60 +17,126 @@ class ProgressBar extends StatelessWidget {
   });
 
   @override
+  State<ProgressBar> createState() => _ProgressBarState();
+}
+
+class _ProgressBarState extends State<ProgressBar>
+    with SingleTickerProviderStateMixin {
+  late AnimationController _animationController;
+  late Animation<double> _progressAnimation;
+  late Tween<double> _valueTween;
+
+  @override
+  void initState() {
+    super.initState();
+    _animationController = AnimationController(
+      vsync: this,
+      duration: Duration(seconds: 1),
+    );
+
+    _valueTween = Tween<double>(begin: 0.0, end: _getTargetProgress());
+    _progressAnimation = _valueTween.animate(
+      CurvedAnimation(parent: _animationController, curve: Curves.easeOut),
+    );
+
+    _startAnimation();
+  }
+
+  @override
+  void didUpdateWidget(covariant ProgressBar oldWidget) {
+    super.didUpdateWidget(oldWidget);
+
+    if (oldWidget.value != widget.value) {
+      _valueTween = Tween<double>(
+        begin: _progressAnimation.value,
+        end: _getTargetProgress(),
+      );
+      _progressAnimation = _valueTween.animate(
+        CurvedAnimation(parent: _animationController, curve: Curves.easeOut),
+      );
+      _startAnimation();
+    }
+  }
+
+  double _getTargetProgress() {
+    return (widget.value ?? 0) / 100.0;
+  }
+
+  void _startAnimation() {
+    if (widget.value == null) {
+      _animationController.repeat(); // 加载中,持续动画
+    } else {
+      _animationController
+        ..reset()
+        ..forward(); // 有值,从 0 -> value 动画
+    }
+  }
+
+  @override
+  void dispose() {
+    _animationController.dispose();
+    super.dispose();
+  }
+
+  @override
   Widget build(BuildContext context) {
-    return Obx(() {
-      final double progress = (value.value / 100).clamp(0.0, 1.0);
-      return Container(
-        child: Row(
-          children: [
-            Text(title),
-            SizedBox(width: 3.w),
-            Expanded(
-              child: Stack(
-                children: [
-                  Container(
-                    height: 11.h,
-                    decoration: BoxDecoration(
-                      color: color.withValues(alpha: 0.3),
-                      borderRadius: BorderRadius.circular(53.r),
-                    ),
-                  ),
-                  FractionallySizedBox(
-                    widthFactor: progress,
+    final bool isLoading = widget.value == null;
+    return Row(
+      children: [
+        Text(widget.title),
+        SizedBox(width: 3.w),
+        Expanded(
+          child: Stack(
+            children: [
+              Container(
+                height: 11.h,
+                decoration: BoxDecoration(
+                  color: widget.color.withValues(alpha: 0.3),
+                  borderRadius: BorderRadius.circular(53.r),
+                ),
+              ),
+              AnimatedBuilder(
+                animation: _progressAnimation,
+                builder: (_, __) {
+                  final double progress = _progressAnimation.value;
+                  return FractionallySizedBox(
+                    widthFactor: progress.clamp(0.0, 1.0),
                     child: Container(
                       height: 11.h,
                       decoration: BoxDecoration(
-                        color: color,
+                        color: widget.color,
                         borderRadius: BorderRadius.circular(53.r),
                       ),
                     ),
-                  ),
-                  Positioned.fill(
-                    child: Center(
-                      child: Text(
-                        "${value.value}%",
-                        textAlign: TextAlign.center,
-                        style: TextStyle(
-                          color: Colors.white,
-                          fontSize: 7.sp,
-                          fontWeight: FontWeight.w500,
-                          shadows: [
-                            Shadow(
-                              color: Colors.black.withValues(alpha: 0.6),
-                              offset: Offset(1, 1),
-                              blurRadius: 3.r,
-                            ),
-                          ],
+                  );
+                },
+              ),
+              Positioned.fill(
+                child: Center(
+                  child: Text(
+                    isLoading
+                        ? "加载中..."
+                        : "${(widget.value ?? 0).clamp(0, 100)}%",
+                    style: TextStyle(
+                      color: Colors.white,
+                      fontSize: 7.sp,
+                      fontWeight: FontWeight.w500,
+                      shadows: [
+                        Shadow(
+                          color: Colors.black.withValues(alpha: 0.6),
+                          offset: Offset(1, 1),
+                          blurRadius: 3.r,
                         ),
-                      ),
+                      ],
                     ),
                   ),
-                ],
+                ),
               ),
-            ),
-          ],
+            ],
+          ),
         ),
-      );
-    });
+      ],
+    );
   }
 }
+