sk2helper 0.0.1 copy "sk2helper: ^0.0.1" to clipboard
sk2helper: ^0.0.1 copied to clipboard

A StoreKit 2 helper plugin for Flutter

example/lib/main.dart

import 'dart:async';
import 'dart:convert';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:sk2helper/models.dart';
import 'package:sk2helper/sk2helper.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool _isLoading = false;
  String _status = '初始化中...';
  List<Product> _products = [];
  final List<Subscription> _subscriptions = [];
  List<RestoredPurchase> _restoredPurchases = [];
  bool _hasActiveSub = false;

  // 测试用产品ID(需要替换为实际的产品ID)
  final List<String> _productIds = [
    'com.example.consumable_product', // 消耗型
    'com.example.nonconsumable_product', // 非消耗型
    'com.example.monthly_subscription', // 月度订阅
    'com.example.yearly_subscription', // 年度订阅
  ];

  @override
  void initState() {
    super.initState();
    _initStoreKit();
  }

  // 初始化 StoreKit
  Future<void> _initStoreKit() async {
    setState(() => _isLoading = true);
    _updateStatus('初始化 StoreKit...');

    try {
      await Sk2helper.initialize();
      _updateStatus('StoreKit 初始化成功');

      // 检查是否有活跃订阅
      _hasActiveSub = await Sk2helper.hasActiveSubscription();

      // 获取产品列表
      await _fetchProducts();
    } catch (e) {
      _updateStatus('初始化失败: $e', isError: true);
    } finally {
      setState(() => _isLoading = false);
    }
  }

  // 获取产品列表
  Future<void> _fetchProducts() async {
    setState(() => _isLoading = true);
    _updateStatus('获取产品信息...');

    try {
      _products = await Sk2helper.fetchProducts(_productIds);
      _updateStatus('获取到 ${_products.length} 个产品');

      // 检查每个产品的订阅状态
      for (var product in _products.where((p) => p.type == 'AUTORENEWABLE')) {
        try {
          final status = await Sk2helper.getSubscriptionStatus(product.id);
          if (status != null) {
            _subscriptions.add(status);
          }
        } catch (e) {
          if (kDebugMode) {
            print('检查订阅状态失败: ${product.id} - $e');
          }
        }
      }
    } catch (e) {
      _updateStatus('获取产品失败: $e', isError: true);
    } finally {
      setState(() => _isLoading = false);
    }
  }

  // 购买产品
  Future<void> _purchaseProduct(Product product) async {
    if (_isLoading) return;

    setState(() => _isLoading = true);
    _updateStatus('购买 ${product.name}...');

    try {
      final transaction = await Sk2helper.purchase(product.id);

      if (kDebugMode) {
        print('购买 transaction - $transaction');
      }

      _updateStatus('购买成功: ${product.name}');

      // 如果是订阅产品,刷新订阅状态
      if (product.type == 'AUTORENEWABLE') {
        await _checkSubscriptionStatus(product.id);
      }

      _showSnackBar('购买成功: ${product.name}', isSuccess: true);
    } on StoreKitError catch (e) {
      if (e.code == 'USER_CANCELLED') {
        _updateStatus('用户取消了购买');
      } else {
        _updateStatus('购买失败: ${e.msg}', isError: true);
        _showSnackBar('购买失败: ${e.msg}');
      }
    } catch (e) {
      _updateStatus('购买失败: $e', isError: true);
      _showSnackBar('购买失败: $e');
    } finally {
      setState(() => _isLoading = false);
    }
  }

  // 恢复购买
  Future<void> _restorePurchases() async {
    if (_isLoading) return;

    setState(() => _isLoading = true);
    _updateStatus('恢复购买中...');

    try {
      _restoredPurchases = await Sk2helper.restore();

      if (_restoredPurchases.isEmpty) {
        _updateStatus('没有找到可恢复的购买');
        _showSnackBar('没有可恢复的购买记录');
      } else {
        _updateStatus('恢复成功 ${_restoredPurchases.length} 个购买');
        _showSnackBar('恢复成功 ${_restoredPurchases.length} 个购买', isSuccess: true);

        // 更新订阅状态
        for (var purchase in _restoredPurchases) {
          await _checkSubscriptionStatus(purchase.productId);
        }
      }
    } on StoreKitError catch (e) {
      _updateStatus('恢复失败: ${e.msg}', isError: true);
      _showSnackBar('恢复失败: ${e.msg}');
    } catch (e) {
      _updateStatus('恢复失败: $e', isError: true);
      _showSnackBar('恢复失败: $e');
    } finally {
      setState(() => _isLoading = false);
    }
  }

  // 检查订阅状态
  Future<void> _checkSubscriptionStatus(String productId) async {
    try {
      final status = await Sk2helper.getSubscriptionStatus(productId);
      if (status != null) {
        // 移除旧的,添加新的
        _subscriptions.removeWhere((s) => s.productId == productId);
        _subscriptions.add(status);

        // 更新活跃订阅状态
        _hasActiveSub = await Sk2helper.hasActiveSubscription();
        setState(() {});
      }
    } catch (e) {
      if (kDebugMode) {
        print('检查订阅状态失败: $productId - $e');
      }
    }
  }

  // 获取购买历史
  Future<void> _fetchPurchaseHistory() async {
    if (_isLoading) return;

    setState(() => _isLoading = true);
    _updateStatus('获取购买历史...');

    try {
      final history = await Sk2helper.fetchPurchaseHistory();
      _updateStatus('获取到 ${history.length} 条购买记录');

      // 显示购买历史对话框
      _showPurchaseHistory(history);
    } catch (e) {
      _updateStatus('获取历史失败: $e', isError: true);
      _showSnackBar('获取购买历史失败: $e');
    } finally {
      setState(() => _isLoading = false);
    }
  }

  // 显示购买历史
  void _showPurchaseHistory(List<String> history) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('购买历史'),
        content: SizedBox(
          width: double.maxFinite,
          child: ListView.builder(
            shrinkWrap: true,
            itemCount: history.length,
            itemBuilder: (context, index) {
              try {
                final data = json.decode(history[index]);
                return ListTile(
                  title: Text('交易 ${data['transactionId'] ?? ''}'),
                  subtitle: Text('产品: ${data['productId'] ?? ''}'),
                  trailing: const Icon(Icons.receipt),
                );
              } catch (e) {
                return ListTile(
                  title: const Text('无效记录'),
                  subtitle: Text(history[index]),
                );
              }
            },
          ),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('关闭'),
          ),
        ],
      ),
    );
  }

  // 显示提示
  void _showSnackBar(String message, {bool isSuccess = false}) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: isSuccess ? Colors.green : null,
        duration: const Duration(seconds: 2),
      ),
    );
  }

  // 更新状态
  void _updateStatus(String message, {bool isError = false}) {
    setState(() {
      _status = message;
    });
    if (kDebugMode) {
      print('状态: $message');
    }
  }

  // 构建产品卡片
  Widget _buildProductCard(Product product) {
    return Card(
      elevation: 2,
      margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        product.name,
                        style: const TextStyle(
                          fontSize: 18,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 4),
                      Text(
                        product.desc,
                        style: const TextStyle(
                          fontSize: 14,
                          color: Colors.grey,
                        ),
                        maxLines: 2,
                        overflow: TextOverflow.ellipsis,
                      ),
                    ],
                  ),
                ),
                Column(
                  crossAxisAlignment: CrossAxisAlignment.end,
                  children: [
                    Text(
                      product.localPrice,
                      style: const TextStyle(
                        fontSize: 20,
                        fontWeight: FontWeight.bold,
                        color: Colors.blue,
                      ),
                    ),
                    if (product.hasTrial)
                      Container(
                        margin: const EdgeInsets.only(top: 4),
                        padding: const EdgeInsets.symmetric(
                          horizontal: 8,
                          vertical: 2,
                        ),
                        decoration: BoxDecoration(
                          color: Colors.green,
                          borderRadius: BorderRadius.circular(4),
                        ),
                        child: const Text(
                          '试用',
                          style: TextStyle(fontSize: 12, color: Colors.white),
                        ),
                      ),
                  ],
                ),
              ],
            ),

            const SizedBox(height: 12),

            // 显示产品详情
            Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                if (product.period.isNotEmpty) Text('周期: ${product.period}'),

                if (product.introOffer.isNotEmpty)
                  Text('优惠: ${product.introOffer}'),

                Text('类型: ${_getProductTypeName(product.type)}'),
              ],
            ),

            const SizedBox(height: 12),

            // 购买按钮
            ElevatedButton(
              onPressed: _isLoading ? null : () => _purchaseProduct(product),
              style: ElevatedButton.styleFrom(
                minimumSize: const Size(double.infinity, 48),
              ),
              child: _isLoading
                  ? const SizedBox(
                      width: 20,
                      height: 20,
                      child: CircularProgressIndicator(strokeWidth: 2),
                    )
                  : Text('购买 - ${product.localPrice}'),
            ),
          ],
        ),
      ),
    );
  }

  // 获取产品类型名称
  String _getProductTypeName(String type) {
    switch (type) {
      case 'CONSUMABLE':
        return '消耗型';
      case 'NON_CONSUMABLE':
        return '非消耗型';
      case 'AUTORENEWABLE':
        return '自动续订订阅';
      case 'NON_RENEWING_SUBSCRIPTION':
        return '非续订订阅';
      default:
        return type;
    }
  }

  // 构建订阅状态卡片
  Widget _buildSubscriptionCard(Subscription subscription) {
    return Card(
      color: subscription.isActive ? Colors.green[50] : null,
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              '产品: ${subscription.productId}',
              style: const TextStyle(fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 4),
            Text('状态: ${subscription.isActive ? '有效' : '无效'}'),
            if (subscription.expireTime > 0)
              Text('到期: ${_formatTime(subscription.expireTime)}'),
            if (subscription.isExpired)
              const Text('已过期', style: TextStyle(color: Colors.red)),
            if (subscription.isRevoked)
              const Text('已撤销', style: TextStyle(color: Colors.red)),
          ],
        ),
      ),
    );
  }

  // 格式化时间
  String _formatTime(int timestamp) {
    final date = DateTime.fromMillisecondsSinceEpoch(timestamp);
    return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')} ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}';
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SK2Helper 示例',
      theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('SK2Helper 示例'),
          actions: [
            if (_hasActiveSub)
              Chip(
                label: const Text('有订阅'),
                backgroundColor: Colors.green,
                labelStyle: const TextStyle(color: Colors.white),
              ),
          ],
        ),
        body: Stack(
          children: [
            // 主内容
            Column(
              children: [
                // 状态栏
                Container(
                  padding: const EdgeInsets.all(16),
                  color: Colors.grey[100],
                  child: Row(
                    children: [
                      Expanded(
                        child: Text(
                          _status,
                          style: TextStyle(
                            color: _status.contains('失败')
                                ? Colors.red
                                : Colors.grey[700],
                          ),
                        ),
                      ),
                      if (_isLoading)
                        const SizedBox(
                          width: 20,
                          height: 20,
                          child: CircularProgressIndicator(strokeWidth: 2),
                        ),
                    ],
                  ),
                ),

                // 操作按钮
                Padding(
                  padding: const EdgeInsets.all(16),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    children: [
                      ElevatedButton.icon(
                        onPressed: _isLoading ? null : _fetchProducts,
                        icon: const Icon(Icons.refresh),
                        label: const Text('刷新产品'),
                      ),
                      ElevatedButton.icon(
                        onPressed: _isLoading ? null : _restorePurchases,
                        icon: const Icon(Icons.restore),
                        label: const Text('恢复购买'),
                      ),
                      ElevatedButton.icon(
                        onPressed: _isLoading ? null : _fetchPurchaseHistory,
                        icon: const Icon(Icons.history),
                        label: const Text('购买历史'),
                      ),
                    ],
                  ),
                ),

                // 订阅状态
                if (_subscriptions.isNotEmpty) ...[
                  const Padding(
                    padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                    child: Text(
                      '订阅状态',
                      style: TextStyle(
                        fontSize: 18,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ),
                  Expanded(
                    flex: 1,
                    child: ListView.builder(
                      shrinkWrap: true,
                      itemCount: _subscriptions.length,
                      itemBuilder: (context, index) =>
                          _buildSubscriptionCard(_subscriptions[index]),
                    ),
                  ),
                ],

                // 产品列表
                Expanded(
                  flex: 2,
                  child: RefreshIndicator(
                    onRefresh: _fetchProducts,
                    child: _products.isEmpty
                        ? const Center(
                            child: Text(
                              '暂无产品',
                              style: TextStyle(
                                fontSize: 16,
                                color: Colors.grey,
                              ),
                            ),
                          )
                        : ListView.builder(
                            itemCount: _products.length,
                            itemBuilder: (context, index) =>
                                _buildProductCard(_products[index]),
                          ),
                  ),
                ),

                // 恢复的购买
                if (_restoredPurchases.isNotEmpty) ...[
                  const Divider(),
                  Padding(
                    padding: const EdgeInsets.all(16),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        const Text(
                          '恢复的购买',
                          style: TextStyle(
                            fontSize: 16,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                        const SizedBox(height: 8),
                        ..._restoredPurchases.map((purchase) {
                          return ListTile(
                            title: Text(purchase.productId),
                            subtitle: Text('交易: ${purchase.transactionId}'),
                            trailing: Text(_formatTime(purchase.purchaseTime)),
                          );
                        }),
                      ],
                    ),
                  ),
                ],
              ],
            ),

            // 加载遮罩
            if (_isLoading)
              Container(
                color: Colors.black54,
                child: const Center(child: CircularProgressIndicator()),
              ),
          ],
        ),

        // 底部信息
        bottomNavigationBar: BottomAppBar(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                Text('产品数: ${_products.length}'),
                Text('订阅数: ${_subscriptions.length}'),
                Text('恢复数: ${_restoredPurchases.length}'),
              ],
            ),
          ),
        ),
      ),
    );
  }
}
1
likes
0
points
0
downloads

Publisher

unverified uploader

Weekly Downloads

A StoreKit 2 helper plugin for Flutter

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on sk2helper

Packages that implement sk2helper