feat(app): learning paths in the studyset screen: worked through and written

- A Paths tab before Flashcards and Notes: each path with its title and
  secondary title, dimmed once completed; Edit, Reset progress (with an
  undo) and Delete (with an undo) behind its options. The studyset opens
  on its paths while one is unfinished, on its flashcards otherwise.
- "Create a path" in Add material asks for the titles in a sheet and
  opens the new path to write; "Import a path (JSON)" reads a file in
  the import format, says what came in and how many blocks were left out
  (images and animations: the app never fetches from the web).
- The player: one step at a time at reading width, a bar of the steps
  above, Back and "Check answers" / "Continue" / "Finish" below. It
  opens at the first unfinished step; an exercise passes only once
  answered right, as often as it takes; after a check, only the
  learner's own picks are marked, with the author's feedback; sources
  behind a chip; a finish screen leading to the next path. Words and
  items are dragged into place, or tapped into place for phones.
- Writing: the path's screen (titles, description, tags, steps added by
  kind, reordered, deleted with an undo) and the step's (title, text
  with the format bar, its kind's exercise, feedback for right and
  wrong in the whole window, sources). The step saves on what it
  started from, as notes do, read-only while devices disagree until a
  version is kept.
- The library and the topics count paths; steps done count on the
  dashboard, for the streak and the activity.
- The rich-text screen for a studyset's description moves to core, for
  the steps' feedback too, and gains the format bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-03 09:25:08 +02:00
commit e4bfe3bdeb
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -214,6 +214,371 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get studysetTabNotes => 'Notes';
@override
String get studysetTabPaths => 'Paths';
@override
String get studysetCreatePath => 'Create a path';
@override
String get pathImportMenu => 'Import a path (JSON)';
@override
String get pathImportFileType => 'Learning path (JSON)';
@override
String pathImported(String title, int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Imported \"$title\" with $count steps.',
one: 'Imported \"$title\" with 1 step.',
);
return '$_temp0';
}
@override
String pathImportSkipped(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other:
'$count blocks were left out: images and animations aren\'t imported.',
one: '1 block was left out: images and animations aren\'t imported.',
);
return '$_temp0';
}
@override
String pathImportFailed(String reason) {
return 'Couldn\'t import this file. $reason';
}
@override
String get pathImportUnreadable => 'It isn\'t text in UTF-8.';
@override
String get pathsEmpty => 'No paths yet';
@override
String get pathsEmptyHint => 'A path leads through a lesson, step by step.';
@override
String get pathOptions => 'Path options';
@override
String get pathEdit => 'Edit';
@override
String get pathResetProgress => 'Reset progress';
@override
String get pathProgressReset => 'Progress reset';
@override
String get pathDelete => 'Delete';
@override
String get pathDeleteTitle => 'Delete path?';
@override
String get pathDeleteMessage =>
'This path and its steps will be removed from the studyset. You can undo this for a few seconds.';
@override
String get pathDeleteConfirm => 'Delete';
@override
String get pathDeleted => 'Path deleted';
@override
String get pathCreateTitle => 'New path';
@override
String get pathCreate => 'Create';
@override
String get pathTitleLabel => 'Title';
@override
String get pathTitleRequired => 'A path needs a title.';
@override
String get pathSecondaryTitleLabel => 'Secondary title';
@override
String get pathSecondaryTitleHint =>
'E.g. the title in the language you learn';
@override
String get pathEditTitle => 'Edit path';
@override
String get pathDescription => 'Description';
@override
String get pathAddDescription => 'Add a description';
@override
String get pathDescriptionEditTitle => 'Path description';
@override
String get pathAddTags => 'Add tags';
@override
String get pathTags => 'Tags';
@override
String get pathSteps => 'Steps';
@override
String get pathStepsEmpty => 'No steps yet';
@override
String get pathStepsEmptyHint =>
'Pick a step type to start writing the lesson.';
@override
String get pathAddStep => 'Add step';
@override
String get pathReorder => 'Reorder';
@override
String get pathReorderDone => 'Done';
@override
String get pathStepDelete => 'Delete step';
@override
String get pathStepDeleted => 'Step deleted';
@override
String get stepUntitled => 'Untitled step';
@override
String get stepKindInfo => 'Info';
@override
String get stepKindSingleChoice => 'Single choice';
@override
String get stepKindMultipleChoice => 'Multiple choice';
@override
String get stepKindDragAndDrop => 'Drag and drop';
@override
String get stepKindFillInTheBlank => 'Fill in the blank';
@override
String get stepEditTitle => 'Edit step';
@override
String get stepTitleHint => 'Title';
@override
String get stepOptions => 'Options';
@override
String stepOptionHint(int number) {
return 'Option $number';
}
@override
String get stepAddOption => 'Add option';
@override
String get stepRemoveOption => 'Remove option';
@override
String get stepCorrectOption => 'The right one';
@override
String get stepCorrectOptions => 'Right ones';
@override
String get stepCategories => 'Categories';
@override
String stepCategoryHint(int number) {
return 'Category $number';
}
@override
String get stepAddCategory => 'Add category';
@override
String get stepRemoveCategory => 'Remove category';
@override
String get stepItems => 'Items';
@override
String stepItemHint(int number) {
return 'Item $number';
}
@override
String get stepAddItem => 'Add item';
@override
String get stepRemoveItem => 'Remove item';
@override
String get stepItemCategory => 'Belongs to';
@override
String get stepPassage => 'Passage';
@override
String get stepPassageHint =>
'Type ___ (three underscores) wherever a word should be dropped in.';
@override
String stepBlankHint(int number) {
return 'Word for gap $number';
}
@override
String get stepIncomplete =>
'Not ready to answer yet: fill in every field and choose what is right.';
@override
String get stepFeedbackRight => 'Feedback after a right answer';
@override
String get stepFeedbackWrong => 'Feedback after a wrong answer';
@override
String get stepFeedbackNone => 'None';
@override
String get stepSources => 'Sources';
@override
String get stepAddSource => 'Add source';
@override
String get stepRemoveSource => 'Remove source';
@override
String get stepSourceAuthors => 'Authors';
@override
String get stepSourceYear => 'Year';
@override
String get stepSourceTitle => 'Title';
@override
String get stepSourceUrl => 'Link';
@override
String stepSourcesCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count sources',
one: '1 source',
);
return '$_temp0';
}
@override
String get stepConflict =>
'This step was changed differently on another device. Keep one version to go on writing.';
@override
String get stepFieldTitle => 'Title';
@override
String get stepFieldBody => 'Text';
@override
String get stepFieldExercise => 'Exercise';
@override
String get stepFieldRightFeedback => 'Feedback after a right answer';
@override
String get stepFieldWrongFeedback => 'Feedback after a wrong answer';
@override
String get stepFieldSources => 'Sources';
@override
String get runnerClose => 'Close';
@override
String get runnerEditStep => 'Edit step';
@override
String get runnerBack => 'Back';
@override
String get runnerCheck => 'Check answers';
@override
String get runnerContinue => 'Continue';
@override
String get runnerFinish => 'Finish';
@override
String get runnerRight => 'Correct!';
@override
String get runnerWrong => 'Not quite';
@override
String get runnerAlreadyRight => 'You already answered this correctly.';
@override
String get runnerSelectAll => 'Select all that apply.';
@override
String get runnerDragHint =>
'Drag each item into the category it belongs to, or tap it and then the category. Tap a placed item to take it back.';
@override
String get runnerBlankHint =>
'Drag a word into each gap, or tap it and then the gap. Tap a placed word to take it back.';
@override
String get runnerEmpty => 'This path has no steps yet.';
@override
String get runnerFinished => 'You finished this path';
@override
String runnerFinishedSteps(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count steps complete.',
one: '1 step complete.',
);
return '$_temp0';
}
@override
String get runnerNextPath => 'Continue to next path';
@override
String get runnerBackToStudyset => 'Back to studyset';
@override
String studysetCountPaths(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count paths',
one: '1 path',
);
return '$_temp0';
}
@override
String get studysetCreateNote => 'Create a note';
@ -414,7 +779,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get studysetDescriptionEditTitle => 'Edit description';
@override
String get studysetDescriptionSave => 'Save';
String get richTextSave => 'Save';
@override
String get studysetSheetDescription => 'Description';