Files
kitchen/docs/superpowers/plans/2026-04-18-farm-game-implementation.md
Developer e347b3ca73 release
2026-04-18 05:20:42 +08:00

86 KiB
Raw Blame History

小妈菜园 Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 在工具中心集成"小妈菜园"小游戏,实现完整的本地农场模拟经营体验

Architecture: 本地优先架构复用项目现有服务HiveService、AnimationServiceGetX 管理状态share_plus 实现分享功能

Tech Stack:

  • 已有库: get (状态管理), hive_ce (本地存储), share_plus (分享), animations (动画), cupertino_icons (iOS图标), path_provider, uuid, logger, intl, badges, fl_chart
  • 需新增: build_runner (^2.4.13), hive_ce_generator (^1.8.0) [dev_dependencies]

安装新依赖命令:

flutter pub add --dev build_runner
flutter pub add --dev hive_ce_generator

文件结构总览

新增文件

lib/src/
├── models/farm/
│   ├── farm_player.dart           # 玩家数据模型
│   ├── farm_player.g.dart         # Hive 适配器(自动生成)
│   ├── farm_land.dart             # 土地数据模型
│   ├── farm_land.g.dart           # Hive 适配器(自动生成)
│   ├── inventory_item.dart        # 背包物品模型
│   ├── inventory_item.g.dart      # Hive 适配器(自动生成)
│   ├── crop_config.dart           # 作物配置(普通类,无需 Hive
│   ├── crop_registry.dart         # 作物注册表(静态配置)
│   ├── achievement_config.dart    # 成就配置(普通类,无需 Hive
│   └── achievement_registry.dart  # 成就注册表(静态配置)
│
├── controllers/farm/
│   ├── farm_game_controller.dart      # 核心游戏控制器
│   ├── farm_shop_controller.dart      # 商店控制器
│   ├── farm_inventory_controller.dart # 背包控制器
│   └── farm_achievement_controller.dart # 成就控制器
│
├── pages/tools/farm/
│   ├── farm_game_page.dart            # 主游戏页面
│   ├── farm_shop_page.dart            # 商店页面
│   ├── farm_inventory_page.dart       # 背包页面
│   ├── farm_achievement_page.dart     # 成就页面
│   └── widgets/
│       ├── land_widget.dart           # 土地 Widget
│       └── farm_share_painter.dart    # 分享图片绘制器
│
├── config/
│   └── farm_config.dart            # 游戏全局配置常量
│
└── utils/
    └── farm_share_util.dart        # 分享功能工具类

docs/superpowers/
└── specs/
    └── 2026-04-18-farm-game-design.md  # 设计文档(已创建)

修改文件

lib/src/services/data/hive_service.dart   # 扩展农场数据支持
lib/src/config/app_routes.dart            # 新增路由定义
lib/src/models/tool_item_model.dart       # 新增工具注册项
lib/src/app_binding.dart                  # 注册控制器
docs/design/IOS26_UI_DESIGN.md            # (可选)更新设计参考
CHANGELOG.md                              # 更新变更日志

开发任务

Task 1: 数据模型层 - 基础模型定义

目标: 创建所有 Hive 数据模型,确保数据可持久化

Files:

  • Create: lib/src/models/farm/farm_player.dart
  • Create: lib/src/models/farm/farm_land.dart
  • Create: lib/src/models/farm/inventory_item.dart
  • Create: lib/src/models/farm/crop_config.dart
  • Create: lib/src/models/farm/achievement_config.dart

Step 1: 创建玩家模型

Create: lib/src/models/farm/farm_player.dart

/*
 * 文件: farm_player.dart
 * 名称: 农场玩家模型
 * 作用: 存储玩家等级、经验、金币、解锁状态等数据
 * 更新: 2026-04-18 初始创建
 */

import 'package:hive_ce/hive.dart';

part 'farm_player.g.dart';

@HiveType(typeId: 100)
class FarmPlayer extends HiveObject {
  @HiveField(0)
  String playerId;

  @HiveField(1)
  String playerName;

  @HiveField(2)
  int level;

  @HiveField(3)
  int experience;

  @HiveField(4)
  int gold;

  @HiveField(5)
  int diamond;

  @HiveField(6)
  DateTime createTime;

  @HiveField(7)
  int totalHarvest;

  @HiveField(8)
  int totalPlant;

  @HiveField(9)
  List<String> unlockedCrops;

  @HiveField(10)
  List<String> achievements;

  FarmPlayer({
    required this.playerId,
    required this.playerName,
    this.level = 1,
    this.experience = 0,
    this.gold = 100,
    this.diamond = 10,
    required this.createTime,
    this.totalHarvest = 0,
    this.totalPlant = 0,
    List<String>? unlockedCrops,
    List<String>? achievements,
  })  : unlockedCrops = unlockedCrops ?? [],
        achievements = achievements ?? [];

  int get expToNextLevel => level * 100;

  double get expProgress => experience / expToNextLevel;

  factory FarmPlayer.createDefault(String deviceId) {
    return FarmPlayer(
      playerId: deviceId,
      playerName: '小厨神',
      createTime: DateTime.now(),
      unlockedCrops: ['radish', 'potato', 'cabbage'],
    );
  }
}

Step 2: 创建土地模型

Create: lib/src/models/farm/farm_land.dart

/*
 * 文件: farm_land.dart
 * 名称: 农场土地模型
 * 作用: 存储每块土地的种植状态、作物生长信息
 * 更新: 2026-04-18 初始创建
 */

import 'package:hive_ce/hive.dart';

part 'farm_land.g.dart';

@HiveType(typeId: 101)
class FarmLand extends HiveObject {
  @HiveField(0)
  int landId;

  @HiveField(1)
  bool isUnlocked;

  @HiveField(2)
  String? cropId;

  @HiveField(3)
  DateTime? plantTime;

  @HiveField(4)
  int growthStage;

  @HiveField(5)
  bool needWater;

  @HiveField(6)
  bool needFertilizer;

  @HiveField(7)
  DateTime? lastWaterTime;

  @HiveField(8)
  bool isWithered;

  @HiveField(9)
  bool isReady;

  FarmLand({
    required this.landId,
    this.isUnlocked = false,
    this.cropId,
    this.plantTime,
    this.growthStage = 0,
    this.needWater = false,
    this.needFertilizer = false,
    this.lastWaterTime,
    this.isWithered = false,
    this.isReady = false,
  });

  double get growthProgress {
    if (plantTime == null || cropId == null) return 0.0;
    final crop = CropRegistry.getById(cropId!);
    if (crop == null) return 0.0;

    final elapsed = DateTime.now().difference(plantTime!).inMinutes;
    return (elapsed / crop.growthTime).clamp(0.0, 1.0);
  }

  String get currentDisplayEmoji {
    if (cropId == null) return '🟫';
    final crop = CropRegistry.getById(cropId!);
    if (crop == null) return '🟫';

    if (isWithered) return '🥀';
    if (isReady) return crop.stages.last.emoji;

    final index = growthStage.clamp(0, crop.stages.length - 1);
    return crop.stages[index].emoji;
  }

  factory FarmLand.initial(int id, {bool unlocked = false}) {
    return FarmLand(landId: id, isUnlocked: unlocked);
  }
}

Step 3: 创建背包物品模型

Create: lib/src/models/farm/inventory_item.dart

/*
 * 文件: inventory_item.dart
 * 名称: 背包物品模型
 * 作用: 存储种子、果实、道具等背包物品
 * 更新: 2026-04-18 初始创建
 */

import 'package:hive_ce/hive.dart';

part 'inventory_item.g.dart';

@HiveType(typeId: 102)
class InventoryItem extends HiveObject {
  @HiveField(0)
  String itemId;

  @HiveField(1)
  String itemName;

  @HiveField(2)
  String itemType; // seed, fruit, fertilizer, tool

  @HiveField(3)
  int quantity;

  @HiveField(4)
  String emoji;

  @HiveField(5)
  int price;

  InventoryItem({
    required this.itemId,
    required this.itemName,
    required this.itemType,
    required this.quantity,
    required this.emoji,
    this.price = 0,
  });

  factory InventoryItem.seed({
    required String cropId,
    required String name,
    required String emoji,
    required int price,
    int quantity = 1,
  }) {
    return InventoryItem(
      itemId: '${cropId}_seed',
      itemName: '$name种子',
      itemType: 'seed',
      quantity: quantity,
      emoji: emoji,
      price: price,
    );
  }

  bool get isSeed => itemType == 'seed';
  bool get isFruit => itemType == 'fruit';
}

Step 4: 创建作物配置模型

Create: lib/src/models/farm/crop_config.dart

/*
 * 文件: crop_config.dart
 * 名称: 作物配置模型
 * 作用: 定义作物的生长时间、价格、阶段等静态配置
 * 更新: 2026-04-18 初始创建
 */

class StageInfo {
  final int stage;
  final String emoji;
  final double durationPercent;
  final bool needWater;

  const StageInfo({
    required this.stage,
    required this.emoji,
    required this.durationPercent,
    this.needWater = false,
  });
}

class CropConfig {
  final String id;
  final String name;
  final String emoji;
  final int growthTime; // 分钟
  final int seedPrice;
  final int harvestPrice;
  final int harvestExp;
  final int unlockLevel;
  final List<StageInfo> stages;

  const CropConfig({
    required this.id,
    required this.name,
    required this.emoji,
    required this.growthTime,
    required this.seedPrice,
    required this.harvestPrice,
    required this.harvestExp,
    required this.unlockLevel,
    required this.stages,
  });
}

Step 5: 创建成就配置模型

Create: lib/src/models/farm/achievement_config.dart

/*
 * 文件: achievement_config.dart
 * 名称: 成就配置模型
 * 作用: 定义成就条件和奖励
 * 更新: 2026-04-18 初始创建
 */

class AchievementConfig {
  final String id;
  final String name;
  final String description;
  final String emoji;
  final int rewardGold;
  final int rewardExp;
  final int rewardDiamond;
  final String conditionType;
  final int conditionValue;

  const AchievementConfig({
    required this.id,
    required this.name,
    required this.description,
    required this.emoji,
    this.rewardGold = 0,
    this.rewardExp = 0,
    this.rewardDiamond = 0,
    required this.conditionType,
    required this.conditionValue,
  });
}

Task 2: 配置注册表 - 静态数据注册

目标: 创建作物和成就的注册表,提供便捷的查询接口

Files:

  • Create: lib/src/models/farm/crop_registry.dart
  • Create: lib/src/models/farm/achievement_registry.dart
  • Create: lib/src/config/farm_config.dart

Step 1: 创建作物注册表

Create: lib/src/models/farm/crop_registry.dart

/*
 * 文件: crop_registry.dart
 * 名称: 作物注册表
 * 作用: 注册所有作物配置,提供查询接口
 * 更新: 2026-04-18 初始创建
 */

import 'crop_config.dart';

class CropRegistry {
  CropRegistry._();

  static const Map<String, CropConfig> _crops = {
    'radish': CropConfig(
      id: 'radish',
      name: '萝卜',
      emoji: '🥕',
      growthTime: 30,
      seedPrice: 10,
      harvestPrice: 25,
      harvestExp: 15,
      unlockLevel: 1,
      stages: [
        StageInfo(stage: 0, emoji: '🌱', durationPercent: 0.05, needWater: true),
        StageInfo(stage: 1, emoji: '🌿', durationPercent: 0.25, needWater: true),
        StageInfo(stage: 2, emoji: '☘️', durationPercent: 0.55, needWater: true),
        StageInfo(stage: 3, emoji: '🥕', durationPercent: 0.15, needWater: false),
      ],
    ),
    'potato': CropConfig(
      id: 'potato',
      name: '土豆',
      emoji: '🥔',
      growthTime: 45,
      seedPrice: 15,
      harvestPrice: 40,
      harvestExp: 25,
      unlockLevel: 1,
      stages: [
        StageInfo(stage: 0, emoji: '🌱', durationPercent: 0.05, needWater: true),
        StageInfo(stage: 1, emoji: '🌿', durationPercent: 0.25, needWater: true),
        StageInfo(stage: 2, emoji: '🪴', durationPercent: 0.55, needWater: true),
        StageInfo(stage: 3, emoji: '🥔', durationPercent: 0.15, needWater: false),
      ],
    ),
    'cabbage': CropConfig(
      id: 'cabbage',
      name: '卷心菜',
      emoji: '🥬',
      growthTime: 40,
      seedPrice: 12,
      harvestPrice: 35,
      harvestExp: 20,
      unlockLevel: 1,
      stages: [
        StageInfo(stage: 0, emoji: '🌱', durationPercent: 0.05, needWater: true),
        StageInfo(stage: 1, emoji: '🌿', durationPercent: 0.25, needWater: true),
        StageInfo(stage: 2, emoji: '☘️', durationPercent: 0.55, needWater: true),
        StageInfo(stage: 3, emoji: '🥬', durationPercent: 0.15, needWater: false),
      ],
    ),
    'tomato': CropConfig(
      id: 'tomato',
      name: '西红柿',
      emoji: '🍅',
      growthTime: 60,
      seedPrice: 25,
      harvestPrice: 60,
      harvestExp: 35,
      unlockLevel: 2,
      stages: [
        StageInfo(stage: 0, emoji: '🌱', durationPercent: 0.05, needWater: true),
        StageInfo(stage: 1, emoji: '🌿', durationPercent: 0.25, needWater: true),
        StageInfo(stage: 2, emoji: '🪴', durationPercent: 0.55, needWater: true),
        StageInfo(stage: 3, emoji: '🍅', durationPercent: 0.15, needWater: false),
      ],
    ),
    'carrot': CropConfig(
      id: 'carrot',
      name: '胡萝卜',
      emoji: '🥕',
      growthTime: 50,
      seedPrice: 20,
      harvestPrice: 50,
      harvestExp: 30,
      unlockLevel: 2,
      stages: [
        StageInfo(stage: 0, emoji: '🌱', durationPercent: 0.05, needWater: true),
        StageInfo(stage: 1, emoji: '🌿', durationPercent: 0.25, needWater: true),
        StageInfo(stage: 2, emoji: '☘️', durationPercent: 0.55, needWater: true),
        StageInfo(stage: 3, emoji: '🥕', durationPercent: 0.15, needWater: false),
      ],
    ),
    'corn': CropConfig(
      id: 'corn',
      name: '玉米',
      emoji: '🌽',
      growthTime: 90,
      seedPrice: 40,
      harvestPrice: 95,
      harvestExp: 55,
      unlockLevel: 3,
      stages: [
        StageInfo(stage: 0, emoji: '🌱', durationPercent: 0.05, needWater: true),
        StageInfo(stage: 1, emoji: '🌿', durationPercent: 0.25, needWater: true),
        StageInfo(stage: 2, emoji: '🪴', durationPercent: 0.55, needWater: true),
        StageInfo(stage: 3, emoji: '🌽', durationPercent: 0.15, needWater: false),
      ],
    ),
    'pepper': CropConfig(
      id: 'pepper',
      name: '辣椒',
      emoji: '🌶️',
      growthTime: 70,
      seedPrice: 30,
      harvestPrice: 75,
      harvestExp: 45,
      unlockLevel: 3,
      stages: [
        StageInfo(stage: 0, emoji: '🌱', durationPercent: 0.05, needWater: true),
        StageInfo(stage: 1, emoji: '🌿', durationPercent: 0.25, needWater: true),
        StageInfo(stage: 2, emoji: '🪴', durationPercent: 0.55, needWater: true),
        StageInfo(stage: 3, emoji: '🌶️', durationPercent: 0.15, needWater: false),
      ],
    ),
    'eggplant': CropConfig(
      id: 'eggplant',
      name: '茄子',
      emoji: '🍆',
      growthTime: 80,
      seedPrice: 35,
      harvestPrice: 85,
      harvestExp: 50,
      unlockLevel: 4,
      stages: [
        StageInfo(stage: 0, emoji: '🌱', durationPercent: 0.05, needWater: true),
        StageInfo(stage: 1, emoji: '🌿', durationPercent: 0.25, needWater: true),
        StageInfo(stage: 2, emoji: '🪴', durationPercent: 0.55, needWater: true),
        StageInfo(stage: 3, emoji: '🍆', durationPercent: 0.15, needWater: false),
      ],
    ),
    'strawberry': CropConfig(
      id: 'strawberry',
      name: '草莓',
      emoji: '🍓',
      growthTime: 120,
      seedPrice: 60,
      harvestPrice: 140,
      harvestExp: 80,
      unlockLevel: 5,
      stages: [
        StageInfo(stage: 0, emoji: '🌱', durationPercent: 0.05, needWater: true),
        StageInfo(stage: 1, emoji: '🌿', durationPercent: 0.25, needWater: true),
        StageInfo(stage: 2, emoji: '🌸', durationPercent: 0.55, needWater: true),
        StageInfo(stage: 3, emoji: '🍓', durationPercent: 0.15, needWater: false),
      ],
    ),
    'pumpkin': CropConfig(
      id: 'pumpkin',
      name: '南瓜',
      emoji: '🎃',
      growthTime: 150,
      seedPrice: 80,
      harvestPrice: 180,
      harvestExp: 100,
      unlockLevel: 6,
      stages: [
        StageInfo(stage: 0, emoji: '🌱', durationPercent: 0.05, needWater: true),
        StageInfo(stage: 1, emoji: '🌿', durationPercent: 0.25, needWater: true),
        StageInfo(stage: 2, emoji: '🪴', durationPercent: 0.55, needWater: true),
        StageInfo(stage: 3, emoji: '🎃', durationPercent: 0.15, needWater: false),
      ],
    ),
    'watermelon': CropConfig(
      id: 'watermelon',
      name: '西瓜',
      emoji: '🍉',
      growthTime: 180,
      seedPrice: 100,
      harvestPrice: 220,
      harvestExp: 120,
      unlockLevel: 8,
      stages: [
        StageInfo(stage: 0, emoji: '🌱', durationPercent: 0.05, needWater: true),
        StageInfo(stage: 1, emoji: '🌿', durationPercent: 0.25, needWater: true),
        StageInfo(stage: 2, emoji: '🪴', durationPercent: 0.55, needWater: true),
        StageInfo(stage: 3, emoji: '🍉', durationPercent: 0.15, needWater: false),
      ],
    ),
    'grape': CropConfig(
      id: 'grape',
      name: '葡萄',
      emoji: '🍇',
      growthTime: 200,
      seedPrice: 120,
      harvestPrice: 260,
      harvestExp: 140,
      unlockLevel: 10,
      stages: [
        StageInfo(stage: 0, emoji: '🌱', durationPercent: 0.05, needWater: true),
        StageInfo(stage: 1, emoji: '🌿', durationPercent: 0.25, needWater: true),
        StageInfo(stage: 2, emoji: '🌸', durationPercent: 0.55, needWater: true),
        StageInfo(stage: 3, emoji: '🍇', durationPercent: 0.15, needWater: false),
      ],
    ),
  };

  static CropConfig? getById(String id) => _crops[id];

  static List<CropConfig> getAll() => _crops.values.toList();

  static List<CropConfig> getUnlockedCrops(int level) {
    return _crops.values.where((c) => c.unlockLevel <= level).toList();
  }

  static List<CropConfig> getAvailableForLevel(int level) {
    return _crops.values
        .where((c) => c.unlockLevel == level)
        .toList();
  }
}

Step 2: 创建成就注册表

Create: lib/src/models/farm/achievement_registry.dart

/*
 * 文件: achievement_registry.dart
 * 名称: 成就注册表
 * 作用: 注册所有成就配置,提供查询和验证接口
 * 更新: 2026-04-18 初始创建
 */

import 'achievement_config.dart';

class AchievementRegistry {
  AchievementRegistry._();

  static const Map<String, AchievementConfig> _achievements = {
    'first_harvest': AchievementConfig(
      id: 'first_harvest',
      name: '初次收获',
      description: '完成第一次收获',
      emoji: '🌾',
      rewardGold: 50,
      rewardExp: 20,
      conditionType: 'totalHarvest',
      conditionValue: 1,
    ),
    'harvest_10': AchievementConfig(
      id: 'harvest_10',
      name: '小有成就',
      description: '收获 10 次',
      emoji: '🏅',
      rewardGold: 100,
      rewardExp: 50,
      conditionType: 'totalHarvest',
      conditionValue: 10,
    ),
    'harvest_50': AchievementConfig(
      id: 'harvest_50',
      name: '丰收达人',
      description: '收获 50 次',
      emoji: '🏆',
      rewardGold: 300,
      rewardExp: 100,
      rewardDiamond: 10,
      conditionType: 'totalHarvest',
      conditionValue: 50,
    ),
    'level_5': AchievementConfig(
      id: 'level_5',
      name: '初出茅庐',
      description: '达到 5 级',
      emoji: '⭐',
      rewardGold: 200,
      rewardExp: 0,
      conditionType: 'level',
      conditionValue: 5,
    ),
    'level_10': AchievementConfig(
      id: 'level_10',
      name: '农场老手',
      description: '达到 10 级',
      emoji: '🌟',
      rewardGold: 500,
      rewardExp: 0,
      rewardDiamond: 20,
      conditionType: 'level',
      conditionValue: 10,
    ),
    'unlock_all': AchievementConfig(
      id: 'unlock_all',
      name: '丰收大师',
      description: '解锁所有作物',
      emoji: '👑',
      rewardGold: 1000,
      rewardExp: 0,
      rewardDiamond: 50,
      conditionType: 'unlockedCrops',
      conditionValue: 12,
    ),
  };

  static AchievementConfig? getById(String id) => _achievements[id];

  static Map<String, AchievementConfig> getAll() => _achievements;

  static List<AchievementConfig> checkNewAchievements(
    String playerLevel,
    int playerValue,
    List<String> completedIds,
  ) {
    return _achievements.values
        .where((a) =>
            !completedIds.contains(a.id) &&
            a.conditionType == playerLevel &&
            playerValue >= a.conditionValue)
        .toList();
  }
}

Step 3: 创建游戏配置文件

Create: lib/src/config/farm_config.dart

/*
 * 文件: farm_config.dart
 * 名称: 农场游戏配置
 * 作用: 定义游戏全局常量配置
 * 更新: 2026-04-18 初始创建
 */

class FarmConfig {
  FarmConfig._();

  /// 土地总数
  static const int totalLands = 12;

  /// 初始解锁土地数
  static const int initialUnlockedLands = 6;

  /// 解锁新土地所需金币
  static const int unlockLandCost = 200;

  /// 浇水加速比例(%
  static const double waterSpeedBoost = 0.2;

  /// 枯萎时间(分钟)
  static const int witherTimeMinutes = 120;

  /// 成熟后过期时间(分钟)
  static const int matureExpireMinutes = 1440; // 24 小时

  /// 初始金币
  static const int initialGold = 100;

  /// 初始钻石
  static const int initialDiamond = 10;

  /// 初始种子数量
  static const int initialSeedQuantity = 5;

  /// 分享图片尺寸
  static const double shareImageWidth = 800;
  static const double shareImageHeight = 1000;

  /// 调试模式(发布时设为 false
  static const bool debugMode = true;
}

Task 3: 数据服务层 - 扩展现有 HiveService

目标: 扩展现有的 HiveService添加农场游戏的 Box 支持

Files:

  • Modify: lib/src/services/data/hive_service.dart (扩展 Box 和注册适配器)

Step 1: 在 HiveService 中添加农场游戏 Box

Modify: lib/src/services/data/hive_service.dart

重要: 不需要创建新的 FarmDataService,直接扩展现有的 HiveService 类。

在 HiveService 类中添加:

// 1. 在变量声明区域添加
late Box<FarmPlayer> _farmPlayer;
late Box<FarmLand> _farmLands;
late Box<InventoryItem> _farmInventory;

// 2. 添加 getter 访问器
Box<FarmPlayer> get farmPlayer => _farmPlayer;
Box<FarmLand> get farmLands => _farmLands;
Box<InventoryItem> get farmInventory => _farmInventory;

// 3. 在 _registerAdapters() 方法中添加
void _registerAdapters() {
  // ... 现有代码 ...
  
  // 农场游戏适配器 (typeId: 100-102)
  if (!Hive.isAdapterRegistered(100)) {
    Hive.registerAdapter(FarmPlayerAdapter());
  }
  if (!Hive.isAdapterRegistered(101)) {
    Hive.registerAdapter(FarmLandAdapter());
  }
  if (!Hive.isAdapterRegistered(102)) {
    Hive.registerAdapter(InventoryItemAdapter());
  }
}

// 4. 在 _openBoxes() 方法中添加
Future<void> _openBoxes() async {
  // ... 现有代码 ...
  
  // 农场游戏 Boxes
  _farmPlayer = await _openBoxSafe<FarmPlayer>('farmPlayer');
  _farmLands = await _openBoxSafe<FarmLand>('farmLands');
  _farmInventory = await _openBoxSafe<InventoryItem>('farmInventory');
}

// 5. 在 _initializeDefaultData() 方法中添加
Future<void> _initializeDefaultData() async {
  // ... 现有代码 ...
  
  // 农场游戏初始化
  await _initializeFarmData();
}

// 6. 添加新方法
Future<void> _initializeFarmData() async {
  if (_farmPlayer.isEmpty) {
    final deviceId = 'device_${DateTime.now().millisecondsSinceEpoch}';
    final player = FarmPlayer.createDefault(deviceId);
    await _farmPlayer.put('player', player);
    
    // 初始化 12 块土地(前 6 块解锁)
    for (int i = 0; i < FarmConfig.totalLands; i++) {
      final land = FarmLand.initial(i, unlocked: i < FarmConfig.initialUnlockedLands);
      await _farmLands.put(i, land);
    }
    
    // 初始种子
    await _farmInventory.put(
      'radish_seed',
      InventoryItem.seed(
        cropId: 'radish',
        name: '萝卜',
        emoji: '🥕',
        price: 10,
        quantity: FarmConfig.initialSeedQuantity,
      ),
    );
  }
}

Step 2: 添加必要的 import

在 HiveService 文件顶部添加:

import 'package:mom_kitchen/src/models/farm/farm_player.dart';
import 'package:mom_kitchen/src/models/farm/farm_land.dart';
import 'package:mom_kitchen/src/models/farm/inventory_item.dart';
import 'package:mom_kitchen/src/config/farm_config.dart';

Task 4: 控制器层 - 核心游戏逻辑

目标: 实现游戏核心控制器,处理所有游戏逻辑

Files:

  • Create: lib/src/controllers/farm/farm_game_controller.dart
  • Create: lib/src/controllers/farm/farm_shop_controller.dart
  • Create: lib/src/controllers/farm/farm_inventory_controller.dart
  • Create: lib/src/controllers/farm/farm_achievement_controller.dart
  • Modify: lib/src/app_binding.dart (注册控制器)

Step 1: 创建核心游戏控制器

Create: lib/src/controllers/farm/farm_game_controller.dart

(文件较长,包含种植、浇水、收获、升级等核心逻辑)

/*
 * 文件: farm_game_controller.dart
 * 名称: 农场游戏控制器
 * 作用: 管理游戏核心逻辑:种植、生长、浇水、收获、升级
 * 更新: 2026-04-18 初始创建
 */

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mom_kitchen/src/models/farm/farm_player.dart';
import 'package:mom_kitchen/src/models/farm/farm_land.dart';
import 'package:mom_kitchen/src/models/farm/inventory_item.dart';
import 'package:mom_kitchen/src/models/farm/crop_config.dart';
import 'package:mom_kitchen/src/models/farm/crop_registry.dart';
import 'package:mom_kitchen/src/models/farm/achievement_config.dart';
import 'package:mom_kitchen/src/models/farm/achievement_registry.dart';
import 'package:mom_kitchen/src/services/data/hive_service.dart';
import 'package:mom_kitchen/src/config/farm_config.dart';

class FarmGameController extends GetxController {
  final _hiveService = Get.find<HiveService>();

  final Rx<FarmPlayer> player = FarmPlayer.createDefault('').obs;
  final RxList<FarmLand> lands = <FarmLand>[].obs;
  final RxList<InventoryItem> inventory = <InventoryItem>[].obs;
  final RxList<String> unlockedAchievements = <String>[].obs;

  Timer? _growthTimer;

  @override
  void onInit() {
    super.onInit();
    _loadData();
    _startGrowthTimer();
  }

  @override
  void onClose() {
    _growthTimer?.cancel();
    super.onClose();
  }

  void _loadData() {
    final hiveService = Get.find<HiveService>();
    player.value = hiveService.farmPlayer.get('player') ?? FarmPlayer.createDefault('');
    lands.assignAll(hiveService.farmLands.values.toList());
    inventory.assignAll(hiveService.farmInventory.values.toList());
    unlockedAchievements.assignAll(player.value.achievements);
    _updateGrowthStages();
  }

  void _startGrowthTimer() {
    _growthTimer = Timer.periodic(const Duration(seconds: 10), (_) {
      _updateGrowthStages();
    });
  }

  void _updateGrowthStages() {
    bool changed = false;
    final now = DateTime.now();

    for (final land in lands) {
      if (land.cropId == null || land.isWithered || land.isReady) continue;

      final crop = CropRegistry.getById(land.cropId!);
      if (crop == null) continue;

      final elapsed = now.difference(land.plantTime!).inMinutes.toDouble();
      final progress = elapsed / crop.growthTime;

      // 检查枯萎
      if (land.needWater && land.lastWaterTime != null) {
        final sinceWater = now.difference(land.lastWaterTime!).inMinutes;
        if (sinceWater > FarmConfig.witherTimeMinutes) {
          land.isWithered = true;
          changed = true;
          continue;
        }
      }

      // 更新生长阶段
      double accumulated = 0;
      for (final stage in crop.stages) {
        accumulated += crop.growthTime * stage.durationPercent;
        if (elapsed >= accumulated) {
          final newStage = stage.stage;
          if (newStage != land.growthStage) {
            land.growthStage = newStage;
            land.needWater = stage.needWater;
            changed = true;
          }
        }
      }

      // 检查是否成熟
      if (progress >= 1.0 && !land.isReady) {
        land.isReady = true;
        changed = true;
      }
    }

    if (changed) {
      _saveLands();
      lands.refresh();
    }
  }

  Future<void> plantCrop({required int landId, required String cropId}) async {
    final land = lands.firstWhere((l) => l.landId == landId);
    if (land.cropId != null) {
      Get.snackbar('提示', '这块土地已经种植了作物');
      return;
    }

    final seedItemId = '${cropId}_seed';
    final seedItem = inventory.firstWhere(
      (item) => item.itemId == seedItemId,
      orElse: () => throw Exception('没有该作物种子'),
    );

    if (seedItem.quantity <= 0) {
      Get.snackbar('提示', '种子数量不足,请前往商店购买');
      return;
    }

    // 消耗种子
    seedItem.quantity--;
    if (seedItem.quantity == 0) {
      inventory.remove(seedItem);
    }
    await _saveInventory();

    // 种植
    land.cropId = cropId;
    land.plantTime = DateTime.now();
    land.growthStage = 0;
    land.needWater = true;
    land.isWithered = false;
    land.isReady = false;
    land.lastWaterTime = null;

    player.value.totalPlant++;
    await _savePlayer();
    await _saveLands();

    Get.snackbar('🌱 种植成功', '已开始种植${CropRegistry.getById(cropId)?.name}');
  }

  Future<void> waterLand(int landId) async {
    final land = lands.firstWhere((l) => l.landId == landId);
    if (!land.needWater || land.isWithered || land.isReady) {
      Get.snackbar('提示', '这块土地不需要浇水');
      return;
    }

    land.needWater = false;
    land.lastWaterTime = DateTime.now();
    await _saveLands();

    Get.snackbar('💧 浇水成功', '作物生长速度已提升');
  }

  Future<void> harvestCrop(int landId) async {
    final land = lands.firstWhere((l) => l.landId == landId);
    if (!land.isReady) {
      Get.snackbar('提示', '作物还未成熟');
      return;
    }

    final crop = CropRegistry.getById(land.cropId!);
    if (crop == null) return;

    // 增加奖励
    player.value.gold += crop.harvestPrice;
    player.value.experience += crop.harvestExp;
    player.value.totalHarvest++;

    // 添加果实到背包
    final fruitItem = InventoryItem(
      itemId: '${crop.id}_fruit',
      itemName: crop.name,
      itemType: 'fruit',
      quantity: 1,
      emoji: crop.emoji,
      price: crop.harvestPrice,
    );

    final existingFruit = inventory.where(
      (item) => item.itemId == fruitItem.itemId,
    );
    if (existingFruit.isNotEmpty) {
      existingFruit.first.quantity++;
    } else {
      inventory.add(fruitItem);
    }

    // 重置土地
    land.cropId = null;
    land.plantTime = null;
    land.growthStage = 0;
    land.isReady = false;
    land.isWithered = false;
    land.needWater = false;

    await _savePlayer();
    await _saveInventory();
    await _saveLands();

    // 检查升级
    _checkLevelUp();

    // 检查成就
    _checkAchievements();

    Get.snackbar('🎉 收获成功', '获得 ${crop.harvestPrice} 金币,+${crop.harvestExp} 经验');
  }

  Future<void> clearWitheredLand(int landId) async {
    final land = lands.firstWhere((l) => l.landId == landId);
    if (!land.isWithered) return;

    land.cropId = null;
    land.plantTime = null;
    land.growthStage = 0;
    land.isWithered = false;
    land.isReady = false;
    land.needWater = false;

    await _saveLands();
    Get.snackbar('🧹 清理完成', '土地已恢复');
  }

  void _checkLevelUp() {
    while (player.value.experience >= player.value.expToNextLevel) {
      player.value.experience -= player.value.expToNextLevel;
      player.value.level++;

      // 升级奖励
      player.value.gold += player.value.level * 50;
      player.value.diamond += 5;

      // 解锁新作物
      final newCrops = CropRegistry.getAvailableForLevel(player.value.level);
      for (final crop in newCrops) {
        if (!player.value.unlockedCrops.contains(crop.id)) {
          player.value.unlockedCrops.add(crop.id);
        }
      }

      Get.snackbar(
        '🎊 升级!',
        '当前等级Lv.${player.value.level}\n奖励:${player.value.level * 50} 金币 + 5 钻石',
        duration: const Duration(seconds: 3),
      );

      _checkAchievements();
    }
    _savePlayer();
  }

  void _checkAchievements() {
    final completed = player.value.achievements;
    final newOnes = AchievementRegistry.checkNewAchievements(
      'totalHarvest',
      player.value.totalHarvest,
      completed,
    );

    newOnes.addAll(AchievementRegistry.checkNewAchievements(
      'level',
      player.value.level,
      completed,
    ));

    for (final achievement in newOnes) {
      if (!completed.contains(achievement.id)) {
        completed.add(achievement.id);
        player.value.gold += achievement.rewardGold;
        player.value.experience += achievement.rewardExp;
        player.value.diamond += achievement.rewardDiamond;

        newAchievements.add(achievement);
        Get.snackbar(
          '🏆 成就解锁!',
          '${achievement.emoji} ${achievement.name}\n${achievement.description}',
          duration: const Duration(seconds: 3),
        );
      }
    }

    _savePlayer();
  }

  Future<void> _savePlayer() async {
    await _dataService.savePlayer(player.value);
  }

  Future<void> _saveLands() async {
    await _dataService.saveAllLands(lands);
  }

  Future<void> _saveInventory() async {
    for (final item in inventory) {
      await _dataService.saveItem(item);
    }
  }

  // 调试功能
  Future<void> debugAddGold() async {
    player.value.gold += 1000;
    await _savePlayer();
    Get.snackbar('调试', '已添加 1000 金币');
  }

  Future<void> debugSpeedUp() async {
    for (final land in lands) {
      if (land.cropId != null && !land.isReady) {
        land.plantTime = DateTime.now().subtract(
          Duration(
            minutes: CropRegistry.getById(land.cropId!)!.growthTime,
          ),
        );
      }
    }
    await _saveLands();
    Get.snackbar('调试', '所有作物已加速成熟');
  }
}

Step 2: 创建商店控制器

Create: lib/src/controllers/farm/farm_shop_controller.dart

/*
 * 文件: farm_shop_controller.dart
 * 名称: 农场商店控制器
 * 作用: 管理种子购买逻辑
 * 更新: 2026-04-18 初始创建
 */

import 'package:get/get.dart';
import 'package:mom_kitchen/src/models/farm/crop_config.dart';
import 'package:mom_kitchen/src/models/farm/crop_registry.dart';
import 'package:mom_kitchen/src/models/farm/inventory_item.dart';
import 'package:mom_kitchen/src/services/data/farm_data_service.dart';
import 'package:mom_kitchen/src/controllers/farm/farm_game_controller.dart';

class FarmShopController extends GetxController {
  final _dataService = FarmDataService.instance;
  final _gameController = Get.find<FarmGameController>();

  RxList<CropConfig> availableCrops = <CropConfig>[].obs;

  @override
  void onInit() {
    super.onInit();
    _loadAvailableCrops();
  }

  void _loadAvailableCrops() {
    availableCrops.assignAll(CropRegistry.getAll());
  }

  Future<void> buySeed(String cropId) async {
    final crop = CropRegistry.getById(cropId);
    if (crop == null) return;

    final player = _gameController.player.value;
    if (player.gold < crop.seedPrice) {
      Get.snackbar('金币不足', '需要 ${crop.seedPrice} 金币,当前 ${player.gold} 金币');
      return;
    }

    // 扣除金币
    player.gold -= crop.seedPrice;
    _gameController.player.value = player;
    await _dataService.savePlayer(player);

    // 添加种子到背包
    final seedItem = InventoryItem.seed(
      cropId: crop.id,
      name: crop.name,
      emoji: crop.emoji,
      price: crop.seedPrice,
      quantity: 1,
    );

    final inventory = _gameController.inventory;
    final existing = inventory.where(
      (item) => item.itemId == seedItem.itemId,
    );
    if (existing.isNotEmpty) {
      existing.first.quantity++;
    } else {
      inventory.add(seedItem);
    }

    Get.snackbar('🛒 购买成功', '已购买 ${crop.name}种子');
  }
}

Step 3: 创建背包控制器

Create: lib/src/controllers/farm/farm_inventory_controller.dart

/*
 * 文件: farm_inventory_controller.dart
 * 名称: 农场背包控制器
 * 作用: 管理背包物品显示和分类
 * 更新: 2026-04-18 初始创建
 */

import 'package:get/get.dart';
import 'package:mom_kitchen/src/models/farm/inventory_item.dart';
import 'package:mom_kitchen/src/controllers/farm/farm_game_controller.dart';

class FarmInventoryController extends GetxController {
  final _gameController = Get.find<FarmGameController>();

  final RxString selectedTab = 'seed'.obs;

  List<InventoryItem> get seeds =>
      _gameController.inventory.where((i) => i.isSeed).toList();

  List<InventoryItem> get fruits =>
      _gameController.inventory.where((i) => i.isFruit).toList();

  List<InventoryItem> get tools =>
      _gameController.inventory.where((i) => i.itemType == 'tool').toList();

  List<InventoryItem> get filteredItems {
    switch (selectedTab.value) {
      case 'seed':
        return seeds;
      case 'fruit':
        return fruits;
      case 'tool':
        return tools;
      default:
        return _gameController.inventory;
    }
  }

  void selectTab(String tab) {
    selectedTab.value = tab;
  }

  int get totalItems => _gameController.inventory.fold(
        0,
        (sum, item) => sum + item.quantity,
      );
}

Step 4: 创建成就控制器

Create: lib/src/controllers/farm/farm_achievement_controller.dart

/*
 * 文件: farm_achievement_controller.dart
 * 名称: 农场成就控制器
 * 作用: 管理成就显示和进度
 * 更新: 2026-04-18 初始创建
 */

import 'package:get/get.dart';
import 'package:mom_kitchen/src/models/farm/achievement_config.dart';
import 'package:mom_kitchen/src/models/farm/achievement_registry.dart';
import 'package:mom_kitchen/src/controllers/farm/farm_game_controller.dart';

class FarmAchievementController extends GetxController {
  final _gameController = Get.find<FarmGameController>();

  List<AchievementConfig> get allAchievements =>
      AchievementRegistry.getAll().values.toList();

  List<AchievementConfig> get completedAchievements => allAchievements
      .where((a) => _gameController.player.value.achievements.contains(a.id))
      .toList();

  List<AchievementConfig> get pendingAchievements => allAchievements
      .where((a) =>
          !_gameController.player.value.achievements.contains(a.id))
      .toList();

  double getProgress(AchievementConfig achievement) {
    final player = _gameController.player.value;

    switch (achievement.conditionType) {
      case 'totalHarvest':
        return (player.totalHarvest / achievement.conditionValue)
            .clamp(0.0, 1.0);
      case 'level':
        return (player.level / achievement.conditionValue).clamp(0.0, 1.0);
      case 'unlockedCrops':
        return (player.unlockedCrops.length / achievement.conditionValue)
            .clamp(0.0, 1.0);
      default:
        return 0.0;
    }
  }
}

Step 5: 注册控制器到 AppBinding

Modify: lib/src/app_binding.dart

dependencies() 方法末尾添加:

// --- 农场游戏控制器 ---
Get.lazyPut(() => FarmGameController(), fenix: true);
Get.lazyPut(() => FarmShopController(), fenix: true);
Get.lazyPut(() => FarmInventoryController(), fenix: true);
Get.lazyPut(() => FarmAchievementController(), fenix: true);

同时在文件顶部添加导入:

import 'package:mom_kitchen/src/controllers/farm/farm_game_controller.dart';
import 'package:mom_kitchen/src/controllers/farm/farm_shop_controller.dart';
import 'package:mom_kitchen/src/controllers/farm/farm_inventory_controller.dart';
import 'package:mom_kitchen/src/controllers/farm/farm_achievement_controller.dart';

Task 5: UI 层 - 主游戏页面

目标: 创建主游戏页面,包含 Canvas 绘制的菜园网格和交互逻辑

Files:

  • Create: lib/src/pages/tools/farm/widgets/farm_grid_painter.dart
  • Create: lib/src/pages/tools/farm/widgets/land_widget.dart
  • Create: lib/src/pages/tools/farm/widgets/crop_widget.dart
  • Create: lib/src/pages/tools/farm/farm_game_page.dart

Step 1: 创建土地 Widget

Create: lib/src/pages/tools/farm/widgets/land_widget.dart

/*
 * 文件: land_widget.dart
 * 名称: 土地 Widget
 * 作用: 显示单块土地及其作物状态
 * 更新: 2026-04-18 初始创建
 */

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mom_kitchen/src/config/design_tokens.dart';
import 'package:mom_kitchen/src/models/farm/farm_land.dart';
import 'package:mom_kitchen/src/controllers/farm/farm_game_controller.dart';

class LandWidget extends StatelessWidget {
  final FarmLand land;
  final VoidCallback onTap;

  const LandWidget({
    super.key,
    required this.land,
    required this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark;

    return GestureDetector(
      onTap: onTap,
      child: AnimatedContainer(
        duration: DesignTokens.durationNormal,
        decoration: BoxDecoration(
          color: land.isUnlocked
              ? (isDark
                  ? const Color(0xFF3D2B1F)
                  : const Color(0xFFDEB887))
              : (isDark ? DarkDesignTokens.card : DesignTokens.text3.withValues(alpha: 0.1)),
          borderRadius: DesignTokens.borderRadiusMd,
          border: Border.all(
            color: land.isUnlocked
                ? (isDark ? const Color(0xFF5C4033) : const Color(0xFFCD853F))
                : (isDark ? DarkDesignTokens.glassBorder : DesignTokens.text3.withValues(alpha: 0.2)),
            width: 2,
          ),
          boxShadow: [
            BoxShadow(
              color: isDark ? Colors.black.withValues(alpha: 0.3) : Colors.black.withValues(alpha: 0.1),
              blurRadius: 8,
              offset: const Offset(0, 4),
            ),
          ],
        ),
        child: land.isUnlocked
            ? _buildLandContent()
            : _buildLockedOverlay(),
      ),
    );
  }

  Widget _buildLandContent() {
    if (land.cropId == null) {
      return Center(
        child: Text(
          '🟫',
          style: const TextStyle(fontSize: 32),
        ),
      );
    }

    return Stack(
      children: [
        Center(
          child: AnimatedSwitcher(
            duration: DesignTokens.durationNormal,
            child: Text(
              land.currentDisplayEmoji,
              key: ValueKey(land.currentDisplayEmoji),
              style: const TextStyle(fontSize: 36),
            ),
          ),
        ),
        if (land.needWater && !land.isWithered)
          Positioned(
            top: 4,
            right: 4,
            child: Container(
              padding: const EdgeInsets.all(2),
              decoration: BoxDecoration(
                color: Colors.blue.withValues(alpha: 0.8),
                borderRadius: DesignTokens.borderRadiusFull,
              ),
              child: const Text('💧', style: TextStyle(fontSize: 10)),
            ),
          ),
        if (land.isReady)
          Positioned(
            top: 4,
            right: 4,
            child: Container(
              padding: const EdgeInsets.all(2),
              decoration: BoxDecoration(
                color: Colors.green.withValues(alpha: 0.8),
                borderRadius: DesignTokens.borderRadiusFull,
              ),
              child: const Text('✨', style: TextStyle(fontSize: 10)),
            ),
          ),
        if (land.isWithered)
          Positioned.fill(
            child: Container(
              decoration: BoxDecoration(
                color: Colors.black.withValues(alpha: 0.3),
                borderRadius: DesignTokens.borderRadiusMd,
              ),
              child: const Center(
                child: Text('🥀', style: TextStyle(fontSize: 24)),
              ),
            ),
          ),
      ],
    );
  }

  Widget _buildLockedOverlay() {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Text('🔒', style: TextStyle(fontSize: 24)),
          const SizedBox(height: 4),
          Text(
            '未解锁',
            style: TextStyle(
              fontSize: DesignTokens.fontXs,
              color: CupertinoTheme.brightnessOf(Get.context!) == Brightness.dark
                  ? DarkDesignTokens.text3
                  : DesignTokens.text3,
            ),
          ),
        ],
      ),
    );
  }
}

Step 2: 创建主游戏页面

Create: lib/src/pages/tools/farm/farm_game_page.dart

/*
 * 文件: farm_game_page.dart
 * 名称: 农场主游戏页面
 * 作用: 展示菜园网格,提供种植、浇水、收获等操作
 * 更新: 2026-04-18 初始创建
 */

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mom_kitchen/src/config/design_tokens.dart';
import 'package:mom_kitchen/src/controllers/farm/farm_game_controller.dart';
import 'package:mom_kitchen/src/pages/tools/farm/widgets/land_widget.dart';

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

  @override
  State<FarmGamePage> createState() => _FarmGamePageState();
}

class _FarmGamePageState extends State<FarmGamePage> {
  late FarmGameController _controller;
  bool _isInitialized = false;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    if (!_isInitialized) {
      _isInitialized = true;
      _controller = Get.find<FarmGameController>();
    }
  }

  @override
  Widget build(BuildContext context) {
    final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark;

    return CupertinoPageScaffold(
      backgroundColor: isDark ? DarkDesignTokens.background : DesignTokens.background,
      child: SafeArea(
        child: Column(
          children: [
            _buildHeader(isDark),
            _buildStatusBar(isDark),
            Expanded(child: _buildGardenGrid(isDark)),
            _buildActionBar(isDark),
          ],
        ),
      ),
    );
  }

  Widget _buildHeader(bool isDark) {
    return Padding(
      padding: const EdgeInsets.fromLTRB(
        DesignTokens.space4,
        DesignTokens.space3,
        DesignTokens.space4,
        DesignTokens.space2,
      ),
      child: Row(
        children: [
          GestureDetector(
            onTap: () => Get.back(),
            child: Container(
              width: 40,
              height: 40,
              decoration: BoxDecoration(
                color: isDark
                    ? DarkDesignTokens.glass
                    : DesignTokens.text3.withValues(alpha: 0.08),
                borderRadius: DesignTokens.borderRadiusMd,
              ),
              child: const Icon(CupertinoIcons.back, size: 20),
            ),
          ),
          const SizedBox(width: DesignTokens.space3),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  '🌾 小妈菜园',
                  style: TextStyle(
                    fontSize: DesignTokens.fontXxl,
                    fontWeight: FontWeight.w700,
                    color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
                  ),
                ),
                Text(
                  '种菜收菜,体验农场乐趣',
                  style: TextStyle(
                    fontSize: DesignTokens.fontSm,
                    color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3,
                  ),
                ),
              ],
            ),
          ),
          Row(
            children: [
              _buildNavButton(
                icon: CupertinoIcons.bag,
                onTap: () => Get.toNamed('/farm-inventory'),
                isDark: isDark,
              ),
              const SizedBox(width: 8),
              _buildNavButton(
                icon: CupertinoIcons.cart,
                onTap: () => Get.toNamed('/farm-shop'),
                isDark: isDark,
              ),
              const SizedBox(width: 8),
              _buildNavButton(
                icon: CupertinoIcons.trophy,
                onTap: () => Get.toNamed('/farm-achievement'),
                isDark: isDark,
              ),
            ],
          ),
        ],
      ),
    );
  }

  Widget _buildNavButton({
    required IconData icon,
    required VoidCallback onTap,
    required bool isDark,
  }) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
        width: 40,
        height: 40,
        decoration: BoxDecoration(
          color: DesignTokens.dynamicPrimary.withValues(alpha: 0.1),
          borderRadius: DesignTokens.borderRadiusMd,
        ),
        child: Icon(icon, size: 20, color: DesignTokens.dynamicPrimary),
      ),
    );
  }

  Widget _buildStatusBar(bool isDark) {
    return Obx(() {
      final player = _controller.player.value;
      return Container(
        padding: const EdgeInsets.symmetric(
          horizontal: DesignTokens.space4,
          vertical: DesignTokens.space2,
        ),
        margin: const EdgeInsets.symmetric(horizontal: DesignTokens.space4),
        decoration: BoxDecoration(
          color: isDark ? DarkDesignTokens.card : DesignTokens.card,
          borderRadius: DesignTokens.borderRadiusMd,
          border: Border.all(
            color: isDark
                ? DarkDesignTokens.glassBorder
                : DesignTokens.text3.withValues(alpha: 0.08),
          ),
        ),
        child: Row(
          children: [
            _buildStatusItem(
              icon: '⭐',
              value: 'Lv.${player.level}',
              progress: player.expProgress,
              isDark: isDark,
            ),
            const SizedBox(width: DesignTokens.space3),
            _buildStatusItem(
              icon: '💰',
              value: '${player.gold}',
              isDark: isDark,
            ),
            const SizedBox(width: DesignTokens.space3),
            _buildStatusItem(
              icon: '💎',
              value: '${player.diamond}',
              isDark: isDark,
            ),
            const Spacer(),
            _buildStatusItem(
              icon: '🌾',
              value: '${player.totalHarvest}',
              label: '收获',
              isDark: isDark,
            ),
          ],
        ),
      );
    });
  }

  Widget _buildStatusItem({
    required String icon,
    required String value,
    double? progress,
    String? label,
    required bool isDark,
  }) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Text(icon, style: const TextStyle(fontSize: 16)),
        const SizedBox(width: 4),
        Text(
          value,
          style: TextStyle(
            fontSize: DesignTokens.fontMd,
            fontWeight: FontWeight.w600,
            color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
          ),
        ),
        if (progress != null) ...[
          const SizedBox(width: 4),
          SizedBox(
            width: 40,
            child: LinearProgressIndicator(
              value: progress,
              backgroundColor: isDark
                  ? DarkDesignTokens.glassBorder
                  : DesignTokens.text3.withValues(alpha: 0.1),
              valueColor: AlwaysStoppedAnimation(DesignTokens.dynamicPrimary),
            ),
          ),
        ],
        if (label != null) ...[
          const SizedBox(width: 4),
          Text(
            label,
            style: TextStyle(
              fontSize: DesignTokens.fontXs,
              color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3,
            ),
          ),
        ],
      ],
    );
  }

  Widget _buildGardenGrid(bool isDark) {
    return Obx(() {
      final lands = _controller.lands;
      return Padding(
        padding: const EdgeInsets.all(DesignTokens.space4),
        child: LayoutBuilder(
          builder: (context, constraints) {
            final crossAxisCount = constraints.maxWidth > 600 ? 4 : 3;
            final itemWidth =
                (constraints.maxWidth - 12 * (crossAxisCount - 1)) / crossAxisCount;

            return GridView.builder(
              physics: const BouncingScrollPhysics(),
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: crossAxisCount,
                mainAxisSpacing: 12,
                crossAxisSpacing: 12,
                childAspectRatio: 1,
              ),
              itemCount: lands.length,
              itemBuilder: (context, index) {
                final land = lands[index];
                return SizedBox(
                  width: itemWidth,
                  child: LandWidget(
                    land: land,
                    onTap: () => _showLandActionSheet(land, isDark),
                  ),
                );
              },
            );
          },
        ),
      );
    });
  }

  void _showLandActionSheet(dynamic land, bool isDark) {
    showCupertinoModalPopup(
      context: context,
      builder: (context) {
        return CupertinoActionSheet(
          title: Text('土地 ${land.landId + 1}'),
          actions: [
            if (land.cropId == null && land.isUnlocked)
              CupertinoActionSheetAction(
                onPressed: () {
                  Get.back();
                  _showPlantSelector(land.landId);
                },
                child: const Text('🌱 播种'),
              ),
            if (land.needWater && !land.isWithered)
              CupertinoActionSheetAction(
                onPressed: () {
                  Get.back();
                  _controller.waterLand(land.landId);
                },
                child: const Text('💧 浇水'),
              ),
            if (land.isReady)
              CupertinoActionSheetAction(
                onPressed: () {
                  Get.back();
                  _controller.harvestCrop(land.landId);
                },
                child: const Text('🎉 收获'),
              ),
            if (land.isWithered)
              CupertinoActionSheetAction(
                onPressed: () {
                  Get.back();
                  _controller.clearWitheredLand(land.landId);
                },
                child: const Text('🧹 清理'),
              ),
            if (!land.isUnlocked)
              CupertinoActionSheetAction(
                onPressed: () {
                  Get.back();
                  _unlockLand(land.landId);
                },
                child: const Text('🔓 解锁土地'),
              ),
          ],
          cancelButton: CupertinoActionSheetAction(
            isDefaultAction: true,
            onPressed: () => Get.back(),
            child: const Text('取消'),
          ),
        );
      },
    );
  }

  void _showPlantSelector(int landId) {
    final seeds = _controller.inventory.where((i) => i.isSeed && i.quantity > 0).toList();

    showCupertinoModalPopup(
      context: context,
      builder: (context) {
        return CupertinoActionSheet(
          title: const Text('选择要播种的种子'),
          actions: seeds.map((seed) {
            return CupertinoActionSheetAction(
              onPressed: () {
                Get.back();
                final cropId = seed.itemId.replaceAll('_seed', '');
                _controller.plantCrop(landId: landId, cropId: cropId);
              },
              child: Text('${seed.emoji} ${seed.itemName} x${seed.quantity}'),
            );
          }).toList(),
          cancelButton: CupertinoActionSheetAction(
            isDefaultAction: true,
            onPressed: () => Get.back(),
            child: const Text('取消'),
          ),
        );
      },
    );
  }

  void _unlockLand(int landId) {
    showCupertinoDialog(
      context: context,
      builder: (context) {
        return CupertinoAlertDialog(
          title: const Text('解锁土地'),
          content: Text('解锁这块土地需要 200 金币,是否确认解锁?'),
          actions: [
            CupertinoDialogAction(
              child: const Text('取消'),
              onPressed: () => Get.back(),
            ),
            CupertinoDialogAction(
              isDefaultAction: true,
              child: const Text('确认'),
              onPressed: () {
                Get.back();
                final player = _controller.player.value;
                if (player.gold >= 200) {
                  player.gold -= 200;
                  final land = _controller.lands.firstWhere(
                    (l) => l.landId == landId,
                  );
                  land.isUnlocked = true;
                  _controller.player.value = player;
                  Get.snackbar('解锁成功', '土地 ${landId + 1} 已解锁');
                } else {
                  Get.snackbar('金币不足', '需要 200 金币解锁土地');
                }
              },
            ),
          ],
        );
      },
    );
  }

  Widget _buildActionBar(bool isDark) {
    return Container(
      padding: const EdgeInsets.all(DesignTokens.space4),
      decoration: BoxDecoration(
        color: isDark ? DarkDesignTokens.card : DesignTokens.card,
        border: Border(
          top: BorderSide(
            color: isDark
                ? DarkDesignTokens.glassBorder
                : DesignTokens.text3.withValues(alpha: 0.08),
          ),
        ),
      ),
      child: Row(
        children: [
          _buildDebugButton(isDark),
          const Spacer(),
          ElevatedButton.icon(
            onPressed: () => _showShareSheet(),
            icon: const Icon(CupertinoIcons.share),
            label: const Text('分享收获'),
            style: ElevatedButton.styleFrom(
              backgroundColor: DesignTokens.dynamicPrimary,
              foregroundColor: CupertinoColors.white,
              padding: const EdgeInsets.symmetric(
                horizontal: DesignTokens.space4,
                vertical: DesignTokens.space3,
              ),
              shape: RoundedRectangleBorder(
                borderRadius: DesignTokens.borderRadiusMd,
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildDebugButton(bool isDark) {
    return CupertinoButton(
      padding: EdgeInsets.zero,
      onPressed: () {
        showCupertinoModalPopup(
          context: context,
          builder: (context) {
            return CupertinoActionSheet(
              title: const Text('🐛 调试功能'),
              message: const Text('以下功能仅用于开发测试'),
              actions: [
                CupertinoActionSheetAction(
                  onPressed: () {
                    Get.back();
                    _controller.debugAddGold();
                  },
                  child: const Text('💰 添加 1000 金币'),
                ),
                CupertinoActionSheetAction(
                  onPressed: () {
                    Get.back();
                    _controller.debugSpeedUp();
                  },
                  child: const Text('⚡ 加速所有作物'),
                ),
              ],
              cancelButton: CupertinoActionSheetAction(
                isDefaultAction: true,
                onPressed: () => Get.back(),
                child: const Text('关闭'),
              ),
            );
          },
        );
      },
      child: Icon(
        CupertinoIcons.hammer,
        color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3,
      ),
    );
  }

  void _showShareSheet() {
    Get.snackbar('分享功能', '正在生成分享图片...', duration: const Duration(seconds: 1));
    // TODO: 实现分享逻辑
  }
}

Task 6: UI 层 - 商店页面

目标: 创建种子商店页面,支持购买操作

Files:

  • Create: lib/src/pages/tools/farm/farm_shop_page.dart

Step 1: 创建商店页面

Create: lib/src/pages/tools/farm/farm_shop_page.dart

/*
 * 文件: farm_shop_page.dart
 * 名称: 农场商店页面
 * 作用: 显示可购买的种子列表,支持购买操作
 * 更新: 2026-04-18 初始创建
 */

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mom_kitchen/src/config/design_tokens.dart';
import 'package:mom_kitchen/src/controllers/farm/farm_shop_controller.dart';
import 'package:mom_kitchen/src/controllers/farm/farm_game_controller.dart';
import 'package:mom_kitchen/src/models/farm/crop_config.dart';

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

  @override
  State<FarmShopPage> createState() => _FarmShopPageState();
}

class _FarmShopPageState extends State<FarmShopPage> {
  late FarmShopController _shopController;
  late FarmGameController _gameController;
  bool _isInitialized = false;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    if (!_isInitialized) {
      _isInitialized = true;
      _shopController = Get.find<FarmShopController>();
      _gameController = Get.find<FarmGameController>();
    }
  }

  @override
  Widget build(BuildContext context) {
    final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark;

    return CupertinoPageScaffold(
      backgroundColor: isDark ? DarkDesignTokens.background : DesignTokens.background,
      child: SafeArea(
        child: Column(
          children: [
            _buildHeader(isDark),
            Expanded(child: _buildShopList(isDark)),
          ],
        ),
      ),
    );
  }

  Widget _buildHeader(bool isDark) {
    return Padding(
      padding: const EdgeInsets.all(DesignTokens.space4),
      child: Row(
        children: [
          GestureDetector(
            onTap: () => Get.back(),
            child: Container(
              width: 40,
              height: 40,
              decoration: BoxDecoration(
                color: isDark
                    ? DarkDesignTokens.glass
                    : DesignTokens.text3.withValues(alpha: 0.08),
                borderRadius: DesignTokens.borderRadiusMd,
              ),
              child: const Icon(CupertinoIcons.back, size: 20),
            ),
          ),
          const SizedBox(width: DesignTokens.space3),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  '🏪 种子商店',
                  style: TextStyle(
                    fontSize: DesignTokens.fontXxl,
                    fontWeight: FontWeight.w700,
                    color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
                  ),
                ),
                Obx(() => Text(
                      '💰 ${_gameController.player.value.gold} 金币',
                      style: TextStyle(
                        fontSize: DesignTokens.fontSm,
                        color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3,
                      ),
                    )),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildShopList(bool isDark) {
    return Obx(() {
      final crops = _shopController.availableCrops;
      return ListView.separated(
        padding: const EdgeInsets.all(DesignTokens.space4),
        itemCount: crops.length,
        separatorBuilder: (context, index) => const SizedBox(height: 12),
        itemBuilder: (context, index) {
          final crop = crops[index];
          final isUnlocked = _gameController.player.value.unlockedCrops.contains(crop.id);

          return _buildCropCard(crop, isUnlocked, isDark);
        },
      );
    });
  }

  Widget _buildCropCard(CropConfig crop, bool isUnlocked, bool isDark) {
    return Container(
      padding: const EdgeInsets.all(DesignTokens.space4),
      decoration: BoxDecoration(
        color: isDark ? DarkDesignTokens.card : DesignTokens.card,
        borderRadius: DesignTokens.borderRadiusLg,
        border: Border.all(
          color: isDark
              ? DarkDesignTokens.glassBorder
              : DesignTokens.text3.withValues(alpha: 0.08),
        ),
        boxShadow: DesignTokens.shadowsMd,
      ),
      child: Row(
        children: [
          Container(
            width: 64,
            height: 64,
            decoration: BoxDecoration(
              color: DesignTokens.dynamicPrimary.withValues(alpha: 0.1),
              borderRadius: DesignTokens.borderRadiusMd,
            ),
            child: Center(
              child: Text(crop.emoji, style: const TextStyle(fontSize: 36)),
            ),
          ),
          const SizedBox(width: DesignTokens.space4),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Row(
                  children: [
                    Text(
                      crop.name,
                      style: TextStyle(
                        fontSize: DesignTokens.fontLg,
                        fontWeight: FontWeight.w600,
                        color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
                      ),
                    ),
                    if (!isUnlocked) ...[
                      const SizedBox(width: 8),
                      Container(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 8,
                          vertical: 2,
                        ),
                        decoration: BoxDecoration(
                          color: DesignTokens.orange.withValues(alpha: 0.2),
                          borderRadius: DesignTokens.borderRadiusSm,
                        ),
                        child: Text(
                          'Lv.${crop.unlockLevel} 解锁',
                          style: TextStyle(
                            fontSize: DesignTokens.fontXs,
                            color: DesignTokens.orange,
                          ),
                        ),
                      ),
                    ],
                  ],
                ),
                const SizedBox(height: 8),
                Row(
                  children: [
                    _buildInfoTag('⏱️', '${crop.growthTime} 分钟', isDark),
                    const SizedBox(width: 8),
                    _buildInfoTag('💰', '收获 ${crop.harvestPrice}', isDark),
                    const SizedBox(width: 8),
                    _buildInfoTag('⭐', '+${crop.harvestExp} EXP', isDark),
                  ],
                ),
              ],
            ),
          ),
          const SizedBox(width: DesignTokens.space3),
          SizedBox(
            width: 80,
            child: CupertinoButton(
              padding: EdgeInsets.zero,
              color: isUnlocked ? DesignTokens.dynamicPrimary : DesignTokens.text3,
              borderRadius: DesignTokens.borderRadiusMd,
              onPressed: isUnlocked ? () => _shopController.buySeed(crop.id) : null,
              child: Text(
                '${crop.seedPrice}💰',
                style: TextStyle(
                  fontSize: DesignTokens.fontMd,
                  fontWeight: FontWeight.w600,
                  color: CupertinoColors.white,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildInfoTag(String icon, String text, bool isDark) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Text(icon, style: const TextStyle(fontSize: 12)),
        const SizedBox(width: 4),
        Text(
          text,
          style: TextStyle(
            fontSize: DesignTokens.fontSm,
            color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2,
          ),
        ),
      ],
    );
  }
}

Task 7: UI 层 - 背包和成就页面

目标: 创建背包和成就页面

Files:

  • Create: lib/src/pages/tools/farm/farm_inventory_page.dart
  • Create: lib/src/pages/tools/farm/farm_achievement_page.dart

Step 1: 创建背包页面

Create: lib/src/pages/tools/farm/farm_inventory_page.dart

/*
 * 文件: farm_inventory_page.dart
 * 名称: 农场背包页面
 * 作用: 显示背包物品,支持分类查看
 * 更新: 2026-04-18 初始创建
 */

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mom_kitchen/src/config/design_tokens.dart';
import 'package:mom_kitchen/src/controllers/farm/farm_inventory_controller.dart';
import 'package:mom_kitchen/src/models/farm/inventory_item.dart';

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

  @override
  State<FarmInventoryPage> createState() => _FarmInventoryPageState();
}

class _FarmInventoryPageState extends State<FarmInventoryPage> {
  late FarmInventoryController _controller;
  bool _isInitialized = false;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    if (!_isInitialized) {
      _isInitialized = true;
      _controller = Get.find<FarmInventoryController>();
    }
  }

  @override
  Widget build(BuildContext context) {
    final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark;

    return CupertinoPageScaffold(
      backgroundColor: isDark ? DarkDesignTokens.background : DesignTokens.background,
      child: SafeArea(
        child: Column(
          children: [
            _buildHeader(isDark),
            _buildTabs(isDark),
            Expanded(child: _buildInventoryList(isDark)),
          ],
        ),
      ),
    );
  }

  Widget _buildHeader(bool isDark) {
    return Padding(
      padding: const EdgeInsets.all(DesignTokens.space4),
      child: Row(
        children: [
          GestureDetector(
            onTap: () => Get.back(),
            child: Container(
              width: 40,
              height: 40,
              decoration: BoxDecoration(
                color: isDark
                    ? DarkDesignTokens.glass
                    : DesignTokens.text3.withValues(alpha: 0.08),
                borderRadius: DesignTokens.borderRadiusMd,
              ),
              child: const Icon(CupertinoIcons.back, size: 20),
            ),
          ),
          const SizedBox(width: DesignTokens.space3),
          Expanded(
            child: Text(
              '🎒 我的背包',
              style: TextStyle(
                fontSize: DesignTokens.fontXxl,
                fontWeight: FontWeight.w700,
                color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
              ),
            ),
          ),
          Obx(() => Text(
                '共 ${_controller.totalItems} 件',
                style: TextStyle(
                  fontSize: DesignTokens.fontMd,
                  color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3,
                ),
              )),
        ],
      ),
    );
  }

  Widget _buildTabs(bool isDark) {
    return Obx(() {
      final tabs = [
        {'id': 'seed', 'label': '🌱 种子', 'count': _controller.seeds.length},
        {'id': 'fruit', 'label': '🍎 果实', 'count': _controller.fruits.length},
        {'id': 'tool', 'label': '🔧 道具', 'count': _controller.tools.length},
      ];

      return Container(
        margin: const EdgeInsets.symmetric(horizontal: DesignTokens.space4),
        padding: const EdgeInsets.all(4),
        decoration: BoxDecoration(
          color: isDark ? DarkDesignTokens.segmentedBg : DesignTokens.segmentedBg,
          borderRadius: DesignTokens.borderRadiusMd,
        ),
        child: Row(
          children: tabs.map((tab) {
            final isSelected = _controller.selectedTab.value == tab['id'];
            return Expanded(
              child: GestureDetector(
                onTap: () => _controller.selectTab(tab['id'] as String),
                child: Container(
                  padding: const EdgeInsets.symmetric(vertical: 10),
                  decoration: BoxDecoration(
                    color: isSelected
                        ? (isDark ? DarkDesignTokens.card : DesignTokens.card)
                        : Colors.transparent,
                    borderRadius: DesignTokens.borderRadiusSm,
                    boxShadow: isSelected ? DesignTokens.shadowsSm : null,
                  ),
                  child: Center(
                    child: Text(
                      '${tab['label']} (${tab['count']})',
                      style: TextStyle(
                        fontSize: DesignTokens.fontSm,
                        fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
                        color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
                      ),
                    ),
                  ),
                ),
              ),
            );
          }).toList(),
        ),
      );
    });
  }

  Widget _buildInventoryList(bool isDark) {
    return Obx(() {
      final items = _controller.filteredItems;

      if (items.isEmpty) {
        return Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text('📭', style: TextStyle(fontSize: 64)),
              const SizedBox(height: DesignTokens.space4),
              Text(
                '背包空空如也',
                style: TextStyle(
                  fontSize: DesignTokens.fontLg,
                  fontWeight: FontWeight.w600,
                  color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
                ),
              ),
              const SizedBox(height: DesignTokens.space2),
              Text(
                '快去种植或购买种子吧!',
                style: TextStyle(
                  fontSize: DesignTokens.fontSm,
                  color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3,
                ),
              ),
            ],
          ),
        );
      }

      return GridView.builder(
        padding: const EdgeInsets.all(DesignTokens.space4),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          mainAxisSpacing: 12,
          crossAxisSpacing: 12,
          childAspectRatio: 0.8,
        ),
        itemCount: items.length,
        itemBuilder: (context, index) {
          return _buildItemCard(items[index], isDark);
        },
      );
    });
  }

  Widget _buildItemCard(InventoryItem item, bool isDark) {
    return Container(
      padding: const EdgeInsets.all(DesignTokens.space3),
      decoration: BoxDecoration(
        color: isDark ? DarkDesignTokens.card : DesignTokens.card,
        borderRadius: DesignTokens.borderRadiusLg,
        border: Border.all(
          color: isDark
              ? DarkDesignTokens.glassBorder
              : DesignTokens.text3.withValues(alpha: 0.08),
        ),
        boxShadow: DesignTokens.shadowsSm,
      ),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(item.emoji, style: const TextStyle(fontSize: 40)),
          const SizedBox(height: 8),
          Text(
            item.itemName,
            style: TextStyle(
              fontSize: DesignTokens.fontSm,
              fontWeight: FontWeight.w600,
              color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
            ),
            textAlign: TextAlign.center,
            maxLines: 2,
            overflow: TextOverflow.ellipsis,
          ),
          const SizedBox(height: 4),
          Text(
            'x${item.quantity}',
            style: TextStyle(
              fontSize: DesignTokens.fontLg,
              fontWeight: FontWeight.w700,
              color: DesignTokens.dynamicPrimary,
            ),
          ),
        ],
      ),
    );
  }
}

Step 2: 创建成就页面

Create: lib/src/pages/tools/farm/farm_achievement_page.dart

/*
 * 文件: farm_achievement_page.dart
 * 名称: 农场成就页面
 * 作用: 显示成就列表和进度
 * 更新: 2026-04-18 初始创建
 */

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mom_kitchen/src/config/design_tokens.dart';
import 'package:mom_kitchen/src/controllers/farm/farm_achievement_controller.dart';
import 'package:mom_kitchen/src/models/farm/achievement_config.dart';

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

  @override
  State<FarmAchievementPage> createState() => _FarmAchievementPageState();
}

class _FarmAchievementPageState extends State<FarmAchievementPage> {
  late FarmAchievementController _controller;
  bool _isInitialized = false;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    if (!_isInitialized) {
      _isInitialized = true;
      _controller = Get.find<FarmAchievementController>();
    }
  }

  @override
  Widget build(BuildContext context) {
    final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark;

    return CupertinoPageScaffold(
      backgroundColor: isDark ? DarkDesignTokens.background : DesignTokens.background,
      child: SafeArea(
        child: Column(
          children: [
            _buildHeader(isDark),
            Expanded(child: _buildAchievementList(isDark)),
          ],
        ),
      ),
    );
  }

  Widget _buildHeader(bool isDark) {
    return Padding(
      padding: const EdgeInsets.all(DesignTokens.space4),
      child: Row(
        children: [
          GestureDetector(
            onTap: () => Get.back(),
            child: Container(
              width: 40,
              height: 40,
              decoration: BoxDecoration(
                color: isDark
                    ? DarkDesignTokens.glass
                    : DesignTokens.text3.withValues(alpha: 0.08),
                borderRadius: DesignTokens.borderRadiusMd,
              ),
              child: const Icon(CupertinoIcons.back, size: 20),
            ),
          ),
          const SizedBox(width: DesignTokens.space3),
          Expanded(
            child: Text(
              '🏆 成就中心',
              style: TextStyle(
                fontSize: DesignTokens.fontXxl,
                fontWeight: FontWeight.w700,
                color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
              ),
            ),
          ),
          Obx(() => Text(
                '${_controller.completedAchievements.length}/${_controller.allAchievements.length}',
                style: TextStyle(
                  fontSize: DesignTokens.fontMd,
                  color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3,
                ),
              )),
        ],
      ),
    );
  }

  Widget _buildAchievementList(bool isDark) {
    return ListView.separated(
      padding: const EdgeInsets.all(DesignTokens.space4),
      itemCount: _controller.allAchievements.length,
      separatorBuilder: (context, index) => const SizedBox(height: 12),
      itemBuilder: (context, index) {
        final achievement = _controller.allAchievements[index];
        final isCompleted = _controller.completedAchievements.contains(achievement);
        return _buildAchievementCard(achievement, isCompleted, isDark);
      },
    );
  }

  Widget _buildAchievementCard(
    AchievementConfig achievement,
    bool isCompleted,
    bool isDark,
  ) {
    final progress = _controller.getProgress(achievement);

    return Container(
      padding: const EdgeInsets.all(DesignTokens.space4),
      decoration: BoxDecoration(
        color: isCompleted
            ? (DesignTokens.green.withValues(alpha: isDark ? 0.1 : 0.05))
            : (isDark ? DarkDesignTokens.card : DesignTokens.card),
        borderRadius: DesignTokens.borderRadiusLg,
        border: Border.all(
          color: isCompleted
              ? DesignTokens.green.withValues(alpha: 0.3)
              : (isDark
                  ? DarkDesignTokens.glassBorder
                  : DesignTokens.text3.withValues(alpha: 0.08)),
        ),
        boxShadow: DesignTokens.shadowsSm,
      ),
      child: Row(
        children: [
          Container(
            width: 56,
            height: 56,
            decoration: BoxDecoration(
              color: isCompleted
                  ? DesignTokens.green.withValues(alpha: 0.2)
                  : (isDark
                      ? DarkDesignTokens.glass
                      : DesignTokens.text3.withValues(alpha: 0.06)),
              borderRadius: DesignTokens.borderRadiusMd,
            ),
            child: Center(
              child: Opacity(
                opacity: isCompleted ? 1.0 : 0.4,
                child: Text(
                  achievement.emoji,
                  style: const TextStyle(fontSize: 28),
                ),
              ),
            ),
          ),
          const SizedBox(width: DesignTokens.space4),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Row(
                  children: [
                    Text(
                      achievement.name,
                      style: TextStyle(
                        fontSize: DesignTokens.fontLg,
                        fontWeight: FontWeight.w600,
                        color: isCompleted
                            ? DesignTokens.green
                            : (isDark
                                ? DarkDesignTokens.text1
                                : DesignTokens.text1),
                      ),
                    ),
                    const SizedBox(width: 8),
                    if (isCompleted)
                      const Icon(
                        CupertinoIcons.checkmark_circle_fill,
                        size: 16,
                        color: Colors.green,
                      ),
                  ],
                ),
                const SizedBox(height: 4),
                Text(
                  achievement.description,
                  style: TextStyle(
                    fontSize: DesignTokens.fontSm,
                    color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2,
                  ),
                ),
                const SizedBox(height: 8),
                LinearProgressIndicator(
                  value: progress,
                  backgroundColor: isDark
                      ? DarkDesignTokens.glassBorder
                      : DesignTokens.text3.withValues(alpha: 0.1),
                  valueColor: AlwaysStoppedAnimation(
                    isCompleted ? DesignTokens.green : DesignTokens.dynamicPrimary,
                  ),
                ),
                const SizedBox(height: 4),
                Row(
                  children: [
                    Text(
                      '奖励:${achievement.rewardGold}💰 ${achievement.rewardExp}⭐',
                      style: TextStyle(
                        fontSize: DesignTokens.fontXs,
                        color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3,
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  List<AchievementConfig> get completedAchievements {
    return _controller.allAchievements
        .where((a) => _controller.completedAchievements.contains(a))
        .toList();
  }
}

Task 8: 路由配置和工具注册

目标: 在工具中心注册小妈菜园的入口

Files:

  • Modify: lib/src/config/app_routes.dart
  • Modify: lib/src/models/tool_item_model.dart

Step 1: 添加路由定义

Modify: lib/src/config/app_routes.dart

在常量定义区域添加:

static const String farmGame = '/farm-game';
static const String farmShop = '/farm-shop';
static const String farmInventory = '/farm-inventory';
static const String farmAchievement = '/farm-achievement';

pages 列表中添加:

GetPage(
  name: farmGame,
  page: () => const FarmGamePage(),
  middlewares: [PageStandardsMiddleware()],
),
GetPage(
  name: farmShop,
  page: () => const FarmShopPage(),
  middlewares: [PageStandardsMiddleware()],
),
GetPage(
  name: farmInventory,
  page: () => const FarmInventoryPage(),
  middlewares: [PageStandardsMiddleware()],
),
GetPage(
  name: farmAchievement,
  page: () => const FarmAchievementPage(),
  middlewares: [PageStandardsMiddleware()],
),

在文件顶部添加导入:

import 'package:mom_kitchen/src/pages/tools/farm/farm_game_page.dart';
import 'package:mom_kitchen/src/pages/tools/farm/farm_shop_page.dart';
import 'package:mom_kitchen/src/pages/tools/farm/farm_inventory_page.dart';
import 'package:mom_kitchen/src/pages/tools/farm/farm_achievement_page.dart';

Step 2: 在工具中心添加工具项

Modify: lib/src/models/tool_item_model.dart

ToolRegistry.defaultTools 列表末尾添加:

ToolItem(
  id: 'farm_game',
  name: '小妈菜园',
  icon: '🌾',
  needsNetwork: false,
  category: 'planning',
  route: '/farm-game',
  description: '种菜收菜,体验农场乐趣',
  waterfallSlot: WaterfallSlotConfig(
    show: true,
    priority: 10,
    badge: 'NEW',
  ),
),

Task 9: 初始化服务和数据

目标: 确保游戏数据服务在启动时正确初始化

Files:

  • Modify: lib/main.dart 或初始化入口文件

Step 1: 在应用启动时初始化农场数据

main() 函数或 AppService 初始化部分添加:

// 导入
import 'package:mom_kitchen/src/services/data/farm_data_service.dart';

// 在现有初始化逻辑中添加
await FarmDataService.instance.init();

Task 10: 测试和验证

目标: 验证所有功能正常工作

Step 1: 运行项目

flutter run

Step 2: 测试清单

  • 进入工具中心,确认"小妈菜园"入口显示
  • 点击进入游戏,确认页面正常渲染
  • 点击空地,测试播种功能
  • 等待生长或点击调试加速,测试生长阶段更新
  • 测试浇水功能
  • 作物成熟后测试收获功能
  • 确认金币和经验增加
  • 测试升级提示
  • 进入商店,测试购买种子
  • 进入背包,确认物品显示正确
  • 进入成就页面,确认成就进度显示
  • 测试调试功能(添加金币、加速作物)
  • 切换深色模式,确认 UI 适配
  • 退出应用后重新进入,确认数据持久化

Step 3: 空指针检测

flutter analyze

确保无警告和错误。


执行顺序

  1. Task 1-2: 数据模型和配置注册表(无依赖,可并行)
  2. Task 3: 数据服务层(依赖 Task 1
  3. Task 4: 控制器层(依赖 Task 2-3
  4. Task 5-7: UI 页面(依赖 Task 4
  5. Task 8-9: 路由和初始化(依赖 Task 5-7
  6. Task 10: 测试验证

总结

本计划包含 10 个主要任务涵盖数据层、业务层、UI 层和集成测试。按照任务顺序逐步执行,每个任务完成后应确保编译通过再进行下一个任务。

预计总文件数:

  • 新增文件:约 20 个
  • 修改文件:约 4 个
  • 总代码量:约 2500-3000 行

所有代码遵循项目现有的 iOS 26 Liquid Glass 设计规范和 GetX 状态管理模式。