feat(flutter_availability_data_interface): add serialization to template data

This commit is contained in:
Joey Boerwinkel 2024-07-02 15:00:41 +02:00
parent 178db2d753
commit de5bc0b27c
3 changed files with 197 additions and 0 deletions

View file

@ -1,3 +1,4 @@
// ignore_for_file: Generated using data class generator
/// A model defining the data structure for an availability /// A model defining the data structure for an availability
class AvailabilityModel { class AvailabilityModel {
/// Creates a new availability /// Creates a new availability
@ -55,6 +56,22 @@ class AvailabilityBreakModel {
Duration? duration, Duration? duration,
}) : _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<String, dynamic> 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 /// The start time for this break
/// ///
/// If duration is not the same as the difference between [startTime] and /// If duration is not the same as the difference between [startTime] and
@ -102,4 +119,15 @@ class AvailabilityBreakModel {
endTime: endTime ?? this.endTime, endTime: endTime ?? this.endTime,
duration: duration ?? _duration, 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<String, dynamic> toMap() => <String, dynamic>{
"startTime": startTime.millisecondsSinceEpoch,
"endTime": endTime.millisecondsSinceEpoch,
"duration": _duration?.inMinutes,
};
} }

View file

@ -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"; import "package:flutter_availability_data_interface/src/models/availability.dart";
/// A limited set of different availability template types /// A limited set of different availability template types
@ -22,6 +23,26 @@ class AvailabilityTemplateModel {
this.id, this.id,
}); });
factory AvailabilityTemplateModel.fromType({
required String userId,
required String name,
required int color,
required AvailabilityTemplateType templateType,
required Map<String, dynamic> 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 /// The identifier for this template
final String? id; final String? id;
@ -41,6 +62,8 @@ class AvailabilityTemplateModel {
/// The specific data for this template /// The specific data for this template
final TemplateData templateData; final TemplateData templateData;
Map<String, dynamic> get rawTemplateData => templateData.toMap();
} }
/// Used as the key for defining week-based templates /// Used as the key for defining week-based templates
@ -79,12 +102,24 @@ enum WeekDay {
/// ///
/// ignore: one_member_abstracts /// ignore: one_member_abstracts
abstract interface class TemplateData { abstract interface class TemplateData {
factory TemplateData.fromType(
AvailabilityTemplateType type,
Map<String, dynamic> 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], /// Applies the current template to all days found between [start] and [end],
/// inclusive /// inclusive
List<AvailabilityModel> apply({ List<AvailabilityModel> apply({
required DateTime start, required DateTime start,
required DateTime end, required DateTime end,
}); });
/// Serialize the template to representational data
Map<String, dynamic> toMap();
} }
/// A week based template data structure /// 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': <String, dynamic>{},
/// '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<String, dynamic> 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<String, dynamic> toMap() => {
for (var entry in _data.entries) ...{
entry.key.index.toString(): entry.value.toMap(),
},
};
final Map<WeekDay, DayTemplateData> _data; final Map<WeekDay, DayTemplateData> _data;
/// retrieves an unmodifiable map for each date. /// retrieves an unmodifiable map for each date.
@ -140,6 +205,35 @@ class DayTemplateData implements TemplateData {
required this.breaks, required this.breaks,
}); });
/// Parses the template data from a map.
///
/// This assumes that the structure of the map is the following:
///
/// ```
/// {
/// '0': <String, dynamic>{},
/// '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<String, dynamic> data) {
var rawBreaks = data["breaks"] as List?;
var breaks = <AvailabilityBreakModel>[
if (rawBreaks != null) ...[
for (var rawBreak in rawBreaks) ...[
AvailabilityBreakModel.fromMap(rawBreak as Map<String, dynamic>),
],
],
];
return DayTemplateData(
startTime: DateTime.parse(data["startTime"]),
endTime: DateTime.parse(data["endTime"]),
breaks: breaks,
);
}
/// The start time to apply on a new availability /// The start time to apply on a new availability
final DateTime startTime; final DateTime startTime;
@ -157,4 +251,15 @@ class DayTemplateData implements TemplateData {
// TODO(Joey): Implement the apply method // TODO(Joey): Implement the apply method
throw UnimplementedError(); throw UnimplementedError();
} }
@override
Map<String, dynamic> toMap() => {
"startTime": startTime.toIso8601String(),
"endTime": endTime.toIso8601String(),
"breaks": [
for (var breakToSerialize in breaks) ...[
breakToSerialize.toMap(),
],
],
};
} }

View file

@ -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<WeekTemplateData>());
var parsedData = deserialized.templateData as WeekTemplateData;
expect(parsedData.data.length, equals(7));
expect(parsedData.data.entries.first.value, isA<DayTemplateData>());
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))),
);
});
});
});
}