Feature/equipment management - #994
Open
tbrkkup wants to merge 9 commits into
Open
Conversation
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.
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.
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
Equipmentis a Room entity and Gson model in one, following the pattern ofTaskCategoryand the other master data classes. It exposes anupdateEquipment()queue item soDownloadHelperdrives it exactly like every other entity, keyed off a newDB_LAST_TIME_EQUIPMENTpreference thatPrefsUtil.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.
ObjectUtilgained the equipment cases for id, name, description, created timestamp and userfields. That is what lets the existing genericMasterObjectListFragmentrender 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
MasterEquipmentFragmenthandles 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
ObjectUtilbut 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
ObjectUtiland navigation changes.Manual viewing was checked for PDF, JPG and PNG, for files uploaded from the app and from the web UI.