From 786a73dad798f41da18174f7cdc945a03c2067c4 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Mon, 25 May 2026 00:21:42 -0400 Subject: [PATCH] fix(android): extract Discover tiles to DiscoverTiles.kt (detekt TooManyFunctions) DiscoverScreen.kt still tripped TooManyFunctions (13/11) after the VM split because the file kept all the per-row composables + helpers. Moved SuggestionTile + ResultTile + Avatar + AvatarShape into a sibling DiscoverTiles.kt and dropped the now-unused imports (background, CircleShape, RoundedCornerShape, AssistChip, Disc3, User, AsyncImage, size, clip, ImageVector, TextOverflow). DiscoverScreen.kt now hosts 10 functions: the screen + 6 layout-only helpers (SearchBar, KindChips, SuggestionsPane, SuggestionsList, SuggestionsHeader, ResultsList) + LoadingCentered + CenteredMessage + snackbarFor. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../minstrel/discover/ui/DiscoverScreen.kt | 119 --------------- .../minstrel/discover/ui/DiscoverTiles.kt | 136 ++++++++++++++++++ 2 files changed, 136 insertions(+), 119 deletions(-) create mode 100644 android/app/src/main/java/com/fabledsword/minstrel/discover/ui/DiscoverTiles.kt diff --git a/android/app/src/main/java/com/fabledsword/minstrel/discover/ui/DiscoverScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/discover/ui/DiscoverScreen.kt index c536a0cf..dd83a11b 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/discover/ui/DiscoverScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/discover/ui/DiscoverScreen.kt @@ -1,6 +1,5 @@ package com.fabledsword.minstrel.discover.ui -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -9,15 +8,10 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.AssistChip -import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilterChip @@ -38,18 +32,13 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController -import coil3.compose.AsyncImage import com.composables.icons.lucide.ArrowLeft -import com.composables.icons.lucide.Disc3 import com.composables.icons.lucide.Lucide import com.composables.icons.lucide.Search as LucideSearch -import com.composables.icons.lucide.User import com.fabledsword.minstrel.discover.data.RequestOutcome import com.fabledsword.minstrel.models.ArtistSuggestionRef import com.fabledsword.minstrel.models.LidarrRequestKind @@ -247,114 +236,6 @@ private fun ResultsList( } } -@Composable -private fun SuggestionTile(s: ArtistSuggestionRef, onRequest: () -> Unit) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 10.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(12.dp), - ) { - Avatar(imageUrl = s.imageUrl, fallback = Lucide.User, shape = AvatarShape.CIRCLE) - Column(modifier = Modifier.weight(1f)) { - Text( - text = s.name, - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurface, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - if (s.attributionText.isNotEmpty()) { - Text( - text = s.attributionText, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - ) - } - } - Button(onClick = onRequest) { Text("Request") } - } -} - -@Composable -private fun ResultTile( - row: LidarrSearchResultRef, - locallyRequested: Boolean, - onRequest: () -> Unit, -) { - val effectivelyRequested = row.requested || locallyRequested - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 10.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(12.dp), - ) { - Avatar(imageUrl = row.imageUrl, fallback = Lucide.Disc3, shape = AvatarShape.ROUNDED) - Column(modifier = Modifier.weight(1f)) { - Text( - text = row.name, - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurface, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - if (row.secondaryText.isNotEmpty()) { - Text( - text = row.secondaryText, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } - } - when { - row.inLibrary -> AssistChip(onClick = {}, enabled = false, label = { Text("In library") }) - effectivelyRequested -> AssistChip(onClick = {}, enabled = false, label = { Text("Requested") }) - else -> Button(onClick = onRequest) { Text("Request") } - } - } -} - -private enum class AvatarShape { CIRCLE, ROUNDED } - -@Composable -private fun Avatar( - imageUrl: String, - fallback: androidx.compose.ui.graphics.vector.ImageVector, - shape: AvatarShape, -) { - val clip = when (shape) { - AvatarShape.CIRCLE -> CircleShape - AvatarShape.ROUNDED -> RoundedCornerShape(6.dp) - } - Box( - modifier = Modifier - .size(56.dp) - .clip(clip) - .background(MaterialTheme.colorScheme.surfaceVariant), - contentAlignment = Alignment.Center, - ) { - if (imageUrl.isEmpty()) { - Icon( - imageVector = fallback, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } else { - AsyncImage( - model = imageUrl, - contentDescription = null, - modifier = Modifier.fillMaxSize(), - ) - } - } -} - @Composable private fun LoadingCentered() { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { diff --git a/android/app/src/main/java/com/fabledsword/minstrel/discover/ui/DiscoverTiles.kt b/android/app/src/main/java/com/fabledsword/minstrel/discover/ui/DiscoverTiles.kt new file mode 100644 index 00000000..441f8062 --- /dev/null +++ b/android/app/src/main/java/com/fabledsword/minstrel/discover/ui/DiscoverTiles.kt @@ -0,0 +1,136 @@ +package com.fabledsword.minstrel.discover.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.AssistChip +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import com.composables.icons.lucide.Disc3 +import com.composables.icons.lucide.Lucide +import com.composables.icons.lucide.User +import com.fabledsword.minstrel.models.ArtistSuggestionRef +import com.fabledsword.minstrel.models.LidarrSearchResultRef + +internal enum class AvatarShape { CIRCLE, ROUNDED } + +@Composable +internal fun SuggestionTile(s: ArtistSuggestionRef, onRequest: () -> Unit) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + Avatar(imageUrl = s.imageUrl, fallback = Lucide.User, shape = AvatarShape.CIRCLE) + Column(modifier = Modifier.weight(1f)) { + Text( + text = s.name, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + if (s.attributionText.isNotEmpty()) { + Text( + text = s.attributionText, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + } + } + Button(onClick = onRequest) { Text("Request") } + } +} + +@Composable +internal fun ResultTile( + row: LidarrSearchResultRef, + locallyRequested: Boolean, + onRequest: () -> Unit, +) { + val effectivelyRequested = row.requested || locallyRequested + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + Avatar(imageUrl = row.imageUrl, fallback = Lucide.Disc3, shape = AvatarShape.ROUNDED) + Column(modifier = Modifier.weight(1f)) { + Text( + text = row.name, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + if (row.secondaryText.isNotEmpty()) { + Text( + text = row.secondaryText, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } + when { + row.inLibrary -> AssistChip(onClick = {}, enabled = false, label = { Text("In library") }) + effectivelyRequested -> + AssistChip(onClick = {}, enabled = false, label = { Text("Requested") }) + else -> Button(onClick = onRequest) { Text("Request") } + } + } +} + +@Composable +private fun Avatar(imageUrl: String, fallback: ImageVector, shape: AvatarShape) { + val clip = when (shape) { + AvatarShape.CIRCLE -> CircleShape + AvatarShape.ROUNDED -> RoundedCornerShape(6.dp) + } + Box( + modifier = Modifier + .size(56.dp) + .clip(clip) + .background(MaterialTheme.colorScheme.surfaceVariant), + contentAlignment = Alignment.Center, + ) { + if (imageUrl.isEmpty()) { + Icon( + imageVector = fallback, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } else { + AsyncImage( + model = imageUrl, + contentDescription = null, + modifier = Modifier.fillMaxSize(), + ) + } + } +}