fix(android): MainAppBarActions sources isAdmin internally

The isAdmin parameter on MainAppBarActions defaulted to false and no
call site passed it, so the Admin overflow item never appeared for
actual admins — leaving the admin section unreachable from normal UI.

Move the lookup into a tiny AppBarActionsViewModel that reads
AuthController.currentUser, and drop the parameter from the public
signature so future call sites can't recreate the dead-param hazard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-26 10:31:38 -04:00
parent 0927b9177b
commit 484ad6c496
2 changed files with 40 additions and 2 deletions
@@ -0,0 +1,33 @@
package com.fabledsword.minstrel.shared.widgets
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.fabledsword.minstrel.auth.AuthController
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
private const val SHARE_STOP_TIMEOUT_MS = 5_000L
/**
* Backs [MainAppBarActions] with the current user's admin flag so the
* overflow "Admin" item appears for every admin without each shell
* screen having to plumb the value through. Sourced from
* [AuthController.currentUser] so it stays live across sign-in,
* sign-out, and role changes pushed via rehydration.
*/
@HiltViewModel
class AppBarActionsViewModel @Inject constructor(
authController: AuthController,
) : ViewModel() {
val isAdmin: StateFlow<Boolean> = authController.currentUser
.map { it?.isAdmin == true }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(SHARE_STOP_TIMEOUT_MS),
initialValue = false,
)
}
@@ -11,6 +11,8 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavHostController
import com.composables.icons.lucide.House
import com.composables.icons.lucide.LibraryBig
@@ -33,7 +35,9 @@ import com.fabledsword.minstrel.nav.Settings as SettingsRoute
* [Home] [Library] [Search] [ ⋮ Playlists / Discover / Settings / Admin ]
*
* The icon for the screen the user is currently on is suppressed so
* the action set looks contextual.
* the action set looks contextual. The "Admin" overflow item is
* sourced internally from [AppBarActionsViewModel] so admins see it
* everywhere without each call site having to plumb the flag.
*
* `currentRouteName` is a soft string match against the destination
* route name (route classes serialize to their FQN by default in
@@ -43,8 +47,9 @@ import com.fabledsword.minstrel.nav.Settings as SettingsRoute
fun MainAppBarActions(
navController: NavHostController,
currentRouteName: String?,
isAdmin: Boolean = false,
viewModel: AppBarActionsViewModel = hiltViewModel(),
) {
val isAdmin by viewModel.isAdmin.collectAsStateWithLifecycle()
Row {
if (currentRouteName != Home::class.qualifiedName) {
IconButton(onClick = { navController.navigate(Home) }) {