实现应用的商业化模式

前言

积分系统是 CleanMark AI 的核心商业模式。用户通过积分消费 AI 服务,可以通过以下方式获取积分:

  • 注册赠送:新用户注册赠送 5 积分
  • 观看广告:观看激励视频广告获得 1 积分
  • 充值购买:通过内购或支付宝/微信支付购买积分包

这篇文章我会带你实现完整的积分系统和广告集成。


一、积分系统设计

1.1 业务规则

操作 积分变化 说明
注册 +5 新用户注册赠送
图片去水印 -1 每次处理消耗 1 积分
视频去水印 -3 每次处理消耗 3 积分
观看广告 +1 每天最多 10 次
充值 +N 根据充值金额
请添加图片描述

1.2 数据模型

// lib/features/credits/models/credit_transaction.dart

/// 积分交易记录
class CreditTransaction {
  final String id;
  final String userId;
  final int amount;           // 积分变化量(正数为增加,负数为减少)
  final TransactionType type; // 交易类型
  final String? description;  // 描述
  final DateTime createdAt;

  const CreditTransaction({
    required this.id,
    required this.userId,
    required this.amount,
    required this.type,
    this.description,
    required this.createdAt,
  });

  factory CreditTransaction.fromJson(Map<String, dynamic> json) {
    return CreditTransaction(
      id: json['id'] as String,
      userId: json['userId'] as String,
      amount: json['amount'] as int,
      type: TransactionType.fromString(json['type'] as String),
      description: json['description'] as String?,
      createdAt: DateTime.parse(json['createdAt'] as String),
    );
  }
}

/// 交易类型
enum TransactionType {
  register,      // 注册赠送
  consume,       // 消费
  adReward,      // 广告奖励
  purchase;      // 充值购买

  static TransactionType fromString(String type) {
    switch (type.toUpperCase()) {
      case 'REGISTER':
        return TransactionType.register;
      case 'CONSUME':
        return TransactionType.consume;
      case 'AD_REWARD':
        return TransactionType.adReward;
      case 'PURCHASE':
        return TransactionType.purchase;
      default:
        return TransactionType.consume;
    }
  }

  String get displayName {
    switch (this) {
      case TransactionType.register:
        return '注册赠送';
      case TransactionType.consume:
        return '消费';
      case TransactionType.adReward:
        return '广告奖励';
      case TransactionType.purchase:
        return '充值购买';
    }
  }
}

1.3 积分充值套餐

// lib/features/credits/models/credit_package.dart

/// 积分充值套餐
class CreditPackage {
  final String id;
  final int credits;      // 积分数量
  final double price;     // 价格(元)
  final String? bonus;    // 赠送说明
  final bool isPopular;   // 是否热门

  const CreditPackage({
    required this.id,
    required this.credits,
    required this.price,
    this.bonus,
    this.isPopular = false,
  });

  /// 单价(元/积分)
  double get unitPrice => price / credits;
}

/// 预定义套餐
const List<CreditPackage> creditPackages = [
  CreditPackage(
    id: 'package_10',
    credits: 10,
    price: 6.0,
  ),
  CreditPackage(
    id: 'package_50',
    credits: 50,
    price: 25.0,
    bonus: '赠送 5 积分',
    isPopular: true,
  ),
  CreditPackage(
    id: 'package_100',
    credits: 100,
    price: 45.0,
    bonus: '赠送 15 积分',
  ),
  CreditPackage(
    id: 'package_500',
    credits: 500,
    price: 200.0,
    bonus: '赠送 100 积分',
  ),
];

二、积分展示组件

请添加图片描述

2.1 积分余额卡片

// lib/features/credits/presentation/widgets/credit_balance_card.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../app/app_colors.dart';
import '../../../auth/presentation/providers/user_provider.dart';

class CreditBalanceCard extends ConsumerWidget {
  const CreditBalanceCard({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final user = ref.watch(userProvider);

    return Container(
      padding: const EdgeInsets.all(20),
      decoration: BoxDecoration(
        gradient: const LinearGradient(
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
          colors: [
            AppColors.purple,
            AppColors.blue,
          ],
        ),
        borderRadius: BorderRadius.circular(16),
        boxShadow: [
          BoxShadow(
            color: AppColors.purple.withOpacity(0.3),
            blurRadius: 20,
            offset: const Offset(0, 10),
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              const Text(
                '我的积分',
                style: TextStyle(
                  color: Colors.white70,
                  fontSize: 14,
                ),
              ),
              IconButton(
                icon: const Icon(Icons.history, color: Colors.white70),
                onPressed: () {
                  Navigator.pushNamed(context, '/credit-history');
                },
              ),
            ],
          ),
          const SizedBox(height: 8),
          Row(
            crossAxisAlignment: CrossAxisAlignment.end,
            children: [
              Text(
                '${user?.credits ?? 0}',
                style: const TextStyle(
                  color: Colors.white,
                  fontSize: 48,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const SizedBox(width: 8),
              const Padding(
                padding: EdgeInsets.only(bottom: 12),
                child: Text(
                  '积分',
                  style: TextStyle(
                    color: Colors.white70,
                    fontSize: 16,
                  ),
                ),
              ),
            ],
          ),
          const SizedBox(height: 16),
          Row(
            children: [
              Expanded(
                child: _buildActionButton(
                  icon: Icons.add_circle_outline,
                  label: '充值',
                  onTap: () {
                    Navigator.pushNamed(context, '/credit-purchase');
                  },
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: _buildActionButton(
                  icon: Icons.play_circle_outline,
                  label: '看广告赚积分',
                  onTap: () {
                    // 显示广告
                  },
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }

  Widget _buildActionButton({
    required IconData icon,
    required String label,
    required VoidCallback onTap,
  }) {
    return InkWell(
      onTap: onTap,
      borderRadius: BorderRadius.circular(8),
      child: Container(
        padding: const EdgeInsets.symmetric(vertical: 12),
        decoration: BoxDecoration(
          color: Colors.white.withOpacity(0.2),
          borderRadius: BorderRadius.circular(8),
        ),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(icon, color: Colors.white, size: 20),
            const SizedBox(width: 8),
            Text(
              label,
              style: const TextStyle(
                color: Colors.white,
                fontSize: 14,
                fontWeight: FontWeight.w500,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

三、积分充值页面

3.1 充值页面 UI

// lib/features/credits/presentation/pages/credit_purchase_page.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../app/app_colors.dart';
import '../../../../shared/widgets/gradient_button.dart';
import '../../models/credit_package.dart';

class CreditPurchasePage extends ConsumerStatefulWidget {
  const CreditPurchasePage({super.key});

  
  ConsumerState<CreditPurchasePage> createState() => _CreditPurchasePageState();
}

class _CreditPurchasePageState extends ConsumerState<CreditPurchasePage> {
  CreditPackage? _selectedPackage;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('充值积分'),
      ),
      body: Column(
        children: [
          Expanded(
            child: ListView(
              padding: const EdgeInsets.all(16),
              children: [
                // 套餐列表
                ...creditPackages.map((package) => _buildPackageCard(package)),

                const SizedBox(height: 24),

                // 说明
                _buildDescription(),
              ],
            ),
          ),

          // 底部购买按钮
          _buildBottomBar(),
        ],
      ),
    );
  }

  Widget _buildPackageCard(CreditPackage package) {
    final isSelected = _selectedPackage?.id == package.id;

    return GestureDetector(
      onTap: () {
        setState(() {
          _selectedPackage = package;
        });
      },
      child: Container(
        margin: const EdgeInsets.only(bottom: 12),
        padding: const EdgeInsets.all(16),
        decoration: BoxDecoration(
          color: AppColors.cardDark,
          borderRadius: BorderRadius.circular(12),
          border: Border.all(
            color: isSelected ? AppColors.purple : AppColors.gray700,
            width: 2,
          ),
        ),
        child: Row(
          children: [
            // 选择指示器
            Container(
              width: 24,
              height: 24,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                border: Border.all(
                  color: isSelected ? AppColors.purple : AppColors.gray500,
                  width: 2,
                ),
                color: isSelected ? AppColors.purple : Colors.transparent,
              ),
              child: isSelected
                  ? const Icon(Icons.check, size: 16, color: Colors.white)
                  : null,
            ),

            const SizedBox(width: 16),

            // 积分信息
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    children: [
                      Text(
                        '${package.credits} 积分',
                        style: const TextStyle(
                          color: AppColors.white,
                          fontSize: 18,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      if (package.isPopular) ...[
                        const SizedBox(width: 8),
                        Container(
                          padding: const EdgeInsets.symmetric(
                            horizontal: 8,
                            vertical: 2,
                          ),
                          decoration: BoxDecoration(
                            color: AppColors.orange,
                            borderRadius: BorderRadius.circular(4),
                          ),
                          child: const Text(
                            '热门',
                            style: TextStyle(
                              color: Colors.white,
                              fontSize: 12,
                            ),
                          ),
                        ),
                      ],
                    ],
                  ),
                  if (package.bonus != null) ...[
                    const SizedBox(height: 4),
                    Text(
                      package.bonus!,
                      style: const TextStyle(
                        color: AppColors.orange,
                        fontSize: 12,
                      ),
                    ),
                  ],
                ],
              ),
            ),

            // 价格
            Text(
              ${package.price.toStringAsFixed(2)}',
              style: const TextStyle(
                color: AppColors.white,
                fontSize: 20,
                fontWeight: FontWeight.bold,
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildDescription() {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: AppColors.blue.withOpacity(0.1),
        borderRadius: BorderRadius.circular(12),
        border: Border.all(
          color: AppColors.blue.withOpacity(0.3),
        ),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              Icon(Icons.info_outline, color: AppColors.blue, size: 20),
              const SizedBox(width: 8),
              const Text(
                '积分说明',
                style: TextStyle(
                  color: AppColors.blue,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ],
          ),
          const SizedBox(height: 8),
          Text(
            '• 图片去水印消耗 1 积分\n'
            '• 视频去水印消耗 3 积分\n'
            '• 积分永久有效,不会过期\n'
            '• 支持支付宝、微信支付',
            style: TextStyle(
              color: AppColors.gray400,
              fontSize: 14,
              height: 1.5,
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildBottomBar() {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: AppColors.cardDark,
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.2),
            blurRadius: 10,
            offset: const Offset(0, -5),
          ),
        ],
      ),
      child: SafeArea(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            if (_selectedPackage != null)
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  const Text(
                    '支付金额',
                    style: TextStyle(
                      color: AppColors.gray400,
                      fontSize: 14,
                    ),
                  ),
                  Text(
                    ${_selectedPackage!.price.toStringAsFixed(2)}',
                    style: const TextStyle(
                      color: AppColors.orange,
                      fontSize: 24,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ],
              ),
            const SizedBox(height: 12),
            GradientButton(
              text: _selectedPackage == null ? '请选择套餐' : '立即支付',
              onPressed: _selectedPackage == null ? null : _handlePurchase,
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _handlePurchase() async {
    if (_selectedPackage == null) return;

    // 显示支付方式选择
    final paymentMethod = await _showPaymentMethodDialog();
    if (paymentMethod == null) return;

    // 调用支付
    // TODO: 实现支付逻辑
  }

  Future<String?> _showPaymentMethodDialog() async {
    return showDialog<String>(
      context: context,
      builder: (context) => AlertDialog(
        backgroundColor: AppColors.cardDark,
        title: const Text(
          '选择支付方式',
          style: TextStyle(color: AppColors.white),
        ),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            _buildPaymentOption('支付宝', Icons.account_balance_wallet, 'alipay'),
            const SizedBox(height: 12),
            _buildPaymentOption('微信支付', Icons.wechat, 'wechat'),
          ],
        ),
      ),
    );
  }

  Widget _buildPaymentOption(String label, IconData icon, String method) {
    return InkWell(
      onTap: () => Navigator.pop(context, method),
      child: Container(
        padding: const EdgeInsets.all(16),
        decoration: BoxDecoration(
          color: AppColors.gray800,
          borderRadius: BorderRadius.circular(8),
        ),
        child: Row(
          children: [
            Icon(icon, color: AppColors.white),
            const SizedBox(width: 16),
            Text(
              label,
              style: const TextStyle(
                color: AppColors.white,
                fontSize: 16,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

四、广告集成

4.1 Google AdMob 集成

添加依赖:

# pubspec.yaml
dependencies:
  google_mobile_ads: ^5.0.0

配置广告 ID:

// lib/core/constants/ad_constants.dart

class AdConstants {
  // Android 广告 ID
  static const String androidRewardedAdId = 'ca-app-pub-xxxxx/xxxxx';

  // iOS 广告 ID
  static const String iosRewardedAdId = 'ca-app-pub-xxxxx/xxxxx';

  // 测试广告 ID
  static const String testRewardedAdId = 'ca-app-pub-3940256099942544/5224354917';

  /// 获取激励视频广告 ID
  static String get rewardedAdId {
    if (kDebugMode) {
      return testRewardedAdId;
    }
    return PlatformUtils.isAndroid ? androidRewardedAdId : iosRewardedAdId;
  }
}

4.2 广告服务封装

// lib/core/services/ad_service.dart

import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:flutter/foundation.dart';
import '../constants/ad_constants.dart';

class AdService {
  static final AdService _instance = AdService._internal();
  factory AdService() => _instance;
  AdService._internal();

  RewardedAd? _rewardedAd;
  bool _isAdLoading = false;
  bool _isAdReady = false;

  /// 初始化广告 SDK
  static Future<void> initialize() async {
    await MobileAds.instance.initialize();
  }

  /// 加载激励视频广告
  Future<void> loadRewardedAd() async {
    if (_isAdLoading || _isAdReady) return;

    _isAdLoading = true;

    await RewardedAd.load(
      adUnitId: AdConstants.rewardedAdId,
      request: const AdRequest(),
      rewardedAdLoadCallback: RewardedAdLoadCallback(
        onAdLoaded: (ad) {
          _rewardedAd = ad;
          _isAdReady = true;
          _isAdLoading = false;

          // 设置全屏内容回调
          _rewardedAd!.fullScreenContentCallback = FullScreenContentCallback(
            onAdDismissedFullScreenContent: (ad) {
              ad.dispose();
              _rewardedAd = null;
              _isAdReady = false;
              // 预加载下一个广告
              loadRewardedAd();
            },
            onAdFailedToShowFullScreenContent: (ad, error) {
              ad.dispose();
              _rewardedAd = null;
              _isAdReady = false;
            },
          );
        },
        onAdFailedToLoad: (error) {
          _isAdLoading = false;
          _isAdReady = false;
          if (kDebugMode) {
            print('激励视频广告加载失败: ${error.message}');
          }
        },
      ),
    );
  }

  /// 显示激励视频广告
  Future<bool> showRewardedAd() async {
    if (!_isAdReady || _rewardedAd == null) {
      return false;
    }

    bool rewarded = false;

    await _rewardedAd!.show(
      onUserEarnedReward: (ad, reward) {
        rewarded = true;
        if (kDebugMode) {
          print('用户获得奖励: ${reward.amount} ${reward.type}');
        }
      },
    );

    return rewarded;
  }

  /// 广告是否已准备好
  bool get isAdReady => _isAdReady;

  /// 释放资源
  void dispose() {
    _rewardedAd?.dispose();
    _rewardedAd = null;
    _isAdReady = false;
  }
}

4.3 观看广告赚积分

请添加图片描述

// lib/features/credits/presentation/widgets/watch_ad_button.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../app/app_colors.dart';
import '../../../../core/services/ad_service.dart';
import '../providers/credits_provider.dart';

class WatchAdButton extends ConsumerStatefulWidget {
  const WatchAdButton({super.key});

  
  ConsumerState<WatchAdButton> createState() => _WatchAdButtonState();
}

class _WatchAdButtonState extends ConsumerState<WatchAdButton> {
  final AdService _adService = AdService();
  bool _isLoading = false;

  
  void initState() {
    super.initState();
    // 预加载广告
    _adService.loadRewardedAd();
  }

  Future<void> _watchAd() async {
    if (_isLoading) return;

    setState(() {
      _isLoading = true;
    });

    try {
      // 检查今日观看次数
      final canWatch = await ref.read(creditsProvider.notifier).canWatchAd();

      if (!canWatch) {
        _showMessage('今日观看次数已达上限(10次)');
        return;
      }

      // 显示广告
      final rewarded = await _adService.showRewardedAd();

      if (rewarded) {
        // 发放奖励
        final result = await ref.read(creditsProvider.notifier).rewardAdCredit();

        result.fold(
          (failure) => _showMessage(failure.message),
          (credits) => _showMessage('恭喜获得 1 积分!当前积分:$credits'),
        );
      } else {
        _showMessage('广告未完整观看,未获得奖励');
      }
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  void _showMessage(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }

  
  Widget build(BuildContext context) {
    return ElevatedButton.icon(
      onPressed: _isLoading ? null : _watchAd,
      icon: _isLoading
          ? const SizedBox(
              width: 20,
              height: 20,
              child: CircularProgressIndicator(strokeWidth: 2),
            )
          : const Icon(Icons.play_circle_outline),
      label: Text(_isLoading ? '加载中...' : '看广告赚 1 积分'),
      style: ElevatedButton.styleFrom(
        backgroundColor: AppColors.orange,
        foregroundColor: Colors.white,
        padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
      ),
    );
  }
}

4.4 广告次数限制

// lib/features/credits/presentation/providers/credits_provider.dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:dartz/dartz.dart';
import '../../../../core/errors/failures.dart';
import '../../domain/usecases/reward_ad_credit_usecase.dart';
import '../../domain/usecases/check_ad_limit_usecase.dart';

class CreditsNotifier extends StateNotifier<int> {
  final RewardAdCreditUseCase _rewardAdCreditUseCase;
  final CheckAdLimitUseCase _checkAdLimitUseCase;

  CreditsNotifier(
    this._rewardAdCreditUseCase,
    this._checkAdLimitUseCase,
  ) : super(0);

  /// 检查是否可以观看广告
  Future<bool> canWatchAd() async {
    final result = await _checkAdLimitUseCase();
    return result.fold(
      (failure) => false,
      (canWatch) => canWatch,
    );
  }

  /// 发放广告奖励
  Future<Either<Failure, int>> rewardAdCredit() async {
    final result = await _rewardAdCreditUseCase();

    return result.fold(
      (failure) => Left(failure),
      (newCredits) {
        state = newCredits;
        return Right(newCredits);
      },
    );
  }
}

final creditsProvider = StateNotifierProvider<CreditsNotifier, int>(
  (ref) {
    final rewardAdCreditUseCase = ref.watch(rewardAdCreditUseCaseProvider);
    final checkAdLimitUseCase = ref.watch(checkAdLimitUseCaseProvider);
    return CreditsNotifier(rewardAdCreditUseCase, checkAdLimitUseCase);
  },
);

五、积分历史记录

5.1 历史记录页面

// lib/features/credits/presentation/pages/credit_history_page.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../app/app_colors.dart';
import '../../models/credit_transaction.dart';
import '../providers/credit_history_provider.dart';

class CreditHistoryPage extends ConsumerWidget {
  const CreditHistoryPage({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final historyState = ref.watch(creditHistoryProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('积分明细'),
      ),
      body: historyState.when(
        data: (transactions) {
          if (transactions.isEmpty) {
            return _buildEmptyState();
          }
          return ListView.builder(
            padding: const EdgeInsets.all(16),
            itemCount: transactions.length,
            itemBuilder: (context, index) {
              return _buildTransactionItem(transactions[index]);
            },
          );
        },
        loading: () => const Center(child: CircularProgressIndicator()),
        error: (error, stack) => Center(
          child: Text(
            '加载失败: $error',
            style: const TextStyle(color: AppColors.red),
          ),
        ),
      ),
    );
  }

  Widget _buildEmptyState() {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(
            Icons.receipt_long_outlined,
            size: 64,
            color: AppColors.gray500,
          ),
          const SizedBox(height: 16),
          Text(
            '暂无积分记录',
            style: TextStyle(
              color: AppColors.gray400,
              fontSize: 16,
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildTransactionItem(CreditTransaction transaction) {
    final isIncome = transaction.amount > 0;
    final color = isIncome ? AppColors.green : AppColors.red;
    final sign = isIncome ? '+' : '';

    return Container(
      margin: const EdgeInsets.only(bottom: 12),
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: AppColors.cardDark,
        borderRadius: BorderRadius.circular(12),
      ),
      child: Row(
        children: [
          // 图标
          Container(
            width: 40,
            height: 40,
            decoration: BoxDecoration(
              color: color.withOpacity(0.1),
              borderRadius: BorderRadius.circular(8),
            ),
            child: Icon(
              _getIconForType(transaction.type),
              color: color,
              size: 20,
            ),
          ),

          const SizedBox(width: 12),

          // 信息
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  transaction.type.displayName,
                  style: const TextStyle(
                    color: AppColors.white,
                    fontSize: 16,
                    fontWeight: FontWeight.w500,
                  ),
                ),
                const SizedBox(height: 4),
                Text(
                  _formatDateTime(transaction.createdAt),
                  style: const TextStyle(
                    color: AppColors.gray400,
                    fontSize: 12,
                  ),
                ),
              ],
            ),
          ),

          // 积分变化
          Text(
            '$sign${transaction.amount}',
            style: TextStyle(
              color: color,
              fontSize: 18,
              fontWeight: FontWeight.bold,
            ),
          ),
        ],
      ),
    );
  }

  IconData _getIconForType(TransactionType type) {
    switch (type) {
      case TransactionType.register:
        return Icons.card_giftcard;
      case TransactionType.consume:
        return Icons.remove_circle_outline;
      case TransactionType.adReward:
        return Icons.play_circle_outline;
      case TransactionType.purchase:
        return Icons.add_circle_outline;
    }
  }

  String _formatDateTime(DateTime dateTime) {
    return '${dateTime.month}-${dateTime.day} ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}';
  }
}

本篇完整小结

这篇文章我们完成了:

  1. ✅ 积分系统业务规则设计
  2. ✅ 数据模型定义(CreditTransaction、CreditPackage)
  3. ✅ 积分余额卡片组件
  4. ✅ 积分充值页面(套餐选择 + 支付)
  5. ✅ Google AdMob 集成
  6. ✅ 激励视频广告实现
  7. ✅ 观看广告赚积分功能
  8. ✅ 广告次数限制(每天 10 次)
  9. ✅ 积分历史记录页面

关键要点:

  • 积分是应用的核心商业模式,贯穿整个用户流程
  • 使用 Google AdMob 提供免费获取积分的途径
  • 充值套餐设计要有梯度,引导用户购买更大套餐
  • 广告次数限制防止滥用,保护广告收益
  • 积分历史记录提供透明度,增强用户信任

广告集成最佳实践:

  1. 预加载广告:在用户点击前提前加载,减少等待时间
  2. 测试广告 ID:开发阶段使用测试 ID,避免账号被封
  3. 错误处理:广告加载失败时提供友好提示
  4. 奖励验证:服务器端验证广告观看,防止作弊
  5. 用户体验:不要过度打扰用户,合理控制广告频率

HarmonyOS 适配注意事项:

  • Google AdMob 不支持 HarmonyOS,需要集成华为广告服务(Petal Ads)
  • 使用条件编译或平台判断,为不同平台提供不同广告实现
  • 封装统一的广告接口,隔离平台差异
// 平台适配示例
abstract class AdService {
  Future<void> loadRewardedAd();
  Future<bool> showRewardedAd();
}

class GoogleAdService implements AdService {
  // Google AdMob 实现
}

class HuaweiAdService implements AdService {
  // 华为广告实现
}

AdService createAdService() {
  if (PlatformUtils.isHarmonyOS) {
    return HuaweiAdService();
  }
  return GoogleAdService();
}

思考题

  1. 如何防止用户通过修改系统时间绕过广告次数限制?
  2. 如果用户观看广告后应用崩溃,如何确保积分正确发放?
  3. 如何设计积分套餐定价策略,最大化收益?

下一篇预告:第12篇 - 历史记录与任务管理

Logo

一站式 AI 云服务平台

更多推荐