浏览代码

Merge branch 'v1.0.0' of http://git.atmob.com:28999/Atmob-Flutter/AiKeyboard2025 into v1.0.0

hezihao 8 月之前
父节点
当前提交
6d531f1c28

二进制
assets/images/bg_discount.webp


二进制
assets/images/bg_discount_content.webp


二进制
assets/images/bg_discount_tag_top.webp


二进制
assets/images/bg_discount_title.webp


二进制
assets/images/gif_discount_unlock_button.gif


二进制
assets/images/icon_discount_box_title.webp


二进制
assets/images/icon_discount_character.webp


二进制
assets/images/icon_discount_chat.webp


二进制
assets/images/icon_discount_close.webp


二进制
assets/images/icon_discount_social.webp


二进制
assets/images/icon_discount_subhead.webp


二进制
assets/images/icon_discount_title.webp


二进制
assets/images/icon_discount_tutorial.webp


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

@@ -192,5 +192,11 @@
     <string name="surprise_dialog_end_desc">后结束</string>
     <string name="surprise_dialog_only">仅需</string>
 
+    <string name="discount_dialog_end_desc">后失效</string>
+    <string name="discount_dialog_desc">一键教你留住Ta的心</string>
+    <string name="discount_dialog_chat">无限畅聊</string>
+    <string name="discount_dialog_tutorial">恋爱教程</string>
+    <string name="discount_dialog_character">百种人设</string>
+    <string name="discount_dialog_social">扩大社交</string>
 
 </resources>

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

@@ -40,6 +40,7 @@ import '../module/keyboard_manage/keyboard_manage_controller.dart' as _i922;
 import '../module/login/login_controller.dart' as _i1008;
 import '../module/main/main_controller.dart' as _i731;
 import '../module/mine/mine_controller.dart' as _i732;
+import '../module/store/discount/discount_controller.dart' as _i333;
 import '../module/store/store_controller.dart' as _i344;
 import '../module/store/suprise/goods_surprise_controller.dart' as _i935;
 import '../plugins/keyboard_android_platform.dart' as _i79;
@@ -58,6 +59,7 @@ extension GetItInjectableX on _i174.GetIt {
     gh.factory<_i923.BrowserController>(() => _i923.BrowserController());
     gh.factory<_i1008.LoginController>(() => _i1008.LoginController());
     gh.factory<_i731.MainController>(() => _i731.MainController());
+    gh.factory<_i333.DiscountController>(() => _i333.DiscountController());
     gh.singleton<_i361.Dio>(
       () => networkModule.createStreamDio(),
       instanceName: 'streamDio',

+ 6 - 2
lib/module/character_custom/character_custom_controller.dart

@@ -67,7 +67,9 @@ class CharacterCustomController extends BaseController {
       maxLength: currentCharacterCustomConfig?.maxHobbyWords ?? 10,
       hintText: StringName.customLabelHobbiesHint,
       clickCallback: (value) {
-        hobbiesLabelsList.add(Hobbies(name: value));
+        hobbiesLabelsList.map((e) => e.name).contains(value)
+            ? ToastUtil.show("添加失败,标签 $value 已存在")
+            : hobbiesLabelsList.add(Hobbies(name: value));
       },
     );
   }
@@ -79,7 +81,9 @@ class CharacterCustomController extends BaseController {
       maxLength: currentCharacterCustomConfig?.maxCharacterWords ?? 10,
       hintText: StringName.customLabelCharacterHint,
       clickCallback: (value) {
-        characterLabelsList.add(CharactersList(name: value));
+        characterLabelsList.map((e) => e.name).contains(value)
+            ? ToastUtil.show("添加失败,标签 $value 已存在")
+            : characterLabelsList.add(CharactersList(name: value));
       },
     );
   }

+ 85 - 76
lib/module/character_custom/character_custom_page.dart

@@ -9,6 +9,7 @@ import 'package:keyboard/utils/toast_util.dart';
 
 import '../../resource/assets.gen.dart';
 import '../../utils/styles.dart';
+import '../../widget/auto_scroll_list_view.dart';
 
 class CharacterCustomPage extends BasePage<CharacterCustomController> {
   const CharacterCustomPage({super.key});
@@ -285,6 +286,8 @@ class CharacterCustomPage extends BasePage<CharacterCustomController> {
     required bool isShowEmoji,
   }) {
     return Obx(() {
+      int rowCount = 3; // 3 行
+      int columnCount = (items.length / rowCount).ceil(); // 计算列数
       return Column(
         children: [
           Padding(
@@ -301,109 +304,115 @@ class CharacterCustomPage extends BasePage<CharacterCustomController> {
           Text(
             subtitle,
             style: TextStyle(
-              color: Color(0xFF755BAB).withValues(alpha: 0.6),
+              color: const Color(0xFF755BAB).withOpacity(0.6),
               fontSize: 12.sp,
               fontWeight: FontWeight.w500,
             ),
           ),
           Container(
             margin: EdgeInsets.only(top: 32.h, left: 21.w, right: 21.w),
-            alignment: Alignment.center,
-            child: Wrap(
-              alignment: WrapAlignment.center,
-              spacing: 8.0.r,
-              runSpacing: 10.r,
-              children: [
-                ...items.map((item) {
-                  final emoji = item.emoji ?? "";
-                  final name = item.name ?? "";
-                  return Obx(() {
-                    bool isSelected = selectedLabels.contains(item);
-                    return Stack(
-                      children: [
-                        ChoiceChip(
-                          label: Row(
-                            mainAxisSize: MainAxisSize.min,
-                            children: [
-                              Text(
+            height: 160.h,
+            child: AutoScrollListView(
+              itemCount: columnCount, // 列数
+              scrollDirection: Axis.horizontal,
+              itemBuilder: (context, columnIndex) {
+                //  3 个 item
+                int startIndex = columnIndex * rowCount;
+                List<dynamic> columnItems =
+                    items.skip(startIndex).take(rowCount).toList();
+
+                return Column(
+                  mainAxisSize: MainAxisSize.min,
+                  children:
+                      columnItems.map((item) {
+                        final emoji = item.emoji ?? "";
+                        final name = item.name ?? "";
+
+                        return Padding(
+                          padding: EdgeInsets.symmetric(
+                            vertical: 4.h,
+                            horizontal: 4.w,
+                          ),
+                          child: Obx(() {
+                            bool isSelected = selectedLabels.any(
+                              (selected) => selected.name == item.name,
+                            );
+                            return ChoiceChip(
+                              label: Text(
                                 isShowEmoji ? "$emoji$name" : name,
                                 style: TextStyle(
                                   color:
                                       isSelected
-                                          ? Color(0xFFF5F4F9)
-                                          : Color(0xFF755BAB),
+                                          ? Colors.white
+                                          : const Color(0xFF755BAB),
                                   fontSize: 14.sp,
                                   fontWeight: FontWeight.w400,
                                 ),
                               ),
-                            ],
-                          ),
-                          showCheckmark: false,
-                          selected: isSelected,
-                          selectedColor: Color(0xFFB782FF),
-                          backgroundColor: Colors.white,
-                          shape: RoundedRectangleBorder(
-                            side: BorderSide(
-                              width: 1.w,
-                              color: const Color(0x4C755BAB),
-                            ),
-                            borderRadius: BorderRadius.circular(31.r),
-                          ),
-                          onSelected: (selected) {
-                            onSelected(item);
-                          },
-                        ),
-                      ],
-                    );
-                  });
-                }),
-                Visibility(
-                  visible: isCustomEnabled,
-                  child: GestureDetector(
-                    onTap: onCustomClick,
-                    child: Container(
-                      margin: EdgeInsets.only(top: 3.h),
-                      child: DottedBorder(
-                        color: const Color(0xFFC9C2DB),
-                        strokeWidth: 1.0.w,
-                        borderType: BorderType.RRect,
-                        radius: Radius.circular(20.r),
-                        child: Container(
-                          width: 86.w,
-                          height: 33.h,
-                          alignment: Alignment.center,
-                          child: Row(
-                            mainAxisAlignment: MainAxisAlignment.center,
-                            children: [
-                              Assets.images.iconCharacterCustomPlus.image(
-                                width: 18.w,
-                                height: 18.w,
-                              ),
-                              Text(
-                                StringName.characterCustomCustomizable,
-                                style: TextStyle(
-                                  color: const Color(0xFFC9C2DB),
-                                  fontSize: 14.sp,
-                                  fontWeight: FontWeight.w500,
+                              showCheckmark: false,
+                              selected: isSelected,
+                              selectedColor: const Color(0xFFB782FF),
+                              backgroundColor: Colors.white,
+                              shape: RoundedRectangleBorder(
+                                side: BorderSide(
+                                  width: 1.w,
+                                  color: const Color(0x4C755BAB),
                                 ),
+                                borderRadius: BorderRadius.circular(31.r),
                               ),
-                            ],
+                              onSelected: (selected) {
+                                onSelected(item);
+                              },
+                            );
+                          }),
+                        );
+                      }).toList(),
+                );
+              },
+            ),
+          ),
+          Visibility(
+            visible: isCustomEnabled,
+            child: GestureDetector(
+              onTap: onCustomClick,
+              child: Container(
+                margin: EdgeInsets.only(top: 3.h),
+                child: DottedBorder(
+                  color: const Color(0xFFC9C2DB),
+                  strokeWidth: 1.0.w,
+                  borderType: BorderType.RRect,
+                  radius: Radius.circular(20.r),
+                  child: Container(
+                    width: 115.w,
+                    height: 33.h,
+                    alignment: Alignment.center,
+                    child: Row(
+                      mainAxisAlignment: MainAxisAlignment.center,
+                      children: [
+                        Assets.images.iconCharacterCustomPlus.image(
+                          width: 18.w,
+                          height: 18.w,
+                        ),
+                        Text(
+                          StringName.characterCustomCustomizable,
+                          style: TextStyle(
+                            color: const Color(0xFFC9C2DB),
+                            fontSize: 14.sp,
+                            fontWeight: FontWeight.w500,
                           ),
                         ),
-                      ),
+                      ],
                     ),
                   ),
                 ),
-              ],
+              ),
             ),
           ),
           Container(
             margin: EdgeInsets.only(top: 107.h, bottom: 32.h),
             child: _buildNextButton(
               isEnable: selectedLabels.isNotEmpty,
-              onTap: () {
-                nextClick();
-              },
+              onTap: nextClick,
             ),
           ),
         ],

+ 30 - 2
lib/module/store/discount/discount_controller.dart

@@ -1,9 +1,37 @@
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
+import 'package:get/get_rx/src/rx_types/rx_types.dart';
+import 'package:injectable/injectable.dart';
 import 'package:keyboard/base/base_controller.dart';
 
-class DiscountController extends BaseController{
+import '../../../data/bean/pay_way_info.dart';
+
+@injectable
+class DiscountController extends BaseController {
+  final RxList<PayWayInfo> payWayList = <PayWayInfo>[].obs;
+  final Rxn<PayWayInfo> _selectedPayWay = Rxn<PayWayInfo>();
+
+  PayWayInfo? get selectedPayWay => _selectedPayWay.value;
+
   @override
   void onInit() {
     super.onInit();
   }
 
-}
+  void clickPayWaySwitch() {
+    if (payWayList.isNotEmpty) {
+      int currentIndex = payWayList.indexOf(
+        _selectedPayWay.value ?? payWayList.first,
+      );
+      int nextIndex = (currentIndex + 1) % payWayList.length;
+      _selectedPayWay.value = payWayList[nextIndex];
+    }
+  }
+
+  void clickBack() {
+    SmartDialog.dismiss(tag: 'discountDialog');
+  }
+
+  void clickPayNow() {
+    print('clickPayNow');
+  }
+}

+ 531 - 8
lib/module/store/discount/discount_view.dart

@@ -1,22 +1,31 @@
-import 'package:flutter/src/widgets/framework.dart';
+import 'package:flutter/material.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:get/get.dart';
 import 'package:keyboard/base/base_view.dart';
 import 'package:keyboard/module/store/discount/discount_controller.dart';
-import 'package:get/get.dart';
-import 'package:flutter/material.dart';
+import 'package:keyboard/resource/string.gen.dart';
+
+import '../../../data/consts/payment_type.dart';
+import '../../../resource/assets.gen.dart';
+import '../../../utils/styles.dart';
+import '../../../widget/vertical_dots.dart';
 
 class DiscountView extends BaseView<DiscountController> {
   const DiscountView({super.key});
 
   @override
+  Color backgroundColor() {
+    return Colors.transparent;
+  }
+
+  @override
   Widget buildBody(BuildContext context) {
     return Container(
+      clipBehavior: Clip.hardEdge,
+      padding: EdgeInsets.only(bottom: 20.h),
+      width: 360.w,
       decoration: ShapeDecoration(
-        gradient: LinearGradient(
-          begin: Alignment(0.50, 0.00),
-          end: Alignment(0.50, 1.00),
-          colors: [const Color(0xFF9EED00), const Color(0xFF48D900)],
-        ),
+        color: Color(0xFFF7FDAD),
         shape: RoundedRectangleBorder(
           borderRadius: BorderRadius.only(
             topLeft: Radius.circular(28.r),
@@ -25,9 +34,523 @@ class DiscountView extends BaseView<DiscountController> {
         ),
       ),
       child: Column(
+        mainAxisAlignment: MainAxisAlignment.start,
         mainAxisSize: MainAxisSize.min,
+        children: [
+          buildTitleCard(),
+          SizedBox(height: 20.h),
+          buildBottomCard(),
+          Transform.translate(
+            offset: Offset(0, -30.h),
+            child: GestureDetector(
+              onTap: () {
+                controller.clickPayNow();
+              },
+              child: Assets.images.gifDiscountUnlockButton.image(width: 244.h),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  //标题和多重权益
+  Widget buildTitleCard() {
+    return Container(
+      width: 360.w,
+      height: 290.h,
+      decoration: BoxDecoration(
+        image: DecorationImage(
+          image: Assets.images.bgDiscountTitle.provider(),
+          fit: BoxFit.fill,
+        ),
+      ),
+      child: Stack(
+        children: [
+          Positioned(
+            top: 16.h,
+            left: 16.w,
+            child: GestureDetector(
+              onTap: () => controller.clickBack(),
+              child: Assets.images.iconDiscountClose.image(
+                width: 32.w,
+                height: 32.w,
+              ),
+            ),
+          ),
+          Positioned(
+            left: 0,
+            right: 0,
+            bottom: 0,
+            child: Column(
+              mainAxisAlignment: MainAxisAlignment.end,
+              crossAxisAlignment: CrossAxisAlignment.center,
+              children: [
+                Transform.translate(
+                  offset: Offset(0, -3),
+                  child: ClipPath(
+                    clipper: BottomCurveClipper(),
+                    child: Container(
+                      width: 328.w,
+                      height: 150.h,
+                      padding: EdgeInsets.only(left: 15.w, right: 15.w),
+                      decoration: ShapeDecoration(
+                        gradient: LinearGradient(
+                          begin: Alignment(0.50, 0.56),
+                          end: Alignment(0.50, 1.00),
+                          colors: [Colors.white, const Color(0xFFDFFFD7)],
+                        ),
+                        shape: RoundedRectangleBorder(
+                          borderRadius: BorderRadius.circular(25),
+                        ),
+                        shadows: [
+                          BoxShadow(
+                            color: Color(0xFFC6FF32),
+                            blurRadius: 4.r,
+                            offset: Offset(0, 0),
+                            spreadRadius: 0,
+                          ),
+                        ],
+                      ),
+                      child: Column(
+                        crossAxisAlignment: CrossAxisAlignment.center,
+                        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+                        children: [
+                          Row(
+                            children: [
+                              Assets.images.iconDiscountBoxTitle.image(
+                                width: 73.w,
+                                height: 22.h,
+                              ),
+                              Text(
+                                StringName.discountDialogDesc,
+                                style: TextStyle(
+                                  color: const Color(0xFF6EC1A8),
+                                  fontSize: 10.sp,
+                                  fontWeight: FontWeight.w400,
+                                ),
+                              ),
+                            ],
+                          ),
+                          Row(
+                            mainAxisAlignment: MainAxisAlignment.spaceAround,
+                            children: [
+                              _buildBox(
+                                colors: [
+                                  const Color(0xFFBDE7FF),
+                                  const Color(0xFFF3FBFF),
+                                ],
+                                image: Assets.images.iconDiscountChat,
+                                text: StringName.discountDialogChat,
+                              ),
+                              _buildBox(
+                                colors: [
+                                  const Color(0xFFFFD5DC),
+                                  const Color(0xFFFFFAFB),
+                                ],
+                                image: Assets.images.iconDiscountTutorial,
+                                text: StringName.discountDialogTutorial,
+                              ),
+                              _buildBox(
+                                colors: [
+                                  const Color(0xFFFFECBC),
+                                  const Color(0xFFFFFDDE),
+                                ],
+                                image: Assets.images.iconDiscountCharacter,
+                                text: StringName.discountDialogCharacter,
+                              ),
+                              _buildBox(
+                                colors: [
+                                  const Color(0xFFD2DBFF),
+                                  const Color(0xFFF1F4FF),
+                                ],
+                                image: Assets.images.iconDiscountSocial,
+                                text: StringName.discountDialogSocial,
+                              ),
+                            ],
+                          ),
+                        ],
+                      ),
+                    ),
+                  ),
+                ),
+              ],
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget buildBottomCard() {
+    return Container(
+      width: 330.w,
+      decoration: ShapeDecoration(
+        gradient: LinearGradient(
+          begin: Alignment(0.00, 0.50),
+          end: Alignment(1.00, 0.50),
+          colors: [const Color(0xFFB2FB52), const Color(0xFF9CFD27)],
+        ),
+        shape: RoundedRectangleBorder(
+          side: BorderSide(width: 1.w, color: Colors.white),
+          borderRadius: BorderRadius.circular(20.r),
+        ),
+      ),
+      child: Stack(
+        children: [
+          Positioned(
+            child: IgnorePointer(
+              child: Opacity(
+                opacity: 0.4,
+                child: Assets.images.bgDiscountContent.image(),
+              ),
+            ),
+          ),
+
+          Positioned(
+            child: Column(
+              children: [
+                _buildDescTime(),
+                Container(
+                  width: 330.w,
+                  decoration: ShapeDecoration(
+                    color: Colors.white,
+                    shape: RoundedRectangleBorder(
+                      borderRadius: BorderRadius.circular(20.r),
+                    ),
+                  ),
+                  child: SingleChildScrollView(
+                    child: Column(
+                      mainAxisAlignment: MainAxisAlignment.center,
+                      crossAxisAlignment: CrossAxisAlignment.center,
+                      children: [
+                        SizedBox(height: 12.h),
+                        buildGoodsItem(
+                          tagTop: true,
+                          tagTopDesc: "秒杀价",
+                          title: "测试",
+                          tagRight: true,
+                          tagRightDesc: "买断价",
+                          selected: false,
+                          onTap: () {},
+                        ),
+                        SizedBox(height: 12.h),
+                        buildGoodsItem(
+                          tagTop: true,
+                          tagTopDesc: "秒杀价",
+                          title: "测试",
+                          tagRight: true,
+                          tagRightDesc: "买断价",
+                          selected: true,
+                          onTap: () {},
+                        ),
+                        SizedBox(height: 12.h),
+                        _buildPayWayCard(),
+
+                        Container(
+                          padding: EdgeInsets.symmetric(horizontal: 16.w),
+                          alignment: Alignment.centerLeft,
+                          child: Text(
+                            '*首周18.8元,后续38元/周,可随时取消',
+                            style: TextStyle(
+                              color: const Color(0x99673300),
+                              fontSize: 10.sp,
+                              fontWeight: FontWeight.w400,
+                              height: 2,
+                            ),
+                          ),
+                        ),
+                        SizedBox(height: 54.h),
+                      ],
+                    ),
+                  ),
+                ),
+              ],
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget buildGoodsItem({
+    required bool tagTop,
+    required String? tagTopDesc,
+    required bool tagRight,
+    required String? tagRightDesc,
+    required String title,
+
+    required bool selected,
+    required VoidCallback onTap,
+  }) {
+    return GestureDetector(
+      onTap: onTap,
+      child: Stack(
+        clipBehavior: Clip.none,
+        children: [
+          Container(
+            padding: EdgeInsets.symmetric(horizontal: 16.w),
+            height: 72.h,
+            width: 298.w,
+            decoration: ShapeDecoration(
+              color: selected ? Color(0xFFFFF9BB) : Color(0xFFFFFDE2),
+              shape: RoundedRectangleBorder(
+                side: BorderSide(
+                  width: 2.w,
+                  color: selected ? Color(0xFFFF7F14) : Color(0xFFFFFAC1),
+                ),
+                borderRadius: BorderRadius.circular(16.r),
+              ),
+            ),
+            child: Row(
+              children: [
+                Text(
+                  title,
+                  style: TextStyle(
+                    fontSize: 22,
+                    fontWeight: FontWeight.bold,
+                    color: Color(0xFF663300),
+                  ),
+                ),
+                SizedBox(width: 8),
+                if (tagRight == tagRight) // 只有“买断价”才显示这个标签
+                  Container(
+                    padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
+                    decoration: BoxDecoration(
+                      color: Color(0xFFFFD5A0),
+                      borderRadius: BorderRadius.circular(6.r),
+                    ),
+                    child: Text(
+                      tagRightDesc ?? '',
+                      style: TextStyle(fontSize: 12, color: Color(0xFFBB5A00)),
+                    ),
+                  ),
+                Spacer(),
+                Container(
+                  width: 20.w,
+                  height: 20.w,
+                  decoration: BoxDecoration(
+                    shape: BoxShape.circle,
+                    border: Border.all(
+                      color: selected ? Color(0xFFFF7F14) : Color(0xFFFEE86B),
+                      width: 2.w,
+                    ),
+                    color: selected ? Color(0xFFFF8400) : Colors.white,
+                  ),
+                  child:
+                      selected
+                          ? Icon(Icons.check, color: Colors.white, size: 16)
+                          : null,
+                ),
+              ],
+            ),
+          ),
+          if (tagTop == true) // 只对“秒杀价”显示上面的标签
+            Positioned(
+              top: -6.h,
+
+              child: Container(
+                padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 2.h),
+                decoration: BoxDecoration(
+                  image: DecorationImage(
+                    image: Assets.images.bgDiscountTagTop.provider(),
+                    fit: BoxFit.fill,
+                  ),
+                  borderRadius: BorderRadius.circular(4.r),
+                ),
+                child: Text(
+                  tagTopDesc ?? '',
+                  style: TextStyle(
+                    fontSize: 12.sp,
+                    color: Colors.white,
+                    fontWeight: FontWeight.w600,
+                  ),
+                ),
+              ),
+            ),
+        ],
+      ),
+    );
+  }
+
+  // 倒计时
+  _buildDescTime() {
+    return Container(
+      padding: EdgeInsets.only(
+        left: 16.w,
+        right: 16.w,
+        top: 12.h,
+        bottom: 12.h,
+      ),
+      child: Row(
+        crossAxisAlignment: CrossAxisAlignment.center,
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: [
+          Assets.images.iconDiscountSubhead.image(width: 90.w, height: 20.h),
+          Row(
+            children: [
+              _buildTimeBox("00"),
+              VerticalDots(
+                height: 8.h,
+                dotSize: 2.w,
+                color: const Color(0xFF2F640B),
+                horizontalPadding: 5.w,
+              ),
+              _buildTimeBox("00"),
+              VerticalDots(
+                height: 8.h,
+                dotSize: 2.w,
+                color: const Color(0xFF2F640B),
+                horizontalPadding: 5.w,
+              ),
+              _buildTimeBox("00"),
+              SizedBox(width: 6.w),
+              Text(
+                StringName.discountDialogEndDesc,
+                style: TextStyle(
+                  color: const Color(0xFF2F640B),
+                  fontSize: 10.sp,
+                  fontWeight: FontWeight.w500,
+                ),
+              ),
+            ],
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildTimeBox(String value) {
+    return Container(
+      width: 18.w,
+      height: 15.h,
+      decoration: ShapeDecoration(
+        color: Colors.white,
+        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4.r)),
+      ),
+      child: Center(
+        child: Text(
+          value,
+          style: TextStyle(
+            color: const Color(0xFF2F640B),
+            fontSize: 10.sp,
+            fontWeight: FontWeight.w500,
+          ),
+        ),
+      ),
+    );
+  }
+
+  // 功能模块
+  Widget _buildBox({
+    required List<Color> colors,
+    required AssetGenImage image,
+    required String text,
+  }) {
+    return Container(
+      width: 70.w,
+      height: 76.w,
+      decoration: ShapeDecoration(
+        gradient: LinearGradient(
+          begin: Alignment(0.50, 0.00),
+          end: Alignment(0.50, 1.00),
+          colors: colors,
+        ),
+        shape: RoundedRectangleBorder(
+          side: BorderSide(width: 1, color: Colors.white),
+          borderRadius: BorderRadius.circular(10.r),
+        ),
+      ),
+      child: Column(
+        mainAxisAlignment: MainAxisAlignment.center,
+        children: [
+          image.image(width: 28.w, height: 28.w),
+          SizedBox(height: 4.h),
+          Text(
+            text,
+            style: TextStyle(
+              color: const Color(0xFF4A4B28),
+              fontSize: 13.sp,
+              fontWeight: FontWeight.w500,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
 
+  Widget _buildPayWayCard() {
+    return GestureDetector(
+      onTap: () => controller.clickPayWaySwitch(),
+      child: Container(
+        height: 36.h,
+        margin: EdgeInsets.symmetric(horizontal: 17.w),
+        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,
+                  ),
+                ],
+              );
+            }),
+          ],
+        ),
       ),
     );
   }
 }
+
+// 底部曲线剪裁
+class BottomCurveClipper extends CustomClipper<Path> {
+  @override
+  Path getClip(Size size) {
+    Path path = Path();
+    path.lineTo(0, size.height - 20);
+    path.quadraticBezierTo(
+      size.width / 2,
+      size.height,
+      size.width,
+      size.height - 20,
+    );
+    path.lineTo(size.width, 0);
+    path.close();
+    return path;
+  }
+
+  @override
+  bool shouldReclip(CustomClipper<Path> oldClipper) => false;
+}

+ 1 - 29
lib/module/store/suprise/surprise_dialog.dart

@@ -9,6 +9,7 @@ import '../../../resource/assets.gen.dart';
 import '../../../resource/colors.gen.dart';
 import '../../../utils/styles.dart';
 import '../../../widget/horizontal_dashed_line.dart';
+import '../../../widget/vertical_dots.dart';
 import 'goods_surprise_controller.dart';
 
 class SurpriseDialog {
@@ -271,33 +272,4 @@ class SurpriseDialog {
   }
 }
 
-class VerticalDots extends StatelessWidget {
-  final double dotSize;
-  final double height;
-  final Color? color;
 
-  const VerticalDots({
-    super.key,
-    required this.dotSize,
-    required this.height,
-    this.color = Colors.white,
-  });
-
-  @override
-  Widget build(BuildContext context) {
-    return SizedBox(
-      width: 2.w,
-      height: 8.h,
-      child: Column(
-        mainAxisAlignment: MainAxisAlignment.spaceBetween,
-        children: List.generate(2, (index) {
-          return Container(
-            width: dotSize,
-            height: dotSize,
-            decoration: BoxDecoration(color: color, shape: BoxShape.circle),
-          );
-        }),
-      ),
-    );
-  }
-}

+ 17 - 0
lib/module/store/ticket/discount_ticket_dialog.dart

@@ -119,6 +119,7 @@ class DiscountTicketDialog {
                                                       fontSize: 27.sp,
                                                       fontWeight:
                                                           FontWeight.w700,
+                                                      height: 0,
                                                     ),
                                                   ),
                                                   TextSpan(
@@ -130,6 +131,7 @@ class DiscountTicketDialog {
                                                       color: const Color(
                                                         0xFFF55208,
                                                       ),
+                                                      height: 0,
                                                       fontSize: 36.sp,
                                                       fontWeight:
                                                           FontWeight.w700,
@@ -139,6 +141,21 @@ class DiscountTicketDialog {
                                               ),
                                             );
                                           }),
+                                          Obx(() {
+                                            return Text(
+                                              controller
+                                                      .secondAmount
+                                                      ?.description ??
+                                                  "",
+                                              style: TextStyle(
+                                                color: Colors.black.withAlpha(
+                                                  66,
+                                                ),
+                                                fontSize: 10.sp,
+                                                fontWeight: FontWeight.w400,
+                                              ),
+                                            );
+                                          }),
                                         ],
                                       ),
                                     ),

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

@@ -81,6 +81,22 @@ class $AssetsImagesGen {
   AssetGenImage get bgCharacterGirlBanner =>
       const AssetGenImage('assets/images/bg_character_girl_banner.webp');
 
+  /// File path: assets/images/bg_discount.webp
+  AssetGenImage get bgDiscount =>
+      const AssetGenImage('assets/images/bg_discount.webp');
+
+  /// File path: assets/images/bg_discount_content.webp
+  AssetGenImage get bgDiscountContent =>
+      const AssetGenImage('assets/images/bg_discount_content.webp');
+
+  /// File path: assets/images/bg_discount_tag_top.webp
+  AssetGenImage get bgDiscountTagTop =>
+      const AssetGenImage('assets/images/bg_discount_tag_top.webp');
+
+  /// File path: assets/images/bg_discount_title.webp
+  AssetGenImage get bgDiscountTitle =>
+      const AssetGenImage('assets/images/bg_discount_title.webp');
+
   /// File path: assets/images/bg_keyboard_manage.webp
   AssetGenImage get bgKeyboardManage =>
       const AssetGenImage('assets/images/bg_keyboard_manage.webp');
@@ -128,6 +144,10 @@ class $AssetsImagesGen {
   AssetGenImage get bgTicketDialogPrices2 =>
       const AssetGenImage('assets/images/bg_ticket_dialog_prices2.webp');
 
+  /// File path: assets/images/gif_discount_unlock_button.gif
+  AssetGenImage get gifDiscountUnlockButton =>
+      const AssetGenImage('assets/images/gif_discount_unlock_button.gif');
+
   /// File path: assets/images/icon_about_arrow_left.webp
   AssetGenImage get iconAboutArrowLeft =>
       const AssetGenImage('assets/images/icon_about_arrow_left.webp');
@@ -259,6 +279,38 @@ class $AssetsImagesGen {
   AssetGenImage get iconDialogPaySuccess =>
       const AssetGenImage('assets/images/icon_dialog_pay_success.webp');
 
+  /// File path: assets/images/icon_discount_box_title.webp
+  AssetGenImage get iconDiscountBoxTitle =>
+      const AssetGenImage('assets/images/icon_discount_box_title.webp');
+
+  /// File path: assets/images/icon_discount_character.webp
+  AssetGenImage get iconDiscountCharacter =>
+      const AssetGenImage('assets/images/icon_discount_character.webp');
+
+  /// File path: assets/images/icon_discount_chat.webp
+  AssetGenImage get iconDiscountChat =>
+      const AssetGenImage('assets/images/icon_discount_chat.webp');
+
+  /// File path: assets/images/icon_discount_close.webp
+  AssetGenImage get iconDiscountClose =>
+      const AssetGenImage('assets/images/icon_discount_close.webp');
+
+  /// File path: assets/images/icon_discount_social.webp
+  AssetGenImage get iconDiscountSocial =>
+      const AssetGenImage('assets/images/icon_discount_social.webp');
+
+  /// File path: assets/images/icon_discount_subhead.webp
+  AssetGenImage get iconDiscountSubhead =>
+      const AssetGenImage('assets/images/icon_discount_subhead.webp');
+
+  /// File path: assets/images/icon_discount_title.webp
+  AssetGenImage get iconDiscountTitle =>
+      const AssetGenImage('assets/images/icon_discount_title.webp');
+
+  /// File path: assets/images/icon_discount_tutorial.webp
+  AssetGenImage get iconDiscountTutorial =>
+      const AssetGenImage('assets/images/icon_discount_tutorial.webp');
+
   /// File path: assets/images/icon_goods_info_title.webp
   AssetGenImage get iconGoodsInfoTitle =>
       const AssetGenImage('assets/images/icon_goods_info_title.webp');
@@ -476,6 +528,10 @@ class $AssetsImagesGen {
     bgCharacterDialog,
     bgCharacterDialogImage,
     bgCharacterGirlBanner,
+    bgDiscount,
+    bgDiscountContent,
+    bgDiscountTagTop,
+    bgDiscountTitle,
     bgKeyboardManage,
     bgKeyboardManageIntimacy,
     bgMine,
@@ -488,6 +544,7 @@ class $AssetsImagesGen {
     bgTicketDialog,
     bgTicketDialogPrices,
     bgTicketDialogPrices2,
+    gifDiscountUnlockButton,
     iconAboutArrowLeft,
     iconAlipayPayment,
     iconAlipayScanPayment,
@@ -519,6 +576,14 @@ class $AssetsImagesGen {
     iconDialogPayFail,
     iconDialogPayFailService,
     iconDialogPaySuccess,
+    iconDiscountBoxTitle,
+    iconDiscountCharacter,
+    iconDiscountChat,
+    iconDiscountClose,
+    iconDiscountSocial,
+    iconDiscountSubhead,
+    iconDiscountTitle,
+    iconDiscountTutorial,
     iconGoodsInfoTitle,
     iconKeyboardManageCustom,
     iconKeyboardManageFavorite,

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

@@ -135,6 +135,12 @@ class StringName {
   static final String paySuccessDialogPermanent = 'pay_success_dialog_permanent'.tr; // 恭喜您成为终身会员,感谢您的支持
   static final String surpriseDialogEndDesc = 'surprise_dialog_end_desc'.tr; // 后结束
   static final String surpriseDialogOnly = 'surprise_dialog_only'.tr; // 仅需
+  static final String discountDialogEndDesc = 'discount_dialog_end_desc'.tr; // 后失效
+  static final String discountDialogDesc = 'discount_dialog_desc'.tr; // 一键教你留住Ta的心
+  static final String discountDialogChat = 'discount_dialog_chat'.tr; // 无限畅聊
+  static final String discountDialogTutorial = 'discount_dialog_tutorial'.tr; // 恋爱教程
+  static final String discountDialogCharacter = 'discount_dialog_character'.tr; // 百种人设
+  static final String discountDialogSocial = 'discount_dialog_social'.tr; // 扩大社交
 }
 class StringMultiSource {
   StringMultiSource._();
@@ -273,6 +279,12 @@ class StringMultiSource {
       'pay_success_dialog_permanent': '恭喜您成为终身会员,感谢您的支持',
       'surprise_dialog_end_desc': '后结束',
       'surprise_dialog_only': '仅需',
+      'discount_dialog_end_desc': '后失效',
+      'discount_dialog_desc': '一键教你留住Ta的心',
+      'discount_dialog_chat': '无限畅聊',
+      'discount_dialog_tutorial': '恋爱教程',
+      'discount_dialog_character': '百种人设',
+      'discount_dialog_social': '扩大社交',
     },
   };
 }

+ 89 - 0
lib/widget/auto_scroll_list_view.dart

@@ -0,0 +1,89 @@
+import 'dart:async';
+
+import 'package:flutter/cupertino.dart';
+
+class AutoScrollListView extends StatefulWidget {
+  final NullableIndexedWidgetBuilder itemBuilder;
+
+  final int itemCount;
+
+  final Axis scrollDirection;
+
+  final EdgeInsetsGeometry? padding;
+
+  final bool isAutoScrolling;
+
+  const AutoScrollListView({
+    super.key,
+    required this.itemBuilder,
+    required this.itemCount,
+    this.padding,
+    this.isAutoScrolling = true,
+    this.scrollDirection = Axis.horizontal,
+  });
+
+  @override
+  State<AutoScrollListView> createState() => _AutoScrollListViewState();
+}
+
+class _AutoScrollListViewState extends State<AutoScrollListView> {
+  final ScrollController scrollController = ScrollController();
+  Timer? _timer;
+  bool _isUserScrolling = true;
+
+  @override
+  void initState() {
+    super.initState();
+    _isUserScrolling = widget.isAutoScrolling;
+    _startAutoScroll();
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+    scrollController.dispose();
+    _timer?.cancel();
+  }
+
+  void _startAutoScroll() {
+    _timer = Timer.periodic(Duration(milliseconds: 50), (timer) {
+      if (_isUserScrolling) {
+        scrollController.animateTo(
+          scrollController.position.pixels + 1,
+          duration: Duration(milliseconds: 50),
+          curve: Curves.linear,
+        );
+      }
+    });
+  }
+
+  void _onUserScroll() {
+    if (!_isUserScrolling) {
+      return;
+    }
+    _timer?.cancel();
+    _timer = Timer(Duration(seconds: 1), () {
+      setState(() {
+        _isUserScrolling = widget.isAutoScrolling;
+      });
+      _startAutoScroll();
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return GestureDetector(
+      onPanDown: (_) => _onUserScroll(),
+      onPanUpdate: (_) => _onUserScroll(),
+      child: ListView.builder(
+        padding: widget.padding,
+        scrollDirection: widget.scrollDirection,
+        itemCount: 1000000,
+        itemBuilder: (context, index) {
+          return widget.itemBuilder(context, index % widget.itemCount);
+        },
+        controller: scrollController,
+      ),
+    );
+  }
+}

+ 35 - 0
lib/widget/vertical_dots.dart

@@ -0,0 +1,35 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+class VerticalDots extends StatelessWidget {
+  final double dotSize;
+  final double height;
+  final Color? color;
+  final double horizontalPadding;
+
+  const VerticalDots({
+    super.key,
+    required this.dotSize,
+    required this.height,
+    this.color = Colors.white,
+    this.horizontalPadding = 0,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      margin: EdgeInsets.symmetric(horizontal: horizontalPadding),
+      width: 2.w,
+      height: 8.h,
+      child: Column(
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: List.generate(2, (index) {
+          return Container(
+            width: dotSize,
+            height: dotSize,
+            decoration: BoxDecoration(color: color, shape: BoxShape.circle),
+          );
+        }),
+      ),
+    );
+  }
+}