testsAndMisc-archive/horatio/horatio_app/lib/router.dart
Krzysztof kuhy Rudnicki 85edd6ba02 feat: annotations subsystem — core models, drift DB, cubits, and UI
Add the complete annotations feature for marking and annotating script text:

Core models (horatio_core):
- TextMark, LineNote, AnnotationSnapshot, MarkType, NoteCategory
- Script.id field + UUID generation in text_parser

Database layer (horatio_app):
- Drift tables: text_marks, line_notes, annotation_snapshots
- AppDatabase with AnnotationDao (full CRUD + streams + bulk replace)

State management:
- AnnotationCubit: mark/note CRUD, line selection, editing context
- AnnotationHistoryCubit: snapshot save/restore with stream updates

UI components:
- MarkOverlay: colored span rendering for text marks
- NoteIndicator: per-line note count badge
- MarkTypePicker: 6-type ActionChip selector
- NoteEditorSheet: category dropdown + text field bottom sheet
- AnnotationEditorScreen: full editor with long-press marks + note editing
- AnnotationHistoryScreen: snapshot timeline with restore dialog

Wiring:
- main.dart: async DB init with path_provider
- app.dart: RepositoryProvider<AnnotationDao>
- router.dart: /annotations + /annotation-history routes
- role_selection_screen: Annotate Script option
- run.sh: app_codegen step + coverage filtering for generated code

352 tests (105 core + 247 app), 100% branch coverage, zero dead code.
2026-03-29 17:59:26 +02:00

131 lines
4.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:horatio_app/screens/annotation_editor_screen.dart';
import 'package:horatio_app/screens/annotation_history_screen.dart';
import 'package:horatio_app/screens/home_screen.dart';
import 'package:horatio_app/screens/import_screen.dart';
import 'package:horatio_app/screens/rehearsal_screen.dart';
import 'package:horatio_app/screens/role_selection_screen.dart';
import 'package:horatio_app/screens/schedule_screen.dart';
import 'package:horatio_app/screens/srs_review_screen.dart';
import 'package:horatio_core/horatio_core.dart';
/// Route paths.
abstract final class RoutePaths {
/// Home / script library.
static const String home = '/';
/// Import a new script.
static const String import_ = '/import';
/// Select a role after importing a script.
static const String roleSelection = '/role-selection';
/// View memorization schedule.
static const String schedule = '/schedule';
/// Interactive rehearsal mode.
static const String rehearsal = '/rehearsal';
/// SRS flashcard review.
static const String srsReview = '/srs-review';
/// Annotation editor.
static const String annotations = '/annotations';
/// Annotation history.
static const String annotationHistory = '/annotation-history';
}
/// Application router configuration.
final GoRouter appRouter = GoRouter(
routes: [
GoRoute(
path: RoutePaths.home,
builder: (context, state) => const HomeScreen(),
),
GoRoute(
path: RoutePaths.import_,
builder: (context, state) => const ImportScreen(),
),
GoRoute(
path: RoutePaths.roleSelection,
redirect: (context, state) =>
state.extra == null ? RoutePaths.home : null,
builder: (context, state) {
if (state.extra case final Script script) {
return RoleSelectionScreen(script: script);
}
return const SizedBox.shrink();
},
),
GoRoute(
path: RoutePaths.schedule,
redirect: (context, state) =>
state.extra == null ? RoutePaths.home : null,
builder: (context, state) {
if (state.extra case final Map<String, Object> extra) {
return ScheduleScreen(
script: extra['script']! as Script,
selectedRole: extra['role']! as Role,
);
}
return const SizedBox.shrink();
},
),
GoRoute(
path: RoutePaths.rehearsal,
redirect: (context, state) =>
state.extra == null ? RoutePaths.home : null,
builder: (context, state) {
if (state.extra case final Map<String, Object> extra) {
return RehearsalScreen(
script: extra['script']! as Script,
selectedRole: extra['role']! as Role,
);
}
return const SizedBox.shrink();
},
),
GoRoute(
path: RoutePaths.srsReview,
redirect: (context, state) =>
state.extra == null ? RoutePaths.home : null,
builder: (context, state) {
if (state.extra case final List<SrsCard> cards) {
return SrsReviewScreen(cards: cards);
}
return const SizedBox.shrink();
},
),
GoRoute(
path: RoutePaths.annotations,
redirect: (context, state) =>
state.extra == null ? RoutePaths.home : null,
builder: (context, state) {
if (state.extra case final Script script) {
return AnnotationEditorScreen(script: script);
}
return const SizedBox.shrink();
},
),
GoRoute(
path: RoutePaths.annotationHistory,
redirect: (context, state) =>
state.extra == null ? RoutePaths.home : null,
builder: (context, state) {
if (state.extra case final Script script) {
return AnnotationHistoryScreen(script: script);
}
return const SizedBox.shrink();
},
),
],
errorBuilder: (context, state) => Scaffold(
appBar: AppBar(title: const Text('Not Found')),
body: Center(
child: Text('Page not found: ${state.uri}'),
),
),
);