purchase_legal_notes.dart 6.88 KB
import 'package:doublefeel_flutter/app/modules/purchase/widgets/purchase_colors.dart';
import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

/// OHOS subscription terms shown on the purchase and membership pages.
class OhosPurchaseLegalNotes extends StatelessWidget {
  const OhosPurchaseLegalNotes({
    super.key,
    required this.onContactUsTap,
    required this.onSupportEmailTap,
  });

  final VoidCallback onContactUsTap;
  final VoidCallback onSupportEmailTap;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            context.l10n.purchaseNotesTitle,
            style: const TextStyle(
              color: Colors.black,
              fontSize: 18,
              fontWeight: FontWeight.w600,
              height: 1.2,
            ),
          ),
          const SizedBox(height: 24),
          const _LegalText(
            prefix: '1. ',
            spans: [
              _LegalTextSpan(
                text: '本服务为自动续费订阅服务,¥68/年或¥16/月,到期后将按所选订阅周期自动续费并扣款。',
              ),
            ],
          ),
          const SizedBox(height: 12),
          const _LegalText(
            prefix: '2. ',
            spans: [
              _LegalTextSpan(
                text:
                    '如需取消自动续费,请前往支付宝 > 我的 > 设置 > 支付设置 > 免密支付/自动扣款 > 关闭对应服务。取消后,当前会员权益可使用至本周期结束,下个周期将不再扣费。',
              ),
            ],
          ),
          const SizedBox(height: 12),
          const _LegalText(
            prefix: '3. ',
            spans: [
              _LegalTextSpan(
                text: '确认支付成功后,会员权益立即生效。如支付成功后会员未生效,请尝试重新登录账号。',
              ),
            ],
          ),
          const SizedBox(height: 12),
          const _LegalText(
            prefix: '4. ',
            spans: [
              _LegalTextSpan(
                text: 'DoubleFeel Pro 属于虚拟服务商品,购买后除法律法规另有规定外,通常不支持退款。',
              ),
            ],
          ),
          const SizedBox(height: 12),
          _LegalText(
            prefix: '5. ',
            spans: const [
              _LegalTextSpan(text: '如有其他问题,请'),
              _LegalTextSpan(text: '联系我们', isLink: true),
              _LegalTextSpan(text: ',或发送邮件至'),
              _LegalTextSpan(text: 'support@doublefeel.cn', isLink: true),
              _LegalTextSpan(text: '。'),
            ],
            onLinkTaps: [onContactUsTap, onSupportEmailTap],
          ),
        ],
      ),
    );
  }
}

/// iOS subscription terms shown on the purchase and membership pages.
class IosPurchaseLegalNotes extends StatelessWidget {
  const IosPurchaseLegalNotes({
    required this.onRefundExplanationTap,
    required this.onContactUsTap,
  });

  final VoidCallback onRefundExplanationTap;
  final VoidCallback onContactUsTap;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            context.l10n.purchaseNotesTitle,
            style: const TextStyle(
              color: Colors.black,
              fontSize: 18,
              fontWeight: FontWeight.w600,
              height: 1.2,
            ),
          ),
          const SizedBox(height: 24),
          _LegalText(
            prefix: '1. ',
            spans: [
              _LegalTextSpan(text: context.l10n.purchaseNoteSubscription),
              _LegalTextSpan(
                text: context.l10n.purchaseLinkLearnMore,
                isLink: true,
              ),
              const _LegalTextSpan(text: '。'),
            ],
            onLinkTaps: [onRefundExplanationTap],
          ),
          const SizedBox(height: 12),
          _LegalText(
            prefix: '2. ',
            spans: [
              _LegalTextSpan(text: context.l10n.purchaseNoteContact),
              _LegalTextSpan(
                text: context.l10n.purchaseLinkContactUs,
                isLink: true,
              ),
              const _LegalTextSpan(text: '。'),
            ],
            onLinkTaps: [onContactUsTap],
          ),
        ],
      ),
    );
  }
}

class _LegalTextSpan {
  const _LegalTextSpan({required this.text, this.isLink = false});

  final String text;
  final bool isLink;
}

class _LegalText extends StatefulWidget {
  const _LegalText({
    required this.prefix,
    required this.spans,
    this.onLinkTaps = const [],
  });

  final String prefix;
  final List<_LegalTextSpan> spans;
  final List<VoidCallback> onLinkTaps;

  @override
  State<_LegalText> createState() => _LegalTextState();
}

class _LegalTextState extends State<_LegalText> {
  late List<TapGestureRecognizer> _linkRecognizers;

  @override
  void initState() {
    super.initState();
    _linkRecognizers = _createRecognizers();
  }

  @override
  void didUpdateWidget(covariant _LegalText oldWidget) {
    super.didUpdateWidget(oldWidget);
    for (var index = 0; index < _linkRecognizers.length; index++) {
      _linkRecognizers[index].onTap = widget.onLinkTaps[index];
    }
  }

  List<TapGestureRecognizer> _createRecognizers() => [
        for (final onTap in widget.onLinkTaps)
          TapGestureRecognizer()..onTap = onTap,
      ];

  @override
  void dispose() {
    for (final recognizer in _linkRecognizers) {
      recognizer.dispose();
    }
    super.dispose();
  }

  static const _baseStyle = TextStyle(
    color: PurchaseColors.subtitle,
    fontSize: 12,
    height: 1.4,
  );

  static const _linkStyle = TextStyle(
    color: Color(0xFF845EEE),
    fontWeight: FontWeight.w500,
    decoration: TextDecoration.underline,
    decorationColor: Color(0xFF845EEE),
  );

  @override
  Widget build(BuildContext context) {
    var linkIndex = 0;
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(widget.prefix, style: _baseStyle),
        Expanded(
          child: Text.rich(
            TextSpan(
              style: _baseStyle,
              children: [
                for (final span in widget.spans)
                  TextSpan(
                    text: span.text,
                    style: span.isLink ? _linkStyle : null,
                    recognizer:
                        span.isLink ? _linkRecognizers[linkIndex++] : null,
                  ),
              ],
            ),
            textScaler: MediaQuery.textScalerOf(context),
          ),
        ),
      ],
    );
  }
}