feat: add local-first private AI digest workflow

Migrate app code into canonical feature slices, add phone-only AI digest scheduling and review, wire local notification/background task support, and cover the flow with tests.
This commit is contained in:
Rijad Zuzo
2026-05-17 00:17:20 +02:00
parent dab50abf0e
commit f655adfbea
212 changed files with 24178 additions and 15895 deletions
+4
View File
@@ -0,0 +1,4 @@
# Ideas Domain
Gift ideas, event ideas, and related idea models live here. Keep the models
simple so future recommendation logic can consume them without UI coupling.
@@ -0,0 +1,76 @@
// ignore_for_file: sort_constructors_first
import 'package:flutter/foundation.dart';
/// Higher-level bucket for plan and gift suggestions.
enum IdeaType { gift, event }
/// Saved idea for a person or for the broader relationship backlog.
@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,
);
}
}