Browse Source

[feat]缓存选中键盘

云天逵 7 tháng trước cách đây
mục cha
commit
2cfb7bcf77
37 tập tin đã thay đổi với 2232 bổ sung235 xóa
  1. BIN
      assets/images/icon_new_discount_banner_1.webp
  2. BIN
      assets/images/icon_new_discount_banner_2.webp
  3. BIN
      assets/images/icon_new_discount_banner_3.webp
  4. BIN
      assets/images/icon_new_discount_character_title.webp
  5. 21 0
      lib/data/api/atmob_api.dart
  6. 99 0
      lib/data/api/atmob_api.g.dart
  7. 21 0
      lib/data/api/request/keyboard_character_start_request.dart
  8. 77 0
      lib/data/api/request/keyboard_character_start_request.g.dart
  9. 17 0
      lib/data/api/request/wechat_login_request.dart
  10. 67 0
      lib/data/api/request/wechat_login_request.g.dart
  11. 32 0
      lib/data/api/response/member_new_user_response.dart
  12. 34 0
      lib/data/api/response/member_new_user_response.g.dart
  13. 14 0
      lib/data/api/response/wechat_login_response.dart
  14. 14 0
      lib/data/api/response/wechat_login_response.g.dart
  15. 45 13
      lib/data/repository/account_repository.dart
  16. 45 10
      lib/data/repository/keyboard_repository.dart
  17. 30 16
      lib/data/repository/store_repository.dart
  18. 21 3
      lib/di/get_it.config.dart
  19. 2 1
      lib/dialog/login/login_dialog.dart
  20. 23 2
      lib/dialog/login/login_dialog_controller.dart
  21. 50 54
      lib/module/character/character_view.dart
  22. 7 0
      lib/module/keyboard/keyboard_controller.dart
  23. 38 33
      lib/module/keyboard/keyboard_view.dart
  24. 15 0
      lib/module/login/login_controller.dart
  25. 5 19
      lib/module/mine/mine_controller.dart
  26. 1 0
      lib/module/new_user/new_user_controller.dart
  27. 6 11
      lib/module/new_user/result/new_user_result_controller.dart
  28. 1 1
      lib/module/profile/edit/profile_edit_controller.dart
  29. 15 26
      lib/module/profile/profile_controller.dart
  30. 3 3
      lib/module/profile/profile_page.dart
  31. 451 0
      lib/module/store/new_discount/new_discount_controller.dart
  32. 634 0
      lib/module/store/new_discount/new_discount_page.dart
  33. 1 1
      lib/module/store/store_controller.dart
  34. 140 0
      lib/module/user_profile/user_profile_controller.dart
  35. 229 0
      lib/module/user_profile/user_profile_page.dart
  36. 21 0
      lib/resource/assets.gen.dart
  37. 53 42
      lib/router/app_pages.dart

BIN
assets/images/icon_new_discount_banner_1.webp


BIN
assets/images/icon_new_discount_banner_2.webp


BIN
assets/images/icon_new_discount_banner_3.webp


BIN
assets/images/icon_new_discount_character_title.webp


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

@@ -17,6 +17,7 @@ import 'package:keyboard/data/api/request/intimacy_analyze_request.dart';
 import 'package:keyboard/data/api/request/intimacy_reply_analyze_request.dart';
 import 'package:keyboard/data/api/request/intimacy_reply_chat_request.dart';
 import 'package:keyboard/data/api/request/keyboard_character_list_request.dart';
+import 'package:keyboard/data/api/request/keyboard_character_start_request.dart';
 import 'package:keyboard/data/api/request/keyboard_character_update_request.dart';
 import 'package:keyboard/data/api/request/keyboard_choose_request.dart';
 import 'package:keyboard/data/api/request/keyboard_generate_request.dart';
@@ -28,6 +29,7 @@ import 'package:keyboard/data/api/request/order_pay_request.dart';
 import 'package:keyboard/data/api/request/order_status_request.dart';
 import 'package:keyboard/data/api/request/send_code_request.dart';
 import 'package:keyboard/data/api/request/user_info_setting_request.dart';
+import 'package:keyboard/data/api/request/wechat_login_request.dart';
 import 'package:keyboard/data/api/response/character_add_response.dart';
 import 'package:keyboard/data/api/response/character_custom_config_response.dart';
 import 'package:keyboard/data/api/response/character_custom_generate_response.dart';
@@ -57,10 +59,12 @@ import 'package:keyboard/data/api/response/keyboard_love_index_response.dart';
 import 'package:keyboard/data/api/response/keyboard_meme_explain_response.dart';
 import 'package:keyboard/data/api/response/keyboard_prologue_list_response.dart';
 import 'package:keyboard/data/api/response/login_response.dart';
+import 'package:keyboard/data/api/response/member_new_user_response.dart';
 import 'package:keyboard/data/api/response/new_user_get_character_response.dart';
 import 'package:keyboard/data/api/response/order_pay_response.dart';
 import 'package:keyboard/data/api/response/order_status_response.dart';
 import 'package:keyboard/data/api/response/user_info_response.dart';
+import 'package:keyboard/data/api/response/wechat_login_response.dart';
 import 'package:keyboard/data/repository/config_repository.dart';
 import 'package:retrofit/error_logger.dart';
 import 'package:retrofit/http.dart';
@@ -83,6 +87,12 @@ abstract class AtmobApi {
     @Body() LoginRequest request,
   );
 
+  // 微信登录
+  @POST("/central/open/v1/user/wechat/login")
+  Future<BaseResponse<WechatLoginResponse>> loginUserWechatLogin(
+    @Body() WechatLoginRequest request,
+  );
+
   // 注销账号
   @POST("/central/open/v1/user/deprecate")
   Future<BaseResponse> deprecate(@Body() AppBaseRequest request);
@@ -191,6 +201,11 @@ abstract class AtmobApi {
     @Body() KeyboardCharacterUpdateRequest request,
   );
 
+  @POST("/project/keyboard/v1/character/keyboard/start")
+  Future<BaseResponse> keyboardCharacterStart(
+    @Body() KeyboardCharacterStartRequest request,
+  );
+
   // 生成键盘
   @POST("/project/keyboard/v1/keyboard/generate")
   Future<BaseResponse<KeyboardGenerateResponse>> keyboardGenerate(
@@ -290,4 +305,10 @@ abstract class AtmobApi {
   Future<BaseResponse<KeyboardMemeExplainResponse>> getKeyboardMemeExplain(
     @Body() KeyboardMemeExplainRequest request,
   );
+
+  //获取新人流程会员页信息
+  @POST("/project/keyboard/v1/member/newUser")
+  Future<BaseResponse<MemberNewUserResponse>> getMemberUserResponse(
+    @Body() AppBaseRequest request,
+  );
 }

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

@@ -82,6 +82,39 @@ class _AtmobApi implements AtmobApi {
   }
 
   @override
+  Future<BaseResponse<WechatLoginResponse>> loginUserWechatLogin(
+    WechatLoginRequest request,
+  ) async {
+    final _extra = <String, dynamic>{};
+    final queryParameters = <String, dynamic>{};
+    final _headers = <String, dynamic>{};
+    final _data = <String, dynamic>{};
+    _data.addAll(request.toJson());
+    final _options = _setStreamType<BaseResponse<WechatLoginResponse>>(
+      Options(method: 'POST', headers: _headers, extra: _extra)
+          .compose(
+            _dio.options,
+            '/central/open/v1/user/wechat/login',
+            queryParameters: queryParameters,
+            data: _data,
+          )
+          .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
+    );
+    final _result = await _dio.fetch<Map<String, dynamic>>(_options);
+    late BaseResponse<WechatLoginResponse> _value;
+    try {
+      _value = BaseResponse<WechatLoginResponse>.fromJson(
+        _result.data!,
+        (json) => WechatLoginResponse.fromJson(json as Map<String, dynamic>),
+      );
+    } on Object catch (e, s) {
+      errorLogger?.logError(e, s, _options);
+      rethrow;
+    }
+    return _value;
+  }
+
+  @override
   Future<BaseResponse<dynamic>> deprecate(AppBaseRequest request) async {
     final _extra = <String, dynamic>{};
     final queryParameters = <String, dynamic>{};
@@ -737,6 +770,39 @@ class _AtmobApi implements AtmobApi {
   }
 
   @override
+  Future<BaseResponse<dynamic>> keyboardCharacterStart(
+    KeyboardCharacterStartRequest request,
+  ) async {
+    final _extra = <String, dynamic>{};
+    final queryParameters = <String, dynamic>{};
+    final _headers = <String, dynamic>{};
+    final _data = <String, dynamic>{};
+    _data.addAll(request.toJson());
+    final _options = _setStreamType<BaseResponse<dynamic>>(
+      Options(method: 'POST', headers: _headers, extra: _extra)
+          .compose(
+            _dio.options,
+            '/project/keyboard/v1/character/keyboard/start',
+            queryParameters: queryParameters,
+            data: _data,
+          )
+          .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
+    );
+    final _result = await _dio.fetch<Map<String, dynamic>>(_options);
+    late BaseResponse<dynamic> _value;
+    try {
+      _value = BaseResponse<dynamic>.fromJson(
+        _result.data!,
+        (json) => json as dynamic,
+      );
+    } on Object catch (e, s) {
+      errorLogger?.logError(e, s, _options);
+      rethrow;
+    }
+    return _value;
+  }
+
+  @override
   Future<BaseResponse<KeyboardGenerateResponse>> keyboardGenerate(
     KeyboardGenerateRequest request,
   ) async {
@@ -1345,6 +1411,39 @@ class _AtmobApi implements AtmobApi {
     return _value;
   }
 
+  @override
+  Future<BaseResponse<MemberNewUserResponse>> getMemberUserResponse(
+    AppBaseRequest request,
+  ) async {
+    final _extra = <String, dynamic>{};
+    final queryParameters = <String, dynamic>{};
+    final _headers = <String, dynamic>{};
+    final _data = <String, dynamic>{};
+    _data.addAll(request.toJson());
+    final _options = _setStreamType<BaseResponse<MemberNewUserResponse>>(
+      Options(method: 'POST', headers: _headers, extra: _extra)
+          .compose(
+            _dio.options,
+            '/project/keyboard/v1/member/newUser',
+            queryParameters: queryParameters,
+            data: _data,
+          )
+          .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
+    );
+    final _result = await _dio.fetch<Map<String, dynamic>>(_options);
+    late BaseResponse<MemberNewUserResponse> _value;
+    try {
+      _value = BaseResponse<MemberNewUserResponse>.fromJson(
+        _result.data!,
+        (json) => MemberNewUserResponse.fromJson(json as Map<String, dynamic>),
+      );
+    } on Object catch (e, s) {
+      errorLogger?.logError(e, s, _options);
+      rethrow;
+    }
+    return _value;
+  }
+
   RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
     if (T != dynamic &&
         !(requestOptions.responseType == ResponseType.bytes ||

+ 21 - 0
lib/data/api/request/keyboard_character_start_request.dart

@@ -0,0 +1,21 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import '../../../base/app_base_request.dart';
+
+part 'keyboard_character_start_request.g.dart';
+
+@JsonSerializable()
+class KeyboardCharacterStartRequest extends AppBaseRequest {
+  @JsonKey(name: "keyboardId")
+  String keyboardId;
+  @JsonKey(name: "characterIds")
+  List<String> characterIds;
+
+  KeyboardCharacterStartRequest({
+    required this.keyboardId,
+    required this.characterIds,
+  });
+
+  @override
+  Map<String, dynamic> toJson() => _$KeyboardCharacterStartRequestToJson(this);
+}

+ 77 - 0
lib/data/api/request/keyboard_character_start_request.g.dart

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

+ 17 - 0
lib/data/api/request/wechat_login_request.dart

@@ -0,0 +1,17 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import '../../../base/app_base_request.dart';
+
+
+part 'wechat_login_request.g.dart';
+
+@JsonSerializable()
+class WechatLoginRequest extends AppBaseRequest {
+  @JsonKey(name: "code")
+  String code;
+
+  WechatLoginRequest(this.code);
+
+  @override
+  Map<String, dynamic> toJson() => _$WechatLoginRequestToJson(this);
+}

+ 67 - 0
lib/data/api/request/wechat_login_request.g.dart

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

+ 32 - 0
lib/data/api/response/member_new_user_response.dart

@@ -0,0 +1,32 @@
+import 'package:json_annotation/json_annotation.dart';
+import 'package:keyboard/data/bean/character_info.dart';
+import 'package:keyboard/data/bean/pay_way_info.dart';
+
+import '../../bean/goods_info.dart';
+
+part 'member_new_user_response.g.dart';
+
+@JsonSerializable()
+class MemberNewUserResponse {
+  @JsonKey(name: "intimacy")
+  int? intimacy;
+
+  @JsonKey(name: "items")
+  List<GoodsInfo>? goodsInfoList;
+
+  @JsonKey(name: "payOptions")
+  List<PayWayInfo>? payInfoList;
+
+  @JsonKey(name: "characterInfos")
+  List<CharacterInfo>? characterInfos;
+
+  MemberNewUserResponse({
+    this.intimacy,
+    this.goodsInfoList,
+    this.payInfoList,
+    this.characterInfos,
+  });
+
+  factory MemberNewUserResponse.fromJson(Map<String, dynamic> json) =>
+      _$MemberNewUserResponseFromJson(json);
+}

+ 34 - 0
lib/data/api/response/member_new_user_response.g.dart

@@ -0,0 +1,34 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'member_new_user_response.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+MemberNewUserResponse _$MemberNewUserResponseFromJson(
+  Map<String, dynamic> json,
+) => MemberNewUserResponse(
+  intimacy: (json['intimacy'] as num?)?.toInt(),
+  goodsInfoList:
+      (json['items'] as List<dynamic>?)
+          ?.map((e) => GoodsInfo.fromJson(e as Map<String, dynamic>))
+          .toList(),
+  payInfoList:
+      (json['payOptions'] as List<dynamic>?)
+          ?.map((e) => PayWayInfo.fromJson(e as Map<String, dynamic>))
+          .toList(),
+  characterInfos:
+      (json['characterInfos'] as List<dynamic>?)
+          ?.map((e) => CharacterInfo.fromJson(e as Map<String, dynamic>))
+          .toList(),
+);
+
+Map<String, dynamic> _$MemberNewUserResponseToJson(
+  MemberNewUserResponse instance,
+) => <String, dynamic>{
+  'intimacy': instance.intimacy,
+  'items': instance.goodsInfoList,
+  'payOptions': instance.payInfoList,
+  'characterInfos': instance.characterInfos,
+};

+ 14 - 0
lib/data/api/response/wechat_login_response.dart

@@ -0,0 +1,14 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'wechat_login_response.g.dart';
+
+@JsonSerializable()
+class WechatLoginResponse {
+  @JsonKey(name: "authToken")
+  String authToken;
+
+  WechatLoginResponse(this.authToken);
+
+  factory WechatLoginResponse.fromJson(Map<String, dynamic> json) =>
+      _$WechatLoginResponseFromJson(json);
+}

+ 14 - 0
lib/data/api/response/wechat_login_response.g.dart

@@ -0,0 +1,14 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'wechat_login_response.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+WechatLoginResponse _$WechatLoginResponseFromJson(Map<String, dynamic> json) =>
+    WechatLoginResponse(json['authToken'] as String);
+
+Map<String, dynamic> _$WechatLoginResponseToJson(
+  WechatLoginResponse instance,
+) => <String, dynamic>{'authToken': instance.authToken};

+ 45 - 13
lib/data/repository/account_repository.dart

@@ -4,6 +4,8 @@ import 'package:get/get.dart';
 import 'package:injectable/injectable.dart';
 import 'package:keyboard/data/api/atmob_api.dart';
 import 'package:keyboard/data/api/request/user_info_setting_request.dart';
+import 'package:keyboard/data/api/request/wechat_login_request.dart';
+import 'package:keyboard/data/api/response/wechat_login_response.dart';
 import 'package:keyboard/data/bean/member_info.dart';
 import 'package:keyboard/data/repository/keyboard_repository.dart';
 
@@ -33,7 +35,7 @@ class AccountRepository {
   Rxn<UserInfoResponse> get userInfo => _userInfo;
 
   RxnString loginPhoneNum = RxnString();
-  RxBool isLogin = RxBool(false);
+ final RxBool isLogin = false.obs;
 
   Rxn<MemberInfo> memberStatusInfo = Rxn<MemberInfo>();
 
@@ -45,18 +47,12 @@ class AccountRepository {
 
   static String? token = KVUtil.getString(keyAccountLoginToken, null);
 
+  RxnString get tokenRxn => RxnString(token);
   final KeyboardRepository keyboardRepository =
       KeyboardRepository.getInstance();
 
   AccountRepository(this.atmobApi) {
     AtmobLog.d(tag, '$tag....init $hashCode');
-    isLogin.bindStream(
-      loginPhoneNum.map((value) {
-        final result = value?.isNotEmpty == true;
-        KVUtil.putBool(Constants.keyIsLogin, result);
-        return result;
-      }),
-    );
 
     loginPhoneNum.value = KVUtil.getString(keyAccountLoginPhoneNum, null);
     refreshUserInfo();
@@ -100,11 +96,30 @@ class AccountRepository {
           throw error;
         });
   }
-  Future<void> deprecateAccount() {
-
-      return atmobApi.deprecate(AppBaseRequest()).then(HttpHandler.handle(true));
 
+  Future<WechatLoginResponse> wechatLogin(String code) {
+    if (_errorCodeTimes >= 5) {
+      throw LoginTooOftenException();
+    }
+    return atmobApi
+        .loginUserWechatLogin(WechatLoginRequest(code))
+        .then(HttpHandler.handle(true))
+        .then((response) {
+          _errorCodeTimes = 0;
+          onWechatLoginSuccess(response.authToken);
+          return response;
+        })
+        .catchError((error) {
+          if (error is ServerErrorException &&
+              error.code == ErrorCode.verificationCodeError) {
+            _errorCodeTimes++;
+          }
+          throw error;
+        });
+  }
 
+  Future<void> deprecateAccount() {
+    return atmobApi.deprecate(AppBaseRequest()).then(HttpHandler.handle(true));
   }
 
   void refreshUserInfo() {
@@ -134,6 +149,16 @@ class AccountRepository {
         .then(HttpHandler.handle(true))
         .then((response) {
           _userInfo.value = response;
+          if (response.loginStatus != null) {
+            if (response.loginStatus == 1) {
+              print("loginStatus == 1");
+              isLogin.value = true;
+            }
+            if (response.loginStatus == 0) {
+              print("loginStatus == 0");
+              isLogin.value = false;
+            }
+          }
           memberStatusInfo.value = response.memberInfo;
           if (response.memberInfo != null) {
             KVUtil.putBool(
@@ -176,15 +201,22 @@ class AccountRepository {
     keyboardRepository.refreshData();
   }
 
+  void onWechatLoginSuccess(String authToken) {
+    AccountRepository.token = authToken;
+    refreshUserInfo();
+    KVUtil.putString(keyAccountLoginToken, authToken);
+    keyboardRepository.refreshData();
+  }
+
   void logout() {
     token = null;
-
     KVUtil.putString(keyAccountLoginPhoneNum, null);
     KVUtil.putString(keyAccountLoginToken, null);
     memberStatusInfo.value = null;
     KVUtil.putBool(Constants.keyIsMember, false);
     loginPhoneNum.value = null;
-    keyboardRepository.cleanData();
+    isLogin.value = false;
+    // keyboardRepository.cleanData();
   }
 
   // 意见反馈

+ 45 - 10
lib/data/repository/keyboard_repository.dart

@@ -11,10 +11,12 @@ import 'package:keyboard/data/api/response/keyboard_prologue_list_response.dart'
 import 'package:keyboard/utils/atmob_log.dart';
 import 'dart:convert';
 import '../../di/get_it.dart';
+import '../../module/keyboard_manage/keyboard_manage_controller.dart';
 import '../../utils/async_util.dart';
 import '../../utils/http_handler.dart';
 import '../api/atmob_api.dart';
 import '../api/request/keyboard_character_list_request.dart';
+import '../api/request/keyboard_character_start_request.dart';
 import '../api/request/keyboard_choose_request.dart';
 import '../api/request/keyboard_meme_explain_request.dart';
 import '../api/request/keyboard_update_request.dart';
@@ -33,6 +35,10 @@ class KeyboardRepository {
 
   RxList<KeyboardInfo> get keyboardInfoList => _keyboardInfoList;
 
+  final RxList<KeyboardInfo> _customKeyboardInfoList = RxList();
+
+  RxList<KeyboardInfo> get customKeyboardInfoList => _customKeyboardInfoList;
+
   final Rxn<KeyboardLoveIndexResponse> _homeLoveIndex =
       Rxn<KeyboardLoveIndexResponse>();
 
@@ -46,15 +52,19 @@ class KeyboardRepository {
   CancelableFuture? homeInfoFuture;
   CancelableFuture? homeLoveIndexFuture;
 
+  final Rxn<KeyboardInfo> _chooseKeyboardInfo = Rxn<KeyboardInfo>(null);
+
+  Rxn<KeyboardInfo> get chooseKeyboardInfo => _chooseKeyboardInfo;
+
   KeyboardRepository(this.atmobApi) {
     print('$tag....init');
     refreshData();
   }
 
   Future refreshData() async {
-    refreshKeyboardList();
     await Future.delayed(const Duration(milliseconds: 500));
     // 延迟为了保证首页数据能够正常获取,不然保存的时候,获取太快了,导致还是拉到旧的数值
+    refreshKeyboardList();
     refreshUserInfo();
     refreshLoveIndex();
   }
@@ -89,6 +99,7 @@ class KeyboardRepository {
 
   Future cleanData() async {
     _keyboardInfoList.clear();
+
     getKeyboardHomeInfo();
     getKeyboardLoveIndex();
   }
@@ -96,7 +107,17 @@ class KeyboardRepository {
   Future refreshKeyboardList() async {
     return getKeyboardList().then((response) {
       _keyboardInfoList.value = response.keyboardInfos;
-      print('$tag refreshKeyboardList: ${response.keyboardInfos.first.id}');
+      final chosen = response.keyboardInfos.firstWhereOrNull(
+        (e) => e.isChoose == true,
+      );
+      if (chosen != null) {
+        _chooseKeyboardInfo.value = chosen;
+      }
+      // 过滤掉系统键盘
+      _customKeyboardInfoList.value =
+          response.keyboardInfos
+              .where((keyboard) => keyboard.type != KeyboardType.system.name)
+              .toList();
     });
   }
 
@@ -130,7 +151,21 @@ class KeyboardRepository {
             characterIds: characterIds,
           ),
         )
-        .then(HttpHandler.handle(true));
+        .then(HttpHandler.handle(false));
+  }
+
+  Future<void> keyboardCharacterStart({
+    required List<String> characterIds,
+    required String keyboardId,
+  }) {
+    return atmobApi
+        .keyboardCharacterStart(
+          KeyboardCharacterStartRequest(
+            keyboardId: keyboardId,
+            characterIds: characterIds,
+          ),
+        )
+        .then(HttpHandler.handle(false));
   }
 
   // 更新键盘信息
@@ -153,7 +188,7 @@ class KeyboardRepository {
             gender: gender,
           ),
         )
-        .then(HttpHandler.handle(false));
+        .then(HttpHandler.handle(true));
   }
 
   // 选择键盘
@@ -215,20 +250,20 @@ class KeyboardRepository {
           return response;
         });
   }
+
   Future<KeyboardMemeExplainResponse> getKeyboardMemeExplain({
     required String birthday,
     required String targetBirthday,
   }) {
     return atmobApi
         .getKeyboardMemeExplain(
-      KeyboardMemeExplainRequest(
-        birthday: birthday,
-        targetBirthday: targetBirthday,
-      ),
-    )
+          KeyboardMemeExplainRequest(
+            birthday: birthday,
+            targetBirthday: targetBirthday,
+          ),
+        )
         .then(HttpHandler.handle(true));
   }
 
-
   static KeyboardRepository getInstance() => getIt.get<KeyboardRepository>();
 }

+ 30 - 16
lib/data/repository/store_repository.dart

@@ -3,12 +3,16 @@ import 'package:keyboard/data/api/atmob_api.dart';
 import 'package:keyboard/data/api/response/item_list_response.dart';
 import 'package:keyboard/data/api/response/order_pay_response.dart';
 import 'package:keyboard/utils/http_handler.dart';
-
+import 'package:get/get.dart';
 import '../../base/app_base_request.dart';
+import '../../utils/async_util.dart';
 import '../../utils/payment_status_manager.dart';
 import '../api/request/order_pay_request.dart';
 import '../api/request/order_status_request.dart';
 import '../api/response/item_retention_response.dart';
+import '../api/response/member_new_user_response.dart';
+import '../bean/goods_info.dart';
+import '../bean/pay_way_info.dart';
 import 'account_repository.dart';
 
 @lazySingleton
@@ -18,8 +22,9 @@ class StoreRepository {
   final AtmobApi atmobApi;
   final AccountRepository accountRepository;
 
-  StoreRepository(this.atmobApi, this.accountRepository);
-
+  StoreRepository(this.atmobApi, this.accountRepository) {
+    print('$tag....init');
+  }
 
   Future<ItemListResponse> getGoodsInfoList() async {
     return await atmobApi
@@ -27,18 +32,29 @@ class StoreRepository {
         .then(HttpHandler.handle(true));
   }
 
+  Future<MemberNewUserResponse> getMemberNewUserGoodsList() async {
+    return await atmobApi
+        .getMemberUserResponse(AppBaseRequest())
+        .then(HttpHandler.handle(true));
+  }
 
-  Future<OrderPayResponse> submitAndRequestPay(
-      {required int goodsId,
-        required int payPlatform,
-        required int payMethod}) {
+  Future<OrderPayResponse> submitAndRequestPay({
+    required int goodsId,
+    required int payPlatform,
+    required int payMethod,
+  }) {
     return atmobApi
-        .orderPay(OrderPayRequest(
-        goodsId: goodsId, payPlatform: payPlatform, payMethod: payMethod))
+        .orderPay(
+          OrderPayRequest(
+            goodsId: goodsId,
+            payPlatform: payPlatform,
+            payMethod: payMethod,
+          ),
+        )
         .then(HttpHandler.handle(false));
   }
 
-  Future<int>orderStatus(String outTradeNo, {String? receiptData}) {
+  Future<int> orderStatus(String outTradeNo, {String? receiptData}) {
     return atmobApi
         .orderStatus(OrderStatusRequest(outTradeNo, receiptData))
         .then(HttpHandler.handle(false))
@@ -46,15 +62,13 @@ class StoreRepository {
           if (data.payStatus == PaymentStatus.payStatusSuccess) {
             accountRepository.refreshUserInfo();
           }
-      return data.payStatus;
-    });
+          return data.payStatus;
+        });
   }
 
-  Future<ItemRetentionResponse> getItemRetention()  {
-    return  atmobApi
+  Future<ItemRetentionResponse> getItemRetention() {
+    return atmobApi
         .getItemRetention(AppBaseRequest())
         .then(HttpHandler.handle(true));
   }
-
-
 }

+ 21 - 3
lib/di/get_it.config.dart

@@ -74,9 +74,11 @@ import '../module/new_user/result/new_user_result_controller.dart' as _i576;
 import '../module/profile/edit/profile_edit_controller.dart' as _i344;
 import '../module/profile/profile_controller.dart' as _i244;
 import '../module/store/discount/discount_controller.dart' as _i333;
+import '../module/store/new_discount/new_discount_controller.dart' as _i326;
 import '../module/store/store_controller.dart' as _i344;
 import '../module/store/suprise/goods_surprise_controller.dart' as _i935;
 import '../module/user_info/user_info_controller.dart' as _i866;
+import '../module/user_profile/user_profile_controller.dart' as _i329;
 import '../plugins/keyboard_method_handler.dart' as _i415;
 import '../utils/intimacy_analyze_config_helper.dart' as _i738;
 import '../utils/payment_status_manager.dart' as _i779;
@@ -139,9 +141,6 @@ extension GetItInjectableX on _i174.GetIt {
       () => networkModule.createFileDio(),
       instanceName: 'fileDio',
     );
-    gh.factory<_i798.LoginDialogController>(
-      () => _i798.LoginDialogController(gh<_i495.WechatLoginService>()),
-    );
     gh.singleton<_i723.AtmobFileApi>(
       () => networkModule.provideAtmobFileApi(
         gh<_i361.Dio>(instanceName: 'fileDio'),
@@ -211,6 +210,12 @@ 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>(),
@@ -310,6 +315,12 @@ extension GetItInjectableX on _i174.GetIt {
         gh<_i50.ConfigRepository>(),
       ),
     );
+    gh.factory<_i798.LoginDialogController>(
+      () => _i798.LoginDialogController(
+        gh<_i495.WechatLoginService>(),
+        gh<_i83.AccountRepository>(),
+      ),
+    );
     gh.factory<_i510.ConversationAnalysisController>(
       () => _i510.ConversationAnalysisController(
         gh<_i738.IntimacyAnalyzeConfigHelper>(),
@@ -358,6 +369,13 @@ extension GetItInjectableX on _i174.GetIt {
         gh<_i779.PaymentStatusManager>(),
       ),
     );
+    gh.factory<_i326.NewDiscountController>(
+      () => _i326.NewDiscountController(
+        gh<_i987.StoreRepository>(),
+        gh<_i83.AccountRepository>(),
+        gh<_i779.PaymentStatusManager>(),
+      ),
+    );
     return this;
   }
 }

+ 2 - 1
lib/dialog/login/login_dialog.dart

@@ -14,9 +14,10 @@ class LoginDialog {
       clickMaskDismiss: true,
       alignment: Alignment.bottomCenter,
       animationType: SmartAnimationType.centerScale_otherSlide,
+
       tag: TAG,
       keepSingle: true,
-      // onDismiss: () => Get.delete<LoginDialogController>(),
+      onDismiss: () => Get.delete<LoginDialogController>(),
       builder: (_) => const LoginDialogView(),
     );
   }

+ 23 - 2
lib/dialog/login/login_dialog_controller.dart

@@ -6,11 +6,15 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
 import 'package:get/get.dart';
 import 'package:injectable/injectable.dart';
 import 'package:keyboard/base/base_controller.dart';
+import 'package:keyboard/data/repository/account_repository.dart';
 import 'package:keyboard/module/login/login_page.dart';
 import 'package:keyboard/utils/atmob_log.dart';
 import 'package:keyboard/utils/toast_util.dart';
 
+import '../../data/consts/error_code.dart';
 import '../../handler/wechat_login_service.dart';
+import '../../resource/string.gen.dart';
+import '../../utils/http_handler.dart';
 import '../member_agreement_dialog.dart';
 import '../privacy_agreement_dialog.dart';
 import 'login_dialog.dart';
@@ -20,8 +24,9 @@ class LoginDialogController extends BaseController {
   final tag = "LoginDialogController";
 
   final WechatLoginService wechatLoginService;
+  final AccountRepository accountRepository;
 
-  LoginDialogController(this.wechatLoginService);
+  LoginDialogController(this.wechatLoginService, this.accountRepository);
 
   final RxBool _isAgree = false.obs;
 
@@ -45,7 +50,23 @@ class LoginDialogController extends BaseController {
     wechatLoginService.login(
       onSuccess: (code) {
         debugPrint("登录成功 code: $code");
-        // TODO: 通过 code 获取后端 token
+        accountRepository
+            .wechatLogin(code)
+            .then((data) {
+              ToastUtil.show(StringName.loginSuccess);
+              SmartDialog.dismiss(tag: LoginDialog.TAG);
+            })
+            .catchError((error) {
+              if (error is ServerErrorException) {
+                if (error.code == ErrorCode.verificationCodeError) {
+                  ToastUtil.show(StringName.loginVerificationCodeErrorToast);
+                } else {
+                  ToastUtil.show(error.message);
+                }
+              } else {
+                ToastUtil.show(StringName.loginFailedToast);
+              }
+            });
       },
       onError: (code, msg) {
         ToastUtil.show("微信登录失败:$msg");

+ 50 - 54
lib/module/character/character_view.dart

@@ -19,7 +19,6 @@ class CharacterView extends BaseView<CharacterController> {
 
   @override
   Widget buildBody(BuildContext context) {
-
     return Scaffold(
       backgroundColor: Color(0xFFF6F5FA),
       body: Stack(
@@ -86,25 +85,26 @@ class CharacterView extends BaseView<CharacterController> {
                     children: [
                       // 定义按钮与人设之间的间距
                       SizedBox(height: 53.h),
-                    // 人设市场标识和下拉框
+                      // 人设市场标识和下拉框
                       _marketSignAndDropDown(),
                       SizedBox(height: 15.h),
                       _tabBar(),
-
                     ],
                   ),
                 ),
               ],
             ),
             Positioned(
-                top: 0,
-                left: 0,
-                child: _customizeButton(onTap: controller.clickCustomCharacter)),
+              top: 0,
+              left: 0,
+              child: _customizeButton(onTap: controller.clickCustomCharacter),
+            ),
           ],
         ),
       ],
     );
   }
+
   // 人设市场标识和下拉框
   Widget _marketSignAndDropDown() {
     return Padding(
@@ -112,10 +112,7 @@ class CharacterView extends BaseView<CharacterController> {
       child: Row(
         mainAxisAlignment: MainAxisAlignment.spaceBetween,
         children: [
-          Assets.images.iconCharacterMarket.image(
-            width: 73.w,
-            height: 25.h,
-          ),
+          Assets.images.iconCharacterMarket.image(width: 73.w, height: 25.h),
           Obx(() {
             return DropdownButton<String>(
               // hint: Text(''),
@@ -134,42 +131,35 @@ class CharacterView extends BaseView<CharacterController> {
                 controller.switchKeyboard(newValue);
               },
 
-              items: List.generate(
-                controller.keyboardInfoList.length,
-                    (index) {
-                  String? value =
-                  controller.keyboardInfoList[index].name;
-                  return DropdownMenuItem<String>(
-                    value: value,
-                    child: Column(
-                      crossAxisAlignment: CrossAxisAlignment.start,
-                      mainAxisSize: MainAxisSize.min,
-                      children: [
-                        Padding(
-                          padding: EdgeInsets.symmetric(
-                            vertical: 8,
-                          ),
-                          child: Text(
-                            value ?? "",
-                            style: TextStyle(
-                              color: Colors.black.withAlpha(204),
-                              fontSize: 14.sp,
-                              fontWeight: FontWeight.w400,
-                            ),
+              items: List.generate(controller.keyboardInfoList.length, (index) {
+                String? value = controller.keyboardInfoList[index].name;
+                return DropdownMenuItem<String>(
+                  value: value,
+                  child: Column(
+                    crossAxisAlignment: CrossAxisAlignment.start,
+                    mainAxisSize: MainAxisSize.min,
+                    children: [
+                      Padding(
+                        padding: EdgeInsets.symmetric(vertical: 8),
+                        child: Text(
+                          value ?? "",
+                          style: TextStyle(
+                            color: Colors.black.withAlpha(204),
+                            fontSize: 14.sp,
+                            fontWeight: FontWeight.w400,
                           ),
                         ),
-                        if (index !=
-                            controller.keyboardInfoList.length - 1)
-                          Divider(
-                            color: Color(0xFFF6F6F6),
-                            thickness: 1,
-                            height: 1,
-                          ),
-                      ],
-                    ),
-                  );
-                },
-              ),
+                      ),
+                      if (index != controller.keyboardInfoList.length - 1)
+                        Divider(
+                          color: Color(0xFFF6F6F6),
+                          thickness: 1,
+                          height: 1,
+                        ),
+                    ],
+                  ),
+                );
+              }),
             );
           }),
         ],
@@ -265,16 +255,16 @@ class CharacterView extends BaseView<CharacterController> {
               Container(
                 width: 80.w,
                 height: isSelected ? 38.h : 32.h,
-                padding: isSelected
-                    ?EdgeInsets.only(bottom: 4.h)
-                    :EdgeInsets.zero,
+                padding:
+                    isSelected ? EdgeInsets.only(bottom: 4.h) : EdgeInsets.zero,
                 decoration:
                     isSelected
                         ? BoxDecoration(
                           borderRadius: BorderRadius.circular(36.r),
                           image: DecorationImage(
                             image:
-                                Assets.images.iconCharacterGroupSelected.provider(),
+                                Assets.images.iconCharacterGroupSelected
+                                    .provider(),
                             fit: BoxFit.fill,
                           ),
                         )
@@ -296,7 +286,9 @@ class CharacterView extends BaseView<CharacterController> {
                       e.name ?? "",
                       style: TextStyle(
                         color:
-                            isSelected ? Colors.black : Colors.black.withAlpha(104),
+                            isSelected
+                                ? Colors.black
+                                : Colors.black.withAlpha(104),
                         fontSize: 14.sp,
                         fontWeight: FontWeight.w500,
                       ),
@@ -304,8 +296,7 @@ class CharacterView extends BaseView<CharacterController> {
                   ],
                 ),
               ),
-              !isSelected? SizedBox(height: 4.h):SizedBox(),
-
+              !isSelected ? SizedBox(height: 4.h) : SizedBox(),
             ],
           );
         }),
@@ -330,7 +321,6 @@ class CharacterView extends BaseView<CharacterController> {
       );
     });
   }
-
 }
 
 /// **🔹 可伸缩的
@@ -359,7 +349,7 @@ class CharacterHeaderDelegate extends SliverPersistentHeaderDelegate {
       expandedHeight,
     );
 
-    final opacity = 1 - currentVisibleHeight / expandedHeight;
+    final opacity =1- currentVisibleHeight / (shrinkOffset + expandedHeight);
     return Stack(
       // clipBehavior: Clip.none,
       children: [
@@ -380,7 +370,13 @@ class CharacterHeaderDelegate extends SliverPersistentHeaderDelegate {
           left: 0,
           right: 0,
           height: currentVisibleHeight,
-          child: Opacity(opacity: opacity, child: Container(color: Colors.purple.shade700)),
+          child: Opacity(
+            opacity: opacity,
+            child: Container(
+              width: double.infinity,
+              color: Color(0XffB683FD),
+            ),
+          ),
         ),
         Positioned(bottom: 0, left: 0, right: 0, child: bottomWidget),
 

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

@@ -7,6 +7,7 @@ import 'package:keyboard/base/base_controller.dart';
 import 'package:keyboard/data/api/response/keyboard_home_info_response.dart';
 import 'package:keyboard/data/repository/keyboard_repository.dart';
 import 'package:keyboard/module/keyboard_manage/keyboard_manage_page.dart';
+import 'package:keyboard/module/store/new_discount/new_discount_page.dart';
 import 'package:keyboard/module/store/store_page.dart';
 
 import '../../data/api/response/keyboard_love_index_response.dart';
@@ -77,6 +78,12 @@ class KeyBoardController extends BaseController {
     }
   }
 
+  void clickBanner() {
+    debugPrint("click banner");
+  NewDiscountPage.start();
+
+  }
+
   void startCountdown() {
     _timer = Timer.periodic(const Duration(milliseconds: 10), (timer) {
       if (timeLeft.value > 0) {

+ 38 - 33
lib/module/keyboard/keyboard_view.dart

@@ -674,45 +674,50 @@ class KeyBoardView extends BaseView<KeyBoardController> {
       if (!controller.isShowBanner.value) {
         return SizedBox(width: 328.w, height: 84.h);
       }
-      return SizedBox(
-        width: 328.w,
-        height: 84.h,
-        child: Stack(
-          clipBehavior: Clip.none,
-          children: [
-            Positioned(
-              top: 20.h,
-              child: Assets.images.iconKeyboardBanner.image(
-                width: 328.w,
-                height: 64.h,
+      return GestureDetector(
+        onTap: () {
+          controller.clickBanner();
+        },
+        child: SizedBox(
+          width: 328.w,
+          height: 84.h,
+          child: Stack(
+            clipBehavior: Clip.none,
+            children: [
+              Positioned(
+                top: 20.h,
+                child: Assets.images.iconKeyboardBanner.image(
+                  width: 328.w,
+                  height: 64.h,
+                ),
               ),
-            ),
-            Positioned(
-              right: 53.w,
-              bottom: 18.h,
-              child: Obx(
-                () => Text(
-                  controller.formattedTime,
-                  style: TextStyle(
-                    color: Colors.white,
-                    fontSize: 12.sp,
-                    fontWeight: FontWeight.w500,
+              Positioned(
+                right: 53.w,
+                bottom: 18.h,
+                child: Obx(
+                  () => Text(
+                    controller.formattedTime,
+                    style: TextStyle(
+                      color: Colors.white,
+                      fontSize: 12.sp,
+                      fontWeight: FontWeight.w500,
+                    ),
                   ),
                 ),
               ),
-            ),
-            Positioned(
-              right: 6.w,
-              top: 0.h,
-              child: GestureDetector(
-                onTap: controller.clickCloseBanner,
-                child: Assets.images.iconKeyboardBannerClose.image(
-                  width: 14.w,
-                  height: 22.h,
+              Positioned(
+                right: 6.w,
+                top: 0.h,
+                child: GestureDetector(
+                  onTap: controller.clickCloseBanner,
+                  child: Assets.images.iconKeyboardBannerClose.image(
+                    width: 14.w,
+                    height: 22.h,
+                  ),
                 ),
               ),
-            ),
-          ],
+            ],
+          ),
         ),
       );
     });

+ 15 - 0
lib/module/login/login_controller.dart

@@ -168,6 +168,21 @@ class LoginController extends BaseController {
     wechatLoginService.login(
       onSuccess: (code) {
         debugPrint("登录成功 code: $code");
+        accountRepository.wechatLogin(code).then((data) {
+          Get.back();
+          ToastUtil.show(StringName.loginSuccess);
+        }).catchError((error) {
+          if (error is ServerErrorException) {
+            if (error.code == ErrorCode.verificationCodeError) {
+              ToastUtil.show(StringName.loginVerificationCodeErrorToast);
+            } else {
+              ToastUtil.show(error.message);
+            }
+          } else {
+            ToastUtil.show(StringName.loginFailedToast);
+          }
+        });
+
       },
       onError: (code, msg) {
         ToastUtil.show("微信登录失败:$msg");

+ 5 - 19
lib/module/mine/mine_controller.dart

@@ -31,12 +31,12 @@ import '../../utils/http_handler.dart';
 import '../../utils/toast_util.dart';
 import '../store/store_page.dart';
 import '../store/ticket/discount_ticket_dialog.dart';
+import '../user_profile/user_profile_page.dart';
 
 @injectable
 class MineController extends BaseController {
   final AccountRepository accountRepository;
 
-
   MineController(this.accountRepository);
 
   bool get isLogin => accountRepository.isLogin.value;
@@ -44,7 +44,7 @@ class MineController extends BaseController {
   String? get phone => accountRepository.loginPhoneNum.value;
 
   String getUserName() {
-    if (isLogin || phone != null && phone!.length > 4) {
+    if (isLogin && phone != null && phone!.length > 4) {
       return '${StringName.mineAccountLoggedDesc}${phone!.substring(phone!.length - 4)}';
     } else {
       return StringName.mineAccountNoLogin;
@@ -57,17 +57,15 @@ class MineController extends BaseController {
   }
 
   longClickVip() {
-
     KeyboardAndroidPlatform.enableFloatingWindow(true);
     KeyboardAndroidPlatform.openInputMethodSettings();
   }
 
   clickOnlineCustomerService() {
     debugPrint('clickOnlineCustomerService');
-
   }
 
-  clickUserCard(){
+  clickUserCard() {
     if (isLogin) {
       UserInfoPage.start();
     } else {
@@ -75,30 +73,18 @@ class MineController extends BaseController {
     }
   }
 
-
   clickTutorials() {
     debugPrint('clickTutorials');
     NewUserPage.start();
   }
 
-
   longClickTutorials() {
-    NewUserResultPage.start(
-      newUserKeyboardInfo:  KeyboardInfo(
-        id: "w_1006",
-        name: "王金啊",
-        gender: 2,
-        imageUrl: "http://cdn.atmob.com/keyboard/avatar/default.png",
-        intimacy: 30,
-        birthday: "2021-11-10",
-      )
-    );
+    NewUserPage.start();
   }
 
   clickPersonalProfile() {
     debugPrint('clickPersonalProfile');
-    ProfilePage.start();
-
+    UserProfilePage.start();
   }
 
   clickFeedback() {

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

@@ -166,6 +166,7 @@ class NewUserController extends BaseController
         gender: currentGender,
         imageUrl: null,
       );
+      accountRepository.refreshUserInfo();
     } catch (error) {
       if (error is ServerErrorException) {
         ToastUtil.show(error.message);

+ 6 - 11
lib/module/new_user/result/new_user_result_controller.dart

@@ -5,6 +5,7 @@ import 'package:keyboard/data/bean/character_info.dart';
 import 'package:keyboard/data/repository/account_repository.dart';
 import 'package:keyboard/data/repository/characters_repository.dart';
 import 'package:keyboard/data/repository/keyboard_repository.dart';
+import 'package:keyboard/module/main/main_page.dart';
 import 'package:keyboard/utils/atmob_log.dart';
 
 import '../../../data/api/response/keyboard_meme_explain_response.dart';
@@ -38,14 +39,7 @@ class NewUserResultController extends BaseController {
   final RxList<CharacterInfo> selectCharactersList = <CharacterInfo>[].obs;
 
   final Rx<KeyboardInfo> _newUserKeyboardInfo =
-      KeyboardInfo(
-        id: "w_1006",
-        name: "王金啊",
-        gender: 2,
-        imageUrl: "http://cdn.atmob.com/keyboard/avatar/default.png",
-        intimacy: 30,
-        birthday: "2021-11-10",
-      ).obs;
+      KeyboardInfo().obs;
 
   KeyboardInfo get newUserKeyboardInfo => _newUserKeyboardInfo.value;
 
@@ -83,7 +77,7 @@ class NewUserResultController extends BaseController {
 
   void clickOpenNow() async {
     AtmobLog.d(tag, "点击立即使用");
-    AtmobLog.i(tag, 'clickSave keyboardChanged');
+
     if (_newUserKeyboardInfo.value.id == null ||
         _newUserKeyboardInfo.value.id!.isEmpty) {
       return;
@@ -91,7 +85,7 @@ class NewUserResultController extends BaseController {
     List<String> characterIds =
         selectCharactersList.map((e) => e.id).cast<String>().toList();
     keyboardRepository
-        .keyboardCharacterUpdate(
+        .keyboardCharacterStart(
           keyboardId: _newUserKeyboardInfo.value.id!,
           characterIds: characterIds,
         )
@@ -101,6 +95,7 @@ class NewUserResultController extends BaseController {
           );
           AtmobLog.d(tag, "更新键盘人设成功");
           ToastUtil.show(StringName.keyboardSaveSuccess);
+          MainPage.start();
         })
         .catchError((error) {
           if (error is ServerErrorException) {
@@ -114,7 +109,7 @@ class NewUserResultController extends BaseController {
     try {
       if (!_isValid(userInfo?.birthday) ||
           !_isValid(_newUserKeyboardInfo.value.birthday)) {
-        AtmobLog.e(tag, "生日信息不完整,无法获取星座梗语与解读");
+        AtmobLog.e(tag, "生日信息不完整,无法获取星座");
         return;
       }
       final result = await keyboardRepository.getKeyboardMemeExplain(

+ 1 - 1
lib/module/profile/edit/profile_edit_controller.dart

@@ -269,7 +269,7 @@ class ProfileEditController extends BaseController {
       AtmobLog.d(tag, 'clickSaveButton error: ${error.message}');
     } else {
       ErrorHandler.toastError(error);
-      AtmobLog.d(tag, 'clickSaveButton error: $error');
+      AtmobLog.d(tag, 'clickSaveButton error1: $error');
     }
   }
 

+ 15 - 26
lib/module/profile/profile_controller.dart

@@ -29,9 +29,10 @@ class ProfileController extends BaseController {
   KeyboardInfo get currentCustomKeyboardInfo =>
       _currentCustomKeyboardInfo.value;
 
-  final RxList<KeyboardInfo> _customKeyboardInfoList = RxList();
+  RxList<KeyboardInfo> get customKeyboardInfoList =>
+      keyboardRepository.customKeyboardInfoList;
+
 
-  RxList<KeyboardInfo> get keyboardInfoList => _customKeyboardInfoList;
 
   ProfileController(this.keyboardRepository, this.accountRepository) {
     AtmobLog.d(tag, '....init');
@@ -43,26 +44,14 @@ class ProfileController extends BaseController {
     getCustomKeyboard();
   }
 
-  //   获取定制键盘
   Future<void> getCustomKeyboard() async {
-    AtmobLog.i(tag, 'getCustomKeyboard');
-    keyboardRepository.getKeyboardList(type: KeyboardType.custom.name).then((
-      keyboardListResponse,
-    ) {
-      AtmobLog.i(
-        tag,
-        'keyboardListResponse: ${keyboardListResponse.keyboardInfos}',
-      );
-      _customKeyboardInfoList.value = keyboardListResponse.keyboardInfos;
-
-      //检查是否是选择的键盘,如果没有选择的键盘,默认选择第一个
-      if (_customKeyboardInfoList.isNotEmpty) {
-        _currentCustomKeyboardInfo.value = _customKeyboardInfoList.firstWhere(
-          (element) => element.isChoose == true,
-          orElse: () => _customKeyboardInfoList.first,
-        );
-      }
-    });
+    keyboardRepository.refreshData();
+    print('getCustomKeyboard: ${customKeyboardInfoList.length}');
+    if (keyboardRepository.chooseKeyboardInfo.value == null) {
+      return;
+    }
+    _currentCustomKeyboardInfo.value =
+        keyboardRepository.chooseKeyboardInfo.value!;
   }
 
   clickOnChangeKeyboard(KeyboardInfo keyboardInfo) {
@@ -86,11 +75,11 @@ class ProfileController extends BaseController {
   clickSaveButton() async {
     AtmobLog.d(tag, "clickSaveButton");
     final keyboardInfo = _currentCustomKeyboardInfo.value;
-    if (_currentCustomKeyboardInfo.value.isChoose == true) {
-      ToastUtil.show("当前键盘已选择");
-      clickBack();
-      return;
-    }
+    // if (_currentCustomKeyboardInfo.value.isChoose == true) {
+    //   ToastUtil.show("当前键盘已选择");
+    //   clickBack();
+    //   return;
+    // }
     if (keyboardInfo.id?.isNotEmpty == true) {
       try {
         await keyboardRepository.keyboardChoose(keyboardId: keyboardInfo.id!);

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

@@ -48,7 +48,7 @@ class ProfilePage extends BasePage<ProfileController> {
               ),
               // 键盘列表
               Obx(() {
-                if (controller.keyboardInfoList.isEmpty) {
+                if (controller.customKeyboardInfoList.isEmpty) {
                   return  SliverToBoxAdapter(child:  _buildKeyboardListItem(
                     keyboardInfo: null,
                     isChosen: false,
@@ -58,7 +58,7 @@ class ProfilePage extends BasePage<ProfileController> {
                 return SliverList(
                   delegate: SliverChildBuilderDelegate((context, index) {
                     KeyboardInfo keyboardInfo =
-                    controller.keyboardInfoList[index];
+                    controller.customKeyboardInfoList[index];
                     return Obx(() {
                       return _buildKeyboardListItem(
                         keyboardInfo: keyboardInfo,
@@ -66,7 +66,7 @@ class ProfilePage extends BasePage<ProfileController> {
                         hasKeyboard: true,
                       );
                     });
-                  }, childCount: controller.keyboardInfoList.length),
+                  }, childCount: controller.customKeyboardInfoList.length),
                 );
               }),
 

+ 451 - 0
lib/module/store/new_discount/new_discount_controller.dart

@@ -0,0 +1,451 @@
+import 'package:agile_pay/flutter_pay.dart';
+import 'package:carousel_slider/carousel_controller.dart';
+import 'package:carousel_slider/carousel_options.dart';
+import 'package:flutter/material.dart';
+import 'package:injectable/injectable.dart';
+import 'package:keyboard/base/base_controller.dart';
+import 'package:get/get.dart';
+import 'package:keyboard/data/bean/character_info.dart';
+import '../../../data/api/response/user_info_response.dart';
+import '../../../data/bean/goods_info.dart';
+import '../../../data/bean/member_info.dart';
+import '../../../data/bean/pay_way_info.dart';
+import '../../../data/bean/wechat_payment_sign_bean.dart';
+import '../../../data/consts/error_code.dart';
+import '../../../data/consts/payment_type.dart';
+import '../../../data/repository/account_repository.dart';
+import '../../../data/repository/store_repository.dart';
+import '../../../dialog/alipay_qr_code_dialog.dart';
+import '../../../dialog/loading_dialog.dart';
+import '../../../dialog/login/login_dialog.dart';
+import '../../../dialog/member_agreement_dialog.dart';
+import '../../../dialog/payment_fail_dialog.dart';
+import '../../../dialog/payment_success_dialog.dart';
+import '../../../dialog/wechat_qr_code_dialog.dart';
+import '../../../resource/assets.gen.dart';
+import '../../../resource/string.gen.dart';
+import '../../../utils/async_util.dart';
+import '../../../utils/atmob_log.dart';
+import '../../../utils/date_util.dart';
+import '../../../utils/error_handler.dart';
+import '../../../utils/http_handler.dart';
+import '../../../utils/payment_status_manager.dart';
+import '../../../utils/toast_util.dart';
+import '../store_banner_bean.dart';
+
+@injectable
+class NewDiscountController extends BaseController
+    implements PaymentStatusCallback {
+  final String tag = "NewDiscountController";
+
+  final StoreRepository storeRepository;
+
+  final AccountRepository accountRepository;
+
+  final PaymentStatusManager paymentStatusManager;
+
+  final RxList<GoodsInfo> goodsInfoList = <GoodsInfo>[].obs;
+
+  final Rxn<GoodsInfo> _selectedGoodsInfoItem = Rxn<GoodsInfo>();
+
+  GoodsInfo? get selectedGoodsInfoItem => _selectedGoodsInfoItem.value;
+
+  final Rxn<PayWayInfo> _selectedPayWay = Rxn<PayWayInfo>();
+
+  PayWayInfo? get selectedPayWay => _selectedPayWay.value;
+
+  final RxList<PayWayInfo> payWayList = <PayWayInfo>[].obs;
+
+  CancelableFuture? _storeDataFuture;
+
+  bool get isLogin => accountRepository.isLogin.value;
+
+  final isAgree = false.obs;
+
+  MemberInfo? get memberStatusInfo => accountRepository.memberStatusInfo.value;
+
+  UserInfoResponse? get userInfo => accountRepository.userInfo.value;
+
+  final RxList<GoodsInfo> filteredGoodsList = <GoodsInfo>[].obs;
+
+  final RxList<CharacterInfo> _charactersList = <CharacterInfo>[].obs;
+  List<CharacterInfo> get charactersList => _charactersList;
+
+
+
+  final CarouselSliderController carouselSliderController =
+      CarouselSliderController();
+
+  final RxInt _currentBanner = 0.obs;
+
+  int get currentBannerIndex => _currentBanner.value;
+
+  /// 轮播图控制器
+  final List<AssetGenImage> bannerList = [
+    Assets.images.iconNewDiscountBanner1,
+
+    Assets.images.iconNewDiscountBanner2,
+
+    Assets.images.iconNewDiscountBanner3,
+  ];
+
+  NewDiscountController(
+    this.storeRepository,
+    this.accountRepository,
+    this.paymentStatusManager,
+  );
+
+  onBannerChanged(int index, CarouselPageChangedReason reason) {
+    _currentBanner.value = index;
+  }
+
+  void refreshStoreData() {
+    _storeDataFuture?.cancel();
+    _storeDataFuture = AsyncUtil.retryWithExponentialBackoff(
+      () => _requestGoodsInfoList(),
+      4,
+    );
+    _storeDataFuture?.catchError((error) {
+      ErrorHandler.toastError(error);
+    });
+  }
+
+  Future<void> _requestGoodsInfoList() async {
+    try {
+      final response = await storeRepository.getMemberNewUserGoodsList();
+      goodsInfoList.clear();
+      payWayList.clear();
+      _charactersList.clear();
+      _selectedGoodsInfoItem.value = null;
+      if (response.goodsInfoList?.isNotEmpty == true) {
+        goodsInfoList.addAll(response.goodsInfoList!);
+        _selectedGoodsInfoItem.value = goodsInfoList.first;
+      }
+      if (response.payInfoList?.isNotEmpty == true) {
+        payWayList.addAll(response.payInfoList!);
+        _selectedPayWay.value = payWayList.first;
+        updateFilteredGoodsList();
+      }
+      if (response.characterInfos?.isNotEmpty == true) {
+        _charactersList.addAll(response.characterInfos!);
+      }
+    } catch (e) {
+      AtmobLog.e(tag, e.toString());
+    }
+  }
+
+  void updateFilteredGoodsList() {
+    if (_selectedPayWay.value == null) {
+      return;
+    }
+    _selectedGoodsInfoItem.value = null;
+    int selectedPayWayId = _selectedPayWay.value!.id;
+    filteredGoodsList.assignAll(
+      goodsInfoList.where((goods) {
+        return goods.payOptionIds == null ||
+            goods.payOptionIds!.contains(selectedPayWayId);
+      }).toList(),
+    );
+    if (filteredGoodsList.isNotEmpty) {
+      _selectedGoodsInfoItem.value = filteredGoodsList.first;
+    }
+  }
+
+  void onGoodsItemClick(GoodsInfo goodsInfo) {
+    _selectedGoodsInfoItem.value = goodsInfo;
+    debugPrint("'item: ${goodsInfo.toJson()}");
+  }
+
+  void clickPayWaySwitch() {
+    if (payWayList.isNotEmpty) {
+      int currentIndex = payWayList.indexOf(
+        _selectedPayWay.value ?? payWayList.first,
+      );
+      int nextIndex = (currentIndex + 1) % payWayList.length;
+      _selectedPayWay.value = payWayList[nextIndex];
+      updateFilteredGoodsList(); // 切换支付方式后,更新商品列表
+    }
+  }
+
+  void clickBack() {
+    Get.back();
+  }
+
+  void clickPayNow() {
+    if (selectedGoodsInfoItem == null) {
+      ToastUtil.show(StringName.memberPleaseChoiceGoods);
+      return;
+    }
+    if (selectedPayWay == null) {
+      ToastUtil.show(StringName.memberPleaseChoicePayment);
+      return;
+    }
+    if (!isAgree.value) {
+      MemberAgreementDialog.show(
+        btnConfirm: () {
+          isAgree.value = true;
+          clickPayNow();
+        },
+      );
+      return;
+    }
+    AtmobLog.d(tag, 'clickPayNow: ${selectedGoodsInfoItem!.toJson()}');
+    final buyGoods = selectedGoodsInfoItem!;
+    final buyPayWay = selectedPayWay!;
+
+    int goodsId = buyGoods.id;
+    int payPlatform = buyPayWay.payPlatform;
+    int payMethod = buyPayWay.payMethod;
+    int payWayType = getPayWayType(
+      payMethod: payMethod,
+      payPlatform: payPlatform,
+    );
+
+    LoadingDialog.show(StringName.payLoading);
+    storeRepository
+        .submitAndRequestPay(
+          goodsId: goodsId,
+          payPlatform: payPlatform,
+          payMethod: payMethod,
+        )
+        .then((response) {
+          if (payWayType == PayWayType.paymentWayWechat) {
+            _onWeChatPay(
+              response.outTradeNo,
+              response.wechatPayPrepayJson!,
+              payMethod,
+              buyGoods,
+              buyPayWay,
+            );
+          } else if (payWayType == PayWayType.paymentWayWechatScan) {
+            _onWechatScanPay(
+              response.outTradeNo,
+              response.wechatPayQrcodeUrl!,
+              buyPayWay,
+              buyGoods,
+            );
+          } else if (payWayType == PayWayType.paymentWayAlipay) {
+            _onAliPay(
+              response.outTradeNo,
+              response.alipayOrderString!,
+              payMethod,
+              buyGoods,
+              buyPayWay,
+            );
+          } else if (payWayType == PayWayType.paymentWayAlipayScan) {
+            _onAliScanPay(
+              response.outTradeNo,
+              response.alipayQrcodeHtml!,
+              buyPayWay,
+              buyGoods,
+            );
+          } else {
+            ToastUtil.show(StringName.payNotSupport);
+          }
+        })
+        .catchError((error) {
+          if (error is ServerErrorException) {
+            if (error.code == ErrorCode.payOrderError) {
+              refreshStoreData();
+              ToastUtil.show(error.message);
+            } else if (error.code == ErrorCode.noLoginError) {
+              ToastUtil.show(StringName.accountNoLogin);
+              LoginDialog.show();
+            } else {
+              ToastUtil.show(error.message);
+              paymentFail();
+            }
+          } else {
+            ToastUtil.show(StringName.memberPaymentFailed);
+            paymentFail();
+          }
+        })
+        .whenComplete(() {
+          LoadingDialog.hide();
+        });
+  }
+
+  void paymentFail() {
+    PaymentFailDialog.show(
+      btnConfirm: () {
+        clickPayNow();
+      },
+    );
+  }
+
+  void _onAliScanPay(
+    String outTradeNo,
+    String qrHtml,
+    PayWayInfo payWayInfo,
+    GoodsInfo goodsInfo,
+  ) {
+    AlipayQrCodeDialog.show(
+      qrCodeHtml: qrHtml,
+      loadSuccessCallback: () {
+        checkPaymentStatus(outTradeNo, payWayInfo, goodsInfo);
+      },
+      onCloseCallback: () async {
+        //关闭后再持续查询几秒
+        CustomLoadingDialog.show();
+        await Future.delayed(Duration(seconds: 4));
+        paymentStatusManager.removePollingSubscription(outTradeNo);
+        CustomLoadingDialog.hide();
+      },
+    );
+  }
+
+  void _onAliPay(
+    String outTradeNo,
+    String payJson,
+    int payMethod,
+    GoodsInfo buyGoods,
+    PayWayInfo buyPayWay,
+  ) {
+    final payInfo = AliPayInfo(payJson);
+    requestSdkPay(payInfo, outTradeNo, payMethod, buyGoods, buyPayWay);
+  }
+
+  void _onWeChatPay(
+    String outTradeNo,
+    String payJson,
+    int payMethod,
+    GoodsInfo buyGoods,
+    PayWayInfo buyPayWay,
+  ) {
+    final bean = WechatPaymentSignBean.stringToBean(payJson);
+    final payInfo = WechatPayInfo(
+      appId: bean.appId,
+      partnerId: bean.partnerId,
+      prepayId: bean.prepayId,
+      package: bean.package,
+      noncestr: bean.nonceStr,
+      timestamp: bean.timeStamp,
+      sign: bean.sign,
+    );
+    requestSdkPay(payInfo, outTradeNo, payMethod, buyGoods, buyPayWay);
+  }
+
+  void _onWechatScanPay(
+    String outTradeNo,
+    String qrCodeUrl,
+    PayWayInfo payWayInfo,
+    GoodsInfo goodsInfo,
+  ) {
+    WechatQrCodeDialog.show(
+      qrCodeUrl: qrCodeUrl,
+      loadSuccessCallback: () {
+        checkPaymentStatus(outTradeNo, payWayInfo, goodsInfo);
+      },
+      onCloseCallback: () async {
+        //关闭后再持续查询几秒
+        CustomLoadingDialog.show();
+        await Future.delayed(Duration(seconds: 4));
+        paymentStatusManager.removePollingSubscription(outTradeNo);
+        CustomLoadingDialog.hide();
+      },
+    );
+  }
+
+  void checkPaymentStatus(
+    String orderNo,
+    PayWayInfo payWayInfo,
+    GoodsInfo goodsInfo, {
+    String? receiptData,
+  }) {
+    paymentStatusManager.registerPaymentSuccessCallback(orderNo, this);
+    paymentStatusManager.checkPaymentStatus(
+      orderNo,
+      payWayInfo,
+      goodsInfo,
+      receiptData: receiptData,
+    );
+  }
+
+  void requestSdkPay(
+    dynamic payInfo,
+    String outTradeNo,
+    int payMethod,
+    GoodsInfo buyGoods,
+    PayWayInfo buyPayWay,
+  ) {
+    AgilePay.startPay(
+      payInfo,
+      success: (String? result) {
+        LoadingDialog.show(StringName.payQuerypayState);
+        checkPaymentStatus(
+          outTradeNo,
+          buyPayWay,
+          buyGoods,
+          receiptData: result,
+        );
+      },
+      payError: (int error, String? errorMessage) {
+        debugPrint('zk---payError: $error, $errorMessage');
+        paymentFail();
+        errorPayToast(error);
+
+        errorEventReport(payMethod);
+      },
+      error: (int errno, String? error) {
+        debugPrint('zk---error: $errno, $error');
+        errorPayToast(errno);
+        errorEventReport(payMethod);
+      },
+    );
+  }
+
+  void errorEventReport(int payMethod) {
+    if (payMethod == PayMethod.wechat) {
+      // EventHandler.report();
+    } else if (payMethod == PayMethod.alipay) {
+      // EventHandler.report();
+    } else if (payMethod == PayMethod.apple) {
+      // EventHandler.report();
+    }
+  }
+
+  void errorPayToast(int errno) {
+    if (errno == AgilePayCode.payCodeNotSupport) {
+      ToastUtil.show(StringName.payNotSupport);
+    } else if (errno == AgilePayCode.payCodeCancelError) {
+      ToastUtil.show(StringName.payUserCancel);
+    } else if (errno == AgilePayCode.payCodeWxEnvError) {
+      ToastUtil.show(StringName.payWxEvnError);
+    } else if (errno == AgilePayCode.payCodeNotConnectStore) {
+      ToastUtil.show(StringName.payNotConnectStore);
+    } else {
+      ToastUtil.show(StringName.payError);
+    }
+  }
+
+  @override
+  void onReady() {
+    super.onReady();
+    refreshStoreData();
+  }
+
+  @override
+  void onClose() {
+    super.onClose();
+    _storeDataFuture?.cancel();
+    paymentStatusManager.unregisterPaymentSuccessCallback(this);
+  }
+
+  @override
+  void onPaymentSuccess(
+    String orderNo,
+    PayWayInfo payWayInfo,
+    GoodsInfo goodsInfo,
+  ) {
+    LoadingDialog.hide();
+    PaymentSuccessDialog.show(
+      infoText:
+          memberStatusInfo?.permanent == true
+              ? StringName.paySuccessDialogPermanent
+              : "${goodsInfo.name}${DateUtil.fromMillisecondsSinceEpoch('yyyy年MM月dd日', memberStatusInfo?.endTimestamp ?? 0)}${StringName.paySuccessDialogDesc}",
+      btnConfirm: () {
+        AtmobLog.d(tag, 'onGoodsItemClick: ${goodsInfo.toJson()}');
+      },
+    );
+  }
+}

+ 634 - 0
lib/module/store/new_discount/new_discount_page.dart

@@ -0,0 +1,634 @@
+import 'package:flutter/src/widgets/framework.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:keyboard/base/base_page.dart';
+import 'package:keyboard/module/store/new_discount/new_discount_controller.dart';
+import 'package:flutter/material.dart';
+import 'package:carousel_slider/carousel_slider.dart';
+import 'package:get/get.dart';
+
+import '../../../data/bean/character_info.dart';
+import '../../../data/consts/payment_type.dart';
+import '../../../data/consts/web_url.dart';
+import '../../../resource/assets.gen.dart';
+import '../../../resource/colors.gen.dart';
+import '../../../resource/string.gen.dart';
+import '../../../router/app_pages.dart';
+import '../../../utils/styles.dart';
+import '../../../widget/auto_scroll_list_view.dart';
+import '../../../widget/click_text_span.dart';
+
+class NewDiscountPage extends BasePage<NewDiscountController> {
+  const NewDiscountPage({super.key});
+
+  /// 跳转
+  static void start() {
+    Get.toNamed(RoutePath.newDiscount);
+  }
+
+  @override
+  bool immersive() {
+    return true;
+  }
+
+  @override
+  backgroundColor() => Colors.white;
+
+  @override
+  Widget buildBody(BuildContext context) {
+    return Stack(
+      children: [
+        Positioned(top: 0.w, child: _buildBanner()),
+        Positioned(
+          top: 408.w,
+          child: Container(
+            decoration: BoxDecoration(
+              color: ColorName.white,
+              borderRadius: BorderRadius.only(
+                topLeft: Radius.circular(24.r),
+                topRight: Radius.circular(24.r),
+              ),
+            ),
+            width: 360.w,
+            height: 392.w,
+
+            child: SingleChildScrollView(
+              child: Expanded(
+                child: Column(
+                  children: [
+                    _buildGoodsCard(),
+                    SizedBox(height: 200.w),
+                  ],
+                ),
+              ),
+            ),
+          ),
+        ),
+        Positioned(top: 0, left: 0, right: 0, child: _buildTitle()),
+
+        Positioned(bottom: 0, left: 0, right: 0, child: _buildBuyButtonCard()),
+      ],
+    );
+  }
+
+  _buildTitle() {
+    return SafeArea(
+      child: Container(
+        alignment: Alignment.centerLeft,
+        padding: EdgeInsets.only(top: 16.h, left: 16.w, right: 16.w),
+        child: Row(
+          mainAxisAlignment: MainAxisAlignment.spaceBetween,
+          children: [
+            GestureDetector(
+              onTap: controller.clickBack,
+              child: Assets.images.iconStoreBack.image(
+                width: 32.w,
+                height: 32.w,
+              ),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildBanner() {
+    return SizedBox(
+      width: 360.w,
+      child: Stack(
+        children: [
+          CarouselSlider(
+            carouselController: controller.carouselSliderController,
+            options: CarouselOptions(
+              height: 438.w,
+              viewportFraction: 1,
+              initialPage: 0,
+              enableInfiniteScroll: true,
+              reverse: false,
+              autoPlay: true,
+              autoPlayInterval: const Duration(seconds: 3),
+              autoPlayAnimationDuration: const Duration(milliseconds: 500),
+              autoPlayCurve: Curves.fastOutSlowIn,
+              scrollDirection: Axis.horizontal,
+              onPageChanged: (index, reason) {
+                controller.onBannerChanged(index, reason);
+              },
+            ),
+
+            items:
+                controller.bannerList.map((item) {
+                  return item.image(width: 360.w, fit: BoxFit.contain);
+                }).toList(),
+          ),
+          Positioned(
+            bottom: 149.16.w,
+            left: 31.w,
+            child: Assets.images.iconNewDiscountCharacterTitle.image(
+              width: 296.w,
+              height: 39.w,
+            ),
+          ),
+          Positioned(
+            bottom: 211.13.w,
+            left: 42.w,
+            right: 0,
+            child: _buildIndicator(),
+          ),
+          Positioned(
+            bottom: 0,
+            left: 0,
+            right: 0,
+            child: _buildAutoCharacterList(),
+          ),
+        ],
+      ),
+    );
+  }
+
+  /// 指示器
+  _buildIndicator() {
+    return Row(
+      children:
+          controller.bannerList.asMap().entries.map((entry) {
+            return Obx(() {
+              final isSelectedBanner =
+                  controller.currentBannerIndex == entry.key;
+              return Row(
+                mainAxisAlignment: MainAxisAlignment.spaceAround,
+                children: [
+                  GestureDetector(
+                    onTap:
+                        () => controller.carouselSliderController.animateToPage(
+                          entry.key,
+                        ),
+                    child: AnimatedContainer(
+                      duration: const Duration(milliseconds: 300),
+                      margin: EdgeInsets.only(right: 5.w),
+                      height: 5.w,
+                      width: isSelectedBanner ? 10.w : 5.w,
+                      decoration: BoxDecoration(
+                        color:
+                            isSelectedBanner
+                                ? const Color(0xff483459)
+                                : const Color(0xffAFB4BF),
+                        borderRadius: BorderRadius.circular(5.r),
+                      ),
+                    ),
+                  ),
+                ],
+              );
+            });
+          }).toList(),
+    );
+  }
+
+  Widget _buildAutoCharacterList() {
+    return Obx(() {
+      if (controller.charactersList.isEmpty) {
+        return Container();
+      }
+      return SizedBox(
+        height: 130.w,
+        child: AutoScrollListView(
+          itemCount: (controller.charactersList.length / 2).ceil(),
+          scrollDirection: Axis.horizontal,
+          itemBuilder: (context, columnIndex) {
+            int rowCount = 2;
+            int startIndex = columnIndex * rowCount;
+            final List<CharacterInfo> columnItems =
+                controller.charactersList
+                    .skip(startIndex)
+                    .take(rowCount)
+                    .toList();
+            return Column(
+              children:
+                  columnItems.map((item) {
+                    final emoji = item.emoji ?? "";
+                    final name = item.name ?? "";
+                    return Padding(
+                      padding: EdgeInsets.symmetric(
+                        vertical: 4.w,
+                        horizontal: 4.w,
+                      ),
+                      child: Container(
+                        padding: EdgeInsets.symmetric(
+                          horizontal: 12.w,
+                          vertical: 6.w,
+                        ),
+                        decoration: ShapeDecoration(
+                          color: Colors.white,
+                          shape: RoundedRectangleBorder(
+                            side: BorderSide(
+                              width: 1.w,
+                              color: const Color(0xFFF4F1FF),
+                            ),
+                            borderRadius: BorderRadius.circular(31.r),
+                          ),
+                        ),
+                        child: Text(
+                          "$emoji$name",
+                          style: TextStyle(
+                            color: Colors.black.withAlpha(204),
+                            fontSize: 13.sp,
+                            fontWeight: FontWeight.w400,
+                          ),
+                        ),
+                      ),
+                    );
+                  }).toList(),
+            );
+          },
+        ),
+      );
+    });
+  }
+
+  Widget _buildBuyButtonCard() {
+    return Container(
+      width: 360.w,
+      padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 11.h),
+      decoration: ShapeDecoration(
+        color: Colors.white,
+        shape: RoundedRectangleBorder(
+          borderRadius: BorderRadius.only(
+            topLeft: Radius.circular(24.r),
+            topRight: Radius.circular(24.r),
+          ),
+        ),
+        shadows: [
+          BoxShadow(
+            color: Color(0x99FFE498),
+            blurRadius: 20,
+            offset: Offset(0, 0),
+            spreadRadius: 0,
+          ),
+        ],
+      ),
+      child: Column(
+        children: [
+          GestureDetector(
+            onTap: controller.clickPayNow,
+            child: Container(
+              width: 328.w,
+              height: 54.h,
+              decoration: ShapeDecoration(
+                gradient: LinearGradient(
+                  begin: Alignment(0.60, -0.39),
+                  end: Alignment(0.60, 0.95),
+                  colors: [
+                    const Color(0xFFF95FAC),
+                    const Color(0xFFFD5E4D),
+                    const Color(0xFFFD5E4D),
+                    const Color(0xFFFB8A3C),
+                  ],
+                ),
+                shape: RoundedRectangleBorder(
+                  borderRadius: BorderRadius.circular(30.55.r),
+                ),
+              ),
+              child: Center(
+                child: Text(
+                  StringName.storePayNow,
+                  style: Styles.getTextStyleWhiteW500(17.sp),
+                ),
+              ),
+            ),
+          ),
+          SizedBox(height: 11.h),
+          Row(
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: [
+              Obx(() {
+                return GestureDetector(
+                  behavior: HitTestBehavior.opaque,
+                  onTap: () {
+                    controller.isAgree.value = !controller.isAgree.value;
+                  },
+                  child:
+                      controller.isAgree.value
+                          ? Assets.images.iconStoreAgreePrivacy.image(
+                            width: 16.w,
+                            height: 16.w,
+                          )
+                          : SizedBox(
+                            child: Container(
+                              padding: EdgeInsets.all(1.w),
+                              width: 16.w,
+                              height: 16.w,
+                              child: Container(
+                                decoration: BoxDecoration(
+                                  shape: BoxShape.circle,
+                                  border: Border.all(
+                                    color: Colors.black.withAlpha(153),
+                                    width: 1.w,
+                                  ),
+                                ),
+                              ),
+                            ),
+                          ),
+                );
+              }),
+              Text.rich(
+                TextSpan(
+                  children: [
+                    TextSpan(
+                      text: StringName.textSpanIHaveReadAndAgree,
+                      style: TextStyle(
+                        color: Colors.black.withAlpha(153),
+                        fontSize: 10.sp,
+                        fontWeight: FontWeight.w400,
+                      ),
+                    ),
+                    ClickTextSpan(
+                      text: StringName.textSpanPrivacyPolicy,
+                      url: WebUrl.privacyPolicy,
+                    ),
+
+                    TextSpan(
+                      text: StringName.textSpanAnd,
+                      style: TextStyle(
+                        color: Colors.black.withAlpha(153),
+                        fontSize: 10.sp,
+                        fontWeight: FontWeight.w400,
+                      ),
+                    ),
+
+                    ClickTextSpan(
+                      text: StringName.textSpanServiceTerms,
+                      url: WebUrl.serviceAgreement,
+                    ),
+                  ],
+                ),
+              ),
+            ],
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildGoodsCard() {
+    return Container(
+      margin: EdgeInsets.symmetric(horizontal: 16.w),
+      padding: EdgeInsets.only(
+        top: 16.w,
+        left: 16.w,
+        right: 16.w,
+        bottom: 24.w,
+      ),
+      decoration: BoxDecoration(
+        color: Colors.white,
+        borderRadius: BorderRadius.only(
+          topLeft: Radius.circular(16.r),
+          topRight: Radius.circular(16.r),
+        ),
+      ),
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        mainAxisAlignment: MainAxisAlignment.start,
+        children: [
+
+          SizedBox(height: 16.h),
+          Obx(() {
+            return Column(
+              children:
+                  controller.filteredGoodsList.map((item) {
+                    return Obx(() {
+                      return GestureDetector(
+                        onTap: () => controller.onGoodsItemClick(item),
+                        child: _buildGoodsItem(
+                          item,
+                          controller.selectedGoodsInfoItem?.id == item.id,
+                        ),
+                      );
+                    });
+                  }).toList(),
+            );
+          }),
+          _buildPayWayCard(),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildPayWayCard() {
+    return GestureDetector(
+      onTap: () => controller.clickPayWaySwitch(),
+      child: Container(
+        height: 36.h,
+        padding: EdgeInsets.symmetric(horizontal: 10.w),
+        decoration: ShapeDecoration(
+          shape: RoundedRectangleBorder(
+            side: BorderSide(width: 1, color: const Color(0xFFECEBE0)),
+            borderRadius: BorderRadius.circular(10.r),
+          ),
+        ),
+        child: Row(
+          children: [
+            Text(
+              StringName.storePayWay,
+              style: Styles.getTextStyleBlack204W400(14.sp),
+            ),
+            const Spacer(),
+            Obx(() {
+              if (controller.selectedPayWay == null) {
+                return SizedBox.shrink();
+              }
+              return Row(
+                children: [
+                  Image.asset(
+                    getPaymentIconPath(
+                      payMethod: controller.selectedPayWay!.payMethod,
+                      payPlatform: controller.selectedPayWay!.payPlatform,
+                    ),
+                    width: 20.w,
+                    height: 20.w,
+                  ),
+                  SizedBox(width: 4.w),
+                  Text(
+                    controller.selectedPayWay?.title ?? '',
+                    style: Styles.getTextStyleBlack204W400(14.sp),
+                  ),
+                  SizedBox(width: 6.w),
+                  Assets.images.iconStoreSwitchPay.image(
+                    width: 20.w,
+                    height: 20.w,
+                    fit: BoxFit.fill,
+                  ),
+                ],
+              );
+            }),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildGoodsItem(item, bool isSelected) {
+    return Container(
+      margin: EdgeInsets.only(bottom: 8.h),
+
+
+      decoration: ShapeDecoration(
+        color: Colors.white,
+        shape: RoundedRectangleBorder(
+          side: BorderSide(
+            width: 2.w,
+            color: Colors.black.withAlpha( 26),
+          ),
+          borderRadius: BorderRadius.circular(16),
+        ),
+      ),
+      child: Row(
+        children: [
+          Text(
+            '¥${item.amountText}',
+            textAlign: TextAlign.center,
+            style:
+            isSelected
+                ? Styles.getTextStyleFF7F14W500(12.sp)
+                : Styles.getTextStyleWhiteW500(12.sp),
+          )
+          // Container(
+          //   width: 212.w,
+          //   height: 70.h,
+          //   decoration: ShapeDecoration(
+          //     gradient:
+          //         isSelected
+          //             ? LinearGradient(
+          //               begin: Alignment(-0.06, 0.50),
+          //               end: Alignment(1.14, 0.50),
+          //               colors: [
+          //                 const Color(0xFFFFF895),
+          //                 const Color(0xFFFFE941),
+          //               ],
+          //             )
+          //             : null,
+          //     color: isSelected ? null : const Color(0xFFFFFDEE),
+          //     shape: RoundedRectangleBorder(
+          //       side: BorderSide(width: 1, color: const Color(0xFFFEE86B)),
+          //       borderRadius: BorderRadius.circular(isSelected ? 8 : 10.r),
+          //     ),
+          //   ),
+          //   child: Stack(
+          //     children: [
+          //       if (isSelected)
+          //         IgnorePointer(
+          //           child: Assets.images.bgStoreSelectedItem.image(
+          //             width: 212.w,
+          //             height: 70.h,
+          //             fit: BoxFit.fill,
+          //           ),
+          //         ),
+          //       Padding(
+          //         padding: EdgeInsets.only(left: 16.w),
+          //         child: Column(
+          //           crossAxisAlignment: CrossAxisAlignment.start,
+          //           mainAxisAlignment: MainAxisAlignment.center,
+          //           children: [
+          //             Row(
+          //               children: [
+          //                 RichText(
+          //                   text: TextSpan(
+          //                     children: [
+          //                       TextSpan(
+          //                         text: '¥',
+          //                         style: Styles.getTextStyleFF663300W700(14.sp),
+          //                       ),
+          //                       TextSpan(
+          //                         text: item.priceDescNumber,
+          //                         style: Styles.getTextStyleFF663300W700(18.sp),
+          //                       ),
+          //                       TextSpan(
+          //                         text: item.priceDescUnit,
+          //                         style: Styles.getTextStyleFF663300W400(13.sp),
+          //                       ),
+          //                     ],
+          //                   ),
+          //                 ),
+          //                 if (item.mostDesc?.isNotEmpty == true)
+          //                   Container(
+          //                     padding: EdgeInsets.only(
+          //                       left: 16.w,
+          //                       top: 2.h,
+          //                       bottom: 2.h,
+          //                     ),
+          //                     decoration: BoxDecoration(
+          //                       image: DecorationImage(
+          //                         image: Assets.images.iconStoreMost.provider(),
+          //                         fit: BoxFit.cover,
+          //                         alignment: Alignment.bottomLeft,
+          //                       ),
+          //                     ),
+          //                     child: Text(
+          //                       item.mostDesc!,
+          //                       style: TextStyle(
+          //                         color: Colors.white,
+          //                         fontSize: 12.sp,
+          //                         fontWeight: FontWeight.w500,
+          //                         letterSpacing: -0.60,
+          //                       ),
+          //                     ),
+          //                   ),
+          //               ],
+          //             ),
+          //             Text(
+          //               item.description!,
+          //               style: Styles.getTextStyle99673300W400(12.sp),
+          //             ),
+          //           ],
+          //         ),
+          //       ),
+          //     ],
+          //   ),
+          // ),
+          // // 右侧
+          // Expanded(
+          //   child: Column(
+          //     mainAxisAlignment: MainAxisAlignment.center,
+          //     crossAxisAlignment: CrossAxisAlignment.center,
+          //     children: [
+          //       Text(
+          //         item.name,
+          //         style:
+          //             isSelected
+          //                 ? Styles.getTextStyleFFECBBW500(15.sp)
+          //                 : Styles.getTextStyleFF663300W500(15.sp),
+          //       ),
+          //       Container(
+          //         padding: EdgeInsets.symmetric(horizontal: 8.w),
+          //         decoration: ShapeDecoration(
+          //           color: isSelected ? const Color(0xFFFFECBB) : null,
+          //           gradient:
+          //               isSelected
+          //                   ? null
+          //                   : LinearGradient(
+          //                     begin: Alignment(0.77, -0.00),
+          //                     end: Alignment(0.77, 1.00),
+          //                     colors: [
+          //                       const Color(0xFFFF9416),
+          //                       const Color(0xFFFF7813),
+          //                     ],
+          //                   ),
+          //           shape: RoundedRectangleBorder(
+          //             borderRadius: BorderRadius.circular(
+          //               isSelected ? 17.r : 10.r,
+          //             ),
+          //           ),
+          //         ),
+          //         child: Text(
+          //           '¥${item.amountText}',
+          //           textAlign: TextAlign.center,
+          //           style:
+          //               isSelected
+          //                   ? Styles.getTextStyleFF7F14W500(12.sp)
+          //                   : Styles.getTextStyleWhiteW500(12.sp),
+          //         ),
+          //       ),
+          //     ],
+          //   ),
+          // ),
+        ],
+      ),
+    );
+  }
+}

+ 1 - 1
lib/module/store/store_controller.dart

@@ -474,7 +474,7 @@ class StoreController extends BaseController implements PaymentStatusCallback {
   }
 
   String getUserName() {
-    if (isLogin || phone != null && phone!.length > 4) {
+    if (isLogin && phone != null && phone!.length > 4) {
       return '${StringName.mineAccountLoggedDesc}${phone!.substring(phone!.length - 4)}';
     } else {
       return "";

+ 140 - 0
lib/module/user_profile/user_profile_controller.dart

@@ -0,0 +1,140 @@
+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 '../../data/api/response/user_info_response.dart';
+import '../../data/bean/default_avatar_info.dart';
+import '../../data/repository/config_repository.dart';
+import '../../resource/string.gen.dart';
+import '../../utils/atmob_log.dart';
+import '../change/birthday/change_birthday_page.dart';
+import '../change/gender/change_gender_page.dart';
+import '../change/nickname/change_nickname_page.dart';
+
+@injectable
+class UserProfileController extends BaseController {
+  final tag = "UserProfileController";
+
+  final ConfigRepository configRepository;
+
+  final AccountRepository accountRepository;
+
+  final RxList<String> _girlAvatars = <String>[].obs;
+
+  final RxList<String> _boyAvatars = <String>[].obs;
+
+  final RxString _userAvatarUrl = "".obs;
+
+  String get userAvatarUrl => _userAvatarUrl.value;
+
+  final RxnInt _currentGender = RxnInt(null);
+
+  int? get currentGender => _currentGender.value;
+
+  final Rxn<String> _currentBirthday = Rxn<String>(null);
+
+  String? get currentBirthday => _currentBirthday.value;
+
+  final RxnString _currentNickname = RxnString(null);
+
+  String? get currentNickname => _currentNickname.value;
+
+  String? get phone => accountRepository.loginPhoneNum.value;
+
+  bool get isLogin => accountRepository.isLogin.value;
+
+  UserInfoResponse? get userInfo => accountRepository.userInfo.value;
+
+  Rxn<DefaultAvatarInfo> get currentDefaultAvatarInfo =>
+      configRepository.defaultAvatarInfo;
+
+  UserProfileController(this.configRepository, this.accountRepository);
+
+  @override
+  void onInit() {
+    super.onInit();
+    updateAvatarListsAndSelectFirst(configRepository.defaultAvatarInfo.value);
+  }
+
+  void clickBack() {
+    Get.back();
+  }
+
+  void updateAvatarListsAndSelectFirst(DefaultAvatarInfo? info) {
+    _boyAvatars.assignAll(info?.maleAvatars ?? []);
+    _girlAvatars.assignAll(info?.femaleAvatars ?? []);
+    if (_currentGender.value == 1) {
+      _userAvatarUrl.value = _boyAvatars.isNotEmpty ? _boyAvatars.first : "";
+    } else {
+      _userAvatarUrl.value = _girlAvatars.isNotEmpty ? _girlAvatars.first : "";
+    }
+  }
+
+  void nextAvatar() {
+    AtmobLog.d(tag, "nextAvatar");
+
+    if (_currentGender.value == 1) {
+      int currentIndex = _boyAvatars.indexOf(_userAvatarUrl.value);
+      _userAvatarUrl.value =
+          _boyAvatars[(currentIndex + 1) % _boyAvatars.length];
+    } else {
+      int currentIndex = _girlAvatars.indexOf(_userAvatarUrl.value);
+      _userAvatarUrl.value =
+          _girlAvatars[(currentIndex + 1) % _girlAvatars.length];
+    }
+  }
+
+  void clickBirthday() async {
+    AtmobLog.d(tag, 'clickBirthday');
+    final result = await ChangeBirthdayPage.start(
+      birthday: _currentBirthday.value,
+    );
+    if (result != null) {
+      AtmobLog.d(tag, 'clickBirthday result: $result');
+      _currentBirthday.value = result;
+    }
+  }
+
+  String getUserName() {
+    if (isLogin && phone != null && phone!.length > 4) {
+      return '${StringName.mineAccountLoggedDesc}${phone!.substring(phone!.length - 4)}';
+    } else {
+      return StringName.mineAccountNoLogin;
+    }
+  }
+
+  void clickGender() async {
+    AtmobLog.d(tag, 'clickGender');
+    final result = await ChangeGenderPage.start(gender: _currentGender.value);
+    if (result != null) {
+      _currentGender.value = result;
+    }
+
+    // 有变化才改
+    if (result == _currentGender.value) return;
+    if (result == 1 && _boyAvatars.isNotEmpty) {
+      _userAvatarUrl.value = _boyAvatars.first;
+    } else if (_girlAvatars.isNotEmpty) {
+      _userAvatarUrl.value = _girlAvatars.first;
+    } else {
+      _userAvatarUrl.value = "";
+    }
+  }
+
+  void clickNickname() async {
+    AtmobLog.d(tag, 'clickNickname');
+    final result = await ChangeNicknamePage.start(
+      nickName: _currentNickname.value,
+    );
+    if (result != null) {
+      _currentNickname.value = result;
+    }
+  }
+
+  String get genderText {
+    if (_currentGender.value == 1) return '男';
+    if (_currentGender.value == 2) return '女';
+    return '请选择';
+  }
+}

+ 229 - 0
lib/module/user_profile/user_profile_page.dart

@@ -0,0 +1,229 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/src/widgets/framework.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:keyboard/base/base_page.dart';
+import 'package:keyboard/module/user_profile/user_profile_controller.dart';
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+
+import '../../resource/assets.gen.dart';
+import '../../resource/colors.gen.dart';
+import '../../resource/string.gen.dart';
+import '../../router/app_pages.dart';
+import '../../utils/styles.dart';
+import '../../widget/avatar/avatar_image_widget.dart';
+
+class UserProfilePage extends BasePage<UserProfileController> {
+  const UserProfilePage({super.key});
+
+  static start() {
+    Get.toNamed(RoutePath.userProfile);
+  }
+
+  @override
+  Color backgroundColor() {
+    return const Color(0xFFF6F5FA);
+  }
+
+  @override
+  bool immersive() {
+    return true;
+  }
+
+  @override
+  Widget buildBody(BuildContext context) {
+    return Stack(
+      children: [
+        IgnorePointer(child: Assets.images.bgMine.image(width: 360.w)),
+        SafeArea(
+          child: Stack(
+            children: [
+              Column(
+                crossAxisAlignment: CrossAxisAlignment.center,
+                children: [
+                  _buildTitle(),
+                  SizedBox(height: 70.w),
+                  _buildAvatar(),
+                  SizedBox(height: 12.w),
+                  _buildNameCard(),
+                  SizedBox(height: 28.w),
+                  Container(
+                    margin: EdgeInsets.symmetric(horizontal: 16.w),
+                    decoration: ShapeDecoration(
+                      color: Colors.white,
+                      shape: RoundedRectangleBorder(
+                        borderRadius: BorderRadius.circular(12.r),
+                      ),
+                    ),
+                    child: Column(
+                      children: [
+                        Obx(() {
+                          return _buildListItem(
+                            StringName.gender, controller.genderText,
+                            onTap: () {
+                              controller.clickGender();
+                            },
+                          );
+                        }),
+                        Divider(
+                          indent: 16.w,
+                          endIndent: 16.w,
+                          height: 1.h,
+                          color: Color(0xFFF5F4F9),
+                        ),
+                        Obx(() {
+                          return _buildListItem(
+                            StringName.birthday, controller.currentBirthday??"请选择生日",
+                            onTap: () {
+                              controller.clickBirthday();
+                            },
+                          );
+                        }),
+                      ],
+                    ),
+                  ),
+                ],
+              ),
+            ],
+          ),
+        ),
+      ],
+    );
+  }
+
+  _buildNameCard() {
+    return GestureDetector(
+      onTap: () {
+        controller.clickNickname();
+      },
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.center,
+        crossAxisAlignment: CrossAxisAlignment.end,
+        children: [
+          Obx(() {
+            return Text(
+              controller.currentNickname ?? "请输入昵称",
+              textAlign: TextAlign.center,
+              style: TextStyle(
+                color: Colors.black,
+                fontSize: 20.sp,
+                fontWeight: FontWeight.w500,
+              ),
+            );
+          }),
+          Container(
+            child: Assets.images.iconCharacterCustomDetailEdit.image(
+              width: 20.r,
+              height: 20.r,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildAvatar() {
+    return GestureDetector(
+      onTap: controller.nextAvatar,
+      child: Obx(() {
+        return Stack(
+          children: [
+            SizedBox(
+              width: 84.w,
+              height: 84.w,
+              child:
+              controller.userAvatarUrl.isNotEmpty
+                  ? CircleAvatarWidget(
+                image:
+                Assets.images.iconKeyboardDefaultAvatar.provider(),
+                imageUrl: controller.userAvatarUrl,
+                size: 84.w,
+                borderColor: Colors.white,
+                borderWidth: 2.r,
+                placeholder: (_, __) {
+                  return const CupertinoActivityIndicator();
+                },
+              )
+                  : SizedBox(),
+            ),
+            Positioned(right: 0, bottom: 0, child: _buildAvatarSwitch()),
+          ],
+        );
+      }),
+    );
+  }
+
+  Widget _buildAvatarSwitch() {
+    return GestureDetector(
+      onTap: controller.nextAvatar,
+      child: SizedBox(
+        width: 32.r,
+        height: 32.r,
+        child: Assets.images.iconCharacterCustomDetailSwitch.image(
+          width: 32.r,
+          height: 32.r,
+          fit: BoxFit.contain,
+        ),
+      ),
+    );
+  }
+
+  Widget _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.personalProfile,
+            style: Styles.getTextStyleBlack204W500(17.sp),
+          ),
+          SizedBox(),
+        ],
+      ),
+    );
+  }
+
+
+
+  Widget _buildListItem(String title, String desc,
+      {required VoidCallback onTap}) {
+    return InkWell(
+      // 效果
+      onTap: onTap,
+      child: Container(
+        padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 12.h),
+        child: Row(
+          children: [
+            Text(
+              title,
+              style: TextStyle(
+                fontSize: 15.sp,
+                color: Colors.black.withAlpha(204),
+                fontWeight: FontWeight.w500,
+              ),
+            ),
+            const Spacer(),
+            Text(
+              desc,
+              style: TextStyle(
+                color: Colors.black.withAlpha(153),
+                fontSize: 14.sp,
+                fontWeight: FontWeight.w400,
+              ),
+            ),
+            Assets.images.iconAboutArrowLeft.image(width: 20.w, height: 20.w),
+          ],
+        ),
+      ),
+    );
+  }
+}

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

@@ -795,6 +795,23 @@ class $AssetsImagesGen {
   AssetGenImage get iconModeSwitchArrow =>
       const AssetGenImage('assets/images/icon_mode_switch_arrow.webp');
 
+  /// File path: assets/images/icon_new_discount_banner_1.webp
+  AssetGenImage get iconNewDiscountBanner1 =>
+      const AssetGenImage('assets/images/icon_new_discount_banner_1.webp');
+
+  /// File path: assets/images/icon_new_discount_banner_2.webp
+  AssetGenImage get iconNewDiscountBanner2 =>
+      const AssetGenImage('assets/images/icon_new_discount_banner_2.webp');
+
+  /// File path: assets/images/icon_new_discount_banner_3.webp
+  AssetGenImage get iconNewDiscountBanner3 =>
+      const AssetGenImage('assets/images/icon_new_discount_banner_3.webp');
+
+  /// File path: assets/images/icon_new_discount_character_title.webp
+  AssetGenImage get iconNewDiscountCharacterTitle => const AssetGenImage(
+    'assets/images/icon_new_discount_character_title.webp',
+  );
+
   /// File path: assets/images/icon_new_user_birthday_logo.webp
   AssetGenImage get iconNewUserBirthdayLogo =>
       const AssetGenImage('assets/images/icon_new_user_birthday_logo.webp');
@@ -1214,6 +1231,10 @@ class $AssetsImagesGen {
     iconMineVipDescArrow,
     iconMineVipOrderArrow,
     iconModeSwitchArrow,
+    iconNewDiscountBanner1,
+    iconNewDiscountBanner2,
+    iconNewDiscountBanner3,
+    iconNewDiscountCharacterTitle,
     iconNewUserBirthdayLogo,
     iconNewUserOpenNow,
     iconNewUserResultLoveLeft,

+ 53 - 42
lib/router/app_pages.dart

@@ -25,10 +25,12 @@ import 'package:keyboard/module/new_user/result/new_user_result_page.dart';
 import 'package:keyboard/module/profile/edit/profile_edit_controller.dart';
 import 'package:keyboard/module/profile/profile_controller.dart';
 import 'package:keyboard/module/profile/profile_page.dart';
+import 'package:keyboard/module/store/new_discount/new_discount_page.dart';
 import 'package:keyboard/module/store/store_controller.dart';
 import 'package:keyboard/module/store/store_page.dart';
 import 'package:keyboard/module/user_info/user_info_controller.dart';
 import 'package:keyboard/module/user_info/user_info_page.dart';
+import 'package:keyboard/module/user_profile/user_profile_controller.dart';
 
 import '../di/get_it.dart';
 import '../module/about/about_page.dart';
@@ -61,7 +63,9 @@ import '../module/new_user/new_user_page.dart';
 import '../module/new_user/step/gender/step_gender_logic.dart';
 import '../module/profile/edit/profile_edit_page.dart';
 import '../module/store/discount/discount_controller.dart';
+import '../module/store/new_discount/new_discount_controller.dart';
 import '../module/store/suprise/goods_surprise_controller.dart';
+import '../module/user_profile/user_profile_page.dart';
 
 abstract class AppPage {
   static final pages = <GetPage>[...generalPages];
@@ -101,9 +105,13 @@ abstract class RoutePath {
   static const newUser = '/newUser';
   static const newUserResult = '/newUserResult';
 
+  // 用户个人档案页面
+  static const userProfile = '/userProfile';
 
   // 图片预览页
   static const imageViewer = '/imageViewerPage';
+
+  static const newDiscount = '/newDiscount';
 }
 
 class AppBinding extends Bindings {
@@ -146,6 +154,8 @@ class AppBinding extends Bindings {
     lazyPut(() => getIt.get<NewUserResultController>());
 
     lazyPut(() => getIt.get<CustomDirectionEditController>());
+    lazyPut(() => getIt.get<UserProfileController>());
+    lazyPut(() => getIt.get<NewDiscountController>());
   }
 
   void lazyPut<S>(InstanceBuilderCallback<S> builder) {
@@ -154,46 +164,47 @@ class AppBinding extends Bindings {
 }
 
 final generalPages = [
-GetPage
-(
-name: RoutePath.mainTab, page: () => MainPage()),
-GetPage(name: RoutePath.login, page: () => LoginPage()),
-GetPage(name: RoutePath.feedback, page: () => FeedbackPage()),
-GetPage(name: RoutePath.about, page: () => AboutPage()),
-GetPage(name: RoutePath.browser, page: () => BrowserPage()),
-GetPage(name: RoutePath.keyboardManage, page: () => KeyboardManagePage()),
-GetPage(name: RoutePath.characterCustom, page: () => CharacterCustomPage()),
-GetPage(
-name: RoutePath.characterCustomDetail,
-page: () => CharacterCustomDetailPage(),
-),
-GetPage(
-name: RoutePath.characterCustomList,
-page: () => CharacterCustomListPage(),
-),
-GetPage(name: RoutePath.store, page: () => StorePage()),
-GetPage(name: RoutePath.profile, page: () => ProfilePage()),
-GetPage(name: RoutePath.profileEdit, page: () => ProfileEditPage()),
-// 键盘引导页
-GetPage(name: RoutePath.keyboardGuide, page: () => KeyboardGuidePage()),
-// 亲密度报告页
-GetPage(name: RoutePath.intimacyAnalyse, page: () => IntimacyAnalysePage()),
-// 亲密度分析上传页
-GetPage(
-name: RoutePath.intimacyAnalyseUpload,
-page: () => IntimacyAnalyseUploadPage(),
-),
-
-GetPage(name: RoutePath.changeNickname, page: () => ChangeNicknamePage()),
-GetPage(name: RoutePath.changeGender, page: () => ChangeGenderPage()),
-GetPage(name: RoutePath.changeBirthday, page: () => ChangeBirthdayPage()),
-GetPage(name: RoutePath.changeHobbies, page: () => ChangeHobbiesPage()),
-GetPage(name: RoutePath.changeCharacters, page: () => ChangeCharacterPage()),
-
-// 图片预览页
-GetPage(name: RoutePath.imageViewer, page: () => ImageViewerPage()),
-
-GetPage(name: RoutePath.userInfo, page: () => UserInfoPage()),
-GetPage(name: RoutePath.newUser, page: () => NewUserPage()),
-GetPage(name: RoutePath.newUserResult, page:()=> NewUserResultPage()),
+  GetPage(name: RoutePath.mainTab, page: () => MainPage()),
+  GetPage(name: RoutePath.login, page: () => LoginPage()),
+  GetPage(name: RoutePath.feedback, page: () => FeedbackPage()),
+  GetPage(name: RoutePath.about, page: () => AboutPage()),
+  GetPage(name: RoutePath.browser, page: () => BrowserPage()),
+  GetPage(name: RoutePath.keyboardManage, page: () => KeyboardManagePage()),
+  GetPage(name: RoutePath.characterCustom, page: () => CharacterCustomPage()),
+  GetPage(
+    name: RoutePath.characterCustomDetail,
+    page: () => CharacterCustomDetailPage(),
+  ),
+  GetPage(
+    name: RoutePath.characterCustomList,
+    page: () => CharacterCustomListPage(),
+  ),
+  GetPage(name: RoutePath.store, page: () => StorePage()),
+  GetPage(name: RoutePath.profile, page: () => ProfilePage()),
+  GetPage(name: RoutePath.profileEdit, page: () => ProfileEditPage()),
+  // 键盘引导页
+  GetPage(name: RoutePath.keyboardGuide, page: () => KeyboardGuidePage()),
+  // 亲密度报告页
+  GetPage(name: RoutePath.intimacyAnalyse, page: () => IntimacyAnalysePage()),
+  // 亲密度分析上传页
+  GetPage(
+    name: RoutePath.intimacyAnalyseUpload,
+    page: () => IntimacyAnalyseUploadPage(),
+  ),
+
+  GetPage(name: RoutePath.changeNickname, page: () => ChangeNicknamePage()),
+  GetPage(name: RoutePath.changeGender, page: () => ChangeGenderPage()),
+  GetPage(name: RoutePath.changeBirthday, page: () => ChangeBirthdayPage()),
+  GetPage(name: RoutePath.changeHobbies, page: () => ChangeHobbiesPage()),
+  GetPage(name: RoutePath.changeCharacters, page: () => ChangeCharacterPage()),
+
+  // 图片预览页
+  GetPage(name: RoutePath.imageViewer, page: () => ImageViewerPage()),
+
+  GetPage(name: RoutePath.userInfo, page: () => UserInfoPage()),
+  GetPage(name: RoutePath.newUser, page: () => NewUserPage()),
+  GetPage(name: RoutePath.newUserResult, page: () => NewUserResultPage()),
+  GetPage(name: RoutePath.userProfile, page: () => UserProfilePage()),
+
+  GetPage(name: RoutePath.newDiscount, page: () => NewDiscountPage()),
 ];