import 'dart:collection'; import 'package:flutter/material.dart'; import 'package:habo/constants.dart'; import 'package:habo/model/category.dart'; class HabitData { int? id; int position; String title; bool twoDayRule; String cue; String routine; String reward; bool showReward; bool advanced; bool notification; TimeOfDay notTime; String sanction; bool showSanction; String accountant; HabitType habitType; double targetValue; double partialValue; String unit; bool archived; SplayTreeMap events; List categories; // Runtime computed fields int streak; bool streakVisible; bool orangeStreak; HabitData({ this.id, this.position = 0, this.title = '', this.twoDayRule = false, this.cue = '', this.routine = '', this.reward = '', this.showReward = false, this.advanced = false, this.notification = false, this.notTime = const TimeOfDay(hour: 20, minute: 0), this.sanction = '', this.showSanction = false, this.accountant = '', this.habitType = HabitType.boolean, this.targetValue = 100.0, this.partialValue = 10.0, this.unit = '', this.archived = false, SplayTreeMap? events, this.categories = const [], this.streak = 0, this.streakVisible = false, this.orangeStreak = false, }) : events = events ?? SplayTreeMap(); bool isCompletedForDate(DateTime date) { final key = DateTime(date.year, date.month, date.day); final event = events[key]; if (event == null) return false; final dayType = event[0] as DayType; if (dayType == DayType.check) return true; if (dayType == DayType.progress && event.length >= 4) { final progressValue = event[2] as double? ?? 0.0; final target = event[3] as double? ?? targetValue; return progressValue >= target; } return false; } double getProgressForDate(DateTime date) { final key = DateTime(date.year, date.month, date.day); final event = events[key]; if (event == null || event.length < 3) return 0.0; return event[2] as double? ?? 0.0; } double getProgressPercentage(DateTime date) { final key = DateTime(date.year, date.month, date.day); final event = events[key]; if (event == null || event.length < 4) return 0.0; final progressValue = event[2] as double? ?? 0.0; final target = event[3] as double? ?? targetValue; if (target == 0) return 0.0; return (progressValue / target).clamp(0.0, 1.0); } Map toJson() { final eventsJson = {}; for (final entry in events.entries) { eventsJson[entry.key.toIso8601String()] = [ entry.value[0] is DayType ? (entry.value[0] as DayType).index : entry.value[0], if (entry.value.length > 1) entry.value[1] else '', if (entry.value.length > 2) entry.value[2], if (entry.value.length > 3) entry.value[3], ]; } return { 'id': id, 'position': position, 'title': title, 'twoDayRule': twoDayRule, 'cue': cue, 'routine': routine, 'reward': reward, 'showReward': showReward, 'advanced': advanced, 'notification': notification, 'notTime': {'hour': notTime.hour, 'minute': notTime.minute}, 'sanction': sanction, 'showSanction': showSanction, 'accountant': accountant, 'habitType': habitType.index, 'targetValue': targetValue, 'partialValue': partialValue, 'unit': unit, 'archived': archived, 'events': eventsJson, }; } factory HabitData.fromJson(Map json) { final eventsJson = json['events'] as Map? ?? {}; final events = SplayTreeMap(); for (final entry in eventsJson.entries) { try { final date = DateTime.parse(entry.key); final value = entry.value as List; final dayType = value[0] is int ? DayType.values[value[0] as int] : value[0]; final event = [ dayType, if (value.length > 1) value[1] else '', if (value.length > 2) value[2] else 0.0, if (value.length > 3) value[3] else 0.0, ]; events[date] = event; } catch (_) {} } final notTimeJson = json['notTime']; TimeOfDay notTime; if (notTimeJson is Map) { notTime = TimeOfDay( hour: notTimeJson['hour'] as int? ?? 20, minute: notTimeJson['minute'] as int? ?? 0, ); } else { notTime = const TimeOfDay(hour: 20, minute: 0); } return HabitData( id: json['id'] as int?, position: json['position'] as int? ?? 0, title: json['title'] as String? ?? '', twoDayRule: json['twoDayRule'] as bool? ?? false, cue: json['cue'] as String? ?? '', routine: json['routine'] as String? ?? '', reward: json['reward'] as String? ?? '', showReward: json['showReward'] as bool? ?? false, advanced: json['advanced'] as bool? ?? false, notification: json['notification'] as bool? ?? false, notTime: notTime, sanction: json['sanction'] as String? ?? '', showSanction: json['showSanction'] as bool? ?? false, accountant: json['accountant'] as String? ?? '', habitType: HabitType.values[json['habitType'] as int? ?? 0], targetValue: (json['targetValue'] as num?)?.toDouble() ?? 100.0, partialValue: (json['partialValue'] as num?)?.toDouble() ?? 10.0, unit: json['unit'] as String? ?? '', archived: json['archived'] as bool? ?? false, events: events, ); } }