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"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.8.0"
|
version: "4.8.0"
|
||||||
|
intl:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: intl
|
||||||
|
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.20.2"
|
||||||
io:
|
io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -952,6 +960,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
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:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -1021,6 +1037,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.0"
|
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:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ dependencies:
|
|||||||
url_launcher: ^6.3.1
|
url_launcher: ^6.3.1
|
||||||
record: ^5.0.0
|
record: ^5.0.0
|
||||||
just_audio: ^0.9.39
|
just_audio: ^0.9.39
|
||||||
|
table_calendar: ^3.1.2
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
record_linux: ^1.3.0
|
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/data/api/voice_api.dart';
|
||||||
import 'package:fabled_app/providers/voice_provider.dart';
|
import 'package:fabled_app/providers/voice_provider.dart';
|
||||||
import 'package:fabled_app/data/models/knowledge_item.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', () {
|
group('BriefingFeed.fromJson', () {
|
||||||
test('parses all fields', () {
|
test('parses all fields', () {
|
||||||
final json = {
|
final json = {
|
||||||
|
|||||||
Reference in New Issue
Block a user