Add feed's favicon from UI Avatars (#70)
* Add feed's favicon from UI Avatars * Fix backwards writes
This commit is contained in:
parent
c79649bb77
commit
f76abd98be
|
@ -108,6 +108,7 @@ dependencies {
|
|||
implementation "com.rometools:rome:$rome"
|
||||
|
||||
// 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-svg:$coil")
|
||||
implementation("io.coil-kt:coil-gif:$coil")
|
||||
|
|
|
@ -1,24 +1,11 @@
|
|||
package me.ash.reader
|
||||
|
||||
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.work.Configuration
|
||||
import androidx.work.WorkManager
|
||||
import coil.ComponentRegistry
|
||||
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 kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
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.ReaderDatabase
|
||||
import me.ash.reader.ui.ext.*
|
||||
import okhttp3.Cache
|
||||
import okhttp3.OkHttpClient
|
||||
import org.conscrypt.Conscrypt
|
||||
import java.io.File
|
||||
import java.security.Security
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidApp
|
||||
class App : Application(), Configuration.Provider, ImageLoader {
|
||||
class App : Application(), Configuration.Provider {
|
||||
init {
|
||||
// From: https://gitlab.com/spacecowboy/Feeder
|
||||
// Install Conscrypt to handle TLSv1.3 pre Android10
|
||||
|
@ -88,6 +79,9 @@ class App : Application(), Configuration.Provider, ImageLoader {
|
|||
@DispatcherDefault
|
||||
lateinit var dispatcherDefault: CoroutineDispatcher
|
||||
|
||||
@Inject
|
||||
lateinit var imageLoader: ImageLoader
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
CrashHandler(this)
|
||||
|
@ -130,58 +124,29 @@ class App : Application(), Configuration.Provider, ImageLoader {
|
|||
.setWorkerFactory(workerFactory)
|
||||
.setMinimumLoggingLevel(android.util.Log.DEBUG)
|
||||
.build()
|
||||
|
||||
override val components: ComponentRegistry
|
||||
get() = ComponentRegistry.Builder()
|
||||
.add(SvgDecoder.Factory())
|
||||
.add(
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
ImageDecoderDecoder.Factory()
|
||||
} else {
|
||||
GifDecoder.Factory()
|
||||
}
|
||||
)
|
||||
.build()
|
||||
override val defaults: DefaultRequestOptions
|
||||
get() = DefaultRequestOptions()
|
||||
override val diskCache: DiskCache
|
||||
get() = DiskCache.Builder()
|
||||
.directory(cacheDir.resolve("images"))
|
||||
.maxSizePercent(0.02)
|
||||
.build()
|
||||
override val memoryCache: MemoryCache
|
||||
get() = MemoryCache.Builder(this)
|
||||
.maxSizePercent(0.25)
|
||||
.build()
|
||||
|
||||
override fun enqueue(request: ImageRequest): Disposable {
|
||||
// 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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun cachingHttpClient(
|
||||
cacheDirectory: File? = null,
|
||||
cacheSize: Long = 10L * 1024L * 1024L,
|
||||
trustAllCerts: Boolean = true,
|
||||
connectTimeoutSecs: Long = 30L,
|
||||
readTimeoutSecs: Long = 30L
|
||||
): OkHttpClient {
|
||||
val builder: OkHttpClient.Builder = OkHttpClient.Builder()
|
||||
|
||||
if (cacheDirectory != null) {
|
||||
builder.cache(Cache(cacheDirectory, cacheSize))
|
||||
}
|
||||
|
||||
builder
|
||||
.connectTimeout(connectTimeoutSecs, TimeUnit.SECONDS)
|
||||
.readTimeout(readTimeoutSecs, TimeUnit.SECONDS)
|
||||
.followRedirects(true)
|
||||
|
||||
// if (trustAllCerts) {
|
||||
// builder.trustAllCerts()
|
||||
// }
|
||||
|
||||
return builder.build()
|
||||
}
|
|
@ -4,16 +4,22 @@ import android.os.Bundle
|
|||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.profileinstaller.ProfileInstallerInitializer
|
||||
import coil.ImageLoader
|
||||
import coil.compose.LocalImageLoader
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import me.ash.reader.data.preference.LanguagesPreference
|
||||
import me.ash.reader.data.preference.SettingsProvider
|
||||
import me.ash.reader.ui.ext.languages
|
||||
import me.ash.reader.ui.page.common.HomeEntry
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
@Inject
|
||||
lateinit var imageLoader: ImageLoader
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -27,8 +33,12 @@ class MainActivity : ComponentActivity() {
|
|||
}
|
||||
|
||||
setContent {
|
||||
SettingsProvider {
|
||||
HomeEntry()
|
||||
CompositionLocalProvider(
|
||||
LocalImageLoader provides imageLoader,
|
||||
) {
|
||||
SettingsProvider {
|
||||
HomeEntry()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ data class Article(
|
|||
@ColumnInfo
|
||||
var fullContent: String? = null,
|
||||
@ColumnInfo
|
||||
var img: String? = null,
|
||||
val img: String? = null,
|
||||
@ColumnInfo
|
||||
val link: String,
|
||||
@ColumnInfo(index = true)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -115,10 +115,9 @@ class RssHelper @Inject constructor(
|
|||
.take(100)
|
||||
.trim(),
|
||||
fullContent = content,
|
||||
img = findImg((desc ?: content) ?: ""),
|
||||
link = it.link ?: "",
|
||||
).apply {
|
||||
img = findImg(rawDescription)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
a
|
||||
|
@ -130,7 +129,8 @@ class RssHelper @Inject constructor(
|
|||
// Using negative lookahead to skip data: urls, being inline base64
|
||||
// And capturing original quote to use as ending quote
|
||||
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)
|
||||
|
|
|
@ -3,9 +3,7 @@ package me.ash.reader.ui.component
|
|||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
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.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import coil.imageLoader
|
||||
import coil.compose.LocalImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil.size.Precision
|
||||
import coil.size.Scale
|
||||
|
@ -33,37 +31,10 @@ fun AsyncImage(
|
|||
@DrawableRes placeholder: Int? = R.drawable.ic_hourglass_empty_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(
|
||||
modifier = modifier,
|
||||
model = ImageRequest
|
||||
.Builder(context)
|
||||
.Builder(LocalContext.current)
|
||||
.data(data)
|
||||
.crossfade(true)
|
||||
.scale(scale)
|
||||
|
@ -72,9 +43,21 @@ fun AsyncImage(
|
|||
.build(),
|
||||
contentDescription = contentDescription,
|
||||
contentScale = contentScale,
|
||||
imageLoader = context.imageLoader,
|
||||
placeholder = placeholderPainter,
|
||||
error = errorPainter,
|
||||
imageLoader = LocalImageLoader.current,
|
||||
placeholder = placeholder?.run {
|
||||
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,
|
||||
): Painter = ForwardingPainter(painter, alpha, colorFilter, onDraw)
|
||||
|
||||
@Immutable
|
||||
data class ForwardingDrawInfo(
|
||||
val painter: Painter,
|
||||
val alpha: Float,
|
||||
val colorFilter: ColorFilter?,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
private class ForwardingPainter(
|
||||
private val painter: Painter,
|
||||
private var alpha: Float,
|
||||
|
|
45
app/src/main/java/me/ash/reader/ui/page/home/FeedIcon.kt
Normal file
45
app/src/main/java/me/ash/reader/ui/page/home/FeedIcon.kt
Normal 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,
|
||||
// )
|
||||
}
|
|
@ -60,7 +60,7 @@ class HomeViewModel @Inject constructor(
|
|||
private fun fetchArticles() {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
pagingData = Pager(PagingConfig(pageSize = 15)) {
|
||||
pagingData = Pager(PagingConfig(pageSize = 50)) {
|
||||
if (_viewState.value.searchContent.isNotBlank()) {
|
||||
rssRepository.get().searchArticles(
|
||||
content = _viewState.value.searchContent.trim(),
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
package me.ash.reader.ui.page.home.feeds
|
||||
|
||||
import android.view.HapticFeedbackConstants
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.material3.Badge
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
|
@ -20,6 +20,7 @@ import androidx.compose.ui.unit.Dp
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
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.FeedOptionViewModel
|
||||
import kotlin.math.ln
|
||||
|
@ -30,7 +31,6 @@ import kotlin.math.ln
|
|||
)
|
||||
@Composable
|
||||
fun FeedItem(
|
||||
modifier: Modifier = Modifier,
|
||||
feed: Feed,
|
||||
feedOptionViewModel: FeedOptionViewModel = hiltViewModel(),
|
||||
tonalElevation: Dp,
|
||||
|
@ -38,6 +38,11 @@ fun FeedItem(
|
|||
) {
|
||||
val view = LocalView.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val tonalElevationAlpha by remember {
|
||||
derivedStateOf {
|
||||
(ln(tonalElevation.value + 1.4f) + 2f) / 100f
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
@ -63,12 +68,7 @@ fun FeedItem(
|
|||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Row(modifier = Modifier.weight(1f)) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.size(20.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.outline.copy(alpha = 0.2f))
|
||||
) {}
|
||||
FeedIcon(feed.name)
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 12.dp, end = 6.dp),
|
||||
text = feed.name,
|
||||
|
@ -78,10 +78,10 @@ fun FeedItem(
|
|||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
if (feed.important ?: 0 != 0) {
|
||||
if ((feed.important ?: 0) != 0) {
|
||||
Badge(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceTint.copy(
|
||||
alpha = (ln(tonalElevation.value + 1.4f) + 2f) / 100f
|
||||
alpha = tonalElevationAlpha
|
||||
),
|
||||
contentColor = MaterialTheme.colorScheme.outline,
|
||||
content = {
|
||||
|
|
|
@ -8,7 +8,6 @@ import androidx.compose.foundation.combinedClickable
|
|||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.ExpandLess
|
||||
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.GroupOptionViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class, androidx.compose.foundation.ExperimentalFoundationApi::class)
|
||||
@OptIn(androidx.compose.foundation.ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun GroupItem(
|
||||
modifier: Modifier = Modifier,
|
||||
|
@ -114,7 +113,6 @@ fun GroupItem(
|
|||
Column {
|
||||
feeds.forEach { feed ->
|
||||
FeedItem(
|
||||
modifier = Modifier.padding(horizontal = 20.dp),
|
||||
feed = feed,
|
||||
tonalElevation = tonalElevation,
|
||||
) {
|
||||
|
|
|
@ -6,8 +6,6 @@ import androidx.compose.material.ExperimentalMaterialApi
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.CreateNewFolder
|
||||
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.Text
|
||||
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.roundClick
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
|
@ -56,12 +55,13 @@ fun FeedOptionDrawer(
|
|||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.roundClick { },
|
||||
imageVector = Icons.Rounded.RssFeed,
|
||||
contentDescription = feed?.name ?: stringResource(R.string.unknown),
|
||||
tint = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
FeedIcon(feedName = feed?.name ?: "", size = 24.dp)
|
||||
// Icon(
|
||||
// modifier = Modifier.roundClick { },
|
||||
// imageVector = Icons.Rounded.RssFeed,
|
||||
// contentDescription = feed?.name ?: stringResource(R.string.unknown),
|
||||
// tint = MaterialTheme.colorScheme.secondary,
|
||||
// )
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
modifier = Modifier.roundClick {
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
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.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
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.ui.component.AsyncImage
|
||||
import me.ash.reader.ui.ext.formatAsString
|
||||
import me.ash.reader.ui.page.home.FeedIcon
|
||||
|
||||
@Composable
|
||||
fun ArticleItem(
|
||||
modifier: Modifier = Modifier,
|
||||
articleWithFeed: ArticleWithFeed,
|
||||
onClick: (ArticleWithFeed) -> Unit = {},
|
||||
) {
|
||||
|
@ -107,12 +105,7 @@ fun ArticleItem(
|
|||
) {
|
||||
// Feed icon
|
||||
if (articleListFeedIcon.value) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.size(20.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.outline.copy(alpha = 0.2f))
|
||||
) {}
|
||||
FeedIcon(articleWithFeed.feed.name)
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
}
|
||||
|
||||
|
|
|
@ -30,15 +30,14 @@ fun LazyListScope.ArticleList(
|
|||
}
|
||||
}
|
||||
is FlowItemView.Date -> {
|
||||
val separator = pagingItems[index] as FlowItemView.Date
|
||||
if (separator.showSpacer) item { Spacer(modifier = Modifier.height(40.dp)) }
|
||||
if (item.showSpacer) item { Spacer(modifier = Modifier.height(40.dp)) }
|
||||
if (articleListDateStickyHeader) {
|
||||
stickyHeader(key = separator.date) {
|
||||
StickyHeader(separator.date, articleListFeedIcon, articleListTonalElevation)
|
||||
stickyHeader(key = item.date) {
|
||||
StickyHeader(item.date, articleListFeedIcon, articleListTonalElevation)
|
||||
}
|
||||
} else {
|
||||
item(key = separator.date) {
|
||||
StickyHeader(separator.date, articleListFeedIcon, articleListTonalElevation)
|
||||
item(key = item.date) {
|
||||
StickyHeader(item.date, articleListFeedIcon, articleListTonalElevation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import kotlinx.coroutines.launch
|
|||
import me.ash.reader.data.entity.ArticleWithFeed
|
||||
import me.ash.reader.data.repository.RssRepository
|
||||
import java.util.*
|
||||
import javax.annotation.concurrent.Immutable
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
|
@ -116,7 +117,10 @@ enum class MarkAsReadBefore {
|
|||
All,
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed class FlowItemView {
|
||||
@Immutable
|
||||
class Article(val articleWithFeed: ArticleWithFeed) : FlowItemView()
|
||||
@Immutable
|
||||
class Date(val date: String, val showSpacer: Boolean) : FlowItemView()
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package me.ash.reader.ui.page.home.read
|
||||
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
@ -35,7 +36,6 @@ fun ReadPage(
|
|||
) {
|
||||
val viewState = readViewModel.viewState.collectAsStateValue()
|
||||
val isScrollDown = viewState.listState.isScrollDown()
|
||||
// val isScrollDown by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
navController.currentBackStackEntryFlow.collect {
|
||||
|
@ -46,6 +46,7 @@ fun ReadPage(
|
|||
}
|
||||
|
||||
LaunchedEffect(viewState.articleWithFeed?.article?.id) {
|
||||
Log.i("RLog", "ReadPage: ${viewState.articleWithFeed}")
|
||||
viewState.articleWithFeed?.let {
|
||||
if (it.article.isUnread) {
|
||||
readViewModel.dispatch(ReadViewAction.MarkUnread(false))
|
||||
|
|
Loading…
Reference in New Issue
Block a user