focus-flow/lib/src/models.dart

326 lines
7.9 KiB
Dart

import 'task_statistics.dart';
/// Scheduling behavior category.
enum TaskType {
/// Movable planned work.
flexible,
/// Required visible block that should not be moved automatically.
inflexible,
/// Required visible task that remains actionable if missed.
critical,
/// Hidden scheduling constraint, not a task card.
locked,
/// Unplanned completed/logged task.
surprise,
/// Intentional rest time.
freeSlot,
}
/// Current lifecycle state of a task.
enum TaskStatus {
/// Scheduled or queued work that has not started.
planned,
/// Work currently in progress.
active,
/// Work finished by the user.
completed,
/// Work that was not completed in its intended time.
missed,
/// Work intentionally removed from the plan.
cancelled,
/// Work intentionally dismissed because it no longer applies.
noLongerRelevant,
/// Unscheduled work kept for later planning.
backlog,
}
/// User-facing importance level.
enum PriorityLevel {
/// Lowest priority.
veryLow,
/// Low priority.
low,
/// Default middle priority.
medium,
/// High priority.
high,
/// Highest priority.
veryHigh,
}
/// Expected reward or payoff from completing a task.
enum RewardLevel {
/// No reward level has been captured; this is not equivalent to low reward.
notSet,
/// Very low expected reward.
veryLow,
/// Low expected reward.
low,
/// Medium expected reward.
medium,
/// High expected reward.
high,
/// Very high expected reward.
veryHigh,
}
/// Expected effort or activation difficulty for a task.
enum DifficultyLevel {
/// No difficulty has been captured yet.
notSet,
/// Very easy to start or complete.
veryEasy,
/// Easy to start or complete.
easy,
/// Medium effort.
medium,
/// Hard to start or complete.
hard,
/// Very hard to start or complete.
veryHard,
}
/// Reminder intensity preference.
enum ReminderProfile {
/// No reminder nudges.
silent,
/// Low-friction reminder nudges.
gentle,
/// Repeated reminder nudges.
persistent,
/// Strong reminder behavior for required items.
strict,
}
/// Lightweight backlog-only metadata.
enum BacklogTag {
/// Task is intentionally saved as a someday/wishlist item.
wishlist,
}
/// Starter task model for the scheduling core.
///
/// This is a public placeholder API for early V1 chunks. Keep behavior changes
/// explicit and covered by tests as the model becomes more complete.
class Task {
const Task({
required this.id,
required this.title,
required this.projectId,
required this.type,
required this.status,
required this.createdAt,
required this.updatedAt,
this.priority,
this.reward = RewardLevel.notSet,
this.difficulty = DifficultyLevel.notSet,
this.durationMinutes,
this.scheduledStart,
this.scheduledEnd,
this.parentTaskId,
this.backlogTags = const <BacklogTag>{},
this.stats = const TaskStatistics(),
});
/// Create a minimal captured task without requiring planning details.
factory Task.quickCapture({
required String id,
required String title,
required DateTime createdAt,
String projectId = 'inbox',
TaskType type = TaskType.flexible,
TaskStatus status = TaskStatus.backlog,
PriorityLevel priority = PriorityLevel.medium,
RewardLevel reward = RewardLevel.notSet,
DifficultyLevel difficulty = DifficultyLevel.notSet,
Set<BacklogTag> backlogTags = const <BacklogTag>{},
DateTime? updatedAt,
}) {
if (title.trim().isEmpty) {
throw ArgumentError.value(title, 'title', 'Title is required.');
}
return Task(
id: id,
title: title.trim(),
projectId: projectId,
type: type,
status: status,
priority: priority,
reward: reward,
difficulty: difficulty,
createdAt: createdAt,
updatedAt: updatedAt ?? createdAt,
backlogTags: backlogTags,
);
}
final String id;
final String title;
final String projectId;
final TaskType type;
final TaskStatus status;
final PriorityLevel? priority;
final RewardLevel reward;
final DifficultyLevel difficulty;
final int? durationMinutes;
final DateTime? scheduledStart;
final DateTime? scheduledEnd;
final String? parentTaskId;
final Set<BacklogTag> backlogTags;
final DateTime createdAt;
final DateTime updatedAt;
final TaskStatistics stats;
bool get isFlexible => type == TaskType.flexible;
bool get isRequiredVisible =>
type == TaskType.critical || type == TaskType.inflexible;
bool get isLocked => type == TaskType.locked;
bool get isBacklog => status == TaskStatus.backlog;
Task copyWith({
String? id,
String? title,
String? projectId,
TaskType? type,
TaskStatus? status,
PriorityLevel? priority,
RewardLevel? reward,
DifficultyLevel? difficulty,
int? durationMinutes,
DateTime? scheduledStart,
DateTime? scheduledEnd,
String? parentTaskId,
Set<BacklogTag>? backlogTags,
DateTime? createdAt,
DateTime? updatedAt,
TaskStatistics? stats,
bool clearSchedule = false,
}) {
return Task(
id: id ?? this.id,
title: title ?? this.title,
projectId: projectId ?? this.projectId,
type: type ?? this.type,
status: status ?? this.status,
priority: priority ?? this.priority,
reward: reward ?? this.reward,
difficulty: difficulty ?? this.difficulty,
durationMinutes: durationMinutes ?? this.durationMinutes,
scheduledStart:
clearSchedule ? null : (scheduledStart ?? this.scheduledStart),
scheduledEnd: clearSchedule ? null : (scheduledEnd ?? this.scheduledEnd),
parentTaskId: parentTaskId ?? this.parentTaskId,
backlogTags: backlogTags ?? this.backlogTags,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
stats: stats ?? this.stats,
);
}
}
/// Starter project defaults used when creating or scheduling tasks.
class ProjectProfile {
const ProjectProfile({
required this.id,
required this.name,
required this.colorKey,
this.defaultPriority = PriorityLevel.medium,
this.defaultReward = RewardLevel.notSet,
this.defaultDifficulty = DifficultyLevel.notSet,
this.defaultReminderProfile = ReminderProfile.gentle,
this.defaultDurationMinutes,
});
final String id;
final String name;
final String colorKey;
final PriorityLevel defaultPriority;
final RewardLevel defaultReward;
final DifficultyLevel defaultDifficulty;
final ReminderProfile defaultReminderProfile;
final int? defaultDurationMinutes;
/// Create a task using project defaults while allowing explicit overrides.
Task createTask({
required String id,
required String title,
required DateTime createdAt,
TaskType type = TaskType.flexible,
TaskStatus status = TaskStatus.backlog,
PriorityLevel? priority,
RewardLevel? reward,
DifficultyLevel? difficulty,
int? durationMinutes,
DateTime? scheduledStart,
DateTime? scheduledEnd,
String? parentTaskId,
Set<BacklogTag> backlogTags = const <BacklogTag>{},
DateTime? updatedAt,
}) {
return Task(
id: id,
title: title,
projectId: this.id,
type: type,
status: status,
priority: priority ?? defaultPriority,
reward: reward ?? defaultReward,
difficulty: difficulty ?? defaultDifficulty,
durationMinutes: durationMinutes ?? defaultDurationMinutes,
scheduledStart: scheduledStart,
scheduledEnd: scheduledEnd,
parentTaskId: parentTaskId,
backlogTags: backlogTags,
createdAt: createdAt,
updatedAt: updatedAt ?? createdAt,
);
}
}
/// Starter time range value used by scheduling helpers.
class TimeInterval {
const TimeInterval({
required this.start,
required this.end,
this.label,
});
final DateTime start;
final DateTime end;
final String? label;
Duration get duration => end.difference(start);
bool overlaps(TimeInterval other) {
return start.isBefore(other.end) && end.isAfter(other.start);
}
}