purchase_view.dart 9.57 KB
import 'package:doublefeel_flutter/app/utils/assets_helper.dart';
import 'package:doublefeel_flutter/app/widget/payment_confirmation_bottom_sheet.dart';
import 'package:doublefeel_flutter/app/widget/purchase_legal_notes.dart';
import 'package:doublefeel_flutter/app/widget/premium_benefit_list_view.dart';
import 'package:doublefeel_flutter/core/util/size_extensions.dart';
import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
import 'package:doublefeel_flutter/r.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';

import '../controllers/purchase_controller.dart';
import '../purchase_route_args.dart';
import '../widgets/purchase_bottom_bar.dart';
import '../widgets/purchase_colors.dart';
import '../widgets/purchase_plan_card.dart';

class PurchaseView extends GetView<PurchaseController> {
  const PurchaseView({super.key});

  @override
  Widget build(BuildContext context) {
    return PopScope(
        canPop: !controller.isFromOnboard,
        onPopInvokedWithResult: (didPop, _) {
          if (!didPop) controller.onBackPressed();
        },
        child: AnnotatedRegion<SystemUiOverlayStyle>(
          value: const SystemUiOverlayStyle(
            statusBarColor: Colors.transparent,
            statusBarIconBrightness: Brightness.dark,
            statusBarBrightness: Brightness.light,
            systemNavigationBarColor: PurchaseColors.background,
            systemNavigationBarIconBrightness: Brightness.dark,
          ),
          child: Scaffold(
            backgroundColor: PurchaseColors.background,
            body: Stack(
              children: [
                Positioned.fill(
                  child: SingleChildScrollView(
                    controller: controller.scrollController,
                    physics: const ClampingScrollPhysics(),
                    padding: EdgeInsets.only(
                      bottom: 170 + MediaQuery.paddingOf(context).bottom,
                    ),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        const _HeroHeader(),
                        Padding(
                          padding: EdgeInsets.only(left: 18, top: 9.h),
                          child: Text(
                            context.l10n.purchaseHeroTitle,
                            style: const TextStyle(
                              color: Colors.black,
                              fontSize: 24,
                              fontWeight: FontWeight.w600,
                              height: 1.2,
                            ),
                          ),
                        ),
                        const SizedBox(height: 26),
                        _PlanScroller(controller: controller),
                        const SizedBox(height: 22),
                        _SectionTitle(context.l10n.purchaseBenefitsTitle),
                        const SizedBox(height: 14),
                        const PremiumBenefitListView(),
                        const SizedBox(height: 24),
                        controller.isOhos
                            ? OhosPurchaseLegalNotes(
                                onContactUsTap: controller.openContactUs,
                                onSupportEmailTap: controller.openSupportEmail,
                              )
                            : IosPurchaseLegalNotes(
                                onRefundExplanationTap:
                                    controller.openRefundExplanation,
                                onContactUsTap: controller.openContactUs,
                              ),
                      ],
                    ),
                  ),
                ),
                Positioned(
                  left: 0,
                  right: 0,
                  top: 0,
                  child: _PurchaseNavigationBar(controller: controller),
                ),
                Positioned(
                  left: 0,
                  right: 0,
                  bottom: 0,
                  child: Obx(
                    () => PurchaseBottomBar(
                      label: context.l10n.purchaseUnlockNow,
                      loading: controller.isUnlocking.value,
                      onPressed: () {
                        if (controller.isOhos) {
                          final plan = controller.selectedPlan;
                          if (plan == null) {
                            controller.unlock();
                            return;
                          }
                          showPaymentConfirmationBottomSheet(
                            amount: plan.price,
                            productName: plan.title.isEmpty
                                ? 'DoubleFeel终身会员'
                                : plan.title,
                            paymentMethod: const PaymentMethod(
                              name: '支付宝支付',
                              iconAsset:
                                  'assets/images/premium/ic_payment_alipay.png',
                            ),
                            onPay: controller.unlock,
                          );
                          return;
                        }
                        controller.unlock();
                      },
                      onTermsTap: controller.openUserTerms,
                      onPrivacyTap: controller.openPrivacyPolicy,
                      showAutoRenewalAgreement: controller.isOhos,
                      onAutoRenewalAgreementTap:
                          controller.openAutoRenewalAgreement,
                    ),
                  ),
                ),
              ],
            ),
          ),
        ));
  }
}

class _HeroHeader extends StatelessWidget {
  const _HeroHeader();

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 320.h,
      width: double.infinity,
      child: Image.asset(
        assetCompatPath('assets/images/premium/bg_purchase_top.webp'),
        width: double.infinity,
        height: double.infinity,
        fit: BoxFit.cover,
      ),
    );
  }
}

class _PlanScroller extends StatelessWidget {
  const _PlanScroller({required this.controller});

  final PurchaseController controller;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 123,
      child: Obx(
        () {
          if (controller.isLoadingProducts.value && controller.plans.isEmpty) {
            return const Center(
              child: CircularProgressIndicator(strokeWidth: 2),
            );
          }

          final selectedIndex = controller.selectedIndex.value;
          return ListView.separated(
            controller: controller.planScrollController,
            padding: const EdgeInsets.symmetric(horizontal: 18),
            scrollDirection: Axis.horizontal,
            physics: const ClampingScrollPhysics(),
            itemCount: controller.plans.length,
            separatorBuilder: (_, __) => const SizedBox(width: 12),
            itemBuilder: (context, index) {
              final plan = controller.plans[index];
              return PurchasePlanCard(
                plan: plan,
                selected: selectedIndex == index,
                onTap: () => controller.selectPlan(index),
              );
            },
          );
        },
      ),
    );
  }
}

class _SectionTitle extends StatelessWidget {
  const _SectionTitle(this.text);

  final String text;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Text(
        text,
        style: const TextStyle(
          color: Colors.black,
          fontSize: 18,
          fontWeight: FontWeight.w600,
          height: 1.2,
        ),
      ),
    );
  }
}

class _PurchaseNavigationBar extends StatelessWidget {
  const _PurchaseNavigationBar({required this.controller});

  final PurchaseController controller;

  @override
  Widget build(BuildContext context) {
    final topInset = MediaQuery.paddingOf(context).top;
    final arguments = Get.arguments;
    final showCloseButton = arguments is Map &&
        arguments[PurchaseRouteArgs.showCloseButton] == true;
    return Obx(
      () => AnimatedContainer(
        duration: const Duration(milliseconds: 160),
        height: topInset + 44,
        padding: EdgeInsets.only(top: topInset),
        color: controller.isScrolled.value ? Colors.white : Colors.transparent,
        child: Row(
          children: [
            const SizedBox(width: 4),
            IconButton(
              onPressed: controller.onBackPressed,
              icon: Image.asset(
                showCloseButton
                    ? R.assetsImagesCloseIcon
                    : R.assetsImagesNavBackIcon,
                width: 24,
                height: 24,
              ),
            ),
            const Spacer(),
            /*
            TextButton(
              onPressed: controller.isRestoring.value
                  ? null
                  : controller.restorePurchase,
              style: TextButton.styleFrom(
                foregroundColor: PurchaseColors.title,
                padding: const EdgeInsets.symmetric(horizontal: 16),
                textStyle: const TextStyle(fontSize: 14, height: 1.2),
              ),
              child: controller.isRestoring.value
                  ? const SizedBox(
                      width: 16,
                      height: 16,
                      child: CircularProgressIndicator(strokeWidth: 2),
                    )
                  : Text(context.l10n.purchaseRestore),
            ),
            */
          ],
        ),
      ),
    );
  }
}