feat: add table_calendar package and CalendarEvent model with tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
class CalendarEvent {
|
||||
final int id;
|
||||
final String title;
|
||||
final DateTime startDt;
|
||||
final DateTime? endDt;
|
||||
final bool allDay;
|
||||
final String description;
|
||||
final String location;
|
||||
final String color;
|
||||
final String? recurrence;
|
||||
final int? projectId;
|
||||
final int? reminderMinutes;
|
||||
|
||||
const CalendarEvent({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.startDt,
|
||||
this.endDt,
|
||||
required this.allDay,
|
||||
required this.description,
|
||||
required this.location,
|
||||
required this.color,
|
||||
this.recurrence,
|
||||
this.projectId,
|
||||
this.reminderMinutes,
|
||||
});
|
||||
|
||||
factory CalendarEvent.fromJson(Map<String, dynamic> json) => CalendarEvent(
|
||||
id: json['id'] as int,
|
||||
title: json['title'] as String? ?? '',
|
||||
startDt: DateTime.parse(json['start_dt'] as String),
|
||||
endDt: json['end_dt'] != null
|
||||
? DateTime.parse(json['end_dt'] as String)
|
||||
: null,
|
||||
allDay: json['all_day'] as bool? ?? false,
|
||||
description: json['description'] as String? ?? '',
|
||||
location: json['location'] as String? ?? '',
|
||||
color: json['color'] as String? ?? '',
|
||||
recurrence: json['recurrence'] as String?,
|
||||
projectId: json['project_id'] as int?,
|
||||
reminderMinutes: json['reminder_minutes'] as int?,
|
||||
);
|
||||
}
|
||||
|
||||
/// Strips time from a DateTime, returning midnight local.
|
||||
/// Used as map keys in CalendarState.eventsByDay.
|
||||
DateTime dateOnly(DateTime dt) => DateTime(dt.year, dt.month, dt.day);
|
||||
@@ -408,6 +408,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.8.0"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.20.2"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -952,6 +960,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
simple_gesture_detector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: simple_gesture_detector
|
||||
sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@@ -1021,6 +1037,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.0"
|
||||
table_calendar:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: table_calendar
|
||||
sha256: "0c0c6219878b363a2d5f40c7afb159d845f253d061dc3c822aa0d5fe0f721982"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -30,6 +30,7 @@ dependencies:
|
||||
url_launcher: ^6.3.1
|
||||
record: ^5.0.0
|
||||
just_audio: ^0.9.39
|
||||
table_calendar: ^3.1.2
|
||||
|
||||
dependency_overrides:
|
||||
record_linux: ^1.3.0
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:fabled_app/data/models/calendar_event.dart';
|
||||
import 'package:fabled_app/data/api/voice_api.dart';
|
||||
import 'package:fabled_app/providers/voice_provider.dart';
|
||||
import 'package:fabled_app/data/models/knowledge_item.dart';
|
||||
@@ -190,6 +191,65 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('CalendarEvent.fromJson', () {
|
||||
test('parses all fields', () {
|
||||
final json = {
|
||||
'id': 10,
|
||||
'title': 'Team meeting',
|
||||
'start_dt': '2026-04-07T09:00:00+00:00',
|
||||
'end_dt': '2026-04-07T10:00:00+00:00',
|
||||
'all_day': false,
|
||||
'description': 'Weekly sync',
|
||||
'location': 'Room 4',
|
||||
'color': '#6366F1',
|
||||
'recurrence': 'FREQ=WEEKLY',
|
||||
'project_id': 3,
|
||||
'reminder_minutes': 15,
|
||||
};
|
||||
final event = CalendarEvent.fromJson(json);
|
||||
expect(event.id, equals(10));
|
||||
expect(event.title, equals('Team meeting'));
|
||||
expect(event.startDt, equals(DateTime.parse('2026-04-07T09:00:00+00:00')));
|
||||
expect(event.endDt, equals(DateTime.parse('2026-04-07T10:00:00+00:00')));
|
||||
expect(event.allDay, isFalse);
|
||||
expect(event.description, equals('Weekly sync'));
|
||||
expect(event.location, equals('Room 4'));
|
||||
expect(event.color, equals('#6366F1'));
|
||||
expect(event.recurrence, equals('FREQ=WEEKLY'));
|
||||
expect(event.projectId, equals(3));
|
||||
expect(event.reminderMinutes, equals(15));
|
||||
});
|
||||
|
||||
test('handles null optional fields', () {
|
||||
final json = {
|
||||
'id': 11,
|
||||
'title': 'Birthday',
|
||||
'start_dt': '2026-05-01T00:00:00+00:00',
|
||||
'end_dt': null,
|
||||
'all_day': true,
|
||||
'description': '',
|
||||
'location': '',
|
||||
'color': '',
|
||||
'recurrence': null,
|
||||
'project_id': null,
|
||||
'reminder_minutes': null,
|
||||
};
|
||||
final event = CalendarEvent.fromJson(json);
|
||||
expect(event.endDt, isNull);
|
||||
expect(event.allDay, isTrue);
|
||||
expect(event.recurrence, isNull);
|
||||
expect(event.projectId, isNull);
|
||||
expect(event.reminderMinutes, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('dateOnly', () {
|
||||
test('strips time from datetime', () {
|
||||
final dt = DateTime(2026, 4, 7, 14, 30, 45);
|
||||
expect(dateOnly(dt), equals(DateTime(2026, 4, 7)));
|
||||
});
|
||||
});
|
||||
|
||||
group('BriefingFeed.fromJson', () {
|
||||
test('parses all fields', () {
|
||||
final json = {
|
||||
|
||||
Reference in New Issue
Block a user