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:
parent
91f6376b93
commit
e4bfe3bdeb
1 changed files with 6310 additions and 65 deletions
|
|
@ -10,6 +10,12 @@ import 'package:halkyon_learn_flutter/src/feature/card/bloc/card_composer_bloc.d
|
|||
import 'package:halkyon_learn_flutter/src/feature/card/widget/composer/card_composer_screen.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/explore/widget/explore_screen.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/home/widget/home_screen.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/learning_path/bloc/path_editor_bloc.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/learning_path/bloc/path_runner_bloc.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/learning_path/bloc/step_editor_bloc.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/learning_path/widget/path_editor_screen.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/learning_path/widget/path_runner_screen.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/learning_path/widget/step_editor_screen.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/note/bloc/note_editor_bloc.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/note/widget/note_editor_screen.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/settings/widget/settings_screen.dart';
|
||||
|
|
@ -140,6 +146,33 @@ GoRouter buildRouter({String initialLocation = RoutePaths.home}) => GoRouter(
|
|||
noteId: state.pathParameters['noteId'],
|
||||
),
|
||||
),
|
||||
// A learning path: worked through, written, and one of its steps
|
||||
// written.
|
||||
GoRoute(
|
||||
path: '/studysets/:studysetId/paths/:pathId',
|
||||
builder: (context, state) => _pathRunner(
|
||||
context,
|
||||
state.pathParameters['studysetId']!,
|
||||
state.pathParameters['pathId']!,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/studysets/:studysetId/paths/:pathId/edit',
|
||||
builder: (context, state) => _pathEditor(
|
||||
context,
|
||||
state.pathParameters['studysetId']!,
|
||||
state.pathParameters['pathId']!,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/studysets/:studysetId/paths/:pathId/steps/:stepId',
|
||||
builder: (context, state) => _stepEditor(
|
||||
context,
|
||||
state.pathParameters['studysetId']!,
|
||||
state.pathParameters['pathId']!,
|
||||
state.pathParameters['stepId']!,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: RoutePaths.settings,
|
||||
builder: (context, state) => const SettingsScreen(),
|
||||
|
|
@ -147,6 +180,66 @@ GoRouter buildRouter({String initialLocation = RoutePaths.home}) => GoRouter(
|
|||
],
|
||||
);
|
||||
|
||||
/// The learning path [pathId] of the studyset [studysetId], to work
|
||||
/// through; a studyset gone says so.
|
||||
Widget _pathRunner(BuildContext context, String studysetId, String pathId) {
|
||||
final person = PersonScope.of(context);
|
||||
return FoundById<Studyset>(
|
||||
id: studysetId,
|
||||
items: person.studysets.watchAll,
|
||||
idOf: (studyset) => studyset.id,
|
||||
builder: (studyset, _) => BlocProvider(
|
||||
create: (context) =>
|
||||
PathRunnerBloc(person.paths, person.pathProgress, studysetId, pathId)
|
||||
..add(const PathRunnerEventStarted()),
|
||||
child: PathRunnerScreen(studysetId: studysetId, pathId: pathId),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// The learning path [pathId] of the studyset [studysetId], to write.
|
||||
/// Leaving closes its bloc, which saves what is unsaved.
|
||||
Widget _pathEditor(BuildContext context, String studysetId, String pathId) {
|
||||
final person = PersonScope.of(context);
|
||||
return FoundById<Studyset>(
|
||||
id: studysetId,
|
||||
items: person.studysets.watchAll,
|
||||
idOf: (studyset) => studyset.id,
|
||||
builder: (studyset, _) => BlocProvider(
|
||||
create: (context) => PathEditorBloc(
|
||||
person.paths,
|
||||
person.tags,
|
||||
person.stepDeletions,
|
||||
studysetId,
|
||||
pathId,
|
||||
)..add(const PathEditorEventStarted()),
|
||||
child: PathEditorScreen(studysetId: studysetId, pathId: pathId),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// The step [stepId] of the learning path [pathId], to write. Leaving
|
||||
/// closes its bloc, which saves what is unsaved.
|
||||
Widget _stepEditor(
|
||||
BuildContext context,
|
||||
String studysetId,
|
||||
String pathId,
|
||||
String stepId,
|
||||
) {
|
||||
final person = PersonScope.of(context);
|
||||
return FoundById<Studyset>(
|
||||
id: studysetId,
|
||||
items: person.studysets.watchAll,
|
||||
idOf: (studyset) => studyset.id,
|
||||
builder: (studyset, _) => BlocProvider(
|
||||
create: (context) =>
|
||||
StepEditorBloc(person.paths, pathId, stepId)
|
||||
..add(const StepEditorEventStarted()),
|
||||
child: const StepEditorScreen(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _studysetScreen(BuildContext context, String id) => FoundById<Studyset>(
|
||||
id: id,
|
||||
items: PersonScope.of(context).studysets.watchAll,
|
||||
|
|
|
|||
Loading…
Reference in a new issue