Add feed's favicon from UI Avatars (#70)

* Add feed's favicon from UI Avatars

* Fix backwards writes
This commit is contained in:
Ashinch 2022-05-15 21:16:34 +08:00 committed by GitHub
parent c79649bb77
commit f76abd98be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 209 additions and 146 deletions

View File

@ -108,6 +108,7 @@ dependencies {
implementation "com.rometools:rome:$rome" implementation "com.rometools:rome:$rome"
// https://coil-kt.github.io/coil/changelog/ // https://coil-kt.github.io/coil/changelog/
implementation("io.coil-kt:coil-base:$coil")
implementation("io.coil-kt:coil-compose:$coil") implementation("io.coil-kt:coil-compose:$coil")
implementation("io.coil-kt:coil-svg:$coil") implementation("io.coil-kt:coil-svg:$coil")
implementation("io.coil-kt:coil-gif:$coil") implementation("io.coil-kt:coil-gif:$coil")

View File

@ -1,24 +1,11 @@
package me.ash.reader package me.ash.reader
import android.app.Application import android.app.Application
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.os.Build
import androidx.hilt.work.HiltWorkerFactory import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration import androidx.work.Configuration
import androidx.work.WorkManager import androidx.work.WorkManager
import coil.ComponentRegistry
import coil.ImageLoader import coil.ImageLoader
import coil.decode.DataSource
import coil.decode.GifDecoder
import coil.decode.ImageDecoderDecoder
import coil.decode.SvgDecoder
import coil.disk.DiskCache
import coil.memory.MemoryCache
import coil.request.*
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -29,12 +16,16 @@ import me.ash.reader.data.source.AppNetworkDataSource
import me.ash.reader.data.source.OpmlLocalDataSource import me.ash.reader.data.source.OpmlLocalDataSource
import me.ash.reader.data.source.ReaderDatabase import me.ash.reader.data.source.ReaderDatabase
import me.ash.reader.ui.ext.* import me.ash.reader.ui.ext.*
import okhttp3.Cache
import okhttp3.OkHttpClient
import org.conscrypt.Conscrypt import org.conscrypt.Conscrypt
import java.io.File
import java.security.Security import java.security.Security
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
@HiltAndroidApp @HiltAndroidApp
class App : Application(), Configuration.Provider, ImageLoader { class App : Application(), Configuration.Provider {
init { init {
// From: https://gitlab.com/spacecowboy/Feeder // From: https://gitlab.com/spacecowboy/Feeder
// Install Conscrypt to handle TLSv1.3 pre Android10 // Install Conscrypt to handle TLSv1.3 pre Android10
@ -88,6 +79,9 @@ class App : Application(), Configuration.Provider, ImageLoader {
@DispatcherDefault @DispatcherDefault
lateinit var dispatcherDefault: CoroutineDispatcher lateinit var dispatcherDefault: CoroutineDispatcher
@Inject
lateinit var imageLoader: ImageLoader
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
CrashHandler(this) CrashHandler(this)
@ -130,58 +124,29 @@ class App : Application(), Configuration.Provider, ImageLoader {
.setWorkerFactory(workerFactory) .setWorkerFactory(workerFactory)
.setMinimumLoggingLevel(android.util.Log.DEBUG) .setMinimumLoggingLevel(android.util.Log.DEBUG)
.build() .build()
}
override val components: ComponentRegistry
get() = ComponentRegistry.Builder() fun cachingHttpClient(
.add(SvgDecoder.Factory()) cacheDirectory: File? = null,
.add( cacheSize: Long = 10L * 1024L * 1024L,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { trustAllCerts: Boolean = true,
ImageDecoderDecoder.Factory() connectTimeoutSecs: Long = 30L,
} else { readTimeoutSecs: Long = 30L
GifDecoder.Factory() ): OkHttpClient {
} val builder: OkHttpClient.Builder = OkHttpClient.Builder()
)
.build() if (cacheDirectory != null) {
override val defaults: DefaultRequestOptions builder.cache(Cache(cacheDirectory, cacheSize))
get() = DefaultRequestOptions() }
override val diskCache: DiskCache
get() = DiskCache.Builder() builder
.directory(cacheDir.resolve("images")) .connectTimeout(connectTimeoutSecs, TimeUnit.SECONDS)
.maxSizePercent(0.02) .readTimeout(readTimeoutSecs, TimeUnit.SECONDS)
.build() .followRedirects(true)
override val memoryCache: MemoryCache
get() = MemoryCache.Builder(this) // if (trustAllCerts) {
.maxSizePercent(0.25) // builder.trustAllCerts()
.build() // }
override fun enqueue(request: ImageRequest): Disposable { return builder.build()
// Always call onStart before onSuccess.
request.target?.onStart(request.placeholder)
val result = ColorDrawable(Color.BLACK)
request.target?.onSuccess(result)
return object : Disposable {
override val job = CompletableDeferred(newResult(request, result))
override val isDisposed get() = true
override fun dispose() {}
}
}
override suspend fun execute(request: ImageRequest): ImageResult {
return newResult(request, ColorDrawable(Color.BLACK))
}
override fun newBuilder(): ImageLoader.Builder {
throw UnsupportedOperationException()
}
override fun shutdown() {
}
private fun newResult(request: ImageRequest, drawable: Drawable): SuccessResult {
return SuccessResult(
drawable = drawable,
request = request,
dataSource = DataSource.MEMORY_CACHE
)
}
} }

View File

@ -4,16 +4,22 @@ import android.os.Bundle
import android.util.Log import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.runtime.CompositionLocalProvider
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.profileinstaller.ProfileInstallerInitializer import androidx.profileinstaller.ProfileInstallerInitializer
import coil.ImageLoader
import coil.compose.LocalImageLoader
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import me.ash.reader.data.preference.LanguagesPreference import me.ash.reader.data.preference.LanguagesPreference
import me.ash.reader.data.preference.SettingsProvider import me.ash.reader.data.preference.SettingsProvider
import me.ash.reader.ui.ext.languages import me.ash.reader.ui.ext.languages
import me.ash.reader.ui.page.common.HomeEntry import me.ash.reader.ui.page.common.HomeEntry
import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@Inject
lateinit var imageLoader: ImageLoader
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -27,9 +33,13 @@ class MainActivity : ComponentActivity() {
} }
setContent { setContent {
CompositionLocalProvider(
LocalImageLoader provides imageLoader,
) {
SettingsProvider { SettingsProvider {
HomeEntry() HomeEntry()
} }
} }
} }
}
} }

View File

@ -32,7 +32,7 @@ data class Article(
@ColumnInfo @ColumnInfo
var fullContent: String? = null, var fullContent: String? = null,
@ColumnInfo @ColumnInfo
var img: String? = null, val img: String? = null,
@ColumnInfo @ColumnInfo
val link: String, val link: String,
@ColumnInfo(index = true) @ColumnInfo(index = true)

View File

@ -0,0 +1,62 @@
package me.ash.reader.data.module
import android.content.Context
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import coil.ImageLoader
import coil.decode.GifDecoder
import coil.decode.ImageDecoderDecoder
import coil.decode.SvgDecoder
import coil.disk.DiskCache
import coil.memory.MemoryCache
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.Dispatchers
import me.ash.reader.cachingHttpClient
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object ImageLoaderModule {
@Provides
@Singleton
fun provideImageLoader(
@ApplicationContext context: Context
): ImageLoader {
return ImageLoader.Builder(context)
.okHttpClient(
okHttpClient = cachingHttpClient(
cacheDirectory = context.cacheDir.resolve("http")
).newBuilder()
//.addNetworkInterceptor(UserAgentInterceptor)
.build()
)
.dispatcher(Dispatchers.Default) // This slightly improves scrolling performance
.components{
add(SvgDecoder.Factory())
add(
if (SDK_INT >= Build.VERSION_CODES.P) {
ImageDecoderDecoder.Factory()
} else {
GifDecoder.Factory()
}
)
}
.diskCache(
DiskCache.Builder()
.directory(context.cacheDir.resolve("images"))
.maxSizePercent(0.02)
.build()
)
.memoryCache(
MemoryCache.Builder(context)
.maxSizePercent(0.25)
.build()
)
.build()
}
}

View File

@ -115,10 +115,9 @@ class RssHelper @Inject constructor(
.take(100) .take(100)
.trim(), .trim(),
fullContent = content, fullContent = content,
img = findImg((desc ?: content) ?: ""),
link = it.link ?: "", link = it.link ?: "",
).apply { )
img = findImg(rawDescription)
}
) )
} }
a a
@ -130,7 +129,8 @@ class RssHelper @Inject constructor(
// Using negative lookahead to skip data: urls, being inline base64 // Using negative lookahead to skip data: urls, being inline base64
// And capturing original quote to use as ending quote // And capturing original quote to use as ending quote
val regex = """img.*?src=(["'])((?!data).*?)\1""".toRegex(RegexOption.DOT_MATCHES_ALL) val regex = """img.*?src=(["'])((?!data).*?)\1""".toRegex(RegexOption.DOT_MATCHES_ALL)
return regex.find(rawDescription)?.groupValues?.get(2) // Base64 encoded images can be quite large - and crash database cursors
return regex.find(rawDescription)?.groupValues?.get(2)?.takeIf { !it.startsWith("data:") }
} }
@Throws(Exception::class) @Throws(Exception::class)

View File

@ -3,9 +3,7 @@ package me.ash.reader.ui.component
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.Immutable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.DefaultAlpha import androidx.compose.ui.graphics.DefaultAlpha
@ -14,7 +12,7 @@ import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import coil.imageLoader import coil.compose.LocalImageLoader
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.size.Precision import coil.size.Precision
import coil.size.Scale import coil.size.Scale
@ -33,37 +31,10 @@ fun AsyncImage(
@DrawableRes placeholder: Int? = R.drawable.ic_hourglass_empty_black_24dp, @DrawableRes placeholder: Int? = R.drawable.ic_hourglass_empty_black_24dp,
@DrawableRes error: Int? = R.drawable.ic_broken_image_black_24dp, @DrawableRes error: Int? = R.drawable.ic_broken_image_black_24dp,
) { ) {
val context = LocalContext.current
val color = MaterialTheme.colorScheme.onSurfaceVariant
val placeholderPainterResource = placeholder?.run { painterResource(this) }
val errorPainterResource = error?.run { painterResource(this) }
val placeholderPainter by remember {
mutableStateOf(
placeholderPainterResource?.run {
forwardingPainter(
painter = this,
colorFilter = ColorFilter.tint(color),
alpha = 0.1f,
)
}
)
}
val errorPainter by remember {
mutableStateOf(
errorPainterResource?.run {
forwardingPainter(
painter = this,
colorFilter = ColorFilter.tint(color),
alpha = 0.1f,
)
}
)
}
coil.compose.AsyncImage( coil.compose.AsyncImage(
modifier = modifier, modifier = modifier,
model = ImageRequest model = ImageRequest
.Builder(context) .Builder(LocalContext.current)
.data(data) .data(data)
.crossfade(true) .crossfade(true)
.scale(scale) .scale(scale)
@ -72,9 +43,21 @@ fun AsyncImage(
.build(), .build(),
contentDescription = contentDescription, contentDescription = contentDescription,
contentScale = contentScale, contentScale = contentScale,
imageLoader = context.imageLoader, imageLoader = LocalImageLoader.current,
placeholder = placeholderPainter, placeholder = placeholder?.run {
error = errorPainter, forwardingPainter(
painter = painterResource(this),
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant),
alpha = 0.1f,
)
},
error = error?.run {
forwardingPainter(
painter = painterResource(this),
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant),
alpha = 0.1f,
)
},
) )
} }
@ -90,12 +73,14 @@ fun forwardingPainter(
onDraw: DrawScope.(ForwardingDrawInfo) -> Unit = DefaultOnDraw, onDraw: DrawScope.(ForwardingDrawInfo) -> Unit = DefaultOnDraw,
): Painter = ForwardingPainter(painter, alpha, colorFilter, onDraw) ): Painter = ForwardingPainter(painter, alpha, colorFilter, onDraw)
@Immutable
data class ForwardingDrawInfo( data class ForwardingDrawInfo(
val painter: Painter, val painter: Painter,
val alpha: Float, val alpha: Float,
val colorFilter: ColorFilter?, val colorFilter: ColorFilter?,
) )
@Immutable
private class ForwardingPainter( private class ForwardingPainter(
private val painter: Painter, private val painter: Painter,
private var alpha: Float, private var alpha: Float,

View File

@ -0,0 +1,45 @@
package me.ash.reader.ui.page.home
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@Composable
fun FeedIcon(
feedName: String,
size: Dp = 20.dp
) {
Row(
modifier = Modifier
.size(20.dp)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.outline.copy(alpha = 0.2f))
) {}
// val url by remember {
// mutableStateOf(
// "https://ui-avatars.com/api/?length=1&background=random&name=${
// URLEncoder.encode(
// feedName,
// Charsets.UTF_8.toString()
// )
// }"
// )
// }
//
// AsyncImage(
// modifier = Modifier
// .size(size)
// .clip(CircleShape),
// contentDescription = feedName,
// data = url,
// placeholder = null,
// )
}

View File

@ -60,7 +60,7 @@ class HomeViewModel @Inject constructor(
private fun fetchArticles() { private fun fetchArticles() {
_viewState.update { _viewState.update {
it.copy( it.copy(
pagingData = Pager(PagingConfig(pageSize = 15)) { pagingData = Pager(PagingConfig(pageSize = 50)) {
if (_viewState.value.searchContent.isNotBlank()) { if (_viewState.value.searchContent.isNotBlank()) {
rssRepository.get().searchArticles( rssRepository.get().searchArticles(
content = _viewState.value.searchContent.trim(), content = _viewState.value.searchContent.trim(),

View File

@ -1,16 +1,16 @@
package me.ash.reader.ui.page.home.feeds package me.ash.reader.ui.page.home.feeds
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Badge import androidx.compose.material3.Badge
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@ -20,6 +20,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import me.ash.reader.data.entity.Feed import me.ash.reader.data.entity.Feed
import me.ash.reader.ui.page.home.FeedIcon
import me.ash.reader.ui.page.home.feeds.option.feed.FeedOptionViewAction import me.ash.reader.ui.page.home.feeds.option.feed.FeedOptionViewAction
import me.ash.reader.ui.page.home.feeds.option.feed.FeedOptionViewModel import me.ash.reader.ui.page.home.feeds.option.feed.FeedOptionViewModel
import kotlin.math.ln import kotlin.math.ln
@ -30,7 +31,6 @@ import kotlin.math.ln
) )
@Composable @Composable
fun FeedItem( fun FeedItem(
modifier: Modifier = Modifier,
feed: Feed, feed: Feed,
feedOptionViewModel: FeedOptionViewModel = hiltViewModel(), feedOptionViewModel: FeedOptionViewModel = hiltViewModel(),
tonalElevation: Dp, tonalElevation: Dp,
@ -38,6 +38,11 @@ fun FeedItem(
) { ) {
val view = LocalView.current val view = LocalView.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val tonalElevationAlpha by remember {
derivedStateOf {
(ln(tonalElevation.value + 1.4f) + 2f) / 100f
}
}
Row( Row(
modifier = Modifier modifier = Modifier
@ -63,12 +68,7 @@ fun FeedItem(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Row(modifier = Modifier.weight(1f)) { Row(modifier = Modifier.weight(1f)) {
Row( FeedIcon(feed.name)
modifier = Modifier
.size(20.dp)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.outline.copy(alpha = 0.2f))
) {}
Text( Text(
modifier = Modifier.padding(start = 12.dp, end = 6.dp), modifier = Modifier.padding(start = 12.dp, end = 6.dp),
text = feed.name, text = feed.name,
@ -78,10 +78,10 @@ fun FeedItem(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
} }
if (feed.important ?: 0 != 0) { if ((feed.important ?: 0) != 0) {
Badge( Badge(
containerColor = MaterialTheme.colorScheme.surfaceTint.copy( containerColor = MaterialTheme.colorScheme.surfaceTint.copy(
alpha = (ln(tonalElevation.value + 1.4f) + 2f) / 100f alpha = tonalElevationAlpha
), ),
contentColor = MaterialTheme.colorScheme.outline, contentColor = MaterialTheme.colorScheme.outline,
content = { content = {

View File

@ -8,7 +8,6 @@ import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ExpandLess import androidx.compose.material.icons.rounded.ExpandLess
import androidx.compose.material.icons.rounded.ExpandMore import androidx.compose.material.icons.rounded.ExpandMore
@ -32,7 +31,7 @@ import me.ash.reader.ui.ext.alphaLN
import me.ash.reader.ui.page.home.feeds.option.group.GroupOptionViewAction import me.ash.reader.ui.page.home.feeds.option.group.GroupOptionViewAction
import me.ash.reader.ui.page.home.feeds.option.group.GroupOptionViewModel import me.ash.reader.ui.page.home.feeds.option.group.GroupOptionViewModel
@OptIn(ExperimentalMaterialApi::class, androidx.compose.foundation.ExperimentalFoundationApi::class) @OptIn(androidx.compose.foundation.ExperimentalFoundationApi::class)
@Composable @Composable
fun GroupItem( fun GroupItem(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -114,7 +113,6 @@ fun GroupItem(
Column { Column {
feeds.forEach { feed -> feeds.forEach { feed ->
FeedItem( FeedItem(
modifier = Modifier.padding(horizontal = 20.dp),
feed = feed, feed = feed,
tonalElevation = tonalElevation, tonalElevation = tonalElevation,
) { ) {

View File

@ -6,8 +6,6 @@ import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.CreateNewFolder import androidx.compose.material.icons.outlined.CreateNewFolder
import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.rounded.RssFeed
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -26,6 +24,7 @@ import me.ash.reader.ui.component.TextFieldDialog
import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.ext.collectAsStateValue
import me.ash.reader.ui.ext.roundClick import me.ash.reader.ui.ext.roundClick
import me.ash.reader.ui.ext.showToast import me.ash.reader.ui.ext.showToast
import me.ash.reader.ui.page.home.FeedIcon
import me.ash.reader.ui.page.home.feeds.subscribe.ResultView import me.ash.reader.ui.page.home.feeds.subscribe.ResultView
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@ -56,12 +55,13 @@ fun FeedOptionDrawer(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
Icon( FeedIcon(feedName = feed?.name ?: "", size = 24.dp)
modifier = Modifier.roundClick { }, // Icon(
imageVector = Icons.Rounded.RssFeed, // modifier = Modifier.roundClick { },
contentDescription = feed?.name ?: stringResource(R.string.unknown), // imageVector = Icons.Rounded.RssFeed,
tint = MaterialTheme.colorScheme.secondary, // contentDescription = feed?.name ?: stringResource(R.string.unknown),
) // tint = MaterialTheme.colorScheme.secondary,
// )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Text( Text(
modifier = Modifier.roundClick { modifier = Modifier.roundClick {

View File

@ -1,9 +1,7 @@
package me.ash.reader.ui.page.home.flow package me.ash.reader.ui.page.home.flow
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Star import androidx.compose.material.icons.rounded.Star
@ -26,10 +24,10 @@ import me.ash.reader.data.entity.ArticleWithFeed
import me.ash.reader.data.preference.* import me.ash.reader.data.preference.*
import me.ash.reader.ui.component.AsyncImage import me.ash.reader.ui.component.AsyncImage
import me.ash.reader.ui.ext.formatAsString import me.ash.reader.ui.ext.formatAsString
import me.ash.reader.ui.page.home.FeedIcon
@Composable @Composable
fun ArticleItem( fun ArticleItem(
modifier: Modifier = Modifier,
articleWithFeed: ArticleWithFeed, articleWithFeed: ArticleWithFeed,
onClick: (ArticleWithFeed) -> Unit = {}, onClick: (ArticleWithFeed) -> Unit = {},
) { ) {
@ -107,12 +105,7 @@ fun ArticleItem(
) { ) {
// Feed icon // Feed icon
if (articleListFeedIcon.value) { if (articleListFeedIcon.value) {
Row( FeedIcon(articleWithFeed.feed.name)
modifier = Modifier
.size(20.dp)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.outline.copy(alpha = 0.2f))
) {}
Spacer(modifier = Modifier.width(10.dp)) Spacer(modifier = Modifier.width(10.dp))
} }

View File

@ -30,15 +30,14 @@ fun LazyListScope.ArticleList(
} }
} }
is FlowItemView.Date -> { is FlowItemView.Date -> {
val separator = pagingItems[index] as FlowItemView.Date if (item.showSpacer) item { Spacer(modifier = Modifier.height(40.dp)) }
if (separator.showSpacer) item { Spacer(modifier = Modifier.height(40.dp)) }
if (articleListDateStickyHeader) { if (articleListDateStickyHeader) {
stickyHeader(key = separator.date) { stickyHeader(key = item.date) {
StickyHeader(separator.date, articleListFeedIcon, articleListTonalElevation) StickyHeader(item.date, articleListFeedIcon, articleListTonalElevation)
} }
} else { } else {
item(key = separator.date) { item(key = item.date) {
StickyHeader(separator.date, articleListFeedIcon, articleListTonalElevation) StickyHeader(item.date, articleListFeedIcon, articleListTonalElevation)
} }
} }
} }

View File

@ -12,6 +12,7 @@ import kotlinx.coroutines.launch
import me.ash.reader.data.entity.ArticleWithFeed import me.ash.reader.data.entity.ArticleWithFeed
import me.ash.reader.data.repository.RssRepository import me.ash.reader.data.repository.RssRepository
import java.util.* import java.util.*
import javax.annotation.concurrent.Immutable
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@ -116,7 +117,10 @@ enum class MarkAsReadBefore {
All, All,
} }
@Immutable
sealed class FlowItemView { sealed class FlowItemView {
@Immutable
class Article(val articleWithFeed: ArticleWithFeed) : FlowItemView() class Article(val articleWithFeed: ArticleWithFeed) : FlowItemView()
@Immutable
class Date(val date: String, val showSpacer: Boolean) : FlowItemView() class Date(val date: String, val showSpacer: Boolean) : FlowItemView()
} }

View File

@ -1,6 +1,7 @@
package me.ash.reader.ui.page.home.read package me.ash.reader.ui.page.home.read
import android.content.Intent import android.content.Intent
import android.util.Log
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
@ -35,7 +36,6 @@ fun ReadPage(
) { ) {
val viewState = readViewModel.viewState.collectAsStateValue() val viewState = readViewModel.viewState.collectAsStateValue()
val isScrollDown = viewState.listState.isScrollDown() val isScrollDown = viewState.listState.isScrollDown()
// val isScrollDown by remember { mutableStateOf(false) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
navController.currentBackStackEntryFlow.collect { navController.currentBackStackEntryFlow.collect {
@ -46,6 +46,7 @@ fun ReadPage(
} }
LaunchedEffect(viewState.articleWithFeed?.article?.id) { LaunchedEffect(viewState.articleWithFeed?.article?.id) {
Log.i("RLog", "ReadPage: ${viewState.articleWithFeed}")
viewState.articleWithFeed?.let { viewState.articleWithFeed?.let {
if (it.article.isUnread) { if (it.article.isUnread) {
readViewModel.dispatch(ReadViewAction.MarkUnread(false)) readViewModel.dispatch(ReadViewAction.MarkUnread(false))