Skip to content

Commit

Permalink
fix(ui): null safe feed icon component (#580)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ashinch authored Feb 5, 2024
1 parent 39af42a commit 134233d
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 41 deletions.
56 changes: 35 additions & 21 deletions app/src/main/java/me/ash/reader/ui/component/FeedIcon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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...
Expand All @@ -56,7 +42,9 @@ fun FeedIcon(
modifier = Modifier
.size(size)
.clip(CircleShape),
base64Uri = iconUrl)
base64Uri = iconUrl,
onEmpty = { FontIcon(size, feedName) },
)
} else {
RYAsyncImage(
modifier = Modifier
Expand All @@ -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)
}
17 changes: 11 additions & 6 deletions app/src/main/java/me/ash/reader/ui/component/base/Base64Image.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand All @@ -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
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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)
Expand Down Expand Up @@ -62,4 +70,4 @@ fun FeedbackIconButton(
)
}
}
}
}
41 changes: 36 additions & 5 deletions app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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()
}
Expand Down
57 changes: 51 additions & 6 deletions app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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,
)
}
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/java/me/ash/reader/ui/theme/Type.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -117,4 +119,4 @@ internal fun Typography.applyTextDirection() = this.copy(
labelLarge = labelLarge.applyTextDirection(),
labelMedium = labelMedium.applyTextDirection(),
labelSmall = labelSmall.applyTextDirection(),
)
)

0 comments on commit 134233d

Please sign in to comment.