Add server-plugin-defined nav pages#1431
Conversation
Adds a new server-plugin-driven concept of "custom pages" — admin declares pages in PagesConfig (id, title, icon, position, rows), each becomes a nav-drawer entry slotted in at one of: AfterHome, AfterFavorites, AfterDiscover, AfterLibraries, End. Pages within the same position keep their YAML order. Client fetches the page list at user switch and a single page's rows on demand via the new ServerPluginApi.fetchPages / fetchPage. Rows are rendered through the existing HomePageContent composable, so the visual experience (backdrop, header, focus handling) matches the home screen. To avoid re-fetching every time the user navigates to a page (the nav back stack pops and re-pushes the entry, recreating the ViewModel) a small CustomPageRowsCache singleton holds the latest fetched rows per (userId, pageId). The cache is cleared on user switch. The admin-supplied icon string is rendered in two ways: - http(s):// URL → loaded via Coil's AsyncImage (PNG / SVG / JPG) - any other name → looked up in a small Material Icons whitelist (Home, Star, Settings, ...) and rendered as an ImageVector Unknown / missing names fall back to a generic star icon.
4c40a2f to
a8a1c51
Compare
damontecres
left a comment
There was a problem hiding this comment.
Needs some changes.
Plugin configuration changes don't fully propagate to the client without a logout / app restart.
client refreshes the navigation drawer and the home-page settings only on user switch
This is true, but I do want to add refreshing the user config and other server settings more often. But that change is probably out of scope for this PR though.
| .build() | ||
| return okHttpClient.newCall(request).execute().use { res -> | ||
| if (res.isSuccessful) { | ||
| json.decodeFromStream<List<PageSummary>>(res.body.byteStream()) |
There was a problem hiding this comment.
Wrap the list in an object. This makes it easier to add new fields in the future that older versions of the client can ignore
| Timber.w("fetchPage(%s) returned 404", id) | ||
| null | ||
| } else { | ||
| throw ApiClientException(res.code.toString() + " " + res.body.string()) |
There was a problem hiding this comment.
Use InvalidStatusException instead
| tint: Color, | ||
| ) { | ||
| val trimmed = iconName?.trim().orEmpty() | ||
| if (trimmed.startsWith("http://", ignoreCase = true) || |
There was a problem hiding this comment.
Instead of doing clean up in compose code, do this work in fetchCustomPagesByPosition when creating the CustomPageNavDrawerItem.
This composable should just be something simple like:
when(customPageNavDrawerItem.type){
PageNavDrawerItemType.URL-> AsyncImage(...)
PageNavDrawerItemType.ICON->Icon(...)
else-> Icon(Icons.Default.Star)
}
| } | ||
|
|
||
| private fun customPageMaterialIcon(name: String): ImageVector? = | ||
| when (name.lowercase().replace("[_\\s-]".toRegex(), "")) { |
There was a problem hiding this comment.
Move this out of compose code
See also: https://github.com/damontecres/Wholphin/pull/1431/changes#r3330387263
|
Also, if we're going down the route of customizing the nav drawer, I'd like to make it possible to customize in-app as well. That would have be a new feature/PR. |
Description
Plugin-PR damontecres/jellyfin-plugin-wholphin#5 adds the option to define additional pages for the navigation drawer. Wholphin uses these two new endpoints in following sequence:
/wholphin/pagesand imports these to the nav drawer layout/wholphin/pages/{id}with the corresponding idTo avoid re-fetching every time the user navigates to a page, a small CustomPageRowsCache singleton holds the latest fetched rows per (userId, pageId). The cache is cleared on user switch.
Known limitation:
Plugin configuration changes don't fully propagate to the client without a logout / app restart.
The Wholphin client refreshes the navigation drawer and the home-page settings only on user switch. Custom-page row contents are fetched fresh on every page open, so row changes within an existing page show up after a brief background refresh. But anything that affects the drawer or home-screen layout is cached in-memory until the user logs out:
This limitation should be fixed in a seperate PR after this one was merged.
Related issues
Testing
Tested via Android Emulator and following config:
Screenshots
My config adds two additional pages under the home entry. Trending Movies uses an URL, where as Trending Shows uses on of our supported Material Icons (Play).
AI or LLM usage
This PR was developed in pair with Claude (Anthropic). I understand the code and can explain every change made in the PR. Manual end-to-end testing was performed by me on an Android TV emulator.