diff --git a/app/src/main/java/me/ash/reader/ui/component/FeedIcon.kt b/app/src/main/java/me/ash/reader/ui/component/FeedIcon.kt index 11c216401..52e162b41 100644 --- a/app/src/main/java/me/ash/reader/ui/component/FeedIcon.kt +++ b/app/src/main/java/me/ash/reader/ui/component/FeedIcon.kt @@ -12,11 +12,13 @@ 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.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import me.ash.reader.R import me.ash.reader.ui.component.base.Base64Image import me.ash.reader.ui.component.base.RYAsyncImage @@ -29,25 +31,9 @@ fun FeedIcon( ) { if (iconUrl.isNullOrEmpty()) { if (placeholderIcon == null) { - Box( - modifier = Modifier - .size(size) - .clip(CircleShape) - .background(MaterialTheme.colorScheme.primary), - contentAlignment = Alignment.Center, - ) { - Text( - text = feedName.ifEmpty { " " }.first().toString(), - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onPrimary, - fontSize = 10.sp, - ) - } + FontIcon(size, feedName) } else { - Icon( - imageVector = placeholderIcon, - contentDescription = feedName, - ) + ImageIcon(placeholderIcon, feedName) } } // e.g. image/gif;base64,R0lGODlh... @@ -56,7 +42,9 @@ fun FeedIcon( modifier = Modifier .size(size) .clip(CircleShape), - base64Uri = iconUrl) + base64Uri = iconUrl, + onEmpty = { FontIcon(size, feedName) }, + ) } else { RYAsyncImage( modifier = Modifier @@ -69,8 +57,34 @@ fun FeedIcon( } } +@Composable +private fun ImageIcon(placeholderIcon: ImageVector, feedName: String) { + Icon( + imageVector = placeholderIcon, + contentDescription = feedName, + ) +} + +@Composable +private fun FontIcon(size: Dp, feedName: String) { + Box( + modifier = Modifier + .size(size) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.primary), + contentAlignment = Alignment.Center, + ) { + Text( + text = feedName.ifEmpty { " " }.first().toString(), + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onPrimary, + fontSize = 10.sp, + ) + } +} + @Preview @Composable -fun FeedIconPrev(){ - FeedIcon("AFF", null) +fun FeedIconPrev() { + FeedIcon(stringResource(R.string.preview_feed_name), null) } diff --git a/app/src/main/java/me/ash/reader/ui/component/base/Base64Image.kt b/app/src/main/java/me/ash/reader/ui/component/base/Base64Image.kt index 23293fcfd..22f1a2212 100644 --- a/app/src/main/java/me/ash/reader/ui/component/base/Base64Image.kt +++ b/app/src/main/java/me/ash/reader/ui/component/base/Base64Image.kt @@ -16,7 +16,8 @@ import com.caverock.androidsvg.SVG @Composable fun Base64Image( modifier: Modifier = Modifier, - base64Uri: String + base64Uri: String, + onEmpty: @Composable () -> Unit = {}, ) { val isSvg = base64Uri.startsWith("image/svg") @@ -30,11 +31,15 @@ fun Base64Image( val bytes = base64ToBytes(base64Uri) val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) - Image( - bitmap = bitmap.asImageBitmap(), - modifier = modifier, - contentDescription = null - ) + if (bitmap == null) { + onEmpty() + } else { + Image( + bitmap = bitmap.asImageBitmap(), + modifier = modifier, + contentDescription = null + ) + } } } diff --git a/app/src/main/java/me/ash/reader/ui/component/base/FeedbackIconButton.kt b/app/src/main/java/me/ash/reader/ui/component/base/FeedbackIconButton.kt index 8670dcc68..58b3f6b86 100644 --- a/app/src/main/java/me/ash/reader/ui/component/base/FeedbackIconButton.kt +++ b/app/src/main/java/me/ash/reader/ui/component/base/FeedbackIconButton.kt @@ -4,7 +4,13 @@ import android.view.HapticFeedbackConstants import android.view.SoundEffectConstants import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.* +import androidx.compose.material3.Badge +import androidx.compose.material3.BadgedBox +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -20,6 +26,7 @@ fun FeedbackIconButton( imageVector: ImageVector, contentDescription: String?, tint: Color = LocalContentColor.current, + enabled: Boolean = true, showBadge: Boolean = false, isHaptic: Boolean? = true, isSound: Boolean? = true, @@ -28,6 +35,7 @@ fun FeedbackIconButton( val view = LocalView.current IconButton( + enabled = enabled, onClick = { if (isHaptic == true) view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) if (isSound == true) view.playSoundEffect(SoundEffectConstants.CLICK) @@ -62,4 +70,4 @@ fun FeedbackIconButton( ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt index 0c6d594a0..b020c67d0 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt @@ -1,9 +1,19 @@ package me.ash.reader.ui.page.home.feeds import androidx.activity.compose.BackHandler -import androidx.compose.animation.core.* +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.windowInsetsBottomHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState @@ -14,7 +24,15 @@ import androidx.compose.material.icons.rounded.Add import androidx.compose.material.icons.rounded.Refresh import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate @@ -26,9 +44,21 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController import androidx.work.WorkInfo import me.ash.reader.R -import me.ash.reader.infrastructure.preference.* +import me.ash.reader.infrastructure.preference.LocalFeedsFilterBarFilled +import me.ash.reader.infrastructure.preference.LocalFeedsFilterBarPadding +import me.ash.reader.infrastructure.preference.LocalFeedsFilterBarStyle +import me.ash.reader.infrastructure.preference.LocalFeedsFilterBarTonalElevation +import me.ash.reader.infrastructure.preference.LocalFeedsGroupListExpand +import me.ash.reader.infrastructure.preference.LocalFeedsGroupListTonalElevation +import me.ash.reader.infrastructure.preference.LocalFeedsTopBarTonalElevation +import me.ash.reader.infrastructure.preference.LocalNewVersionNumber +import me.ash.reader.infrastructure.preference.LocalSkipVersionNumber import me.ash.reader.ui.component.FilterBar -import me.ash.reader.ui.component.base.* +import me.ash.reader.ui.component.base.Banner +import me.ash.reader.ui.component.base.DisplayText +import me.ash.reader.ui.component.base.FeedbackIconButton +import me.ash.reader.ui.component.base.RYScaffold +import me.ash.reader.ui.component.base.Subtitle import me.ash.reader.ui.ext.alphaLN import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.ext.findActivity @@ -144,6 +174,7 @@ fun FeedsPage( imageVector = Icons.Rounded.Refresh, contentDescription = stringResource(R.string.refresh), tint = MaterialTheme.colorScheme.onSurface, + enabled = !isSyncing ) { if (!isSyncing) homeViewModel.sync() } diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt index a9124f121..6fb26d1c0 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt @@ -2,20 +2,40 @@ package me.ash.reader.ui.page.home.flow import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.material.* +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.Spacer +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.layout.width +import androidx.compose.material.DismissDirection +import androidx.compose.material.DismissValue +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.SwipeToDismiss import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.CheckCircleOutline import androidx.compose.material.icons.rounded.Star +import androidx.compose.material.rememberDismissState import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -24,7 +44,13 @@ import coil.size.Precision import coil.size.Scale import me.ash.reader.R import me.ash.reader.domain.model.article.ArticleWithFeed -import me.ash.reader.infrastructure.preference.* +import me.ash.reader.infrastructure.preference.FlowArticleReadIndicatorPreference +import me.ash.reader.infrastructure.preference.LocalFlowArticleListDesc +import me.ash.reader.infrastructure.preference.LocalFlowArticleListFeedIcon +import me.ash.reader.infrastructure.preference.LocalFlowArticleListFeedName +import me.ash.reader.infrastructure.preference.LocalFlowArticleListImage +import me.ash.reader.infrastructure.preference.LocalFlowArticleListReadIndicator +import me.ash.reader.infrastructure.preference.LocalFlowArticleListTime import me.ash.reader.ui.component.FeedIcon import me.ash.reader.ui.component.base.RYAsyncImage import me.ash.reader.ui.component.base.SIZE_1000 @@ -43,6 +69,18 @@ fun ArticleItem( val articleListDesc = LocalFlowArticleListDesc.current val articleListDate = LocalFlowArticleListTime.current val articleListReadIndicator = LocalFlowArticleListReadIndicator.current + var titleHeight by remember { mutableStateOf(0) } + val density = LocalDensity.current + var descriptionLines by remember { mutableStateOf(1) } + LaunchedEffect(titleHeight) { + with(density) { + descriptionLines = if (titleHeight > 0 && titleHeight + 16.dp.roundToPx() / 16 > 32) { + 1 + } else { + 2 + } + } + } Column( modifier = Modifier @@ -136,8 +174,15 @@ fun ArticleItem( text = articleWithFeed.article.title, color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.titleMedium, - maxLines = if (articleListDesc.value) 2 else 4, + // maxLines = if (articleListDesc.value) 2 else 4, overflow = TextOverflow.Ellipsis, + modifier = Modifier.onGloballyPositioned {coordinates -> + if (titleHeight == 0) { + titleHeight = with(density) { + coordinates.size.height.toDp().value.toInt() + } + } + } ) // Description @@ -147,7 +192,7 @@ fun ArticleItem( text = articleWithFeed.article.shortDescription, color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodySmall, - maxLines = 2, + maxLines = descriptionLines, overflow = TextOverflow.Ellipsis, ) } diff --git a/app/src/main/java/me/ash/reader/ui/theme/Type.kt b/app/src/main/java/me/ash/reader/ui/theme/Type.kt index 0f1923493..ed64555d5 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/Type.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/Type.kt @@ -6,6 +6,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.unit.sp +// TODO: Rename file to Typography.kt and add @Stable + val SystemTypography = Typography( displayLarge = TextStyle( fontWeight = FontWeight.W400, @@ -117,4 +119,4 @@ internal fun Typography.applyTextDirection() = this.copy( labelLarge = labelLarge.applyTextDirection(), labelMedium = labelMedium.applyTextDirection(), labelSmall = labelSmall.applyTextDirection(), -) \ No newline at end of file +)