86 KiB
小妈菜园 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、AnimationService),GetX 管理状态,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
确保无警告和错误。
执行顺序
- Task 1-2: 数据模型和配置注册表(无依赖,可并行)
- Task 3: 数据服务层(依赖 Task 1)
- Task 4: 控制器层(依赖 Task 2-3)
- Task 5-7: UI 页面(依赖 Task 4)
- Task 8-9: 路由和初始化(依赖 Task 5-7)
- Task 10: 测试验证
总结
本计划包含 10 个主要任务,涵盖数据层、业务层、UI 层和集成测试。按照任务顺序逐步执行,每个任务完成后应确保编译通过再进行下一个任务。
预计总文件数:
- 新增文件:约 20 个
- 修改文件:约 4 个
- 总代码量:约 2500-3000 行
所有代码遵循项目现有的 iOS 26 Liquid Glass 设计规范和 GetX 状态管理模式。