feat(learn): Add material no longer offers flashcards from a text (ADR 0021, D56 amended)
The developer's review of 4 Nov 2026. The entry goes from a studyset's Add material, and with it the sheet for pasting a text (proposeCardsFromText, _TextSheet), its three texts and proposeCards' fromStudyset, which only it set to false. A note's and a PDF page's flashcards stay; their tests now drive the proposals sheet from a note. Whats-New: Add material no longer offers Flashcards from a text; a note's or a PDF page's flashcards stay. Whats-New-De: Material hinzufügen bietet keine Karteikarten aus einem Text mehr an; die aus einer Notiz oder einer PDF-Seite bleiben. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
bc4953aaf8
commit
bda09a2959
8 changed files with 35 additions and 158 deletions
|
|
@ -1875,9 +1875,6 @@
|
|||
"proposalsFromNote": "Karteikarten aus dieser Notiz",
|
||||
"proposalsFromPage": "Aus dieser Seite",
|
||||
"proposalsFromSelection": "Aus der Auswahl",
|
||||
"proposalsFromText": "Karteikarten aus einem Text",
|
||||
"proposalsTextHint": "Füg den Text ein, aus dem Karteikarten werden sollen",
|
||||
"proposalsPropose": "Karteikarten vorschlagen",
|
||||
"settingsSearchEngine": "Suchmaschine",
|
||||
"searchEngineDevice": "Suche des Geräts",
|
||||
"searchEngineCustom": "Eigene Adresse",
|
||||
|
|
|
|||
|
|
@ -2248,10 +2248,6 @@
|
|||
"@proposalsFromPage": {"description": "In a PDF's flashcards: has an assistant propose cards from the page shown (D56)."},
|
||||
"proposalsFromSelection": "From the selection",
|
||||
"@proposalsFromSelection": {"description": "In a PDF's flashcards: has an assistant propose cards from the text selected (D56)."},
|
||||
"proposalsFromText": "Flashcards from a text",
|
||||
"@proposalsFromText": {"description": "Add material: has an assistant propose cards from text pasted (D56)."},
|
||||
"proposalsTextHint": "Paste the text to make flashcards from",
|
||||
"proposalsPropose": "Propose flashcards",
|
||||
"settingsSearchEngine": "Search engine",
|
||||
"searchEngineDevice": "The device's search",
|
||||
"searchEngineCustom": "Your own address",
|
||||
|
|
|
|||
|
|
@ -11090,24 +11090,6 @@ abstract class AppLocalizations {
|
|||
/// **'From the selection'**
|
||||
String get proposalsFromSelection;
|
||||
|
||||
/// Add material: has an assistant propose cards from text pasted (D56).
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Flashcards from a text'**
|
||||
String get proposalsFromText;
|
||||
|
||||
/// No description provided for @proposalsTextHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Paste the text to make flashcards from'**
|
||||
String get proposalsTextHint;
|
||||
|
||||
/// No description provided for @proposalsPropose.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Propose flashcards'**
|
||||
String get proposalsPropose;
|
||||
|
||||
/// No description provided for @settingsSearchEngine.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -7054,16 +7054,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get proposalsFromSelection => 'Aus der Auswahl';
|
||||
|
||||
@override
|
||||
String get proposalsFromText => 'Karteikarten aus einem Text';
|
||||
|
||||
@override
|
||||
String get proposalsTextHint =>
|
||||
'Füg den Text ein, aus dem Karteikarten werden sollen';
|
||||
|
||||
@override
|
||||
String get proposalsPropose => 'Karteikarten vorschlagen';
|
||||
|
||||
@override
|
||||
String get settingsSearchEngine => 'Suchmaschine';
|
||||
|
||||
|
|
|
|||
|
|
@ -6991,15 +6991,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get proposalsFromSelection => 'From the selection';
|
||||
|
||||
@override
|
||||
String get proposalsFromText => 'Flashcards from a text';
|
||||
|
||||
@override
|
||||
String get proposalsTextHint => 'Paste the text to make flashcards from';
|
||||
|
||||
@override
|
||||
String get proposalsPropose => 'Propose flashcards';
|
||||
|
||||
@override
|
||||
String get settingsSearchEngine => 'Search engine';
|
||||
|
||||
|
|
|
|||
|
|
@ -26,13 +26,12 @@ bool proposesCards(BuildContext context) =>
|
|||
/// studyset [studysetId] (ADR 0021, D55), in a sheet where the person
|
||||
/// chooses them, changes them and adds them, tied to [source] where they
|
||||
/// come from a PDF's page; once [readyToAsk], [text] counting as the
|
||||
/// studyset's content where it is [fromStudyset] (a note, a page), so a
|
||||
/// private one goes only once agreed to. Says how many were added.
|
||||
/// studyset's content (a note, a page), so a private one goes only once
|
||||
/// agreed to. Says how many were added.
|
||||
Future<void> proposeCards(
|
||||
BuildContext context, {
|
||||
required String studysetId,
|
||||
required String text,
|
||||
bool fromStudyset = true,
|
||||
PdfPageRef? source,
|
||||
}) async {
|
||||
final choice = nextAssistant(context);
|
||||
|
|
@ -41,7 +40,7 @@ Future<void> proposeCards(
|
|||
}
|
||||
final scope = AssistantScope(
|
||||
studysetId: studysetId,
|
||||
partsFrom: fromStudyset ? {studysetId} : const {},
|
||||
partsFrom: {studysetId},
|
||||
);
|
||||
if (!await readyToAsk(context, choice: choice, scope: scope) ||
|
||||
!context.mounted) {
|
||||
|
|
@ -73,27 +72,6 @@ Future<void> proposeCards(
|
|||
}
|
||||
}
|
||||
|
||||
/// Asks for a text to make flashcards of, pasted or typed, then has them
|
||||
/// proposed for [studysetId] (D56): the text is the person's, not the
|
||||
/// studyset's, so nothing private is said to go.
|
||||
Future<void> proposeCardsFromText(
|
||||
BuildContext context, {
|
||||
required String studysetId,
|
||||
}) async {
|
||||
final text = await showAdaptiveSheet<String>(
|
||||
context,
|
||||
builder: (context) => const _TextSheet(),
|
||||
);
|
||||
if (text != null && context.mounted) {
|
||||
await proposeCards(
|
||||
context,
|
||||
studysetId: studysetId,
|
||||
text: text,
|
||||
fromStudyset: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The cards [name] proposes, each chosen or not, changed in a sheet of
|
||||
/// its own, and Add for those chosen, which closes with how many.
|
||||
class const _ProposalsSheet({required final String name})
|
||||
|
|
@ -276,50 +254,3 @@ class _EditSheetState() extends State<_EditSheet> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A text to make flashcards of; closes with it.
|
||||
class const _TextSheet() extends StatefulWidget {
|
||||
@override
|
||||
State<_TextSheet> createState() => _TextSheetState();
|
||||
}
|
||||
|
||||
class _TextSheetState() extends State<_TextSheet> {
|
||||
final _text = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_text.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
return SheetScaffold(
|
||||
title: l10n.proposalsFromText,
|
||||
body: TextField(
|
||||
controller: _text,
|
||||
autofocus: true,
|
||||
// As much as goes to the assistant.
|
||||
maxLength: maxProposalSource,
|
||||
buildCounter: nearLimitCounter,
|
||||
minLines: 6,
|
||||
maxLines: 14,
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.proposalsTextHint,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
footer: ListenableBuilder(
|
||||
listenable: _text,
|
||||
builder: (context, _) => FilledButton.icon(
|
||||
icon: const Icon(LucideIcons.sparkles),
|
||||
label: Text(l10n.proposalsPropose),
|
||||
onPressed: _text.text.trim().isEmpty
|
||||
? null
|
||||
: () => Navigator.of(context).pop(_text.text),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import 'package:halkyon_learn_flutter/src/feature/anki/data/anki_importer.dart';
|
|||
import 'package:halkyon_learn_flutter/src/feature/anki/widget/anki_import_sheet.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/assistant/model/assistant_topic.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/assistant/widget/assistant_field.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/assistant/widget/card_proposals_sheet.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/card/widget/card_list.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/card/widget/card_text_import_sheet.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/dictionary/model/dictionary.dart';
|
||||
|
|
@ -1394,16 +1393,6 @@ class _AddMaterialState() extends State<_AddMaterial> {
|
|||
onPressed: () => context.push(RoutePaths.jellyfin(studysetId)),
|
||||
child: Text(l10n.studysetWatch),
|
||||
),
|
||||
// Only where an assistant can be asked (D56).
|
||||
if (proposesCards(context))
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(LucideIcons.sparkles),
|
||||
onPressed: () {
|
||||
context.read<ContentTabCubit>().show(StudysetTab.flashcards);
|
||||
unawaited(proposeCardsFromText(context, studysetId: studysetId));
|
||||
},
|
||||
child: Text(l10n.proposalsFromText),
|
||||
),
|
||||
// Only for a language the instance has a dictionary of.
|
||||
if (dictionary != null)
|
||||
MenuItemButton(
|
||||
|
|
|
|||
|
|
@ -144,27 +144,28 @@ void main() {
|
|||
];
|
||||
}
|
||||
|
||||
testWidgets('Add material proposes flashcards from a text pasted: every '
|
||||
'one chosen, changed or left out, and those chosen added', (
|
||||
tester,
|
||||
) async {
|
||||
await showApp(tester, RoutePaths.studyset('japanese'));
|
||||
/// Shows a note of Japanese's holding [text], for its flashcards.
|
||||
Future<void> showNote(WidgetTester tester, String text) async {
|
||||
final notes = NoteRepository(noteGateway);
|
||||
addTearDown(notes.dispose);
|
||||
final (:noteId, revisionId: _) = await notes.create(
|
||||
'japanese',
|
||||
NoteContent(title: 'Verbs', body: RichContent.plain(text)),
|
||||
);
|
||||
await showApp(tester, RoutePaths.note('japanese', noteId));
|
||||
}
|
||||
|
||||
testWidgets("a note's proposed flashcards: every one chosen, changed or "
|
||||
'left out, and those chosen added', (tester) async {
|
||||
await showNote(tester, '食べる means to eat.');
|
||||
assistants.answers.add(
|
||||
_proposes([('食べる', 'to eat'), ('飲む', 'to drink'), ('見る', 'to see')]),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Add material'));
|
||||
await tester.pumpAndSettle();
|
||||
await tapAndSettle(tester, find.text('Flashcards from a text'));
|
||||
await tester.enterText(
|
||||
find.byType(TextField).last,
|
||||
'食べる means to eat.',
|
||||
);
|
||||
await tester.pump();
|
||||
await tapAndSettle(tester, find.text('Propose flashcards'));
|
||||
await tapAndSettle(tester, find.byTooltip('Flashcards from this note'));
|
||||
|
||||
expect(find.text('Flashcards by Claude'), findsOneWidget);
|
||||
expect(_sent(assistants.asked.single.$2), '食べる means to eat.');
|
||||
expect(_sent(assistants.asked.single.$2), 'Verbs\n\n食べる means to eat.');
|
||||
expect(find.text('Add 3 flashcards'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('飲む'));
|
||||
|
|
@ -196,7 +197,7 @@ void main() {
|
|||
testWidgets('none proposed says so, and Try again asks once more', (
|
||||
tester,
|
||||
) async {
|
||||
await showApp(tester, RoutePaths.studyset('japanese'));
|
||||
await showNote(tester, 'Some text.');
|
||||
assistants.answers
|
||||
..add([
|
||||
LlmFinished(
|
||||
|
|
@ -206,12 +207,7 @@ void main() {
|
|||
),
|
||||
])
|
||||
..add(_proposes([('Q', 'A')]));
|
||||
await tester.tap(find.text('Add material'));
|
||||
await tester.pumpAndSettle();
|
||||
await tapAndSettle(tester, find.text('Flashcards from a text'));
|
||||
await tester.enterText(find.byType(TextField).last, 'Some text.');
|
||||
await tester.pump();
|
||||
await tapAndSettle(tester, find.text('Propose flashcards'));
|
||||
await tapAndSettle(tester, find.byTooltip('Flashcards from this note'));
|
||||
|
||||
expect(find.text('Claude proposed no flashcards.'), findsOneWidget);
|
||||
await tapAndSettle(tester, find.text('Try again'));
|
||||
|
|
@ -282,6 +278,16 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
testWidgets('Add material offers no flashcards from a text (the '
|
||||
"developer's review of 4 Nov 2026): a note's, a page's are enough", (
|
||||
tester,
|
||||
) async {
|
||||
await showApp(tester, RoutePaths.studyset('japanese'));
|
||||
await tester.tap(find.text('Add material'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Flashcards from a text'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('nothing offers flashcards without an assistant', (
|
||||
tester,
|
||||
) async {
|
||||
|
|
@ -292,13 +298,6 @@ void main() {
|
|||
'japanese',
|
||||
NoteContent(title: 'Kana', body: RichContent.plain('あ is a.')),
|
||||
);
|
||||
await showApp(tester, RoutePaths.studyset('japanese'));
|
||||
await tester.tap(find.text('Add material'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Flashcards from a text'), findsNothing);
|
||||
await tester.tapAt(Offset.zero);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await showApp(tester, RoutePaths.note('japanese', noteId));
|
||||
expect(find.byTooltip('Flashcards from this note'), findsNothing);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue