ソースを参照

[feat]新增折扣页,添加网络错误提示

Destiny 1 年間 前
コミット
21509865cf

+ 1 - 1
lib/data/repositories/store_repository.dart

@@ -30,7 +30,7 @@ class StoreRepository {
       int itemId, int payPlatform, int payMethod) {
     return atmobApi
         .orderPay(OrderPayRequest(itemId, payPlatform, payMethod))
-        .then(HttpHandler.handle(false));
+        .then(HttpHandler.handle(true));
   }
 
   Future<int> orderStatus(String outTradeNo, {String? receiptData}) {

+ 23 - 0
lib/module/store/discount/discount_view.dart

@@ -9,6 +9,7 @@ import 'package:get/get.dart';
 
 import '../../../resource/assets.gen.dart';
 import 'count_down_timer.dart';
+import 'func_page_view.dart';
 
 class DiscountPage extends BasePage<DiscountController> {
   const DiscountPage({super.key});
@@ -97,6 +98,28 @@ class DiscountPage extends BasePage<DiscountController> {
                 ),
                 // 创建一个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'),
+                    ),
+                    PreviewItem(
+                      title: 'Backup Photos',
+                      icon: Icons.photo_library_outlined,
+                      onTap: () => print('Tapped Backup'),
+                    ),
+                  ],
+                ),
                 Spacer(),
                 Text(
                   "3-Day Free Trial",

+ 191 - 0
lib/module/store/discount/func_page_view.dart

@@ -0,0 +1,191 @@
+import 'dart:async';
+
+import 'package:clean/utils/expand.dart';
+import 'package:flutter/Material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+
+class InfinitePreviewPageView extends StatefulWidget {
+  final List<Widget> items;
+  final double height;
+  final Duration autoPlayDuration;
+  final bool autoPlay;
+
+  const InfinitePreviewPageView({
+    super.key,
+    required this.items,
+    this.height = 98,
+    this.autoPlayDuration = const Duration(seconds: 3),
+    this.autoPlay = true,
+  });
+
+  @override
+  State<InfinitePreviewPageView> createState() => _InfinitePreviewPageViewState();
+}
+
+class _InfinitePreviewPageViewState extends State<InfinitePreviewPageView> {
+  late final PageController _pageController;
+  Timer? _timer;
+  double _currentPage = 0;
+  bool _isInitialized = false;
+
+  // 实际页面索引
+  int get _actualPage => _currentPage.round() % widget.items.length;
+
+  @override
+  void initState() {
+    super.initState();
+    // 设置初始页面
+    final initialPage = widget.items.length * 1000;
+    _currentPage = initialPage.toDouble(); // 设置初始currentPage
+
+    _pageController = PageController(
+      viewportFraction: 0.8,
+      initialPage: initialPage,
+    );
+
+    _pageController.addListener(() {
+      setState(() {
+        _currentPage = _pageController.page ?? initialPage.toDouble();
+      });
+    });
+
+    // 延迟一帧设置初始化完成
+    WidgetsBinding.instance.addPostFrameCallback((_) {
+      setState(() {
+        _isInitialized = true;
+      });
+    });
+
+    if (widget.autoPlay) {
+      _startAutoPlay();
+    }
+  }
+
+  void _startAutoPlay() {
+    _timer?.cancel();
+    _timer = Timer.periodic(widget.autoPlayDuration, (timer) {
+      if (widget.items.isNotEmpty) {
+        _pageController.nextPage(
+          duration: const Duration(milliseconds: 300),
+          curve: Curves.easeOut,
+        );
+      }
+    });
+  }
+
+  @override
+  void dispose() {
+    _pageController.dispose();
+    _timer?.cancel();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    if (widget.items.isEmpty) return const SizedBox();
+
+    return SizedBox(
+      height: widget.height,
+      child: PageView.builder(
+        controller: _pageController,
+        itemBuilder: (context, index) {
+          final itemIndex = index % widget.items.length;
+
+          // 如果还没初始化完成,不应用缩放效果
+          if (!_isInitialized) {
+            return Padding(
+              padding: const EdgeInsets.symmetric(horizontal: 8),
+              child: Container(
+                decoration: BoxDecoration(
+                  color: const Color(0xFF1E1E1E),
+                  borderRadius: BorderRadius.circular(16),
+                ),
+                child: widget.items[itemIndex],
+              ),
+            );
+          }
+
+          // 计算缩放和透明度
+          double diffScale = (_currentPage - index).abs();
+          double scale = 1 - (diffScale * 0.1).clamp(0.0, 0.3);
+          double opacity = 1 - (diffScale * 0.3).clamp(0.0, 0.5);
+
+          return Transform.scale(
+            scale: scale,
+            child: Opacity(
+              opacity: opacity,
+              child: Padding(
+                padding: const EdgeInsets.symmetric(horizontal: 8),
+                child: Container(
+                  decoration: BoxDecoration(
+                    color: const Color(0xFF1E1E1E),
+                    borderRadius: BorderRadius.circular(16),
+                  ),
+                  child: widget.items[itemIndex],
+                ),
+              ),
+            ),
+          );
+        },
+      ),
+    );
+  }
+}
+
+// 单个项目组件
+class PreviewItem extends StatelessWidget {
+  final String title;
+  final IconData icon;
+  final VoidCallback? onTap;
+
+  const PreviewItem({
+    super.key,
+    required this.title,
+    required this.icon,
+    this.onTap,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    return InkWell(
+      onTap: onTap,
+      // borderRadius: BorderRadius.circular(16),
+      child: Container(
+        padding: const EdgeInsets.all(16),
+        decoration: BoxDecoration(
+          gradient: LinearGradient(
+            begin: Alignment.centerLeft,
+            end: Alignment.centerRight,
+            colors: [
+              '#36363D'.color,
+              '#18181F'.color,
+            ],
+          ),
+          borderRadius: BorderRadius.circular(23.r),
+        ),
+        child: Row(
+          children: [
+            Container(
+              width: 40,
+              height: 40,
+              child: Icon(
+                icon,
+                color: Colors.purple,
+                size: 24,
+              ),
+            ),
+            const SizedBox(width: 12),
+            Text(
+              title,
+              style: const TextStyle(
+                color: Colors.white,
+                fontSize: 16,
+                fontWeight: FontWeight.w500,
+              ),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+}

+ 33 - 30
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/utils/error_handler.dart';
 import 'package:flutter/Material.dart';
 import 'package:get/get.dart';
 import 'package:in_app_purchase/in_app_purchase.dart';
@@ -74,6 +75,11 @@ class StoreController extends BaseController implements PaymentStatusCallback {
 
     LoadingDialog.show("");
 
+    Future.delayed(const Duration(seconds: 20), () {
+      LoadingDialog.hide();
+      ToastUtil.show("Restore record not found");
+    });
+
     final result = await ApplePay().restore();
     if (result["success"] == true) {
       // LoadingDialog.hide();
@@ -86,11 +92,6 @@ class StoreController extends BaseController implements PaymentStatusCallback {
       print('恢复失败: ${result['error']}');
     }
 
-    Future.delayed(const Duration(seconds: 20), () {
-      LoadingDialog.hide();
-      ToastUtil.show("Restore record not found");
-    });
-
     // AgilePay.restore(success: (String? result) {
     //   // LoadingDialog.hide();
     // }, payError: (int error, String? errorMessage) {
@@ -126,33 +127,35 @@ class StoreController extends BaseController implements PaymentStatusCallback {
     int payMethod = paymentWay.payMethod;
     LoadingDialog.show("");
     try {
-      OrderPayResponse response =
-      await storeRepository.orderPay(storeItem.id, payPlatform, payMethod);
-
-      dynamic payInfo;
-      String outTradeNo = response.outTradeNo;
-      if (payPlatform == PayPlatform.apple) {
-        payInfo = ApplePayInfo(
-            storeItem.appleGoodsId,
-            storeItem.subscribable == 1
-                ? ProductType.nonConsumable
-                : ProductType.consumable,
-            response.appAccountToken);
-      }
-      Future.delayed(const Duration(seconds: 30), () {
+      // 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");
+          print('购买失败: ${result['error']}');
+        }
+      }).catchError((error) {
         LoadingDialog.hide();
+        ErrorHandler.toastError(error);
       });
-      final result = await ApplePay().purchase(productId: storeItem.appleGoodsId, appAccountToken: response.appAccountToken);
-      if (result["success"] == true) {
-        // LoadingDialog.hide();
-        var receipt = result['receipt'];
-        print('购买成功: ${result['receipt']}');
-        checkPaymentStatus(outTradeNo, paymentWay, storeItem, receiptData: receipt);
-      } else {
-        LoadingDialog.hide();
-        ToastUtil.show("Pay Error");
-        print('购买失败: ${result['error']}');
-      }
+
       // AgilePay.startPay(payInfo, success: (String? result) {
       //   LoadingDialog.show("");
       //   checkPaymentStatus(outTradeNo, paymentWay, storeItem,

+ 34 - 0
lib/utils/error_handler.dart

@@ -0,0 +1,34 @@
+import 'package:clean/utils/toast_util.dart';
+import 'package:get/get.dart';
+import '../resource/string.gen.dart';
+import 'http_handler.dart';
+
+class ErrorHandler {
+  ErrorHandler._();
+
+  static void toastError(dynamic error, {String? message}) {
+    String toastMessage = (error is ServerErrorException)
+        ? _getToastMessageFromError(error)
+        : _getDefaultToastMessage(message);
+    ToastUtil.show(toastMessage);
+  }
+
+  static String _getToastMessageFromError(ServerErrorException error) {
+    return getErrorCodeMsg(error.code) ??
+        error.message ?? "connect error";
+  }
+
+  static String? getErrorCodeMsg(int? code) {
+    String? msg;
+    // switch (code) {
+    //   case ErrorCode.errorCodeNoLogin:
+    //     msg = StringName.errorCodeNoLogin;
+    //     break;
+    // }
+    return msg;
+  }
+
+  static String _getDefaultToastMessage(String? message) {
+    return message ?? "connect error";
+  }
+}