From 0dd6ccb757b547d946a579ae8a3effa2f98c83e5 Mon Sep 17 00:00:00 2001 From: inpt333 Date: Tue, 19 Nov 2024 15:16:43 +0100 Subject: [PATCH] feat: delete schedules --- lib/epics/schedules.dart | 14 ++++ lib/l10n/app_de.arb | 2 + lib/l10n/app_en.arb | 2 + lib/l10n/app_es.arb | 2 + lib/l10n/app_fr.arb | 2 + lib/l10n/app_it.arb | 2 + lib/presentation/containers/group_events.dart | 5 ++ .../containers/group_events.freezed.dart | 25 ++++++- lib/presentation/widgets/group_events.dart | 68 ++++++++++++++----- lib/repositories/members.dart | 2 +- lib/repositories/schedules.dart | 4 ++ test/repositories_test.dart | 2 +- 12 files changed, 111 insertions(+), 19 deletions(-) diff --git a/lib/epics/schedules.dart b/lib/epics/schedules.dart index a48ba9aa..ff321da7 100644 --- a/lib/epics/schedules.dart +++ b/lib/epics/schedules.dart @@ -9,6 +9,7 @@ import 'package:rxdart/rxdart.dart'; createSchedulesEpics(SchedulesRepository schedules) => combineEpics([ _createRetrieveGroupSchedulesEpic(schedules), _createCreateOneScheduleEpic(schedules), + _createDeleteOneScheduleEpic(schedules), ]); /// Fetch all schedules for a group @@ -24,6 +25,7 @@ Epic _createRetrieveGroupSchedulesEpic( ); } +/// When the user requests to create a new schedule Epic _createCreateOneScheduleEpic(SchedulesRepository schedules) { return (Stream actions, EpicStore store) => actions .whereType>() @@ -35,3 +37,15 @@ Epic _createCreateOneScheduleEpic(SchedulesRepository schedules) { FailCreateOne(entity: action.entity, error: error)), ); } + +/// When the user requests to delete one schedule +Epic _createDeleteOneScheduleEpic(SchedulesRepository schedules) { + return (Stream actions, EpicStore store) => + actions.whereType>().asyncMap( + (action) => schedules + .deleteSchedule(action.id) + .then((_) => SuccessDeleteOne(action.id)) + .catchError((error) => + FailDeleteOne(id: action.id, error: error)), + ); +} diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 9b3ba2a4..eefe3dd1 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -16,6 +16,8 @@ "delete": "Löschen", "deleteProfile": "Profil löschen", "deleteProfileConfirmation": "Bist du sicher, dass du dein Profil löschen möchtest?", + "deleteSchedule": "Event löschen", + "deleteScheduleConfirmation": "Bist du sicher, dass du dieses Event löschen möchtest?", "details": "Einzelheiten", "enterGroupDescription": "Du kannst eine optionale Gruppenbeschreibung eingeben", "enterGroupName": "Gib einen Gruppennamen ein", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 159dcdd4..b3484817 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -16,6 +16,8 @@ "delete": "Delete", "deleteProfile": "Delete profile", "deleteProfileConfirmation": "Are you sure you want to delete your profile?", + "deleteSchedule": "Delete event", + "deleteScheduleConfirmation": "Are you sure you want to delete this event?", "details": "Details", "enterGroupDescription": "You may enter an optional group description", "enterGroupName": "Enter a group name", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 32c1ca74..daf83cf2 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -16,6 +16,8 @@ "delete": "Eliminar", "deleteProfile": "Eliminar perfil", "deleteProfileConfirmation": "¿Estás seguro de que quieres eliminar tu perfil?", + "deleteSchedule": "Eliminar evento", + "deleteScheduleConfirmation": "¿Estás seguro de que quieres eliminar este evento?", "details": "Detalles", "enterGroupDescription": "Puedes poner una descripción del grupo si quieres", "enterGroupName": "Ponle un nombre al grupo", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 9f56e5d1..07a09a77 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -16,6 +16,8 @@ "delete": "Supprime", "deleteProfile": "Supprime le profil", "deleteProfileConfirmation": "Es-tu sûr de vouloir supprimer ton profil?", + "deleteSchedule": "Supprime l'événement", + "deleteScheduleConfirmation": "Es-tu sûr de vouloir supprimer cet événement?", "details": "Détails", "enterGroupDescription": "Tu peux entrer une description de groupe si tu veux", "enterGroupName": "Entre un nom de groupe", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index c8d252a8..522e239d 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -16,6 +16,8 @@ "delete": "Elimina", "deleteProfile": "Elimina il profilo", "deleteProfileConfirmation": "Sei sicuro di voler eliminare il tuo profilo?", + "deleteSchedule": "Elimina evento", + "deleteScheduleConfirmation": "Sei sicuro di voler eliminare questo evento?", "details": "Dettagli", "enterGroupDescription": "Puoi inserire una descrizione del gruppo se vuoi", "enterGroupName": "Inserisci il nome del gruppo", diff --git a/lib/presentation/containers/group_events.dart b/lib/presentation/containers/group_events.dart index 5c2e3811..3276fc67 100644 --- a/lib/presentation/containers/group_events.dart +++ b/lib/presentation/containers/group_events.dart @@ -28,6 +28,7 @@ class GroupEventsContainer extends StatelessWidget { group: vm.group, schedules: vm.schedules, onCreate: vm.onScheduleCreate, + onDelete: vm.onScheduleDelete, rrulel10n: vm.rrulel10n, ), ); @@ -41,6 +42,7 @@ sealed class _ViewModel with _$ViewModel { Group? group, Iterable? schedules, ValueSetter? onScheduleCreate, + ValueSetter? onScheduleDelete, Future? rrulel10n, }) = __ViewModel; @@ -59,6 +61,9 @@ sealed class _ViewModel with _$ViewModel { onScheduleCreate: (schedule) => store.dispatch( RequestCreateOne(schedule), ), + onScheduleDelete: (schedule) => store.dispatch( + RequestDeleteOne(schedule.id.toString()), + ), rrulel10n: rruleL10nSelector(store.state), ); } diff --git a/lib/presentation/containers/group_events.freezed.dart b/lib/presentation/containers/group_events.freezed.dart index 67d27d79..46cc314e 100644 --- a/lib/presentation/containers/group_events.freezed.dart +++ b/lib/presentation/containers/group_events.freezed.dart @@ -21,6 +21,8 @@ mixin _$ViewModel { Iterable? get schedules => throw _privateConstructorUsedError; ValueSetter? get onScheduleCreate => throw _privateConstructorUsedError; + ValueSetter? get onScheduleDelete => + throw _privateConstructorUsedError; Future? get rrulel10n => throw _privateConstructorUsedError; /// Create a copy of _ViewModel @@ -41,6 +43,7 @@ abstract class _$ViewModelCopyWith<$Res> { Group? group, Iterable? schedules, ValueSetter? onScheduleCreate, + ValueSetter? onScheduleDelete, Future? rrulel10n}); $GroupCopyWith<$Res>? get group; @@ -65,6 +68,7 @@ class __$ViewModelCopyWithImpl<$Res, $Val extends _ViewModel> Object? group = freezed, Object? schedules = freezed, Object? onScheduleCreate = freezed, + Object? onScheduleDelete = freezed, Object? rrulel10n = freezed, }) { return _then(_value.copyWith( @@ -84,6 +88,10 @@ class __$ViewModelCopyWithImpl<$Res, $Val extends _ViewModel> ? _value.onScheduleCreate : onScheduleCreate // ignore: cast_nullable_to_non_nullable as ValueSetter?, + onScheduleDelete: freezed == onScheduleDelete + ? _value.onScheduleDelete + : onScheduleDelete // ignore: cast_nullable_to_non_nullable + as ValueSetter?, rrulel10n: freezed == rrulel10n ? _value.rrulel10n : rrulel10n // ignore: cast_nullable_to_non_nullable @@ -119,6 +127,7 @@ abstract class _$$_ViewModelImplCopyWith<$Res> Group? group, Iterable? schedules, ValueSetter? onScheduleCreate, + ValueSetter? onScheduleDelete, Future? rrulel10n}); @override @@ -142,6 +151,7 @@ class __$$_ViewModelImplCopyWithImpl<$Res> Object? group = freezed, Object? schedules = freezed, Object? onScheduleCreate = freezed, + Object? onScheduleDelete = freezed, Object? rrulel10n = freezed, }) { return _then(_$_ViewModelImpl( @@ -161,6 +171,10 @@ class __$$_ViewModelImplCopyWithImpl<$Res> ? _value.onScheduleCreate : onScheduleCreate // ignore: cast_nullable_to_non_nullable as ValueSetter?, + onScheduleDelete: freezed == onScheduleDelete + ? _value.onScheduleDelete + : onScheduleDelete // ignore: cast_nullable_to_non_nullable + as ValueSetter?, rrulel10n: freezed == rrulel10n ? _value.rrulel10n : rrulel10n // ignore: cast_nullable_to_non_nullable @@ -177,6 +191,7 @@ class _$_ViewModelImpl implements __ViewModel { this.group, this.schedules, this.onScheduleCreate, + this.onScheduleDelete, this.rrulel10n}); @override @@ -188,11 +203,13 @@ class _$_ViewModelImpl implements __ViewModel { @override final ValueSetter? onScheduleCreate; @override + final ValueSetter? onScheduleDelete; + @override final Future? rrulel10n; @override String toString() { - return '_ViewModel(loading: $loading, group: $group, schedules: $schedules, onScheduleCreate: $onScheduleCreate, rrulel10n: $rrulel10n)'; + return '_ViewModel(loading: $loading, group: $group, schedules: $schedules, onScheduleCreate: $onScheduleCreate, onScheduleDelete: $onScheduleDelete, rrulel10n: $rrulel10n)'; } @override @@ -205,6 +222,8 @@ class _$_ViewModelImpl implements __ViewModel { const DeepCollectionEquality().equals(other.schedules, schedules) && (identical(other.onScheduleCreate, onScheduleCreate) || other.onScheduleCreate == onScheduleCreate) && + (identical(other.onScheduleDelete, onScheduleDelete) || + other.onScheduleDelete == onScheduleDelete) && (identical(other.rrulel10n, rrulel10n) || other.rrulel10n == rrulel10n)); } @@ -216,6 +235,7 @@ class _$_ViewModelImpl implements __ViewModel { group, const DeepCollectionEquality().hash(schedules), onScheduleCreate, + onScheduleDelete, rrulel10n); /// Create a copy of _ViewModel @@ -233,6 +253,7 @@ abstract class __ViewModel implements _ViewModel { final Group? group, final Iterable? schedules, final ValueSetter? onScheduleCreate, + final ValueSetter? onScheduleDelete, final Future? rrulel10n}) = _$_ViewModelImpl; @override @@ -244,6 +265,8 @@ abstract class __ViewModel implements _ViewModel { @override ValueSetter? get onScheduleCreate; @override + ValueSetter? get onScheduleDelete; + @override Future? get rrulel10n; /// Create a copy of _ViewModel diff --git a/lib/presentation/widgets/group_events.dart b/lib/presentation/widgets/group_events.dart index b4e76851..ee9e7f9e 100644 --- a/lib/presentation/widgets/group_events.dart +++ b/lib/presentation/widgets/group_events.dart @@ -8,6 +8,7 @@ class GroupEvents extends StatelessWidget { final Group? group; // TODO is this needed? final Iterable? schedules; final ValueSetter? onCreate; + final ValueSetter? onDelete; final Future? rrulel10n; const GroupEvents({ @@ -15,22 +16,10 @@ class GroupEvents extends StatelessWidget { this.group, this.schedules, this.onCreate, + this.onDelete, this.rrulel10n, }); - _createNewEvent(BuildContext context) async { - final result = await GroupScheduleCreateRoute(groupId: group!.id.toString()) - .push(context); - - if (result is! Schedule) { - // TODO error handling - return; - } - - final newSchedule = result.copyWith(groupId: group!.id); - onCreate?.call(newSchedule); - } - @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; @@ -52,9 +41,10 @@ class GroupEvents extends StatelessWidget { } return Text(l10n.loading); }), - onTap: () { - // TODO Go to schedule edit form - }, + trailing: IconButton( + icon: Icon(Icons.delete), + onPressed: () => _deleteEvent(context, schedule), + ), ); }, ) @@ -73,4 +63,50 @@ class GroupEvents extends StatelessWidget { ), ); } + + _createNewEvent(BuildContext context) async { + final result = await GroupScheduleCreateRoute(groupId: group!.id.toString()) + .push(context); + + if (result is! Schedule) { + // TODO error handling + return; + } + + final newSchedule = result.copyWith(groupId: group!.id); + onCreate?.call(newSchedule); + } + + _deleteEvent(BuildContext context, Schedule schedule) async { + final doDelete = await showAdaptiveDialog( + context: context, + builder: (context) { + final l10n = AppLocalizations.of(context)!; + final theme = Theme.of(context); + final nav = Navigator.of(context); + + return AlertDialog.adaptive( + icon: const Icon(Icons.logout), + title: Text(l10n.deleteSchedule), + content: Text(l10n.deleteScheduleConfirmation), + actions: [ + TextButton( + onPressed: () => nav.pop(false), + child: Text(l10n.cancel), + ), + TextButton( + onPressed: () => nav.pop(true), + style: TextButton.styleFrom( + foregroundColor: theme.colorScheme.error, + ), + child: Text(l10n.delete), + ), + ], + ); + }); + + if (doDelete == null || !doDelete) return; + + onDelete?.call(schedule); + } } diff --git a/lib/repositories/members.dart b/lib/repositories/members.dart index 17cdf98d..a2366893 100644 --- a/lib/repositories/members.dart +++ b/lib/repositories/members.dart @@ -38,7 +38,7 @@ class MembersRepository extends SupabaseRepository with Postgrest { .withConverter(Member.fromJson); } - Future removeMember(int memberId) async { + Future deleteMember(int memberId) async { return table().delete().eq('id', memberId); } diff --git a/lib/repositories/schedules.dart b/lib/repositories/schedules.dart index aeae835c..afd0b40f 100644 --- a/lib/repositories/schedules.dart +++ b/lib/repositories/schedules.dart @@ -26,4 +26,8 @@ class SchedulesRepository extends SupabaseRepository with Postgrest { .eq('group_id', groupId) .withConverter((data) => data.map(Schedule.fromJson)); } + + Future deleteSchedule(String scheduleId) async { + return table().delete().eq('id', scheduleId); + } } diff --git a/test/repositories_test.dart b/test/repositories_test.dart index 2f7213e3..582ba2da 100644 --- a/test/repositories_test.dart +++ b/test/repositories_test.dart @@ -227,7 +227,7 @@ void main() { final updatedGuest = await membersRepository.updateMember( memberId: guest.id, displayNameOverride: 'A guest with a name'); - await membersRepository.removeMember(updatedGuest.id); + await membersRepository.deleteMember(updatedGuest.id); }), ), );