diff --git a/app/src/main/java/friendly/android/FeedCard.kt b/app/src/main/java/friendly/android/FeedCard.kt index 53688f9..6b0c2df 100644 --- a/app/src/main/java/friendly/android/FeedCard.kt +++ b/app/src/main/java/friendly/android/FeedCard.kt @@ -6,9 +6,9 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio @@ -23,13 +23,14 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.ElevatedSuggestionChip +import androidx.compose.material3.CardDefaults.elevatedCardElevation +import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.FilledTonalIconButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.SuggestionChip import androidx.compose.material3.SuggestionChipDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -64,8 +65,9 @@ fun FeedCard( modifier: Modifier = Modifier, ) { Box(modifier = modifier.fillMaxSize()) { - OutlinedCard( + ElevatedCard( modifier = Modifier.fillMaxSize(), + elevation = elevatedCardElevation(), ) { FeedCardContent( entry = entry, @@ -91,41 +93,31 @@ private fun FeedCardContent( .verticalScroll(rememberScrollState()) .fillMaxSize(), ) { - AvatarWithInterests( + AvatarWithOverlay( nickname = entry.nickname, userId = entry.id, avatarUri = entry.avatarUri, - interests = entry.interests, + isExtendedNetwork = entry.isExtendedNetwork, + isRequest = entry.isRequest, modifier = Modifier .fillMaxWidth() .aspectRatio(1f), ) - Spacer(modifier = Modifier.height(8.dp)) - - Column( - modifier = Modifier - .padding(horizontal = 12.dp) - .fillMaxSize(), + Surface( + color = MaterialTheme.colorScheme.surfaceContainerLow, ) { - Text( - text = entry.nickname.string, - style = MaterialTheme.typography.headlineMedium, - fontWeight = FontWeight.SemiBold, - modifier = Modifier.fillMaxWidth(), - ) - - Spacer(Modifier.height(8.dp)) - - ExpandableDescription( - description = entry.description, - collapsedMaxLine = 7, - expandText = stringResource(R.string.expand), - modifier = Modifier - .fillMaxWidth(), - ) + Column { + ExpandableDescription( + description = entry.description, + collapsedMaxLine = 7, + expandText = stringResource(R.string.expand), + modifier = Modifier.fillMaxWidth(), + interests = entry.interests, + ) - Spacer(Modifier.height(96.dp)) + Spacer(Modifier.height(96.dp)) + } } } @@ -137,7 +129,9 @@ private fun FeedCardContent( ) { FilledTonalIconButton( colors = IconButtonDefaults.iconButtonColors( - containerColor = MaterialTheme.colorScheme.errorContainer, + containerColor = + MaterialTheme.colorScheme.surfaceContainerHighest, + contentColor = MaterialTheme.colorScheme.onSurface, ), onClick = { dislike(entry) }, modifier = Modifier @@ -157,6 +151,10 @@ private fun FeedCardContent( Spacer(Modifier.weight(1f)) FilledTonalIconButton( + colors = IconButtonDefaults.iconButtonColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer, + ), onClick = { like(entry) }, modifier = Modifier.size( IconButtonDefaults.mediumContainerSize( @@ -172,32 +170,6 @@ private fun FeedCardContent( ) } } - - Row( - horizontalArrangement = Arrangement.spacedBy(4.dp), - modifier = Modifier - .padding(8.dp) - .align(Alignment.TopStart), - ) { - if (entry.isRequest) { - Surface(modifier = Modifier.clip(RoundedCornerShape(16.dp))) { - Text( - text = stringResource(R.string.friend_request), - modifier = Modifier - .padding(horizontal = 12.dp, vertical = 8.dp), - ) - } - } - if (entry.isExtendedNetwork) { - Surface(modifier = Modifier.clip(RoundedCornerShape(16.dp))) { - Text( - text = stringResource(R.string.extended_network), - modifier = Modifier - .padding(horizontal = 12.dp, vertical = 8.dp), - ) - } - } - } } } @@ -206,6 +178,7 @@ fun ExpandableDescription( description: UserDescription, collapsedMaxLine: Int = 7, expandText: String, + interests: List, modifier: Modifier = Modifier, ) { val text = description.string @@ -213,61 +186,76 @@ fun ExpandableDescription( var isClickable by remember { mutableStateOf(false) } var lastCharacterIndex by remember { mutableStateOf(0) } - Column( + Surface( + color = MaterialTheme.colorScheme.surfaceContainer, modifier = modifier + .padding(horizontal = 8.dp) .clickable( onClick = { isExpanded = true }, interactionSource = remember { MutableInteractionSource() }, indication = null, - ), + ) + .clip(MaterialTheme.shapes.medium), ) { - val annotatedText = buildAnnotatedString { - if (isClickable) { - if (isExpanded) { - append(text) - } else { - val adjustText = text - .take(lastCharacterIndex) - .dropLast(expandText.length) - .dropLastWhile { it.isWhitespace() || it == '.' } + Column(Modifier.padding(vertical = 16.dp)) { + Interests(interests) - append(adjustText) + Spacer(Modifier.height(8.dp)) + + val annotatedText = buildAnnotatedString { + if (isClickable) { + if (isExpanded) { + append(text) + } else { + val adjustText = text + .take(lastCharacterIndex) + .dropLast(expandText.length) + .dropLastWhile { it.isWhitespace() || it == '.' } + + append(adjustText) + } + } else { + append(text) } - } else { - append(text) } - } - Text( - text = annotatedText, - maxLines = if (isExpanded) Int.MAX_VALUE else collapsedMaxLine, - onTextLayout = { textLayoutResult -> - if (!isExpanded && textLayoutResult.hasVisualOverflow) { - isClickable = true - lastCharacterIndex = textLayoutResult - .getLineEnd(collapsedMaxLine - 1) - } - }, - modifier = Modifier.animateContentSize(), - ) - if (isClickable && !isExpanded) { Text( - text = expandText, - fontWeight = FontWeight.Black, - textAlign = TextAlign.End, + text = annotatedText, + maxLines = if (isExpanded) Int.MAX_VALUE else collapsedMaxLine, + onTextLayout = { textLayoutResult -> + if (!isExpanded && textLayoutResult.hasVisualOverflow) { + isClickable = true + lastCharacterIndex = textLayoutResult + .getLineEnd(collapsedMaxLine - 1) + } + }, + style = MaterialTheme.typography.bodyMedium, modifier = Modifier - .fillMaxWidth(), + .padding(horizontal = 20.dp) + .animateContentSize(), ) + if (isClickable && !isExpanded) { + Text( + text = expandText, + fontWeight = FontWeight.Black, + textAlign = TextAlign.End, + style = MaterialTheme.typography.labelLarge, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + ) + } } } } @Composable -private fun AvatarWithInterests( +private fun AvatarWithOverlay( userId: UserId, nickname: Nickname, avatarUri: Uri?, - interests: List, + isExtendedNetwork: Boolean, + isRequest: Boolean, modifier: Modifier = Modifier, ) { Box(modifier = modifier) { @@ -278,31 +266,88 @@ private fun AvatarWithInterests( modifier = Modifier.fillMaxSize(), ) - LazyRow( + Surface( + color = MaterialTheme.colorScheme.surfaceContainerLow, + shape = RoundedCornerShape( + topStart = 20.dp, + topEnd = 20.dp, + ), modifier = Modifier - .align(Alignment.BottomCenter) + .align(Alignment.BottomStart) .fillMaxWidth(), ) { - itemsIndexed(interests) { i, interest -> - val useDark = isSystemInDarkTheme() - val color = remember(interest.string, useDark) { - Color.pastelFromString( - string = interest.string, - useDark = useDark, + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .height(IntrinsicSize.Min) + .padding(top = 16.dp) + .padding(bottom = 8.dp) + .padding(horizontal = 20.dp), + ) { + Text( + text = nickname.string, + style = MaterialTheme.typography.headlineSmall, + fontWeight = FontWeight.SemiBold, + maxLines = 2, + overflow = Ellipsis, + modifier = Modifier.weight(1f, fill = false), + ) + if (isRequest) { + SuggestionChip( + onClick = {}, + label = { + Text(stringResource(R.string.friend_request)) + }, + modifier = Modifier + .padding(start = 8.dp) + .height(36.dp), + ) + } else if (isExtendedNetwork) { + SuggestionChip( + onClick = {}, + label = { + Text(stringResource(R.string.extended_network)) + }, + modifier = Modifier + .padding(start = 8.dp) + .height(30.dp), ) } - Spacer(Modifier.width(8.dp)) - ElevatedSuggestionChip( - onClick = {}, - label = { Text(interest.string) }, - colors = SuggestionChipDefaults.suggestionChipColors( - containerColor = color, - labelColor = MaterialTheme.colorScheme.onSurface, - ), + } + } + } +} + +@Composable +private fun Interests(interests: List) { + LazyRow( + modifier = Modifier.fillMaxWidth(), + ) { + itemsIndexed(interests) { i, interest -> + val useDark = isSystemInDarkTheme() + val color = remember(interest.string, useDark) { + Color.pastelFromString( + string = interest.string, + useDark = useDark, ) - if (i == interests.lastIndex) { - Spacer(Modifier.width(8.dp)) - } + } + if (i == 0) { + Spacer(Modifier.width(20.dp)) + } else { + Spacer(Modifier.width(8.dp)) + } + SuggestionChip( + onClick = {}, + label = { Text(interest.string) }, + colors = SuggestionChipDefaults.suggestionChipColors( + containerColor = color, + labelColor = MaterialTheme.colorScheme.onSurface, + ), + border = null, + modifier = Modifier.height(28.dp), + ) + if (i == interests.lastIndex) { + Spacer(Modifier.width(20.dp)) } } } diff --git a/app/src/main/java/friendly/android/IndicatedCardFeed.kt b/app/src/main/java/friendly/android/IndicatedCardFeed.kt index eca4005..ea82926 100644 --- a/app/src/main/java/friendly/android/IndicatedCardFeed.kt +++ b/app/src/main/java/friendly/android/IndicatedCardFeed.kt @@ -29,6 +29,7 @@ fun IndicatedCardFeed( modifier: Modifier = Modifier, ) { val swipeCardsState = rememberSwipeableCardsState( + visibleCardsInStack = 2, itemCount = { currentItems.size }, ) @@ -54,7 +55,7 @@ fun IndicatedCardFeed( }, modifier = Modifier.fillMaxSize(), ) { - items(currentItems) { item, _, _ -> + items(currentItems) { item, index, offset -> FeedCard( entry = item, like = { entry -> diff --git a/app/src/main/java/friendly/android/ProfileScreen.kt b/app/src/main/java/friendly/android/ProfileScreen.kt index d34e8a1..35b0bf4 100644 --- a/app/src/main/java/friendly/android/ProfileScreen.kt +++ b/app/src/main/java/friendly/android/ProfileScreen.kt @@ -21,7 +21,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog @@ -418,7 +417,10 @@ fun LoadedProfileState( Spacer(Modifier.height(16.dp)) FlowRow( - horizontalArrangement = Arrangement.Center, + horizontalArrangement = Arrangement.spacedBy( + space = 8.dp, + alignment = Alignment.CenterHorizontally, + ), verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth(), ) { @@ -432,23 +434,17 @@ fun LoadedProfileState( } val label = MaterialTheme.colorScheme.onSurface key(interest.string) { - Row { - Spacer(Modifier.width(8.dp)) - SuggestionChip( - onClick = {}, - label = { Text(interest.string) }, - colors = SuggestionChipDefaults - .suggestionChipColors( - containerColor = color, - labelColor = label, - ), - border = null, - modifier = Modifier.height(32.dp), - ) - if (i == interests.size) { - Spacer(Modifier.width(8.dp)) - } - } + SuggestionChip( + onClick = {}, + label = { Text(interest.string) }, + colors = SuggestionChipDefaults + .suggestionChipColors( + containerColor = color, + labelColor = label, + ), + border = null, + modifier = Modifier.height(32.dp), + ) } } } diff --git a/cards/src/main/kotlin/friendly/cards/LazySwipeableCardsScope.kt b/cards/src/main/kotlin/friendly/cards/LazySwipeableCardsScope.kt index bf18134..6b4d28f 100644 --- a/cards/src/main/kotlin/friendly/cards/LazySwipeableCardsScope.kt +++ b/cards/src/main/kotlin/friendly/cards/LazySwipeableCardsScope.kt @@ -35,12 +35,12 @@ class LazySwipeableCardsScopeImpl : LazySwipeableCardsScope { } } -inline fun LazySwipeableCardsScope.items( +fun LazySwipeableCardsScope.items( items: List, - noinline itemContent: @Composable (T, Int, Offset) -> Unit, + itemContent: @Composable (T, Int, Offset) -> Unit, ) = addItems(items, itemContent) -inline fun LazySwipeableCardsScope.item( +fun LazySwipeableCardsScope.item( item: T, - noinline itemContent: @Composable (T, Int, Offset) -> Unit, + itemContent: @Composable (T, Int, Offset) -> Unit, ) = addItem(item, itemContent) diff --git a/cards/src/main/kotlin/friendly/cards/RememberSwipeableCardsState.kt b/cards/src/main/kotlin/friendly/cards/RememberSwipeableCardsState.kt index e7f894a..0807830 100644 --- a/cards/src/main/kotlin/friendly/cards/RememberSwipeableCardsState.kt +++ b/cards/src/main/kotlin/friendly/cards/RememberSwipeableCardsState.kt @@ -22,11 +22,13 @@ import androidx.compose.runtime.remember */ @Composable fun rememberSwipeableCardsState( + visibleCardsInStack: Int = SwipeableCardsDefaults.VISIBLE_CARDS_IN_STACK, initialCardIndex: Int = 0, itemCount: () -> Int, ): SwipeableCardsState { val state = remember { SwipeableCardsState( + visibleCardsInStack = visibleCardsInStack, initialCardIndex = initialCardIndex, itemCount = itemCount, )