Train timetable, keyless map, NBS rates, and weekly content sync - #5
Merged
Conversation
Recolour The seed was the Serbian flag red, and Material 3 tints every surface with the seed — so backgrounds and cards came out pink. The palette is now a blue accent over a neutral surface ramp: colour marks actions, the active tab and an urgent deadline, and nothing else. Train timetable New screen over the Serbian Railways timetable at w3.srbvoz.rs, which exposes a JSON station lookup and two server-rendered result pages. Route search and a per-station departure/arrival board, with recently used stations remembered. Row layouts are pinned down in one service rather than spread through the UI, because the operator renders each result twice for wide and narrow layouts. Map without an API key Replaces nothing, adds a screen: 362 relocant-run businesses from the stats.srb.guide catalogue on an OpenStreetMap map. OSM needs no key and no billing account, unlike the Google Maps SDK. The catalogue is bundled, so the list and the category filters work offline and only the tiles need a connection. The app identifies itself as the OSM tile usage policy requires. Exchange rates Adds the National Bank of Serbia as a fifth source. It is the only one with a real API, and it replaces the previous reference rate, which was scraped out of one office's ticker. Reference sources are flagged and excluded from the "best rate" comparison — the NBS is not a counter you can walk up to. Content sync sync-guide.yml becomes sync-content.yml and now refreshes three datasets weekly: the guide, the places catalogue (new) and the chat directory, which had drifted to 340 hand-maintained entries and is now 432 from the catalogue with topic, size and activity. Only datasets whose payload actually changed are staged, so a new sync timestamp alone never produces a commit. Guide updates between releases Because that workflow commits the guide to master, the app now checks GitHub for a newer copy once a day, conditional on an ETag so an unchanged guide costs a single 304. Downloads are validated before being accepted and the bundled asset stays the fallback, so a bad fetch cannot leave the app without content. Settings gains a manual check. Search Article text was re-lowercased on every keystroke — 1.5 MB per search. The lowercased forms are now computed once per article and reused.
112 images that nothing referenced. They were never declared under `assets:` in pubspec.yaml — a bare `assets/data/` entry does not recurse into subdirectories — so they were not in the APK either; they only sat in the repository. They predate the guide re-sync. The previous guide's image links were site-relative paths like `/guides/business/open/media/…webp`, which are addresses on srb.guide rather than local assets, and whose names do not match these files. The current guide references 283 images, all remote. Verified before deleting: no Dart code loads `assets/data/media`, no image reference in any bundled dataset points at `assets/`, and the app builds and renders articles with the directory gone.
GitHub is force-running the Node 20 actions on Node 24 and warns on every run; setup-java v4 is explicitly no longer receiving updates. actions/checkout v4 -> v7 actions/setup-java v4 -> v6 actions/upload-artifact v4 -> v7 actions/github-script v7 -> v9 peter-evans/create-pull-request v6 -> v8 softprops/action-gh-release v2 -> v3 subosito/flutter-action stays on v2, which is still its current major. Every tag was checked to exist before pinning, and the inputs used here — `flutter-version`, `distribution`/`java-version`, `path`/`retention-days`, `branch`/`add-paths`, `draft`/`files` — are unchanged across these majors.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Works through the remaining items in the backlog: two new screens, a fifth rate
source, a broader weekly content sync, and content updates that no longer wait
for a Play release. Every external source was verified live before anything was
built on it.
Recolour
The seed colour was the Serbian flag red, and Material 3 tints every surface
with the seed — which is why backgrounds and cards came out pink rather than
just the accents. The palette is now a blue accent over a neutral surface
ramp: colour marks actions, the active tab and an urgent deadline, and
nothing else. Cards gained a hairline border since they are no longer separated
from the background by tint.
Train timetable
Serbian Railways' site is a jQuery front end over a JSON station lookup and two
server-rendered result pages. The app uses the same endpoints:
Stations you have used are remembered, so the usual trip takes two taps. Each
result is rendered twice by the operator (wide and narrow layouts), so the row
layouts are pinned down in one service rather than spread through the UI.
Verified live: 36 connections and 98 board entries parsed for Beograd Centar.
Map without an API key
362 relocant-run businesses from the stats.srb.guide catalogue on an
OpenStreetMap map. OSM needs no API key and no billing account, which is
what made this practical — the Google Maps SDK needs both.
The catalogue is bundled, so the list, the search and the category filters work
offline; only the tiles need a connection. Categories get their own icon and
colour, and each place links out to Google Maps for directions.
Its API requires auth, so the catalogue is read from the map page's hydration
payload, which
robots.txtallows. Records are located by id and each field isthen read from that record's own window — field order varies between records,
and a single fixed-order pattern silently dropped 28 of them.
Exchange rates: a fifth source
Adds the National Bank of Serbia official rate. It is the only source with a
real API, and it replaces the previous reference rate, which was scraped out of
one office's ticker as a side effect.
Reference sources are now flagged and excluded from the "best rate"
comparison — the NBS is not a counter you can walk up to. RUB and GBP have no
NBS cash rate, so those fall back to the non-cash figure.
All five verified live in
test/exchange_parsers_live_test.dart.Content sync covers three datasets
sync-guide.ymlbecomessync-content.ymland now refreshes weekly:guide.jsonplaces.jsontg_chats.jsonThe chat directory had drifted — it was a hand-maintained list of 340 entries.
It now comes from the catalogue with topic, size and whether each chat is still
active, while keeping the original
name/url/groupkeys so the existingscreen keeps working.
Only datasets whose payload actually changed are staged, so a new sync
timestamp alone never produces a commit.
Guide updates between releases
Because that workflow commits the guide to
master, the app now checks GitHubfor a newer copy once a day, conditional on an
ETagso an unchanged guidecosts a single 304 and no body transfer. A correction on srb.guide reaches
users in days instead of waiting for a Play release.
The download is validated before it is accepted — the same shape checks CI runs
— and the bundled asset stays the fallback, so a bad fetch can never leave the
app without content. Settings gains a manual check.
Search
Article text was re-lowercased on every keystroke: 1.5 MB per search, with
the search debounced to every 200 ms. The lowercased forms are now computed once
per article and reused.
One item I did not implement
The backlog asked for SQLite + FTS5. I measured before building it, and the
numbers argue against it at this size:
guide.jsonis already ~0.5 MB, not 2.4 MB — Androidcompresses assets, and the file gzips to 0.53 MB.
.dbis about the same size, andbuilding the index on device means shipping the JSON and storing the DB, so
on-device usage goes up.
package:sqlite33.x compiles SQLite from source through Dart build hooks,which is a new native dependency in the Android and CI build I would rather
not add for a 12 ms operation.
So the cost is a native dependency and build risk, and the benefit is nil until
the content is several times larger. I did the part that was actually costing
something — the per-keystroke lowercasing above. Happy to add FTS5 if you want
it regardless; the search API is already isolated behind
GuideRepository.Housekeeping
Removed
assets/data/media/— 112 images, 5.6 MB, that nothing referenced.They were never declared under
assets:inpubspec.yaml(a bareassets/data/entry does not recurse), so they were not in the APK either; theyonly sat in the repository. They predate the guide re-sync: the old guide's
image links were site-relative paths like
/guides/business/open/media/…webp,addresses on srb.guide rather than local files, whose names do not match these.
Checked before deleting — no Dart loads
assets/data/media, no bundled datasetpoints at
assets/, and the app builds and renders articles without it.Updated the actions to their current majors. GitHub was force-running the
Node 20 ones on Node 24 and warning on every run, and
setup-javav4 isexplicitly no longer maintained:
subosito/flutter-actionstays on v2, still its current major. Every tag waschecked to exist before pinning, and the inputs used here are unchanged across
these majors.
Verification
dart format,flutter analyze(0 issues), 29 unit tests, guide validationlive sites
targetSdk36 /minSdk24 andgains no new permissions
tiles and 362 markers, a train search returning real departures, and the rates
screen showing NBS alongside the four offices