From c8164900615a472bc517a2d2deae4e35a2f1e465 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 27 May 2026 13:14:34 -0400 Subject: [PATCH] fix(android) detekt: shrink AdminUsersScreen, real regenerate fix, suppress TooManyFunctions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../minstrel/admin/ui/AdminUsersScreen.kt | 141 +++++++++++------- .../playlists/ui/PlaylistDetailScreen.kt | 6 +- .../minstrel/requests/ui/RequestsScreen.kt | 2 + 3 files changed, 90 insertions(+), 59 deletions(-) diff --git a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminUsersScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminUsersScreen.kt index e672cbec..16f96beb 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminUsersScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminUsersScreen.kt @@ -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( diff --git a/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistDetailScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistDetailScreen.kt index eda751e0..8fe272f4 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistDetailScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistDetailScreen.kt @@ -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 -> diff --git a/android/app/src/main/java/com/fabledsword/minstrel/requests/ui/RequestsScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/requests/ui/RequestsScreen.kt index 9f6fde7f..874abe9a 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/requests/ui/RequestsScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/requests/ui/RequestsScreen.kt @@ -1,3 +1,5 @@ +@file:Suppress("TooManyFunctions") // Compose screen + private helper composables + package com.fabledsword.minstrel.requests.ui import androidx.compose.foundation.layout.Arrangement