Files
rely/lib/features/local/local_models.dart
T
2026-02-22 23:19:49 +01:00

1023 lines
31 KiB
Dart

// ignore_for_file: sort_constructors_first
import 'package:flutter/foundation.dart';
enum IdeaType { gift, event }
enum ReminderCadence { daily, weekly, monthly }
enum PreferenceSignalPolarity { like, dislike, neutral }
enum PreferenceSignalStatus { inferred, confirmed, dismissed }
@immutable
class PersonProfile {
const PersonProfile({
required this.id,
required this.name,
required this.relationship,
required this.affinityScore,
required this.nextMoment,
required this.tags,
required this.notes,
this.location,
});
final String id;
final String name;
final String relationship;
final int affinityScore;
final DateTime nextMoment;
final List<String> tags;
final String notes;
final String? location;
PersonProfile copyWith({
String? id,
String? name,
String? relationship,
int? affinityScore,
DateTime? nextMoment,
List<String>? tags,
String? notes,
String? location,
}) {
return PersonProfile(
id: id ?? this.id,
name: name ?? this.name,
relationship: relationship ?? this.relationship,
affinityScore: affinityScore ?? this.affinityScore,
nextMoment: nextMoment ?? this.nextMoment,
tags: tags ?? this.tags,
notes: notes ?? this.notes,
location: location ?? this.location,
);
}
Map<String, dynamic> toJson() {
return <String, dynamic>{
'id': id,
'name': name,
'relationship': relationship,
'affinityScore': affinityScore,
'nextMoment': nextMoment.toUtc().toIso8601String(),
'tags': tags,
'notes': notes,
'location': location,
};
}
factory PersonProfile.fromJson(Map<String, dynamic> json) {
return PersonProfile(
id: json['id'] as String,
name: json['name'] as String,
relationship: json['relationship'] as String,
affinityScore: (json['affinityScore'] as num).toInt(),
nextMoment: DateTime.parse(json['nextMoment'] as String).toLocal(),
tags: (json['tags'] as List<dynamic>? ?? <dynamic>[])
.map((dynamic tag) => '$tag')
.toList(growable: false),
notes: json['notes'] as String? ?? '',
location: json['location'] as String?,
);
}
}
@immutable
class RelationshipMoment {
const RelationshipMoment({
required this.id,
required this.personId,
required this.title,
required this.summary,
required this.at,
required this.type,
});
final String id;
final String personId;
final String title;
final String summary;
final DateTime at;
final String type;
RelationshipMoment copyWith({
String? id,
String? personId,
String? title,
String? summary,
DateTime? at,
String? type,
}) {
return RelationshipMoment(
id: id ?? this.id,
personId: personId ?? this.personId,
title: title ?? this.title,
summary: summary ?? this.summary,
at: at ?? this.at,
type: type ?? this.type,
);
}
Map<String, dynamic> toJson() {
return <String, dynamic>{
'id': id,
'personId': personId,
'title': title,
'summary': summary,
'at': at.toUtc().toIso8601String(),
'type': type,
};
}
factory RelationshipMoment.fromJson(Map<String, dynamic> json) {
return RelationshipMoment(
id: json['id'] as String,
personId: json['personId'] as String,
title: json['title'] as String,
summary: json['summary'] as String,
at: DateTime.parse(json['at'] as String).toLocal(),
type: json['type'] as String,
);
}
}
@immutable
class RelationshipIdea {
const RelationshipIdea({
required this.id,
required this.type,
required this.title,
required this.details,
required this.createdAt,
this.personId,
this.isArchived = false,
});
final String id;
final String? personId;
final IdeaType type;
final String title;
final String details;
final DateTime createdAt;
final bool isArchived;
RelationshipIdea copyWith({
String? id,
String? personId,
IdeaType? type,
String? title,
String? details,
DateTime? createdAt,
bool? isArchived,
}) {
return RelationshipIdea(
id: id ?? this.id,
personId: personId ?? this.personId,
type: type ?? this.type,
title: title ?? this.title,
details: details ?? this.details,
createdAt: createdAt ?? this.createdAt,
isArchived: isArchived ?? this.isArchived,
);
}
Map<String, dynamic> toJson() {
return <String, dynamic>{
'id': id,
'personId': personId,
'type': type.name,
'title': title,
'details': details,
'createdAt': createdAt.toUtc().toIso8601String(),
'isArchived': isArchived,
};
}
factory RelationshipIdea.fromJson(Map<String, dynamic> json) {
final String typeName = json['type'] as String? ?? IdeaType.gift.name;
return RelationshipIdea(
id: json['id'] as String,
personId: json['personId'] as String?,
type: IdeaType.values.firstWhere(
(IdeaType type) => type.name == typeName,
orElse: () => IdeaType.gift,
),
title: json['title'] as String,
details: json['details'] as String? ?? '',
createdAt: DateTime.parse(json['createdAt'] as String).toLocal(),
isArchived: json['isArchived'] as bool? ?? false,
);
}
}
@immutable
class ReminderRule {
const ReminderRule({
required this.id,
required this.title,
required this.cadence,
required this.nextAt,
this.personId,
this.enabled = true,
});
final String id;
final String? personId;
final String title;
final ReminderCadence cadence;
final DateTime nextAt;
final bool enabled;
ReminderRule copyWith({
String? id,
String? personId,
String? title,
ReminderCadence? cadence,
DateTime? nextAt,
bool? enabled,
}) {
return ReminderRule(
id: id ?? this.id,
personId: personId ?? this.personId,
title: title ?? this.title,
cadence: cadence ?? this.cadence,
nextAt: nextAt ?? this.nextAt,
enabled: enabled ?? this.enabled,
);
}
Map<String, dynamic> toJson() {
return <String, dynamic>{
'id': id,
'personId': personId,
'title': title,
'cadence': cadence.name,
'nextAt': nextAt.toUtc().toIso8601String(),
'enabled': enabled,
};
}
factory ReminderRule.fromJson(Map<String, dynamic> json) {
final String cadenceName =
json['cadence'] as String? ?? ReminderCadence.weekly.name;
return ReminderRule(
id: json['id'] as String,
personId: json['personId'] as String?,
title: json['title'] as String,
cadence: ReminderCadence.values.firstWhere(
(ReminderCadence cadence) => cadence.name == cadenceName,
orElse: () => ReminderCadence.weekly,
),
nextAt: DateTime.parse(json['nextAt'] as String).toLocal(),
enabled: json['enabled'] as bool? ?? true,
);
}
}
@immutable
class DashboardTask {
const DashboardTask({
required this.id,
required this.title,
required this.description,
required this.when,
this.done = false,
});
final String id;
final String title;
final String description;
final DateTime when;
final bool done;
DashboardTask copyWith({
String? id,
String? title,
String? description,
DateTime? when,
bool? done,
}) {
return DashboardTask(
id: id ?? this.id,
title: title ?? this.title,
description: description ?? this.description,
when: when ?? this.when,
done: done ?? this.done,
);
}
Map<String, dynamic> toJson() {
return <String, dynamic>{
'id': id,
'title': title,
'description': description,
'when': when.toUtc().toIso8601String(),
'done': done,
};
}
factory DashboardTask.fromJson(Map<String, dynamic> json) {
return DashboardTask(
id: json['id'] as String,
title: json['title'] as String,
description: json['description'] as String,
when: DateTime.parse(json['when'] as String).toLocal(),
done: json['done'] as bool? ?? false,
);
}
}
@immutable
class SourceProfileLink {
const SourceProfileLink({
required this.id,
required this.sourceApp,
required this.normalizedDisplayName,
required this.profileId,
required this.firstSeenAt,
required this.lastSeenAt,
this.sourceUserId,
this.sourceThreadId,
this.sourceFingerprint,
});
final String id;
final String sourceApp;
final String? sourceUserId;
final String? sourceThreadId;
final String? sourceFingerprint;
final String normalizedDisplayName;
final String profileId;
final DateTime firstSeenAt;
final DateTime lastSeenAt;
SourceProfileLink copyWith({
String? id,
String? sourceApp,
String? sourceUserId,
String? sourceThreadId,
String? sourceFingerprint,
String? normalizedDisplayName,
String? profileId,
DateTime? firstSeenAt,
DateTime? lastSeenAt,
}) {
return SourceProfileLink(
id: id ?? this.id,
sourceApp: sourceApp ?? this.sourceApp,
sourceUserId: sourceUserId ?? this.sourceUserId,
sourceThreadId: sourceThreadId ?? this.sourceThreadId,
sourceFingerprint: sourceFingerprint ?? this.sourceFingerprint,
normalizedDisplayName:
normalizedDisplayName ?? this.normalizedDisplayName,
profileId: profileId ?? this.profileId,
firstSeenAt: firstSeenAt ?? this.firstSeenAt,
lastSeenAt: lastSeenAt ?? this.lastSeenAt,
);
}
Map<String, dynamic> toJson() {
return <String, dynamic>{
'id': id,
'sourceApp': sourceApp,
'sourceUserId': sourceUserId,
'sourceThreadId': sourceThreadId,
'sourceFingerprint': sourceFingerprint,
'normalizedDisplayName': normalizedDisplayName,
'profileId': profileId,
'firstSeenAt': firstSeenAt.toUtc().toIso8601String(),
'lastSeenAt': lastSeenAt.toUtc().toIso8601String(),
};
}
factory SourceProfileLink.fromJson(Map<String, dynamic> json) {
return SourceProfileLink(
id: json['id'] as String,
sourceApp: json['sourceApp'] as String,
sourceUserId: json['sourceUserId'] as String?,
sourceThreadId: json['sourceThreadId'] as String?,
sourceFingerprint: json['sourceFingerprint'] as String?,
normalizedDisplayName: json['normalizedDisplayName'] as String? ?? '',
profileId: json['profileId'] as String,
firstSeenAt: DateTime.parse(json['firstSeenAt'] as String).toLocal(),
lastSeenAt: DateTime.parse(json['lastSeenAt'] as String).toLocal(),
);
}
}
@immutable
class SharedMessageEntry {
const SharedMessageEntry({
required this.id,
required this.sourceApp,
required this.profileId,
required this.messageText,
required this.sharedAt,
required this.importedAt,
required this.resolvedAutomatically,
this.sourceDisplayName,
this.sourceUserId,
this.sourceThreadId,
});
final String id;
final String sourceApp;
final String profileId;
final String messageText;
final DateTime sharedAt;
final DateTime importedAt;
final bool resolvedAutomatically;
final String? sourceDisplayName;
final String? sourceUserId;
final String? sourceThreadId;
SharedMessageEntry copyWith({
String? id,
String? sourceApp,
String? profileId,
String? messageText,
DateTime? sharedAt,
DateTime? importedAt,
bool? resolvedAutomatically,
String? sourceDisplayName,
String? sourceUserId,
String? sourceThreadId,
}) {
return SharedMessageEntry(
id: id ?? this.id,
sourceApp: sourceApp ?? this.sourceApp,
profileId: profileId ?? this.profileId,
messageText: messageText ?? this.messageText,
sharedAt: sharedAt ?? this.sharedAt,
importedAt: importedAt ?? this.importedAt,
resolvedAutomatically:
resolvedAutomatically ?? this.resolvedAutomatically,
sourceDisplayName: sourceDisplayName ?? this.sourceDisplayName,
sourceUserId: sourceUserId ?? this.sourceUserId,
sourceThreadId: sourceThreadId ?? this.sourceThreadId,
);
}
Map<String, dynamic> toJson() {
return <String, dynamic>{
'id': id,
'sourceApp': sourceApp,
'profileId': profileId,
'messageText': messageText,
'sharedAt': sharedAt.toUtc().toIso8601String(),
'importedAt': importedAt.toUtc().toIso8601String(),
'resolvedAutomatically': resolvedAutomatically,
'sourceDisplayName': sourceDisplayName,
'sourceUserId': sourceUserId,
'sourceThreadId': sourceThreadId,
};
}
factory SharedMessageEntry.fromJson(Map<String, dynamic> json) {
return SharedMessageEntry(
id: json['id'] as String,
sourceApp: json['sourceApp'] as String,
profileId: json['profileId'] as String,
messageText: json['messageText'] as String? ?? '',
sharedAt: DateTime.parse(json['sharedAt'] as String).toLocal(),
importedAt: DateTime.parse(json['importedAt'] as String).toLocal(),
resolvedAutomatically: json['resolvedAutomatically'] as bool? ?? true,
sourceDisplayName: json['sourceDisplayName'] as String?,
sourceUserId: json['sourceUserId'] as String?,
sourceThreadId: json['sourceThreadId'] as String?,
);
}
}
enum SharedInboxReason {
ambiguousProfileMatch,
nearProfileConflict,
missingIdentity,
}
@immutable
class SharedInboxEntry {
const SharedInboxEntry({
required this.id,
required this.sourceApp,
required this.messageText,
required this.sharedAt,
required this.receivedAt,
required this.reason,
required this.candidateProfileIds,
required this.normalizedDisplayName,
this.sourceFingerprint,
this.sourceDisplayName,
this.sourceUserId,
this.sourceThreadId,
});
final String id;
final String sourceApp;
final String messageText;
final DateTime sharedAt;
final DateTime receivedAt;
final SharedInboxReason reason;
final List<String> candidateProfileIds;
final String normalizedDisplayName;
final String? sourceFingerprint;
final String? sourceDisplayName;
final String? sourceUserId;
final String? sourceThreadId;
SharedInboxEntry copyWith({
String? id,
String? sourceApp,
String? messageText,
DateTime? sharedAt,
DateTime? receivedAt,
SharedInboxReason? reason,
List<String>? candidateProfileIds,
String? normalizedDisplayName,
String? sourceFingerprint,
String? sourceDisplayName,
String? sourceUserId,
String? sourceThreadId,
}) {
return SharedInboxEntry(
id: id ?? this.id,
sourceApp: sourceApp ?? this.sourceApp,
messageText: messageText ?? this.messageText,
sharedAt: sharedAt ?? this.sharedAt,
receivedAt: receivedAt ?? this.receivedAt,
reason: reason ?? this.reason,
candidateProfileIds: candidateProfileIds ?? this.candidateProfileIds,
normalizedDisplayName:
normalizedDisplayName ?? this.normalizedDisplayName,
sourceFingerprint: sourceFingerprint ?? this.sourceFingerprint,
sourceDisplayName: sourceDisplayName ?? this.sourceDisplayName,
sourceUserId: sourceUserId ?? this.sourceUserId,
sourceThreadId: sourceThreadId ?? this.sourceThreadId,
);
}
Map<String, dynamic> toJson() {
return <String, dynamic>{
'id': id,
'sourceApp': sourceApp,
'messageText': messageText,
'sharedAt': sharedAt.toUtc().toIso8601String(),
'receivedAt': receivedAt.toUtc().toIso8601String(),
'reason': reason.name,
'candidateProfileIds': candidateProfileIds,
'normalizedDisplayName': normalizedDisplayName,
'sourceFingerprint': sourceFingerprint,
'sourceDisplayName': sourceDisplayName,
'sourceUserId': sourceUserId,
'sourceThreadId': sourceThreadId,
};
}
factory SharedInboxEntry.fromJson(Map<String, dynamic> json) {
final String reasonName =
json['reason'] as String? ?? SharedInboxReason.missingIdentity.name;
return SharedInboxEntry(
id: json['id'] as String,
sourceApp: json['sourceApp'] as String,
messageText: json['messageText'] as String? ?? '',
sharedAt: DateTime.parse(json['sharedAt'] as String).toLocal(),
receivedAt: DateTime.parse(json['receivedAt'] as String).toLocal(),
reason: SharedInboxReason.values.firstWhere(
(SharedInboxReason item) => item.name == reasonName,
orElse: () => SharedInboxReason.missingIdentity,
),
candidateProfileIds:
(json['candidateProfileIds'] as List<dynamic>? ?? <dynamic>[])
.map((dynamic id) => '$id')
.toList(growable: false),
normalizedDisplayName: json['normalizedDisplayName'] as String? ?? '',
sourceFingerprint: json['sourceFingerprint'] as String?,
sourceDisplayName: json['sourceDisplayName'] as String?,
sourceUserId: json['sourceUserId'] as String?,
sourceThreadId: json['sourceThreadId'] as String?,
);
}
}
@immutable
class PersonPreferenceSignal {
const PersonPreferenceSignal({
required this.id,
required this.personId,
required this.key,
required this.label,
required this.category,
required this.polarity,
required this.confidence,
required this.status,
required this.firstSeenAt,
required this.lastSeenAt,
required this.occurrenceCount,
this.sourceApps = const <String>[],
this.evidenceMessageIds = const <String>[],
this.evidenceSnippets = const <String>[],
});
final String id;
final String personId;
final String key;
final String label;
final String category;
final PreferenceSignalPolarity polarity;
final double confidence;
final PreferenceSignalStatus status;
final DateTime firstSeenAt;
final DateTime lastSeenAt;
final int occurrenceCount;
final List<String> sourceApps;
final List<String> evidenceMessageIds;
final List<String> evidenceSnippets;
PersonPreferenceSignal copyWith({
String? id,
String? personId,
String? key,
String? label,
String? category,
PreferenceSignalPolarity? polarity,
double? confidence,
PreferenceSignalStatus? status,
DateTime? firstSeenAt,
DateTime? lastSeenAt,
int? occurrenceCount,
List<String>? sourceApps,
List<String>? evidenceMessageIds,
List<String>? evidenceSnippets,
}) {
return PersonPreferenceSignal(
id: id ?? this.id,
personId: personId ?? this.personId,
key: key ?? this.key,
label: label ?? this.label,
category: category ?? this.category,
polarity: polarity ?? this.polarity,
confidence: confidence ?? this.confidence,
status: status ?? this.status,
firstSeenAt: firstSeenAt ?? this.firstSeenAt,
lastSeenAt: lastSeenAt ?? this.lastSeenAt,
occurrenceCount: occurrenceCount ?? this.occurrenceCount,
sourceApps: sourceApps ?? this.sourceApps,
evidenceMessageIds: evidenceMessageIds ?? this.evidenceMessageIds,
evidenceSnippets: evidenceSnippets ?? this.evidenceSnippets,
);
}
Map<String, dynamic> toJson() {
return <String, dynamic>{
'id': id,
'personId': personId,
'key': key,
'label': label,
'category': category,
'polarity': polarity.name,
'confidence': confidence,
'status': status.name,
'firstSeenAt': firstSeenAt.toUtc().toIso8601String(),
'lastSeenAt': lastSeenAt.toUtc().toIso8601String(),
'occurrenceCount': occurrenceCount,
'sourceApps': sourceApps,
'evidenceMessageIds': evidenceMessageIds,
'evidenceSnippets': evidenceSnippets,
};
}
factory PersonPreferenceSignal.fromJson(Map<String, dynamic> json) {
final String polarityName =
json['polarity'] as String? ?? PreferenceSignalPolarity.neutral.name;
final String statusName =
json['status'] as String? ?? PreferenceSignalStatus.inferred.name;
return PersonPreferenceSignal(
id: json['id'] as String,
personId: json['personId'] as String,
key: json['key'] as String,
label: json['label'] as String? ?? '',
category: json['category'] as String? ?? 'general',
polarity: PreferenceSignalPolarity.values.firstWhere(
(PreferenceSignalPolarity item) => item.name == polarityName,
orElse: () => PreferenceSignalPolarity.neutral,
),
confidence: (json['confidence'] as num?)?.toDouble() ?? 0,
status: PreferenceSignalStatus.values.firstWhere(
(PreferenceSignalStatus item) => item.name == statusName,
orElse: () => PreferenceSignalStatus.inferred,
),
firstSeenAt: DateTime.parse(
json['firstSeenAt'] as String? ??
DateTime.now().toUtc().toIso8601String(),
).toLocal(),
lastSeenAt: DateTime.parse(
json['lastSeenAt'] as String? ??
DateTime.now().toUtc().toIso8601String(),
).toLocal(),
occurrenceCount: (json['occurrenceCount'] as num?)?.toInt() ?? 1,
sourceApps: (json['sourceApps'] as List<dynamic>? ?? <dynamic>[])
.map((dynamic item) => '$item')
.toList(growable: false),
evidenceMessageIds:
(json['evidenceMessageIds'] as List<dynamic>? ?? <dynamic>[])
.map((dynamic item) => '$item')
.toList(growable: false),
evidenceSnippets:
(json['evidenceSnippets'] as List<dynamic>? ?? <dynamic>[])
.map((dynamic item) => '$item')
.toList(growable: false),
);
}
}
@immutable
class DashboardSummary {
const DashboardSummary({
required this.activePeople,
required this.upcomingPlans,
required this.pendingIdeas,
required this.weeklyConsistency,
});
final int activePeople;
final int upcomingPlans;
final int pendingIdeas;
final int weeklyConsistency;
}
@immutable
class LocalDataState {
const LocalDataState({
required this.people,
required this.moments,
required this.ideas,
required this.reminders,
required this.tasks,
this.sourceLinks = const <SourceProfileLink>[],
this.sharedMessages = const <SharedMessageEntry>[],
this.sharedInbox = const <SharedInboxEntry>[],
this.preferenceSignals = const <PersonPreferenceSignal>[],
});
final List<PersonProfile> people;
final List<RelationshipMoment> moments;
final List<RelationshipIdea> ideas;
final List<ReminderRule> reminders;
final List<DashboardTask> tasks;
final List<SourceProfileLink> sourceLinks;
final List<SharedMessageEntry> sharedMessages;
final List<SharedInboxEntry> sharedInbox;
final List<PersonPreferenceSignal> preferenceSignals;
LocalDataState copyWith({
List<PersonProfile>? people,
List<RelationshipMoment>? moments,
List<RelationshipIdea>? ideas,
List<ReminderRule>? reminders,
List<DashboardTask>? tasks,
List<SourceProfileLink>? sourceLinks,
List<SharedMessageEntry>? sharedMessages,
List<SharedInboxEntry>? sharedInbox,
List<PersonPreferenceSignal>? preferenceSignals,
}) {
return LocalDataState(
people: people ?? this.people,
moments: moments ?? this.moments,
ideas: ideas ?? this.ideas,
reminders: reminders ?? this.reminders,
tasks: tasks ?? this.tasks,
sourceLinks: sourceLinks ?? this.sourceLinks,
sharedMessages: sharedMessages ?? this.sharedMessages,
sharedInbox: sharedInbox ?? this.sharedInbox,
preferenceSignals: preferenceSignals ?? this.preferenceSignals,
);
}
DashboardSummary summary({DateTime? now}) {
final DateTime baseline = now ?? DateTime.now();
return DashboardSummary(
activePeople: people.length,
upcomingPlans: people
.where((PersonProfile p) => p.nextMoment.isAfter(baseline))
.length,
pendingIdeas: ideas
.where((RelationshipIdea idea) => !idea.isArchived)
.length,
weeklyConsistency: moments.isEmpty
? 0
: (70 + moments.length * 4).clamp(0, 100),
);
}
Map<String, dynamic> toJson() {
return <String, dynamic>{
'people': people
.map((PersonProfile person) => person.toJson())
.toList(growable: false),
'moments': moments
.map((RelationshipMoment moment) => moment.toJson())
.toList(growable: false),
'ideas': ideas
.map((RelationshipIdea idea) => idea.toJson())
.toList(growable: false),
'reminders': reminders
.map((ReminderRule reminder) => reminder.toJson())
.toList(growable: false),
'tasks': tasks
.map((DashboardTask task) => task.toJson())
.toList(growable: false),
'sourceLinks': sourceLinks
.map((SourceProfileLink link) => link.toJson())
.toList(growable: false),
'sharedMessages': sharedMessages
.map((SharedMessageEntry entry) => entry.toJson())
.toList(growable: false),
'sharedInbox': sharedInbox
.map((SharedInboxEntry entry) => entry.toJson())
.toList(growable: false),
'preferenceSignals': preferenceSignals
.map((PersonPreferenceSignal signal) => signal.toJson())
.toList(growable: false),
};
}
factory LocalDataState.fromJson(Map<String, dynamic> json) {
return LocalDataState(
people: (json['people'] as List<dynamic>? ?? <dynamic>[])
.map(
(dynamic person) =>
PersonProfile.fromJson(person as Map<String, dynamic>),
)
.toList(growable: false),
moments: (json['moments'] as List<dynamic>? ?? <dynamic>[])
.map(
(dynamic moment) =>
RelationshipMoment.fromJson(moment as Map<String, dynamic>),
)
.toList(growable: false),
ideas: (json['ideas'] as List<dynamic>? ?? <dynamic>[])
.map(
(dynamic idea) =>
RelationshipIdea.fromJson(idea as Map<String, dynamic>),
)
.toList(growable: false),
reminders: (json['reminders'] as List<dynamic>? ?? <dynamic>[])
.map(
(dynamic reminder) =>
ReminderRule.fromJson(reminder as Map<String, dynamic>),
)
.toList(growable: false),
tasks: (json['tasks'] as List<dynamic>? ?? <dynamic>[])
.map(
(dynamic task) =>
DashboardTask.fromJson(task as Map<String, dynamic>),
)
.toList(growable: false),
sourceLinks: (json['sourceLinks'] as List<dynamic>? ?? <dynamic>[])
.map(
(dynamic link) =>
SourceProfileLink.fromJson(link as Map<String, dynamic>),
)
.toList(growable: false),
sharedMessages: (json['sharedMessages'] as List<dynamic>? ?? <dynamic>[])
.map(
(dynamic entry) =>
SharedMessageEntry.fromJson(entry as Map<String, dynamic>),
)
.toList(growable: false),
sharedInbox: (json['sharedInbox'] as List<dynamic>? ?? <dynamic>[])
.map(
(dynamic entry) =>
SharedInboxEntry.fromJson(entry as Map<String, dynamic>),
)
.toList(growable: false),
preferenceSignals:
(json['preferenceSignals'] as List<dynamic>? ?? <dynamic>[])
.map(
(dynamic signal) => PersonPreferenceSignal.fromJson(
signal as Map<String, dynamic>,
),
)
.toList(growable: false),
);
}
static LocalDataState seed() {
return LocalDataState(
people: <PersonProfile>[
PersonProfile(
id: 'p-ava',
name: 'Ava Hart',
relationship: 'Partner',
affinityScore: 92,
nextMoment: DateTime(2026, 2, 16, 19, 30),
tags: <String>['coffee', 'books', 'slow mornings'],
notes: 'Prefers thoughtful plans over expensive plans.',
location: 'Austin, TX',
),
PersonProfile(
id: 'p-jordan',
name: 'Jordan Lee',
relationship: 'Close Friend',
affinityScore: 78,
nextMoment: DateTime(2026, 2, 18, 18, 0),
tags: <String>['running', 'street food'],
notes: 'Has a race on Sunday; ask how training is going.',
location: 'Chicago, IL',
),
PersonProfile(
id: 'p-mila',
name: 'Mila Stone',
relationship: 'Sister',
affinityScore: 84,
nextMoment: DateTime(2026, 2, 20, 12, 0),
tags: <String>['plants', 'retro music'],
notes: 'Birthday prep should start this week.',
location: 'Seattle, WA',
),
],
moments: <RelationshipMoment>[
RelationshipMoment(
id: 'm-1',
personId: 'p-ava',
title: 'Sunday Walk Tradition',
summary: 'Short walk + no-phone hour worked really well.',
at: DateTime(2026, 2, 9, 9, 30),
type: 'ritual',
),
RelationshipMoment(
id: 'm-2',
personId: 'p-jordan',
title: 'Post-workout Check-in',
summary: 'Shared training playlist and grabbed smoothies.',
at: DateTime(2026, 2, 11, 19, 0),
type: 'support',
),
RelationshipMoment(
id: 'm-3',
personId: 'p-mila',
title: 'Gift Idea Captured',
summary: 'Found a vintage lamp from her saved style board.',
at: DateTime(2026, 2, 12, 14, 20),
type: 'gift',
),
],
ideas: <RelationshipIdea>[
RelationshipIdea(
id: 'i-1',
personId: 'p-ava',
type: IdeaType.gift,
title: 'Handwritten recipe journal',
details: 'Collect 10 memories and recipes from this year.',
createdAt: DateTime(2026, 2, 12, 9),
),
RelationshipIdea(
id: 'i-2',
personId: 'p-mila',
type: IdeaType.event,
title: 'Plant market + brunch date',
details: 'Saturday morning slot; book nearby cafe in advance.',
createdAt: DateTime(2026, 2, 13, 11),
),
],
reminders: <ReminderRule>[
ReminderRule(
id: 'r-1',
personId: 'p-jordan',
title: 'Weekly check-in message',
cadence: ReminderCadence.weekly,
nextAt: DateTime(2026, 2, 18, 18),
),
ReminderRule(
id: 'r-2',
personId: 'p-ava',
title: 'Sunday ritual planning',
cadence: ReminderCadence.weekly,
nextAt: DateTime(2026, 2, 16, 10),
),
],
tasks: <DashboardTask>[
DashboardTask(
id: 't-1',
title: 'Plan Friday dinner with Ava',
description: 'Keep it low-key: ramen + bookstore stop.',
when: DateTime(2026, 2, 16, 17, 0),
),
DashboardTask(
id: 't-2',
title: 'Send race-day encouragement to Jordan',
description: 'Voice note before 8:00 AM.',
when: DateTime(2026, 2, 17, 7, 30),
),
DashboardTask(
id: 't-3',
title: 'Finalize Mila birthday shortlist',
description: 'Pick 1 meaningful gift and 1 backup.',
when: DateTime(2026, 2, 18, 20, 0),
),
],
);
}
}