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 package com.fabledsword.minstrel.admin.ui
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@@ -70,64 +72,21 @@ fun AdminUsersScreen(
} }
} }
Scaffold( AdminUsersScaffold(
modifier = Modifier.fillMaxSize(), navController = navController,
topBar = { usersState = state,
TopAppBar( invitesState = invitesState,
title = { Text("Admin · Users") }, onRefresh = {
navigationIcon = { viewModel.refresh().join()
IconButton(onClick = { navController.popBackStack() }) { invitesViewModel.refresh()
Icon(Lucide.ArrowLeft, contentDescription = "Back")
}
},
actions = {
MainAppBarActions(
navController = navController,
currentRouteName = AdminUsers::class.qualifiedName,
)
},
)
}, },
) { inner -> onSetAdmin = viewModel::setAdmin,
PullToRefreshScaffold( onSetAutoApprove = viewModel::setAutoApprove,
onRefresh = { onAskResetPassword = { resetPasswordFor = it },
viewModel.refresh().join() onAskDelete = { deleteCandidate = it },
invitesViewModel.refresh() onAskGenerateInvite = { showGenerateInvite = true },
}, onRevokeInvite = invitesViewModel::revoke,
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,
)
}
}
}
PendingDialogs( PendingDialogs(
resetPasswordFor = resetPasswordFor, 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 @Composable
private fun SectionHeader(label: String, trailing: (@Composable () -> Unit)? = null) { private fun SectionHeader(label: String, trailing: (@Composable () -> Unit)? = null) {
Row( Row(
@@ -1,3 +1,5 @@
@file:Suppress("TooManyFunctions") // Compose screen + private helper composables
package com.fabledsword.minstrel.playlists.ui package com.fabledsword.minstrel.playlists.ui
import androidx.compose.foundation.background import androidx.compose.foundation.background
@@ -197,8 +199,8 @@ class PlaylistDetailViewModel @Inject constructor(
fun regenerate() { fun regenerate() {
val playlist = (internal.value as? PlaylistDetailUiState.Success) val playlist = (internal.value as? PlaylistDetailUiState.Success)
?.detail?.playlist ?: return ?.detail?.playlist ?: return
val variant = playlist.systemVariant ?: return val variant = playlist.systemVariant
if (!playlist.refreshable) return ?.takeIf { playlist.refreshable } ?: return
viewModelScope.launch { viewModelScope.launch {
runCatching { repository.refreshSystemPlaylist(variant) } runCatching { repository.refreshSystemPlaylist(variant) }
.onSuccess { newId -> .onSuccess { newId ->
@@ -1,3 +1,5 @@
@file:Suppress("TooManyFunctions") // Compose screen + private helper composables
package com.fabledsword.minstrel.requests.ui package com.fabledsword.minstrel.requests.ui
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement