import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; import '../../../core/providers/app_providers.dart'; import '../../config/application/app_config_controller.dart'; /// Observable state for the in-app purchase flow. class IapState { const IapState({ this.product, this.isLoading = false, this.isPurchasing = false, this.errorMessage, this.productsAvailable = false, this.lastEvent, }); final ProductDetails? product; final bool isLoading; final bool isPurchasing; final String? errorMessage; final bool productsAvailable; /// Last human-readable event (e.g. "解锁成功"). final String? lastEvent; IapState copyWith({ ProductDetails? product, bool? isLoading, bool? isPurchasing, String? errorMessage, bool? productsAvailable, String? lastEvent, }) { return IapState( product: product ?? this.product, isLoading: isLoading ?? this.isLoading, isPurchasing: isPurchasing ?? this.isPurchasing, errorMessage: errorMessage, productsAvailable: productsAvailable ?? this.productsAvailable, lastEvent: lastEvent, ); } } final iapControllerProvider = NotifierProvider( IapController.new, ); class IapController extends Notifier { late final InAppPurchase _store = InAppPurchase.instance; StreamSubscription>? _sub; @override IapState build() { ref.onDispose(() { _sub?.cancel(); _sub = null; }); _init(); return const IapState(isLoading: true); } Future _init() async { final available = await _store.isAvailable(); if (!available) { state = state.copyWith( isLoading: false, productsAvailable: false, errorMessage: '无法连接到 App Store,请检查网络设置', ); return; } _sub = _store.purchaseStream.listen( _onPurchaseUpdated, onError: (Object e) { state = state.copyWith(isPurchasing: false, errorMessage: '支付失败:$e'); }, ); await _loadProduct(); } Future _loadProduct() async { final response = await _store.queryProductDetails({kUnlockProductId}); final error = response.error; if (error != null) { state = state.copyWith( isLoading: false, productsAvailable: false, errorMessage: '商品信息加载失败:${error.message}', ); return; } final products = response.productDetails; if (products.isEmpty) { // No product configured in App Store Connect yet. state = state.copyWith( isLoading: false, productsAvailable: false, errorMessage: null, ); return; } final product = products.first; state = state.copyWith( isLoading: false, productsAvailable: true, product: product, errorMessage: null, ); } Future buy() async { final product = state.product; if (product == null) { state = state.copyWith(errorMessage: '商品尚未就绪,请稍后再试'); return; } state = state.copyWith( isPurchasing: true, errorMessage: null, lastEvent: null, ); final param = PurchaseParam(productDetails: product); final ok = await _store.buyNonConsumable(purchaseParam: param); if (!ok) { state = state.copyWith(isPurchasing: false, errorMessage: '支付已取消'); } } Future restore() async { state = state.copyWith( isLoading: true, errorMessage: null, lastEvent: null, ); await _store.restorePurchases(); // The purchase stream will deliver restored purchases; clear loading after // a short delay in case nothing arrives. Future.delayed(const Duration(seconds: 2), () { if (state.isLoading) { state = state.copyWith( isLoading: false, lastEvent: state.lastEvent ?? '未找到历史购买记录', ); } }); } void _onPurchaseUpdated(List purchases) { for (final purchase in purchases) { _handle(purchase); } } Future _handle(PurchaseDetails purchase) async { switch (purchase.status) { case PurchaseStatus.purchased: case PurchaseStatus.restored: if (purchase.pendingCompletePurchase) { await _store.completePurchase(purchase); } await ref .read(appConfigProvider.notifier) .markUnlocked( fromRestore: purchase.status == PurchaseStatus.restored, ); state = IapState( product: state.product, productsAvailable: state.productsAvailable, lastEvent: purchase.status == PurchaseStatus.restored ? '权益已恢复' : '解锁成功!已获得无限记分权益', ); break; case PurchaseStatus.error: state = state.copyWith( isPurchasing: false, errorMessage: '支付失败:${purchase.error?.message ?? '未知错误'}', ); break; case PurchaseStatus.canceled: state = state.copyWith(isPurchasing: false, lastEvent: '支付已取消'); break; case PurchaseStatus.pending: // Purchase awaiting approval/processing; keep the loading state. state = state.copyWith(isPurchasing: true); break; } } /// Debug-only unlock used so the app remains usable without App Store /// Connect configuration during development. Future debugUnlock() async { await ref.read(appConfigProvider.notifier).debugUnlock(); state = state.copyWith(lastEvent: '已通过开发模式解锁'); } void clearEvent() { state = state.copyWith(lastEvent: null, errorMessage: null); } } @visibleForTesting IapState iapStateForTest({ ProductDetails? product, bool isLoading = false, bool isPurchasing = false, String? errorMessage, bool productsAvailable = false, String? lastEvent, }) { return IapState( product: product, isLoading: isLoading, isPurchasing: isPurchasing, errorMessage: errorMessage, productsAvailable: productsAvailable, lastEvent: lastEvent, ); }