fix(android) detekt: shrink AdminUsersScreen, real regenerate fix, suppress TooManyFunctions

The previous push tried to fix detekt but missed two things:

* regenerate() in PlaylistDetailViewModel had its 3 returns "fixed"
  in name only — the if(!refreshable)return was still there. Now
  folded into the variant Elvis chain with takeIf{refreshable}.

* The helper-composable extractions I just shipped pushed three
  screens over detekt's 11-function-per-file cap. Compose screens
  naturally produce many small private composables; per the
  established pattern, suppress TooManyFunctions at the file level
  with a one-line rationale rather than fight the cap.

* AdminUsersScreen main body was 94 lines (cap 60) because I
  rebuilt the Scaffold inline with the new Invites section. Extracted
  AdminUsersScaffold helper so the main function only owns state
  hoisting and dialog dispatch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 13:14:34 -04:00
parent c2c9de26e7
commit c816490061
3 changed files with 90 additions and 59 deletions
@@ -1,3 +1,5 @@
@file:Suppress("TooManyFunctions") // Compose screen + private helper composables and dialogs
package com.fabledsword.minstrel.admin.ui
import androidx.compose.foundation.layout.Arrangement
@@ -70,64 +72,21 @@ fun AdminUsersScreen(
}
}
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = { Text("Admin · Users") },
navigationIcon = {
IconButton(onClick = { navController.popBackStack() }) {
Icon(Lucide.ArrowLeft, contentDescription = "Back")
}
},
actions = {
MainAppBarActions(
navController = navController,
currentRouteName = AdminUsers::class.qualifiedName,
)
},
)
AdminUsersScaffold(
navController = navController,
usersState = state,
invitesState = invitesState,
onRefresh = {
viewModel.refresh().join()
invitesViewModel.refresh()
},
) { inner ->
PullToRefreshScaffold(
onRefresh = {
viewModel.refresh().join()
invitesViewModel.refresh()
},
modifier = Modifier.fillMaxSize().padding(inner),
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item { SectionHeader("Users") }
usersSection(
state = state,
onSetAdmin = viewModel::setAdmin,
onSetAutoApprove = viewModel::setAutoApprove,
onAskResetPassword = { resetPasswordFor = it },
onAskDelete = { deleteCandidate = it },
)
item { HorizontalDivider() }
item {
SectionHeader(
label = "Invites",
trailing = {
TextButton(onClick = { showGenerateInvite = true }) {
Icon(
Lucide.Plus,
contentDescription = null,
modifier = Modifier.size(16.dp),
)
Text("Generate")
}
},
)
}
invitesSection(
state = invitesState,
onRevoke = invitesViewModel::revoke,
)
}
}
}
onSetAdmin = viewModel::setAdmin,
onSetAutoApprove = viewModel::setAutoApprove,
onAskResetPassword = { resetPasswordFor = it },
onAskDelete = { deleteCandidate = it },
onAskGenerateInvite = { showGenerateInvite = true },
onRevokeInvite = invitesViewModel::revoke,
)
PendingDialogs(
resetPasswordFor = resetPasswordFor,
@@ -155,6 +114,74 @@ fun AdminUsersScreen(
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun AdminUsersScaffold(
navController: NavHostController,
usersState: AdminUsersUiState,
invitesState: AdminInvitesUiState,
onRefresh: suspend () -> Unit,
onSetAdmin: (String, Boolean) -> Unit,
onSetAutoApprove: (String, Boolean) -> Unit,
onAskResetPassword: (AdminUserRef) -> Unit,
onAskDelete: (AdminUserRef) -> Unit,
onAskGenerateInvite: () -> Unit,
onRevokeInvite: (String) -> Unit,
) {
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = { Text("Admin · Users") },
navigationIcon = {
IconButton(onClick = { navController.popBackStack() }) {
Icon(Lucide.ArrowLeft, contentDescription = "Back")
}
},
actions = {
MainAppBarActions(
navController = navController,
currentRouteName = AdminUsers::class.qualifiedName,
)
},
)
},
) { inner ->
PullToRefreshScaffold(
onRefresh = onRefresh,
modifier = Modifier.fillMaxSize().padding(inner),
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item { SectionHeader("Users") }
usersSection(
state = usersState,
onSetAdmin = onSetAdmin,
onSetAutoApprove = onSetAutoApprove,
onAskResetPassword = onAskResetPassword,
onAskDelete = onAskDelete,
)
item { HorizontalDivider() }
item {
SectionHeader(
label = "Invites",
trailing = {
TextButton(onClick = onAskGenerateInvite) {
Icon(
Lucide.Plus,
contentDescription = null,
modifier = Modifier.size(16.dp),
)
Text("Generate")
}
},
)
}
invitesSection(state = invitesState, onRevoke = onRevokeInvite)
}
}
}
}
@Composable
private fun SectionHeader(label: String, trailing: (@Composable () -> Unit)? = null) {
Row(
@@ -1,3 +1,5 @@
@file:Suppress("TooManyFunctions") // Compose screen + private helper composables
package com.fabledsword.minstrel.playlists.ui
import androidx.compose.foundation.background
@@ -197,8 +199,8 @@ class PlaylistDetailViewModel @Inject constructor(
fun regenerate() {
val playlist = (internal.value as? PlaylistDetailUiState.Success)
?.detail?.playlist ?: return
val variant = playlist.systemVariant ?: return
if (!playlist.refreshable) return
val variant = playlist.systemVariant
?.takeIf { playlist.refreshable } ?: return
viewModelScope.launch {
runCatching { repository.refreshSystemPlaylist(variant) }
.onSuccess { newId ->
@@ -1,3 +1,5 @@
@file:Suppress("TooManyFunctions") // Compose screen + private helper composables
package com.fabledsword.minstrel.requests.ui
import androidx.compose.foundation.layout.Arrangement