Skip to content

Feature/equipment management - #994

Open
tbrkkup wants to merge 9 commits into
patzly:masterfrom
tbrkkup:feature/equipment-management
Open

Feature/equipment management#994
tbrkkup wants to merge 9 commits into
patzly:masterfrom
tbrkkup:feature/equipment-management

Conversation

@tbrkkup

@tbrkkup tbrkkup commented Aug 6, 2026

Copy link
Copy Markdown

This contributes to closing #453

Implements the entity end to end. The motivation, the server-side fields and the open questions are my comment in the issue; this pull request here describes what was built.

Data layer

Equipment is a Room entity and Gson model in one, following the pattern of TaskCategory and the other master data classes. It exposes an updateEquipment() queue item so DownloadHelper drives it exactly like every other entity, keyed off a new DB_LAST_TIME_EQUIPMENT preference that PrefsUtil.clearCachingRelatedSharedPreferences() also clears.

Registering the entity takes the database from 54 to 55, with no migration needed beyond the added table. The exported schema JSON is included.

ObjectUtil gained the equipment cases for id, name, description, created timestamp and userfields. That is what lets the existing generic MasterObjectListFragment render the list without special-casing, so no parallel list implementation was introduced — the only additions there are the entity branch and the navigation action.

Edit form

MasterEquipmentFragment handles create and edit. Name is required, the rest optional. The manual is uploaded to /files/equipmentmanuals/ under a Base64-encoded file name, matching how the server addresses stored files, and removal clears the reference on the object rather than deleting the file.

Tapping the manual row opens the attached file; when nothing is attached yet the same tap opens the file picker. Replace and delete sit as buttons below the row. Any file type is accepted, matching the web UI, which pre-selects PDF files but allows for upload of JPG, PNG as well. PDFs and images are the expected cases.

Opening a stored manual downloads it through DownloadHelper.getFile(), added here, since the existing CustomByteArrayRequestwas upload-only and discarded the response body — writes it to the app's external files directory and hands it to a viewer viaFileProviderandACTION_VIEW`. The content type comes from the file extension, with a magic-byte fallback for files stored without a usable one.

Navigation

The drawer is the only entry point, mirroring the web UI, where equipment is a top-level item.

Scope notes

  • English and German strings are included; other locales fall back to English until translated upstream.
  • Userfields are wired through ObjectUtil but the form does not render editors for them, matching the current state of the comparable master data screens.

Testing

Tested this against a live Grocy instance on device LineageOS 24 on Pixel 9 Pro: create, edit, delete,
manual upload, manual replacement, manual removal, both entry points, and a cache reset to confirm the new preference key clears. Verified the list still renders correctly for the other entities after the ObjectUtil and navigation changes.

Manual viewing was checked for PDF, JPG and PNG, for files uploaded from the app and from the web UI.

tbrkkup added 9 commits August 3, 2026 20:27
Mirrors the equipment entity of the Grocy server API:
name, description and instruction manual file name.

- Equipment: Room entity + Gson model with updateEquipment() queue item
- EquipmentDao: standard insert/delete/query surface
- AppDatabase: register entity, bump version 54 -> 55, export schema
- Constants/PrefsUtil: DB_LAST_TIME_EQUIPMENT caching key
- GrocyApi: ENTITY.EQUIPMENT
- DownloadHelper: dispatch Equipment.updateEquipment() for Equipment.class
- MasterDataOverviewRepository/ViewModel: add equipment as a seventh
  source to the combined Single.zip() load
- MasterObjectListRepository/ViewModel: serve the equipment entity
- ObjectUtil: resolve id, name, description, created timestamp and
  userfields for equipment objects
- MasterEquipmentFragment: create/edit form for name, description and
  instruction manual; uploads the manual to /files/equipmentmanuals and
  clears the reference on removal
- MasterObjectListFragment/MasterDataOverviewFragment: list equipment and
  route to the edit form
- InfoFullscreen(View): empty-state for the equipment list
- GrocyApi: getEquipmentManual() builds the Base64 file URL
- navigation_main: masterEquipmentFragment destination plus a deep link
  so the list can be reached from outside the master data graph
- English and German strings
Places equipment directly below tasks, matching the position it has in
the Grocy web UI. Navigation goes through a deep link because the
equipment list lives in the nested master data navigation graph and is
therefore not reachable by destination id from the root graph.
The manual was write-only: the file name was rendered as static text with
no way to view it, and getEquipmentManual() was only ever used for upload
and deletion.

- DownloadHelper.getFile() fetches a file with the usual authentication
  headers; CustomByteArrayRequest gained an optional byte listener since
  it previously discarded the response body, being upload-only
- The manual is written to the app's external files directory and handed
  to a viewer via FileProvider and ACTION_VIEW

Also fixes the extension the picker derives: everything that was not a
PDF used to be stored as ".bin", which left JPG and PNG manuals
unopenable. The extension is now taken from the MIME type, falling back
to the source file name. For manuals already stored as ".bin" the type is
recovered by sniffing the magic bytes, so existing uploads open too.
Tapping the instruction manual row always launched the file picker, even
when a manual was already attached, so there was no obvious way to view
one. The row now opens the manual when there is one and only falls back
to the picker when there is not.

Replacing a manual moved to its own button next to the delete button,
which also removes the separate open button the row now supersedes.
The web UI does not list equipment under master data; it is a top-level
menu entry next to the batteries overview. Listing it in both places was
an artefact of building the screen on the generic entity list, whose
entry point is the master data overview, rather than a deliberate choice.

The drawer is now the only entry point, matching the web UI. The generic
list is still what renders the screen, reached through the deep link, and
the equipment download stays triggered by MasterObjectListViewModel, so
nothing about loading or caching changes.
Removing a manual already deleted the file on the server, but replacing
one did not: the new file was uploaded and the reference updated while
the previous file stayed behind, so every replacement orphaned a file in
/files/equipmentmanuals.

Both paths now go through deleteManualFile(), which runs only after the
equipment object no longer references the file. A failed delete therefore
leaves an orphan rather than a reference pointing at something that is
gone. The error is logged instead of being swallowed silently.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant