Refactor Material You design for FlowPage
This commit is contained in:
parent
d288177feb
commit
f4828ac01a
|
@ -1,27 +1,3 @@
|
||||||
package me.ash.reader
|
package me.ash.reader
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
fun Int.spacerDollar(str: Any): String = "$this$$str"
|
||||||
import androidx.compose.runtime.saveable.listSaver
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
|
||||||
import androidx.compose.runtime.toMutableStateList
|
|
||||||
|
|
||||||
fun Int.positive() = if (this < 0) 0 else this
|
|
||||||
fun Int.finitelyLarge(value: Int) = if (this > value) value else this
|
|
||||||
fun Int.finitelySmall(value: Int) = if (this < value) value else this
|
|
||||||
|
|
||||||
fun Float.positive() = if (this < 0) 0f else this
|
|
||||||
fun Float.finitelyLarge(value: Float) = if (this > value) value else this
|
|
||||||
fun Float.finitelySmall(value: Float) = if (this < value) value else this
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun <T : Any> rememberMutableStateListOf(vararg elements: T): SnapshotStateList<T> {
|
|
||||||
return rememberSaveable(
|
|
||||||
saver = listSaver(
|
|
||||||
save = { it.toList() },
|
|
||||||
restore = { it.toMutableStateList() }
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
elements.toMutableList().toMutableStateList()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,29 +8,33 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
|
||||||
class Filter(
|
class Filter(
|
||||||
var index: Int,
|
var index: Int,
|
||||||
var title: String,
|
var name: String,
|
||||||
var description: String,
|
var description: String,
|
||||||
var important: Int,
|
var important: Int,
|
||||||
var icon: ImageVector,
|
var icon: ImageVector,
|
||||||
) {
|
) {
|
||||||
|
fun isStarred(): Boolean = this == Starred
|
||||||
|
fun isUnread(): Boolean = this == Unread
|
||||||
|
fun isAll(): Boolean = this == All
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val Starred = Filter(
|
val Starred = Filter(
|
||||||
index = 0,
|
index = 0,
|
||||||
title = "Starred",
|
name = "Starred",
|
||||||
description = " Starred Items",
|
description = " Starred Items",
|
||||||
important = 13,
|
important = 13,
|
||||||
icon = Icons.Rounded.StarOutline,
|
icon = Icons.Rounded.StarOutline,
|
||||||
)
|
)
|
||||||
val Unread = Filter(
|
val Unread = Filter(
|
||||||
index = 1,
|
index = 1,
|
||||||
title = "Unread",
|
name = "Unread",
|
||||||
description = " Unread Items",
|
description = " Unread Items",
|
||||||
important = 666,
|
important = 666,
|
||||||
icon = Icons.Outlined.FiberManualRecord,
|
icon = Icons.Outlined.FiberManualRecord,
|
||||||
)
|
)
|
||||||
val All = Filter(
|
val All = Filter(
|
||||||
index = 2,
|
index = 2,
|
||||||
title = "All",
|
name = "All",
|
||||||
description = " Unread Items",
|
description = " Unread Items",
|
||||||
important = 666,
|
important = 666,
|
||||||
icon = Icons.Rounded.Subject,
|
icon = Icons.Rounded.Subject,
|
||||||
|
|
|
@ -12,6 +12,15 @@ interface FeedDao {
|
||||||
)
|
)
|
||||||
suspend fun queryAll(accountId: Int): List<Feed>
|
suspend fun queryAll(accountId: Int): List<Feed>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT * FROM feed
|
||||||
|
WHERE accountId = :accountId
|
||||||
|
and url = :url
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fun queryByLink(accountId: Int, url: String): List<Feed>
|
||||||
|
|
||||||
@Insert
|
@Insert
|
||||||
suspend fun insert(feed: Feed): Long
|
suspend fun insert(feed: Feed): Long
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,14 @@ interface GroupDao {
|
||||||
)
|
)
|
||||||
fun queryAllGroup(accountId: Int): Flow<MutableList<Group>>
|
fun queryAllGroup(accountId: Int): Flow<MutableList<Group>>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT * FROM `group`
|
||||||
|
WHERE accountId = :accountId
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
suspend fun queryAll(accountId: Int): List<Group>
|
||||||
|
|
||||||
@Insert
|
@Insert
|
||||||
suspend fun insert(group: Group): Long
|
suspend fun insert(group: Group): Long
|
||||||
|
|
||||||
|
|
|
@ -108,7 +108,7 @@ abstract class AbstractRssRepository constructor(
|
||||||
isStarred: Boolean = false,
|
isStarred: Boolean = false,
|
||||||
isUnread: Boolean = false,
|
isUnread: Boolean = false,
|
||||||
): Flow<List<ImportantCount>> {
|
): Flow<List<ImportantCount>> {
|
||||||
val accountId = context.dataStore.get(DataStoreKeys.CurrentAccountId) ?: 0
|
val accountId = context.dataStore.get(DataStoreKeys.CurrentAccountId)!!
|
||||||
Log.i(
|
Log.i(
|
||||||
"RLog",
|
"RLog",
|
||||||
"pullImportant: accountId: ${accountId}, isStarred: ${isStarred}, isUnread: ${isUnread}"
|
"pullImportant: accountId: ${accountId}, isStarred: ${isStarred}, isUnread: ${isUnread}"
|
||||||
|
@ -126,6 +126,11 @@ abstract class AbstractRssRepository constructor(
|
||||||
return articleDao.queryById(id)
|
return articleDao.queryById(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isExist(url: String): Boolean {
|
||||||
|
val accountId = context.dataStore.get(DataStoreKeys.CurrentAccountId)!!
|
||||||
|
return feedDao.queryByLink(accountId, url).isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
fun peekWork(): String {
|
fun peekWork(): String {
|
||||||
return workManager.getWorkInfosByTag("sync").get().size.toString()
|
return workManager.getWorkInfosByTag("sync").get().size.toString()
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,18 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import me.ash.reader.DataStoreKeys
|
import me.ash.reader.DataStoreKeys
|
||||||
import me.ash.reader.data.account.Account
|
import me.ash.reader.data.account.Account
|
||||||
import me.ash.reader.data.account.AccountDao
|
import me.ash.reader.data.account.AccountDao
|
||||||
|
import me.ash.reader.data.group.Group
|
||||||
|
import me.ash.reader.data.group.GroupDao
|
||||||
import me.ash.reader.dataStore
|
import me.ash.reader.dataStore
|
||||||
import me.ash.reader.get
|
import me.ash.reader.get
|
||||||
|
import me.ash.reader.spacerDollar
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AccountRepository @Inject constructor(
|
class AccountRepository @Inject constructor(
|
||||||
@ApplicationContext
|
@ApplicationContext
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val accountDao: AccountDao,
|
private val accountDao: AccountDao,
|
||||||
|
private val groupDao: GroupDao,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun getCurrentAccount(): Account? {
|
suspend fun getCurrentAccount(): Account? {
|
||||||
|
@ -30,6 +34,16 @@ class AccountRepository @Inject constructor(
|
||||||
type = Account.Type.LOCAL,
|
type = Account.Type.LOCAL,
|
||||||
).apply {
|
).apply {
|
||||||
id = accountDao.insert(this).toInt()
|
id = accountDao.insert(this).toInt()
|
||||||
|
}.also {
|
||||||
|
if (groupDao.queryAll(it.id!!).isEmpty()) {
|
||||||
|
groupDao.insert(
|
||||||
|
Group(
|
||||||
|
id = it.id!!.spacerDollar("0"),
|
||||||
|
name = "默认",
|
||||||
|
accountId = it.id!!,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -20,6 +20,7 @@ import me.ash.reader.data.source.FeverApiDataSource
|
||||||
import me.ash.reader.data.source.RssNetworkDataSource
|
import me.ash.reader.data.source.RssNetworkDataSource
|
||||||
import me.ash.reader.dataStore
|
import me.ash.reader.dataStore
|
||||||
import me.ash.reader.get
|
import me.ash.reader.get
|
||||||
|
import me.ash.reader.spacerDollar
|
||||||
import net.dankito.readability4j.extended.Readability4JExtended
|
import net.dankito.readability4j.extended.Readability4JExtended
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -82,7 +83,7 @@ class FeverRssRepository @Inject constructor(
|
||||||
feverGroupsBody.groups.forEach {
|
feverGroupsBody.groups.forEach {
|
||||||
groupDao.insert(
|
groupDao.insert(
|
||||||
Group(
|
Group(
|
||||||
id = it.id.toString(),
|
id = accountId.spacerDollar(it.id),
|
||||||
name = it.title,
|
name = it.title,
|
||||||
accountId = accountId,
|
accountId = accountId,
|
||||||
)
|
)
|
||||||
|
@ -99,7 +100,7 @@ class FeverRssRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
val feeds = feverFeeds.map {
|
val feeds = feverFeeds.map {
|
||||||
Feed(
|
Feed(
|
||||||
id = it.id.toString(),
|
id = accountId.spacerDollar(it.id),
|
||||||
name = it.title,
|
name = it.title,
|
||||||
url = it.url,
|
url = it.url,
|
||||||
groupId = feverFeedsGroupsMap[it.id].toString(),
|
groupId = feverFeedsGroupsMap[it.id].toString(),
|
||||||
|
@ -116,7 +117,7 @@ class FeverRssRepository @Inject constructor(
|
||||||
.forEach {
|
.forEach {
|
||||||
articles.add(
|
articles.add(
|
||||||
Article(
|
Article(
|
||||||
id = it.id,
|
id = accountId.spacerDollar(it.id),
|
||||||
date = Date(it.created_on_time * 1000),
|
date = Date(it.created_on_time * 1000),
|
||||||
title = it.title,
|
title = it.title,
|
||||||
author = it.author,
|
author = it.author,
|
||||||
|
|
|
@ -10,6 +10,7 @@ import javax.inject.Inject
|
||||||
class OpmlRepository @Inject constructor(
|
class OpmlRepository @Inject constructor(
|
||||||
private val groupDao: GroupDao,
|
private val groupDao: GroupDao,
|
||||||
private val feedDao: FeedDao,
|
private val feedDao: FeedDao,
|
||||||
|
private val rssRepository: RssRepository,
|
||||||
private val opmlLocalDataSource: OpmlLocalDataSource
|
private val opmlLocalDataSource: OpmlLocalDataSource
|
||||||
) {
|
) {
|
||||||
suspend fun saveToDatabase(inputStream: InputStream) {
|
suspend fun saveToDatabase(inputStream: InputStream) {
|
||||||
|
@ -18,6 +19,9 @@ class OpmlRepository @Inject constructor(
|
||||||
groupWithFeedList.forEach { groupWithFeed ->
|
groupWithFeedList.forEach { groupWithFeed ->
|
||||||
groupDao.insert(groupWithFeed.group)
|
groupDao.insert(groupWithFeed.group)
|
||||||
groupWithFeed.feeds.forEach { it.groupId = groupWithFeed.group.id }
|
groupWithFeed.feeds.forEach { it.groupId = groupWithFeed.group.id }
|
||||||
|
groupWithFeed.feeds.removeIf {
|
||||||
|
rssRepository.get().isExist(it.url)
|
||||||
|
}
|
||||||
feedDao.insertList(groupWithFeed.feeds)
|
feedDao.insertList(groupWithFeed.feeds)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import me.ash.reader.data.feed.FeedWithArticle
|
||||||
import me.ash.reader.data.source.RssNetworkDataSource
|
import me.ash.reader.data.source.RssNetworkDataSource
|
||||||
import me.ash.reader.dataStore
|
import me.ash.reader.dataStore
|
||||||
import me.ash.reader.get
|
import me.ash.reader.get
|
||||||
|
import me.ash.reader.spacerDollar
|
||||||
import net.dankito.readability4j.Readability4J
|
import net.dankito.readability4j.Readability4J
|
||||||
import net.dankito.readability4j.extended.Readability4JExtended
|
import net.dankito.readability4j.extended.Readability4JExtended
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
|
@ -28,17 +29,17 @@ class RssHelper @Inject constructor(
|
||||||
val accountId = context.dataStore.get(DataStoreKeys.CurrentAccountId) ?: 0
|
val accountId = context.dataStore.get(DataStoreKeys.CurrentAccountId) ?: 0
|
||||||
val parseRss = rssNetworkDataSource.parseRss(feedLink)
|
val parseRss = rssNetworkDataSource.parseRss(feedLink)
|
||||||
val feed = Feed(
|
val feed = Feed(
|
||||||
id = UUID.randomUUID().toString(),
|
id = accountId.spacerDollar(UUID.randomUUID().toString()),
|
||||||
name = parseRss.title!!,
|
name = parseRss.title!!,
|
||||||
url = feedLink,
|
url = feedLink,
|
||||||
groupId = UUID.randomUUID().toString(),
|
groupId = "",
|
||||||
accountId = accountId,
|
accountId = accountId,
|
||||||
)
|
)
|
||||||
val articles = mutableListOf<Article>()
|
val articles = mutableListOf<Article>()
|
||||||
parseRss.items.forEach {
|
parseRss.items.forEach {
|
||||||
articles.add(
|
articles.add(
|
||||||
Article(
|
Article(
|
||||||
id = UUID.randomUUID().toString(),
|
id = accountId.spacerDollar(UUID.randomUUID().toString()),
|
||||||
accountId = accountId,
|
accountId = accountId,
|
||||||
feedId = feed.id,
|
feedId = feed.id,
|
||||||
date = Date(it.publishDate.toString()),
|
date = Date(it.publishDate.toString()),
|
||||||
|
@ -101,7 +102,7 @@ class RssHelper @Inject constructor(
|
||||||
Log.i("RLog", "request rss ${feed.name}: ${it.title}")
|
Log.i("RLog", "request rss ${feed.name}: ${it.title}")
|
||||||
a.add(
|
a.add(
|
||||||
Article(
|
Article(
|
||||||
id = UUID.randomUUID().toString(),
|
id = accountId.spacerDollar(UUID.randomUUID().toString()),
|
||||||
accountId = accountId,
|
accountId = accountId,
|
||||||
feedId = feed.id,
|
feedId = feed.id,
|
||||||
date = Date(it.publishDate.toString()),
|
date = Date(it.publishDate.toString()),
|
||||||
|
@ -126,35 +127,39 @@ class RssHelper @Inject constructor(
|
||||||
feed: Feed,
|
feed: Feed,
|
||||||
articleLink: String?,
|
articleLink: String?,
|
||||||
) {
|
) {
|
||||||
if (articleLink == null) return
|
try {
|
||||||
val execute = OkHttpClient()
|
if (articleLink == null) return
|
||||||
.newCall(Request.Builder().url(articleLink).build())
|
val execute = OkHttpClient()
|
||||||
.execute()
|
.newCall(Request.Builder().url(articleLink).build())
|
||||||
val content = execute.body?.string()
|
.execute()
|
||||||
val regex =
|
val content = execute.body?.string()
|
||||||
Regex("""<link(.+?)rel="shortcut icon"(.+?)href="(.+?)"""")
|
val regex =
|
||||||
if (content != null) {
|
Regex("""<link(.+?)rel="shortcut icon"(.+?)href="(.+?)"""")
|
||||||
var iconLink = regex
|
if (content != null) {
|
||||||
.find(content)
|
var iconLink = regex
|
||||||
?.groups?.get(3)
|
.find(content)
|
||||||
?.value
|
?.groups?.get(3)
|
||||||
Log.i("rlog", "queryRssIcon: $iconLink")
|
?.value
|
||||||
if (iconLink != null) {
|
Log.i("rlog", "queryRssIcon: $iconLink")
|
||||||
if (iconLink.startsWith("//")) {
|
if (iconLink != null) {
|
||||||
iconLink = "http:$iconLink"
|
if (iconLink.startsWith("//")) {
|
||||||
}
|
iconLink = "http:$iconLink"
|
||||||
if (iconLink.startsWith("/")) {
|
}
|
||||||
val domainRegex =
|
if (iconLink.startsWith("/")) {
|
||||||
Regex("""http(s)?://(([\w-]+\.)+\w+(:\d{1,5})?)""")
|
val domainRegex =
|
||||||
iconLink =
|
Regex("""http(s)?://(([\w-]+\.)+\w+(:\d{1,5})?)""")
|
||||||
"http://${domainRegex.find(articleLink)?.groups?.get(2)?.value}$iconLink"
|
iconLink =
|
||||||
}
|
"http://${domainRegex.find(articleLink)?.groups?.get(2)?.value}$iconLink"
|
||||||
saveRssIcon(feedDao, feed, iconLink)
|
}
|
||||||
} else {
|
saveRssIcon(feedDao, feed, iconLink)
|
||||||
|
} else {
|
||||||
// saveRssIcon(feedDao, feed, "")
|
// saveRssIcon(feedDao, feed, "")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// saveRssIcon(feedDao, feed, "")
|
// saveRssIcon(feedDao, feed, "")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("RLog", "queryRssIcon: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,8 @@ import kotlinx.coroutines.launch
|
||||||
import me.ash.reader.data.constant.Symbol
|
import me.ash.reader.data.constant.Symbol
|
||||||
import me.ash.reader.ui.extension.collectAsStateValue
|
import me.ash.reader.ui.extension.collectAsStateValue
|
||||||
import me.ash.reader.ui.extension.findActivity
|
import me.ash.reader.ui.extension.findActivity
|
||||||
import me.ash.reader.ui.page.home.article.ArticlePage
|
|
||||||
import me.ash.reader.ui.page.home.feeds.FeedsPage
|
import me.ash.reader.ui.page.home.feeds.FeedsPage
|
||||||
|
import me.ash.reader.ui.page.home.flow.FlowPage
|
||||||
import me.ash.reader.ui.page.home.read.ReadPage
|
import me.ash.reader.ui.page.home.read.ReadPage
|
||||||
import me.ash.reader.ui.page.home.read.ReadViewAction
|
import me.ash.reader.ui.page.home.read.ReadViewAction
|
||||||
import me.ash.reader.ui.page.home.read.ReadViewModel
|
import me.ash.reader.ui.page.home.read.ReadViewModel
|
||||||
|
@ -98,35 +98,10 @@ fun HomePage(
|
||||||
state = viewState.pagerState,
|
state = viewState.pagerState,
|
||||||
composableList = listOf(
|
composableList = listOf(
|
||||||
{
|
{
|
||||||
FeedsPage(
|
FeedsPage(navController = navController)
|
||||||
navController = navController,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ArticlePage(
|
FlowPage(navController = navController)
|
||||||
navController = navController,
|
|
||||||
BackOnClick = {
|
|
||||||
viewModel.dispatch(
|
|
||||||
HomeViewAction.ScrollToPage(
|
|
||||||
scope = scope,
|
|
||||||
targetPage = 0,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
articleOnClick = {
|
|
||||||
readViewModel.dispatch(ReadViewAction.ScrollToItem(0))
|
|
||||||
readViewModel.dispatch(ReadViewAction.InitData(it))
|
|
||||||
if (it.feed.isFullContent) readViewModel.dispatch(ReadViewAction.RenderFullContent)
|
|
||||||
else readViewModel.dispatch(ReadViewAction.RenderDescriptionContent)
|
|
||||||
readViewModel.dispatch(ReadViewAction.RenderDescriptionContent)
|
|
||||||
viewModel.dispatch(
|
|
||||||
HomeViewAction.ScrollToPage(
|
|
||||||
scope = scope,
|
|
||||||
targetPage = 2,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ReadPage(
|
ReadPage(
|
||||||
|
|
|
@ -1,143 +0,0 @@
|
||||||
package me.ash.reader.ui.page.home.article
|
|
||||||
|
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.alpha
|
|
||||||
import androidx.compose.ui.graphics.asImageBitmap
|
|
||||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import me.ash.reader.DateTimeExt
|
|
||||||
import me.ash.reader.DateTimeExt.toString
|
|
||||||
import me.ash.reader.R
|
|
||||||
import me.ash.reader.data.article.ArticleWithFeed
|
|
||||||
import me.ash.reader.ui.extension.paddingFixedHorizontal
|
|
||||||
import me.ash.reader.ui.extension.roundClick
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ArticleItem(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
articleWithFeed: ArticleWithFeed? = null,
|
|
||||||
isStarredFilter: Boolean,
|
|
||||||
index: Int,
|
|
||||||
articleOnClick: (ArticleWithFeed) -> Unit,
|
|
||||||
) {
|
|
||||||
if (articleWithFeed == null) return
|
|
||||||
Column(
|
|
||||||
modifier = modifier
|
|
||||||
.paddingFixedHorizontal(
|
|
||||||
top = if (index == 0) 8.dp else 0.dp,
|
|
||||||
bottom = 8.dp
|
|
||||||
)
|
|
||||||
.roundClick {
|
|
||||||
articleOnClick(articleWithFeed)
|
|
||||||
}
|
|
||||||
.alpha(
|
|
||||||
if (isStarredFilter || articleWithFeed.article.isUnread) {
|
|
||||||
1f
|
|
||||||
} else {
|
|
||||||
0.7f
|
|
||||||
}
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Column(modifier = modifier.padding(10.dp)) {
|
|
||||||
Row(
|
|
||||||
modifier = modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(start = 32.dp),
|
|
||||||
text = articleWithFeed.feed.name,
|
|
||||||
fontSize = 13.sp,
|
|
||||||
fontWeight = FontWeight.Medium,
|
|
||||||
color = if (isStarredFilter || articleWithFeed.article.isUnread) {
|
|
||||||
MaterialTheme.colorScheme.tertiary
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.outline
|
|
||||||
},
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = articleWithFeed.article.date.toString(
|
|
||||||
DateTimeExt.HH_MM
|
|
||||||
),
|
|
||||||
fontSize = 13.sp,
|
|
||||||
color = MaterialTheme.colorScheme.outline
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Spacer(modifier = modifier.height(1.dp))
|
|
||||||
Row {
|
|
||||||
if (true) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(top = 3.dp)
|
|
||||||
.size(24.dp)
|
|
||||||
.border(
|
|
||||||
2.dp,
|
|
||||||
MaterialTheme.colorScheme.inverseOnSurface,
|
|
||||||
RoundedCornerShape(4.dp)
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
if (articleWithFeed.feed.icon == null) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.default_folder),
|
|
||||||
contentDescription = "icon",
|
|
||||||
modifier = modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(2.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.onPrimaryContainer,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Image(
|
|
||||||
painter = BitmapPainter(
|
|
||||||
BitmapFactory.decodeByteArray(
|
|
||||||
articleWithFeed.feed.icon,
|
|
||||||
0,
|
|
||||||
articleWithFeed.feed.icon!!.size
|
|
||||||
).asImageBitmap()
|
|
||||||
),
|
|
||||||
contentDescription = "icon",
|
|
||||||
modifier = modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(2.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
}
|
|
||||||
Column {
|
|
||||||
Text(
|
|
||||||
text = articleWithFeed.article.title,
|
|
||||||
fontSize = 18.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = if (isStarredFilter || articleWithFeed.article.isUnread) {
|
|
||||||
MaterialTheme.colorScheme.onPrimaryContainer
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.outline
|
|
||||||
},
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
Spacer(modifier = modifier.height(1.dp))
|
|
||||||
Text(
|
|
||||||
text = articleWithFeed.article.shortDescription,
|
|
||||||
fontSize = 18.sp,
|
|
||||||
color = MaterialTheme.colorScheme.outline,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,145 +0,0 @@
|
||||||
package me.ash.reader.ui.page.home.article
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.geometry.Offset
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
|
||||||
import androidx.navigation.NavHostController
|
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
|
||||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
|
||||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import me.ash.reader.DateTimeExt
|
|
||||||
import me.ash.reader.DateTimeExt.toString
|
|
||||||
import me.ash.reader.data.article.ArticleWithFeed
|
|
||||||
import me.ash.reader.data.constant.Filter
|
|
||||||
import me.ash.reader.ui.extension.collectAsStateValue
|
|
||||||
import me.ash.reader.ui.page.home.HomeViewAction
|
|
||||||
import me.ash.reader.ui.page.home.HomeViewModel
|
|
||||||
import me.ash.reader.ui.widget.AnimateLazyColumn
|
|
||||||
import me.ash.reader.ui.widget.TopTitleBox
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
|
||||||
@DelicateCoroutinesApi
|
|
||||||
@Composable
|
|
||||||
fun ArticlePage(
|
|
||||||
navController: NavHostController,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
homeViewModel: HomeViewModel = hiltViewModel(),
|
|
||||||
viewModel: ArticleViewModel = hiltViewModel(),
|
|
||||||
BackOnClick: () -> Unit,
|
|
||||||
articleOnClick: (ArticleWithFeed) -> Unit,
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val viewState = viewModel.viewState.collectAsStateValue()
|
|
||||||
val filterState = homeViewModel.filterState.collectAsStateValue()
|
|
||||||
val pagingItems = viewState.pagingData?.collectAsLazyPagingItems()
|
|
||||||
val refreshState = rememberSwipeRefreshState(isRefreshing = viewState.isRefreshing)
|
|
||||||
val syncState = homeViewModel.syncState.collectAsStateValue()
|
|
||||||
|
|
||||||
LaunchedEffect(homeViewModel.filterState) {
|
|
||||||
homeViewModel.filterState.collect { state ->
|
|
||||||
Log.i("RLog", "LaunchedEffect filterState: ")
|
|
||||||
viewModel.dispatch(
|
|
||||||
ArticleViewAction.FetchData(
|
|
||||||
groupId = state.group?.id,
|
|
||||||
feedId = state.feed?.id,
|
|
||||||
isStarred = state.filter.let { it != Filter.All && it == Filter.Starred },
|
|
||||||
isUnread = state.filter.let { it != Filter.All && it == Filter.Unread },
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SwipeRefresh(
|
|
||||||
state = refreshState,
|
|
||||||
refreshTriggerDistance = 100.dp,
|
|
||||||
onRefresh = {
|
|
||||||
if (syncState.isSyncing) return@SwipeRefresh
|
|
||||||
homeViewModel.dispatch(HomeViewAction.Sync())
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Box {
|
|
||||||
TopTitleBox(
|
|
||||||
title = when {
|
|
||||||
filterState.group != null -> filterState.group.name
|
|
||||||
filterState.feed != null -> filterState.feed.name
|
|
||||||
else -> filterState.filter.title
|
|
||||||
},
|
|
||||||
description = if (syncState.isSyncing) {
|
|
||||||
"Syncing (${syncState.syncedCount}/${syncState.feedCount}) : ${syncState.currentFeedName}"
|
|
||||||
} else {
|
|
||||||
"${viewState.filterImportant}${filterState.filter.description}"
|
|
||||||
},
|
|
||||||
listState = viewState.listState,
|
|
||||||
startOffset = Offset(if (true) 52f else 20f, 72f),
|
|
||||||
startHeight = 50f,
|
|
||||||
startTitleFontSize = 24f,
|
|
||||||
startDescriptionFontSize = 14f,
|
|
||||||
) {
|
|
||||||
viewModel.dispatch(ArticleViewAction.ScrollToItem(0))
|
|
||||||
}
|
|
||||||
Column {
|
|
||||||
ArticlePageTopBar(
|
|
||||||
backOnClick = BackOnClick,
|
|
||||||
readAllOnClick = {
|
|
||||||
viewModel.dispatch(ArticleViewAction.PeekSyncWork)
|
|
||||||
Toast.makeText(context, viewState.syncWorkInfo, Toast.LENGTH_LONG)
|
|
||||||
.show()
|
|
||||||
},
|
|
||||||
searchOnClick = {
|
|
||||||
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
|
||||||
AnimateLazyColumn(
|
|
||||||
state = viewState.listState,
|
|
||||||
reference = filterState.filter,
|
|
||||||
) {
|
|
||||||
if (pagingItems == null) return@AnimateLazyColumn
|
|
||||||
var lastItemDay: String? = null
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(74.dp))
|
|
||||||
}
|
|
||||||
for (itemIndex in 0 until pagingItems.itemCount) {
|
|
||||||
val currentItem = pagingItems.peek(itemIndex)
|
|
||||||
val currentItemDay =
|
|
||||||
currentItem?.article?.date?.toString(DateTimeExt.YYYY_MM_DD, true)
|
|
||||||
?: "null"
|
|
||||||
if (lastItemDay != currentItemDay) {
|
|
||||||
if (itemIndex != 0) {
|
|
||||||
item { Spacer(modifier = Modifier.height(40.dp)) }
|
|
||||||
}
|
|
||||||
stickyHeader {
|
|
||||||
ArticleDateHeader(currentItemDay, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
ArticleItem(
|
|
||||||
modifier = modifier,
|
|
||||||
articleWithFeed = pagingItems[itemIndex],
|
|
||||||
isStarredFilter = filterState.filter == Filter.Starred,
|
|
||||||
index = itemIndex,
|
|
||||||
articleOnClick = articleOnClick,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
lastItemDay = currentItemDay
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
package me.ash.reader.ui.page.home.article
|
|
||||||
|
|
||||||
import android.view.HapticFeedbackConstants
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.rounded.ArrowBackIosNew
|
|
||||||
import androidx.compose.material.icons.rounded.DoneAll
|
|
||||||
import androidx.compose.material.icons.rounded.Search
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.SmallTopAppBar
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.platform.LocalView
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ArticlePageTopBar(
|
|
||||||
backOnClick: () -> Unit = {},
|
|
||||||
readAllOnClick: () -> Unit = {},
|
|
||||||
searchOnClick: () -> Unit = {},
|
|
||||||
) {
|
|
||||||
val view = LocalView.current
|
|
||||||
SmallTopAppBar(
|
|
||||||
title = {},
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(onClick = {
|
|
||||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
|
||||||
backOnClick()
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Rounded.ArrowBackIosNew,
|
|
||||||
contentDescription = "Back",
|
|
||||||
tint = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
IconButton(onClick = {
|
|
||||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
|
||||||
readAllOnClick()
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Rounded.DoneAll,
|
|
||||||
contentDescription = "Done All",
|
|
||||||
tint = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
IconButton(onClick = {
|
|
||||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
|
||||||
searchOnClick()
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Rounded.Search,
|
|
||||||
contentDescription = "Search",
|
|
||||||
tint = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -15,7 +15,7 @@ import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Feed(
|
fun FeedItem(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
name: String,
|
name: String,
|
||||||
important: Int,
|
important: Int,
|
|
@ -2,12 +2,11 @@ package me.ash.reader.ui.page.home.feeds
|
||||||
|
|
||||||
import androidx.compose.animation.core.*
|
import androidx.compose.animation.core.*
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.KeyboardArrowRight
|
import androidx.compose.material.icons.outlined.KeyboardArrowRight
|
||||||
import androidx.compose.material.icons.rounded.Add
|
import androidx.compose.material.icons.rounded.Add
|
||||||
|
@ -19,15 +18,13 @@ import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.draw.rotate
|
||||||
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 androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import me.ash.reader.data.constant.Filter
|
|
||||||
import me.ash.reader.data.constant.Symbol
|
import me.ash.reader.data.constant.Symbol
|
||||||
import me.ash.reader.ui.extension.collectAsStateValue
|
import me.ash.reader.ui.extension.collectAsStateValue
|
||||||
import me.ash.reader.ui.page.home.FilterState
|
|
||||||
import me.ash.reader.ui.page.home.HomeViewAction
|
import me.ash.reader.ui.page.home.HomeViewAction
|
||||||
import me.ash.reader.ui.page.home.HomeViewModel
|
import me.ash.reader.ui.page.home.HomeViewModel
|
||||||
import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeDialog
|
import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeDialog
|
||||||
|
@ -47,6 +44,7 @@ fun FeedsPage(
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val viewState = viewModel.viewState.collectAsStateValue()
|
val viewState = viewModel.viewState.collectAsStateValue()
|
||||||
|
val filterState = homeViewModel.filterState.collectAsStateValue()
|
||||||
val syncState = homeViewModel.syncState.collectAsStateValue()
|
val syncState = homeViewModel.syncState.collectAsStateValue()
|
||||||
|
|
||||||
val infiniteTransition = rememberInfiniteTransition()
|
val infiniteTransition = rememberInfiniteTransition()
|
||||||
|
@ -65,10 +63,7 @@ fun FeedsPage(
|
||||||
LaunchedEffect(homeViewModel.filterState) {
|
LaunchedEffect(homeViewModel.filterState) {
|
||||||
homeViewModel.filterState.collect { state ->
|
homeViewModel.filterState.collect { state ->
|
||||||
viewModel.dispatch(
|
viewModel.dispatch(
|
||||||
FeedsViewAction.FetchData(
|
FeedsViewAction.FetchData(state)
|
||||||
isStarred = state.filter.let { it != Filter.All && it == Filter.Starred },
|
|
||||||
isUnread = state.filter.let { it != Filter.All && it == Filter.Unread },
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,9 +88,7 @@ fun FeedsPage(
|
||||||
homeViewModel.dispatch(HomeViewAction.Sync())
|
homeViewModel.dispatch(HomeViewAction.Sync())
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.graphicsLayer {
|
modifier = Modifier.rotate(if (syncState.isSyncing) angle else 0f),
|
||||||
rotationZ = if (syncState.isSyncing) angle else 0f
|
|
||||||
},
|
|
||||||
imageVector = Icons.Rounded.Refresh,
|
imageVector = Icons.Rounded.Refresh,
|
||||||
contentDescription = "Refresh",
|
contentDescription = "Refresh",
|
||||||
tint = MaterialTheme.colorScheme.onSurface,
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
|
@ -119,79 +112,96 @@ fun FeedsPage(
|
||||||
viewModel.dispatch(FeedsViewAction.AddFromFile(it))
|
viewModel.dispatch(FeedsViewAction.AddFromFile(it))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
Column(
|
LazyColumn {
|
||||||
modifier = Modifier.verticalScroll(rememberScrollState())
|
item {
|
||||||
) {
|
Text(
|
||||||
Text(
|
modifier = Modifier.padding(
|
||||||
modifier = Modifier.padding(
|
start = 24.dp,
|
||||||
start = 24.dp,
|
top = 48.dp,
|
||||||
top = 48.dp,
|
end = 24.dp,
|
||||||
end = 24.dp,
|
bottom = 24.dp
|
||||||
bottom = 24.dp
|
),
|
||||||
),
|
text = viewState.account?.name ?: Symbol.Unknown,
|
||||||
text = viewState.account?.name ?: Symbol.Unknown,
|
style = MaterialTheme.typography.displaySmall,
|
||||||
style = MaterialTheme.typography.displaySmall,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
)
|
||||||
)
|
}
|
||||||
Banner(
|
item {
|
||||||
title = viewState.filter.title,
|
Banner(
|
||||||
desc = "${viewState.filter.important}${viewState.filter.description}",
|
title = viewState.filter.name,
|
||||||
icon = viewState.filter.icon,
|
desc = "${viewState.filter.important}${viewState.filter.description}",
|
||||||
action = {
|
icon = viewState.filter.icon,
|
||||||
Icon(
|
action = {
|
||||||
imageVector = Icons.Outlined.KeyboardArrowRight,
|
Icon(
|
||||||
contentDescription = "Goto",
|
imageVector = Icons.Outlined.KeyboardArrowRight,
|
||||||
tint = MaterialTheme.colorScheme.onSurface,
|
contentDescription = "Goto",
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
homeViewModel.dispatch(
|
||||||
|
HomeViewAction.ChangeFilter(
|
||||||
|
filterState.copy(
|
||||||
|
group = null,
|
||||||
|
feed = null
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
},
|
homeViewModel.dispatch(
|
||||||
)
|
HomeViewAction.ScrollToPage(
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
scope = scope,
|
||||||
Subtitle(
|
targetPage = 1,
|
||||||
modifier = Modifier.padding(start = 4.dp),
|
)
|
||||||
text = "Feeds"
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
Column {
|
|
||||||
viewState.groupWithFeedList.forEachIndexed { index, groupWithFeed ->
|
|
||||||
Group(
|
|
||||||
text = groupWithFeed.group.name,
|
|
||||||
feeds = groupWithFeed.feeds,
|
|
||||||
groupOnClick = {
|
|
||||||
homeViewModel.dispatch(
|
|
||||||
HomeViewAction.ChangeFilter(
|
|
||||||
FilterState(
|
|
||||||
group = groupWithFeed.group,
|
|
||||||
feed = null,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
homeViewModel.dispatch(
|
|
||||||
HomeViewAction.ScrollToPage(
|
|
||||||
scope = scope,
|
|
||||||
targetPage = 1,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
feedOnClick = { feed ->
|
|
||||||
homeViewModel.dispatch(
|
|
||||||
HomeViewAction.ChangeFilter(
|
|
||||||
FilterState(
|
|
||||||
group = null,
|
|
||||||
feed = feed,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
homeViewModel.dispatch(
|
|
||||||
HomeViewAction.ScrollToPage(
|
|
||||||
scope = scope,
|
|
||||||
targetPage = 1,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
if (index != viewState.groupWithFeedList.lastIndex) {
|
}
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
Subtitle(
|
||||||
|
modifier = Modifier.padding(start = 28.dp),
|
||||||
|
text = "Feeds"
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
}
|
||||||
|
itemsIndexed(viewState.groupWithFeedList) { index, groupWithFeed ->
|
||||||
|
GroupItem(
|
||||||
|
text = groupWithFeed.group.name,
|
||||||
|
feeds = groupWithFeed.feeds,
|
||||||
|
groupOnClick = {
|
||||||
|
homeViewModel.dispatch(
|
||||||
|
HomeViewAction.ChangeFilter(
|
||||||
|
filterState.copy(
|
||||||
|
group = groupWithFeed.group,
|
||||||
|
feed = null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
homeViewModel.dispatch(
|
||||||
|
HomeViewAction.ScrollToPage(
|
||||||
|
scope = scope,
|
||||||
|
targetPage = 1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
feedOnClick = { feed ->
|
||||||
|
homeViewModel.dispatch(
|
||||||
|
HomeViewAction.ChangeFilter(
|
||||||
|
filterState.copy(
|
||||||
|
group = null,
|
||||||
|
feed = feed
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
homeViewModel.dispatch(
|
||||||
|
HomeViewAction.ScrollToPage(
|
||||||
|
scope = scope,
|
||||||
|
targetPage = 1,
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
if (index != viewState.groupWithFeedList.lastIndex) {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import me.ash.reader.data.group.GroupWithFeed
|
||||||
import me.ash.reader.data.repository.AccountRepository
|
import me.ash.reader.data.repository.AccountRepository
|
||||||
import me.ash.reader.data.repository.OpmlRepository
|
import me.ash.reader.data.repository.OpmlRepository
|
||||||
import me.ash.reader.data.repository.RssRepository
|
import me.ash.reader.data.repository.RssRepository
|
||||||
|
import me.ash.reader.ui.page.home.FilterState
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ class FeedsViewModel @Inject constructor(
|
||||||
fun dispatch(action: FeedsViewAction) {
|
fun dispatch(action: FeedsViewAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is FeedsViewAction.FetchAccount -> fetchAccount(action.callback)
|
is FeedsViewAction.FetchAccount -> fetchAccount(action.callback)
|
||||||
is FeedsViewAction.FetchData -> fetchData(action.isStarred, action.isUnread)
|
is FeedsViewAction.FetchData -> fetchData(action.filterState)
|
||||||
is FeedsViewAction.AddFromFile -> addFromFile(action.inputStream)
|
is FeedsViewAction.AddFromFile -> addFromFile(action.inputStream)
|
||||||
is FeedsViewAction.ScrollToItem -> scrollToItem(action.index)
|
is FeedsViewAction.ScrollToItem -> scrollToItem(action.index)
|
||||||
}
|
}
|
||||||
|
@ -53,9 +54,12 @@ class FeedsViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchData(isStarred: Boolean, isUnread: Boolean) {
|
private fun fetchData(filterState: FilterState) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
pullFeeds(isStarred, isUnread)
|
pullFeeds(
|
||||||
|
isStarred = filterState.filter.isStarred(),
|
||||||
|
isUnread = filterState.filter.isUnread(),
|
||||||
|
)
|
||||||
_viewState
|
_viewState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,8 +139,7 @@ data class FeedsViewState(
|
||||||
|
|
||||||
sealed class FeedsViewAction {
|
sealed class FeedsViewAction {
|
||||||
data class FetchData(
|
data class FetchData(
|
||||||
val isStarred: Boolean,
|
val filterState: FilterState,
|
||||||
val isUnread: Boolean,
|
|
||||||
) : FeedsViewAction()
|
) : FeedsViewAction()
|
||||||
|
|
||||||
data class FetchAccount(
|
data class FetchAccount(
|
||||||
|
|
|
@ -20,7 +20,7 @@ import androidx.compose.ui.unit.dp
|
||||||
import me.ash.reader.data.feed.Feed
|
import me.ash.reader.data.feed.Feed
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Group(
|
fun GroupItem(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
text: String,
|
text: String,
|
||||||
feeds: List<Feed>,
|
feeds: List<Feed>,
|
||||||
|
@ -37,7 +37,7 @@ fun Group(
|
||||||
.clip(RoundedCornerShape(32.dp))
|
.clip(RoundedCornerShape(32.dp))
|
||||||
.background(MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.14f))
|
.background(MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.14f))
|
||||||
.clickable { groupOnClick() }
|
.clickable { groupOnClick() }
|
||||||
.padding(top = 22.dp, bottom = if (expanded) 14.dp else 22.dp)
|
.padding(vertical = 22.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
@ -76,9 +76,11 @@ fun Group(
|
||||||
exit = fadeOut() + shrinkVertically(),
|
exit = fadeOut() + shrinkVertically(),
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
if (feeds.isNotEmpty()) {
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
feeds.forEach { feed ->
|
feeds.forEach { feed ->
|
||||||
Feed(
|
FeedItem(
|
||||||
modifier = Modifier.padding(horizontal = 20.dp),
|
modifier = Modifier.padding(horizontal = 20.dp),
|
||||||
name = feed.name,
|
name = feed.name,
|
||||||
important = feed.important ?: 0,
|
important = feed.important ?: 0,
|
|
@ -2,9 +2,9 @@ package me.ash.reader.ui.page.home.feeds.subscribe
|
||||||
|
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Article
|
import androidx.compose.material.icons.outlined.Article
|
||||||
import androidx.compose.material.icons.outlined.Notifications
|
import androidx.compose.material.icons.outlined.Notifications
|
||||||
|
@ -13,14 +13,13 @@ 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
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import com.google.accompanist.flowlayout.FlowRow
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
import com.google.accompanist.flowlayout.MainAxisAlignment
|
import com.google.accompanist.flowlayout.MainAxisAlignment
|
||||||
import me.ash.reader.data.group.Group
|
import me.ash.reader.data.group.Group
|
||||||
import me.ash.reader.ui.widget.SelectionChip
|
import me.ash.reader.ui.widget.SelectionChip
|
||||||
|
import me.ash.reader.ui.widget.SelectionEditorChip
|
||||||
|
import me.ash.reader.ui.widget.Subtitle
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ResultViewPage(
|
fun ResultViewPage(
|
||||||
|
@ -34,7 +33,9 @@ fun ResultViewPage(
|
||||||
groupOnClick: (groupId: String) -> Unit = {},
|
groupOnClick: (groupId: String) -> Unit = {},
|
||||||
onKeyboardAction: () -> Unit = {},
|
onKeyboardAction: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
Column {
|
Column(
|
||||||
|
modifier = Modifier.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
Link(
|
Link(
|
||||||
text = link
|
text = link
|
||||||
)
|
)
|
||||||
|
@ -82,11 +83,7 @@ private fun Preset(
|
||||||
notificationPresetOnClick: () -> Unit = {},
|
notificationPresetOnClick: () -> Unit = {},
|
||||||
fullContentParsePresetOnClick: () -> Unit = {},
|
fullContentParsePresetOnClick: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
Text(
|
Subtitle(text = "预设")
|
||||||
text = "预设",
|
|
||||||
color = MaterialTheme.colorScheme.primary,
|
|
||||||
fontSize = 14.sp,
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
FlowRow(
|
FlowRow(
|
||||||
mainAxisAlignment = MainAxisAlignment.Start,
|
mainAxisAlignment = MainAxisAlignment.Start,
|
||||||
|
@ -94,38 +91,36 @@ private fun Preset(
|
||||||
mainAxisSpacing = 10.dp,
|
mainAxisSpacing = 10.dp,
|
||||||
) {
|
) {
|
||||||
SelectionChip(
|
SelectionChip(
|
||||||
|
modifier = Modifier.animateContentSize(),
|
||||||
|
content = "接收通知",
|
||||||
selected = selectedNotificationPreset,
|
selected = selectedNotificationPreset,
|
||||||
selectedIcon = {
|
selectedIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Notifications,
|
imageVector = Icons.Outlined.Notifications,
|
||||||
contentDescription = "Check",
|
contentDescription = "Check",
|
||||||
modifier = Modifier.size(20.dp)
|
modifier = Modifier
|
||||||
|
.padding(start = 8.dp)
|
||||||
|
.size(18.dp),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = notificationPresetOnClick,
|
|
||||||
) {
|
) {
|
||||||
Text(
|
notificationPresetOnClick()
|
||||||
text = "接收通知",
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
fontSize = 14.sp,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
SelectionChip(
|
SelectionChip(
|
||||||
|
modifier = Modifier.animateContentSize(),
|
||||||
|
content = "全文解析",
|
||||||
selected = selectedFullContentParsePreset,
|
selected = selectedFullContentParsePreset,
|
||||||
selectedIcon = {
|
selectedIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Article,
|
imageVector = Icons.Outlined.Article,
|
||||||
contentDescription = "Check",
|
contentDescription = "Check",
|
||||||
modifier = Modifier.size(20.dp)
|
modifier = Modifier
|
||||||
|
.padding(start = 8.dp)
|
||||||
|
.size(18.dp),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = fullContentParsePresetOnClick,
|
|
||||||
) {
|
) {
|
||||||
Text(
|
fullContentParsePresetOnClick()
|
||||||
text = "全文解析",
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
fontSize = 14.sp,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,11 +132,7 @@ private fun AddToGroup(
|
||||||
groupOnClick: (groupId: String) -> Unit = {},
|
groupOnClick: (groupId: String) -> Unit = {},
|
||||||
onKeyboardAction: () -> Unit = {},
|
onKeyboardAction: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
Text(
|
Subtitle(text = "添加到组")
|
||||||
text = "添加到组",
|
|
||||||
color = MaterialTheme.colorScheme.primary,
|
|
||||||
fontSize = 14.sp,
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
FlowRow(
|
FlowRow(
|
||||||
mainAxisAlignment = MainAxisAlignment.Start,
|
mainAxisAlignment = MainAxisAlignment.Start,
|
||||||
|
@ -151,37 +142,20 @@ private fun AddToGroup(
|
||||||
groups.forEach {
|
groups.forEach {
|
||||||
SelectionChip(
|
SelectionChip(
|
||||||
modifier = Modifier.animateContentSize(),
|
modifier = Modifier.animateContentSize(),
|
||||||
|
content = it.name,
|
||||||
selected = it.id == selectedGroupId,
|
selected = it.id == selectedGroupId,
|
||||||
onClick = { groupOnClick(it.id) },
|
|
||||||
) {
|
) {
|
||||||
Text(
|
groupOnClick(it.id)
|
||||||
text = it.name,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
fontSize = 14.sp,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectionChip(
|
SelectionEditorChip(
|
||||||
|
modifier = Modifier.animateContentSize(),
|
||||||
|
content = "新建分组",
|
||||||
selected = false,
|
selected = false,
|
||||||
onClick = { /*TODO*/ },
|
onKeyboardAction = onKeyboardAction,
|
||||||
) {
|
) {
|
||||||
BasicTextField(
|
|
||||||
modifier = Modifier.width(56.dp),
|
|
||||||
value = "新建分组",
|
|
||||||
onValueChange = {},
|
|
||||||
textStyle = TextStyle(
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
fontSize = 14.sp,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f)
|
|
||||||
),
|
|
||||||
singleLine = true,
|
|
||||||
keyboardActions = KeyboardActions(
|
|
||||||
onDone = {
|
|
||||||
onKeyboardAction()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,6 +13,10 @@ import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||||
|
import me.ash.reader.DataStoreKeys
|
||||||
|
import me.ash.reader.dataStore
|
||||||
|
import me.ash.reader.get
|
||||||
|
import me.ash.reader.spacerDollar
|
||||||
import me.ash.reader.ui.extension.collectAsStateValue
|
import me.ash.reader.ui.extension.collectAsStateValue
|
||||||
import me.ash.reader.ui.widget.Dialog
|
import me.ash.reader.ui.widget.Dialog
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
@ -38,6 +42,10 @@ fun SubscribeDialog(
|
||||||
|
|
||||||
LaunchedEffect(viewState.visible) {
|
LaunchedEffect(viewState.visible) {
|
||||||
if (viewState.visible) {
|
if (viewState.visible) {
|
||||||
|
val defaultGroupId = context.dataStore
|
||||||
|
.get(DataStoreKeys.CurrentAccountId)!!
|
||||||
|
.spacerDollar("0")
|
||||||
|
viewModel.dispatch(SubscribeViewAction.SelectedGroup(defaultGroupId))
|
||||||
viewModel.dispatch(SubscribeViewAction.Init)
|
viewModel.dispatch(SubscribeViewAction.Init)
|
||||||
} else {
|
} else {
|
||||||
viewModel.dispatch(SubscribeViewAction.Reset)
|
viewModel.dispatch(SubscribeViewAction.Reset)
|
||||||
|
|
|
@ -127,6 +127,14 @@ class SubscribeViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
|
if (rssRepository.get().isExist(_viewState.value.inputContent)) {
|
||||||
|
_viewState.update {
|
||||||
|
it.copy(
|
||||||
|
errorMessage = "已订阅",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
val feedWithArticle = rssHelper.searchFeed(_viewState.value.inputContent)
|
val feedWithArticle = rssHelper.searchFeed(_viewState.value.inputContent)
|
||||||
_viewState.update {
|
_viewState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
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.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
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.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import me.ash.reader.DateTimeExt
|
||||||
|
import me.ash.reader.DateTimeExt.toString
|
||||||
|
import me.ash.reader.data.article.ArticleWithFeed
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ArticleItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
articleWithFeed: ArticleWithFeed,
|
||||||
|
onClick: (ArticleWithFeed) -> Unit = {},
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||||
|
.clip(RoundedCornerShape(12.dp))
|
||||||
|
.clickable { onClick(articleWithFeed) }
|
||||||
|
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||||
|
.alpha(if (articleWithFeed.article.isUnread) 1f else 0.5f),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(start = 30.dp),
|
||||||
|
text = articleWithFeed.feed.name,
|
||||||
|
color = MaterialTheme.colorScheme.tertiary,
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = articleWithFeed.article.date.toString(DateTimeExt.HH_MM),
|
||||||
|
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(20.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(MaterialTheme.colorScheme.outline.copy(alpha = 0.2f))
|
||||||
|
) {}
|
||||||
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = articleWithFeed.article.title,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = articleWithFeed.article.shortDescription,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package me.ash.reader.ui.page.home.flow
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.paging.compose.LazyPagingItems
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import me.ash.reader.DateTimeExt
|
||||||
|
import me.ash.reader.DateTimeExt.toString
|
||||||
|
import me.ash.reader.data.article.ArticleWithFeed
|
||||||
|
import me.ash.reader.ui.page.home.HomeViewAction
|
||||||
|
import me.ash.reader.ui.page.home.HomeViewModel
|
||||||
|
import me.ash.reader.ui.page.home.read.ReadViewAction
|
||||||
|
import me.ash.reader.ui.page.home.read.ReadViewModel
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
fun LazyListScope.generateArticleList(
|
||||||
|
pagingItems: LazyPagingItems<ArticleWithFeed>?,
|
||||||
|
readViewModel: ReadViewModel,
|
||||||
|
homeViewModel: HomeViewModel,
|
||||||
|
scope: CoroutineScope
|
||||||
|
) {
|
||||||
|
if (pagingItems == null) return
|
||||||
|
var lastItemDay: String? = null
|
||||||
|
for (itemIndex in 0 until pagingItems.itemCount) {
|
||||||
|
val currentItem = pagingItems.peek(itemIndex) ?: continue
|
||||||
|
val currentItemDay = currentItem.article.date
|
||||||
|
.toString(DateTimeExt.YYYY_MM_DD, true)
|
||||||
|
if (lastItemDay != currentItemDay) {
|
||||||
|
if (itemIndex != 0) {
|
||||||
|
item { Spacer(modifier = Modifier.height(40.dp)) }
|
||||||
|
}
|
||||||
|
stickyHeader {
|
||||||
|
StickyHeader(currentItemDay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
ArticleItem(
|
||||||
|
articleWithFeed = currentItem,
|
||||||
|
) {
|
||||||
|
readViewModel.dispatch(ReadViewAction.ScrollToItem(0))
|
||||||
|
readViewModel.dispatch(ReadViewAction.InitData(it))
|
||||||
|
if (it.feed.isFullContent) readViewModel.dispatch(ReadViewAction.RenderFullContent)
|
||||||
|
else readViewModel.dispatch(ReadViewAction.RenderDescriptionContent)
|
||||||
|
readViewModel.dispatch(ReadViewAction.RenderDescriptionContent)
|
||||||
|
homeViewModel.dispatch(
|
||||||
|
HomeViewAction.ScrollToPage(
|
||||||
|
scope = scope,
|
||||||
|
targetPage = 2,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastItemDay = currentItemDay
|
||||||
|
}
|
||||||
|
}
|
121
app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt
Normal file
121
app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
package me.ash.reader.ui.page.home.flow
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.ArrowBack
|
||||||
|
import androidx.compose.material.icons.rounded.DoneAll
|
||||||
|
import androidx.compose.material.icons.rounded.Search
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.paging.compose.collectAsLazyPagingItems
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import me.ash.reader.ui.extension.collectAsStateValue
|
||||||
|
import me.ash.reader.ui.page.home.HomeViewAction
|
||||||
|
import me.ash.reader.ui.page.home.HomeViewModel
|
||||||
|
import me.ash.reader.ui.page.home.read.ReadViewModel
|
||||||
|
|
||||||
|
@OptIn(
|
||||||
|
ExperimentalMaterial3Api::class,
|
||||||
|
ExperimentalFoundationApi::class,
|
||||||
|
)
|
||||||
|
@Composable
|
||||||
|
fun FlowPage(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
navController: NavHostController,
|
||||||
|
viewModel: FlowViewModel = hiltViewModel(),
|
||||||
|
homeViewModel: HomeViewModel = hiltViewModel(),
|
||||||
|
readViewModel: ReadViewModel = hiltViewModel(),
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val viewState = viewModel.viewState.collectAsStateValue()
|
||||||
|
val filterState = homeViewModel.filterState.collectAsStateValue()
|
||||||
|
val pagingItems = viewState.pagingData?.collectAsLazyPagingItems()
|
||||||
|
|
||||||
|
LaunchedEffect(homeViewModel.filterState) {
|
||||||
|
homeViewModel.filterState.collect { state ->
|
||||||
|
viewModel.dispatch(
|
||||||
|
FlowViewAction.FetchData(state)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
modifier = Modifier.background(MaterialTheme.colorScheme.surface),
|
||||||
|
topBar = {
|
||||||
|
SmallTopAppBar(
|
||||||
|
title = {},
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = {
|
||||||
|
homeViewModel.dispatch(
|
||||||
|
HomeViewAction.ScrollToPage(
|
||||||
|
scope = scope,
|
||||||
|
targetPage = 0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.ArrowBack,
|
||||||
|
contentDescription = "Back",
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = {}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.DoneAll,
|
||||||
|
contentDescription = "Read All",
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(onClick = {}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Search,
|
||||||
|
contentDescription = "Search",
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
content = {
|
||||||
|
LazyColumn(
|
||||||
|
state = viewState.listState,
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(
|
||||||
|
start = if (true) 54.dp else 24.dp,
|
||||||
|
top = 48.dp,
|
||||||
|
end = 24.dp,
|
||||||
|
bottom = 24.dp
|
||||||
|
),
|
||||||
|
text = when {
|
||||||
|
filterState.group != null -> filterState.group.name
|
||||||
|
filterState.feed != null -> filterState.feed.name
|
||||||
|
else -> filterState.filter.name
|
||||||
|
},
|
||||||
|
style = MaterialTheme.typography.displaySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
generateArticleList(pagingItems, readViewModel, homeViewModel, scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package me.ash.reader.ui.page.home.article
|
package me.ash.reader.ui.page.home.flow
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
@ -13,26 +13,22 @@ import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.ash.reader.data.article.ArticleWithFeed
|
import me.ash.reader.data.article.ArticleWithFeed
|
||||||
import me.ash.reader.data.repository.RssRepository
|
import me.ash.reader.data.repository.RssRepository
|
||||||
|
import me.ash.reader.ui.page.home.FilterState
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class ArticleViewModel @Inject constructor(
|
class FlowViewModel @Inject constructor(
|
||||||
private val rssRepository: RssRepository,
|
private val rssRepository: RssRepository,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val _viewState = MutableStateFlow(ArticleViewState())
|
private val _viewState = MutableStateFlow(ArticleViewState())
|
||||||
val viewState: StateFlow<ArticleViewState> = _viewState.asStateFlow()
|
val viewState: StateFlow<ArticleViewState> = _viewState.asStateFlow()
|
||||||
|
|
||||||
fun dispatch(action: ArticleViewAction) {
|
fun dispatch(action: FlowViewAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is ArticleViewAction.FetchData -> fetchData(
|
is FlowViewAction.FetchData -> fetchData(action.filterState)
|
||||||
groupId = action.groupId,
|
is FlowViewAction.ChangeRefreshing -> changeRefreshing(action.isRefreshing)
|
||||||
feedId = action.feedId,
|
is FlowViewAction.ScrollToItem -> scrollToItem(action.index)
|
||||||
isStarred = action.isStarred,
|
is FlowViewAction.PeekSyncWork -> peekSyncWork()
|
||||||
isUnread = action.isUnread,
|
|
||||||
)
|
|
||||||
is ArticleViewAction.ChangeRefreshing -> changeRefreshing(action.isRefreshing)
|
|
||||||
is ArticleViewAction.ScrollToItem -> scrollToItem(action.index)
|
|
||||||
is ArticleViewAction.PeekSyncWork -> peekSyncWork()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,14 +40,9 @@ class ArticleViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchData(
|
private fun fetchData(filterState: FilterState) {
|
||||||
groupId: String? = null,
|
|
||||||
feedId: String? = null,
|
|
||||||
isStarred: Boolean,
|
|
||||||
isUnread: Boolean,
|
|
||||||
) {
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
rssRepository.get().pullImportant(isStarred, true)
|
rssRepository.get().pullImportant(filterState.filter.isStarred(), true)
|
||||||
.collect { importantList ->
|
.collect { importantList ->
|
||||||
_viewState.update {
|
_viewState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
@ -64,10 +55,10 @@ class ArticleViewModel @Inject constructor(
|
||||||
it.copy(
|
it.copy(
|
||||||
pagingData = Pager(PagingConfig(pageSize = 10)) {
|
pagingData = Pager(PagingConfig(pageSize = 10)) {
|
||||||
rssRepository.get().pullArticles(
|
rssRepository.get().pullArticles(
|
||||||
groupId = groupId,
|
groupId = filterState.group?.id,
|
||||||
feedId = feedId,
|
feedId = filterState.feed?.id,
|
||||||
isStarred = isStarred,
|
isStarred = filterState.filter.isStarred(),
|
||||||
isUnread = isUnread,
|
isUnread = filterState.filter.isUnread(),
|
||||||
)
|
)
|
||||||
}.flow.cachedIn(viewModelScope)
|
}.flow.cachedIn(viewModelScope)
|
||||||
)
|
)
|
||||||
|
@ -95,21 +86,18 @@ data class ArticleViewState(
|
||||||
val syncWorkInfo: String = "",
|
val syncWorkInfo: String = "",
|
||||||
)
|
)
|
||||||
|
|
||||||
sealed class ArticleViewAction {
|
sealed class FlowViewAction {
|
||||||
data class FetchData(
|
data class FetchData(
|
||||||
val groupId: String? = null,
|
val filterState: FilterState,
|
||||||
val feedId: String? = null,
|
) : FlowViewAction()
|
||||||
val isStarred: Boolean,
|
|
||||||
val isUnread: Boolean,
|
|
||||||
) : ArticleViewAction()
|
|
||||||
|
|
||||||
data class ChangeRefreshing(
|
data class ChangeRefreshing(
|
||||||
val isRefreshing: Boolean
|
val isRefreshing: Boolean
|
||||||
) : ArticleViewAction()
|
) : FlowViewAction()
|
||||||
|
|
||||||
data class ScrollToItem(
|
data class ScrollToItem(
|
||||||
val index: Int
|
val index: Int
|
||||||
) : ArticleViewAction()
|
) : FlowViewAction()
|
||||||
|
|
||||||
object PeekSyncWork : ArticleViewAction()
|
object PeekSyncWork : FlowViewAction()
|
||||||
}
|
}
|
|
@ -1,37 +1,29 @@
|
||||||
package me.ash.reader.ui.page.home.article
|
package me.ash.reader.ui.page.home.flow
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
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
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ArticleDateHeader(
|
fun StickyHeader(currentItemDay: String) {
|
||||||
date: String,
|
|
||||||
isDisplayIcon: Boolean
|
|
||||||
) {
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(28.dp)
|
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(MaterialTheme.colorScheme.surface),
|
.background(MaterialTheme.colorScheme.surface),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = date,
|
modifier = Modifier.padding(start = if (true) 54.dp else 24.dp),
|
||||||
fontSize = 13.sp,
|
text = currentItemDay,
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
modifier = Modifier.padding(start = (if (isDisplayIcon) 52 else 20).dp),
|
style = MaterialTheme.typography.labelLarge,
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -22,8 +22,8 @@ fun Banner(
|
||||||
title: String,
|
title: String,
|
||||||
desc: String? = null,
|
desc: String? = null,
|
||||||
icon: ImageVector? = null,
|
icon: ImageVector? = null,
|
||||||
|
action: (@Composable () -> Unit)? = null,
|
||||||
onClick: () -> Unit = {},
|
onClick: () -> Unit = {},
|
||||||
action: (@Composable () -> Unit)? = null
|
|
||||||
) {
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
package me.ash.reader.ui.widget
|
package me.ash.reader.ui.widget
|
||||||
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.material.ChipDefaults
|
import androidx.compose.material.ChipDefaults
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.FilterChip
|
import androidx.compose.material.FilterChip
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Check
|
import androidx.compose.material.icons.rounded.Check
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
@ -20,20 +24,22 @@ import androidx.compose.ui.unit.dp
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SelectionChip(
|
fun SelectionChip(
|
||||||
|
content: String,
|
||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
onClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
shape: Shape = CircleShape,
|
shape: Shape = CircleShape,
|
||||||
selectedIcon: @Composable () -> Unit = {
|
selectedIcon: @Composable () -> Unit = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Check,
|
imageVector = Icons.Rounded.Check,
|
||||||
contentDescription = "Check",
|
contentDescription = "Check",
|
||||||
modifier = Modifier.size(20.dp)
|
modifier = Modifier
|
||||||
|
.padding(start = 8.dp)
|
||||||
|
.size(18.dp)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
content: @Composable RowScope.() -> Unit
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
FilterChip(
|
FilterChip(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
@ -54,6 +60,83 @@ fun SelectionChip(
|
||||||
selectedIcon = selectedIcon,
|
selectedIcon = selectedIcon,
|
||||||
shape = shape,
|
shape = shape,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
content = content,
|
content = {
|
||||||
|
Text(
|
||||||
|
modifier = modifier.padding(
|
||||||
|
start = if (selected) 0.dp else 8.dp,
|
||||||
|
top = 8.dp,
|
||||||
|
end = 8.dp,
|
||||||
|
bottom = 8.dp
|
||||||
|
),
|
||||||
|
text = content,
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
|
@Composable
|
||||||
|
fun SelectionEditorChip(
|
||||||
|
content: String,
|
||||||
|
selected: Boolean,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
|
shape: Shape = CircleShape,
|
||||||
|
selectedIcon: @Composable () -> Unit = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Check,
|
||||||
|
contentDescription = "Check",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 8.dp)
|
||||||
|
.size(16.dp)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onKeyboardAction: () -> Unit = {},
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
FilterChip(
|
||||||
|
modifier = modifier,
|
||||||
|
colors = ChipDefaults.filterChipColors(
|
||||||
|
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
|
leadingIconColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
|
disabledBackgroundColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
|
||||||
|
disabledContentColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
|
||||||
|
disabledLeadingIconColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
|
||||||
|
selectedBackgroundColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
selectedContentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
|
selectedLeadingIconColor = MaterialTheme.colorScheme.onSecondaryContainer
|
||||||
|
),
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
enabled = enabled,
|
||||||
|
selected = selected,
|
||||||
|
selectedIcon = selectedIcon,
|
||||||
|
shape = shape,
|
||||||
|
onClick = onClick,
|
||||||
|
content = {
|
||||||
|
BasicTextField(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
|
start = if (selected) 0.dp else 8.dp,
|
||||||
|
top = 8.dp,
|
||||||
|
end = 8.dp,
|
||||||
|
bottom = 8.dp
|
||||||
|
)
|
||||||
|
.width(56.dp),
|
||||||
|
value = content,
|
||||||
|
onValueChange = {},
|
||||||
|
textStyle = MaterialTheme.typography.titleSmall.copy(
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
),
|
||||||
|
singleLine = true,
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
|
onDone = {
|
||||||
|
onKeyboardAction()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -11,15 +11,15 @@ import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Subtitle(
|
fun Subtitle(
|
||||||
text: String,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
text: String,
|
||||||
color: Color = MaterialTheme.colorScheme.primary,
|
color: Color = MaterialTheme.colorScheme.primary,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = text,
|
text = text,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(24.dp, 8.dp, 16.dp, 8.dp),
|
.padding(vertical = 8.dp),
|
||||||
color = color,
|
color = color,
|
||||||
style = MaterialTheme.typography.labelLarge
|
style = MaterialTheme.typography.labelLarge
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user