chore: apply code review comments

This commit is contained in:
Joey Boerwinkel 2024-07-15 11:30:16 +02:00 committed by Freek van de Ven
parent bc32462c16
commit 3238fc2c4a
12 changed files with 67 additions and 0 deletions

View file

@ -24,6 +24,7 @@ class AvailabilityOptions {
this.colors = const AvailabilityColors(), this.colors = const AvailabilityColors(),
this.confirmationDialogBuilder = DefaultConfirmationDialog.builder, this.confirmationDialogBuilder = DefaultConfirmationDialog.builder,
this.timePickerBuilder, this.timePickerBuilder,
// TODO(Joey): Also have a DefaultLoader.builder
this.loadingIndicatorBuilder = defaultLoader, this.loadingIndicatorBuilder = defaultLoader,
AvailabilityDataInterface? dataInterface, AvailabilityDataInterface? dataInterface,
}) : dataInterface = dataInterface ?? LocalAvailabilityDataInterface(); }) : dataInterface = dataInterface ?? LocalAvailabilityDataInterface();

View file

@ -15,6 +15,8 @@ List<AvailabilityModel> getDefaultLocalAvailabilitiesForUser() {
AvailabilityModel( AvailabilityModel(
id: availability.$1, id: availability.$1,
userId: "", userId: "",
// TODO(Joey): Even though this is testdata, Never use .add(duration)
// to move to the next day.
startDate: currentMonth.add(Duration(days: availability.$2)), startDate: currentMonth.add(Duration(days: availability.$2)),
endDate: currentMonth.add(Duration(days: availability.$2)), endDate: currentMonth.add(Duration(days: availability.$2)),
breaks: [], breaks: [],

View file

@ -207,6 +207,7 @@ class LocalAvailabilityDataInterface implements AvailabilityDataInterface {
return availabilities[index]; return availabilities[index];
} }
} }
// TODO(Joey): Throw proper exceptions here
throw Exception("Availability not found"); throw Exception("Availability not found");
} }
@ -225,6 +226,8 @@ class LocalAvailabilityDataInterface implements AvailabilityDataInterface {
return templates[index]; return templates[index];
} }
} }
// TODO(Joey): Add proper exceptions in the data interface and throw them
// here
throw Exception("Template not found"); throw Exception("Template not found");
} }

View file

@ -54,6 +54,8 @@ class _AvailabilitiesModificationScreenState
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// TODO(Joey): These can be immediately assigned to the properties
// This removes the need for an initState
_availability = _availability =
widget.initialAvailabilities.getAvailabilities().firstOrNull ?? widget.initialAvailabilities.getAvailabilities().firstOrNull ??
AvailabilityModel( AvailabilityModel(
@ -76,6 +78,8 @@ class _AvailabilitiesModificationScreenState
// TODO(freek): the selected period might be longer than 1 month // TODO(freek): the selected period might be longer than 1 month
//so we need to get all the availabilites through a stream //so we need to get all the availabilites through a stream
// TODO(Joey): separate logic from layout to adhere to the single
// responsibility principle
Future<void> onSave() async { Future<void> onSave() async {
if (_clearAvailability) { if (_clearAvailability) {
await service.clearAvailabilities( await service.clearAvailabilities(
@ -100,11 +104,15 @@ class _AvailabilitiesModificationScreenState
} }
Future<void> onClickSave() async { Future<void> onClickSave() async {
// TODO(Joey): The name confirmationDialogBuilder does not represent the
// expected implementation.
var confirmed = await options.confirmationDialogBuilder( var confirmed = await options.confirmationDialogBuilder(
context, context,
title: translations.availabilityDialogConfirmTitle, title: translations.availabilityDialogConfirmTitle,
description: translations.availabilityDialogConfirmDescription, description: translations.availabilityDialogConfirmDescription,
); );
// TODO(Joey): We should make the interface of the dialog function return
// a non nullable bool. Now we are implicitly setting default behaviour
if (confirmed ?? false) { if (confirmed ?? false) {
await onSave(); await onSave();
} }
@ -121,6 +129,7 @@ class _AvailabilitiesModificationScreenState
var clearSection = AvailabilityClearSection( var clearSection = AvailabilityClearSection(
range: widget.dateRange, range: widget.dateRange,
clearAvailable: _clearAvailability, clearAvailable: _clearAvailability,
// TODO(Joey): Extract this function
onChanged: (isChecked) { onChanged: (isChecked) {
setState(() { setState(() {
_clearAvailability = isChecked; _clearAvailability = isChecked;
@ -130,6 +139,7 @@ class _AvailabilitiesModificationScreenState
var templateSelection = AvailabilityTemplateSelection( var templateSelection = AvailabilityTemplateSelection(
selectedTemplates: _selectedTemplates, selectedTemplates: _selectedTemplates,
// TODO(Joey): Extract this function
onTemplateAdd: () async { onTemplateAdd: () async {
var template = await widget.onTemplateSelection(); var template = await widget.onTemplateSelection();
if (template != null) { if (template != null) {
@ -138,6 +148,7 @@ class _AvailabilitiesModificationScreenState
}); });
} }
}, },
// TODO(Joey): Extract these functions
onTemplatesRemoved: () { onTemplatesRemoved: () {
setState(() { setState(() {
_selectedTemplates = []; _selectedTemplates = [];
@ -150,9 +161,11 @@ class _AvailabilitiesModificationScreenState
startTime: _startTime, startTime: _startTime,
endTime: _endTime, endTime: _endTime,
key: ValueKey([_startTime, _endTime]), key: ValueKey([_startTime, _endTime]),
// TODO(Joey): Extract these
onStartChanged: (start) => setState(() { onStartChanged: (start) => setState(() {
_startTime = start; _startTime = start;
}), }),
// TODO(Joey): Extract these
onEndChanged: (end) => setState(() { onEndChanged: (end) => setState(() {
_endTime = end; _endTime = end;
}), }),
@ -160,6 +173,7 @@ class _AvailabilitiesModificationScreenState
var pauseSelection = PauseSelection( var pauseSelection = PauseSelection(
breaks: _availability.breaks, breaks: _availability.breaks,
// TODO(Joey): Extract these
onBreaksChanged: (breaks) { onBreaksChanged: (breaks) {
setState(() { setState(() {
_availability = _availability.copyWith(breaks: breaks); _availability = _availability.copyWith(breaks: breaks);
@ -167,6 +181,8 @@ class _AvailabilitiesModificationScreenState
}, },
); );
// TODO(Joey): this structure is defined multiple times, we should create
// a widget to handle this consistently
var body = CustomScrollView( var body = CustomScrollView(
slivers: [ slivers: [
SliverPadding( SliverPadding(
@ -181,6 +197,7 @@ class _AvailabilitiesModificationScreenState
templateSelection, templateSelection,
const SizedBox(height: 24), const SizedBox(height: 24),
timeSelection, timeSelection,
// TODO(Joey): Not divisible by 4
const SizedBox(height: 26), const SizedBox(height: 26),
pauseSelection, pauseSelection,
], ],

View file

@ -51,6 +51,7 @@ class _AvailabilityOverviewState extends State<AvailabilityOverview> {
var availabilitySnapshot = useStream(availabilityStream); var availabilitySnapshot = useStream(availabilityStream);
// TODO(Joey): Way too complex of a function
var selectedAvailabilities = _selectedRange != null var selectedAvailabilities = _selectedRange != null
? availabilitySnapshot.data ? availabilitySnapshot.data
?.where( ?.where(
@ -93,6 +94,7 @@ class _AvailabilityOverviewState extends State<AvailabilityOverview> {
availabilities: availabilitySnapshot, availabilities: availabilitySnapshot,
); );
// TODO(Joey): too complex of a definition for the function
var onButtonPress = _selectedRange == null var onButtonPress = _selectedRange == null
? null ? null
: () { : () {
@ -111,6 +113,7 @@ class _AvailabilityOverviewState extends State<AvailabilityOverview> {
title: translations.clearAvailabilityConfirmTitle, title: translations.clearAvailabilityConfirmTitle,
description: translations.clearAvailabilityConfirmDescription, description: translations.clearAvailabilityConfirmDescription,
); );
// TODO(Joey): Expect a non nullable
if (confirmed ?? false) { if (confirmed ?? false) {
await service await service
.clearAvailabilities(selectedAvailabilities.getAvailabilities()); .clearAvailabilities(selectedAvailabilities.getAvailabilities());
@ -132,6 +135,7 @@ class _AvailabilityOverviewState extends State<AvailabilityOverview> {
Text(translations.editAvailabilityButton), Text(translations.editAvailabilityButton),
); );
// TODO(Joey): This structure is defined multiple times
var body = CustomScrollView( var body = CustomScrollView(
slivers: [ slivers: [
SliverPadding( SliverPadding(

View file

@ -104,12 +104,15 @@ class _DayTemplateModificationScreenState
var timeSection = TemplateTimeSelection( var timeSection = TemplateTimeSelection(
key: ValueKey(_template.templateData), key: ValueKey(_template.templateData),
// TODO(Joey): Extract this
startTime: TimeOfDay.fromDateTime( startTime: TimeOfDay.fromDateTime(
(_template.templateData as DayTemplateData).startTime, (_template.templateData as DayTemplateData).startTime,
), ),
// TODO(Joey): Extract this
endTime: TimeOfDay.fromDateTime( endTime: TimeOfDay.fromDateTime(
(_template.templateData as DayTemplateData).endTime, (_template.templateData as DayTemplateData).endTime,
), ),
// TODO(Joey): Extract this
onStartChanged: (start) { onStartChanged: (start) {
var startTime = (_template.templateData as DayTemplateData).startTime; var startTime = (_template.templateData as DayTemplateData).startTime;
var updatedStartTime = DateTime( var updatedStartTime = DateTime(
@ -127,6 +130,7 @@ class _DayTemplateModificationScreenState
); );
}); });
}, },
// TODO(Joey): Extract this
onEndChanged: (end) { onEndChanged: (end) {
var endTime = (_template.templateData as DayTemplateData).endTime; var endTime = (_template.templateData as DayTemplateData).endTime;
var updatedEndTime = DateTime( var updatedEndTime = DateTime(
@ -148,6 +152,7 @@ class _DayTemplateModificationScreenState
var colorSection = TemplateColorSelection( var colorSection = TemplateColorSelection(
selectedColor: _selectedColor, selectedColor: _selectedColor,
// TODO(Joey): Extract this
onColorSelected: (color) { onColorSelected: (color) {
setState(() { setState(() {
_selectedColor = color; _selectedColor = color;
@ -158,6 +163,7 @@ class _DayTemplateModificationScreenState
var pauseSection = PauseSelection( var pauseSection = PauseSelection(
breaks: (_template.templateData as DayTemplateData).breaks, breaks: (_template.templateData as DayTemplateData).breaks,
// TODO(Joey): Extrac this
onBreaksChanged: (breaks) { onBreaksChanged: (breaks) {
setState(() { setState(() {
_template = _template.copyWith( _template = _template.copyWith(

View file

@ -100,6 +100,7 @@ class _TemplateListSection extends StatelessWidget {
final String sectionTitle; final String sectionTitle;
final String createButtonText; final String createButtonText;
// transform the stream to a snapshot as low as possible to reduce rebuilds
final AsyncSnapshot<List<AvailabilityTemplateModel>> templatesSnapshot; final AsyncSnapshot<List<AvailabilityTemplateModel>> templatesSnapshot;
final void Function(AvailabilityTemplateModel template) onEditTemplate; final void Function(AvailabilityTemplateModel template) onEditTemplate;
final VoidCallback onAddTemplate; final VoidCallback onAddTemplate;
@ -147,8 +148,13 @@ class _TemplateListSection extends StatelessWidget {
Text(sectionTitle, style: textTheme.titleMedium), Text(sectionTitle, style: textTheme.titleMedium),
const SizedBox(height: 8), const SizedBox(height: 8),
const Divider(height: 1), const Divider(height: 1),
// TODO(Joey): Do not make this nullable, in the build make sure to
// have the expected value ready.
for (var template for (var template
in templatesSnapshot.data ?? <AvailabilityTemplateModel>[]) ...[ in templatesSnapshot.data ?? <AvailabilityTemplateModel>[]) ...[
// TODO(Joey): Extract this as a widget
// TODO(Joey): Do not simply use gesture detectors, always think of
// semantics, interaction and other UX
GestureDetector( GestureDetector(
onTap: () => onClickTemplate(template), onTap: () => onClickTemplate(template),
child: Container( child: Container(
@ -171,6 +177,8 @@ class _TemplateListSection extends StatelessWidget {
const SizedBox(width: 8), const SizedBox(width: 8),
Text(template.name, style: textTheme.bodyLarge), Text(template.name, style: textTheme.bodyLarge),
const Spacer(), const Spacer(),
// TODO(Joey): Do not simply use gesture detectors, always
// think of semantics, interaction and other UX
GestureDetector( GestureDetector(
onTap: () => onEditTemplate(template), onTap: () => onEditTemplate(template),
child: const Icon(Icons.edit), child: const Icon(Icons.edit),

View file

@ -35,6 +35,7 @@ class AvailabilityTemplateSelection extends StatelessWidget {
var options = availabilityScope.options; var options = availabilityScope.options;
var translations = options.translations; var translations = options.translations;
// TODO(Joey): Do not nest ternairy operators
var titleText = selectedTemplates.isEmpty var titleText = selectedTemplates.isEmpty
? translations.availabilityAddTemplateTitle ? translations.availabilityAddTemplateTitle
: selectedTemplates.length > 1 : selectedTemplates.length > 1
@ -62,6 +63,7 @@ class AvailabilityTemplateSelection extends StatelessWidget {
if (selectedTemplates.isEmpty) ...[ if (selectedTemplates.isEmpty) ...[
addButton, addButton,
] else ...[ ] else ...[
// TODO(Joey): Extract this as a widget
Container( Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -69,6 +71,9 @@ class AvailabilityTemplateSelection extends StatelessWidget {
color: theme.colorScheme.primary, color: theme.colorScheme.primary,
width: 1, width: 1,
), ),
// TODO(Joey): This seems like a repeating borderRadius. I can
// understand if these are not configurable, but I do think that
// they should be defined only once.
borderRadius: BorderRadius.circular(5), borderRadius: BorderRadius.circular(5),
), ),
child: Row( child: Row(
@ -79,6 +84,7 @@ class AvailabilityTemplateSelection extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
for (var template in selectedTemplates) ...[ for (var template in selectedTemplates) ...[
// TODO(Joey): Extract this as a widget
Row( Row(
children: [ children: [
Container( Container(

View file

@ -73,6 +73,7 @@ class CalendarGrid extends StatelessWidget {
mainAxisSpacing: 12, mainAxisSpacing: 12,
), ),
itemBuilder: (context, index) { itemBuilder: (context, index) {
// TODO(Joey): Extract all this as a widget
var day = calendarDays[index]; var day = calendarDays[index];
var dayColor = day.color ?? var dayColor = day.color ??
colors.customAvailabilityColor ?? colors.customAvailabilityColor ??
@ -86,6 +87,7 @@ class CalendarGrid extends StatelessWidget {
); );
var textStyle = textTheme.bodyLarge?.copyWith(color: textColor); var textStyle = textTheme.bodyLarge?.copyWith(color: textColor);
// TODO(Joey): Watch out for using gesture detectors
return GestureDetector( return GestureDetector(
onTap: () => onDayTap(day.date), onTap: () => onDayTap(day.date),
child: DecoratedBox( child: DecoratedBox(
@ -186,6 +188,8 @@ List<CalendarDay> _generateCalendarDays(
required bool isNextMonth, required bool isNextMonth,
}) { }) {
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
// TODO(Joey): Never increase days with duration, always use internal
// datetime math.
var day = isNextMonth var day = isNextMonth
? startDay.add(Duration(days: i + 1)) ? startDay.add(Duration(days: i + 1))
: startDay.subtract(Duration(days: count - i)); : startDay.subtract(Duration(days: count - i));

View file

@ -140,6 +140,7 @@ class BreakDisplay extends StatelessWidget {
TimeOfDay.fromDateTime(breakModel.endTime), TimeOfDay.fromDateTime(breakModel.endTime),
); );
// TODO(Joey): Watch out with gesture detectors
return GestureDetector( return GestureDetector(
onTap: onClick, onTap: onClick,
child: Container( child: Container(
@ -158,6 +159,7 @@ class BreakDisplay extends StatelessWidget {
"$endTime", "$endTime",
), ),
const Spacer(), const Spacer(),
// TODO(Joey): Watch out with gesturedetectors
GestureDetector(onTap: onRemove, child: const Icon(Icons.remove)), GestureDetector(onTap: onRemove, child: const Icon(Icons.remove)),
], ],
), ),

View file

@ -110,11 +110,15 @@ class _TemplateLegendState extends State<TemplateLegend> {
), ),
padding: const EdgeInsets.only(right: 2), padding: const EdgeInsets.only(right: 2),
child: _templateDrawerOpen && !templatesLoading child: _templateDrawerOpen && !templatesLoading
// TODO(Joey): A listview inside a scrollview inside the
// scrollable that each page has seems like really strange UX
// TODO(Joey): No ternary operators in the layout
? SingleChildScrollView( ? SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
Container( Container(
constraints: const BoxConstraints( constraints: const BoxConstraints(
// TODO(Joey): Not divisible by 4
maxHeight: 150, maxHeight: 150,
), ),
child: Scrollbar( child: Scrollbar(
@ -125,9 +129,12 @@ class _TemplateLegendState extends State<TemplateLegend> {
child: ListView.builder( child: ListView.builder(
controller: _scrollController, controller: _scrollController,
shrinkWrap: true, shrinkWrap: true,
// TODO(Joey): This seems like an odd way to
// implement appending items
itemCount: templates.length + 2, itemCount: templates.length + 2,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index == 0) { if (index == 0) {
// TODO(Joey): Extract this as a widget
return Column( return Column(
children: [ children: [
_TemplateLegendItem( _TemplateLegendItem(
@ -175,6 +182,9 @@ class _TemplateLegendState extends State<TemplateLegend> {
thickness: 1, thickness: 1,
), ),
), ),
// TODO(Joey): This is too complex of a check to read the layout
// There are 8 different combinations parameters with 2 different
// outcomes
if (!templatesAvailable && if (!templatesAvailable &&
(!_templateDrawerOpen || templatesLoading)) ...[ (!_templateDrawerOpen || templatesLoading)) ...[
const SizedBox(height: 12), const SizedBox(height: 12),
@ -213,6 +223,7 @@ class _TemplateLegendItem extends StatelessWidget {
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: backgroundColor, color: backgroundColor,
// TODO(Joey): Use a global borderRadius
borderRadius: BorderRadius.circular(5), borderRadius: BorderRadius.circular(5),
border: Border.all( border: Border.all(
color: borderColor ?? Colors.transparent, color: borderColor ?? Colors.transparent,
@ -221,6 +232,7 @@ class _TemplateLegendItem extends StatelessWidget {
width: 20, width: 20,
height: 20, height: 20,
), ),
// TODO(Joey): Not divisible by 4
const SizedBox(width: 6), const SizedBox(width: 6),
Text(name, style: Theme.of(context).textTheme.bodyLarge), Text(name, style: Theme.of(context).textTheme.bodyLarge),
], ],

View file

@ -11,6 +11,8 @@ bool isTemplateDeviated(
DateTime? templateStartDate; DateTime? templateStartDate;
DateTime? templateEndDate; DateTime? templateEndDate;
// TODO(Joey): Add a method to a templateModel: getEndTimeForDayOfWeek()
// as well as for start time. Allow polymorphism to resolve this if statement
if (template.templateType == AvailabilityTemplateType.week) { if (template.templateType == AvailabilityTemplateType.week) {
templateStartDate = (template.templateData as WeekTemplateData) templateStartDate = (template.templateData as WeekTemplateData)
.data[WeekDay.values[dayOfWeek - 1]] .data[WeekDay.values[dayOfWeek - 1]]