The implemented UI code is a content-rendering pipeline. It turns application
Content models into one Flutter widget for the requested exercise side.
flowchart TD
CONTENT["Content and side"] --> CR["ContentRenderer"]
CR --> FR["FieldRenderer"]
FR --> HTML["Combined HTML"]
HTML --> WIDGET["HtmlWidget"]
FR -. "media:// lookup" .-> RESOLVER["MediaResolver"]
ContentRenderer.render(content, side) performs four operations:
front, back, or both as appropriate;displayOrder; for equal orders, null identifiers come first
and non-null identifiers are ordered numerically;FieldRenderer to produce an HTML fragment for each field;flutter_widget_from_html
HtmlWidget.FieldRenderer dispatches according to FieldValueType:
| Type | Expected value | Rendering behaviour |
|---|---|---|
text |
TextFieldValue |
Escaped text with line breaks converted to <br> |
html |
TextFieldValue |
HTML fragment with internal media references resolved |
image |
MediaFieldValue |
<img> using a local file URI |
audio |
MediaFieldValue |
<audio> using a local file URI |
video |
MediaFieldValue |
<video> using a local file URI |
A type/value mismatch is treated as invalid application data and produces a
StateError.
HTML may refer to stored media with media://<sha256>. MediaResolver parses
the fragment, finds src and poster attributes, asks ContentController for
the matching media record, and replaces the internal reference with a local
file URI.
Only simple src and poster attributes are handled. URI-list attributes such
as srcset are intentionally unsupported, and a missing media hash is an
error.
The repository does not currently implement screens, navigation, exercise input controls, or state-management bindings. Those elements remain part of the future MVP interface.
The presentation layer may depend on application models and controllers. It must not access DAOs, repositories, SQLite rows, or domain mutation methods directly.