import '../../internal/application/CommitmentClass.dart'; import '../../internal/application/CommitmentStatus.dart'; import '../../internal/application/CommitmentType.dart'; import '../../internal/application/EnergyCost.dart'; import '../../internal/application/ProofType.dart'; /// Something the user said they would do. A due *window*, not a due date — /// "Mon 06:00-08:00" beats "Monday", because a window can actually close. class Commitment { String? id; CommitmentType type; CommitmentClass commitmentClass; String title; String category; DateTime? dueStart; DateTime? dueEnd; int estMinutes; EnergyCost energy; ProofType proofType; /// Minimum foreground minutes when proofType is Timer. int proofTimerMinutes; String rrule; String? parentId; CommitmentStatus status; int deferralCount; Commitment({ this.id, this.type = CommitmentType.TASK, this.commitmentClass = CommitmentClass.Standard, this.title = "", this.category = "", this.dueStart, this.dueEnd, this.estMinutes = 0, this.energy = EnergyCost.Medium, this.proofType = ProofType.Honour, this.proofTimerMinutes = 0, this.rrule = "", this.parentId, this.status = CommitmentStatus.Open, this.deferralCount = 0, }); factory Commitment.fromJson(Map json) { return Commitment( id: json['id'], type: getCommitmentType(json['type']), commitmentClass: getCommitmentClass(json['commitmentClass']), title: json['title'] ?? "", category: json['category'] ?? "", dueStart: DateTime.tryParse(json['dueStart'] ?? ""), dueEnd: DateTime.tryParse(json['dueEnd'] ?? ""), estMinutes: json['estMinutes'] ?? 0, energy: getEnergyCost(json['energy']), proofType: getProofType(json['proofType']), proofTimerMinutes: json['proofTimerMinutes'] ?? 0, rrule: json['rrule'] ?? "", parentId: json['parentId'], status: getCommitmentStatus(json['status']), deferralCount: json['deferralCount'] ?? 0, ); } Map toJson() { final Map data = {}; data['id'] = id; data['type'] = type.name; data['commitmentClass'] = commitmentClass.name; data['title'] = title; data['category'] = category; data['dueStart'] = dueStart?.toIso8601String(); data['dueEnd'] = dueEnd?.toIso8601String(); data['estMinutes'] = estMinutes; data['energy'] = energy.name; data['proofType'] = proofType.name; data['proofTimerMinutes'] = proofTimerMinutes; data['rrule'] = rrule; data['parentId'] = parentId; data['status'] = status.name; data['deferralCount'] = deferralCount; return data; } /// The window has closed. A commitment goes Overdue at close — it never /// silently rolls over to today. bool get windowClosed { if (dueEnd == null) { return false; } return DateTime.now().isAfter(dueEnd!); } /// Whole days past the close of the window; 0 while still open. int get daysOverdue { if (dueEnd == null || !windowClosed) { return 0; } return DateTime.now().difference(dueEnd!).inDays; } /// Whether completing right now would count as a late complete rather than a /// clean one. Late is recorded distinctly and never reduces debt to zero. bool get wouldBeLate { return windowClosed; } /// Non-negotiables are never deferrable, at any count. bool deferrableUnder(int maxDeferrals) { if (commitmentClass == CommitmentClass.NonNegotiable) { return false; } return deferralCount < maxDeferrals; } }