Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion lib/presentation/calendar/component/schedule_detail.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,13 @@ class ScheduleDetail extends StatefulWidget {
{super.key,
required this.schedule,
this.preparationTime,
this.isEarlyStarted = false,
this.onDeleted,
this.onEdit});

final ScheduleEntity schedule;
final Duration? preparationTime;
final bool isEarlyStarted;
final VoidCallback? onEdit;
final VoidCallback? onDeleted;

Expand Down Expand Up @@ -103,8 +105,10 @@ class _ScheduleDetailState extends State<ScheduleDetail> {

List<SwipeAction> _buildSwipeActions(BuildContext context) {
final theme = Theme.of(context);
final now = DateTime.now();
final canEdit = widget.schedule.doneStatus == ScheduleDoneStatus.notEnded &&
!widget.schedule.scheduleTime.isBefore(DateTime.now());
!_hasPreparationStarted(now) &&
!widget.schedule.scheduleTime.isBefore(now);
return [
SwipeAction(
widthSpace: _actionWidth,
Expand Down Expand Up @@ -136,6 +140,24 @@ class _ScheduleDetailState extends State<ScheduleDetail> {
];
}

bool _hasPreparationStarted(DateTime now) {
if (widget.schedule.isStarted || widget.isEarlyStarted) {
return true;
}

final preparationTime = widget.preparationTime;
if (preparationTime == null) {
return false;
}

final preparationStartTime = widget.schedule.scheduleTime.subtract(
widget.schedule.moveTime +
preparationTime +
(widget.schedule.scheduleSpareTime ?? Duration.zero),
);
return !now.isBefore(preparationStartTime);
}

Widget _buildScheduleContent(BuildContext context) {
final theme = Theme.of(context);
return Container(
Expand Down
78 changes: 46 additions & 32 deletions lib/presentation/calendar/screens/calendar_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import 'package:on_time_front/core/di/di_setup.dart';
import 'package:on_time_front/l10n/app_localizations.dart';
import 'package:on_time_front/presentation/app/bloc/schedule/schedule_bloc.dart';
import 'package:on_time_front/presentation/calendar/bloc/monthly_schedules_bloc.dart';
import 'package:on_time_front/presentation/calendar/component/schedule_detail.dart';
import 'package:on_time_front/presentation/schedule_create/screens/schedule_create_screen.dart';
Expand Down Expand Up @@ -363,40 +364,53 @@ class _CalendarScreenState extends State<CalendarScreen> {
final schedule =
state.schedules[_selectedDate]![index];

return ScheduleDetail(
schedule: schedule,
preparationTime:
state.preparationDurationByScheduleId[
schedule.id],
onEdit: () {
_openEditScheduleSheet(
context,
scheduleId: schedule.id,
return BlocBuilder<ScheduleBloc, ScheduleState>(
builder: (context, scheduleState) {
final isEarlyStarted =
scheduleState.isEarlyStarted &&
scheduleState.schedule?.id ==
schedule.id;

return ScheduleDetail(
schedule: schedule,
preparationTime:
state.preparationDurationByScheduleId[
schedule.id],
isEarlyStarted: isEarlyStarted,
onEdit: () {
_openEditScheduleSheet(
context,
scheduleId: schedule.id,
);
},
onDeleted: () {
showTwoButtonDeleteDialog(
context,
title: AppLocalizations.of(context)!
.scheduleDeleteConfirmTitle,
description: AppLocalizations.of(
context)!
.scheduleDeleteConfirmDescription,
cancelText:
AppLocalizations.of(context)!
.cancel,
confirmText:
AppLocalizations.of(context)!
.deleteScheduleConfirmAction,
).then((confirmed) {
if (confirmed != true ||
!context.mounted) {
return;
}
_monthlySchedulesBloc.add(
MonthlySchedulesScheduleDeleted(
schedule: schedule,
),
);
});
},
);
},
onDeleted: () {
showTwoButtonDeleteDialog(
context,
title: AppLocalizations.of(context)!
.scheduleDeleteConfirmTitle,
description: AppLocalizations.of(context)!
.scheduleDeleteConfirmDescription,
cancelText:
AppLocalizations.of(context)!.cancel,
confirmText: AppLocalizations.of(context)!
.deleteScheduleConfirmAction,
).then((confirmed) {
if (confirmed != true ||
!context.mounted) {
return;
}
_monthlySchedulesBloc.add(
MonthlySchedulesScheduleDeleted(
schedule: schedule,
),
);
});
},
);
},
),
Expand Down
97 changes: 97 additions & 0 deletions test/presentation/calendar/component/schedule_detail_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ void main() {
WidgetTester tester, {
ScheduleEntity? customSchedule,
Duration? preparationTime,
bool isEarlyStarted = false,
}) async {
final targetSchedule = customSchedule ?? schedule;
await tester.pumpWidget(
Expand All @@ -50,6 +51,7 @@ void main() {
body: ScheduleDetail(
schedule: targetSchedule,
preparationTime: preparationTime,
isEarlyStarted: isEarlyStarted,
),
),
),
Expand Down Expand Up @@ -87,6 +89,25 @@ void main() {
.reduce((a, b) => a > b ? a : b);
}

int getActionButtonCount(WidgetTester tester) {
final actionContainers = find.byWidgetPredicate((widget) {
if (widget is! Container) {
return false;
}
final decoration = widget.decoration;
if (decoration is! BoxDecoration) {
return false;
}
final radius = decoration.borderRadius;
if (radius is! BorderRadius) {
return false;
}
return radius.topLeft.x == 12 && decoration.color != null;
});

return actionContainers.evaluate().length;
}

testWidgets('expanded tile shows travel, preparation, spare in order',
(tester) async {
await pumpScheduleDetail(tester,
Expand Down Expand Up @@ -175,4 +196,80 @@ void main() {
expect(find.text('Office'), findsNothing);
expect(find.text('09:00'), findsNothing);
});

testWidgets('edit action is available before preparation starts',
(tester) async {
final futureSchedule = ScheduleEntity(
id: 'schedule-2',
place: PlaceEntity(id: 'place-1', placeName: 'Office'),
scheduleName: 'Planning',
scheduleTime: DateTime.now().add(const Duration(hours: 3)),
moveTime: const Duration(minutes: 30),
isChanged: false,
isStarted: false,
scheduleSpareTime: const Duration(minutes: 10),
scheduleNote: '',
);

await pumpScheduleDetail(
tester,
customSchedule: futureSchedule,
preparationTime: const Duration(minutes: 20),
);

await openTrailingActions(tester);

expect(getActionButtonCount(tester), 2);
});

testWidgets('edit action is hidden after preparation start time',
(tester) async {
final scheduleInPreparation = ScheduleEntity(
id: 'schedule-3',
place: PlaceEntity(id: 'place-1', placeName: 'Office'),
scheduleName: 'Planning',
scheduleTime: DateTime.now().add(const Duration(minutes: 30)),
moveTime: const Duration(minutes: 30),
isChanged: false,
isStarted: false,
scheduleSpareTime: const Duration(minutes: 10),
scheduleNote: '',
);

await pumpScheduleDetail(
tester,
customSchedule: scheduleInPreparation,
preparationTime: const Duration(minutes: 20),
);

await openTrailingActions(tester);

expect(getActionButtonCount(tester), 1);
});

testWidgets('edit action is hidden for early-started schedule',
(tester) async {
final futureSchedule = ScheduleEntity(
id: 'schedule-4',
place: PlaceEntity(id: 'place-1', placeName: 'Office'),
scheduleName: 'Planning',
scheduleTime: DateTime.now().add(const Duration(hours: 3)),
moveTime: const Duration(minutes: 30),
isChanged: false,
isStarted: false,
scheduleSpareTime: const Duration(minutes: 10),
scheduleNote: '',
);

await pumpScheduleDetail(
tester,
customSchedule: futureSchedule,
preparationTime: const Duration(minutes: 20),
isEarlyStarted: true,
);

await openTrailingActions(tester);

expect(getActionButtonCount(tester), 1);
});
}
Loading