Skip to content

Commit

Permalink
refactor: opening hours (#541)
Browse files Browse the repository at this point in the history
Fix opening hours to match the cafe

Uses the flutter-native TimeOfDay class, which has localized time formatting.
This has been extended with comparison methods <= and isInTimeslot.
Timeslot class simplifies with this change.

Simplify DateService to only contain one getter.

Make sure of fpdart's Option instead of null values. Use None for closed days.

Clean up tests, add more tests, and use NiceMocks for mocking.
---------

Co-authored-by: Omid Marfavi <[email protected]>
  • Loading branch information
fremartini and marfavi committed Nov 16, 2023
1 parent 57e5565 commit 0ee2343
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 81 deletions.
3 changes: 1 addition & 2 deletions lib/core/external/date_service.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
class DateService {
int currentWeekday() => DateTime.now().weekday;
int currentHour() => DateTime.now().hour;
DateTime get currentDateTime => DateTime.now();
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import 'package:coffeecard/features/opening_hours/domain/entities/timeslot.dart';
import 'package:flutter/material.dart';

class OpeningHoursLocalDataSource {
Map<int, Timeslot> getOpeningHours() {
const normalOperation = Timeslot(start: 8, end: 16);
const shortDayOperation = Timeslot(start: 8, end: 14);
const closed = Timeslot();
const openTime = TimeOfDay(hour: 8, minute: 0);
const normalDayCloseTime = TimeOfDay(hour: 15, minute: 30);
const shortDayCloseTime = TimeOfDay(hour: 13, minute: 30);

const normalDayOpeningHours = Timeslot(openTime, normalDayCloseTime);
const shortDayOpeningHours = Timeslot(openTime, shortDayCloseTime);

return {
DateTime.monday: normalOperation,
DateTime.tuesday: normalOperation,
DateTime.wednesday: normalOperation,
DateTime.thursday: normalOperation,
DateTime.friday: shortDayOperation,
DateTime.saturday: closed,
DateTime.sunday: closed,
DateTime.monday: normalDayOpeningHours,
DateTime.tuesday: normalDayOpeningHours,
DateTime.wednesday: normalDayOpeningHours,
DateTime.thursday: normalDayOpeningHours,
DateTime.friday: shortDayOpeningHours,
};
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import 'package:coffeecard/core/external/date_service.dart';
import 'package:coffeecard/features/opening_hours/data/datasources/opening_hours_local_data_source.dart';
import 'package:coffeecard/features/opening_hours/domain/entities/opening_hours.dart';
import 'package:coffeecard/features/opening_hours/domain/entities/timeslot.dart';
import 'package:coffeecard/features/opening_hours/domain/repositories/opening_hours_repository.dart';
import 'package:flutter/material.dart';
import 'package:fpdart/fpdart.dart';

class OpeningHoursRepositoryImpl implements OpeningHoursRepository {
final OpeningHoursLocalDataSource dataSource;
Expand All @@ -15,9 +18,11 @@ class OpeningHoursRepositoryImpl implements OpeningHoursRepository {
@override
OpeningHours getOpeningHours() {
final allOpeningHours = dataSource.getOpeningHours();
final currentWeekDay = dateService.currentWeekday();
final currentWeekday = dateService.currentDateTime.weekday;

final todaysOpeningHours = allOpeningHours[currentWeekDay]!;
final todaysOpeningHours = Option.fromNullable(
allOpeningHours[currentWeekday],
);

return OpeningHours(
allOpeningHours: allOpeningHours,
Expand All @@ -28,16 +33,12 @@ class OpeningHoursRepositoryImpl implements OpeningHoursRepository {
@override
bool isOpen() {
final todaysOpeningHours = getOpeningHours().todaysOpeningHours;
final currentTimeOfDay =
TimeOfDay.fromDateTime(dateService.currentDateTime);

if (todaysOpeningHours.isClosed) {
return false;
}

final currentHour = dateService.currentHour();

if (currentHour < todaysOpeningHours.start! ||
currentHour > todaysOpeningHours.end!) return false;

return true;
return todaysOpeningHours.match(
() => false,
currentTimeOfDay.isInTimeslot,
);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import 'package:coffeecard/features/opening_hours/domain/entities/timeslot.dart';
import 'package:equatable/equatable.dart';
import 'package:fpdart/fpdart.dart';

class OpeningHours extends Equatable {
final Map<int, Timeslot> allOpeningHours;
final Timeslot todaysOpeningHours;
final Option<Timeslot> todaysOpeningHours;

const OpeningHours({
required this.allOpeningHours,
Expand Down
38 changes: 28 additions & 10 deletions lib/features/opening_hours/domain/entities/timeslot.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
import 'package:coffeecard/core/strings.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';

/// A timeslot with a start and end time.
class Timeslot extends Equatable {
final int? start;
final int? end;
final TimeOfDay startTime;
final TimeOfDay endTime;

const Timeslot({this.start, this.end});
const Timeslot(this.startTime, this.endTime);

bool get isClosed => start == null || end == null;
String format(BuildContext context) {
final start = startTime.format(context);
final end = endTime.format(context);
return '$start-$end';
}

@override
String toString() => isClosed
? Strings.closed
: '${start.toString().padLeft(2, '0')}:00 - $end:00';
List<Object?> get props => [startTime, endTime];
}

@override
List<Object?> get props => [start, end];
/// Operators for comparing [TimeOfDay]s.
extension TimeOfDayOperators on TimeOfDay {
/// Returns true if [other] is before [this].
bool operator <=(TimeOfDay other) {
if (hour < other.hour) {
return true;
} else if (hour == other.hour) {
return minute <= other.minute;
} else {
return false;
}
}

bool isInTimeslot(Timeslot timeslot) {
return timeslot.startTime <= this && this <= timeslot.endTime;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:coffeecard/features/opening_hours/domain/entities/timeslot.dart'
import 'package:coffeecard/features/opening_hours/domain/usecases/check_open_status.dart';
import 'package:coffeecard/features/opening_hours/domain/usecases/get_opening_hours.dart';
import 'package:equatable/equatable.dart';
import 'package:fpdart/fpdart.dart';

part 'opening_hours_state.dart';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class OpeningHoursInitial extends OpeningHoursState {

class OpeningHoursLoaded extends OpeningHoursState {
final Map<int, Timeslot> openingHours;
final Timeslot todaysOpeningHours;
final Option<Timeslot> todaysOpeningHours;
final bool isOpen;

const OpeningHoursLoaded({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@ class OpeningHoursPage extends StatelessWidget {
return MaterialPageRoute(builder: (_) => OpeningHoursPage(state: state));
}

List<MapEntry<int, Timeslot>> get openingHours {
return state.openingHours.entries.toList()
..sort((a, b) => a.key.compareTo(b.key));
}

@override
Widget build(BuildContext context) {
return AppScaffold.withTitle(
Expand All @@ -37,7 +32,7 @@ class OpeningHoursPage extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_OpeningHoursView(openingHours: openingHours),
_OpeningHoursView(openingHours: state.openingHours),
const Gap(36),
Row(
children: [
Expand Down Expand Up @@ -65,18 +60,21 @@ class OpeningHoursPage extends StatelessWidget {
class _OpeningHoursView extends StatelessWidget {
const _OpeningHoursView({required this.openingHours});

final List<MapEntry<int, Timeslot>> openingHours;
final Map<int, Timeslot> openingHours;

@override
Widget build(BuildContext context) {
return ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: openingHours.length,
itemCount: Strings.weekdaysPlural.length,
separatorBuilder: (_, __) => const Gap(12),
itemBuilder: (context, index) {
final weekday = openingHours[index].key;
final hours = openingHours[index].value;
itemBuilder: (_, index) {
final weekday = index + 1;
final hours = switch (openingHours[weekday]) {
final Timeslot timeslot => timeslot.format(context),
_ => Strings.closed,
};

return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
Expand All @@ -85,7 +83,10 @@ class _OpeningHoursView extends StatelessWidget {
Strings.weekdaysPlural[weekday]!,
style: AppTextStyle.settingKey,
),
Text(hours.toString(), style: AppTextStyle.receiptItemKey),
Text(
hours,
style: AppTextStyle.receiptItemKey,
),
],
);
},
Expand Down
Loading

0 comments on commit 0ee2343

Please sign in to comment.