From a986177c922226c4e5f54d4fb9f6058466a5735e Mon Sep 17 00:00:00 2001 From: Wing <44992537+wingio@users.noreply.github.com> Date: Tue, 17 Sep 2024 16:20:29 -0400 Subject: [PATCH] Centralize commonly reused label/icon pair component --- .gitignore | 1 + .../materiiapps/gloom/ui/component/Avatar.kt | 2 +- .../gloom/ui/component/LabeledIcon.kt | 80 +++++++++++++ .../explore/component/TrendingRepoItem.kt | 28 +---- .../ui/screen/home/component/FeedRepoCard.kt | 76 ++++--------- .../ui/screen/repo/component/RepoItem.kt | 106 ++++++------------ .../materiiapps/gloom/ui/util/ModifierUtil.kt | 2 +- 7 files changed, 142 insertions(+), 153 deletions(-) create mode 100644 ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/component/LabeledIcon.kt diff --git a/.gitignore b/.gitignore index 1a83df43..81216ca5 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ bin/ gen/ out/ +.kotlin/ # Uncomment the following line in case you need and you don't have the release build type files in your app # release/ diff --git a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/component/Avatar.kt b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/component/Avatar.kt index 1d997ad5..55b9676e 100644 --- a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/component/Avatar.kt +++ b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/component/Avatar.kt @@ -21,7 +21,7 @@ import org.koin.compose.koinInject @Composable fun Avatar( url: String?, - contentDescription: String?, + contentDescription: String? = null, type: User.Type = User.Type.USER, modifier: Modifier = Modifier ) { diff --git a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/component/LabeledIcon.kt b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/component/LabeledIcon.kt new file mode 100644 index 00000000..d2dea277 --- /dev/null +++ b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/component/LabeledIcon.kt @@ -0,0 +1,80 @@ +package com.materiiapps.gloom.ui.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor +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.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.unit.dp + +/** + * Simple icon and label pair + * + * @param icon The icon to show with the label + * @param label Label to display + * @param modifier The [Modifier] for this [LabeledIcon] + * @param iconTint The color to use for the [icon] + * @param labelColor The color to use for the [label] text + */ +@Composable +fun LabeledIcon( + icon: Painter, + label: String, + modifier: Modifier = Modifier, + iconTint: Color = LocalContentColor.current, + labelColor: Color = LocalContentColor.current.copy(alpha = 0.6f) +) { + Row( + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = modifier + ) { + Icon( + painter = icon, + contentDescription = null, + modifier = Modifier.size(18.dp), + tint = iconTint + ) + + Text( + text = label, + style = MaterialTheme.typography.labelLarge, + color = labelColor + ) + } +} + +/** + * Simple icon and label pair + * + * @param icon The icon to show with the label + * @param label Label to display + * @param modifier The [Modifier] for this [LabeledIcon] + * @param iconTint The color to use for the [icon] + * @param labelColor The color to use for the [label] text + */ +@Composable +fun LabeledIcon( + icon: ImageVector, + label: String, + modifier: Modifier = Modifier, + iconTint: Color = LocalContentColor.current, + labelColor: Color = LocalContentColor.current.copy(alpha = 0.6f) +) { + LabeledIcon( + icon = rememberVectorPainter(icon), + label = label, + modifier = modifier, + iconTint = iconTint, + labelColor = labelColor + ) +} \ No newline at end of file diff --git a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/screen/explore/component/TrendingRepoItem.kt b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/screen/explore/component/TrendingRepoItem.kt index 3fb92488..eeccfdf2 100644 --- a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/screen/explore/component/TrendingRepoItem.kt +++ b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/screen/explore/component/TrendingRepoItem.kt @@ -26,7 +26,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import com.materiiapps.gloom.Res @@ -34,6 +33,7 @@ import com.materiiapps.gloom.api.dto.user.User import com.materiiapps.gloom.domain.manager.TrendingPeriodPreference import com.materiiapps.gloom.gql.fragment.TrendingRepository import com.materiiapps.gloom.ui.component.Avatar +import com.materiiapps.gloom.ui.component.LabeledIcon import com.materiiapps.gloom.ui.theme.gloomColorScheme import com.materiiapps.gloom.ui.util.NumberFormatter import com.materiiapps.gloom.ui.util.parsedColor @@ -163,30 +163,4 @@ fun TrendingRepoItem( } } } -} - -// TODO: Use everywhere -@Composable -private fun LabeledIcon( - icon: ImageVector, - iconTint: Color = LocalContentColor.current, - label: String -) { - Row( - horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = icon, - contentDescription = null, - modifier = Modifier.size(18.dp), - tint = iconTint - ) - - Text( - text = label, - style = MaterialTheme.typography.labelLarge, - color = LocalContentColor.current.copy(alpha = 0.6f) - ) - } } \ No newline at end of file diff --git a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/screen/home/component/FeedRepoCard.kt b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/screen/home/component/FeedRepoCard.kt index e5b77b2b..d72539a4 100644 --- a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/screen/home/component/FeedRepoCard.kt +++ b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/screen/home/component/FeedRepoCard.kt @@ -17,18 +17,15 @@ import androidx.compose.material.icons.outlined.Person import androidx.compose.material.icons.outlined.StarBorder import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ElevatedCard -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString @@ -40,6 +37,7 @@ import com.materiiapps.gloom.Res import com.materiiapps.gloom.api.dto.user.User import com.materiiapps.gloom.gql.fragment.FeedRepository import com.materiiapps.gloom.ui.component.Avatar +import com.materiiapps.gloom.ui.component.LabeledIcon import com.materiiapps.gloom.ui.screen.repo.RepoScreen import com.materiiapps.gloom.ui.theme.gloomColorScheme import com.materiiapps.gloom.ui.util.NumberFormatter @@ -50,7 +48,6 @@ import com.seiko.imageloader.rememberImagePainter import dev.icerock.moko.resources.compose.stringResource @Composable -@OptIn(ExperimentalMaterial3Api::class) fun FeedRepoCard( repo: FeedRepository, starData: Pair? = null, @@ -71,7 +68,7 @@ fun FeedRepoCard( modifier = Modifier .fillMaxWidth() ) { - if (repo.openGraphImageUrl.startsWith("https://repository-images.githubusercontent.com")) + if (repo.openGraphImageUrl.startsWith("https://repository-images.githubusercontent.com")) { Image( painter = rememberImagePainter(repo.openGraphImageUrl), null, @@ -80,6 +77,7 @@ fun FeedRepoCard( .aspectRatio(2f) .fillMaxWidth() ) + } Column( verticalArrangement = Arrangement.spacedBy(12.dp), @@ -94,13 +92,10 @@ fun FeedRepoCard( ) { Avatar( url = repo.owner.avatarUrl, - contentDescription = stringResource( - Res.strings.noun_users_avatar, - repo.owner.login - ), type = User.Type.fromTypeName(repo.owner.__typename), modifier = Modifier.size(20.dp) ) + Text( buildAnnotatedString { append(repo.owner.login) @@ -124,56 +119,29 @@ fun FeedRepoCard( Row( horizontalArrangement = Arrangement.spacedBy(10.dp) ) { - ProvideTextStyle(value = MaterialTheme.typography.labelLarge) { - Row( - horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - starIcon, - contentDescription = null, - modifier = Modifier.size(18.dp), - tint = starColor - ) - Text(text = NumberFormatter.compact(starCount)) - } + LabeledIcon( + icon = starIcon, + label = NumberFormatter.compact(starCount), + iconTint = starColor + ) - if (repo.primaryLanguage != null) { - Row( - horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - Icons.Filled.Circle, - contentDescription = repo.primaryLanguage!!.name, - modifier = Modifier.size(15.dp), - tint = repo.primaryLanguage!!.color?.parsedColor - ?: MaterialTheme.colorScheme.surfaceVariant - ) - Text(text = repo.primaryLanguage!!.name) - } - } + repo.primaryLanguage?.let { (color, name) -> + LabeledIcon( + icon = Icons.Filled.Circle, + label = name, + iconTint = color?.parsedColor ?: Color.Black + ) } } - Row( - horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - Icons.Outlined.Person, - contentDescription = null, - modifier = Modifier.size(18.dp) - ) - Text( - text = pluralStringResource( - res = Res.plurals.noun_contributors, - count = repo.contributorsCount, - NumberFormatter.compact(repo.contributorsCount) - ), - style = MaterialTheme.typography.labelLarge + LabeledIcon( + icon = Icons.Outlined.Person, + label = pluralStringResource( + res = Res.plurals.noun_contributors, + count = repo.contributorsCount, + NumberFormatter.compact(repo.contributorsCount) ) - } + ) FilledTonalButton( onClick = { diff --git a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/screen/repo/component/RepoItem.kt b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/screen/repo/component/RepoItem.kt index e5e84a06..ea53a3d0 100644 --- a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/screen/repo/component/RepoItem.kt +++ b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/screen/repo/component/RepoItem.kt @@ -14,15 +14,15 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Circle import androidx.compose.material.icons.outlined.Star -import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Text import androidx.compose.material3.surfaceColorAtElevation 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.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -32,6 +32,7 @@ import cafe.adriel.voyager.navigator.currentOrThrow import com.materiiapps.gloom.Res import com.materiiapps.gloom.api.model.ModelRepo import com.materiiapps.gloom.ui.component.Avatar +import com.materiiapps.gloom.ui.component.LabeledIcon import com.materiiapps.gloom.ui.icon.Custom import com.materiiapps.gloom.ui.icon.custom.Fork import com.materiiapps.gloom.ui.screen.repo.RepoScreen @@ -39,6 +40,7 @@ import com.materiiapps.gloom.ui.theme.gloomColorScheme import com.materiiapps.gloom.ui.util.NumberFormatter import com.materiiapps.gloom.ui.util.navigate import com.materiiapps.gloom.ui.util.parsedColor +import com.materiiapps.gloom.ui.util.thenIf import dev.icerock.moko.resources.compose.stringResource @Composable @@ -49,24 +51,21 @@ fun RepoItem( ) { val nav = LocalNavigator.currentOrThrow Column( - Modifier - .run { - if (card) { - clip(RoundedCornerShape(16.dp)) - } else this + verticalArrangement = Arrangement.spacedBy(5.dp), + modifier = Modifier + .thenIf(card) { + this + .shadow(1.dp, RoundedCornerShape(16.dp)) + .clip(RoundedCornerShape(16.dp)) + .background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)) } - .background(if (card) MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp) else Color.Transparent) .clickable { if (!repo.name.isNullOrBlank() && (!repo.owner?.username.isNullOrBlank() || login != null)) nav.navigate(RepoScreen(login ?: repo.owner?.username!!, repo.name!!)) } .padding(16.dp) - .run { - if (card) { - width(260.dp) - } else fillMaxWidth() - }, - verticalArrangement = Arrangement.spacedBy(5.dp) + .fillMaxWidth() + .thenIf(card) { width(260.dp) } ) { if (repo.owner != null) Row( horizontalArrangement = Arrangement.spacedBy(8.dp), @@ -74,19 +73,18 @@ fun RepoItem( ) { Avatar( url = repo.owner!!.avatar, - contentDescription = stringResource( - Res.strings.noun_users_avatar, - repo.owner!!.username ?: "ghost" - ), type = repo.owner!!.type, modifier = Modifier.size(20.dp) ) + Text( text = repo.owner!!.username ?: "ghost", style = MaterialTheme.typography.labelMedium ) } + Spacer(Modifier) + Text( text = repo.name ?: stringResource(Res.strings.placeholder_empty_repo), style = MaterialTheme.typography.labelLarge.copy( @@ -97,36 +95,22 @@ fun RepoItem( ) val desc = if (repo.description == null && card) "" else repo.description - desc?.let { + desc?.let { description -> Text( - text = it, - style = MaterialTheme.typography.labelLarge.copy( - color = MaterialTheme.colorScheme.onSurface.copy(0.8f), - ), + text = description, + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.onSurface.copy(0.8f), maxLines = if (card) 1 else 5, overflow = TextOverflow.Ellipsis ) } if (repo.fork == true && !card) { - Row( - horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(vertical = 5.dp) - ) { - Icon( - Icons.Custom.Fork, - contentDescription = stringResource(Res.strings.cd_forked_repo), - modifier = Modifier.size(15.dp), - tint = MaterialTheme.colorScheme.onSurface.copy(0.6f) - ) - Text( - text = stringResource(Res.strings.label_forked_from, repo.parent!!.fullName!!), - style = MaterialTheme.typography.labelLarge.copy( - color = MaterialTheme.colorScheme.onSurface.copy(0.6f) - ) - ) - } + LabeledIcon( + icon = Icons.Custom.Fork, + label = stringResource(Res.strings.label_forked_from, repo.parent!!.fullName!!), + iconTint = LocalContentColor.current.copy(0.6f) + ) } Row( @@ -134,36 +118,18 @@ fun RepoItem( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = 8.dp) ) { - ProvideTextStyle(value = MaterialTheme.typography.labelLarge) { - Row( - horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - Icons.Outlined.Star, - contentDescription = null, - modifier = Modifier.size(18.dp), - tint = MaterialTheme.gloomColorScheme.statusYellow - ) - Text(text = NumberFormatter.compact(repo.stars ?: 0)) - } - + LabeledIcon( + icon = Icons.Outlined.Star, + label = NumberFormatter.compact(repo.stars ?: 0), + iconTint = MaterialTheme.gloomColorScheme.star + ) - if (repo.language != null) { - Row( - horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - Icons.Filled.Circle, - contentDescription = repo.language!!.name, - modifier = Modifier.size(15.dp), - tint = repo.language?.color?.parsedColor - ?: MaterialTheme.colorScheme.surfaceVariant - ) - Text(text = repo.language!!.name) - } - } + repo.language?.let { (name, color) -> + LabeledIcon( + icon = Icons.Filled.Circle, + label = name, + iconTint = color?.parsedColor ?: Color.Black + ) } } } diff --git a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/util/ModifierUtil.kt b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/util/ModifierUtil.kt index 89da1ca4..6c877544 100644 --- a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/util/ModifierUtil.kt +++ b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/util/ModifierUtil.kt @@ -8,7 +8,7 @@ import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.compose.stringResource inline fun Modifier.thenIf(predicate: Boolean, block: Modifier.() -> Modifier) = - if (predicate) then(block()) else this + if (predicate) then(Modifier.block()) else this @Composable fun Modifier.contentDescription(descRes: StringResource, mergeDescendants: Boolean = false) =