Overview
- This repository contains a Flutter app (
aelf_flutter/) and uses the Dart package (offline-liturgy/from /usr/bin/php8.3 --define apc.enable_cli=1 -f /var/www/nextcloud/cron.php) that generates liturgical calendars and content. - High-value entry points:
lib/(flutter source code).
Big-picture architecture
offline-liturgyis a standalone Dart package that computes liturgical data (calendar, compline definitions) and emits YAML used by the Flutter app.- Key function:
complineDefinitionResolution(...)(seeoffline-liturgypackage docs/README andlibsources).
- Key function:
aelf_flutteris a Flutter UI that consumes API, local sqlite Bible assets, and offline_liturgy dart package. Primary UI widgets live inlib/widgets/and follow two patterns:- Liturgy parts (
liturgy_part_*.dart): verse placeholder + Expanded content, usingflutter_htmlor YAML parsers. The reusablelib/widgets/liturgy_row.dartcentralizes the layout. - Office views (
offline_liturgy_*_view.dart): top-level screens for each Divine Office hour (compline, morning, vespers, middle-of-day, readings). They compose widgets fromlib/widgets/offline_liturgy_common_widgets/(psalms, antiphons, hymns, scripture, canticle, headers).
- Liturgy parts (
Developer workflows (how to run & test locally)
- Install Flutter (matching the project's SDK). Some subfolders include
.fvm/— the project may use FVM; usefvm flutterwhere appropriate. - From
aelf_flutter/:- Install deps:
flutter pub get - Run app on a device:
flutter run -d <device-id> - Analyzer:
dart analyzeorflutter analyze - Tests:
dart tool/test_runner.dart(unit + widget, readable output; plainflutter testalso works). Runs in every MR pipeline. - Integration tests:
scripts/run_integration_tests.sh linux [files…](drives the real app; CI runs these on merge requests andmaster) - Format:
dart format .
- Install deps:
- Read
docs/testing.mdbefore changing anything underlib/. The test suite's job is to keep the online (AELF API) liturgy — still used by anyone who switches thefeature_offline_liturgyflag off (it is now on by default) — and the widgets both liturgies share working. If a test intest/parsers/,test/fixtures/ortest/widgets/starts failing, the online path changed — treat that as a regression unless it was deliberate. - If you edit
offline-liturgy, checkoffline-liturgy/README.mdfor how to regenerate assets (YAML-based, not JSON). The app reads assets directly from theoffline-liturgy/assets/directory via the package dependency.
Tests are mandatory (read before any change)
- Every code change ships with tests. A change under
lib/adds or updates tests covering it: unit/widget tests intest/for logic, parsing and rendering;integration_test/for navigation, the app shell and feature-flag behaviour. A bug fix comes with a regression test that fails without the fix. A deliberate behaviour change updates the tests asserting the old behaviour, and says so in the commit message. - Run the tests locally before every commit and push, and report the real
result:
- always:
dart formaton the files you changed,flutter analyze --no-fatal-infosanddart tool/test_runner.dart; - when navigation, the drawer, settings, feature flags, startup or
integration_test/changed:scripts/run_integration_tests.sh linux <files…>(a few minutes: use a Bash timeout of 600000 ms).
- always:
- Never make a failing test pass by deleting it, skipping it, or loosening its assertion without the user's explicit agreement. Fix the code, or, if the change is deliberate, update the test to the new intended behaviour.
- Keep
docs/testing.mdcurrent: update it in the same change whenever a test file is added, removed or renamed, what a test protects changes, a default it relies on changes, or the test tooling / CI jobs change. - Pre-push gate:
scripts/git-hooks/pre-push. Install once per clone withgit config core.hooksPath scripts/git-hooks(check it is set before pushing). It blocks a push when a pushed.dartfile is notdart formatted, when the unit tests fail, when a changed integration test fails, whenlib/changed by 100+ lines with no test changed, or when tests/test tooling changed withoutdocs/testing.md. If it blocks, fix the cause. Never bypass it (--no-verify,AELF_ALLOW_NO_TEST_CHANGES,AELF_ALLOW_NO_DOC_CHANGES,AELF_SKIP_INTEGRATION) unless the user explicitly asks for that push. It runsdart format+ unit tests (~20s) and changed integration files (minutes): givegit pusha Bash timeout of 600000 ms.
Project-specific conventions & patterns
- UI layout pattern: most
liturgy_part_*widgets follow: verse placeholder + Expanded content. UseLiturgyRow(builder: (ctx, zoom) => ...)to respectCurrentZoomprovider and keep layout consistent.- Use
hideVerseIdPlaceholder: trueto hide the placeholder when needed.
- Use
- Column alignment (important): in office views, every text block stacked above/around the psalm verses — titles, subtitles, commentary, antiphons, intro/invitatory lines, versicles — MUST share the same left column as the verse text. Do this by routing the block through
LiturgyRow(or theverseIdPlaceholderwidget directly), NOT with arbitraryPadding(EdgeInsets.symmetric(horizontal: …)). Ad-hoc horizontal padding (e.g. the oldkContentPadding = 16) produces inconsistent, "random-looking" left edges because verses are inset by the verse-id column width, not by 16px.LiturgyPartTitle/LiturgyPartContentTitle/LiturgyPartSubtitle(and the offline-specificOfflineLiturgyPartContentTitle/OfflineLiturgyPartSubtitle) already wrapLiturgyRowbut default tohideVerseIdPlaceholder: true; passhideVerseIdPlaceholder: falsewhen they sit alongside verses so they align.- This applies to the office header title (
office_header_display.dart), psalm titles/subtitles/antiphons (psalms_display.dart), and the evangelic-canticle title/antiphons (evangelic_canticle_display.dart) too — route them throughLiturgyRow/hideVerseIdPlaceholder: false, neverkContentPadding(=horizontal: 16). - Scroll/sliver mode (continuous-reading
CustomScrollView) follows the same rule: the per-section "Psalmodie" headers and anySliverToBoxAdaptercontent must usehideVerseIdPlaceholder: false(notPadding(horizontal: 16)) to share the verse column. - The verse-id column width is shared:
verseIdPlaceholderandBibleVerseIdboth compute10 + verseFontSize * zoom / 100. The psalm renderer (lib/parsers/psalm_parser.dart) usesBibleVerseIdfor verse numbers andverseIdPlaceholderfor continuation/numberless lines — do not reintroduce a separate hand-rolled width for verse numbers. - Right edge: liturgy content reserves a fixed 15px right gap (not zoom-scaled).
LiturgyRowprovides it via a trailing childlessPadding(right: 15), and the legacy parts (liturgy_part_content/antiphon/subtitle/ref) bake in the same value. Anything routed throughLiturgyRowalready has it; the psalm renderer (lib/parsers/psalm_parser.dart) wraps each verse line inPadding(right: 15)to match. So the verse text right edge lines up with antiphons/titles/refs. Do not add a competing right padding at the office-view/ListViewlevel (psalm tabs usehorizontal: 0) — that would double-pad and break the alignment.
- Zoom / sizing: the app uses a
CurrentZoomprovider. Sizes are computed like:fontSize * (zoom ?? 100) / 100. Respect this pattern when changing text sizes. - HTML content: content frequently contains HTML stored in YAML/JSON assets. Widgets use
flutter_htmlto render it and define styles maps keyed by selectors.- See
lib/widgets/liturgy_part_content.dart,lib/widgets/liturgy_part_intro.dart, andlib/widgets/liturgy_content.dart(utilityextractVerses) for parsing/rendering patterns.
- See
- Text formats: there are two formats in the codebase — legacy HTML and YAML-based markup. Parsers live in
lib/parsers/:FormattedTextParser(legacy HTML, largely unused) andYamlTextParser(active, used throughout widgets). - Keep changes minimal and backward-compatible: prefer refactors that preserve current widget APIs (avoid renaming or removing public constructors used across many files).
- Do not break the existing liturgy (compline, mass, vespers, etc.) that uses the external API. It is working, and must keep working reliably for users who switch the new (offline) version off. Very important!
Integration points & external dependencies
flutter_htmlpackage is used widely for rendering HTML content.provideris used for state (e.g.,CurrentZoom).offline-liturgyproduces the canonical liturgical data used by the app — changing its output schema requires coordinated updates inaelf_flutter/lib.- CI: Gitlab CI for Android unofficial builds. Either Gitlab CI with self hoster macOS runner, either Codemagic, for iOS build, for testing and official releases.
Files an agent should read first (in order)
lib/widgets/liturgy_row.dart— central layout abstraction for liturgy partslib/widgets/liturgy_part_*.dart— liturgy part widgets: subtitle, title, intro, intro_ref, content, content_title, antiphon, commentary, rubric, ref, columnlib/widgets/liturgy_part_content.dart— verse extraction + rendering logiclib/widgets/liturgy_content.dart— helperextractVerses(HTML parsing logic)lib/widgets/offline_liturgy_*_view.dart— office hour screens (compline, morning, vespers, middle_of_day, readings)lib/widgets/offline_liturgy_common_widgets/— shared sub-widgets used by office views (psalms, antiphon, hymn, scripture, canticle, header)lib/widgets/offline_liturgy_common_widgets/base_office_view_state.dart— abstract base for all office view states; reads SVG settings (_svgSource) and passes them toCelebrationContext.copyWith()lib/widgets/offline_liturgy_common_widgets/psalm_tone_widget.dart— StatefulWidget that displays SVG psalm music sheets; uses PageView + dot indicator for multiple SVGslib/utils/svg_preprocessor.dart—preprocessPsalmSvg(): replaces font family, text colour (currentColor), and red colour in SVG strings before renderinglib/parsers/—YamlTextParserandFormattedTextParserlib/states/currentZoomState.dart—CurrentZoomChangeNotifierlib/states/liturgyState.dart— includespsalmSvgEnabled/psalmSvgSourcefor SVG feature state (persisted via SharedPreferences)pubspec.yaml— packages & assets declared; update here when adding dependencies or assets
Agent behavior rules (concise, project-specific)
- Preserve UI APIs: do not rename public constructors or change widget signatures without updating all callers.
- Use
LiturgyRowfor the verse placeholder + content layout. When adding new liturgy part widgets, prefer thebuilderpattern so zoom remains consistent. - When editing files, run
dart formatanddart analyzelocally; include code changes that fix analysis issues when safe. - Update or add tests with every change and run them before committing (see "Tests are mandatory" above).
- If you change the assets or
offline-liturgyoutput schema, updateaelf_fluttercode that deserializes the assets and add a short migration note in the commit message. - Prefer small, testable PRs. If a change touches both
offline-liturgyandaelf_flutter, split into two commits: (1)offline-liturgychange + regenerated assets, (2)aelf_flutterdeserialization + UI changes referencing the regenerated assets. - Before git commit, run
dart format lib.
Examples (concrete snippets agents will find useful)
- Respect zoom values when computing font sizes:
fontSize: 16 * (zoom ?? 100) / 100
- Use
LiturgyRowbuilder pattern:LiturgyRow( builder: (context, zoom) => Html(data: content, style: {...}), )
If anything here is unclear or you want extra details (e.g., how assets are generated, or CI steps to reproduce builds), tell me which area to expand and I will iterate.