Sfoglia il codice sorgente

[fix]修改会员页描述配置,修改优惠页界面,修改登录前可支付

Destiny 6 mesi fa
parent
commit
3ada9cd378

+ 78 - 43
ios/Runner/FlutterMethodChannelManager.swift

@@ -7,6 +7,7 @@
 
 import Flutter
 import UIKit
+import StoreKit
 
 class FlutterMethodChannelManager: NSObject {
     
@@ -69,6 +70,15 @@ class FlutterMethodChannelManager: NSObject {
             case "openKeyboardGuide":
                 KeyboardSharedDataManager.shared.saveIsShowGuide()
                 result(true)
+            case "isHasDiscount":
+                if let args = call.arguments as? [String: Any],
+                   let appleGoodId = args["appleGoodId"] as? String {
+                    self.checkProductDiscount(appleGoodId: appleGoodId, completion: { hasDiscount in
+                        result(hasDiscount)
+                    })
+                } else {
+                    result(FlutterError(code: "INVALID_ARGUMENTS", message: "无效的参数", details: nil))
+                }
             default:
                 result(FlutterMethodNotImplemented)
             }
@@ -101,55 +111,80 @@ class FlutterMethodChannelManager: NSObject {
         return currentKeyboardName == appDisplayName
     }
     
-    // 是否添加键盘
-    func isKeyboardEnabled() -> Bool {
-        
-        // 获取系统中已安装的键盘列表
-        let keyboards = UserDefaults.standard.dictionaryRepresentation()
-            .filter { $0.key.contains("Keyboard") }
-        if let keyboardList = keyboards["AppleKeyboards"] as? [String] {
-            
-            let keyboardBundleId = "com.qihuan.zhuiaijianpan.AiKeyboard"
-            let isAdded = keyboardList.contains(keyboardBundleId)
-            return isAdded
-        }
-        return false
-    }
+}
+
+extension FlutterMethodChannelManager {
     
-    // 是否为默认键盘
-    func isCustomKeybroad() -> Bool {
+    private func checkProductDiscount(appleGoodId: String, completion: @escaping (Bool) -> Void) {
         
-        let currentKeyboardName = (((UITextInputMode.activeInputModes as NSArray).filtered(using: NSPredicate(format: "isDisplayed = YES"))).last as? NSObject)?.value(forKey: "extendedDisplayName") as? String
-        
-        let infoDictionary = Bundle.main.infoDictionary!
-        let appDisplayName = infoDictionary["CFBundleDisplayName"] as? String
+        if #available(iOS 15.0, *) {
+            Task {
+                do {
+//                    print("开始请求产品信息 (StoreKit 2)")
+                    let products = try await Product.products(for: [appleGoodId])
+                    print("成功获取产品信息: \(products.count) 个产品")
+                    if let product = products.first {
+                        // 检查是否有优惠
+                        var hasDiscount = await product.subscription?.isEligibleForIntroOffer ?? false
+                        
+                        DispatchQueue.main.async {
+                            completion(hasDiscount)
+                        }
+                    } else {
+                        print("未找到产品")
+                        DispatchQueue.main.async {
+                            completion(false)
+                        }
+                    }
+                } catch {
+                    print("获取产品失败 (StoreKit 2): \(error)")
+                    DispatchQueue.main.async {
+                        completion(false)
+                    }
+                }
+            }
+        }
         
-        return currentKeyboardName == appDisplayName
+//        // 使用StoreKit查询商品信息
+//        let request = SKProductsRequest(productIdentifiers: [appleGoodId])
+//        request.delegate = ProductRequestDelegate(completion: { product in
+//            if let product = product {
+//                // iOS 12.2及以上版本支持促销优惠
+//                if #available(iOS 12.2, *) {
+//                    // 检查商品是否有促销优惠
+//                    let hasDiscount = product.discounts.count > 0
+//                    completion(hasDiscount)
+//                } else {
+//                    completion(false)
+//                }
+//            } else {
+//                completion(false)
+//            }
+//        })
+//        request.start()
     }
+}
+
+// 处理StoreKit产品请求的代理
+class ProductRequestDelegate: NSObject, SKProductsRequestDelegate {
     
-    // 是否添加键盘
-    func isKeyboardEnabled() -> Bool {
-        
-        // 获取系统中已安装的键盘列表
-        let keyboards = UserDefaults.standard.dictionaryRepresentation()
-            .filter { $0.key.contains("Keyboard") }
-        if let keyboardList = keyboards["AppleKeyboards"] as? [String] {
-            
-            let keyboardBundleId = "com.qihuan.zhuiaijianpan.AiKeyboard"
-            let isAdded = keyboardList.contains(keyboardBundleId)
-            return isAdded
+    private let completion: (SKProduct?) -> Void
+
+    init(completion: @escaping (SKProduct?) -> Void) {
+        self.completion = completion
+        super.init()
+    }
+
+    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
+        if let product = response.products.first {
+            completion(product)
+        } else {
+            completion(nil)
         }
-        return false
     }
-    
-    // 是否为默认键盘
-    func isCustomKeybroad() -> Bool {
-        
-        let currentKeyboardName = (((UITextInputMode.activeInputModes as NSArray).filtered(using: NSPredicate(format: "isDisplayed = YES"))).last as? NSObject)?.value(forKey: "extendedDisplayName") as? String
-        
-        let infoDictionary = Bundle.main.infoDictionary!
-        let appDisplayName = infoDictionary["CFBundleDisplayName"] as? String
-        
-        return currentKeyboardName == appDisplayName
+
+    func request(_ request: SKRequest, didFailWithError error: Error) {
+        print("商品请求失败: \(error.localizedDescription)")
+        completion(nil)
     }
 }

+ 11 - 1
lib/data/bean/goods_info.dart

@@ -37,6 +37,10 @@ class GoodsInfo {
   String? timeLimitDesc;
   @JsonKey(name: 'code')
   late String code;
+  @JsonKey(name: 'firstAmount')
+  int? firstAmount;
+  @JsonKey(name: 'discountPriceDesc')
+  String? discountPriceDesc;
 
   GoodsInfo({
     required this.id,
@@ -55,10 +59,14 @@ class GoodsInfo {
     this.mostDesc,
     this.selectDesc,
     this.timeLimitDesc,
+    this.firstAmount,
+    this.discountPriceDesc,
   });
 
   get amountText => (amount / 100).toFormattedString(2);
 
+  get firstAmountText => ((firstAmount ?? 0) / 100).toDouble().toFormattedString(1);
+
   get originalAmountText => "${(originalAmount / 100).toFormattedString(2)}元";
 
   get priceDescNumber => priceDesc.extractAmount();
@@ -88,6 +96,8 @@ class GoodsInfo {
       selectDesc: selectDesc,
       timeLimitDesc: timeLimitDesc,
       code: code,
+      firstAmount: firstAmount,
+      discountPriceDesc: discountPriceDesc,
     );
   }
-}
+}

+ 4 - 0
lib/data/bean/goods_info.g.dart

@@ -26,6 +26,8 @@ GoodsInfo _$GoodsInfoFromJson(Map<String, dynamic> json) => GoodsInfo(
   mostDesc: json['mostDesc'] as String?,
   selectDesc: json['selectDesc'] as String?,
   timeLimitDesc: json['timeLimitDesc'] as String?,
+  firstAmount: (json['firstAmount'] as num?)?.toInt(),
+  discountPriceDesc: json['discountPriceDesc'] as String?,
 );
 
 Map<String, dynamic> _$GoodsInfoToJson(GoodsInfo instance) => <String, dynamic>{
@@ -45,4 +47,6 @@ Map<String, dynamic> _$GoodsInfoToJson(GoodsInfo instance) => <String, dynamic>{
   'selectDesc': instance.selectDesc,
   'timeLimitDesc': instance.timeLimitDesc,
   'code': instance.code,
+  'firstAmount': instance.firstAmount,
+  'discountPriceDesc': instance.discountPriceDesc,
 };

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

@@ -47,8 +47,7 @@ class AccountRepository {
 
   bool get isVipUser =>
       memberStatusInfo.value != null &&
-      memberStatusInfo.value!.isMember &&
-      isLogin.value;
+      memberStatusInfo.value!.isMember;
 
   int? _lastRequestCodeTime;
   int _errorCodeTimes = 0;

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

@@ -66,7 +66,7 @@ class ConfigRepository {
           } else if (config.confCode == 'default_avatar') {
             defaultAvatarInfo.value = DefaultAvatarInfo.fromJson(config.value);
             AtmobLog.d(tag, '获取默认头像配置: ${defaultAvatarInfo.value?.toJson()}');
-          }else if(config.confCode=='member_tips'){
+          } else if(config.confCode=='member_tips'){
             memberTips.value = config.value['tips'] ?? '';
             AtmobLog.d(tag, '购买须知: ${memberTips.value}');
           }
@@ -87,7 +87,7 @@ class ConfigRepository {
   /// 获取配置信息
   Future<ConfigResponse> requestConfigsData() {
     return atmobApi
-        .confs(ConfigRequest(confCodes: ['intimacy', 'default_avatar','member_tips']))
+        .confs(ConfigRequest(confCodes: ['intimacy', 'default_avatar', 'member_tips']))
         .then(HttpHandler.handle(true));
   }
 

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

@@ -384,6 +384,7 @@ extension GetItInjectableX on _i174.GetIt {
       () => _i344.StoreController(
         gh<_i987.StoreRepository>(),
         gh<_i83.AccountRepository>(),
+        gh<_i50.ConfigRepository>(),
         gh<_i779.PaymentStatusManager>(),
         gh<_i442.StoreGoodsCountdownManager>(),
       ),

+ 1 - 1
lib/module/keyboard/keyboard_controller.dart

@@ -79,7 +79,7 @@ class KeyBoardController extends BaseController {
 
   /// 根据当前登录状态 & 会员信息更新 banner显示逻辑
   void _updateBannerState() {
-    if (isLogin && (memberInfo?.isMember == true)) {
+    if ((memberInfo?.isMember == true)) {
       isShowBanner.value = false;
       _timer?.cancel(); // 停止倒计时
     } else {

+ 1 - 1
lib/module/main/main_controller.dart

@@ -110,7 +110,7 @@ class MainController extends BaseController {
   @override
   Future<void> onReady() async {
     super.onReady();
-    if (memberStatusInfo != null && memberStatusInfo!.isMember && isLogin) {
+    if (memberStatusInfo != null && memberStatusInfo!.isMember) {
       return;
     }
     if(!accountRepository.isVipUser){

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

@@ -184,8 +184,8 @@ class MineController extends BaseController {
   }
 
   String getVipButtonDesc() {
-    if (memberInfo?.isMember == true && isLogin) {
-      if (memberInfo?.permanent == true && isLogin) {
+    if (memberInfo?.isMember == true) {
+      if (memberInfo?.permanent == true) {
         return StringName.vipLevel2Btn;
       }
       return StringName.vipLevel1Btn;
@@ -194,8 +194,8 @@ class MineController extends BaseController {
   }
 
   String getVipLevelDesc() {
-    if (memberInfo?.isMember == true && isLogin) {
-      if (memberInfo?.permanent == true && isLogin) {
+    if (memberInfo?.isMember == true) {
+      if (memberInfo?.permanent == true) {
         return StringName.vipLevel2Desc;
       }
       return "${StringName.vipLevel1Desc}${DateUtil.fromMillisecondsSinceEpoch('yyyy年MM月dd日', memberInfo?.endTimestamp ?? 0)}";

+ 11 - 6
lib/module/store/new_discount/new_discount_controller.dart

@@ -9,8 +9,10 @@ import 'package:get/get.dart';
 import 'package:injectable/injectable.dart';
 import 'package:keyboard/base/base_controller.dart';
 import 'package:keyboard/data/bean/character_info.dart';
+import 'package:keyboard/data/repository/config_repository.dart';
 import 'package:keyboard/module/login/login_page.dart';
 import 'package:keyboard/module/store/new_discount/member_card_bean.dart';
+import 'package:keyboard/utils/method_chanel_ios_util.dart';
 import 'package:keyboard/widget/platform_util.dart';
 import '../../../data/api/response/user_info_response.dart';
 import '../../../data/bean/goods_info.dart';
@@ -95,6 +97,8 @@ class NewDiscountController extends BaseController
 
   int get currentBannerIndex => _currentBanner.value;
 
+  late final RxBool isDiscount = false.obs;
+
   /// 轮播图控制器
   final List<AssetGenImage> bannerList = [
     Assets.images.iconNewDiscountBanner1,
@@ -172,7 +176,7 @@ class NewDiscountController extends BaseController
     storeRepository.refreshNewDiscountGoodsInfoList();
   }
 
-  void updateFilteredGoodsList() {
+  Future<void> updateFilteredGoodsList() async {
     if (_selectedPayWay.value == null) {
       return;
     }
@@ -186,6 +190,8 @@ class NewDiscountController extends BaseController
     );
     if (filteredGoodsList.isNotEmpty) {
       _selectedGoodsInfoItem.value = filteredGoodsList.first;
+      var isHasDiscount = await MethodChanelIOSUtil.isHasDiscount(goodsInfoList.first.appleGoodsId);
+      isDiscount.value = isHasDiscount;
     }
   }
 
@@ -561,11 +567,10 @@ class NewDiscountController extends BaseController
 
   @override
   void onPaymentSuccess(
-      String orderNo,
-      PayWayInfo payWayInfo,
-      GoodsInfo goodsInfo,
-      ) {
-
+    String orderNo,
+    PayWayInfo payWayInfo,
+    GoodsInfo goodsInfo,
+  ) {
     if (PlatformUtil.isIOS) {
       accountRepository.refreshUserInfo();
       // 300ms后关闭弹窗

+ 29 - 7
lib/module/store/new_discount/new_discount_page.dart

@@ -379,7 +379,7 @@ class NewDiscountPage extends BasePage<NewDiscountController> {
                   children: [
                     GestureDetector(
                       onTap: () => controller.onGoodsItemClick(item),
-                      child: _buildGoodsItem(item, isSelected),
+                      child: _buildGoodsItem(item, index, isSelected),
                     ),
                     if (index != goodsList.length - 1) SizedBox(height: 10.w),
                   ],
@@ -446,7 +446,15 @@ class NewDiscountPage extends BasePage<NewDiscountController> {
     );
   }
 
-  Widget _buildGoodsItem(GoodsInfo item, bool isSelected) {
+  Widget _buildGoodsItem(GoodsInfo item, int index, bool isSelected) {
+    var amountText = item.amountText;
+    var priceDesc = item.priceDesc;
+    if (index == 0) {
+      if (controller.isDiscount.value) {
+        amountText = item.firstAmountText;
+        priceDesc = item.discountPriceDesc ?? "";
+      }
+    }
     return Container(
       height: 70.w,
       width: 328.w,
@@ -483,7 +491,7 @@ class NewDiscountPage extends BasePage<NewDiscountController> {
           Text(
             (item.code == "vip_permanent")
                 ? '${item.priceDescNumber}'
-                : '${item.amountText}',
+                : '$amountText',
             textAlign: TextAlign.center,
             style: TextStyle(
               color:
@@ -558,7 +566,7 @@ class NewDiscountPage extends BasePage<NewDiscountController> {
               Text(
                 (item.code == "vip_permanent")
                     ? '${item.description}'
-                    : '${item.priceDesc}',
+                    : priceDesc,
 
                 style: TextStyle(
                   color: Colors.black.withAlpha(153),
@@ -803,9 +811,23 @@ class NewDiscountPage extends BasePage<NewDiscountController> {
                     url: WebUrl.privacyPolicy,
                   ),
 
-              ClickTextSpan(
-                text: StringName.textSpanServiceTerms,
-                url: WebUrl.serviceAgreement,
+                  ClickTextSpan(
+                    text: StringName.textSpanServiceTerms,
+                    url: WebUrl.serviceAgreement,
+                  ),
+                  TextSpan(
+                    text: StringName.textSpanAnd,
+                    style: TextStyle(
+                      color: Colors.black.withAlpha(153),
+                      fontSize: 10.sp,
+                      fontWeight: FontWeight.w400,
+                    ),
+                  ),
+                  ClickTextSpan(
+                    text: StringName.textSpanMembershipAgreement,
+                    url: WebUrl.memberServiceAgreement,
+                  ),
+                ],
               ),
               TextSpan(
                 text: StringName.textSpanAnd,

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

@@ -26,6 +26,7 @@ import 'package:keyboard/module/store/ticket/discount_ticket_dialog.dart';
 import 'package:keyboard/module/store/util/store_goods_countdown_manager.dart';
 import 'package:keyboard/utils/async_util.dart';
 import 'package:keyboard/utils/atmob_log.dart';
+import 'package:keyboard/utils/method_chanel_ios_util.dart';
 import 'package:keyboard/widget/platform_util.dart';
 import 'package:lottie/lottie.dart';
 
@@ -58,6 +59,8 @@ class StoreController extends BaseController implements PaymentStatusCallback {
 
   final AccountRepository accountRepository;
 
+  final ConfigRepository configRepository;
+
   final PaymentStatusManager paymentStatusManager;
 
   /// 倒计时管理器
@@ -89,6 +92,8 @@ class StoreController extends BaseController implements PaymentStatusCallback {
 
   UserInfoResponse? get userInfo => accountRepository.userInfo.value;
 
+  late final RxBool isDiscount = false.obs;
+
   // 过滤不同支付的商品
   final RxList<GoodsInfo> filteredGoodsList = <GoodsInfo>[].obs;
 
@@ -146,6 +151,7 @@ class StoreController extends BaseController implements PaymentStatusCallback {
   StoreController(
     this.storeRepository,
     this.accountRepository,
+    this.configRepository,
     this.paymentStatusManager,
     this.storeGoodsCountdownManager,
       this.configRepository,
@@ -175,6 +181,8 @@ class StoreController extends BaseController implements PaymentStatusCallback {
       if (response.goodsInfoList?.isNotEmpty == true) {
         goodsInfoList.addAll(response.goodsInfoList!);
         _selectedGoodsInfoItem.value = goodsInfoList.first;
+        var isHasDiscount = await MethodChanelIOSUtil.isHasDiscount(goodsInfoList.first.appleGoodsId);
+        isDiscount.value = isHasDiscount;
       }
       if (response.payInfoList?.isNotEmpty == true) {
         payWayList.addAll(response.payInfoList!);
@@ -523,7 +531,7 @@ class StoreController extends BaseController implements PaymentStatusCallback {
     super.onClose();
     _storeDataFuture?.cancel();
     paymentStatusManager.unregisterPaymentSuccessCallback(this);
-    if (memberStatusInfo != null && memberStatusInfo!.isMember && isLogin) {
+    if (memberStatusInfo != null && memberStatusInfo!.isMember) {
       return;
     }
     if (PlatformUtil.isAndroid) {

+ 22 - 13
lib/module/store/store_page.dart

@@ -57,7 +57,10 @@ class StorePage extends BasePage<StoreController> {
           ),
           // 恢复订阅入口
           _buildRecoverSubscribe(),
-          SizedBox(height: 50.h),
+          SizedBox(height: 20.h),
+          _buildUserReviews(),
+          SizedBox(height: 20.h),
+          _buildUserNotice(),
         ],
       );
     } else {
@@ -82,6 +85,7 @@ class StorePage extends BasePage<StoreController> {
               SizedBox(height: 12.h),
               _buildGoodsCard(),
               bottomArea,
+              SizedBox(height: 40.h),
             ],
           ),
           Positioned(top: 0, left: 0, right: 0, child: _buildTitle()),
@@ -160,16 +164,16 @@ class StorePage extends BasePage<StoreController> {
 
   // 会员状态文字逻辑提取
   List<TextSpan> _getMemberStatusText() {
-    // 未登录
-    if (!controller.isLogin) {
-      return [
-        TextSpan(
-          text: StringName.memberNoLogged,
-          style: _vipTextStyle(isHighlight: true),
-        ),
-        TextSpan(text: StringName.memberCardNoVipDesc, style: _vipTextStyle()),
-      ];
-    }
+    // // 未登录
+    // if (!controller.isLogin) {
+    //   return [
+    //     TextSpan(
+    //       text: StringName.memberNoLogged,
+    //       style: _vipTextStyle(isHighlight: true),
+    //     ),
+    //     TextSpan(text: StringName.memberCardNoVipDesc, style: _vipTextStyle()),
+    //   ];
+    // }
 
     final isMember = controller.memberStatusInfo?.isMember ?? false;
     final isPermanent = controller.memberStatusInfo?.permanent ?? false;
@@ -336,7 +340,12 @@ class StorePage extends BasePage<StoreController> {
   Widget _buildGoodsItemIos(int index, GoodsInfo item, bool isSelected) {
     // 第一个商品,才有有倒计时
     bool hasCountdown = index == 0;
-
+    var amountText = item.amountText;
+    if (index == 0) {
+      if (controller.isDiscount.value) {
+        amountText = item.firstAmountText;
+      }
+    }
     Widget contentWidget = Stack(
       children: [
         Positioned(
@@ -355,7 +364,7 @@ class StorePage extends BasePage<StoreController> {
                       style: Styles.getTextStyleFF663300W700(14.sp),
                     ),
                     TextSpan(
-                      text: item.amountText,
+                      text: amountText,
                       style: Styles.getTextStyleFF663300W700(18.sp),
                     ),
                   ],

+ 12 - 0
lib/utils/method_chanel_ios_util.dart

@@ -113,4 +113,16 @@ class MethodChanelIOSUtil {
       _channel.invokeMethod('openKeyboardGuide');
     }
   }
+
+  // 检查是否有优惠
+  static Future<bool> isHasDiscount(String appleGoodId) async {
+    try {
+      // 调用原生方法并等待返回结果
+      final bool result = await _channel.invokeMethod('isHasDiscount', {'appleGoodId': appleGoodId});
+      return result;
+    } on PlatformException catch (e) {
+      print('检查商品是否有优惠: ${e.message}');
+      return false; // 发生错误时返回 false
+    }
+  }
 }