Skip to content

Commit 87d5d3e

Browse files
committed
fix: show schedule delete failure dialog
1 parent ca42873 commit 87d5d3e

10 files changed

Lines changed: 481 additions & 302 deletions

File tree

lib/l10n/app_en.arb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,14 @@
460460
"@deleteScheduleConfirmAction": {
461461
"description": "Button text to confirm schedule deletion"
462462
},
463+
"scheduleDeleteFailedTitle": "Appointment cannot be deleted",
464+
"@scheduleDeleteFailedTitle": {
465+
"description": "Title shown when schedule deletion is rejected or fails"
466+
},
467+
"scheduleDeleteFailedDescription": "This appointment can no longer be deleted. Please refresh the calendar and try again if the status changed recently.",
468+
"@scheduleDeleteFailedDescription": {
469+
"description": "Description shown when schedule deletion is rejected or fails"
470+
},
463471
"deleteFeedbackTitle": "We hope to meet you again with a better service",
464472
"@deleteFeedbackTitle": {
465473
"description": "Delete feedback modal title"

lib/l10n/app_ko.arb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@
147147
"scheduleDeleteConfirmTitle": "정말 약속을 삭제할까요?",
148148
"scheduleDeleteConfirmDescription": "약속을 삭제하면 다시 되돌릴 수 없어요.",
149149
"deleteScheduleConfirmAction": "약속 삭제",
150+
"scheduleDeleteFailedTitle": "약속을 삭제할 수 없어요",
151+
"scheduleDeleteFailedDescription": "이 약속은 더 이상 삭제할 수 없어요. 상태가 방금 바뀐 경우 캘린더를 새로고침한 뒤 다시 확인해주세요.",
150152
"deleteFeedbackTitle": "더 좋은 서비스로 다시 만나요",
151153
"deleteFeedbackDescription": "의견 입력은 선택 사항입니다.\n계속 진행하면 계정 탈퇴 요청이 전송됩니다.",
152154
"deleteFeedbackPlaceholder": "탈퇴하시는 이유를 알려주세요.",

lib/l10n/app_localizations.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,18 @@ abstract class AppLocalizations {
716716
/// **'Delete appointment'**
717717
String get deleteScheduleConfirmAction;
718718

719+
/// Title shown when schedule deletion is rejected or fails
720+
///
721+
/// In en, this message translates to:
722+
/// **'Appointment cannot be deleted'**
723+
String get scheduleDeleteFailedTitle;
724+
725+
/// Description shown when schedule deletion is rejected or fails
726+
///
727+
/// In en, this message translates to:
728+
/// **'This appointment can no longer be deleted. Please refresh the calendar and try again if the status changed recently.'**
729+
String get scheduleDeleteFailedDescription;
730+
719731
/// Delete feedback modal title
720732
///
721733
/// In en, this message translates to:

lib/l10n/app_localizations_en.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,13 @@ class AppLocalizationsEn extends AppLocalizations {
363363
@override
364364
String get deleteScheduleConfirmAction => 'Delete appointment';
365365

366+
@override
367+
String get scheduleDeleteFailedTitle => 'Appointment cannot be deleted';
368+
369+
@override
370+
String get scheduleDeleteFailedDescription =>
371+
'This appointment can no longer be deleted. Please refresh the calendar and try again if the status changed recently.';
372+
366373
@override
367374
String get deleteFeedbackTitle =>
368375
'We hope to meet you again with a better service';

lib/l10n/app_localizations_ko.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,13 @@ class AppLocalizationsKo extends AppLocalizations {
339339
@override
340340
String get deleteScheduleConfirmAction => '약속 삭제';
341341

342+
@override
343+
String get scheduleDeleteFailedTitle => '약속을 삭제할 수 없어요';
344+
345+
@override
346+
String get scheduleDeleteFailedDescription =>
347+
'이 약속은 더 이상 삭제할 수 없어요. 상태가 방금 바뀐 경우 캘린더를 새로고침한 뒤 다시 확인해주세요.';
348+
342349
@override
343350
String get deleteFeedbackTitle => '더 좋은 서비스로 다시 만나요';
344351

lib/presentation/calendar/bloc/monthly_schedules_bloc.dart

Lines changed: 74 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'dart:async';
33
import 'package:equatable/equatable.dart';
44
import 'package:flutter_bloc/flutter_bloc.dart';
55
import 'package:injectable/injectable.dart';
6+
import 'package:on_time_front/core/dio/api_error_message.dart';
67
import 'package:on_time_front/core/logging/app_logger.dart';
78
import 'package:on_time_front/domain/entities/preparation_entity.dart';
89
import 'package:on_time_front/domain/entities/schedule_entity.dart';
@@ -35,19 +36,15 @@ class MonthlySchedulesBloc
3536
on<MonthlySchedulesPreparationsPrefetchRequested>(
3637
_onPreparationsPrefetchRequested,
3738
);
38-
on<MonthlySchedulesPreparationsStreamChanged>(
39-
_onPreparationsStreamChanged,
40-
);
39+
on<MonthlySchedulesPreparationsStreamChanged>(_onPreparationsStreamChanged);
4140

42-
_preparationSubscription = _streamPreparationsUseCase().listen(
43-
(preparations) {
44-
add(
45-
MonthlySchedulesPreparationsStreamChanged(
46-
preparations: preparations,
47-
),
48-
);
49-
},
50-
);
41+
_preparationSubscription = _streamPreparationsUseCase().listen((
42+
preparations,
43+
) {
44+
add(
45+
MonthlySchedulesPreparationsStreamChanged(preparations: preparations),
46+
);
47+
});
5148
}
5249

5350
final LoadSchedulesForMonthUseCase _loadSchedulesForMonthUseCase;
@@ -85,9 +82,8 @@ class MonthlySchedulesBloc
8582
_requestVisibleDatePreparationPrefetch(nextState);
8683
return nextState;
8784
},
88-
onError: (error, stackTrace) => state.copyWith(
89-
status: () => MonthlySchedulesStatus.error,
90-
),
85+
onError: (error, stackTrace) =>
86+
state.copyWith(status: () => MonthlySchedulesStatus.error),
9187
);
9288
}
9389

@@ -119,12 +115,14 @@ class MonthlySchedulesBloc
119115
? event.endDate
120116
: state.endDate!;
121117

122-
emit(state.copyWith(
123-
status: () => MonthlySchedulesStatus.loading,
124-
schedules: () => state.schedules,
125-
startDate: () => startDate,
126-
endDate: () => endDate,
127-
));
118+
emit(
119+
state.copyWith(
120+
status: () => MonthlySchedulesStatus.loading,
121+
schedules: () => state.schedules,
122+
startDate: () => startDate,
123+
endDate: () => endDate,
124+
),
125+
);
128126

129127
try {
130128
await _loadSchedulesForMonthUseCase(event.date);
@@ -149,9 +147,7 @@ class MonthlySchedulesBloc
149147
return nextState;
150148
},
151149
onError: (error, stackTrace) {
152-
return state.copyWith(
153-
status: () => MonthlySchedulesStatus.error,
154-
);
150+
return state.copyWith(status: () => MonthlySchedulesStatus.error);
155151
},
156152
);
157153
}
@@ -160,19 +156,29 @@ class MonthlySchedulesBloc
160156
MonthlySchedulesScheduleDeleted event,
161157
Emitter<MonthlySchedulesState> emit,
162158
) async {
159+
final previousPreparationMap = Map<String, Duration>.from(
160+
state.preparationDurationByScheduleId,
161+
);
163162
try {
164-
final updatedPreparationMap =
165-
Map<String, Duration>.from(state.preparationDurationByScheduleId)
166-
..remove(event.schedule.id);
167-
emit(state.copyWith(
168-
lastDeletedSchedule: () => event.schedule,
169-
preparationDurationByScheduleId: () => updatedPreparationMap,
170-
));
163+
final updatedPreparationMap = Map<String, Duration>.from(
164+
previousPreparationMap,
165+
)..remove(event.schedule.id);
166+
emit(
167+
state.copyWith(
168+
lastDeletedSchedule: () => event.schedule,
169+
preparationDurationByScheduleId: () => updatedPreparationMap,
170+
),
171+
);
171172
await _deleteScheduleUseCase(event.schedule);
172173
} catch (e) {
173-
emit(state.copyWith(
174-
status: () => MonthlySchedulesStatus.error,
175-
));
174+
emit(
175+
state.copyWith(
176+
lastDeletedSchedule: () => null,
177+
preparationDurationByScheduleId: () => previousPreparationMap,
178+
deleteFailureMessage: () => ApiErrorMessage.fromException(e),
179+
deleteFailureCount: () => state.deleteFailureCount + 1,
180+
),
181+
);
176182
}
177183
}
178184

@@ -183,18 +189,19 @@ class MonthlySchedulesBloc
183189
try {
184190
await _loadSchedulesForMonthUseCase(event.date);
185191
} catch (_) {
186-
emit(state.copyWith(
187-
status: () => MonthlySchedulesStatus.error,
188-
));
192+
emit(state.copyWith(status: () => MonthlySchedulesStatus.error));
189193
}
190194
}
191195

192196
void _onVisibleDateChanged(
193197
MonthlySchedulesVisibleDateChanged event,
194198
Emitter<MonthlySchedulesState> emit,
195199
) {
196-
final normalizedDate =
197-
DateTime(event.date.year, event.date.month, event.date.day);
200+
final normalizedDate = DateTime(
201+
event.date.year,
202+
event.date.month,
203+
event.date.day,
204+
);
198205
final nextState = state.copyWith(visibleDate: () => normalizedDate);
199206
emit(nextState);
200207
_requestVisibleDatePreparationPrefetch(nextState);
@@ -221,8 +228,9 @@ class MonthlySchedulesBloc
221228
// Stream update already has fresher data.
222229
continue;
223230
}
224-
final preparation =
225-
await _getPreparationByScheduleIdUseCase(scheduleId);
231+
final preparation = await _getPreparationByScheduleIdUseCase(
232+
scheduleId,
233+
);
226234
fetchedDurations[scheduleId] = preparation.totalDuration;
227235
hasUpdates = true;
228236
} catch (_) {
@@ -234,11 +242,7 @@ class MonthlySchedulesBloc
234242
final updatedMap = Map<String, Duration>.from(
235243
state.preparationDurationByScheduleId,
236244
)..addAll(fetchedDurations);
237-
emit(
238-
state.copyWith(
239-
preparationDurationByScheduleId: () => updatedMap,
240-
),
241-
);
245+
emit(state.copyWith(preparationDurationByScheduleId: () => updatedMap));
242246
}
243247
}
244248

@@ -272,15 +276,12 @@ class MonthlySchedulesBloc
272276
return;
273277
}
274278

275-
emit(
276-
state.copyWith(
277-
preparationDurationByScheduleId: () => nextDurations,
278-
),
279-
);
279+
emit(state.copyWith(preparationDurationByScheduleId: () => nextDurations));
280280
}
281281

282282
void _requestVisibleDatePreparationPrefetch(
283-
MonthlySchedulesState sourceState) {
283+
MonthlySchedulesState sourceState,
284+
) {
284285
final visibleDate = sourceState.visibleDate;
285286
if (visibleDate == null) {
286287
return;
@@ -292,29 +293,30 @@ class MonthlySchedulesBloc
292293
if (scheduleIds.isEmpty) {
293294
return;
294295
}
295-
add(MonthlySchedulesPreparationsPrefetchRequested(
296-
scheduleIds: scheduleIds));
296+
add(
297+
MonthlySchedulesPreparationsPrefetchRequested(scheduleIds: scheduleIds),
298+
);
297299
}
298300

299301
Map<DateTime, List<ScheduleEntity>> _groupSchedulesByDate(
300302
List<ScheduleEntity> schedules,
301303
) {
302-
return schedules.fold<Map<DateTime, List<ScheduleEntity>>>(
303-
{},
304-
(previousValue, element) {
305-
final scheduleTime = DateTime(
306-
element.scheduleTime.year,
307-
element.scheduleTime.month,
308-
element.scheduleTime.day,
309-
);
310-
if (previousValue.containsKey(scheduleTime)) {
311-
previousValue[scheduleTime]!.add(element);
312-
} else {
313-
previousValue[scheduleTime] = [element];
314-
}
315-
return previousValue;
316-
},
317-
);
304+
return schedules.fold<Map<DateTime, List<ScheduleEntity>>>({}, (
305+
previousValue,
306+
element,
307+
) {
308+
final scheduleTime = DateTime(
309+
element.scheduleTime.year,
310+
element.scheduleTime.month,
311+
element.scheduleTime.day,
312+
);
313+
if (previousValue.containsKey(scheduleTime)) {
314+
previousValue[scheduleTime]!.add(element);
315+
} else {
316+
previousValue[scheduleTime] = [element];
317+
}
318+
return previousValue;
319+
});
318320
}
319321

320322
List<String> _getScheduleIdsForDate(
@@ -329,7 +331,8 @@ class MonthlySchedulesBloc
329331
}
330332

331333
Set<String> _getCachedScheduleIds(
332-
Map<DateTime, List<ScheduleEntity>> schedules) {
334+
Map<DateTime, List<ScheduleEntity>> schedules,
335+
) {
333336
final ids = <String>{};
334337
for (final scheduleList in schedules.values) {
335338
for (final schedule in scheduleList) {

lib/presentation/calendar/bloc/monthly_schedules_state.dart

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,24 @@ part of 'monthly_schedules_bloc.dart';
33
enum MonthlySchedulesStatus { initial, loading, success, error }
44

55
final class MonthlySchedulesState extends Equatable {
6-
const MonthlySchedulesState(
7-
{this.status = MonthlySchedulesStatus.initial,
8-
this.schedules = const {},
9-
this.preparationDurationByScheduleId = const {},
10-
this.lastDeletedSchedule,
11-
this.startDate,
12-
this.endDate,
13-
this.visibleDate});
6+
const MonthlySchedulesState({
7+
this.status = MonthlySchedulesStatus.initial,
8+
this.schedules = const {},
9+
this.preparationDurationByScheduleId = const {},
10+
this.lastDeletedSchedule,
11+
this.deleteFailureMessage,
12+
this.deleteFailureCount = 0,
13+
this.startDate,
14+
this.endDate,
15+
this.visibleDate,
16+
});
1417

1518
final MonthlySchedulesStatus status;
1619
final Map<DateTime, List<ScheduleEntity>> schedules;
1720
final Map<String, Duration> preparationDurationByScheduleId;
1821
final ScheduleEntity? lastDeletedSchedule;
22+
final String? deleteFailureMessage;
23+
final int deleteFailureCount;
1924
final DateTime? startDate;
2025
final DateTime? endDate;
2126
final DateTime? visibleDate;
@@ -25,6 +30,8 @@ final class MonthlySchedulesState extends Equatable {
2530
Map<DateTime, List<ScheduleEntity>> Function()? schedules,
2631
Map<String, Duration> Function()? preparationDurationByScheduleId,
2732
ScheduleEntity? Function()? lastDeletedSchedule,
33+
String? Function()? deleteFailureMessage,
34+
int Function()? deleteFailureCount,
2835
DateTime? Function()? startDate,
2936
DateTime? Function()? endDate,
3037
DateTime? Function()? visibleDate,
@@ -38,6 +45,12 @@ final class MonthlySchedulesState extends Equatable {
3845
lastDeletedSchedule: lastDeletedSchedule != null
3946
? lastDeletedSchedule()
4047
: this.lastDeletedSchedule,
48+
deleteFailureMessage: deleteFailureMessage != null
49+
? deleteFailureMessage()
50+
: this.deleteFailureMessage,
51+
deleteFailureCount: deleteFailureCount != null
52+
? deleteFailureCount()
53+
: this.deleteFailureCount,
4154
startDate: startDate != null ? startDate() : this.startDate,
4255
endDate: endDate != null ? endDate() : this.endDate,
4356
visibleDate: visibleDate != null ? visibleDate() : this.visibleDate,
@@ -46,12 +59,14 @@ final class MonthlySchedulesState extends Equatable {
4659

4760
@override
4861
List<Object?> get props => [
49-
status,
50-
schedules,
51-
preparationDurationByScheduleId,
52-
lastDeletedSchedule,
53-
startDate,
54-
endDate,
55-
visibleDate,
56-
];
62+
status,
63+
schedules,
64+
preparationDurationByScheduleId,
65+
lastDeletedSchedule,
66+
deleteFailureMessage,
67+
deleteFailureCount,
68+
startDate,
69+
endDate,
70+
visibleDate,
71+
];
5772
}

0 commit comments

Comments
 (0)