From de5bc0b27c2fb48c20464b02d2a6cc593164d94c Mon Sep 17 00:00:00 2001 From: Joey Boerwinkel Date: Tue, 2 Jul 2024 15:00:41 +0200 Subject: [PATCH] feat(flutter_availability_data_interface): add serialization to template data --- .../lib/src/models/availability.dart | 28 +++++ .../lib/src/models/templates.dart | 105 ++++++++++++++++++ .../test/models_templates_test.dart | 64 +++++++++++ 3 files changed, 197 insertions(+) create mode 100644 packages/flutter_availability_data_interface/test/models_templates_test.dart diff --git a/packages/flutter_availability_data_interface/lib/src/models/availability.dart b/packages/flutter_availability_data_interface/lib/src/models/availability.dart index 120503c..4f252bd 100644 --- a/packages/flutter_availability_data_interface/lib/src/models/availability.dart +++ b/packages/flutter_availability_data_interface/lib/src/models/availability.dart @@ -1,3 +1,4 @@ +// ignore_for_file: Generated using data class generator /// A model defining the data structure for an availability class AvailabilityModel { /// Creates a new availability @@ -55,6 +56,22 @@ class AvailabilityBreakModel { Duration? duration, }) : _duration = duration; + /// Parses a break from a map. + /// + /// This function is primarily used in the storing of template blobs. For each + /// variant of the service it is recommended to implement your own + /// serialization layer. + factory AvailabilityBreakModel.fromMap(Map map) => + AvailabilityBreakModel( + startTime: + DateTime.fromMillisecondsSinceEpoch((map["startTime"] ?? 0) as int), + endTime: + DateTime.fromMillisecondsSinceEpoch((map["endTime"] ?? 0) as int), + duration: map["duration"] != null + ? Duration(minutes: map["duration"] as int) + : null, + ); + /// The start time for this break /// /// If duration is not the same as the difference between [startTime] and @@ -102,4 +119,15 @@ class AvailabilityBreakModel { endTime: endTime ?? this.endTime, duration: duration ?? _duration, ); + + /// Returns a map variant of this object. + /// + /// This is mainly for serialization of template data. Serialization of this + /// object for persistance in your own implementation is recommended to be + /// done in a separate serialization layer. + Map toMap() => { + "startTime": startTime.millisecondsSinceEpoch, + "endTime": endTime.millisecondsSinceEpoch, + "duration": _duration?.inMinutes, + }; } diff --git a/packages/flutter_availability_data_interface/lib/src/models/templates.dart b/packages/flutter_availability_data_interface/lib/src/models/templates.dart index 4393454..ee22306 100644 --- a/packages/flutter_availability_data_interface/lib/src/models/templates.dart +++ b/packages/flutter_availability_data_interface/lib/src/models/templates.dart @@ -1,3 +1,4 @@ +import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart"; import "package:flutter_availability_data_interface/src/models/availability.dart"; /// A limited set of different availability template types @@ -22,6 +23,26 @@ class AvailabilityTemplateModel { this.id, }); + factory AvailabilityTemplateModel.fromType({ + required String userId, + required String name, + required int color, + required AvailabilityTemplateType templateType, + required Map data, + String? id, + }) { + var templateData = TemplateData.fromType(templateType, data); + + return AvailabilityTemplateModel( + userId: userId, + name: name, + color: color, + templateType: templateType, + templateData: templateData, + id: id, + ); + } + /// The identifier for this template final String? id; @@ -41,6 +62,8 @@ class AvailabilityTemplateModel { /// The specific data for this template final TemplateData templateData; + + Map get rawTemplateData => templateData.toMap(); } /// Used as the key for defining week-based templates @@ -79,12 +102,24 @@ enum WeekDay { /// /// ignore: one_member_abstracts abstract interface class TemplateData { + factory TemplateData.fromType( + AvailabilityTemplateType type, + Map data, + ) => + switch (type) { + AvailabilityTemplateType.week => WeekTemplateData.fromMap(data), + AvailabilityTemplateType.day => DayTemplateData.fromMap(data) + }; + /// Applies the current template to all days found between [start] and [end], /// inclusive List apply({ required DateTime start, required DateTime end, }); + + /// Serialize the template to representational data + Map toMap(); } /// A week based template data structure @@ -116,6 +151,36 @@ class WeekTemplateData implements TemplateData { }, ); + /// Parses the template data from a map. + /// + /// This assumes that the structure of the map is the following: + /// + /// ``` + /// { + /// '0': {}, + /// 'index thats parseable by int and matches with a weekday': { + /// // an object that is allowed to be parsed as a DayTemplateData + /// } + /// } + /// ``` + factory WeekTemplateData.fromMap(Map data) => + WeekTemplateData( + data: { + for (var entry in data.entries) ...{ + WeekDay.values[int.parse(entry.key)]: + DayTemplateData.fromMap(entry.value), + }, + }, + ); + + /// returns the map representation of this template data + @override + Map toMap() => { + for (var entry in _data.entries) ...{ + entry.key.index.toString(): entry.value.toMap(), + }, + }; + final Map _data; /// retrieves an unmodifiable map for each date. @@ -140,6 +205,35 @@ class DayTemplateData implements TemplateData { required this.breaks, }); + /// Parses the template data from a map. + /// + /// This assumes that the structure of the map is the following: + /// + /// ``` + /// { + /// '0': {}, + /// 'index thats parseable by int and matches with a weekday': { + /// // an object that is allowed to be parsed as a DayTemplateData + /// } + /// } + /// ``` + factory DayTemplateData.fromMap(Map data) { + var rawBreaks = data["breaks"] as List?; + var breaks = [ + if (rawBreaks != null) ...[ + for (var rawBreak in rawBreaks) ...[ + AvailabilityBreakModel.fromMap(rawBreak as Map), + ], + ], + ]; + + return DayTemplateData( + startTime: DateTime.parse(data["startTime"]), + endTime: DateTime.parse(data["endTime"]), + breaks: breaks, + ); + } + /// The start time to apply on a new availability final DateTime startTime; @@ -157,4 +251,15 @@ class DayTemplateData implements TemplateData { // TODO(Joey): Implement the apply method throw UnimplementedError(); } + + @override + Map toMap() => { + "startTime": startTime.toIso8601String(), + "endTime": endTime.toIso8601String(), + "breaks": [ + for (var breakToSerialize in breaks) ...[ + breakToSerialize.toMap(), + ], + ], + }; } diff --git a/packages/flutter_availability_data_interface/test/models_templates_test.dart b/packages/flutter_availability_data_interface/test/models_templates_test.dart new file mode 100644 index 0000000..d399f29 --- /dev/null +++ b/packages/flutter_availability_data_interface/test/models_templates_test.dart @@ -0,0 +1,64 @@ +import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart"; +import "package:flutter_test/flutter_test.dart"; + +void main() { + group("AvailabilityTemplate", () { + group("Serialization", () { + test("week template should serialize and deserialize correctly", () { + var baseDate = DateTime(1994, 10, 18, 10, 0); + + DayTemplateData createDayTemplateForDay(WeekDay day) { + var baseDayDate = baseDate.add(Duration(days: day.index)); + return DayTemplateData( + startTime: baseDayDate, + endTime: baseDayDate.add(const Duration(hours: 7)), + breaks: [ + AvailabilityBreakModel( + startTime: baseDayDate.add(const Duration(hours: 3)), + endTime: baseDate.add(const Duration(hours: 4)), + ), + ], + ); + } + + var weektemplate = AvailabilityTemplateModel( + userId: "1", + name: "test", + color: 0xFFAABBCC, + templateType: AvailabilityTemplateType.week, + templateData: WeekTemplateData( + data: { + for (var day in WeekDay.values) ...{ + day: createDayTemplateForDay(day), + }, + }, + ), + ); + + var serialized = weektemplate.rawTemplateData; + + var deserialized = AvailabilityTemplateModel.fromType( + userId: weektemplate.userId, + name: weektemplate.name, + color: weektemplate.color, + templateType: weektemplate.templateType, + data: serialized, + ); + + expect(deserialized.templateData, isA()); + var parsedData = deserialized.templateData as WeekTemplateData; + + expect(parsedData.data.length, equals(7)); + expect(parsedData.data.entries.first.value, isA()); + var dayTemplateData = parsedData.data.entries.first.value; + + expect(dayTemplateData.startTime, equals(baseDate)); + expect(dayTemplateData.breaks.length, equals(1)); + expect( + dayTemplateData.breaks.first.startTime, + equals(baseDate.add(const Duration(hours: 3))), + ); + }); + }); + }); +}