Selaa lähdekoodia

[feat]新增会员挽留页,调整一些细节

Destiny 11 kuukautta sitten
vanhempi
commit
5848352547

+ 20 - 0
lib/data/consts/constants.dart

@@ -39,6 +39,10 @@ class Constants {
 
   static const String firstOpenPhotoPreview = 'firstOpenPhotoPreview';
 
+  static const String isFirstIntoApp = 'isFirstIntoApp';
+
+  static const String isFirstIntoStore = 'isFirstIntoStore';
+
 }
 
 String getBaseUrl() {
@@ -97,6 +101,22 @@ void setFirstClickHomeClean(bool isFirst) {
   KVUtil.putBool(Constants.isFirstClickHomeClean, isFirst);
 }
 
+bool isFirstIntoApp() {
+  return KVUtil.getBool(Constants.isFirstIntoApp, true);
+}
+
+void setFirstIntoApp(bool isFirst) {
+  KVUtil.putBool(Constants.isFirstIntoApp, isFirst);
+}
+
+bool isFirstIntoStore() {
+  return KVUtil.getBool(Constants.isFirstIntoStore, true);
+}
+
+void setFirstIntoStore(bool isFirst) {
+  KVUtil.putBool(Constants.isFirstIntoStore, isFirst);
+}
+
 
 
 

+ 6 - 1
lib/dialog/loading_dialog.dart

@@ -1,7 +1,12 @@
 import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
 
 class LoadingDialog {
-  static void show(String msg, {bool backDismiss = false}) {
+
+  static void show() {
+    SmartDialog.showLoading();
+  }
+
+  static void showText(String msg, {bool backDismiss = false}) {
     SmartDialog.showLoading(
       msg: msg,
       backType: backDismiss ? SmartBackType.normal : SmartBackType.block,

+ 12 - 4
lib/module/home/home_controller.dart

@@ -18,10 +18,10 @@ import 'package:wechat_assets_picker/wechat_assets_picker.dart';
 import '../../data/api/response/user_info_response.dart';
 
 class HomeController extends BaseController {
-  Rx<double> totalSpace = 500.0.obs;
-  Rx<double> usedSpace = 300.0.obs;
-  Rx<double> photoSpace = 100.0.obs;
-  Rx<double> freeSpace = 200.0.obs;
+  Rx<double> totalSpace = 0.0.obs;
+  Rx<double> usedSpace = 0.0.obs;
+  Rx<double> photoSpace = 0.0.obs;
+  Rx<double> freeSpace = 0.0.obs;
 
   Rx<String> totalSpaceStr = "".obs;
   Rx<String> usedSpaceStr = "".obs;
@@ -66,6 +66,7 @@ class HomeController extends BaseController {
   Future<void> onInit() async {
     // TODO: implement onInit
     super.onInit();
+
     if (Platform.isAndroid) {
       loadPhotosFromDirectory();
     }
@@ -78,6 +79,13 @@ class HomeController extends BaseController {
     } else {
       ToastUtil.show("请先开启相册权限");
     }
+
+    await userRepository.getUserInfo();
+
+    if (!isFirstIntoApp() && !userRepository.isVip()) {
+      Get.toNamed(RoutePath.discount);
+    }
+    setFirstIntoApp(false);
   }
 
   Future<void> loadPhotosFromDirectory() async {

+ 1 - 1
lib/module/home/home_view.dart

@@ -72,7 +72,7 @@ class HomePage extends BaseView<HomeController> {
           ),
           GestureDetector(
             onTap: () {
-              Get.toNamed(RoutePath.store);
+              Get.toNamed(RoutePath.discount);
             },
             child: Assets.images.iconHomeTitleVip
                 .image(width: 60.8.w, height: 20.h),

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

@@ -2,7 +2,9 @@ import 'dart:io';
 import 'dart:ui';
 import 'dart:typed_data' as typed;
 
+import 'package:clean/data/consts/constants.dart';
 import 'package:clean/data/repositories/user_repository.dart';
+import 'package:clean/router/app_pages.dart';
 import 'package:get/get.dart';
 import 'package:wechat_assets_picker/wechat_assets_picker.dart';
 
@@ -18,8 +20,6 @@ class MainController extends BaseController {
   @override
   void onInit() {
     super.onInit();
-
-    userRepository.getUserInfo();
   }
 
   void changeIndex(int index) {

+ 131 - 1
lib/module/store/discount/discount_controller.dart

@@ -1,5 +1,135 @@
+import 'package:apple_pay/apple_pay.dart';
 import 'package:clean/base/base_controller.dart';
+import 'package:clean/data/bean/payment_way.dart';
+import 'package:clean/data/bean/store_item.dart';
+import 'package:clean/data/consts/constants.dart';
+import 'package:clean/data/repositories/store_repository.dart';
+import 'package:clean/data/repositories/user_repository.dart';
+import 'package:clean/dialog/loading_dialog.dart';
+import 'package:clean/module/store/payment_status_manager.dart';
+import 'package:clean/sdk/pay/applepay/apple_pay_info.dart';
+import 'package:clean/sdk/pay/assist/product_type.dart';
+import 'package:clean/utils/error_handler.dart';
+import 'package:clean/utils/toast_util.dart';
+import 'package:get/get.dart';
 
-class DiscountController extends BaseController {
+class DiscountController extends BaseController implements PaymentStatusCallback {
 
+  final RxList<StoreItem> storeItems = <StoreItem>[].obs;
+
+  final RxList<PaymentWay> paymentWays = <PaymentWay>[].obs;
+
+  final Rxn<StoreItem> currentSelectedStoreItem = Rxn<StoreItem>();
+
+  final Rxn<PaymentWay> currentSelectedPaymentWay = Rxn<PaymentWay>();
+
+  RxBool isFree = false.obs;
+
+  @override
+  Future<void> onInit() async {
+    super.onInit();
+    initStoreIndexData();
+  }
+
+  void initStoreIndexData() {
+    LoadingDialog.show();
+    storeRepository.storeIndex().then((indexData) async {
+      storeItems.clear();
+      storeItems.addAll(indexData.items);
+      currentSelectedStoreItem.value =
+      storeItems.isNotEmpty ? storeItems.first : null;
+
+      paymentWays.clear();
+      paymentWays.addAll(indexData.paymentWays);
+      currentSelectedPaymentWay.value =
+      paymentWays.isNotEmpty ? paymentWays.first : null;
+
+      var freeAppleId = "";
+      for (var item in storeItems) {
+        if (item.freeTrialName != null) {
+          freeAppleId = item.appleGoodsId;
+        }
+      }
+
+      isFree.value = await ApplePay().check(freeAppleId);
+
+      LoadingDialog.hide();
+    });
+  }
+
+  void onBuyClick() async {
+
+    StoreItem? storeItem = currentSelectedStoreItem.value;
+    if (storeItem == null) {
+      // ToastUtil.showToast(StringName.storeChoiceGoods.tr);
+      return;
+    }
+    PaymentWay? paymentWay = currentSelectedPaymentWay.value;
+    if (paymentWay == null) {
+      // ToastUtil.showToast(StringName.storeChoicePayment.tr);
+      return;
+    }
+    int payPlatform = paymentWay.payPlatform;
+    int payMethod = paymentWay.payMethod;
+    LoadingDialog.show();
+    try {
+      // OrderPayResponse response =
+      storeRepository.orderPay(storeItem.id, payPlatform, payMethod).then((response) async {
+
+        dynamic payInfo;
+        String outTradeNo = response.outTradeNo;
+        if (payPlatform == PayPlatform.apple) {
+          payInfo = ApplePayInfo(
+              storeItem.appleGoodsId,
+              storeItem.subscribable == 1
+                  ? ProductType.nonConsumable
+                  : ProductType.consumable,
+              response.appAccountToken);
+        }
+
+        final result = await ApplePay().purchase(productId: storeItem.appleGoodsId, appAccountToken: response.appAccountToken);
+        if (result["success"] == true) {
+          var receipt = result['receipt'];
+          print('购买成功: ${result['receipt']}');
+          checkPaymentStatus(outTradeNo, paymentWay, storeItem, receiptData: receipt);
+        } else {
+          LoadingDialog.hide();
+          ToastUtil.show("Pay Error, Please try again");
+          print('购买失败: ${result['error']}');
+        }
+      }).catchError((error) {
+        LoadingDialog.hide();
+        ErrorHandler.toastError(error);
+      });
+    } catch (error) {
+      LoadingDialog.hide();
+    }
+  }
+
+  void checkPaymentStatus(
+      String orderNo, PaymentWay paymentWay, StoreItem storeItemBean,
+      {String? receiptData}) {
+    paymentStatusManager.registerPaymentSuccessCallback(orderNo, this);
+    paymentStatusManager.checkPaymentStatus(orderNo, paymentWay, storeItemBean,
+        receiptData: receiptData);
+  }
+
+  @override
+  void onPaymentSuccess(String orderNo, PaymentWay paymentWay, StoreItem storeItemBean) {
+    // TODO: implement onPaymentSuccess
+    LoadingDialog.hide();
+    ToastUtil.show("Pay success");
+    // 300ms后关闭弹窗
+    Future.delayed(Duration(seconds: 3), () {
+      userRepository.getUserInfo();
+    });
+    Get.back();
+  }
+
+  @override
+  void onPaymentError(Error error) {
+    // TODO: implement onPaymentError
+    LoadingDialog.hide();
+    ErrorHandler.toastError(error);
+  }
 }

+ 156 - 138
lib/module/store/discount/discount_view.dart

@@ -24,162 +24,180 @@ class DiscountPage extends BasePage<DiscountController> {
 
   @override
   Widget buildBody(BuildContext context) {
-    return Scaffold(
-      backgroundColor: "#05050D".color,
-      body: Stack(
-        children: [
-          // IgnorePointer(
-          //   child: Assets.images.bgStore.image(
-          //     width: 360.w,
-          //   ),
-          // ),
-          SafeArea(
-            child: Column(
-              crossAxisAlignment: CrossAxisAlignment.center,
-              children: [
-                Row(
-                  children: [
-                    Container(
-                      margin: EdgeInsets.only(left: 16.w, top: 14.h),
-                      child: GestureDetector(
-                        onTap: () {
-                          Get.back();
-                        },
-                        child: Assets.images.iconStoreClose
-                            .image(width: 28.w, height: 28.w),
+    return Obx(() {
+      bool isFreeItem =
+      (controller.currentSelectedStoreItem.value?.freeTrialName != null);
+      bool isShowFree = isFreeItem && controller.isFree.value;
+      return Scaffold(
+        backgroundColor: "#05050D".color,
+        body: Stack(
+          children: [
+            SafeArea(
+              child: Column(
+                crossAxisAlignment: CrossAxisAlignment.center,
+                children: [
+                  Row(
+                    children: [
+                      Container(
+                        margin: EdgeInsets.only(left: 16.w, top: 14.h),
+                        child: GestureDetector(
+                          onTap: () {
+                            Get.back();
+                          },
+                          child: Assets.images.iconStoreClose
+                              .image(width: 28.w, height: 28.w),
+                        ),
                       ),
-                    ),
-                  ],
-                ),
-                Assets.images.iconDiscountTitle
-                    .image(width: 259.w, height: 55.h),
-                SizedBox(
-                  height: 20.h,
-                ),
-                Assets.images.iconDiscountPercent
-                    .image(width: 195.w, height: 86.h),
-                SizedBox(
-                  height: 13.h,
-                ),
-                Container(
-                  width: 197.w,
-                  height: 32.h,
-                  padding: EdgeInsets.all(1.w),
-                  decoration: BoxDecoration(
-                    gradient: LinearGradient(
-                      begin: Alignment.topCenter,
-                      end: Alignment.bottomCenter,
-                      colors: [
-                        '#CF9EFD'.color,
-                        '#4DCFFF'.color.withOpacity(0.5),
-                      ],
-                    ),
-                    borderRadius: BorderRadius.all(Radius.circular(18.r)),
+                    ],
                   ),
-                  child: Container(
+                  Assets.images.iconDiscountTitle
+                      .image(width: 259.w, height: 55.h),
+                  SizedBox(
+                    height: 20.h,
+                  ),
+                  Assets.images.iconDiscountPercent
+                      .image(width: 195.w, height: 86.h),
+                  SizedBox(
+                    height: 13.h,
+                  ),
+                  Container(
+                    width: 197.w,
+                    height: 32.h,
+                    padding: EdgeInsets.all(1.w),
                     decoration: BoxDecoration(
-                      color: "#05050D".color,
+                      gradient: LinearGradient(
+                        begin: Alignment.topCenter,
+                        end: Alignment.bottomCenter,
+                        colors: [
+                          '#CF9EFD'.color,
+                          '#4DCFFF'.color.withOpacity(0.5),
+                        ],
+                      ),
                       borderRadius: BorderRadius.all(Radius.circular(18.r)),
                     ),
-                    child: Center(
-                      child: Text(
-                        "Get CleanPro Premium",
-                        style: TextStyle(
-                          color: Colors.white,
-                          fontSize: 15.sp,
-                          fontWeight: FontWeight.w700,
+                    child: Container(
+                      decoration: BoxDecoration(
+                        color: "#05050D".color,
+                        borderRadius: BorderRadius.all(Radius.circular(18.r)),
+                      ),
+                      child: Center(
+                        child: Text(
+                          "Get CleanPro Premium",
+                          style: TextStyle(
+                            color: Colors.white,
+                            fontSize: 15.sp,
+                            fontWeight: FontWeight.w700,
+                          ),
                         ),
                       ),
                     ),
                   ),
-                ),
-                SizedBox(
-                  height: 26.h,
-                ),
-                // 创建一个1分钟的倒计时
-                CountdownTimer(duration: const Duration(minutes: 1)),
-                InfinitePreviewPageView(
-                  height: 80.h,
-                  autoPlay: true,
-                  autoPlayDuration: const Duration(seconds: 3),
-                  items: [
-                    PreviewItem(
-                      title: 'Merge Duplicate Contacts',
-                      icon: Icons.people_outline,
-                      onTap: () => print('Tapped Merge'),
-                    ),
-                    PreviewItem(
-                      title: 'Clean Storage',
-                      icon: Icons.storage_outlined,
-                      onTap: () => print('Tapped Clean'),
+                  SizedBox(
+                    height: 26.h,
+                  ),
+                  // 创建一个1分钟的倒计时
+                  CountdownTimer(duration: const Duration(minutes: 1)),
+                  SizedBox(
+                    height: 40.h,
+                  ),
+                  InfinitePreviewPageView(
+                    height: 98.h,
+                    autoPlay: true,
+                    autoPlayDuration: const Duration(seconds: 5),
+                    items: [
+                      PreviewItem(
+                        title: 'One-click Remove Similar Photos',
+                        icon: Assets.images.iconStoreSimilar.image(),
+                      ),
+                      PreviewItem(
+                        title: 'Detect Blurry and Junk Photos',
+                        icon: Assets.images.iconStoreAi.image(),
+                      ),
+                      PreviewItem(
+                        title: 'Merge Duplicate Contacts',
+                        icon: Assets.images.iconStoreContacts.image(),
+                      ),
+                      PreviewItem(
+                        title: 'Premium Unlimited',
+                        icon: Assets.images.iconStorePremium.image(),
+                      ),
+                    ],
+                  ),
+                  Spacer(),
+                  Text(
+                    isShowFree
+                        ? controller.currentSelectedStoreItem.value
+                                ?.freeTrialName ??
+                            ""
+                        : controller.currentSelectedStoreItem.value?.name ?? "",
+                    style: TextStyle(
+                      color: Colors.white,
+                      fontSize: 16.sp,
+                      fontWeight: FontWeight.w500,
                     ),
-                    PreviewItem(
-                      title: 'Backup Photos',
-                      icon: Icons.photo_library_outlined,
-                      onTap: () => print('Tapped Backup'),
+                  ),
+                  Text(
+                    isShowFree
+                        ? controller.currentSelectedStoreItem.value
+                                ?.freeTrialPriceDesc ??
+                            ""
+                        : controller
+                                .currentSelectedStoreItem.value?.priceDesc ??
+                            "",
+                    style: TextStyle(
+                      color: Colors.white,
+                      fontSize: 13.sp,
                     ),
-                  ],
-                ),
-                Spacer(),
-                Text(
-                  "3-Day Free Trial",
-                  style: TextStyle(
-                    color: Colors.white,
-                    fontSize: 16.sp,
-                    fontWeight: FontWeight.w500,
                   ),
-                ),
-                Text(
-                  "Then \$69.99",
-                  style: TextStyle(
-                    color: Colors.white,
-                    fontSize: 13.sp,
+                  SizedBox(
+                    height: 14.h,
                   ),
-                ),
-                SizedBox(
-                  height: 14.h,
-                ),
-                GestureDetector(
-                  onTap: () {
-                    // controller.onBuyClick();
-                  },
-                  child: Container(
-                    width: 312.w,
-                    height: 48.h,
-                    decoration: BoxDecoration(
-                      color: "#0279FB".color,
-                      borderRadius: BorderRadius.all(
-                        Radius.circular(24.r),
+                  GestureDetector(
+                    onTap: () {
+                      controller.onBuyClick();
+                    },
+                    child: Container(
+                      width: 312.w,
+                      height: 48.h,
+                      decoration: BoxDecoration(
+                        color: "#0279FB".color,
+                        borderRadius: BorderRadius.all(
+                          Radius.circular(24.r),
+                        ),
                       ),
-                    ),
-                    child: Center(
-                      child: Text(
-                        "START FREE TRIAL",
-                        style: TextStyle(
-                          color: Colors.white,
-                          fontWeight: FontWeight.w700,
-                          fontSize: 16.sp,
+                      child: Center(
+                        child: Text(
+                          isShowFree
+                              ? "START FREE TRIAL"
+                              : "START NOW",
+                          style: TextStyle(
+                            color: Colors.white,
+                            fontWeight: FontWeight.w700,
+                            fontSize: 16.sp,
+                          ),
                         ),
                       ),
                     ),
                   ),
-                ),
-                SizedBox(
-                  height: 5.h,
-                ),
-                Text(
-                  "No payment now!",
-                  style: TextStyle(
-                    color: "#57C87A".color,
-                    fontSize: 12.sp,
-                    fontWeight: FontWeight.w500,
+                  SizedBox(
+                    height: 5.h,
+                  ),
+                  Text(
+                    isShowFree
+                        ? "No payment now!"
+                        : "Cancel anytime",
+                    style: TextStyle(
+                      color: isShowFree ? "#57C87A".color : Colors.white,
+                      fontSize: 12.sp,
+                      fontWeight: FontWeight.w500,
+                    ),
                   ),
-                ),
-              ],
+                ],
+              ),
             ),
-          ),
-        ],
-      ),
-    );
+          ],
+        ),
+      );
+    });
   }
-}
+}

+ 22 - 18
lib/module/store/discount/func_page_view.dart

@@ -115,11 +115,11 @@ class _InfinitePreviewPageViewState extends State<InfinitePreviewPageView> {
             child: Opacity(
               opacity: opacity,
               child: Padding(
-                padding: const EdgeInsets.symmetric(horizontal: 8),
+                padding: const EdgeInsets.symmetric(horizontal: 0),
                 child: Container(
                   decoration: BoxDecoration(
                     color: const Color(0xFF1E1E1E),
-                    borderRadius: BorderRadius.circular(16),
+                    borderRadius: BorderRadius.circular(23.r),
                   ),
                   child: widget.items[itemIndex],
                 ),
@@ -135,7 +135,7 @@ class _InfinitePreviewPageViewState extends State<InfinitePreviewPageView> {
 // 单个项目组件
 class PreviewItem extends StatelessWidget {
   final String title;
-  final IconData icon;
+  final Image icon;
   final VoidCallback? onTap;
 
   const PreviewItem({
@@ -151,7 +151,7 @@ class PreviewItem extends StatelessWidget {
       onTap: onTap,
       // borderRadius: BorderRadius.circular(16),
       child: Container(
-        padding: const EdgeInsets.all(16),
+        padding: EdgeInsets.only(left: 16.w, right: 16.w),
         decoration: BoxDecoration(
           gradient: LinearGradient(
             begin: Alignment.centerLeft,
@@ -166,21 +166,25 @@ class PreviewItem extends StatelessWidget {
         child: Row(
           children: [
             Container(
-              width: 40,
-              height: 40,
-              child: Icon(
-                icon,
-                color: Colors.purple,
-                size: 24,
-              ),
+              width: 56.w,
+              height: 56.w,
+              child: icon,
             ),
-            const SizedBox(width: 12),
-            Text(
-              title,
-              style: const TextStyle(
-                color: Colors.white,
-                fontSize: 16,
-                fontWeight: FontWeight.w500,
+            SizedBox(width: 10.w),
+            Expanded(
+              child: Column(
+                crossAxisAlignment: CrossAxisAlignment.start,
+                mainAxisAlignment: MainAxisAlignment.center,
+                children: [
+                  Text(
+                    title,
+                    style: TextStyle(
+                      color: Colors.white,
+                      fontSize: 15.sp,
+                      fontWeight: FontWeight.w500,
+                    ),
+                  ),
+                ],
               ),
             ),
           ],

+ 16 - 21
lib/module/store/store_controller.dart

@@ -4,6 +4,7 @@ import 'package:apple_pay/apple_pay.dart';
 import 'package:classify_photo/classify_photo.dart';
 import 'package:clean/data/repositories/user_repository.dart';
 import 'package:clean/module/store/payment_status_manager.dart';
+import 'package:clean/router/app_pages.dart';
 import 'package:clean/utils/error_handler.dart';
 import 'package:flutter/Material.dart';
 import 'package:get/get.dart';
@@ -37,11 +38,22 @@ class StoreController extends BaseController implements PaymentStatusCallback {
 
   @override
   Future<void> onInit() async {
-
+    super.onInit();
     initStoreIndexData();
   }
 
+  @override
+  void onClose() {
+    // TODO: implement onClose
+    super.onClose();
+    if (isFirstIntoStore() && !userRepository.isVip()) {
+      Get.toNamed(RoutePath.discount);
+      setFirstIntoStore(false);
+    }
+  }
+
   void initStoreIndexData() {
+    LoadingDialog.show();
     storeRepository.storeIndex().then((indexData) async {
       storeItems.clear();
       storeItems.addAll(indexData.items);
@@ -61,6 +73,7 @@ class StoreController extends BaseController implements PaymentStatusCallback {
       }
 
       isFree.value = await ApplePay().check(freeAppleId);
+      LoadingDialog.hide();
     });
   }
 
@@ -73,7 +86,7 @@ class StoreController extends BaseController implements PaymentStatusCallback {
     int payPlatform = paymentWay.payPlatform;
     int payMethod = paymentWay.payMethod;
 
-    LoadingDialog.show("");
+    LoadingDialog.show();
 
     Future.delayed(const Duration(seconds: 20), () {
       LoadingDialog.hide();
@@ -107,7 +120,7 @@ class StoreController extends BaseController implements PaymentStatusCallback {
     }
     int payPlatform = paymentWay.payPlatform;
     int payMethod = paymentWay.payMethod;
-    LoadingDialog.show("");
+    LoadingDialog.show();
     try {
       // OrderPayResponse response =
       storeRepository.orderPay(storeItem.id, payPlatform, payMethod).then((response) async {
@@ -137,23 +150,6 @@ class StoreController extends BaseController implements PaymentStatusCallback {
         LoadingDialog.hide();
         ErrorHandler.toastError(error);
       });
-
-      // AgilePay.startPay(payInfo, success: (String? result) {
-      //   LoadingDialog.show("");
-      //   checkPaymentStatus(outTradeNo, paymentWay, storeItem,
-      //       receiptData: result);
-      //   Future.delayed(const Duration(seconds: 30), () {
-      //     LoadingDialog.hide();
-      //   });
-      // }, payError: (int error, String? errorMessage) {
-      //   debugPrint('zk---payError: $error, $errorMessage');
-      //   // errorPayToast(error);
-      //   LoadingDialog.hide();
-      // }, error: (int errno, String? error) {
-      //   debugPrint('zk---error: $errno, $error');
-      //   // errorPayToast(errno);
-      //   LoadingDialog.hide();
-      // });
     } catch (error) {
       LoadingDialog.hide();
       // ToastUtil.showToast(StringName.storePayError.tr);
@@ -219,6 +215,5 @@ class StoreController extends BaseController implements PaymentStatusCallback {
     // TODO: implement onPaymentError
     LoadingDialog.hide();
     ErrorHandler.toastError(error);
-    userRepository.getUserInfo();
   }
 }