浏览代码

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

hezihao 7 月之前
父节点
当前提交
39ef7fd4d6
共有 44 个文件被更改,包括 1661 次插入370 次删除
  1. 125 125
      android/app/proguard-rules.pro
  2. 5 0
      android/app/src/main/AndroidManifest.xml
  3. 1 0
      assets/anim/anim_intro_first_data.json
  4. 1 0
      assets/anim/anim_intro_second_data.json
  5. 1 0
      assets/anim/anim_keyboard_generating_data.json
  6. 二进制
      assets/images/bg_intro.webp
  7. 二进制
      assets/images/icon_app_logo.webp
  8. 二进制
      assets/images/icon_famate_black.webp
  9. 二进制
      assets/images/icon_intimacy_scale_mark.webp
  10. 二进制
      assets/images/icon_intro_title.webp
  11. 二进制
      assets/images/icon_male_white.webp
  12. 10 2
      assets/string/base/string.xml
  13. 18 6
      lib/data/consts/web_url.dart
  14. 1 1
      lib/data/repository/account_repository.dart
  15. 2 7
      lib/data/repository/keyboard_repository.dart
  16. 13 6
      lib/di/get_it.config.dart
  17. 1 1
      lib/dialog/agreement_again_dialog.dart
  18. 1 1
      lib/dialog/agreement_dialog.dart
  19. 11 1
      lib/dialog/keyboard_generating_dialog.dart
  20. 59 46
      lib/dialog/payment_success_dialog.dart
  21. 12 0
      lib/module/about/about_controller.dart
  22. 16 3
      lib/module/about/about_page.dart
  23. 85 0
      lib/module/intimacy_scale/intimacy_scale_controller.dart
  24. 237 0
      lib/module/intimacy_scale/intimacy_scale_page.dart
  25. 129 0
      lib/module/intro/intro_controller.dart
  26. 197 0
      lib/module/intro/intro_page.dart
  27. 5 0
      lib/module/keyboard/keyboard_controller.dart
  28. 67 54
      lib/module/keyboard/keyboard_view.dart
  29. 48 6
      lib/module/mine/mine_controller.dart
  30. 1 1
      lib/module/mine/mine_view.dart
  31. 1 1
      lib/module/new_user/new_user_controller.dart
  32. 84 86
      lib/module/new_user/step/intimacy/step_intimacy_stages_view.dart
  33. 16 7
      lib/module/new_user/step/partner/step_partner_view.dart
  34. 4 0
      lib/module/profile/profile_controller.dart
  35. 12 1
      lib/module/splash/splash_controller.dart
  36. 2 4
      lib/module/store/new_discount/new_discount_controller.dart
  37. 2 4
      lib/module/store/store_controller.dart
  38. 37 1
      lib/module/user_profile/user_profile_controller.dart
  39. 2 1
      lib/module/user_profile/user_profile_page.dart
  40. 43 0
      lib/resource/assets.gen.dart
  41. 12 2
      lib/resource/string.gen.dart
  42. 14 0
      lib/router/app_pages.dart
  43. 3 3
      lib/utils/intimacy_util.dart
  44. 383 0
      lib/widget/flutter_ruler_picker.dart

+ 125 - 125
android/app/proguard-rules.pro

@@ -1,131 +1,131 @@
-## Add project specific ProGuard rules here.
-## You can control the set of applied configuration files using the
-## proguardFiles setting in build.gradle.
-##
-## For more details, see
-##   http://developer.android.com/guide/developing/tools/proguard.html
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
 #
-## If your project uses WebView with JS, uncomment the following
-## and specify the fully qualified class name to the JavaScript interface
-## class:
-##-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-##   public *;
-##}
-#
-## Uncomment this to preserve the line number information for
-## debugging stack traces.
-##-keepattributes SourceFile,LineNumberTable
-#
-## If you keep the line number information, uncomment this to
-## hide the original source file name.
-##-renamesourcefileattribute SourceFile
-#-dontshrink
-#
-### Log
-#-assumenosideeffects class android.util.Log {
-#    public static *** d(...);
-#    public static *** v(...);
-#    public static *** w(...);
-#    public static *** i(...);
-#    public static *** wtf(...);
-#}
-#-assumenosideeffects class com.atmob.common.logging.AtmobLog {
-#    public static *** d(...);
-#    public static *** v(...);
-#    public static *** w(...);
-#    public static *** i(...);
-#    public static *** wtf(...);
-#}
-#
-### native
-#-keepclasseswithmembernames class * {
-#    native <methods>;
-#}
-#
-### res
-#-keepclassmembers class **.R$* {
-#    public static <fields>;
-#}
-#
-### ViewBinding & DataBinding
-#-keep class * implements androidx.viewbinding.ViewBinding {
-#    public inflate(android.view.LayoutInflater);
-#    public inflate(android.view.LayoutInflater, android.view.ViewGroup, boolean);
-#}
-#
-## common config end
-#
-## RxJava start
-#-dontwarn java.util.concurrent.Flow*
-## RxJava end
-#
-## Glide start
-#-keep public class * implements com.bumptech.glide.module.GlideModule
-#-keep class * extends com.bumptech.glide.module.AppGlideModule {
-# <init>(...);
-#}
-#-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
-#  **[] $VALUES;
-#  public *;
-#}
-#-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder {
-#  *** rewind();
-#}
-## Glide end
-#
-## Turbo start
-#-keep class com.plutus.common.turbo.beans.** {*;}
-## Turbo end
-#
-## bugly start
-#-dontwarn com.tencent.bugly.**
-#-keep public class com.tencent.bugly.**{*;}
-## bugly end
-#
-## Umeng start
-#-keep class com.umeng.** {*;}
-#-keep class org.repackage.** {*;}
-#-keepclassmembers class * {
-#   public <init> (org.json.JSONObject);
-#}
-#-keepclassmembers enum * {
-#    public static **[] values();
-#    public static ** valueOf(java.lang.String);
-#}
-## Umeng end
-#
-##oaid miitmdid start
-#-keep class com.bun.miitmdid.core.** {*;}
-#-keep class com.bun.lib.**{*;}
-#-keep class XI.CA.XI.**{*;}
-#-keep class XI.K0.XI.**{*;}
-#-keep class XI.XI.K0.**{*;}
-#-keep class XI.vs.K0.**{*;}
-#-keep class XI.xo.XI.XI.**{*;}
-#-keep class com.asus.msa.SupplementaryDID.**{*;}
-#-keep class com.asus.msa.sdid.**{*;}
-#-keep class com.bun.lib.**{*;}
-#-keep class com.bun.miitmdid.**{*;}
-#-keep class com.huawei.hms.ads.identifier.**{*;}
-#-keep class com.samsung.android.deviceidservice.**{*;}
-#-keep class org.json.**{*;}
-#-keep public class com.netease.nis.sdkwrapper.Utils {
-#public <methods>;
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
 #}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+-dontshrink
+
+## Log
+-assumenosideeffects class android.util.Log {
+    public static *** d(...);
+    public static *** v(...);
+    public static *** w(...);
+    public static *** i(...);
+    public static *** wtf(...);
+}
+-assumenosideeffects class com.atmob.common.logging.AtmobLog {
+    public static *** d(...);
+    public static *** v(...);
+    public static *** w(...);
+    public static *** i(...);
+    public static *** wtf(...);
+}
+
+## native
+-keepclasseswithmembernames class * {
+    native <methods>;
+}
+
+## res
+-keepclassmembers class **.R$* {
+    public static <fields>;
+}
+
+## ViewBinding & DataBinding
+-keep class * implements androidx.viewbinding.ViewBinding {
+    public inflate(android.view.LayoutInflater);
+    public inflate(android.view.LayoutInflater, android.view.ViewGroup, boolean);
+}
+
+# common config end
+
+# RxJava start
+-dontwarn java.util.concurrent.Flow*
+# RxJava end
+
+# Glide start
+-keep public class * implements com.bumptech.glide.module.GlideModule
+-keep class * extends com.bumptech.glide.module.AppGlideModule {
+ <init>(...);
+}
+-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
+  **[] $VALUES;
+  public *;
+}
+-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder {
+  *** rewind();
+}
+# Glide end
+
+# Turbo start
+-keep class com.plutus.common.turbo.beans.** {*;}
+# Turbo end
+
+# bugly start
+-dontwarn com.tencent.bugly.**
+-keep public class com.tencent.bugly.**{*;}
+# bugly end
+
+# Umeng start
+-keep class com.umeng.** {*;}
+-keep class org.repackage.** {*;}
+-keepclassmembers class * {
+   public <init> (org.json.JSONObject);
+}
+-keepclassmembers enum * {
+    public static **[] values();
+    public static ** valueOf(java.lang.String);
+}
+# Umeng end
+
+#oaid miitmdid start
+-keep class com.bun.miitmdid.core.** {*;}
+-keep class com.bun.lib.**{*;}
+-keep class XI.CA.XI.**{*;}
+-keep class XI.K0.XI.**{*;}
+-keep class XI.XI.K0.**{*;}
+-keep class XI.vs.K0.**{*;}
+-keep class XI.xo.XI.XI.**{*;}
+-keep class com.asus.msa.SupplementaryDID.**{*;}
+-keep class com.asus.msa.sdid.**{*;}
+-keep class com.bun.lib.**{*;}
+-keep class com.bun.miitmdid.**{*;}
+-keep class com.huawei.hms.ads.identifier.**{*;}
+-keep class com.samsung.android.deviceidservice.**{*;}
+-keep class org.json.**{*;}
+-keep public class com.netease.nis.sdkwrapper.Utils {
+public <methods>;
+}
+
 #
-##
-#-keep class androidx.core.content.FileProvider { *; }
-#-keep class android.support.v4.content.FileProvider { *; }
-#
-##oaid miitmdid end
-#
-##flutter start
-#
-#-keep class io.flutter.** { *; }
-#
-##flutter end
-#
-#
+-keep class androidx.core.content.FileProvider { *; }
+-keep class android.support.v4.content.FileProvider { *; }
+
+#oaid miitmdid end
+
+#flutter start
+
+-keep class io.flutter.** { *; }
+
+#flutter end
+
+
 
 # ==================================== Glide ====================================
 

+ 5 - 0
android/app/src/main/AndroidManifest.xml

@@ -28,7 +28,12 @@
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
+
         </activity>
+<!--        两个动画快速切换容易闪退-->
+        <meta-data
+            android:name="io.flutter.embedding.android.EnableImpeller"
+            android:value="false" />
         <!-- Don't delete the meta-data below.
              This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
         <meta-data

文件差异内容过多而无法显示
+ 1 - 0
assets/anim/anim_intro_first_data.json


文件差异内容过多而无法显示
+ 1 - 0
assets/anim/anim_intro_second_data.json


文件差异内容过多而无法显示
+ 1 - 0
assets/anim/anim_keyboard_generating_data.json


二进制
assets/images/bg_intro.webp


二进制
assets/images/icon_app_logo.webp


二进制
assets/images/icon_famate_black.webp


二进制
assets/images/icon_intimacy_scale_mark.webp


二进制
assets/images/icon_intro_title.webp


二进制
assets/images/icon_male_white.webp


+ 10 - 2
assets/string/base/string.xml

@@ -13,7 +13,7 @@
     <string name="feedback">意见反馈</string>
     <string name="about_us">关于我们</string>
     <string name="mine_account_logged_desc">用户</string>
-    <string name="mine_account_no_login">游客,去登</string>
+    <string name="mine_account_no_login">游客,去登</string>
 
 
     <string name="direct_send">生成内容直接发送</string>
@@ -80,6 +80,8 @@
     <string name="privacy_policy">隐私政策</string>
     <string name="service_terms">服务条款</string>
     <string name="child_privacy_policy">儿童隐私保护协议</string>
+    <string name="personal_info_protection">个人信息收集清单</string>
+    <string name="third_party_list">第三方SDK共享清单</string>
 
     <!--    人设页面-->
     <string name="go_customize_character">去定制人设</string>
@@ -330,7 +332,7 @@
     <string name="no_choose_mode_tip">请选择模式</string>
     <string name="no_upload_screenshot_tip">请上传截图</string>
     <string name="no_choose_intimacy_ta_tip">请选择一个亲密档案</string>
-    <string name="no_create_intimacy_ta_tip">需要添加对方档案才能分析 </string>
+    <string name="no_create_intimacy_ta_tip">需要添加对方档案才能分析</string>
     <string name="no_choose_prediction_direction_tip">请选择预测方向</string>
     <string name="no_choose_ai_model_tip">请选择AI模型</string>
 
@@ -419,4 +421,10 @@
     <string name="privacy_disagree">不同意</string>
     <string name="privacy_disagree_and_exit">不同意并退出</string>
     <string name="privacy_agree">同意</string>
+
+    <string name="custom_love_keyboard">定制恋爱键盘</string>
+    <string name="go_to_login">去登录</string>
+
+    <string name="intimacy_index">亲密指数</string>
+
 </resources>

+ 18 - 6
lib/data/consts/web_url.dart

@@ -1,16 +1,26 @@
 class WebUrl {
   WebUrl._();
 
+  // 隐私政策
   static const String _privacyPolicy =
-      "https://doc.v8dashen.com/doc/298eb75d38dc2c4a";
+      "https://doc.v8dashen.com/doc/88b8e528ec1c9652";
 
+  // 服务条款
   static const String _serviceAgreement =
-      "https://doc.v8dashen.com/doc/298eb75d38dc2c4a";
+      "https://doc.v8dashen.com/doc/cd0c14d2db602927";
+
+  // 儿童保护声明
   static const String _kidPolicy =
-      "https://doc.v8dashen.com/doc/298eb75d38dc2c4a";
+      "https://doc.v8dashen.com/doc/db9a720e3455850e";
+
+  // 个人信息保护指引
+  static const String _personalInformationList =
+      "https://doc.v8dashen.com/doc/c463429ebb833508";
 
-  static const String _userAgreement =
-      "https://doc.v8dashen.com/doc/546b8b5175a1b4db";
+
+  // 第三方SDK共享清单
+  static const String _thirdPartyList =
+      "https://doc.v8dashen.com/doc/35b71c0368d01923";
 
   static const String _qiyuService ="https://qiyu-kefu.atmob.com";
 
@@ -21,7 +31,9 @@ class WebUrl {
 
   static String get serviceAgreement => _serviceAgreement;
 
-  static String get userAgreement => _userAgreement;
+  static String get thirdPartyList => _thirdPartyList;
+
+  static String get personalInformationList => _personalInformationList;
 
   static String get qiyuService => _qiyuService;
 }

+ 1 - 1
lib/data/repository/account_repository.dart

@@ -216,7 +216,7 @@ class AccountRepository {
     KVUtil.putBool(Constants.keyIsMember, false);
     loginPhoneNum.value = null;
     isLogin.value = false;
-    keyboardRepository.cleanData();
+    keyboardRepository.refreshData();
   }
 
   // 意见反馈

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

@@ -67,6 +67,8 @@ class KeyboardRepository {
     refreshKeyboardList();
     refreshUserInfo();
     refreshLoveIndex();
+    getKeyboardHomeInfo();
+    getKeyboardLoveIndex();
   }
 
   void refreshUserInfo() async {
@@ -97,13 +99,6 @@ class KeyboardRepository {
     );
   }
 
-  Future cleanData() async {
-    _keyboardInfoList.clear();
-    _customKeyboardInfoList.clear();
-    _chooseKeyboardInfo.value = null;
-    getKeyboardHomeInfo();
-    getKeyboardLoveIndex();
-  }
 
   Future refreshKeyboardList() async {
     return getKeyboardList().then((response) {

+ 13 - 6
lib/di/get_it.config.dart

@@ -65,6 +65,8 @@ import '../module/intimacy_analyse/screenshot_reply/intimacy_analyse_screenshot_
     as _i279;
 import '../module/intimacy_analyse/screenshot_reply/scan_image_reply/scan_image_reply_controller.dart'
     as _i464;
+import '../module/intimacy_scale/intimacy_scale_controller.dart' as _i566;
+import '../module/intro/intro_controller.dart' as _i211;
 import '../module/keyboard/keyboard_controller.dart' as _i161;
 import '../module/keyboard_guide/keyboard_guide_controller.dart' as _i248;
 import '../module/keyboard_manage/keyboard_manage_controller.dart' as _i922;
@@ -132,6 +134,7 @@ extension GetItInjectableX on _i174.GetIt {
     gh.factory<_i1060.ZodiacLoveIntimacyController>(
       () => _i1060.ZodiacLoveIntimacyController(),
     );
+    gh.factory<_i211.IntroController>(() => _i211.IntroController());
     gh.lazySingleton<_i495.WechatLoginService>(
       () => _i495.WechatLoginService(),
     );
@@ -203,6 +206,13 @@ extension GetItInjectableX on _i174.GetIt {
             currentKeyboardInfo: currentKeyboardInfo,
           ),
     );
+    gh.factory<_i329.UserProfileController>(
+      () => _i329.UserProfileController(
+        gh<_i50.ConfigRepository>(),
+        gh<_i83.AccountRepository>(),
+        gh<_i274.KeyboardRepository>(),
+      ),
+    );
     gh.lazySingleton<_i425.ChatRepository>(
       () => _i425.ChatRepository(
         gh<_i329.AtmobStreamApi>(),
@@ -221,12 +231,6 @@ extension GetItInjectableX on _i174.GetIt {
         gh<_i83.AccountRepository>(),
       ),
     );
-    gh.factory<_i329.UserProfileController>(
-      () => _i329.UserProfileController(
-        gh<_i50.ConfigRepository>(),
-        gh<_i83.AccountRepository>(),
-      ),
-    );
     gh.lazySingleton<_i987.StoreRepository>(
       () => _i987.StoreRepository(
         gh<_i243.AtmobApi>(),
@@ -253,6 +257,9 @@ extension GetItInjectableX on _i174.GetIt {
     gh.factory<_i161.KeyBoardController>(
       () => _i161.KeyBoardController(gh<_i274.KeyboardRepository>()),
     );
+    gh.factory<_i566.IntimacyScaleController>(
+      () => _i566.IntimacyScaleController(gh<_i274.KeyboardRepository>()),
+    );
     gh.factory<_i701.NewUserController>(
       () => _i701.NewUserController(
         gh<_i83.AccountRepository>(),

+ 1 - 1
lib/dialog/agreement_again_dialog.dart

@@ -27,7 +27,7 @@ class AgreementAgainDialog {
         return _AgreementAgainDialog(cancelClick, sureClick);
       },
       alignment: Alignment.center,
-      clickMaskDismiss: true,
+      clickMaskDismiss: false,
     );
   }
 

+ 1 - 1
lib/dialog/agreement_dialog.dart

@@ -27,7 +27,7 @@ class AgreementDialog {
         return _AgreementDialog(cancelClick, sureClick);
       },
       alignment: Alignment.center,
-      clickMaskDismiss: true,
+      clickMaskDismiss: false,
     );
   }
 

+ 11 - 1
lib/dialog/keyboard_generating_dialog.dart

@@ -1,6 +1,9 @@
 import 'package:flutter/material.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
 import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
+import 'package:lottie/lottie.dart';
+
+import '../resource/assets.gen.dart';
 
 class KeyboardGeneratingDialog{
   static const tag = "KeyboardGeneratingDialog";
@@ -22,9 +25,16 @@ class KeyboardGeneratingDialog{
                 colors: [const Color(0xFFE0D5FD), Colors.white],
               ),
               shape: RoundedRectangleBorder(
-                borderRadius: BorderRadius.circular(20),
+                borderRadius: BorderRadius.circular(20.r),
               ),
             ),
+            child: Lottie.asset(
+              Assets.anim.animKeyboardGeneratingData,
+              repeat: true,
+              width: 200.w,
+              height: 200.h,
+              fit: BoxFit.contain,
+            ),
           );
         });
   }

+ 59 - 46
lib/dialog/payment_success_dialog.dart

@@ -1,17 +1,24 @@
 import 'package:flutter/material.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
 import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
+import 'package:keyboard/data/repository/account_repository.dart';
 import 'package:keyboard/resource/string.gen.dart';
 
+import '../data/bean/goods_info.dart';
 import '../resource/assets.gen.dart';
 import '../resource/colors.gen.dart';
+import '../utils/date_util.dart';
 import '../utils/styles.dart';
+import 'package:get/get.dart';
 
 // 支付成功
 class PaymentSuccessDialog {
   static const String tag = 'PaymentSuccessDialog';
 
-  static void show({required String infoText, required Function? btnConfirm}) {
+  static void show({
+    required Function? btnConfirm,
+    required GoodsInfo goodsInfo,
+  }) {
     SmartDialog.show(
       tag: tag,
       backType: SmartBackType.block,
@@ -44,65 +51,71 @@ class PaymentSuccessDialog {
                       crossAxisAlignment: CrossAxisAlignment.center,
                       mainAxisAlignment: MainAxisAlignment.center,
                       children: [
-                    Assets.images.iconDialogPaySuccess.image(
-                    width: 88.w,
-                      height: 88.h,
-                    ),
-                    SizedBox(height: 8.h),
-                    Text(
-                      StringName.paySuccessDialogTitle,
-                      style: Styles.getTextStyleBlack204W500(16.sp),
+                        Assets.images.iconDialogPaySuccess.image(
+                          width: 88.w,
+                          height: 88.h,
+                        ),
+                        SizedBox(height: 8.h),
+                        Text(
+                          StringName.paySuccessDialogTitle,
+                          style: Styles.getTextStyleBlack204W500(16.sp),
+                        ),
+                        Obx(() {
+                          return Text(
+                            AccountRepository.getInstance()
+                                        .memberStatusInfo
+                                        .value
+                                        ?.permanent ==
+                                    true
+                                ? StringName.paySuccessDialogPermanent
+                                : "${goodsInfo.name}${DateUtil.fromMillisecondsSinceEpoch('yyyy年MM月dd日', AccountRepository.getInstance().memberStatusInfo.value?.endTimestamp ?? 0)}${StringName.paySuccessDialogDesc}",
+                            style: Styles.getTextStyleBlack153W400(14.sp),
+                          );
+                        }),
+                        SizedBox(height: 24.h),
+
+                        SizedBox(
+                          width: double.infinity,
+                          child: GestureDetector(
+                            onTap: () {
+                              SmartDialog.dismiss();
+                              btnConfirm?.call();
+                            },
+                            child: Container(
+                              height: 40.h,
+                              decoration: Styles.getActivateButtonDecoration(
+                                22.r,
+                              ),
+                              child: Center(
+                                child: Text(
+                                  StringName.paySuccessDialogConfirm,
+                                  style: Styles.getTextStyleWhiteW500(16.sp),
+                                ),
+                              ),
+                            ),
+                          ),
+                        ),
+                      ],
                     ),
-                    Text(
-                        infoText,
-                        style: Styles.getTextStyleBlack153W400(14.sp),
                   ),
-                  SizedBox(height: 24.h),
-
-                  SizedBox(
-                    width: double.infinity,
+                  Positioned(
+                    right: 14.w,
+                    top: 14.h,
                     child: GestureDetector(
                       onTap: () {
                         SmartDialog.dismiss();
                         btnConfirm?.call();
                       },
-                      child: Container(
-                        height: 40.h,
-                        decoration: Styles.getActivateButtonDecoration(
-                          22.r,
-                        ),
-                        child: Center(
-                          child: Text(
-                            StringName.paySuccessDialogConfirm,
-                            style: Styles.getTextStyleWhiteW500(16.sp),
-                          ),
-                        ),
+                      child: Assets.images.iconCustomDialogClose.image(
+                        width: 24.w,
+                        height: 24.h,
                       ),
                     ),
-
                   ),
                 ],
               ),
             ),
-            Positioned(
-              right: 14.w,
-              top: 14.h,
-              child: GestureDetector(
-                onTap: () {
-                  SmartDialog.dismiss();
-                  btnConfirm?.call();
-                },
-                child: Assets.images.iconCustomDialogClose.image(
-                  width: 24.w,
-                  height: 24.h,
-                ),
-              ),
-            ),
           ],
-        ),)
-        ,
-        ]
-        ,
         );
       },
     );

+ 12 - 0
lib/module/about/about_controller.dart

@@ -29,4 +29,16 @@ class AboutController extends BaseController {
     debugPrint('clickChildPrivacyPolicy');
     BrowserPage.start(WebUrl.kidPolicy);
   }
+
+  clickThirdParty() {
+    debugPrint('clickThirdParty');
+    BrowserPage.start(WebUrl.thirdPartyList);
+  }
+
+  clickPersonalInformation() {
+    debugPrint('clickPersonalInformation');
+    BrowserPage.start(WebUrl.personalInformationList);
+  }
+
+
 }

+ 16 - 3
lib/module/about/about_page.dart

@@ -53,10 +53,13 @@ class AboutPage extends BasePage<AboutController> {
                         width: 72.w,
                         height: 72.w,
                         decoration: BoxDecoration(
-                          color: Colors.grey[200],
-                          borderRadius: BorderRadius.circular(16),
-                        ),
 
+                          borderRadius: BorderRadius.circular(16.r),
+                        ),
+                        child: Assets.images.iconAppLogo.image(
+                          width: 72.w,
+                          height: 72.w,
+                        ),
                       ),
                       SizedBox(height: 21.h),
                       Text(
@@ -107,6 +110,16 @@ class AboutPage extends BasePage<AboutController> {
                         StringName.childPrivacyPolicy,
                         onTap: controller.clickChildPrivacyPolicy,
                       ),
+                      _buildDivider(),
+                      _buildListItem(
+                        StringName.thirdPartyList,
+                        onTap: controller.clickThirdParty,
+                      ),
+                      _buildDivider(),
+                      _buildListItem(
+                        StringName.personalProfile,
+                        onTap: controller.clickPersonalInformation,
+                      ),
                     ],
                   ),
                 ),

+ 85 - 0
lib/module/intimacy_scale/intimacy_scale_controller.dart

@@ -0,0 +1,85 @@
+import 'package:injectable/injectable.dart';
+import 'package:keyboard/base/base_controller.dart';
+import 'package:get/get.dart';
+import 'package:keyboard/data/repository/keyboard_repository.dart';
+import 'package:keyboard/utils/toast_util.dart';
+import '../../data/bean/keyboard_info.dart';
+import '../../utils/error_handler.dart';
+import '../../utils/http_handler.dart';
+import '../../widget/flutter_ruler_picker.dart';
+import '../store/store_page.dart';
+
+@injectable
+class IntimacyScaleController extends BaseController {
+  final tag = "IntimacyScaleController";
+
+  final KeyboardRepository keyboardRepository;
+
+  Rxn<KeyboardInfo> get currentKeyboardInfo =>
+      keyboardRepository.chooseKeyboardInfo;
+
+  late Rx<RulerPickerController> rulerPickerController;
+
+  List<RulerRange> ranges = const [RulerRange(begin: 0, end: 100, scale: 1)];
+
+  // 当前亲密度
+  RxInt currentCustomIntimacy = 0.obs;
+
+  IntimacyScaleController(this.keyboardRepository);
+
+  @override
+  void onInit() {
+    super.onInit();
+
+    currentCustomIntimacy.value = currentKeyboardInfo.value?.intimacy ?? 0;
+    rulerPickerController =
+        RulerPickerController(
+          value: currentKeyboardInfo.value?.intimacy ?? 0,
+        ).obs;
+    rulerPickerController.value.addListener(() {
+      currentCustomIntimacy.value = rulerPickerController.value.value.toInt();
+    });
+  }
+
+  @override
+  void onReady() {
+    super.onReady();
+  }
+
+  @override
+  void onClose() {
+    super.onClose();
+  }
+
+  void clickBack() {
+    Get.back();
+  }
+
+  void onChangeIntimacy(num value) {
+    currentCustomIntimacy.value = value.toInt();
+  }
+
+  Future<void> clickSaveButton() async {
+    if (currentKeyboardInfo.value == null &&
+        currentKeyboardInfo.value?.id == null) {
+      ToastUtil.show("请选择ta的键盘");
+      return;
+    }
+
+    try {
+      await keyboardRepository.updateKeyboardInfo(
+        keyboardId: currentKeyboardInfo.value!.id!,
+        intimacy: currentCustomIntimacy.value,
+      );
+      await keyboardRepository.getKeyboardHomeInfo();
+      Get.back();
+    } catch (error) {
+      if (error is ServerErrorException && error.code == 1005) {
+        ToastUtil.show('请开通会员解锁权益~');
+        StorePage.start();
+      } else {
+        ToastUtil.show(error.toString());
+      }
+    }
+  }
+}

+ 237 - 0
lib/module/intimacy_scale/intimacy_scale_page.dart

@@ -0,0 +1,237 @@
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:keyboard/base/base_page.dart';
+import 'package:keyboard/module/intimacy_scale/intimacy_scale_controller.dart';
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+
+import '../../resource/assets.gen.dart';
+import '../../resource/string.gen.dart';
+import '../../router/app_pages.dart';
+import '../../utils/intimacy_util.dart';
+import '../../utils/styles.dart';
+import '../../widget/flutter_ruler_picker.dart';
+import '../../widget/heart_fill_view.dart';
+
+class IntimacyScalePage extends BasePage<IntimacyScaleController> {
+  const IntimacyScalePage({super.key});
+
+  static void start() {
+    Get.toNamed(RoutePath.intimacyScale);
+  }
+
+  @override
+  bool immersive() {
+    return true;
+  }
+
+  @override
+  Color backgroundColor() {
+    return Color(0xFFF6F5FA);
+  }
+
+  @override
+  Widget buildBody(BuildContext context) {
+    return Stack(
+      children: [
+        SafeArea(
+          child: Column(
+            crossAxisAlignment: CrossAxisAlignment.center,
+            children: [
+              _buildTitle(),
+              SizedBox(height: 128.w),
+
+              SizedBox(
+                width: 250.w,
+                child: Stack(
+                  alignment: Alignment.center,
+                  children: [
+                    Obx(() {
+                      return HeartFillAnimation(
+                        fillProgress:
+                        controller.currentCustomIntimacy.value > 0
+                            ? controller.currentCustomIntimacy.value / 100
+                            : 0,
+                        width: 210.w,
+                      );
+                    }),
+                    Positioned(
+                      left: 0,
+                      right: 0,
+                      child: Obx(() {
+                        return Text.rich(
+                          TextSpan(
+                            children: [
+                              TextSpan(
+                                text: controller.currentCustomIntimacy.value.toString(),
+                                style: TextStyle(
+                                  color: Colors.white,
+                                  fontSize: 82.72.sp,
+                                  height: 0,
+                                  fontWeight: FontWeight.w700,
+                                  shadows: [
+                                    Shadow(
+                                      offset: Offset(0, 3),
+                                      blurRadius: 9,
+                                      color: const Color(
+                                        0xFFFF6BD3,
+                                      ).withOpacity(0.63),
+                                    ),
+                                  ],
+                                ),
+                              ),
+                              TextSpan(
+                                text: "%",
+                                style: TextStyle(
+                                  color: Colors.white,
+                                  fontSize: 35.15.sp,
+                                  fontWeight: FontWeight.w700,
+                                  shadows: [
+                                    Shadow(
+                                      offset: Offset(0, 3),
+                                      blurRadius: 9,
+                                      color: const Color(
+                                        0xFFFF6BD3,
+                                      ).withOpacity(0.63),
+                                    ),
+                                  ],
+                                ),
+                              ),
+                            ],
+                          ),
+                          textAlign: TextAlign.center,
+                        );
+                      }),
+                    ),
+                  ],
+                ),
+              ),
+
+              SizedBox(height: 50.w),
+
+              buildIntimacyLevelName(),
+
+              SizedBox(height: 20.w),
+              buildRulerPicker(context),
+            ],
+          ),
+        ),
+        Positioned(
+          bottom: 20.h,
+          left: 16.w,
+          right: 16.w,
+          child: InkWell(
+            onTap: () {
+              controller.clickSaveButton();
+            },
+            child: Center(
+              child: Container(
+                width: 150.w,
+                height: 48.h,
+                alignment: Alignment.center,
+                decoration: Styles.getActivateButtonDecoration(31.r),
+                child: Text(
+                  StringName.save,
+                  style: Styles.getTextStyleWhiteW500(16.sp),
+                ),
+              ),
+            ),
+          ),
+        ),
+        // 背景图片
+        IgnorePointer(child: Assets.images.bgMine.image(width: 360.w)),
+      ],
+    );
+  }
+
+  Row buildRulerPicker(BuildContext context) {
+    return Row(
+      children: [
+        SizedBox(
+          child: Obx(() {
+            return RulerPicker(
+              rulerBackgroundColor: Colors.transparent,
+              controller: controller.rulerPickerController.value,
+              onBuildRulerScaleText: (index, value) {
+                return "";
+              },
+              ranges: controller.ranges,
+              onValueChanged: (value) {
+                controller.onChangeIntimacy(value);
+              },
+
+              width: 360.w,
+              height: 96.w,
+              rulerMarginTop: 25.w,
+              marker: Column(
+                children: [
+                  Assets.images.iconIntimacyScaleMark.image(
+                    width: 25.w,
+                    height: 25.w,
+                  ),
+                  Container(
+                    width: 8.w,
+                    height: 48.w,
+                    decoration: BoxDecoration(
+                      borderRadius: BorderRadius.circular(40.w),
+                      color: Color(0xff7d46fc),
+                    ),
+                  ),
+                ],
+              ),
+            );
+          }),
+        ),
+      ],
+    );
+  }
+
+  _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.intimacyIndex,
+            style: Styles.getTextStyleBlack204W500(17.sp),
+          ),
+          SizedBox(width: 24.w, height: 24.w),
+        ],
+      ),
+    );
+  }
+
+  // 亲密阶段名称
+  Widget buildIntimacyLevelName() {
+    return Obx(() {
+      return Container(
+        padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
+        decoration: ShapeDecoration(
+          color: Colors.white,
+          shape: RoundedRectangleBorder(
+            borderRadius: BorderRadius.circular(17.r),
+          ),
+        ),
+        child: Row(
+          mainAxisSize: MainAxisSize.min,
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: [
+            Assets.images.iconKeyboardLoveLogo.image(width: 18.w, height: 18.w),
+            Text(
+              IntimacyUtil.getIntimacyName(controller.currentCustomIntimacy.value),
+              style: Styles.getTextStyleBlack153W400(14.sp),
+            ),
+          ],
+        ),
+      );
+    });
+  }
+}

+ 129 - 0
lib/module/intro/intro_controller.dart

@@ -0,0 +1,129 @@
+import 'dart:async';
+
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:injectable/injectable.dart';
+import 'package:keyboard/base/base_controller.dart';
+import 'package:get/get.dart';
+import 'package:keyboard/dialog/login/login_dialog.dart';
+import 'package:keyboard/module/new_user/new_user_page.dart';
+import '../../data/consts/constants.dart';
+import '../../resource/assets.gen.dart';
+
+@injectable
+class IntroController extends BaseController {
+  final tag = "IntroController";
+  Rx<PageController> pageController = PageController().obs;
+
+  var currentPage = 0.obs;
+
+  Timer? _autoPageTimer;
+
+  IntroController();
+
+  final List<PageBean> pageList = [
+    PageBean(
+      Text.rich(
+        textAlign: TextAlign.center,
+        TextSpan(
+          children: [
+            TextSpan(
+              text: '想知道\n',
+              style: TextStyle(
+                color: Colors.black.withAlpha(153),
+                fontSize: 14.sp,
+                fontWeight: FontWeight.w500,
+              ),
+            ),
+            TextSpan(
+              text: 'Ta ',
+              style: TextStyle(
+                color: const Color(0xFF7D46FC),
+                fontSize: 24.sp,
+                fontWeight: FontWeight.w600,
+              ),
+            ),
+            TextSpan(
+              text: '对你到底是‘朋友’还是‘心动’?',
+              style: TextStyle(
+                color: Colors.black.withAlpha(153),
+                fontSize: 14.sp,
+                fontWeight: FontWeight.w500,
+              ),
+            ),
+          ],
+        ),
+      ),
+      Assets.anim.animIntroFirstData,
+    ),
+    PageBean(SizedBox(), Assets.anim.animIntroSecondData),
+  ];
+
+  @override
+  void onInit() {
+    super.onInit();
+    setFirstIntro(false);
+  }
+  @override
+  void onReady() {
+    super.onReady();
+    _resetTimer();
+  }
+  void _resetTimer() {
+    _autoPageTimer?.cancel();
+    _autoPageTimer = Timer.periodic(const Duration(seconds: 3), (timer) {
+      swiperIntro();
+    });
+  }
+
+
+  void swiperIntro() {
+    if (currentPage.value == pageList.length - 1) {
+      pageController.value.jumpToPage(0);
+
+    } else {
+      // 跳转到下一页
+
+      pageController.value.nextPage(
+        duration: const Duration(milliseconds: 500),
+        curve: Curves.easeInOut,
+      );
+      currentPage.value++;
+    }
+  }
+
+
+  void onPageChanged(int index) {
+
+    currentPage.value = index;
+    _resetTimer();
+  }
+
+  void clickLogin() {
+    Timer(const Duration(milliseconds: 500), () {
+      LoginDialog.show();
+    });
+  }
+
+  void clickCustomButton() {
+    Timer(const Duration(milliseconds: 500), () {
+      NewUserPage.start();
+    });
+  }
+
+  @override
+  void onClose() {
+    super.onClose();
+    _autoPageTimer?.cancel();
+  }
+
+
+}
+
+class PageBean {
+  Widget title;
+  String animUrl;
+
+  PageBean(this.title, this.animUrl);
+}

+ 197 - 0
lib/module/intro/intro_page.dart

@@ -0,0 +1,197 @@
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:keyboard/base/base_page.dart';
+import 'package:keyboard/module/intro/intro_controller.dart';
+import 'package:get/get.dart';
+import 'package:flutter/material.dart';
+import 'package:keyboard/resource/string.gen.dart';
+import 'package:lottie/lottie.dart';
+import 'package:nested_scroll_views/material.dart';
+
+import '../../resource/assets.gen.dart';
+import '../../resource/colors.gen.dart';
+import '../../router/app_pages.dart';
+
+class IntroPage extends BasePage<IntroController> {
+  const IntroPage({Key? key}) : super(key: key);
+
+  static void start() {
+    Get.toNamed(RoutePath.intro);
+  }
+
+  @override
+  bool immersive() {
+    return true;
+  }
+
+  @override
+  Widget buildBody(BuildContext context) {
+    return Stack(
+      children: [
+        Assets.images.bgIntro.image(width: double.infinity, fit: BoxFit.fill),
+        SizedBox(
+          width: double.infinity,
+          height: double.infinity,
+          child: SafeArea(
+            child: Stack(
+              children: [
+                Column(
+                  crossAxisAlignment: CrossAxisAlignment.center,
+                  children: [
+                    SizedBox(height: 42.w),
+                    Assets.images.iconIntroTitle.image(
+                      width: 189.w,
+                      height: 40.6.w,
+                      fit: BoxFit.contain,
+                    ),
+                    SizedBox(height: 20.w),
+                    Flexible(
+                      child:
+
+                          NestedPageView(
+                        controller: controller.pageController.value,
+                        onPageChanged: (index) {
+                          controller.onPageChanged(index);
+                        },
+                        children: List.generate(
+                          controller.pageList.length,
+                          (index) =>
+                              buildPage(controller.pageList[index], index),
+                        ),
+                      ),
+                    ),
+                  ],
+                ),
+                Column(
+                  mainAxisAlignment: MainAxisAlignment.end,
+                  children: [
+                    buildIndicator(),
+                    SizedBox(height: 50.w),
+                    _buildCustomButton(),
+                    SizedBox(height: 25.w),
+                    _buildGoToLoginButton(),
+                    SizedBox(height: 50.w),
+                  ],
+                ),
+              ],
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+
+  Widget buildIndicator() {
+    return Obx(() {
+      int totalPages = controller.pageList.length;
+      int currentPage = controller.currentPage.value;
+
+      return Container(
+        width: 50.w,
+        height: 10.h,
+        decoration: BoxDecoration(
+          color: const Color(0xF0F4EEFF),
+          borderRadius: BorderRadius.circular(5.r),
+        ),
+        padding: EdgeInsets.all(2.w),
+
+        child: Align(
+          alignment: Alignment.centerLeft,
+          child: AnimatedContainer(
+            duration: Duration(milliseconds: 300),
+            width: (50.w - 4.w) / totalPages,
+
+            height: double.infinity,
+            margin: EdgeInsets.only(
+              left: ((50.w - 4.w) / totalPages) * currentPage,
+            ),
+            decoration: BoxDecoration(
+              color: const Color(0xffa084ff),
+              borderRadius: BorderRadius.circular(3.r),
+            ),
+          ),
+        ),
+      );
+    });
+  }
+
+  Widget buildPage(PageBean pageBean, int index) {
+    return  Column(
+        mainAxisSize: MainAxisSize.min,
+        crossAxisAlignment: CrossAxisAlignment.center,
+        mainAxisAlignment: MainAxisAlignment.start,
+        children: [
+          pageBean.title,
+          SizedBox(height: 20.h),
+
+            Lottie.asset(
+              pageBean.animUrl,
+              width: 360.w,
+              repeat: true,
+              fit: BoxFit.contain,
+            ),
+        ],
+      );
+
+  }
+
+  Widget _buildCustomButton() {
+    return Container(
+      height: 48.w,
+      margin: EdgeInsets.only(left: 50.w, right: 50.w),
+      child: Material(
+        color: Colors.transparent,
+        borderRadius: BorderRadius.circular(50.r),
+        child: InkWell(
+          borderRadius: BorderRadius.circular(50.r),
+          onTap: () {
+            controller.clickCustomButton();
+          },
+          child: Ink(
+            decoration: BoxDecoration(
+              gradient: LinearGradient(
+                begin: Alignment.centerLeft,
+                end: Alignment.centerRight,
+                transform: GradientRotation(0.5),
+                colors: [Color(0xFF7D46FC), Color(0xFFBC87FF)],
+              ),
+              borderRadius: BorderRadius.circular(50.r),
+              boxShadow: [
+                BoxShadow(
+                  color: Color(0x66BDA8C9),
+                  blurRadius: 10.r,
+                  offset: Offset(0, 4),
+                  spreadRadius: 0,
+                ),
+              ],
+            ),
+            child: Center(
+              child: Text(
+                StringName.customLoveKeyboard,
+                textAlign: TextAlign.center,
+                style: TextStyle(
+                  color: Colors.white,
+                  fontSize: 16.sp,
+                  fontWeight: FontWeight.w500,
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+
+  _buildGoToLoginButton() {
+    return InkWell(
+      onTap: controller.clickLogin,
+      child: Text(
+        StringName.goToLogin,
+        style: TextStyle(
+          color: const Color(0xFF8649FF),
+          fontSize: 14.sp,
+          fontWeight: FontWeight.w500,
+        ),
+      ),
+    );
+  }
+}

+ 5 - 0
lib/module/keyboard/keyboard_controller.dart

@@ -14,6 +14,7 @@ import 'package:keyboard/module/store/store_page.dart';
 import '../../data/api/response/keyboard_love_index_response.dart';
 import '../../utils/atmob_log.dart';
 import '../intimacy_analyse/intimacy_analyse_page.dart';
+import '../intimacy_scale/intimacy_scale_page.dart';
 import '../keyboard_guide/keyboard_guide_page.dart';
 import '../profile/profile_page.dart';
 
@@ -87,6 +88,10 @@ class KeyBoardController extends BaseController {
     NewDiscountPage.start();
   }
 
+  void clickLovePercentage(){
+
+    IntimacyScalePage.start();
+  }
   void startCountdown() {
     _timer = Timer.periodic(const Duration(milliseconds: 10), (timer) {
       if (timeLeft.value > 0) {

+ 67 - 54
lib/module/keyboard/keyboard_view.dart

@@ -10,6 +10,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/heart_fill_view.dart';
 import '../../widget/pargress_bar.dart';
 import 'keyboard_controller.dart';
 
@@ -146,9 +147,9 @@ class KeyBoardView extends BaseView<KeyBoardController> {
               mainAxisAlignment: MainAxisAlignment.spaceBetween,
               children: [
                 _buildAvatar(true),
-                const SizedBox(width: 16),
+                SizedBox(width: 16.w),
                 _buildLovePercentage(),
-                const SizedBox(width: 16),
+                SizedBox(width: 16.w),
                 _buildAvatar(false),
               ],
             ),
@@ -288,7 +289,7 @@ class KeyBoardView extends BaseView<KeyBoardController> {
                 ],
               ),
               Container(
-                padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 4.w),
+                padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.w),
                 decoration: BoxDecoration(
                   color: Colors.white,
                   borderRadius: BorderRadius.circular(22.r),
@@ -353,63 +354,75 @@ class KeyBoardView extends BaseView<KeyBoardController> {
               ),
             ),
             SizedBox(height: 7.h),
-            SizedBox(
-              width: 88.w,
-              height: 72.h,
-              child: Stack(
-                children: [
-                  Assets.images.bgKeyboardLove.image(width: 88.w, height: 72.h),
-                  Positioned.fill(
-                    child: Center(
-                      child: Obx(
-                        () => Text.rich(
-                          TextSpan(
-                            children: [
-                              TextSpan(
-                                text:
-                                    controller.homeInfo?.intimacy != null
-                                        ? controller.homeInfo?.intimacy
-                                            .toString()
-                                        : "?",
-                                style: TextStyle(
-                                  color: Colors.white,
-                                  fontSize: 33.sp,
-                                  fontWeight: FontWeight.w700,
-                                  shadows: [
-                                    Shadow(
-                                      offset: Offset(0, 1),
-                                      blurRadius: 4.r,
-                                      color: const Color(
-                                        0xFFFF6BD3,
-                                      ).withValues(alpha: 0.63),
-                                    ),
-                                  ],
+            GestureDetector(
+              onTap: () {
+                controller.clickLovePercentage();
+              },
+              child: SizedBox(
+                width: 88.w,
+                height: 72.h,
+                child: Stack(
+                  children: [
+                    // Assets.images.bgKeyboardLove.image(width: 88.w, height: 72.h),
+                    HeartFillAnimation(
+                      fillProgress:
+                          controller.homeInfo?.intimacy != null
+                              ? controller.homeInfo!.intimacy! / 100
+                              : 0,
+                      width: 88.w,
+                    ),
+                    Positioned.fill(
+                      child: Center(
+                        child: Obx(
+                          () => Text.rich(
+                            TextSpan(
+                              children: [
+                                TextSpan(
+                                  text:
+                                      controller.homeInfo?.intimacy != null
+                                          ? controller.homeInfo?.intimacy
+                                              .toString()
+                                          : "?",
+                                  style: TextStyle(
+                                    color: Colors.white,
+                                    fontSize: 33.sp,
+                                    fontWeight: FontWeight.w700,
+                                    shadows: [
+                                      Shadow(
+                                        offset: Offset(0, 1),
+                                        blurRadius: 4.r,
+                                        color: const Color(
+                                          0xFFFF6BD3,
+                                        ).withValues(alpha: 0.63),
+                                      ),
+                                    ],
+                                  ),
                                 ),
-                              ),
-                              TextSpan(
-                                text: '%',
-                                style: TextStyle(
-                                  color: Colors.white,
-                                  fontSize: 14.sp,
-                                  fontWeight: FontWeight.w700,
-                                  shadows: [
-                                    Shadow(
-                                      offset: Offset(0, 1),
-                                      blurRadius: 4.r,
-                                      color: const Color(
-                                        0xFFFF6BD3,
-                                      ).withValues(alpha: 0.63),
-                                    ),
-                                  ],
+                                TextSpan(
+                                  text: '%',
+                                  style: TextStyle(
+                                    color: Colors.white,
+                                    fontSize: 14.sp,
+                                    fontWeight: FontWeight.w700,
+                                    shadows: [
+                                      Shadow(
+                                        offset: Offset(0, 1),
+                                        blurRadius: 4.r,
+                                        color: const Color(
+                                          0xFFFF6BD3,
+                                        ).withValues(alpha: 0.63),
+                                      ),
+                                    ],
+                                  ),
                                 ),
-                              ),
-                            ],
+                              ],
+                            ),
                           ),
                         ),
                       ),
                     ),
-                  ),
-                ],
+                  ],
+                ),
               ),
             ),
           ],

+ 48 - 6
lib/module/mine/mine_controller.dart

@@ -1,3 +1,5 @@
+import 'dart:io';
+
 import 'package:flutter/material.dart';
 import 'package:flutter/rendering.dart';
 import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@@ -14,12 +16,16 @@ import 'package:keyboard/utils/atmob_log.dart';
 
 import '../../data/bean/keyboard_info.dart';
 import '../../data/consts/build_config.dart';
+import '../../data/consts/constants.dart';
 import '../../data/consts/error_code.dart';
+import '../../data/consts/web_url.dart';
 import '../../data/repository/account_repository.dart';
 import '../../dialog/login/login_dialog.dart';
 import '../../plugins/keyboard_android_platform.dart';
 import '../../plugins/keyboard_method_handler.dart';
 import '../../resource/colors.gen.dart';
+import '../../utils/app_info_util.dart';
+import '../browser/browser_page.dart';
 import '../intimacy_analyse/intimacy_analyse_page.dart';
 import '../keyboard_guide/keyboard_guide_page.dart';
 import '../new_user/result/new_user_result_page.dart';
@@ -57,12 +63,43 @@ class MineController extends BaseController {
   }
 
   longClickVip() {
+    if (isLogin) {
+      UserInfoPage.start();
+    } else {
+      LoginDialog.show();
+    }
     KeyboardAndroidPlatform.enableFloatingWindow(true);
     KeyboardAndroidPlatform.openInputMethodSettings();
   }
 
   clickOnlineCustomerService() {
-    debugPrint('clickOnlineCustomerService');
+    if (isLogin) {
+      debugPrint('clickOnlineCustomerService');
+      goToCustomerService();
+    } else {
+      ToastUtil.show('请先登录');
+      LoginDialog.show();
+      return;
+    }
+  }
+
+  void goToCustomerService() {
+    final userInfo = accountRepository.userInfo.value;
+    if (userInfo == null) {
+      ToastUtil.show('网络异常,请稍后再试');
+      accountRepository.refreshUserInfo();
+      return;
+    }
+    int appPlatform = 0;
+    if (Platform.isAndroid) {
+      appPlatform = 1;
+    } else if (Platform.isIOS) {
+      appPlatform = 2;
+    }
+    //拼接字符串
+    String url =
+        "${WebUrl.qiyuService}?ssid=${userInfo.ssid}&device_id=${userInfo.deviceId}&app_platform=$appPlatform&app_version=${appInfoUtil.appVersionName}&package_name=${appInfoUtil.packageName}&app_name=${appInfoUtil.appName}";
+    BrowserPage.start(url);
   }
 
   clickUserCard() {
@@ -78,9 +115,7 @@ class MineController extends BaseController {
     NewUserPage.start();
   }
 
-  longClickTutorials() {
-    NewUserPage.start();
-  }
+
 
   clickPersonalProfile() {
     debugPrint('clickPersonalProfile');
@@ -88,9 +123,16 @@ class MineController extends BaseController {
   }
 
   clickFeedback() {
-    debugPrint('clickFeedback');
 
-    FeedbackPage.start();
+    if (isLogin) {
+      debugPrint('clickOnlineCustomerService');
+      FeedbackPage.start();
+    } else {
+      ToastUtil.show('请先登录');
+      LoginDialog.show();
+      return;
+    }
+
   }
 
   clickAboutUs() {

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

@@ -60,7 +60,7 @@ class MineView extends BaseView<MineController> {
             text: StringName.tutorials,
             funIcon: Assets.images.iconMineTutorials.path,
             onTap: controller.clickTutorials,
-            onLongTap: controller.longClickTutorials
+
           ),
 
           baseFunctionButton(

+ 1 - 1
lib/module/new_user/new_user_controller.dart

@@ -64,7 +64,7 @@ class NewUserController extends BaseController
   @override
   void onInit() {
     super.onInit();
-    setFirstIntro(false);
+
     updateAvatarListsAndSelectFirst(currentDefaultAvatarInfo.value);
 
     ever<DefaultAvatarInfo?>(currentDefaultAvatarInfo, (info) {

+ 84 - 86
lib/module/new_user/step/intimacy/step_intimacy_stages_view.dart

@@ -49,37 +49,35 @@ class StepIntimacyStagesView extends BasePage<NewUserController> {
             ),
           ),
           SizedBox(height: 40.w),
-          Row(
-            mainAxisAlignment: MainAxisAlignment.spaceBetween,
-            children: [
-              CachedNetworkImage(
-                imageUrl:
-                    controller
-                        .intimacyList?[controller.selectedIndex.value]
-                        .iconUrl ??
-                    "",
-                width: 211.w,
-                height: 339.w,
-                fit: BoxFit.contain,
-                placeholder:
-                    (context, url) =>
-                        Assets.images.iconNewUserStagesAmbiguous.image(
-                          width: 211.w,
-                          height: 339.w,
-                          fit: BoxFit.contain,
-                        ),
-                errorWidget:
-                    (_, __, ___) =>
-                        Assets.images.iconNewUserStagesAmbiguous.image(
-                          width: 211.w,
-                          height: 339.w,
-                          fit: BoxFit.contain,
-                        ),
-              ),
+          Obx(() {
+            return Row(
+              mainAxisAlignment: MainAxisAlignment.spaceBetween,
+              children: [
+                CachedNetworkImage(
+                  imageUrl:
+                  controller
+                      .intimacyList?[controller.selectedIndex.value]
+                      .iconUrl ??
+                      "",
+                  width: 192.w,
+                  height: 300.w,
+                  fit: BoxFit.contain,
+                  placeholder:
+                      (context, url) =>
+                          SizedBox(),
+                  errorWidget:
+                      (_, __, ___) =>
+                      Assets.images.iconNewUserStagesAmbiguous.image(
+                        width: 192.w,
+                        height: 300.w,
+                        fit: BoxFit.contain,
+                      ),
+                ),
 
-              _buildStagesList(),
-            ],
-          ),
+                _buildStagesList(),
+              ],
+            );
+          }),
         ],
       ),
     );
@@ -92,64 +90,64 @@ class StepIntimacyStagesView extends BasePage<NewUserController> {
         return Column(
           mainAxisSize: MainAxisSize.min,
           children:
-              (controller.intimacyList == null ||
-                      controller.intimacyList!.isEmpty)
-                  ? [SizedBox.shrink()]
-                  : List.generate(controller.intimacyList!.length, (index) {
-                    final isSelected = controller.selectedIndex.value == index;
-                    return GestureDetector(
-                      onTap: () => controller.selectIndex(index),
-                      child: Container(
-                        height: 64.h,
-                        width: 120.w,
-                        alignment: Alignment.center,
-                        child: AnimatedScale(
-                          scale: isSelected ? 1.0 : 0.82,
-                          duration: const Duration(milliseconds: 250),
-                          curve: Curves.easeOutCubic,
-                          child: Container(
-                            alignment: Alignment.center,
-                            decoration: BoxDecoration(
-                              color: isSelected ? null : Colors.white,
-                              gradient:
-                                  isSelected
-                                      ? LinearGradient(
-                                        begin: Alignment(0.04, 0.21),
-                                        end: Alignment(0.98, 0.76),
-                                        colors: [
-                                          const Color(0xFF7D46FC),
-                                          const Color(0xFFBC87FF),
-                                        ],
-                                      )
-                                      : null,
-                              boxShadow:
-                                  isSelected
-                                      ? [
-                                        BoxShadow(
-                                          color: const Color(0xFFCEBEFF),
-                                          blurRadius: 5.80,
-                                          offset: Offset(0, 4),
-                                        ),
-                                      ]
-                                      : [],
-                              borderRadius: BorderRadius.circular(17.r),
-                            ),
-                            child: Text(
-                              controller.intimacyList?[index].name ?? "",
-                              style: TextStyle(
-                                color:
-                                    isSelected
-                                        ? Colors.white
-                                        : Colors.black.withAlpha(153),
-                                fontSize: 17.14.sp,
-                                fontWeight: FontWeight.w500,
-                              ),
-                            ),
-                          ),
+          (controller.intimacyList == null ||
+              controller.intimacyList!.isEmpty)
+              ? [SizedBox.shrink()]
+              : List.generate(controller.intimacyList!.length, (index) {
+            final isSelected = controller.selectedIndex.value == index;
+            return GestureDetector(
+              onTap: () => controller.selectIndex(index),
+              child: Container(
+                height: 64.h,
+                width: 120.w,
+                alignment: Alignment.center,
+                child: AnimatedScale(
+                  scale: isSelected ? 1.0 : 0.82,
+                  duration: const Duration(milliseconds: 250),
+                  curve: Curves.easeOutCubic,
+                  child: Container(
+                    alignment: Alignment.center,
+                    decoration: BoxDecoration(
+                      color: isSelected ? null : Colors.white,
+                      gradient:
+                      isSelected
+                          ? LinearGradient(
+                        begin: Alignment(0.04, 0.21),
+                        end: Alignment(0.98, 0.76),
+                        colors: [
+                          const Color(0xFF7D46FC),
+                          const Color(0xFFBC87FF),
+                        ],
+                      )
+                          : null,
+                      boxShadow:
+                      isSelected
+                          ? [
+                        BoxShadow(
+                          color: const Color(0xFFCEBEFF),
+                          blurRadius: 5.80,
+                          offset: Offset(0, 4),
                         ),
+                      ]
+                          : [],
+                      borderRadius: BorderRadius.circular(17.r),
+                    ),
+                    child: Text(
+                      controller.intimacyList?[index].name ?? "",
+                      style: TextStyle(
+                        color:
+                        isSelected
+                            ? Colors.white
+                            : Colors.black.withAlpha(153),
+                        fontSize: 17.14.sp,
+                        fontWeight: FontWeight.w500,
                       ),
-                    );
-                  }),
+                    ),
+                  ),
+                ),
+              ),
+            );
+          }),
         );
       }),
     );

+ 16 - 7
lib/module/new_user/step/partner/step_partner_view.dart

@@ -159,6 +159,7 @@ class StepPartnerView extends BaseView<NewUserController> {
                     child: buildGenderSwitchButton(
                       isSelected: controller.partnerGender == 2,
                       text: StringName.newUserPartnerIsFemale,
+                      genderIcon: Assets.images.iconFamateBlack,
                       onTap: () => controller.onPartnerGenderChanged(2),
                     ),
                   ),
@@ -166,6 +167,7 @@ class StepPartnerView extends BaseView<NewUserController> {
                   // 男生按钮
                   Expanded(
                     child: buildGenderSwitchButton(
+                      genderIcon: Assets.images.iconMaleWhite,
                       isSelected: controller.partnerGender == 1,
                       text: StringName.newUserPartnerIsMale,
                       onTap: () => controller.onPartnerGenderChanged(1),
@@ -214,7 +216,7 @@ class StepPartnerView extends BaseView<NewUserController> {
               ),
               maxLines: 1,
               textAlignVertical: TextAlignVertical.center,
-              textInputAction: TextInputAction.next,
+
               decoration: InputDecoration(
                 hintText: StringName.newUserNicknameTitle,
                 counterText: '',
@@ -254,6 +256,7 @@ class StepPartnerView extends BaseView<NewUserController> {
     required String text,
     required bool isSelected,
     required VoidCallback onTap,
+    required AssetGenImage genderIcon,
   }) {
     return GestureDetector(
       onTap: onTap,
@@ -273,12 +276,18 @@ class StepPartnerView extends BaseView<NewUserController> {
                   )
                   : null,
         ),
-        child: Text(
-          text,
-          style:
-              isSelected
-                  ? Styles.getTextStyleWhiteW500(14.sp)
-                  : Styles.getTextStyleBlack204W500(14.sp),
+        child: Row(
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: [
+            genderIcon.image(width: 24.w,height: 24.w,color: isSelected ? Colors.white : Colors.black),
+            Text(
+              text,
+              style:
+                  isSelected
+                      ? Styles.getTextStyleWhiteW500(14.sp)
+                      : Styles.getTextStyleBlack204W500(14.sp),
+            ),
+          ],
         ),
       ),
     );

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

@@ -3,6 +3,8 @@ import 'package:injectable/injectable.dart';
 import 'package:keyboard/base/base_controller.dart';
 import 'package:keyboard/data/repository/account_repository.dart';
 import 'package:keyboard/module/profile/edit/profile_edit_page.dart';
+import 'package:keyboard/module/user_info/user_info_page.dart';
+import 'package:keyboard/module/user_profile/user_profile_page.dart';
 import 'package:keyboard/utils/atmob_log.dart';
 import 'package:get/get.dart';
 import '../../data/api/response/user_info_response.dart';
@@ -103,6 +105,8 @@ class ProfileController extends BaseController {
       if (result == true) {
         await getCustomKeyboard();
       }
+    }else{
+      UserProfilePage.start();
     }
   }
 }

+ 12 - 1
lib/module/splash/splash_controller.dart

@@ -4,12 +4,15 @@ import 'dart:io';
 import 'package:flutter/services.dart';
 import 'package:get/get_utils/src/platform/platform.dart';
 import 'package:injectable/injectable.dart';
+import 'package:keyboard/module/intro/intro_page.dart';
 import 'package:keyboard/module/new_user/new_user_page.dart';
+import 'package:lottie/lottie.dart';
 
 import '../../base/base_controller.dart';
 import '../../data/consts/constants.dart';
 import '../../dialog/agreement_again_dialog.dart';
 import '../../dialog/agreement_dialog.dart';
+import '../../resource/assets.gen.dart';
 import '../../utils/privacy_compliance.dart';
 import '../main/main_page.dart';
 
@@ -18,9 +21,16 @@ class SplashController extends BaseController {
   final splashDelayedTime = 2;
 
 
+  @override
+  Future<void> onInit() async {
+    super.onInit();
+    await AssetLottie(Assets.anim.animIntroFirstData).load();
+    await AssetLottie(Assets.anim.animIntroSecondData).load();
+  }
 
   @override
   void onReady() {
+
     final isAgreePrivacy = PrivacyCompliance.isAgreePrivacyPolicy();
     if (isAgreePrivacy) {
       isAgreePrivacyNextStep();
@@ -48,8 +58,9 @@ class SplashController extends BaseController {
 
   void _goMain(Duration delayTime, {Map<String, dynamic>? arguments}) {
     Timer(delayTime, () {
+
       if (isFirstIntro()) {
-        NewUserPage.start();
+        IntroPage.start();
       }else{
         MainPage.start(arguments: arguments);
       }

+ 2 - 4
lib/module/store/new_discount/new_discount_controller.dart

@@ -526,11 +526,9 @@ class NewDiscountController extends BaseController
     GoodsInfo goodsInfo,
   ) {
     LoadingDialog.hide();
+    AtmobLog.d(tag, 'onPaymentSuccess: $orderNo');
     PaymentSuccessDialog.show(
-      infoText:
-          memberStatusInfo?.permanent == true
-              ? StringName.paySuccessDialogPermanent
-              : "${goodsInfo.name}${DateUtil.fromMillisecondsSinceEpoch('yyyy年MM月dd日', memberStatusInfo?.endTimestamp ?? 0)}${StringName.paySuccessDialogDesc}",
+      goodsInfo: goodsInfo,
       btnConfirm: () {
         AtmobLog.d(tag, 'onGoodsItemClick: ${goodsInfo.toJson()}');
       },

+ 2 - 4
lib/module/store/store_controller.dart

@@ -482,11 +482,9 @@ class StoreController extends BaseController implements PaymentStatusCallback {
     GoodsInfo goodsInfo,
   ) {
     LoadingDialog.hide();
+
     PaymentSuccessDialog.show(
-      infoText:
-          memberStatusInfo?.permanent == true
-              ? StringName.paySuccessDialogPermanent
-              : "${goodsInfo.name}${DateUtil.fromMillisecondsSinceEpoch('yyyy年MM月dd日', memberStatusInfo?.endTimestamp ?? 0)}${StringName.paySuccessDialogDesc}",
+      goodsInfo: goodsInfo,
       btnConfirm: () {
         AtmobLog.d(tag, 'onGoodsItemClick: ${goodsInfo.toJson()}');
       },

+ 37 - 1
lib/module/user_profile/user_profile_controller.dart

@@ -2,6 +2,7 @@ import 'package:injectable/injectable.dart';
 import 'package:keyboard/base/base_controller.dart';
 import 'package:get/get.dart';
 import 'package:keyboard/data/repository/account_repository.dart';
+import 'package:keyboard/data/repository/keyboard_repository.dart';
 
 import '../../data/api/response/user_info_response.dart';
 import '../../data/bean/default_avatar_info.dart';
@@ -9,6 +10,8 @@ import '../../data/repository/config_repository.dart';
 import '../../resource/string.gen.dart';
 import '../../utils/age_zodiac_sign_util.dart';
 import '../../utils/atmob_log.dart';
+import '../../utils/http_handler.dart';
+import '../../utils/toast_util.dart';
 import '../change/birthday/change_birthday_page.dart';
 import '../change/gender/change_gender_page.dart';
 import '../change/nickname/change_nickname_page.dart';
@@ -22,6 +25,9 @@ class UserProfileController extends BaseController {
 
   final AccountRepository accountRepository;
 
+  final KeyboardRepository keyboardRepository;
+
+
   final RxList<String> _girlAvatars = <String>[].obs;
 
   final RxList<String> _boyAvatars = <String>[].obs;
@@ -51,7 +57,7 @@ class UserProfileController extends BaseController {
   Rxn<DefaultAvatarInfo> get currentDefaultAvatarInfo =>
       configRepository.defaultAvatarInfo;
 
-  UserProfileController(this.configRepository, this.accountRepository);
+  UserProfileController(this.configRepository, this.accountRepository,this.keyboardRepository);
 
   @override
   void onInit() {
@@ -94,6 +100,8 @@ class UserProfileController extends BaseController {
       _userAvatarUrl.value =
           _girlAvatars[(currentIndex + 1) % _girlAvatars.length];
     }
+    _settingUseInfo();
+
   }
 
   void clickBirthday() async {
@@ -104,6 +112,7 @@ class UserProfileController extends BaseController {
     if (result != null) {
       AtmobLog.d(tag, 'clickBirthday result: $result');
       _currentBirthday.value = result;
+      _settingUseInfo();
     }
   }
 
@@ -124,8 +133,10 @@ class UserProfileController extends BaseController {
 
     if (result == 1 && _boyAvatars.isNotEmpty) {
       _userAvatarUrl.value = _boyAvatars.first;
+      _settingUseInfo();
     } else if (_girlAvatars.isNotEmpty) {
       _userAvatarUrl.value = _girlAvatars.first;
+      _settingUseInfo();
     } else {
       _userAvatarUrl.value = "";
     }
@@ -138,9 +149,34 @@ class UserProfileController extends BaseController {
     );
     if (result != null) {
       _currentNickname.value = result;
+      _settingUseInfo();
+    }
+  }
+
+  Future<void> _settingUseInfo() async {
+    await Future.delayed(const Duration(milliseconds: 300));
+    try {
+      accountRepository.setUserInfo(
+        name: _currentNickname.value,
+        birthday:
+        _currentBirthday.value,
+        gender: _currentGender.value,
+        imageUrl: _userAvatarUrl.value,
+      );
+      await Future.delayed(const Duration(milliseconds: 300));
+      accountRepository.refreshUserInfo();
+      keyboardRepository.refreshData();
+    } catch (error) {
+      if (error is ServerErrorException) {
+        ToastUtil.show(error.message);
+      } else {
+        AtmobLog.i(tag, error.toString());
+      }
     }
   }
 
+
+
   String get genderText {
     if (_currentGender.value == 1) return '男';
     if (_currentGender.value == 2) return '女';

+ 2 - 1
lib/module/user_profile/user_profile_page.dart

@@ -186,7 +186,8 @@ class UserProfilePage extends BasePage<UserProfileController> {
             StringName.personalProfile,
             style: Styles.getTextStyleBlack204W500(17.sp),
           ),
-          SizedBox(),
+          SizedBox( width: 24.w,
+            height: 24.w,),
         ],
       ),
     );

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

@@ -20,6 +20,16 @@ class $AssetsAnimGen {
   String get animIntimacyAnalyseCreatingReportData =>
       'assets/anim/anim_intimacy_analyse_creating_report_data.json';
 
+  /// File path: assets/anim/anim_intro_first_data.json
+  String get animIntroFirstData => 'assets/anim/anim_intro_first_data.json';
+
+  /// File path: assets/anim/anim_intro_second_data.json
+  String get animIntroSecondData => 'assets/anim/anim_intro_second_data.json';
+
+  /// File path: assets/anim/anim_keyboard_generating_data.json
+  String get animKeyboardGeneratingData =>
+      'assets/anim/anim_keyboard_generating_data.json';
+
   /// File path: assets/anim/anim_new_user_data.json
   String get animNewUserData => 'assets/anim/anim_new_user_data.json';
 
@@ -46,6 +56,9 @@ class $AssetsAnimGen {
   List<String> get values => [
     animDiscountTicketDialogData,
     animIntimacyAnalyseCreatingReportData,
+    animIntroFirstData,
+    animIntroSecondData,
+    animKeyboardGeneratingData,
     animNewUserData,
     animSurpriseDialogData,
     animTabCharacterSelectedData,
@@ -157,6 +170,10 @@ class $AssetsImagesGen {
   AssetGenImage get bgIntimacyAnalyseUserName =>
       const AssetGenImage('assets/images/bg_intimacy_analyse_user_name.webp');
 
+  /// File path: assets/images/bg_intro.webp
+  AssetGenImage get bgIntro =>
+      const AssetGenImage('assets/images/bg_intro.webp');
+
   /// File path: assets/images/bg_keyboard.webp
   AssetGenImage get bgKeyboard =>
       const AssetGenImage('assets/images/bg_keyboard.webp');
@@ -292,6 +309,10 @@ class $AssetsImagesGen {
   AssetGenImage get iconAlipayScanPayment =>
       const AssetGenImage('assets/images/icon_alipay_scan_payment.webp');
 
+  /// File path: assets/images/icon_app_logo.webp
+  AssetGenImage get iconAppLogo =>
+      const AssetGenImage('assets/images/icon_app_logo.webp');
+
   /// File path: assets/images/icon_aquarius.webp
   AssetGenImage get iconAquarius =>
       const AssetGenImage('assets/images/icon_aquarius.webp');
@@ -577,6 +598,10 @@ class $AssetsImagesGen {
   AssetGenImage get iconEmojiPercent =>
       const AssetGenImage('assets/images/icon_emoji_percent.webp');
 
+  /// File path: assets/images/icon_famate_black.webp
+  AssetGenImage get iconFamateBlack =>
+      const AssetGenImage('assets/images/icon_famate_black.webp');
+
   /// File path: assets/images/icon_gemini.webp
   AssetGenImage get iconGemini =>
       const AssetGenImage('assets/images/icon_gemini.webp');
@@ -651,6 +676,14 @@ class $AssetsImagesGen {
     'assets/images/icon_intimacy_analysis_result_top.webp',
   );
 
+  /// File path: assets/images/icon_intimacy_scale_mark.webp
+  AssetGenImage get iconIntimacyScaleMark =>
+      const AssetGenImage('assets/images/icon_intimacy_scale_mark.webp');
+
+  /// File path: assets/images/icon_intro_title.webp
+  AssetGenImage get iconIntroTitle =>
+      const AssetGenImage('assets/images/icon_intro_title.webp');
+
   /// File path: assets/images/icon_keyboard_banner.webp
   AssetGenImage get iconKeyboardBanner =>
       const AssetGenImage('assets/images/icon_keyboard_banner.webp');
@@ -770,6 +803,10 @@ class $AssetsImagesGen {
     'assets/images/icon_login_dialog_wechat_logo_white.webp',
   );
 
+  /// File path: assets/images/icon_male_white.webp
+  AssetGenImage get iconMaleWhite =>
+      const AssetGenImage('assets/images/icon_male_white.webp');
+
   /// File path: assets/images/icon_member_retain_close.webp
   AssetGenImage get iconMemberRetainClose =>
       const AssetGenImage('assets/images/icon_member_retain_close.webp');
@@ -1161,6 +1198,7 @@ class $AssetsImagesGen {
     bgIntimacyAnalyseReportPreviewMask,
     bgIntimacyAnalyseUploadCard,
     bgIntimacyAnalyseUserName,
+    bgIntro,
     bgKeyboard,
     bgKeyboardEasyReply,
     bgKeyboardIntimacyAnalyze,
@@ -1195,6 +1233,7 @@ class $AssetsImagesGen {
     iconAiModel,
     iconAlipayPayment,
     iconAlipayScanPayment,
+    iconAppLogo,
     iconAquarius,
     iconAries,
     iconArrowRight,
@@ -1262,6 +1301,7 @@ class $AssetsImagesGen {
     iconEmojiLike,
     iconEmojiLove,
     iconEmojiPercent,
+    iconFamateBlack,
     iconGemini,
     iconGoodsInfoTitle,
     iconImageViewerClose,
@@ -1277,6 +1317,8 @@ class $AssetsImagesGen {
     iconIntimacyAnalyseUploadTop,
     iconIntimacyAnalysisReportCreating,
     iconIntimacyAnalysisResultTop,
+    iconIntimacyScaleMark,
+    iconIntroTitle,
     iconKeyboardBanner,
     iconKeyboardBannerClose,
     iconKeyboardCurrentCharacterTitle,
@@ -1306,6 +1348,7 @@ class $AssetsImagesGen {
     iconLoginDialogClose,
     iconLoginDialogPhoneLogo,
     iconLoginDialogWechatLogoWhite,
+    iconMaleWhite,
     iconMemberRetainClose,
     iconMineAbout,
     iconMineArrow,

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

@@ -12,7 +12,7 @@ class StringName {
   static final String feedback = 'feedback'.tr; // 意见反馈
   static final String aboutUs = 'about_us'.tr; // 关于我们
   static final String mineAccountLoggedDesc = 'mine_account_logged_desc'.tr; // 用户
-  static final String mineAccountNoLogin = 'mine_account_no_login'.tr; // 游客,去登
+  static final String mineAccountNoLogin = 'mine_account_no_login'.tr; // 游客,去登
   static final String directSend = 'direct_send'.tr; // 生成内容直接发送
   static final String directSendDesc = 'direct_send_desc'.tr; // 内容生成后,点击发送按钮,直接发送
   static final String openFloat = 'open_float'.tr; // 打开悬浮窗
@@ -64,6 +64,8 @@ class StringName {
   static final String privacyPolicy = 'privacy_policy'.tr; // 隐私政策
   static final String serviceTerms = 'service_terms'.tr; // 服务条款
   static final String childPrivacyPolicy = 'child_privacy_policy'.tr; // 儿童隐私保护协议
+  static final String personalInfoProtection = 'personal_info_protection'.tr; // 个人信息收集清单
+  static final String thirdPartyList = 'third_party_list'.tr; // 第三方SDK共享清单
   static final String goCustomizeCharacter = 'go_customize_character'.tr; // 去定制人设
   static final String goCustomizeCharacterDesc = 'go_customize_character_desc'.tr; // 定制你自己的专属人设
   static final String myKeyboard = 'my_keyboard'.tr; // 我的键盘
@@ -312,6 +314,9 @@ class StringName {
   static final String privacyDisagree = 'privacy_disagree'.tr; // 不同意
   static final String privacyDisagreeAndExit = 'privacy_disagree_and_exit'.tr; // 不同意并退出
   static final String privacyAgree = 'privacy_agree'.tr; // 同意
+  static final String customLoveKeyboard = 'custom_love_keyboard'.tr; // 定制恋爱键盘
+  static final String goToLogin = 'go_to_login'.tr; // 去登录
+  static final String intimacyIndex = 'intimacy_index'.tr; // 亲密指数
 }
 class StringMultiSource {
   StringMultiSource._();
@@ -327,7 +332,7 @@ class StringMultiSource {
       'feedback': '意见反馈',
       'about_us': '关于我们',
       'mine_account_logged_desc': '用户',
-      'mine_account_no_login': '游客,去登',
+      'mine_account_no_login': '游客,去登',
       'direct_send': '生成内容直接发送',
       'direct_send_desc': '内容生成后,点击发送按钮,直接发送',
       'open_float': '打开悬浮窗',
@@ -379,6 +384,8 @@ class StringMultiSource {
       'privacy_policy': '隐私政策',
       'service_terms': '服务条款',
       'child_privacy_policy': '儿童隐私保护协议',
+      'personal_info_protection': '个人信息收集清单',
+      'third_party_list': '第三方SDK共享清单',
       'go_customize_character': '去定制人设',
       'go_customize_character_desc': '定制你自己的专属人设',
       'my_keyboard': '我的键盘',
@@ -627,6 +634,9 @@ class StringMultiSource {
       'privacy_disagree': '不同意',
       'privacy_disagree_and_exit': '不同意并退出',
       'privacy_agree': '同意',
+      'custom_love_keyboard': '定制恋爱键盘',
+      'go_to_login': '去登录',
+      'intimacy_index': '亲密指数',
     },
   };
 }

+ 14 - 0
lib/router/app_pages.dart

@@ -15,6 +15,7 @@ import 'package:keyboard/module/character_custom/list/character_custom_list_cont
 import 'package:keyboard/module/feedback/feedback_controller.dart';
 import 'package:keyboard/module/intimacy_analyse/intimacy_analyse_controller.dart';
 import 'package:keyboard/module/intimacy_analyse/intimacy_analyse_page.dart';
+import 'package:keyboard/module/intimacy_scale/intimacy_scale_controller.dart';
 import 'package:keyboard/module/keyboard/keyboard_controller.dart';
 import 'package:keyboard/module/keyboard_manage/keyboard_manage_controller.dart';
 import 'package:keyboard/module/login/login_controller.dart';
@@ -54,6 +55,9 @@ import '../module/intimacy_analyse/intimacy_analyse_upload/intimacy_analyse_uplo
 import '../module/intimacy_analyse/screenshot_reply/conversation_analysis/conversation_analysis_controller.dart';
 import '../module/intimacy_analyse/screenshot_reply/intimacy_analyse_screenshot_reply_controller.dart';
 import '../module/intimacy_analyse/screenshot_reply/scan_image_reply/scan_image_reply_controller.dart';
+import '../module/intimacy_scale/intimacy_scale_page.dart';
+import '../module/intro/intro_controller.dart';
+import '../module/intro/intro_page.dart';
 import '../module/keyboard_guide/keyboard_guide_controller.dart';
 import '../module/keyboard_guide/keyboard_guide_page.dart';
 import '../module/keyboard_manage/keyboard_manage_page.dart';
@@ -117,6 +121,12 @@ abstract class RoutePath {
   static const newDiscount = '/newDiscount';
 
   static const zodiacLoveIntimacy = '/zodiacLoveIntimacy';
+
+  //   介绍页
+  static const intro = '/intro';
+
+//   亲密度刻度调节
+  static const intimacyScale = '/intimacyScale';
 }
 
 class AppBinding extends Bindings {
@@ -161,9 +171,11 @@ class AppBinding extends Bindings {
     lazyPut(() => getIt.get<CustomDirectionEditController>());
     lazyPut(() => getIt.get<UserProfileController>());
     lazyPut(() => getIt.get<NewDiscountController>());
+    lazyPut(() => getIt.get<IntroController>());
 
     lazyPut(() => getIt.get<IntimacyGenerateCharacterEditController>());
     lazyPut(() => getIt.get<ZodiacLoveIntimacyController>());
+    lazyPut(() => getIt.get<IntimacyScaleController>());
   }
 
   void lazyPut<S>(InstanceBuilderCallback<S> builder) {
@@ -219,4 +231,6 @@ final generalPages = [
     name: RoutePath.zodiacLoveIntimacy,
     page: () => ZodiacLoveIntimacyPage(),
   ),
+  GetPage(name: RoutePath.intro, page: () => IntroPage()),
+  GetPage(name: RoutePath.intimacyScale, page: () => IntimacyScalePage()),
 ];

+ 3 - 3
lib/utils/intimacy_util.dart

@@ -17,14 +17,14 @@ class IntimacyUtil {
   /// 获取亲密度名称
   static String getIntimacyName(int intimacy) {
     final levels = _configInfo?.intimacy;
-    if (levels == null || levels.isEmpty) return "未知";
+    if (levels == null || levels.isEmpty) return "?";
 
     for (final level in levels) {
       if (intimacy >= (level.min ?? 0) && intimacy <= (level.max ?? 0)) {
-        return level.name ?? "未知";
+        return level.name ?? "?";
       }
     }
-    return "未知";
+    return "?";
   }
 
 

+ 383 - 0
lib/widget/flutter_ruler_picker.dart

@@ -0,0 +1,383 @@
+import 'dart:math';
+
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+
+/// a triangle painter
+class _TrianglePainter extends CustomPainter {
+  // final double lineSize;
+
+  // _TrianglePainter({this.lineSize = 16});
+  @override
+  void paint(Canvas canvas, Size size) {
+    Path path = Path();
+    path.moveTo(0, 0);
+    path.lineTo(size.width, 0);
+    path.lineTo(size.width / 2, tan(pi / 3) * size.width / 2);
+    path.close();
+    Paint paint = Paint();
+    paint.color = const Color.fromARGB(255, 118, 165, 248);
+    paint.style = PaintingStyle.fill;
+    canvas.drawPath(path, paint);
+  }
+
+  @override
+  bool shouldRepaint(CustomPainter oldDelegate) {
+    return false;
+  }
+}
+
+/// The controller for the ruler picker
+/// init the ruler value from the controller
+/// 用于 RulerPicker 的控制器,可以在构造函数里初始化默认值
+class RulerPickerController extends ValueNotifier<num> {
+  RulerPickerController({num value = 0}) : super(value);
+  num get value => super.value;
+  set value(num newValue) {
+    super.value = newValue;
+  }
+}
+
+typedef void ValueChangedCallback(num value);
+
+/// RulerPicker 标尺选择器
+/// [width] 必须是具体的值,包括父级container的width,不能是 double.infinity,
+/// 可以传入MediaQuery.of(context).size.width
+class RulerPicker extends StatefulWidget {
+  final ValueChangedCallback onValueChanged;
+  final String Function(int index, num rulerScaleValue) onBuildRulerScaleText;
+  final double width;
+  final double height;
+  final TextStyle rulerScaleTextStyle;
+  final List<ScaleLineStyle> scaleLineStyleList;
+  final List<RulerRange> ranges;
+  final Widget? marker;
+  final double rulerMarginTop;
+  final Color rulerBackgroundColor;
+  final RulerPickerController? controller;
+
+  RulerPicker({
+    required this.onValueChanged,
+    required this.width,
+    required this.height,
+    required this.onBuildRulerScaleText,
+    this.ranges = const [],
+    this.rulerMarginTop = 0,
+    this.scaleLineStyleList = const [
+      ScaleLineStyle(
+          scale: 0,
+          color: Color.fromARGB(255, 188, 194, 203),
+          width: 2,
+          height: 32),
+      ScaleLineStyle(
+          color: Color.fromARGB(255, 188, 194, 203), width: 1, height: 20),
+    ],
+    this.rulerScaleTextStyle = const TextStyle(
+      color: Color.fromARGB(255, 188, 194, 203),
+      fontSize: 14,
+    ),
+    this.marker,
+    this.rulerBackgroundColor = Colors.white,
+    this.controller,
+  });
+  @override
+  State<StatefulWidget> createState() {
+    return RulerPickerState();
+  }
+}
+
+class RulerPickerState extends State<RulerPicker> {
+  double lastOffset = 0;
+  bool isPosFixed = false;
+  late ScrollController scrollController;
+  Map<int, ScaleLineStyle> _scaleLineStyleMap = {};
+  int itemCount = 0;
+
+  // 每个刻度间距
+  final double _ruleScaleInterval = 15.w;
+
+  @override
+  void initState() {
+    super.initState();
+
+    itemCount = _calculateItemCount();
+
+    for (var element in widget.scaleLineStyleList) {
+      _scaleLineStyleMap[element.scale] = element;
+    }
+
+    double initValueOffset = getPositionByValue(widget.controller?.value ?? 0);
+
+    scrollController = ScrollController(
+      initialScrollOffset: initValueOffset > 0 ? initValueOffset : 0,
+    );
+
+    scrollController.addListener(_onValueChanged);
+
+    widget.controller?.addListener(() {
+      setPositionByValue(widget.controller?.value ?? 0);
+    });
+  }
+
+  int _calculateItemCount() {
+    int itemCount = 0;
+    for (var element in widget.ranges) {
+      itemCount += ((element.end - element.begin) / element.scale).truncate();
+    }
+    itemCount += 1;
+    return itemCount;
+  }
+
+  void _onValueChanged() {
+    int currentIndex = scrollController.offset ~/ _ruleScaleInterval.toInt();
+    if (currentIndex < 0) currentIndex = 0;
+
+    num currentValue = getRulerScaleValue(currentIndex);
+    var lastConfig = widget.ranges.last;
+    if (currentValue > lastConfig.end) currentValue = lastConfig.end;
+
+    widget.onValueChanged(currentValue);
+  }
+
+  void fixOffset() {
+    final double rawOffset = scrollController.offset;
+    final double fixedOffset =
+        (rawOffset / _ruleScaleInterval).round() * _ruleScaleInterval;
+
+    scrollController.animateTo(
+      fixedOffset,
+      duration: const Duration(milliseconds: 100),
+      curve: Curves.easeOut,
+    );
+
+    Future.delayed(const Duration(milliseconds: 120), () {
+      _onValueChanged(); // 确保最终值是准确的
+    });
+  }
+
+  num getRulerScaleValue(int index) {
+    num rulerScaleValue = 0;
+    RulerRange? currentConfig;
+
+    for (RulerRange config in widget.ranges) {
+      currentConfig = config;
+      if (currentConfig == widget.ranges.last) {
+        break;
+      }
+      var totalCount =
+      ((config.end - config.begin) / config.scale).truncate();
+      if (index <= totalCount) {
+        break;
+      } else {
+        index -= totalCount;
+      }
+    }
+
+    rulerScaleValue = index * currentConfig!.scale + currentConfig.begin;
+    return rulerScaleValue;
+  }
+
+  double getPositionByValue(num value) {
+    double offsetValue = 0;
+    for (RulerRange config in widget.ranges) {
+      if (config.begin <= value && config.end >= value) {
+        offsetValue +=
+            ((value - config.begin) / config.scale) * _ruleScaleInterval;
+        break;
+      } else if (value >= config.begin) {
+        var totalCount =
+        ((config.end - config.begin) / config.scale).truncate();
+        offsetValue += totalCount * _ruleScaleInterval;
+      }
+    }
+    return offsetValue;
+  }
+
+  void setPositionByValue(num value) {
+    double offsetValue = getPositionByValue(value);
+    scrollController.jumpTo(offsetValue);
+    fixOffset();
+  }
+
+  Widget _buildRulerScaleLine(int index) {
+    bool isMajorScale = index % 5 == 0;
+    return Container(
+      width: 8.w,
+      height: isMajorScale ? 48.w : 24.w,
+      decoration: BoxDecoration(
+        borderRadius: BorderRadius.circular(40.w),
+        color: const Color(0xffd0d1d6),
+      ),
+    );
+  }
+
+  Widget _buildRulerScale(BuildContext context, int index) {
+    return Container(
+      width: _ruleScaleInterval,
+      child: Stack(
+        clipBehavior: Clip.none,
+        children: [
+          Align(
+            alignment: Alignment.topCenter,
+            child: _buildRulerScaleLine(index),
+          ),
+          Positioned(
+            bottom: 5,
+            width: 100,
+            left: -50 + _ruleScaleInterval / 2,
+            child: index % 10 == 0
+                ? Container(
+              alignment: Alignment.center,
+              child: Text(
+                widget.onBuildRulerScaleText(
+                    index, getRulerScaleValue(index)),
+                style: widget.rulerScaleTextStyle,
+              ),
+            )
+                : const SizedBox(),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildMark() {
+    Widget triangle() {
+      return SizedBox(
+        width: 15,
+        height: 15,
+        child: CustomPaint(
+          painter: _TrianglePainter(),
+        ),
+      );
+    }
+
+    return SizedBox(
+      width: _ruleScaleInterval * 2,
+      height: 45,
+      child: Stack(
+        children: [
+          Align(alignment: Alignment.topCenter, child: triangle()),
+          Align(
+            child: Container(
+              width: 3,
+              height: 34,
+              color: const Color.fromARGB(255, 118, 165, 248),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  bool isRangesChanged(RulerPicker oldWidget) {
+    if (oldWidget.ranges.length != widget.ranges.length) {
+      return true;
+    }
+    for (int i = 0; i < widget.ranges.length; i++) {
+      RulerRange oldRange = oldWidget.ranges[i];
+      RulerRange range = widget.ranges[i];
+      if (oldRange.begin != range.begin ||
+          oldRange.end != range.end ||
+          oldRange.scale != range.scale) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @override
+  void didUpdateWidget(RulerPicker oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (mounted && isRangesChanged(oldWidget)) {
+      Future.delayed(Duration.zero, () {
+        setState(() {
+          itemCount = _calculateItemCount();
+        });
+        _onValueChanged();
+      });
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      width: widget.width,
+      height: widget.height + widget.rulerMarginTop,
+      child: Stack(
+        children: [
+          Align(
+            alignment: Alignment.bottomCenter,
+            child: Padding(padding: EdgeInsets.only(top: widget.rulerMarginTop),child: Listener(
+              onPointerDown: (_) {
+                FocusScope.of(context).unfocus();
+                isPosFixed = false;
+              },
+              child: NotificationListener<ScrollNotification>(
+                onNotification: (scrollNotification) {
+                  if (scrollNotification is ScrollStartNotification) {
+                    isPosFixed = false;
+                  } else if (scrollNotification is ScrollEndNotification) {
+                    if (!isPosFixed) {
+                      isPosFixed = true;
+                      fixOffset();
+                    }
+                  }
+                  return true;
+                },
+                child: ListView.builder(
+                  padding: EdgeInsets.symmetric(
+                    horizontal: (widget.width - _ruleScaleInterval) / 2,
+                  ),
+                  itemExtent: _ruleScaleInterval,
+                  itemCount: itemCount,
+                  controller: scrollController,
+                  scrollDirection: Axis.horizontal,
+                  itemBuilder: _buildRulerScale,
+                ),
+              ),
+            ),)
+          ),
+          Align(
+            alignment: Alignment.topCenter,
+            child: widget.marker ?? _buildMark(),
+          ),
+        ],
+      ),
+    );
+  }
+
+  @override
+  void dispose() {
+    scrollController.dispose();
+    super.dispose();
+  }
+}
+
+
+class ScaleLineStyle {
+  final int scale;
+  final Color color;
+  final double width;
+  final double height;
+
+  const ScaleLineStyle({
+    this.scale = -1,
+    required this.color,
+    required this.width,
+    required this.height,
+  });
+}
+
+class RulerRange {
+  final double scale;
+  final int begin;
+  final int end;
+  const RulerRange({
+    required this.begin,
+    required this.end,
+    this.scale = 1,
+  });
+}