feat(app): undo deleting a card
Deleting a card, from the list or while studying, asks first ("Delete
flashcard?"), then offers Undo for a few seconds ("Flashcard deleted").
Meanwhile the card leaves every list and session; once the time to
undo is over, its revisions are blanked and it is deleted for good, as
notes and studysets already are (CardDeletions, a DelayedDeletions).
Undo while studying puts the card back into the round where it was.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
c5cbc53bdd
commit
2b188641c3
20 changed files with 219 additions and 44 deletions
|
|
@ -372,6 +372,9 @@
|
||||||
"cardUnbury": "Zurückstellung aufheben",
|
"cardUnbury": "Zurückstellung aufheben",
|
||||||
"cardTileBuried": "Karteikarte bis morgen zurückgestellt",
|
"cardTileBuried": "Karteikarte bis morgen zurückgestellt",
|
||||||
"cardTileUnburied": "Karteikarte nicht mehr zurückgestellt",
|
"cardTileUnburied": "Karteikarte nicht mehr zurückgestellt",
|
||||||
|
"cardDeleteTitle": "Karteikarte löschen?",
|
||||||
|
"cardDeleteMessage": "Diese Karteikarte wird aus dem Lernset entfernt. Du kannst dies für einige Sekunden rückgängig machen.",
|
||||||
|
"cardDeleted": "Karteikarte gelöscht",
|
||||||
"cardFilterTooltip": "Filter",
|
"cardFilterTooltip": "Filter",
|
||||||
"cardFilterTitle": "Filter",
|
"cardFilterTitle": "Filter",
|
||||||
"cardFilterTag": "Tag",
|
"cardFilterTag": "Tag",
|
||||||
|
|
|
||||||
|
|
@ -413,6 +413,9 @@
|
||||||
"cardUnbury": "Unbury",
|
"cardUnbury": "Unbury",
|
||||||
"cardTileBuried": "Flashcard buried until tomorrow",
|
"cardTileBuried": "Flashcard buried until tomorrow",
|
||||||
"cardTileUnburied": "Flashcard unburied",
|
"cardTileUnburied": "Flashcard unburied",
|
||||||
|
"cardDeleteTitle": "Delete flashcard?",
|
||||||
|
"cardDeleteMessage": "This flashcard will be removed from the studyset. You can undo this for a few seconds.",
|
||||||
|
"cardDeleted": "Flashcard deleted",
|
||||||
"cardFilterTooltip": "Filter",
|
"cardFilterTooltip": "Filter",
|
||||||
"cardFilterTitle": "Filter",
|
"cardFilterTitle": "Filter",
|
||||||
"cardFilterTag": "Tag",
|
"cardFilterTag": "Tag",
|
||||||
|
|
|
||||||
|
|
@ -2266,6 +2266,24 @@ abstract class AppLocalizations {
|
||||||
/// **'Flashcard unburied'**
|
/// **'Flashcard unburied'**
|
||||||
String get cardTileUnburied;
|
String get cardTileUnburied;
|
||||||
|
|
||||||
|
/// No description provided for @cardDeleteTitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Delete flashcard?'**
|
||||||
|
String get cardDeleteTitle;
|
||||||
|
|
||||||
|
/// No description provided for @cardDeleteMessage.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'This flashcard will be removed from the studyset. You can undo this for a few seconds.'**
|
||||||
|
String get cardDeleteMessage;
|
||||||
|
|
||||||
|
/// No description provided for @cardDeleted.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Flashcard deleted'**
|
||||||
|
String get cardDeleted;
|
||||||
|
|
||||||
/// No description provided for @cardFilterTooltip.
|
/// No description provided for @cardFilterTooltip.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
|
||||||
|
|
@ -1320,6 +1320,16 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get cardTileUnburied => 'Karteikarte nicht mehr zurückgestellt';
|
String get cardTileUnburied => 'Karteikarte nicht mehr zurückgestellt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cardDeleteTitle => 'Karteikarte löschen?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cardDeleteMessage =>
|
||||||
|
'Diese Karteikarte wird aus dem Lernset entfernt. Du kannst dies für einige Sekunden rückgängig machen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cardDeleted => 'Karteikarte gelöscht';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get cardFilterTooltip => 'Filter';
|
String get cardFilterTooltip => 'Filter';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1311,6 +1311,16 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get cardTileUnburied => 'Flashcard unburied';
|
String get cardTileUnburied => 'Flashcard unburied';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cardDeleteTitle => 'Delete flashcard?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cardDeleteMessage =>
|
||||||
|
'This flashcard will be removed from the studyset. You can undo this for a few seconds.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cardDeleted => 'Flashcard deleted';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get cardFilterTooltip => 'Filter';
|
String get cardFilterTooltip => 'Filter';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@ GoRouter buildRouter({String initialLocation = RoutePaths.home}) => GoRouter(
|
||||||
create: (context) => StudySessionBloc(
|
create: (context) => StudySessionBloc(
|
||||||
person.studysets,
|
person.studysets,
|
||||||
person.cards,
|
person.cards,
|
||||||
|
person.cardDeletions,
|
||||||
person.reviews,
|
person.reviews,
|
||||||
person.tags,
|
person.tags,
|
||||||
person.settings,
|
person.settings,
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import 'package:halkyon_learn_flutter/src/feature/anki/data/anki_package_reader.
|
||||||
import 'package:halkyon_learn_flutter/src/feature/auth/data/auth_repository.dart';
|
import 'package:halkyon_learn_flutter/src/feature/auth/data/auth_repository.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/auth/data/serverpod_auth_gateway.dart';
|
import 'package:halkyon_learn_flutter/src/feature/auth/data/serverpod_auth_gateway.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/auth/model/auth_status.dart';
|
import 'package:halkyon_learn_flutter/src/feature/auth/model/auth_status.dart';
|
||||||
|
import 'package:halkyon_learn_flutter/src/feature/card/data/card_deletions.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/card/data/sync_card_gateway.dart';
|
import 'package:halkyon_learn_flutter/src/feature/card/data/sync_card_gateway.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/geo_map/data/geo_map_repository.dart';
|
import 'package:halkyon_learn_flutter/src/feature/geo_map/data/geo_map_repository.dart';
|
||||||
|
|
@ -255,6 +256,7 @@ Future<PersonDependencies> _openPerson(
|
||||||
pdfSources,
|
pdfSources,
|
||||||
);
|
);
|
||||||
final noteDeletions = NoteDeletions(notes);
|
final noteDeletions = NoteDeletions(notes);
|
||||||
|
final cardDeletions = CardDeletions(cards);
|
||||||
final pathDeletions = LearningPathDeletions(paths);
|
final pathDeletions = LearningPathDeletions(paths);
|
||||||
final stepDeletions = StepDeletions(paths);
|
final stepDeletions = StepDeletions(paths);
|
||||||
final pdfDeletions = PdfDeletions(pdfSources);
|
final pdfDeletions = PdfDeletions(pdfSources);
|
||||||
|
|
@ -273,6 +275,7 @@ Future<PersonDependencies> _openPerson(
|
||||||
assets: assets,
|
assets: assets,
|
||||||
deletions: deletions,
|
deletions: deletions,
|
||||||
noteDeletions: noteDeletions,
|
noteDeletions: noteDeletions,
|
||||||
|
cardDeletions: cardDeletions,
|
||||||
pathDeletions: pathDeletions,
|
pathDeletions: pathDeletions,
|
||||||
stepDeletions: stepDeletions,
|
stepDeletions: stepDeletions,
|
||||||
pdfDeletions: pdfDeletions,
|
pdfDeletions: pdfDeletions,
|
||||||
|
|
@ -285,6 +288,7 @@ Future<PersonDependencies> _openPerson(
|
||||||
// finish while their repositories are open.
|
// finish while their repositories are open.
|
||||||
await deletions.dispose();
|
await deletions.dispose();
|
||||||
await noteDeletions.dispose();
|
await noteDeletions.dispose();
|
||||||
|
await cardDeletions.dispose();
|
||||||
await pathDeletions.dispose();
|
await pathDeletions.dispose();
|
||||||
await stepDeletions.dispose();
|
await stepDeletions.dispose();
|
||||||
await pdfDeletions.dispose();
|
await pdfDeletions.dispose();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:halkyon_learn_flutter/src/core/asset/data/asset_repository.dart';
|
import 'package:halkyon_learn_flutter/src/core/asset/data/asset_repository.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/core/sync/sync_service.dart';
|
import 'package:halkyon_learn_flutter/src/core/sync/sync_service.dart';
|
||||||
|
import 'package:halkyon_learn_flutter/src/feature/card/data/card_deletions.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/learning_path/data/learning_path_deletions.dart';
|
import 'package:halkyon_learn_flutter/src/feature/learning_path/data/learning_path_deletions.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/learning_path/data/learning_path_repository.dart';
|
import 'package:halkyon_learn_flutter/src/feature/learning_path/data/learning_path_repository.dart';
|
||||||
|
|
@ -60,6 +61,9 @@ class PersonDependencies({
|
||||||
/// The note deletions that can still be undone.
|
/// The note deletions that can still be undone.
|
||||||
required final NoteDeletions noteDeletions,
|
required final NoteDeletions noteDeletions,
|
||||||
|
|
||||||
|
/// The card deletions that can still be undone.
|
||||||
|
required final CardDeletions cardDeletions,
|
||||||
|
|
||||||
/// The learning path deletions that can still be undone.
|
/// The learning path deletions that can still be undone.
|
||||||
required final LearningPathDeletions pathDeletions,
|
required final LearningPathDeletions pathDeletions,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:halkyon_learn_flutter/src/feature/card/data/card_deletions.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/card/model/card.dart';
|
import 'package:halkyon_learn_flutter/src/feature/card/model/card.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/card/model/card_content.dart';
|
import 'package:halkyon_learn_flutter/src/feature/card/model/card_content.dart';
|
||||||
|
|
@ -17,14 +18,12 @@ class CardListBloc(
|
||||||
final CardRepository _cards,
|
final CardRepository _cards,
|
||||||
final TagRepository _tags,
|
final TagRepository _tags,
|
||||||
final ReviewRepository _reviews,
|
final ReviewRepository _reviews,
|
||||||
|
final CardDeletions _deletions,
|
||||||
final String _studysetId,
|
final String _studysetId,
|
||||||
) extends Bloc<CardListEvent, CardListState> {
|
) extends Bloc<CardListEvent, CardListState> {
|
||||||
this : super(const CardListStateLoading()) {
|
this : super(const CardListStateLoading()) {
|
||||||
on<CardListEventStarted>(_onStarted);
|
on<CardListEventStarted>(_onStarted);
|
||||||
on<CardListEventMoved>(_onMoved);
|
on<CardListEventMoved>(_onMoved);
|
||||||
on<CardListEventDeleteRequested>(
|
|
||||||
(event, emit) => _cards.delete(event.card),
|
|
||||||
);
|
|
||||||
on<CardListEventReported>(
|
on<CardListEventReported>(
|
||||||
(event, emit) => _cards.saveEdit(
|
(event, emit) => _cards.saveEdit(
|
||||||
event.card.id,
|
event.card.id,
|
||||||
|
|
@ -59,6 +58,9 @@ class CardListBloc(
|
||||||
List<Card>? _cardsNow;
|
List<Card>? _cardsNow;
|
||||||
var _tagging = Tagging.empty;
|
var _tagging = Tagging.empty;
|
||||||
var _buried = <String>{};
|
var _buried = <String>{};
|
||||||
|
|
||||||
|
/// The cards whose deletion can still be undone: they don't show.
|
||||||
|
var _deleted = <String>{};
|
||||||
String? _tagFilter;
|
String? _tagFilter;
|
||||||
|
|
||||||
Future<void> _onStarted(
|
Future<void> _onStarted(
|
||||||
|
|
@ -79,6 +81,15 @@ class CardListBloc(
|
||||||
_reviews.changes,
|
_reviews.changes,
|
||||||
onData: (_) => add(const CardListEventBuriedRead()),
|
onData: (_) => add(const CardListEventBuriedRead()),
|
||||||
),
|
),
|
||||||
|
emit.onEach(
|
||||||
|
_deletions.watchPending(),
|
||||||
|
onData: (pending) {
|
||||||
|
_deleted = pending;
|
||||||
|
if (_loaded() case final loaded?) {
|
||||||
|
emit(loaded);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
emit.forEach(
|
emit.forEach(
|
||||||
_tags.watch(),
|
_tags.watch(),
|
||||||
onData: (tagging) {
|
onData: (tagging) {
|
||||||
|
|
@ -99,7 +110,10 @@ class CardListBloc(
|
||||||
/// The list as it stands, or null while the cards are being read.
|
/// The list as it stands, or null while the cards are being read.
|
||||||
CardListStateLoaded? _loaded() => switch (_cardsNow) {
|
CardListStateLoaded? _loaded() => switch (_cardsNow) {
|
||||||
final cards? => CardListStateLoaded(
|
final cards? => CardListStateLoaded(
|
||||||
cards,
|
[
|
||||||
|
for (final card in cards)
|
||||||
|
if (!_deleted.contains(card.id)) card,
|
||||||
|
],
|
||||||
tagging: _tagging,
|
tagging: _tagging,
|
||||||
tagFilter: _tagFilter,
|
tagFilter: _tagFilter,
|
||||||
buried: _buried,
|
buried: _buried,
|
||||||
|
|
@ -129,15 +143,6 @@ final class const CardListEventMoved(
|
||||||
List<Object?> get props => [card, toIndex];
|
List<Object?> get props => [card, toIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete [card]; the person has confirmed it.
|
|
||||||
final class const CardListEventDeleteRequested(
|
|
||||||
/// The card to delete.
|
|
||||||
final Card card,
|
|
||||||
) extends CardListEvent {
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [card];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Show only the cards with the tag [tagId], or all when it is null.
|
/// Show only the cards with the tag [tagId], or all when it is null.
|
||||||
final class const CardListEventTagFilterChanged(
|
final class const CardListEventTagFilterChanged(
|
||||||
/// The tag's id.
|
/// The tag's id.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import 'package:halkyon_learn_flutter/src/core/async/delayed_deletions.dart';
|
||||||
|
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
||||||
|
|
||||||
|
/// The person's card deletions, which can be undone for a moment (see
|
||||||
|
/// [DelayedDeletions]). Once the time to undo is over, the card's revisions
|
||||||
|
/// are blanked and it is deleted.
|
||||||
|
class CardDeletions(CardRepository cards, {super.undoWindow})
|
||||||
|
extends DelayedDeletions {
|
||||||
|
this : super(cards.deleteById, logName: 'Cards');
|
||||||
|
}
|
||||||
|
|
@ -245,9 +245,12 @@ class CardRepository(final CardGateway _gateway) {
|
||||||
|
|
||||||
/// Deletes [card]. Its revisions are blanked first: the sync engine keeps
|
/// Deletes [card]. Its revisions are blanked first: the sync engine keeps
|
||||||
/// deleted rows until the whole space is deleted (ADR 0002, rule 2).
|
/// deleted rows until the whole space is deleted (ADR 0002, rule 2).
|
||||||
Future<void> delete(Card card) async {
|
Future<void> delete(Card card) => deleteById(card.id);
|
||||||
await _gateway.blankRevisions(card.id);
|
|
||||||
await _gateway.deleteCard(card.id);
|
/// Deletes the card [id]: its revisions blanked first (ADR 0002, rule 2).
|
||||||
|
Future<void> deleteById(String id) async {
|
||||||
|
await _gateway.blankRevisions(id);
|
||||||
|
await _gateway.deleteCard(id);
|
||||||
_changes.add(null);
|
_changes.add(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:halkyon_learn_flutter/l10n/app_localizations.dart';
|
import 'package:halkyon_learn_flutter/l10n/app_localizations.dart';
|
||||||
|
import 'package:halkyon_learn_flutter/src/core/dependencies/person_scope.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
|
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/core/widget/adaptive_sheet.dart';
|
import 'package:halkyon_learn_flutter/src/core/widget/adaptive_sheet.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/core/widget/confirm_dialog.dart';
|
import 'package:halkyon_learn_flutter/src/core/widget/confirm_dialog.dart';
|
||||||
|
|
@ -40,9 +41,13 @@ class const CardList({
|
||||||
}) extends StatelessWidget {
|
}) extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => BlocProvider(
|
Widget build(BuildContext context) => BlocProvider(
|
||||||
create: (context) =>
|
create: (context) => CardListBloc(
|
||||||
CardListBloc(cards, tags, reviews, studysetId)
|
cards,
|
||||||
..add(const CardListEventStarted()),
|
tags,
|
||||||
|
reviews,
|
||||||
|
PersonScope.of(context).cardDeletions,
|
||||||
|
studysetId,
|
||||||
|
)..add(const CardListEventStarted()),
|
||||||
child: BlocBuilder<CardListBloc, CardListState>(
|
child: BlocBuilder<CardListBloc, CardListState>(
|
||||||
builder: (context, state) => switch (state) {
|
builder: (context, state) => switch (state) {
|
||||||
CardListStateLoading() => const SizedBox.shrink(),
|
CardListStateLoading() => const SizedBox.shrink(),
|
||||||
|
|
@ -321,16 +326,24 @@ class const _Cards(
|
||||||
_bury(context, card, buried: buried.contains(card.id)),
|
_bury(context, card, buried: buried.contains(card.id)),
|
||||||
onOpen: () => context.push(RoutePaths.card(studysetId, card.id)),
|
onOpen: () => context.push(RoutePaths.card(studysetId, card.id)),
|
||||||
onDelete: () async {
|
onDelete: () async {
|
||||||
final bloc = context.read<CardListBloc>();
|
|
||||||
final l10n = AppLocalizations.of(context);
|
final l10n = AppLocalizations.of(context);
|
||||||
if (await confirmDestructiveAction(
|
final deletions = PersonScope.of(context).cardDeletions;
|
||||||
context,
|
if (!await confirmDestructiveAction(
|
||||||
title: l10n.composerDeleteTitle,
|
context,
|
||||||
message: l10n.composerDeleteSavedMessage,
|
title: l10n.cardDeleteTitle,
|
||||||
confirmLabel: l10n.composerDelete,
|
message: l10n.cardDeleteMessage,
|
||||||
)) {
|
confirmLabel: l10n.cardTileDelete,
|
||||||
bloc.add(CardListEventDeleteRequested(card));
|
) ||
|
||||||
|
!context.mounted) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
// The deletion outlives the tile.
|
||||||
|
deletions.delete(card.id);
|
||||||
|
showUndoSnackBar(
|
||||||
|
context,
|
||||||
|
message: l10n.cardDeleted,
|
||||||
|
onUndo: () => deletions.undo(card.id),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import 'dart:math' as math;
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:halkyon_learn_flutter/src/feature/card/data/card_deletions.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/card/model/card.dart';
|
import 'package:halkyon_learn_flutter/src/feature/card/model/card.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/card/model/card_content.dart';
|
import 'package:halkyon_learn_flutter/src/feature/card/model/card_content.dart';
|
||||||
|
|
@ -35,6 +36,7 @@ import 'package:halkyon_learn_flutter/src/feature/tag/model/tagging.dart';
|
||||||
class StudySessionBloc(
|
class StudySessionBloc(
|
||||||
final StudysetRepository _studysets,
|
final StudysetRepository _studysets,
|
||||||
final CardRepository _cards,
|
final CardRepository _cards,
|
||||||
|
final CardDeletions _deletions,
|
||||||
final ReviewRepository _reviews,
|
final ReviewRepository _reviews,
|
||||||
final TagRepository _tags,
|
final TagRepository _tags,
|
||||||
final SettingsRepository _settings,
|
final SettingsRepository _settings,
|
||||||
|
|
@ -67,6 +69,7 @@ class StudySessionBloc(
|
||||||
on<StudySessionEventCardEdited>(_onCardEdited);
|
on<StudySessionEventCardEdited>(_onCardEdited);
|
||||||
on<StudySessionEventReported>(_onReported);
|
on<StudySessionEventReported>(_onReported);
|
||||||
on<StudySessionEventCardDeleted>(_onCardDeleted);
|
on<StudySessionEventCardDeleted>(_onCardDeleted);
|
||||||
|
on<StudySessionEventDeleteUndone>(_onDeleteUndone);
|
||||||
on<StudySessionEventBackFirstToggled>((event, emit) async {
|
on<StudySessionEventBackFirstToggled>((event, emit) async {
|
||||||
// A session focused on a side turns for itself; the settings stay as
|
// A session focused on a side turns for itself; the settings stay as
|
||||||
// they are.
|
// they are.
|
||||||
|
|
@ -103,6 +106,9 @@ class StudySessionBloc(
|
||||||
|
|
||||||
/// The card set aside last in this round, and when, to undo it.
|
/// The card set aside last in this round, and when, to undo it.
|
||||||
({Card card, DateTime at, int index})? _lastBuried;
|
({Card card, DateTime at, int index})? _lastBuried;
|
||||||
|
|
||||||
|
/// The card deleted last in this round, and where it was, to undo it.
|
||||||
|
({Card card, int index})? _lastDeleted;
|
||||||
final _ratings = <String, Rating>{};
|
final _ratings = <String, Rating>{};
|
||||||
final _graduated = <String>{};
|
final _graduated = <String>{};
|
||||||
var _cramIndex = 0;
|
var _cramIndex = 0;
|
||||||
|
|
@ -159,6 +165,7 @@ class StudySessionBloc(
|
||||||
_graduated.clear();
|
_graduated.clear();
|
||||||
_undoable.clear();
|
_undoable.clear();
|
||||||
_lastBuried = null;
|
_lastBuried = null;
|
||||||
|
_lastDeleted = null;
|
||||||
_cramIndex = 0;
|
_cramIndex = 0;
|
||||||
final groups = await _groups();
|
final groups = await _groups();
|
||||||
_cardsById = {
|
_cardsById = {
|
||||||
|
|
@ -245,7 +252,9 @@ class StudySessionBloc(
|
||||||
for (final studysetId in studied) {
|
for (final studysetId in studied) {
|
||||||
final group = [
|
final group = [
|
||||||
for (final card in await _cards.cardsOf(studysetId))
|
for (final card in await _cards.cardsOf(studysetId))
|
||||||
if (tagged == null || tagged.contains(card.id)) card,
|
if ((tagged == null || tagged.contains(card.id)) &&
|
||||||
|
!_deletions.pending.contains(card.id))
|
||||||
|
card,
|
||||||
];
|
];
|
||||||
for (final card in group) {
|
for (final card in group) {
|
||||||
_studysetOf[card.id] = studysetId;
|
_studysetOf[card.id] = studysetId;
|
||||||
|
|
@ -554,8 +563,30 @@ class StudySessionBloc(
|
||||||
Emitter<StudySessionState> emit,
|
Emitter<StudySessionState> emit,
|
||||||
) async {
|
) async {
|
||||||
if (state case final StudySessionStateShowing showing) {
|
if (state case final StudySessionStateShowing showing) {
|
||||||
await _cards.delete(showing.card);
|
final card = showing.card;
|
||||||
await _leave(emit, showing.card.id);
|
// Deleted for good once the time to undo it is over.
|
||||||
|
_deletions.delete(card.id);
|
||||||
|
_lastDeleted = (card: card, index: _round.indexOf(card.id));
|
||||||
|
await _leave(emit, card.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onDeleteUndone(
|
||||||
|
StudySessionEventDeleteUndone event,
|
||||||
|
Emitter<StudySessionState> emit,
|
||||||
|
) {
|
||||||
|
final deleted = _lastDeleted;
|
||||||
|
_lastDeleted = null;
|
||||||
|
if (deleted == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final (:card, :index) = deleted;
|
||||||
|
_deletions.undo(card.id);
|
||||||
|
// Back where it was in the round under way; the card on screen stays.
|
||||||
|
if (state case final StudySessionStateShowing showing) {
|
||||||
|
_cardsById[card.id] = card;
|
||||||
|
_round.insert(index.clamp(0, _round.length), card.id);
|
||||||
|
_reshow(emit, showing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -757,6 +788,9 @@ final class const StudySessionEventReported(
|
||||||
List<Object?> get props => [report];
|
List<Object?> get props => [report];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Take back deleting the last card: it is studied again.
|
||||||
|
final class const StudySessionEventDeleteUndone() extends StudySessionEvent;
|
||||||
|
|
||||||
/// Delete the card on screen, and go on with the next.
|
/// Delete the card on screen, and go on with the next.
|
||||||
final class const StudySessionEventCardDeleted() extends StudySessionEvent;
|
final class const StudySessionEventCardDeleted() extends StudySessionEvent;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -286,12 +286,22 @@ class const _Showing(
|
||||||
onDelete: () async {
|
onDelete: () async {
|
||||||
final bloc = context.read<StudySessionBloc>();
|
final bloc = context.read<StudySessionBloc>();
|
||||||
if (await confirmDestructiveAction(
|
if (await confirmDestructiveAction(
|
||||||
context,
|
context,
|
||||||
title: l10n.composerDeleteTitle,
|
title: l10n.cardDeleteTitle,
|
||||||
message: l10n.composerDeleteSavedMessage,
|
message: l10n.cardDeleteMessage,
|
||||||
confirmLabel: l10n.composerDelete,
|
confirmLabel: l10n.cardTileDelete,
|
||||||
)) {
|
) &&
|
||||||
|
context.mounted) {
|
||||||
bloc.add(const StudySessionEventCardDeleted());
|
bloc.add(const StudySessionEventCardDeleted());
|
||||||
|
showUndoSnackBar(
|
||||||
|
context,
|
||||||
|
message: l10n.cardDeleted,
|
||||||
|
onUndo: () {
|
||||||
|
if (!bloc.isClosed) {
|
||||||
|
bloc.add(const StudySessionEventDeleteUndone());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import 'package:halkyon_learn_flutter/src/core/dependencies/person_dependencies.
|
||||||
import 'package:halkyon_learn_flutter/src/core/dependencies/person_scope.dart';
|
import 'package:halkyon_learn_flutter/src/core/dependencies/person_scope.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/core/sync/sync_service.dart';
|
import 'package:halkyon_learn_flutter/src/core/sync/sync_service.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/auth/model/auth_status.dart';
|
import 'package:halkyon_learn_flutter/src/feature/auth/model/auth_status.dart';
|
||||||
|
import 'package:halkyon_learn_flutter/src/feature/card/data/card_deletions.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/learning_path/data/learning_path_deletions.dart';
|
import 'package:halkyon_learn_flutter/src/feature/learning_path/data/learning_path_deletions.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/learning_path/data/learning_path_repository.dart';
|
import 'package:halkyon_learn_flutter/src/feature/learning_path/data/learning_path_repository.dart';
|
||||||
|
|
@ -62,6 +63,7 @@ PersonDependencies personDependencies({Future<void> Function()? onDispose}) {
|
||||||
assets: assets,
|
assets: assets,
|
||||||
deletions: StudysetDeletions(studysets, cards, notes, paths, pdfSources),
|
deletions: StudysetDeletions(studysets, cards, notes, paths, pdfSources),
|
||||||
noteDeletions: NoteDeletions(notes),
|
noteDeletions: NoteDeletions(notes),
|
||||||
|
cardDeletions: CardDeletions(cards),
|
||||||
pathDeletions: LearningPathDeletions(paths),
|
pathDeletions: LearningPathDeletions(paths),
|
||||||
stepDeletions: StepDeletions(paths),
|
stepDeletions: StepDeletions(paths),
|
||||||
pdfDeletions: PdfDeletions(pdfSources),
|
pdfDeletions: PdfDeletions(pdfSources),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/core/content/rich_content.dart';
|
import 'package:halkyon_learn_flutter/src/core/content/rich_content.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/card/bloc/card_list_bloc.dart';
|
import 'package:halkyon_learn_flutter/src/feature/card/bloc/card_list_bloc.dart';
|
||||||
|
import 'package:halkyon_learn_flutter/src/feature/card/data/card_deletions.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/card/model/card_content.dart';
|
import 'package:halkyon_learn_flutter/src/feature/card/model/card_content.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/study/data/review_repository.dart';
|
import 'package:halkyon_learn_flutter/src/feature/study/data/review_repository.dart';
|
||||||
|
|
@ -22,14 +23,17 @@ void main() {
|
||||||
late CardRepository cards;
|
late CardRepository cards;
|
||||||
late TagRepository tags;
|
late TagRepository tags;
|
||||||
late ReviewRepository reviews;
|
late ReviewRepository reviews;
|
||||||
|
late CardDeletions deletions;
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
gateway = FakeCardGateway();
|
gateway = FakeCardGateway();
|
||||||
cards = CardRepository(gateway);
|
cards = CardRepository(gateway);
|
||||||
tags = TagRepository(FakeTagGateway(), cardChanges: cards.changes);
|
tags = TagRepository(FakeTagGateway(), cardChanges: cards.changes);
|
||||||
reviews = ReviewRepository(FakeReviewGateway());
|
reviews = ReviewRepository(FakeReviewGateway());
|
||||||
|
deletions = CardDeletions(cards, undoWindow: Duration.zero);
|
||||||
});
|
});
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
|
await deletions.dispose();
|
||||||
await reviews.dispose();
|
await reviews.dispose();
|
||||||
await tags.dispose();
|
await tags.dispose();
|
||||||
await cards.dispose();
|
await cards.dispose();
|
||||||
|
|
@ -40,7 +44,7 @@ void main() {
|
||||||
'a card reported or buried shows so, and either is taken back',
|
'a card reported or buried shows so, and either is taken back',
|
||||||
() async {
|
() async {
|
||||||
final id = await cards.create('japanese', card('猫', 'cat'));
|
final id = await cards.create('japanese', card('猫', 'cat'));
|
||||||
final bloc = CardListBloc(cards, tags, reviews, 'japanese')
|
final bloc = CardListBloc(cards, tags, reviews, deletions, 'japanese')
|
||||||
..add(const CardListEventStarted());
|
..add(const CardListEventStarted());
|
||||||
addTearDown(bloc.close);
|
addTearDown(bloc.close);
|
||||||
Future<CardListStateLoaded> next(
|
Future<CardListStateLoaded> next(
|
||||||
|
|
@ -70,7 +74,7 @@ void main() {
|
||||||
|
|
||||||
test('lists the cards and follows every change', () async {
|
test('lists the cards and follows every change', () async {
|
||||||
await cards.create('japanese', card('猫', 'cat'));
|
await cards.create('japanese', card('猫', 'cat'));
|
||||||
final bloc = CardListBloc(cards, tags, reviews, 'japanese')
|
final bloc = CardListBloc(cards, tags, reviews, deletions, 'japanese')
|
||||||
..add(const CardListEventStarted());
|
..add(const CardListEventStarted());
|
||||||
addTearDown(bloc.close);
|
addTearDown(bloc.close);
|
||||||
|
|
||||||
|
|
@ -100,7 +104,7 @@ void main() {
|
||||||
test('moves a card', () async {
|
test('moves a card', () async {
|
||||||
await cards.create('japanese', card('a', ''));
|
await cards.create('japanese', card('a', ''));
|
||||||
await cards.create('japanese', card('b', ''));
|
await cards.create('japanese', card('b', ''));
|
||||||
final bloc = CardListBloc(cards, tags, reviews, 'japanese')
|
final bloc = CardListBloc(cards, tags, reviews, deletions, 'japanese')
|
||||||
..add(const CardListEventStarted());
|
..add(const CardListEventStarted());
|
||||||
addTearDown(bloc.close);
|
addTearDown(bloc.close);
|
||||||
final loaded = await bloc.stream.firstWhere(
|
final loaded = await bloc.stream.firstWhere(
|
||||||
|
|
@ -137,7 +141,7 @@ void main() {
|
||||||
await tags.setTagsOf(ids[0], {noun});
|
await tags.setTagsOf(ids[0], {noun});
|
||||||
await tags.setTagsOf(ids[1], {noun});
|
await tags.setTagsOf(ids[1], {noun});
|
||||||
await tags.setTagsOf(ids[2], {verb});
|
await tags.setTagsOf(ids[2], {verb});
|
||||||
bloc = CardListBloc(cards, tags, reviews, 'japanese')
|
bloc = CardListBloc(cards, tags, reviews, deletions, 'japanese')
|
||||||
..add(const CardListEventStarted());
|
..add(const CardListEventStarted());
|
||||||
});
|
});
|
||||||
tearDown(() => bloc.close());
|
tearDown(() => bloc.close());
|
||||||
|
|
|
||||||
|
|
@ -110,10 +110,8 @@ void main() {
|
||||||
expect(find.text('Changed on two devices'), findsOneWidget);
|
expect(find.text('Changed on two devices'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets("a card's options delete it, after asking", (tester) async {
|
/// Deletes the card shown through its options, saying yes.
|
||||||
final id = await cards.create('japanese', card('猫', 'cat'));
|
Future<void> delete(WidgetTester tester) async {
|
||||||
await showList(tester);
|
|
||||||
|
|
||||||
await tester.tap(
|
await tester.tap(
|
||||||
find.descendant(
|
find.descendant(
|
||||||
of: find.byType(CardTile),
|
of: find.byType(CardTile),
|
||||||
|
|
@ -126,11 +124,38 @@ void main() {
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.text('Delete flashcard?'), findsOneWidget);
|
||||||
await tester.tap(find.widgetWithText(FilledButton, 'Delete'));
|
await tester.tap(find.widgetWithText(FilledButton, 'Delete'));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
expect(gateway.deletions, ['blank $id', 'delete $id']);
|
testWidgets("a card's options delete it, after asking; once the time to "
|
||||||
|
'undo is over, for good', (tester) async {
|
||||||
|
final id = await cards.create('japanese', card('猫', 'cat'));
|
||||||
|
await showList(tester);
|
||||||
|
|
||||||
|
await delete(tester);
|
||||||
|
|
||||||
|
expect(find.text('Flashcard deleted'), findsOneWidget);
|
||||||
expect(find.text('No flashcards yet'), findsOneWidget);
|
expect(find.text('No flashcards yet'), findsOneWidget);
|
||||||
|
expect(gateway.deletions, isEmpty);
|
||||||
|
await tester.pump(const Duration(seconds: 5));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(gateway.deletions, ['blank $id', 'delete $id']);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Undo keeps a card deleted a moment ago', (tester) async {
|
||||||
|
await cards.create('japanese', card('猫', 'cat'));
|
||||||
|
await showList(tester);
|
||||||
|
|
||||||
|
await delete(tester);
|
||||||
|
await tester.tap(find.text('Undo'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.pump(const Duration(seconds: 5));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('猫'), findsOneWidget);
|
||||||
|
expect(gateway.deletions, isEmpty);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets("a card's options report it and bury it, each shown on the "
|
testWidgets("a card's options report it and bury it, each shown on the "
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'dart:math' as math;
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/core/content/rich_content.dart';
|
import 'package:halkyon_learn_flutter/src/core/content/rich_content.dart';
|
||||||
|
import 'package:halkyon_learn_flutter/src/feature/card/data/card_deletions.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/card/model/card_content.dart';
|
import 'package:halkyon_learn_flutter/src/feature/card/model/card_content.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/settings/data/settings_repository.dart';
|
import 'package:halkyon_learn_flutter/src/feature/settings/data/settings_repository.dart';
|
||||||
|
|
@ -35,6 +36,7 @@ void main() {
|
||||||
late FakeStudysetGateway studysetGateway;
|
late FakeStudysetGateway studysetGateway;
|
||||||
late StudysetRepository studysets;
|
late StudysetRepository studysets;
|
||||||
late CardRepository cards;
|
late CardRepository cards;
|
||||||
|
late CardDeletions deletions;
|
||||||
late FakeReviewGateway reviewGateway;
|
late FakeReviewGateway reviewGateway;
|
||||||
late ReviewRepository reviews;
|
late ReviewRepository reviews;
|
||||||
late TagRepository tags;
|
late TagRepository tags;
|
||||||
|
|
@ -49,6 +51,7 @@ void main() {
|
||||||
);
|
);
|
||||||
studysets = StudysetRepository(studysetGateway);
|
studysets = StudysetRepository(studysetGateway);
|
||||||
cards = CardRepository(FakeCardGateway());
|
cards = CardRepository(FakeCardGateway());
|
||||||
|
deletions = CardDeletions(cards, undoWindow: Duration.zero);
|
||||||
reviewGateway = FakeReviewGateway();
|
reviewGateway = FakeReviewGateway();
|
||||||
reviews = ReviewRepository(reviewGateway);
|
reviews = ReviewRepository(reviewGateway);
|
||||||
tags = TagRepository(FakeTagGateway());
|
tags = TagRepository(FakeTagGateway());
|
||||||
|
|
@ -56,6 +59,7 @@ void main() {
|
||||||
now = DateTime.utc(2026, 10, 5, 9);
|
now = DateTime.utc(2026, 10, 5, 9);
|
||||||
});
|
});
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
|
await deletions.dispose();
|
||||||
await studysets.dispose();
|
await studysets.dispose();
|
||||||
await cards.dispose();
|
await cards.dispose();
|
||||||
await tags.dispose();
|
await tags.dispose();
|
||||||
|
|
@ -66,6 +70,7 @@ void main() {
|
||||||
StudySessionBloc session() => StudySessionBloc(
|
StudySessionBloc session() => StudySessionBloc(
|
||||||
studysets,
|
studysets,
|
||||||
cards,
|
cards,
|
||||||
|
deletions,
|
||||||
reviews,
|
reviews,
|
||||||
tags,
|
tags,
|
||||||
settings,
|
settings,
|
||||||
|
|
@ -188,6 +193,8 @@ void main() {
|
||||||
bloc.add(const StudySessionEventCardDeleted());
|
bloc.add(const StudySessionEventCardDeleted());
|
||||||
expect((await showing(bloc, '犬')).pills, hasLength(1));
|
expect((await showing(bloc, '犬')).pills, hasLength(1));
|
||||||
});
|
});
|
||||||
|
// Deleted for good once the time to undo it is over.
|
||||||
|
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
(await cards.cardsOf('japanese')).map((each) => each.content.front),
|
(await cards.cardsOf('japanese')).map((each) => each.content.front),
|
||||||
|
|
@ -794,6 +801,7 @@ void main() {
|
||||||
StudySessionBloc focused(StudyFocus focus) => StudySessionBloc(
|
StudySessionBloc focused(StudyFocus focus) => StudySessionBloc(
|
||||||
studysets,
|
studysets,
|
||||||
cards,
|
cards,
|
||||||
|
deletions,
|
||||||
reviews,
|
reviews,
|
||||||
tags,
|
tags,
|
||||||
settings,
|
settings,
|
||||||
|
|
|
||||||
|
|
@ -253,7 +253,11 @@ void main() {
|
||||||
await tester.tap(find.widgetWithText(FilledButton, 'Delete'));
|
await tester.tap(find.widgetWithText(FilledButton, 'Delete'));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('Flashcard deleted'), findsOneWidget);
|
||||||
expect(find.textContaining('犬', findRichText: true), findsOneWidget);
|
expect(find.textContaining('犬', findRichText: true), findsOneWidget);
|
||||||
|
// For good once the time to undo it is over.
|
||||||
|
await tester.pump(const Duration(seconds: 5));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
expect(await cards.cardsOf('japanese'), hasLength(1));
|
expect(await cards.cardsOf('japanese'), hasLength(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import 'package:halkyon_learn_flutter/src/feature/anki/data/anki_package.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/anki/data/anki_package_reader.dart';
|
import 'package:halkyon_learn_flutter/src/feature/anki/data/anki_package_reader.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/auth/data/auth_gateway.dart';
|
import 'package:halkyon_learn_flutter/src/feature/auth/data/auth_gateway.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/auth/data/auth_repository.dart';
|
import 'package:halkyon_learn_flutter/src/feature/auth/data/auth_repository.dart';
|
||||||
|
import 'package:halkyon_learn_flutter/src/feature/card/data/card_deletions.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/card/data/card_gateway.dart';
|
import 'package:halkyon_learn_flutter/src/feature/card/data/card_gateway.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
||||||
import 'package:halkyon_learn_flutter/src/feature/geo_map/data/geo_map_repository.dart';
|
import 'package:halkyon_learn_flutter/src/feature/geo_map/data/geo_map_repository.dart';
|
||||||
|
|
@ -170,6 +171,7 @@ Dependencies testDependencies({
|
||||||
pdfSources,
|
pdfSources,
|
||||||
);
|
);
|
||||||
final noteDeletions = NoteDeletions(notes);
|
final noteDeletions = NoteDeletions(notes);
|
||||||
|
final cardDeletions = CardDeletions(cards);
|
||||||
final pathDeletions = LearningPathDeletions(paths);
|
final pathDeletions = LearningPathDeletions(paths);
|
||||||
final stepDeletions = StepDeletions(paths);
|
final stepDeletions = StepDeletions(paths);
|
||||||
final pdfDeletions = PdfDeletions(pdfSources);
|
final pdfDeletions = PdfDeletions(pdfSources);
|
||||||
|
|
@ -187,6 +189,7 @@ Dependencies testDependencies({
|
||||||
assets: assets,
|
assets: assets,
|
||||||
deletions: deletions,
|
deletions: deletions,
|
||||||
noteDeletions: noteDeletions,
|
noteDeletions: noteDeletions,
|
||||||
|
cardDeletions: cardDeletions,
|
||||||
pathDeletions: pathDeletions,
|
pathDeletions: pathDeletions,
|
||||||
stepDeletions: stepDeletions,
|
stepDeletions: stepDeletions,
|
||||||
pdfDeletions: pdfDeletions,
|
pdfDeletions: pdfDeletions,
|
||||||
|
|
@ -194,6 +197,7 @@ Dependencies testDependencies({
|
||||||
onDispose: () async {
|
onDispose: () async {
|
||||||
await deletions.dispose();
|
await deletions.dispose();
|
||||||
await noteDeletions.dispose();
|
await noteDeletions.dispose();
|
||||||
|
await cardDeletions.dispose();
|
||||||
await pathDeletions.dispose();
|
await pathDeletions.dispose();
|
||||||
await stepDeletions.dispose();
|
await stepDeletions.dispose();
|
||||||
await pdfDeletions.dispose();
|
await pdfDeletions.dispose();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue