- 清理大量废弃的 barrel 导出文件,移除冗余的中间导出层 - 修复所有相对路径导入错误,统一调整为扁平化模块引用 - 更新多平台 pubspec 版本号与依赖库版本 - 补充后端功能问题管理后台与脚本工具 - 调整部分页面的快捷方式文案适配新功能 - 更新部分翻译覆盖率与API文档
145 lines
4.3 KiB
Dart
145 lines
4.3 KiB
Dart
/// ============================================================
|
|
/// 闲言APP — 学习计划服务
|
|
/// 创建时间: 2026-05-02
|
|
/// 更新时间: 2026-06-12
|
|
/// 作用: 学习计划 CRUD + 进度计算 + 连续天数
|
|
/// 上次更新: 从 services/ 子目录移至 study_plan 模块根目录
|
|
/// ============================================================
|
|
|
|
import 'package:drift/drift.dart' show Value;
|
|
import '../../../core/storage/database/app_database.dart';
|
|
import '../../../core/utils/logger.dart';
|
|
import 'study_plan_models.dart';
|
|
|
|
class StudyPlanService {
|
|
StudyPlanService._();
|
|
|
|
static final _db = AppDatabase.instance;
|
|
|
|
static Future<int> createPlan({
|
|
required String title,
|
|
required PlanCategory category,
|
|
required int dailyGoal,
|
|
String description = '',
|
|
}) async {
|
|
try {
|
|
final now = DateTime.now();
|
|
final id = await _db.insertLearningPlan(
|
|
LearningPlansCompanion.insert(
|
|
title: title,
|
|
description: Value(description),
|
|
category: Value(category.name),
|
|
dailyGoal: Value(dailyGoal),
|
|
startDate: now,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
),
|
|
);
|
|
Log.i('学习计划: 创建成功 id=$id title=$title');
|
|
return id;
|
|
} catch (e) {
|
|
Log.e('学习计划: 创建失败', e);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static Future<List<LearningPlan>> getActivePlans() async {
|
|
try {
|
|
return _db.getActiveLearningPlans();
|
|
} catch (e) {
|
|
Log.e('学习计划: 获取活跃计划失败', e);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
static Future<List<LearningPlan>> getAllPlans() async {
|
|
try {
|
|
return _db.getAllLearningPlans();
|
|
} catch (e) {
|
|
Log.e('学习计划: 获取全部计划失败', e);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
static Future<void> completeDailyGoal(int planId, {String? contentId, String? title, String? contentType, int durationSeconds = 0}) async {
|
|
try {
|
|
await _db.insertLearningRecord(
|
|
LearningRecordsCompanion.insert(
|
|
planId: planId,
|
|
contentId: Value(contentId ?? ''),
|
|
contentType: Value(contentType ?? 'poetry'),
|
|
title: Value(title ?? ''),
|
|
durationSeconds: Value(durationSeconds),
|
|
completedAt: DateTime.now(),
|
|
),
|
|
);
|
|
|
|
final plan = await _db.getLearningPlan(planId);
|
|
if (plan != null) {
|
|
final todayRecords = await _db.getTodayLearningRecords(planId: planId);
|
|
final newStreak = _calculateNewStreak(plan, todayRecords.length);
|
|
final newTotal = plan.totalCompleted + 1;
|
|
|
|
await _db.updateLearningPlan(
|
|
LearningPlansCompanion(
|
|
id: Value(planId),
|
|
streakDays: Value(newStreak),
|
|
totalCompleted: Value(newTotal),
|
|
updatedAt: Value(DateTime.now()),
|
|
),
|
|
);
|
|
}
|
|
|
|
Log.i('学习计划: 完成任务 planId=$planId');
|
|
} catch (e) {
|
|
Log.e('学习计划: 完成任务失败', e);
|
|
}
|
|
}
|
|
|
|
static Future<void> togglePlanActive(int planId, bool isActive) async {
|
|
try {
|
|
await _db.updateLearningPlan(
|
|
LearningPlansCompanion(
|
|
id: Value(planId),
|
|
isActive: Value(isActive),
|
|
updatedAt: Value(DateTime.now()),
|
|
),
|
|
);
|
|
Log.i('学习计划: 切换状态 planId=$planId isActive=$isActive');
|
|
} catch (e) {
|
|
Log.e('学习计划: 切换状态失败', e);
|
|
}
|
|
}
|
|
|
|
static Future<void> deletePlan(int planId) async {
|
|
try {
|
|
await _db.deleteLearningRecordsByPlan(planId);
|
|
await _db.deleteLearningPlan(planId);
|
|
Log.i('学习计划: 删除成功 planId=$planId');
|
|
} catch (e) {
|
|
Log.e('学习计划: 删除失败', e);
|
|
}
|
|
}
|
|
|
|
static Future<int> getTodayCompletedCount(int planId) async {
|
|
final records = await _db.getTodayLearningRecords(planId: planId);
|
|
return records.length;
|
|
}
|
|
|
|
static Future<double> getTodayProgress(int planId, int dailyGoal) async {
|
|
final count = await getTodayCompletedCount(planId);
|
|
return (count / dailyGoal).clamp(0.0, 1.0);
|
|
}
|
|
|
|
static Future<List<LearningRecord>> getPlanRecords(int planId, {int limit = 50}) async {
|
|
return _db.getLearningRecords(planId, limit: limit);
|
|
}
|
|
|
|
static int _calculateNewStreak(LearningPlan plan, int todayCount) {
|
|
if (todayCount >= plan.dailyGoal) {
|
|
return plan.streakDays + 1;
|
|
}
|
|
return plan.streakDays;
|
|
}
|
|
}
|