Refactor Material You design for FilterBar and Add subscribe feature
This commit is contained in:
parent
c7c708d92a
commit
b2fe0674c8
|
@ -2,7 +2,8 @@ package me.ash.reader
|
|||
|
||||
import android.app.Application
|
||||
import androidx.hilt.work.HiltWorkerFactory
|
||||
import androidx.work.*
|
||||
import androidx.work.Configuration
|
||||
import androidx.work.WorkManager
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
|
@ -12,7 +13,6 @@ import me.ash.reader.data.repository.*
|
|||
import me.ash.reader.data.source.OpmlLocalDataSource
|
||||
import me.ash.reader.data.source.ReaderDatabase
|
||||
import me.ash.reader.data.source.RssNetworkDataSource
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
@DelicateCoroutinesApi
|
||||
|
@ -54,7 +54,7 @@ class App : Application(), Configuration.Provider {
|
|||
@Inject
|
||||
lateinit var rssRepository: RssRepository
|
||||
|
||||
private val applicationScope = CoroutineScope(Dispatchers.IO)
|
||||
private val applicationScope = CoroutineScope(Dispatchers.Default)
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
@ -73,19 +73,7 @@ class App : Application(), Configuration.Provider {
|
|||
}
|
||||
|
||||
private fun workerInit() {
|
||||
val repeatingRequest = PeriodicWorkRequestBuilder<SyncWorker>(
|
||||
15, TimeUnit.MINUTES
|
||||
).setConstraints(
|
||||
Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build()
|
||||
).addTag(SyncWorker.WORK_NAME).build()
|
||||
|
||||
workManager.enqueueUniquePeriodicWork(
|
||||
SyncWorker.WORK_NAME,
|
||||
ExistingPeriodicWorkPolicy.REPLACE,
|
||||
repeatingRequest
|
||||
)
|
||||
rssRepository.get().doSync()
|
||||
}
|
||||
|
||||
override fun getWorkManagerConfiguration(): Configuration =
|
||||
|
|
|
@ -279,7 +279,7 @@ interface ArticleDao {
|
|||
WHERE id = :id
|
||||
"""
|
||||
)
|
||||
suspend fun queryById(id: Int): ArticleWithFeed?
|
||||
suspend fun queryById(id: String): ArticleWithFeed?
|
||||
|
||||
@Insert
|
||||
suspend fun insert(article: Article): Long
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package me.ash.reader.data.constant
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.FiberManualRecord
|
||||
import androidx.compose.material.icons.outlined.FiberManualRecord
|
||||
import androidx.compose.material.icons.rounded.Star
|
||||
import androidx.compose.material.icons.rounded.StarOutline
|
||||
import androidx.compose.material.icons.rounded.Subject
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
@ -10,6 +12,7 @@ class Filter(
|
|||
var index: Int,
|
||||
var important: Int,
|
||||
var icon: ImageVector,
|
||||
var filledIcon: ImageVector,
|
||||
) {
|
||||
fun isStarred(): Boolean = this == Starred
|
||||
fun isUnread(): Boolean = this == Unread
|
||||
|
@ -20,16 +23,19 @@ class Filter(
|
|||
index = 0,
|
||||
important = 13,
|
||||
icon = Icons.Rounded.StarOutline,
|
||||
filledIcon = Icons.Rounded.Star,
|
||||
)
|
||||
val Unread = Filter(
|
||||
index = 1,
|
||||
important = 666,
|
||||
icon = Icons.Outlined.FiberManualRecord,
|
||||
filledIcon = Icons.Filled.FiberManualRecord,
|
||||
)
|
||||
val All = Filter(
|
||||
index = 2,
|
||||
important = 666,
|
||||
icon = Icons.Rounded.Subject,
|
||||
filledIcon = Icons.Rounded.Subject,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ interface FeedDao {
|
|||
and url = :url
|
||||
"""
|
||||
)
|
||||
fun queryByLink(accountId: Int, url: String): List<Feed>
|
||||
suspend fun queryByLink(accountId: Int, url: String): List<Feed>
|
||||
|
||||
@Insert
|
||||
suspend fun insert(feed: Feed): Long
|
||||
|
|
|
@ -4,9 +4,7 @@ import android.content.Context
|
|||
import android.util.Log
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.*
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
@ -28,6 +26,7 @@ import me.ash.reader.data.group.GroupWithFeed
|
|||
import me.ash.reader.data.source.RssNetworkDataSource
|
||||
import me.ash.reader.dataStore
|
||||
import me.ash.reader.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
abstract class AbstractRssRepository constructor(
|
||||
private val context: Context,
|
||||
|
@ -51,8 +50,18 @@ abstract class AbstractRssRepository constructor(
|
|||
|
||||
abstract suspend fun subscribe(feed: Feed, articles: List<Article>)
|
||||
|
||||
abstract suspend fun addGroup(name: String): String
|
||||
|
||||
abstract suspend fun sync()
|
||||
|
||||
fun doSync() {
|
||||
workManager.enqueueUniquePeriodicWork(
|
||||
SyncWorker.WORK_NAME,
|
||||
ExistingPeriodicWorkPolicy.REPLACE,
|
||||
SyncWorker.repeatingRequest
|
||||
)
|
||||
}
|
||||
|
||||
fun pullGroups(): Flow<MutableList<Group>> {
|
||||
val accountId = context.dataStore.get(DataStoreKeys.CurrentAccountId) ?: 0
|
||||
return groupDao.queryAllGroup(accountId)
|
||||
|
@ -118,11 +127,11 @@ abstract class AbstractRssRepository constructor(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun findArticleById(id: Int): ArticleWithFeed? {
|
||||
suspend fun findArticleById(id: String): ArticleWithFeed? {
|
||||
return articleDao.queryById(id)
|
||||
}
|
||||
|
||||
fun isExist(url: String): Boolean {
|
||||
suspend fun isExist(url: String): Boolean {
|
||||
val accountId = context.dataStore.get(DataStoreKeys.CurrentAccountId)!!
|
||||
return feedDao.queryByLink(accountId, url).isNotEmpty()
|
||||
}
|
||||
|
@ -158,5 +167,13 @@ class SyncWorker @AssistedInject constructor(
|
|||
|
||||
companion object {
|
||||
const val WORK_NAME = "article.sync"
|
||||
|
||||
val repeatingRequest = PeriodicWorkRequestBuilder<SyncWorker>(
|
||||
15, TimeUnit.MINUTES
|
||||
).setConstraints(
|
||||
Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build()
|
||||
).addTag(WORK_NAME).build()
|
||||
}
|
||||
}
|
|
@ -48,6 +48,19 @@ class FeverRssRepository @Inject constructor(
|
|||
})
|
||||
}
|
||||
|
||||
override suspend fun addGroup(name: String): String {
|
||||
val accountId = context.dataStore.get(DataStoreKeys.CurrentAccountId)!!
|
||||
return UUID.randomUUID().toString().also {
|
||||
groupDao.insert(
|
||||
Group(
|
||||
id = it,
|
||||
name = name,
|
||||
accountId = accountId
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sync() {
|
||||
mutex.withLock {
|
||||
val accountId = context.dataStore.get(DataStoreKeys.CurrentAccountId)
|
||||
|
|
|
@ -20,6 +20,7 @@ import me.ash.reader.data.article.Article
|
|||
import me.ash.reader.data.article.ArticleDao
|
||||
import me.ash.reader.data.feed.Feed
|
||||
import me.ash.reader.data.feed.FeedDao
|
||||
import me.ash.reader.data.group.Group
|
||||
import me.ash.reader.data.group.GroupDao
|
||||
import me.ash.reader.data.source.RssNetworkDataSource
|
||||
import me.ash.reader.ui.page.common.ExtraName
|
||||
|
@ -35,7 +36,7 @@ class LocalRssRepository @Inject constructor(
|
|||
private val rssHelper: RssHelper,
|
||||
private val rssNetworkDataSource: RssNetworkDataSource,
|
||||
private val accountDao: AccountDao,
|
||||
groupDao: GroupDao,
|
||||
private val groupDao: GroupDao,
|
||||
workManager: WorkManager,
|
||||
) : AbstractRssRepository(
|
||||
context, accountDao, articleDao, groupDao,
|
||||
|
@ -52,6 +53,19 @@ class LocalRssRepository @Inject constructor(
|
|||
})
|
||||
}
|
||||
|
||||
override suspend fun addGroup(name: String): String {
|
||||
val accountId = context.dataStore.get(DataStoreKeys.CurrentAccountId)!!
|
||||
return UUID.randomUUID().toString().also {
|
||||
groupDao.insert(
|
||||
Group(
|
||||
id = it,
|
||||
name = name,
|
||||
accountId = accountId
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sync() {
|
||||
mutex.withLock {
|
||||
val accountId = context.dataStore.get(DataStoreKeys.CurrentAccountId)
|
||||
|
@ -136,7 +150,7 @@ class LocalRssRepository @Inject constructor(
|
|||
Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
putExtra(
|
||||
ExtraName.ARTICLE_ID,
|
||||
ids[index].toInt()
|
||||
ids[index]
|
||||
)
|
||||
},
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package me.ash.reader.data.repository
|
||||
|
||||
import android.util.Log
|
||||
import me.ash.reader.data.feed.Feed
|
||||
import me.ash.reader.data.feed.FeedDao
|
||||
import me.ash.reader.data.group.GroupDao
|
||||
import me.ash.reader.data.source.OpmlLocalDataSource
|
||||
|
@ -18,11 +19,14 @@ class OpmlRepository @Inject constructor(
|
|||
val groupWithFeedList = opmlLocalDataSource.parseFileInputStream(inputStream)
|
||||
groupWithFeedList.forEach { groupWithFeed ->
|
||||
groupDao.insert(groupWithFeed.group)
|
||||
groupWithFeed.feeds.forEach { it.groupId = groupWithFeed.group.id }
|
||||
groupWithFeed.feeds.removeIf {
|
||||
rssRepository.get().isExist(it.url)
|
||||
val repeatList = mutableListOf<Feed>()
|
||||
groupWithFeed.feeds.forEach {
|
||||
it.groupId = groupWithFeed.group.id
|
||||
if (rssRepository.get().isExist(it.url)) {
|
||||
repeatList.add(it)
|
||||
}
|
||||
feedDao.insertList(groupWithFeed.feeds)
|
||||
}
|
||||
feedDao.insertList((groupWithFeed.feeds subtract repeatList).toList())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("saveToDatabase", "${e.message}")
|
||||
|
|
|
@ -11,8 +11,6 @@ import me.ash.reader.data.group.GroupWithFeed
|
|||
import me.ash.reader.dataStore
|
||||
import me.ash.reader.get
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import org.xmlpull.v1.XmlPullParserException
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
@ -21,7 +19,7 @@ class OpmlLocalDataSource @Inject constructor(
|
|||
@ApplicationContext
|
||||
private val context: Context,
|
||||
) {
|
||||
@Throws(XmlPullParserException::class, IOException::class)
|
||||
// @Throws(XmlPullParserException::class, IOException::class)
|
||||
fun parseFileInputStream(inputStream: InputStream): List<GroupWithFeed> {
|
||||
val groupWithFeedList = mutableListOf<GroupWithFeed>()
|
||||
val accountId = context.dataStore.get(DataStoreKeys.CurrentAccountId) ?: 0
|
||||
|
|
|
@ -12,7 +12,9 @@ fun PagerState.animateScrollToPage(
|
|||
callback: () -> Unit = {}
|
||||
) {
|
||||
scope.launch {
|
||||
if (pageCount > targetPage) {
|
||||
animateScrollToPage(targetPage)
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package me.ash.reader.ui.page.home
|
||||
|
||||
import android.util.Log
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.SoundEffectConstants
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.FastOutLinearInEasing
|
||||
|
@ -10,9 +12,10 @@ import androidx.compose.animation.core.updateTransition
|
|||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.ChipDefaults
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.FilterChip
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Article
|
||||
import androidx.compose.material.icons.outlined.Circle
|
||||
|
@ -26,13 +29,14 @@ import androidx.compose.runtime.*
|
|||
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.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.google.accompanist.flowlayout.FlowCrossAxisAlignment
|
||||
import com.google.accompanist.flowlayout.FlowRow
|
||||
import com.google.accompanist.flowlayout.MainAxisAlignment
|
||||
import com.google.accompanist.flowlayout.SizeMode
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.google.accompanist.pager.PagerState
|
||||
import me.ash.reader.R
|
||||
|
@ -89,7 +93,7 @@ fun HomeBottomNavBar(
|
|||
}
|
||||
|
||||
Divider(
|
||||
modifier = Modifier.alpha(0.3f),
|
||||
color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.24f)
|
||||
)
|
||||
Box(
|
||||
modifier = modifier
|
||||
|
@ -107,7 +111,7 @@ fun HomeBottomNavBar(
|
|||
.animateContentSize()
|
||||
.alpha(1 - readerBarAlpha),
|
||||
) {
|
||||
// Log.i("RLog", "AppNavigationBar: ${readerBarAlpha}, ${1f - readerBarAlpha}")
|
||||
Log.i("RLog", "AppNavigationBar: ${readerBarAlpha}, ${1f - readerBarAlpha}")
|
||||
FilterBar(
|
||||
modifier = modifier,
|
||||
filter = filter,
|
||||
|
@ -148,85 +152,152 @@ private fun FilterBar(
|
|||
filter: Filter,
|
||||
onSelected: (Filter) -> Unit = {},
|
||||
) {
|
||||
val view = LocalView.current
|
||||
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
FlowRow(
|
||||
mainAxisSize = SizeMode.Expand,
|
||||
mainAxisAlignment = MainAxisAlignment.Center,
|
||||
crossAxisAlignment = FlowCrossAxisAlignment.Center,
|
||||
crossAxisSpacing = 0.dp,
|
||||
mainAxisSpacing = 20.dp,
|
||||
) {
|
||||
listOf(
|
||||
Filter.Starred,
|
||||
Filter.Unread,
|
||||
Filter.All
|
||||
).forEach { item ->
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.animateContentSize(),
|
||||
Item(
|
||||
icon = if (filter == item) item.filledIcon else item.icon,
|
||||
name = item.getName(),
|
||||
selected = filter == item,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier
|
||||
.height(30.dp)
|
||||
.defaultMinSize(
|
||||
minWidth = 82.dp
|
||||
)
|
||||
.clip(CircleShape)
|
||||
.clickable(onClick = {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
onSelected(item)
|
||||
})
|
||||
.background(
|
||||
if (filter == item) {
|
||||
MaterialTheme.colorScheme.inverseOnSurface
|
||||
} else {
|
||||
Color.Unspecified
|
||||
}
|
||||
)
|
||||
) {
|
||||
if (filter == item) {
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
Icon(
|
||||
modifier = Modifier.size(
|
||||
if (filter == item) {
|
||||
15.dp
|
||||
} else {
|
||||
19.dp
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun Item(
|
||||
modifier: Modifier = Modifier,
|
||||
icon: ImageVector,
|
||||
name: String,
|
||||
selected: Boolean = false,
|
||||
onClick: () -> Unit = {},
|
||||
) {
|
||||
val view = LocalView.current
|
||||
|
||||
FilterChip(
|
||||
modifier = Modifier
|
||||
.height(36.dp)
|
||||
.animateContentSize(),
|
||||
colors = ChipDefaults.filterChipColors(
|
||||
backgroundColor = MaterialTheme.colorScheme.surface,
|
||||
contentColor = MaterialTheme.colorScheme.outline,
|
||||
leadingIconColor = MaterialTheme.colorScheme.outline,
|
||||
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.onSurface,
|
||||
selectedLeadingIconColor = MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
imageVector = item.icon,
|
||||
contentDescription = item.getName(),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
selected = selected,
|
||||
selectedIcon = {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = name,
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.size(20.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
},
|
||||
onClick = {
|
||||
view.playSoundEffect(SoundEffectConstants.CLICK)
|
||||
onClick()
|
||||
},
|
||||
content = {
|
||||
if (selected) {
|
||||
Text(
|
||||
text = item.getName().uppercase(),
|
||||
fontSize = 11.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = modifier.padding(
|
||||
start = 0.dp,
|
||||
top = 8.dp,
|
||||
end = 8.dp,
|
||||
bottom = 8.dp
|
||||
),
|
||||
text = if (selected) name.uppercase() else "",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = if (selected) {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
} else {
|
||||
MaterialTheme.colorScheme.outline
|
||||
},
|
||||
)
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
} else {
|
||||
Icon(
|
||||
modifier = Modifier.size(
|
||||
if (item.isUnread()) {
|
||||
15
|
||||
} else {
|
||||
19
|
||||
}.dp
|
||||
),
|
||||
imageVector = item.icon,
|
||||
contentDescription = item.getName(),
|
||||
imageVector = icon,
|
||||
contentDescription = name,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = MaterialTheme.colorScheme.outline,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Row(
|
||||
// modifier = Modifier
|
||||
// .animateContentSize()
|
||||
// .height(40.dp)
|
||||
// .width(if (selected) Dp.Unspecified else 40.dp)
|
||||
// .padding(vertical = if (selected) 2.dp else 0.dp)
|
||||
// .clip(CircleShape)
|
||||
// .pointerInput(Unit) {
|
||||
// detectTapGestures(
|
||||
// onTap = {
|
||||
// view.playSoundEffect(SoundEffectConstants.CLICK)
|
||||
// onClick()
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
// .background(
|
||||
// if (selected) {
|
||||
// MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.54f)
|
||||
// } else {
|
||||
// Color.Transparent
|
||||
// }
|
||||
// ),
|
||||
// horizontalArrangement = Arrangement.Center,
|
||||
// verticalAlignment = Alignment.CenterVertically,
|
||||
// ) {
|
||||
// Spacer(modifier = Modifier.width(8.dp))
|
||||
// Icon(
|
||||
// modifier = Modifier.size(20.dp),
|
||||
// imageVector = icon,
|
||||
// contentDescription = name,
|
||||
// tint = if (selected) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
// )
|
||||
// if (selected) {
|
||||
// Spacer(modifier = Modifier.width(8.dp))
|
||||
// Text(
|
||||
// modifier = Modifier.padding(horizontal = 8.dp),
|
||||
// text = name.uppercase(),
|
||||
// style = MaterialTheme.typography.titleSmall,
|
||||
// color = if (selected) {
|
||||
// MaterialTheme.colorScheme.onSurface
|
||||
// } else {
|
||||
// MaterialTheme.colorScheme.outline
|
||||
// },
|
||||
// )
|
||||
// Spacer(modifier = Modifier.width(8.dp))
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
@ -47,7 +47,7 @@ fun HomePage(
|
|||
scope.launch {
|
||||
val article = readViewModel
|
||||
.rssRepository.get()
|
||||
.findArticleById(it.toString().toInt()) ?: return@launch
|
||||
.findArticleById(it.toString()) ?: return@launch
|
||||
readViewModel.dispatch(ReadViewAction.InitData(article))
|
||||
if (article.feed.isFullContent) readViewModel.dispatch(ReadViewAction.RenderFullContent)
|
||||
else readViewModel.dispatch(ReadViewAction.RenderDescriptionContent)
|
||||
|
|
|
@ -47,8 +47,8 @@ class HomeViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun sync(callback: () -> Unit = {}) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
rssRepository.get().sync()
|
||||
viewModelScope.launch {
|
||||
rssRepository.get().doSync()
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,6 +106,7 @@ fun FeedsPage(
|
|||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
},
|
||||
|
@ -138,7 +139,6 @@ fun FeedsPage(
|
|||
Icon(
|
||||
imageVector = Icons.Outlined.KeyboardArrowRight,
|
||||
contentDescription = stringResource(R.string.go_to),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
},
|
||||
) {
|
||||
|
@ -161,7 +161,7 @@ fun FeedsPage(
|
|||
item {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Subtitle(
|
||||
modifier = Modifier.padding(start = 28.dp),
|
||||
modifier = Modifier.padding(start = 26.dp),
|
||||
text = stringResource(R.string.feeds)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
@ -207,6 +207,9 @@ fun FeedsPage(
|
|||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(48.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -5,7 +5,6 @@ import androidx.compose.foundation.lazy.LazyListState
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import me.ash.reader.data.account.Account
|
||||
|
@ -48,14 +47,14 @@ class FeedsViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun addFromFile(inputStream: InputStream) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
viewModelScope.launch {
|
||||
opmlRepository.saveToDatabase(inputStream)
|
||||
rssRepository.get().sync()
|
||||
rssRepository.get().doSync()
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchData(filterState: FilterState) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
viewModelScope.launch {
|
||||
pullFeeds(
|
||||
isStarred = filterState.filter.isStarred(),
|
||||
isUnread = filterState.filter.isUnread(),
|
||||
|
|
|
@ -35,7 +35,7 @@ fun GroupItem(
|
|||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
.clip(RoundedCornerShape(32.dp))
|
||||
.background(MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.14f))
|
||||
.clickable { groupOnClick() }
|
||||
|
@ -78,9 +78,6 @@ fun GroupItem(
|
|||
exit = fadeOut() + shrinkVertically(),
|
||||
) {
|
||||
Column {
|
||||
if (feeds.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
feeds.forEach { feed ->
|
||||
FeedItem(
|
||||
modifier = Modifier.padding(horizontal = 20.dp),
|
||||
|
@ -90,6 +87,9 @@ fun GroupItem(
|
|||
feedOnClick(feed)
|
||||
}
|
||||
}
|
||||
if (feeds.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ import androidx.compose.foundation.rememberScrollState
|
|||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Article
|
||||
import androidx.compose.material.icons.outlined.Notifications
|
||||
import androidx.compose.material.icons.filled.AddAlert
|
||||
import androidx.compose.material.icons.filled.Article
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
|
@ -25,18 +25,23 @@ import me.ash.reader.ui.widget.Subtitle
|
|||
|
||||
@Composable
|
||||
fun ResultViewPage(
|
||||
modifier: Modifier = Modifier,
|
||||
link: String = "",
|
||||
groups: List<Group> = emptyList(),
|
||||
selectedAllowNotificationPreset: Boolean = false,
|
||||
selectedParseFullContentPreset: Boolean = false,
|
||||
selectedGroupId: String = "",
|
||||
newGroupContent: String = "",
|
||||
newGroupSelected: Boolean,
|
||||
onNewGroupValueChange: (String) -> Unit = {},
|
||||
changeNewGroupSelected: (Boolean) -> Unit = {},
|
||||
allowNotificationPresetOnClick: () -> Unit = {},
|
||||
parseFullContentPresetOnClick: () -> Unit = {},
|
||||
groupOnClick: (groupId: String) -> Unit = {},
|
||||
onKeyboardAction: () -> Unit = {},
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.verticalScroll(rememberScrollState())
|
||||
modifier = modifier.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Link(
|
||||
text = link
|
||||
|
@ -54,6 +59,10 @@ fun ResultViewPage(
|
|||
AddToGroup(
|
||||
groups = groups,
|
||||
selectedGroupId = selectedGroupId,
|
||||
newGroupContent = newGroupContent,
|
||||
newGroupSelected = newGroupSelected,
|
||||
onNewGroupValueChange = onNewGroupValueChange,
|
||||
changeNewGroupSelected = changeNewGroupSelected,
|
||||
groupOnClick = groupOnClick,
|
||||
onKeyboardAction = onKeyboardAction,
|
||||
)
|
||||
|
@ -98,11 +107,12 @@ private fun Preset(
|
|||
selected = selectedAllowNotificationPreset,
|
||||
selectedIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Notifications,
|
||||
imageVector = Icons.Filled.AddAlert,
|
||||
contentDescription = stringResource(R.string.allow_notification),
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.size(18.dp),
|
||||
.size(20.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
},
|
||||
) {
|
||||
|
@ -114,11 +124,12 @@ private fun Preset(
|
|||
selected = selectedParseFullContentPreset,
|
||||
selectedIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Article,
|
||||
imageVector = Icons.Filled.Article,
|
||||
contentDescription = stringResource(R.string.parse_full_content),
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.size(18.dp),
|
||||
.size(20.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
},
|
||||
) {
|
||||
|
@ -131,6 +142,10 @@ private fun Preset(
|
|||
private fun AddToGroup(
|
||||
groups: List<Group>,
|
||||
selectedGroupId: String,
|
||||
newGroupContent: String,
|
||||
newGroupSelected: Boolean,
|
||||
onNewGroupValueChange: (String) -> Unit = {},
|
||||
changeNewGroupSelected: (Boolean) -> Unit = {},
|
||||
groupOnClick: (groupId: String) -> Unit = {},
|
||||
onKeyboardAction: () -> Unit = {},
|
||||
) {
|
||||
|
@ -145,19 +160,21 @@ private fun AddToGroup(
|
|||
SelectionChip(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
content = it.name,
|
||||
selected = it.id == selectedGroupId,
|
||||
selected = !newGroupSelected && it.id == selectedGroupId,
|
||||
) {
|
||||
changeNewGroupSelected(false)
|
||||
groupOnClick(it.id)
|
||||
}
|
||||
}
|
||||
|
||||
SelectionEditorChip(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
content = stringResource(R.string.new_group),
|
||||
selected = false,
|
||||
content = newGroupContent,
|
||||
onValueChange = onNewGroupValueChange,
|
||||
selected = newGroupSelected,
|
||||
onKeyboardAction = onKeyboardAction,
|
||||
) {
|
||||
|
||||
changeNewGroupSelected(true)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,13 @@
|
|||
package me.ash.reader.ui.page.home.feeds.subscribe
|
||||
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material.TextField
|
||||
import androidx.compose.material.TextFieldDefaults
|
||||
|
@ -22,7 +25,9 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.google.accompanist.pager.PagerState
|
||||
|
@ -32,12 +37,15 @@ import me.ash.reader.R
|
|||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
fun SearchViewPage(
|
||||
modifier: Modifier = Modifier,
|
||||
pagerState: PagerState,
|
||||
inputContent: String = "",
|
||||
readOnly: Boolean = false,
|
||||
inputLink: String = "",
|
||||
errorMessage: String = "",
|
||||
onValueChange: (String) -> Unit = {},
|
||||
onLinkValueChange: (String) -> Unit = {},
|
||||
onKeyboardAction: () -> Unit = {},
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
|
@ -45,7 +53,7 @@ fun SearchViewPage(
|
|||
focusRequester.requestFocus()
|
||||
}
|
||||
|
||||
Column {
|
||||
Column(modifier = modifier) {
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
TextField(
|
||||
modifier = Modifier.focusRequester(focusRequester),
|
||||
|
@ -55,9 +63,10 @@ fun SearchViewPage(
|
|||
textColor = MaterialTheme.colorScheme.onSurface,
|
||||
focusedIndicatorColor = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
value = inputContent,
|
||||
enabled = !readOnly,
|
||||
value = inputLink,
|
||||
onValueChange = {
|
||||
if (pagerState.currentPage == 0) onValueChange(it)
|
||||
if (!readOnly) onLinkValueChange(it)
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
|
@ -68,9 +77,9 @@ fun SearchViewPage(
|
|||
isError = errorMessage.isNotEmpty(),
|
||||
singleLine = true,
|
||||
trailingIcon = {
|
||||
if (inputContent.isNotEmpty()) {
|
||||
if (inputLink.isNotEmpty()) {
|
||||
IconButton(onClick = {
|
||||
onValueChange("")
|
||||
if (!readOnly) onLinkValueChange("")
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Close,
|
||||
|
@ -90,17 +99,23 @@ fun SearchViewPage(
|
|||
}
|
||||
},
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = {
|
||||
onSearch = {
|
||||
focusManager.clearFocus()
|
||||
onKeyboardAction()
|
||||
}
|
||||
)
|
||||
),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Search
|
||||
),
|
||||
)
|
||||
if (errorMessage.isNotEmpty()) {
|
||||
SelectionContainer {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp)
|
||||
.horizontalScroll(rememberScrollState()),
|
||||
text = errorMessage,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
maxLines = 1,
|
||||
softWrap = false,
|
||||
)
|
||||
|
|
|
@ -2,6 +2,8 @@ package me.ash.reader.ui.page.home.feeds.subscribe
|
|||
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.RssFeed
|
||||
import androidx.compose.material3.Icon
|
||||
|
@ -9,9 +11,13 @@ import androidx.compose.material3.MaterialTheme
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import me.ash.reader.*
|
||||
|
@ -23,10 +29,12 @@ import java.io.InputStream
|
|||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
fun SubscribeDialog(
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: SubscribeViewModel = hiltViewModel(),
|
||||
openInputStreamCallback: (InputStream) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val focusManager = LocalFocusManager.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
|
||||
it?.let { uri ->
|
||||
|
@ -37,13 +45,14 @@ fun SubscribeDialog(
|
|||
}
|
||||
val viewState = viewModel.viewState.collectAsStateValue()
|
||||
val groupsState = viewState.groups.collectAsState(initial = emptyList())
|
||||
var height by remember { mutableStateOf(0) }
|
||||
|
||||
var dialogHeight by remember { mutableStateOf(280.dp) }
|
||||
val readYouString = stringResource(R.string.read_you)
|
||||
val defaultString = stringResource(R.string.defaults)
|
||||
LaunchedEffect(viewState.visible) {
|
||||
if (viewState.visible) {
|
||||
val defaultGroupId = context.dataStore
|
||||
.get(DataStoreKeys.CurrentAccountId)!!
|
||||
.spacerDollar("0")
|
||||
.spacerDollar(readYouString + defaultString)
|
||||
viewModel.dispatch(SubscribeViewAction.SelectedGroup(defaultGroupId))
|
||||
viewModel.dispatch(SubscribeViewAction.Init)
|
||||
} else {
|
||||
|
@ -52,9 +61,21 @@ fun SubscribeDialog(
|
|||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(viewState.pagerState.currentPage) {
|
||||
focusManager.clearFocus()
|
||||
when (viewState.pagerState.currentPage) {
|
||||
0 -> dialogHeight = 280.dp
|
||||
1 -> dialogHeight = Dp.Unspecified
|
||||
}
|
||||
}
|
||||
|
||||
Dialog(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 44.dp)
|
||||
.height(dialogHeight),
|
||||
visible = viewState.visible,
|
||||
onDismissRequest = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.dispatch(SubscribeViewAction.Hide)
|
||||
},
|
||||
icon = {
|
||||
|
@ -66,30 +87,35 @@ fun SubscribeDialog(
|
|||
title = {
|
||||
Text(
|
||||
when (viewState.pagerState.currentPage) {
|
||||
0 -> stringResource(R.string.subscribe)
|
||||
0 -> viewState.title
|
||||
else -> viewState.feed?.name ?: stringResource(R.string.unknown)
|
||||
}
|
||||
)
|
||||
},
|
||||
text = {
|
||||
SubscribeViewPager(
|
||||
// height = when (viewState.pagerState.currentPage) {
|
||||
// 0 -> 84.dp
|
||||
// else -> Dp.Unspecified
|
||||
// },
|
||||
inputContent = viewState.inputContent,
|
||||
readOnly = viewState.lockLinkInput,
|
||||
inputLink = viewState.linkContent,
|
||||
errorMessage = viewState.errorMessage,
|
||||
onValueChange = {
|
||||
viewModel.dispatch(SubscribeViewAction.Input(it))
|
||||
onLinkValueChange = {
|
||||
viewModel.dispatch(SubscribeViewAction.InputLink(it))
|
||||
},
|
||||
onSearchKeyboardAction = {
|
||||
viewModel.dispatch(SubscribeViewAction.Search(scope))
|
||||
},
|
||||
link = viewState.inputContent,
|
||||
link = viewState.linkContent,
|
||||
groups = groupsState.value,
|
||||
selectedAllowNotificationPreset = viewState.allowNotificationPreset,
|
||||
selectedParseFullContentPreset = viewState.parseFullContentPreset,
|
||||
selectedGroupId = viewState.selectedGroupId,
|
||||
newGroupContent = viewState.newGroupContent,
|
||||
onNewGroupValueChange = {
|
||||
viewModel.dispatch(SubscribeViewAction.InputNewGroup(it))
|
||||
},
|
||||
newGroupSelected = viewState.newGroupSelected,
|
||||
changeNewGroupSelected = {
|
||||
viewModel.dispatch(SubscribeViewAction.SelectedNewGroup(it))
|
||||
},
|
||||
pagerState = viewState.pagerState,
|
||||
allowNotificationPresetOnClick = {
|
||||
viewModel.dispatch(SubscribeViewAction.ChangeAllowNotificationPreset)
|
||||
|
@ -109,14 +135,15 @@ fun SubscribeDialog(
|
|||
when (viewState.pagerState.currentPage) {
|
||||
0 -> {
|
||||
TextButton(
|
||||
enabled = viewState.inputContent.isNotEmpty(),
|
||||
enabled = viewState.linkContent.isNotEmpty(),
|
||||
onClick = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.dispatch(SubscribeViewAction.Search(scope))
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.search),
|
||||
color = if (viewState.inputContent.isNotEmpty()) {
|
||||
color = if (viewState.linkContent.isNotEmpty()) {
|
||||
Color.Unspecified
|
||||
} else {
|
||||
MaterialTheme.colorScheme.outline.copy(alpha = 0.7f)
|
||||
|
@ -127,6 +154,7 @@ fun SubscribeDialog(
|
|||
1 -> {
|
||||
TextButton(
|
||||
onClick = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.dispatch(SubscribeViewAction.Subscribe)
|
||||
}
|
||||
) {
|
||||
|
@ -140,6 +168,7 @@ fun SubscribeDialog(
|
|||
0 -> {
|
||||
TextButton(
|
||||
onClick = {
|
||||
focusManager.clearFocus()
|
||||
launcher.launch("*/*")
|
||||
viewModel.dispatch(SubscribeViewAction.Hide)
|
||||
}
|
||||
|
@ -150,6 +179,7 @@ fun SubscribeDialog(
|
|||
1 -> {
|
||||
TextButton(
|
||||
onClick = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.dispatch(SubscribeViewAction.Hide)
|
||||
}
|
||||
) {
|
||||
|
|
|
@ -6,7 +6,8 @@ import com.google.accompanist.pager.ExperimentalPagerApi
|
|||
import com.google.accompanist.pager.PagerState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import me.ash.reader.R
|
||||
|
@ -29,6 +30,7 @@ class SubscribeViewModel @Inject constructor(
|
|||
) : ViewModel() {
|
||||
private val _viewState = MutableStateFlow(SubscribeViewState())
|
||||
val viewState: StateFlow<SubscribeViewState> = _viewState.asStateFlow()
|
||||
private var searchJob: Job? = null
|
||||
|
||||
fun dispatch(action: SubscribeViewAction) {
|
||||
when (action) {
|
||||
|
@ -36,13 +38,15 @@ class SubscribeViewModel @Inject constructor(
|
|||
is SubscribeViewAction.Reset -> reset()
|
||||
is SubscribeViewAction.Show -> changeVisible(true)
|
||||
is SubscribeViewAction.Hide -> changeVisible(false)
|
||||
is SubscribeViewAction.Input -> inputLink(action.content)
|
||||
is SubscribeViewAction.InputLink -> inputLink(action.content)
|
||||
is SubscribeViewAction.Search -> search(action.scope)
|
||||
is SubscribeViewAction.ChangeAllowNotificationPreset ->
|
||||
changeAllowNotificationPreset()
|
||||
is SubscribeViewAction.ChangeParseFullContentPreset ->
|
||||
changeParseFullContentPreset()
|
||||
is SubscribeViewAction.SelectedGroup -> selectedGroup(action.groupId)
|
||||
is SubscribeViewAction.InputNewGroup -> inputNewGroup(action.content)
|
||||
is SubscribeViewAction.SelectedNewGroup -> selectedNewGroup(action.selected)
|
||||
is SubscribeViewAction.Subscribe -> subscribe()
|
||||
}
|
||||
}
|
||||
|
@ -51,24 +55,17 @@ class SubscribeViewModel @Inject constructor(
|
|||
_viewState.update {
|
||||
it.copy(
|
||||
title = stringsRepository.getString(R.string.subscribe),
|
||||
groups = rssRepository.get().pullGroups()
|
||||
groups = rssRepository.get().pullGroups(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun reset() {
|
||||
searchJob?.cancel()
|
||||
searchJob = null
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
visible = false,
|
||||
SubscribeViewState().copy(
|
||||
title = stringsRepository.getString(R.string.subscribe),
|
||||
errorMessage = "",
|
||||
inputContent = "",
|
||||
feed = null,
|
||||
articles = emptyList(),
|
||||
allowNotificationPreset = false,
|
||||
parseFullContentPreset = false,
|
||||
selectedGroupId = "",
|
||||
groups = emptyFlow(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -76,11 +73,20 @@ class SubscribeViewModel @Inject constructor(
|
|||
private fun subscribe() {
|
||||
val feed = _viewState.value.feed ?: return
|
||||
val articles = _viewState.value.articles
|
||||
val groupId = _viewState.value.selectedGroupId
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
viewModelScope.launch {
|
||||
val groupId = async {
|
||||
if (
|
||||
_viewState.value.newGroupSelected &&
|
||||
_viewState.value.newGroupContent.isNotBlank()
|
||||
) {
|
||||
rssRepository.get().addGroup(_viewState.value.newGroupContent)
|
||||
} else {
|
||||
_viewState.value.selectedGroupId
|
||||
}
|
||||
}
|
||||
rssRepository.get().subscribe(
|
||||
feed.copy(
|
||||
groupId = groupId,
|
||||
groupId = groupId.await(),
|
||||
isNotification = _viewState.value.allowNotificationPreset,
|
||||
isFullContent = _viewState.value.parseFullContentPreset,
|
||||
), articles
|
||||
|
@ -97,6 +103,14 @@ class SubscribeViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun selectedNewGroup(selected: Boolean) {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
newGroupSelected = selected,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeParseFullContentPreset() {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
|
@ -114,11 +128,19 @@ class SubscribeViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun search(scope: CoroutineScope) {
|
||||
_viewState.value.inputContent.formatUrl().let { str ->
|
||||
if (str != _viewState.value.inputContent) {
|
||||
searchJob?.cancel()
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
inputContent = str
|
||||
errorMessage = "",
|
||||
)
|
||||
}
|
||||
_viewState.value.linkContent.formatUrl().let { str ->
|
||||
if (str != _viewState.value.linkContent) {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
linkContent = str
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -126,19 +148,20 @@ class SubscribeViewModel @Inject constructor(
|
|||
_viewState.update {
|
||||
it.copy(
|
||||
title = stringsRepository.getString(R.string.searching),
|
||||
lockLinkInput = true,
|
||||
)
|
||||
}
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
if (rssRepository.get().isExist(_viewState.value.inputContent)) {
|
||||
if (rssRepository.get().isExist(_viewState.value.linkContent)) {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
title = stringsRepository.getString(R.string.subscribe),
|
||||
errorMessage = stringsRepository.getString(R.string.already_subscribed),
|
||||
lockLinkInput = false,
|
||||
)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
val feedWithArticle = rssHelper.searchFeed(_viewState.value.inputContent)
|
||||
val feedWithArticle = rssHelper.searchFeed(_viewState.value.linkContent)
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
feed = feedWithArticle.feed,
|
||||
|
@ -152,16 +175,27 @@ class SubscribeViewModel @Inject constructor(
|
|||
it.copy(
|
||||
title = stringsRepository.getString(R.string.subscribe),
|
||||
errorMessage = e.message ?: stringsRepository.getString(R.string.unknown),
|
||||
lockLinkInput = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}.also {
|
||||
searchJob = it
|
||||
}
|
||||
}
|
||||
|
||||
private fun inputLink(content: String) {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
inputContent = content
|
||||
linkContent = content
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun inputNewGroup(content: String) {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
newGroupContent = content
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -180,12 +214,15 @@ data class SubscribeViewState(
|
|||
val visible: Boolean = false,
|
||||
val title: String = "",
|
||||
val errorMessage: String = "",
|
||||
val inputContent: String = "",
|
||||
val linkContent: String = "",
|
||||
val lockLinkInput: Boolean = false,
|
||||
val feed: Feed? = null,
|
||||
val articles: List<Article> = emptyList(),
|
||||
val allowNotificationPreset: Boolean = false,
|
||||
val parseFullContentPreset: Boolean = false,
|
||||
val selectedGroupId: String = "",
|
||||
val newGroupContent: String = "",
|
||||
val newGroupSelected: Boolean = false,
|
||||
val groups: Flow<List<Group>> = emptyFlow(),
|
||||
val pagerState: PagerState = PagerState(),
|
||||
)
|
||||
|
@ -197,7 +234,7 @@ sealed class SubscribeViewAction {
|
|||
object Show : SubscribeViewAction()
|
||||
object Hide : SubscribeViewAction()
|
||||
|
||||
data class Input(
|
||||
data class InputLink(
|
||||
val content: String
|
||||
) : SubscribeViewAction()
|
||||
|
||||
|
@ -212,5 +249,13 @@ sealed class SubscribeViewAction {
|
|||
val groupId: String
|
||||
) : SubscribeViewAction()
|
||||
|
||||
data class InputNewGroup(
|
||||
val content: String
|
||||
) : SubscribeViewAction()
|
||||
|
||||
data class SelectedNewGroup(
|
||||
val selected: Boolean
|
||||
) : SubscribeViewAction()
|
||||
|
||||
object Subscribe : SubscribeViewAction()
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package me.ash.reader.ui.page.home.feeds.subscribe
|
||||
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.google.accompanist.pager.PagerState
|
||||
import me.ash.reader.data.group.Group
|
||||
|
@ -12,33 +13,47 @@ import me.ash.reader.ui.widget.ViewPager
|
|||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
fun SubscribeViewPager(
|
||||
height: Dp = Dp.Unspecified,
|
||||
inputContent: String = "",
|
||||
modifier: Modifier = Modifier,
|
||||
readOnly: Boolean = false,
|
||||
inputLink: String = "",
|
||||
errorMessage: String = "",
|
||||
onValueChange: (String) -> Unit = {},
|
||||
onLinkValueChange: (String) -> Unit = {},
|
||||
onSearchKeyboardAction: () -> Unit = {},
|
||||
link: String = "",
|
||||
groups: List<Group> = emptyList(),
|
||||
selectedAllowNotificationPreset: Boolean = false,
|
||||
selectedParseFullContentPreset: Boolean = false,
|
||||
selectedGroupId: String = "",
|
||||
newGroupContent: String = "",
|
||||
onNewGroupValueChange: (String) -> Unit = {},
|
||||
newGroupSelected: Boolean,
|
||||
changeNewGroupSelected: (Boolean) -> Unit = {},
|
||||
pagerState: PagerState = com.google.accompanist.pager.rememberPagerState(),
|
||||
allowNotificationPresetOnClick: () -> Unit = {},
|
||||
parseFullContentPresetOnClick: () -> Unit = {},
|
||||
groupOnClick: (groupId: String) -> Unit = {},
|
||||
onResultKeyboardAction: () -> Unit = {},
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
ViewPager(
|
||||
modifier = Modifier.height(height),
|
||||
modifier = modifier.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onTap = {
|
||||
focusManager.clearFocus()
|
||||
}
|
||||
)
|
||||
},
|
||||
state = pagerState,
|
||||
userScrollEnabled = false,
|
||||
composableList = listOf(
|
||||
{
|
||||
SearchViewPage(
|
||||
pagerState = pagerState,
|
||||
inputContent = inputContent,
|
||||
readOnly = readOnly,
|
||||
inputLink = inputLink,
|
||||
errorMessage = errorMessage,
|
||||
onValueChange = onValueChange,
|
||||
onLinkValueChange = onLinkValueChange,
|
||||
onKeyboardAction = onSearchKeyboardAction,
|
||||
)
|
||||
},
|
||||
|
@ -49,6 +64,10 @@ fun SubscribeViewPager(
|
|||
selectedAllowNotificationPreset = selectedAllowNotificationPreset,
|
||||
selectedParseFullContentPreset = selectedParseFullContentPreset,
|
||||
selectedGroupId = selectedGroupId,
|
||||
newGroupContent = newGroupContent,
|
||||
onNewGroupValueChange = onNewGroupValueChange,
|
||||
newGroupSelected = newGroupSelected,
|
||||
changeNewGroupSelected = changeNewGroupSelected,
|
||||
allowNotificationPresetOnClick = allowNotificationPresetOnClick,
|
||||
parseFullContentPresetOnClick = parseFullContentPresetOnClick,
|
||||
groupOnClick = groupOnClick,
|
||||
|
|
|
@ -27,10 +27,10 @@ fun ArticleItem(
|
|||
val context = LocalContext.current
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
.padding(horizontal = 12.dp)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.clickable { onClick(articleWithFeed) }
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
.padding(horizontal = 12.dp, vertical = 12.dp)
|
||||
.alpha(if (articleWithFeed.article.isUnread) 1f else 0.5f),
|
||||
) {
|
||||
Row(
|
||||
|
@ -72,7 +72,7 @@ fun ArticleItem(
|
|||
)
|
||||
Text(
|
||||
text = articleWithFeed.article.shortDescription,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
|
@ -8,7 +8,6 @@ import androidx.paging.PagingConfig
|
|||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import me.ash.reader.data.article.ArticleWithFeed
|
||||
|
@ -41,7 +40,7 @@ class FlowViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun fetchData(filterState: FilterState) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
viewModelScope.launch {
|
||||
rssRepository.get().pullImportant(filterState.filter.isStarred(), true)
|
||||
.collect { importantList ->
|
||||
_viewState.update {
|
||||
|
|
|
@ -4,8 +4,9 @@ import android.os.Build
|
|||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import me.ash.reader.ui.theme.*
|
||||
|
||||
private val LightThemeColors = lightColorScheme(
|
||||
|
||||
|
@ -37,6 +38,7 @@ private val LightThemeColors = lightColorScheme(
|
|||
inversePrimary = md_theme_light_inversePrimary,
|
||||
// shadow = md_theme_light_shadow,
|
||||
)
|
||||
|
||||
private val DarkThemeColors = darkColorScheme(
|
||||
|
||||
primary = md_theme_dark_primary,
|
||||
|
@ -68,6 +70,10 @@ private val DarkThemeColors = darkColorScheme(
|
|||
// shadow = md_theme_dark_shadow,
|
||||
)
|
||||
|
||||
val LocalLightThemeColors = staticCompositionLocalOf { LightThemeColors }
|
||||
|
||||
val LocalDarkThemeColors = staticCompositionLocalOf { DarkThemeColors }
|
||||
|
||||
@Composable
|
||||
fun AppTheme(
|
||||
useDarkTheme: Boolean = isSystemInDarkTheme(),
|
||||
|
@ -75,15 +81,27 @@ fun AppTheme(
|
|||
) {
|
||||
// Dynamic color is available on Android 12+
|
||||
val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||
val colorScheme = when {
|
||||
dynamicColor && useDarkTheme -> dynamicDarkColorScheme(LocalContext.current)
|
||||
dynamicColor && !useDarkTheme -> dynamicLightColorScheme(LocalContext.current)
|
||||
useDarkTheme -> DarkThemeColors
|
||||
val light = when {
|
||||
dynamicColor -> dynamicLightColorScheme(LocalContext.current)
|
||||
else -> LightThemeColors
|
||||
}
|
||||
val dark = when {
|
||||
dynamicColor -> dynamicDarkColorScheme(LocalContext.current)
|
||||
else -> DarkThemeColors
|
||||
}
|
||||
val colorScheme = when {
|
||||
useDarkTheme -> dark
|
||||
else -> light
|
||||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalLightThemeColors provides light,
|
||||
LocalDarkThemeColors provides dark,
|
||||
) {
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = AppTypography,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
|
@ -4,17 +4,16 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.clickable
|
||||
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.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import me.ash.reader.ui.theme.LocalLightThemeColors
|
||||
|
||||
@Composable
|
||||
fun Banner(
|
||||
|
@ -25,6 +24,10 @@ fun Banner(
|
|||
action: (@Composable () -> Unit)? = null,
|
||||
onClick: () -> Unit = {},
|
||||
) {
|
||||
val lightThemeColors = LocalLightThemeColors.current
|
||||
val lightPrimaryContainer = lightThemeColors.primaryContainer
|
||||
val lightOnSurface = lightThemeColors.onSurface
|
||||
|
||||
Surface(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
|
@ -34,7 +37,7 @@ fun Banner(
|
|||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
.clip(RoundedCornerShape(32.dp))
|
||||
.background(MaterialTheme.colorScheme.primaryContainer)
|
||||
.background(lightPrimaryContainer)
|
||||
.clickable { onClick() }
|
||||
.padding(16.dp, 20.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
|
@ -44,7 +47,7 @@ fun Banner(
|
|||
imageVector = it,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(end = 16.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
tint = lightOnSurface,
|
||||
)
|
||||
}
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
|
@ -52,22 +55,24 @@ fun Banner(
|
|||
text = title,
|
||||
maxLines = if (desc == null) 2 else 1,
|
||||
style = MaterialTheme.typography.titleLarge.copy(fontSize = 20.sp),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = lightOnSurface,
|
||||
)
|
||||
desc?.let {
|
||||
Text(
|
||||
text = it,
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
|
||||
color = lightOnSurface.copy(alpha = 0.7f),
|
||||
)
|
||||
}
|
||||
}
|
||||
action?.let {
|
||||
Box(Modifier.padding(start = 16.dp)) {
|
||||
CompositionLocalProvider(LocalContentColor provides lightOnSurface) {
|
||||
it()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,14 @@ package me.ash.reader.ui.widget
|
|||
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun Dialog(
|
||||
modifier: Modifier = Modifier,
|
||||
visible: Boolean,
|
||||
onDismissRequest: () -> Unit = {},
|
||||
icon: @Composable (() -> Unit)? = null,
|
||||
|
@ -13,13 +18,10 @@ fun Dialog(
|
|||
confirmButton: @Composable () -> Unit,
|
||||
dismissButton: @Composable (() -> Unit)? = null,
|
||||
) {
|
||||
// AnimatedVisibility(
|
||||
// visible = visible,
|
||||
// enter = fadeIn() + expandVertically(),
|
||||
// exit = fadeOut() + shrinkVertically(),
|
||||
// ) {
|
||||
if (visible) {
|
||||
AlertDialog(
|
||||
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||
modifier = modifier,
|
||||
onDismissRequest = onDismissRequest,
|
||||
icon = icon,
|
||||
title = title,
|
||||
|
|
|
@ -1,25 +1,32 @@
|
|||
package me.ash.reader.ui.widget
|
||||
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.ChipDefaults
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.FilterChip
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Check
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.unit.dp
|
||||
import me.ash.reader.R
|
||||
|
||||
|
@ -34,20 +41,23 @@ fun SelectionChip(
|
|||
shape: Shape = CircleShape,
|
||||
selectedIcon: @Composable () -> Unit = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Check,
|
||||
imageVector = Icons.Filled.CheckCircle,
|
||||
contentDescription = stringResource(R.string.selected),
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.size(18.dp)
|
||||
.size(20.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
},
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
FilterChip(
|
||||
modifier = modifier,
|
||||
colors = ChipDefaults.filterChipColors(
|
||||
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||
contentColor = MaterialTheme.colorScheme.outline,
|
||||
leadingIconColor = MaterialTheme.colorScheme.onSurface,
|
||||
disabledBackgroundColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
|
||||
disabledContentColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
|
||||
|
@ -61,7 +71,10 @@ fun SelectionChip(
|
|||
selected = selected,
|
||||
selectedIcon = selectedIcon,
|
||||
shape = shape,
|
||||
onClick = onClick,
|
||||
onClick = {
|
||||
focusManager.clearFocus()
|
||||
onClick()
|
||||
},
|
||||
content = {
|
||||
Text(
|
||||
modifier = modifier.padding(
|
||||
|
@ -72,6 +85,11 @@ fun SelectionChip(
|
|||
),
|
||||
text = content,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = if (selected) {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
} else {
|
||||
MaterialTheme.colorScheme.outline
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
@ -80,26 +98,30 @@ fun SelectionChip(
|
|||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun SelectionEditorChip(
|
||||
content: String,
|
||||
selected: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
content: String,
|
||||
onValueChange: (String) -> Unit = {},
|
||||
selected: Boolean,
|
||||
enabled: Boolean = true,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
shape: Shape = CircleShape,
|
||||
selectedIcon: @Composable () -> Unit = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Check,
|
||||
imageVector = Icons.Filled.CheckCircle,
|
||||
contentDescription = stringResource(R.string.selected),
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.size(16.dp)
|
||||
.size(20.dp),
|
||||
tint = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
},
|
||||
onKeyboardAction: () -> Unit = {},
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
val placeholder = stringResource(R.string.add_to_group)
|
||||
|
||||
FilterChip(
|
||||
modifier = modifier,
|
||||
colors = ChipDefaults.filterChipColors(
|
||||
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
|
@ -123,21 +145,50 @@ fun SelectionEditorChip(
|
|||
.padding(
|
||||
start = if (selected) 0.dp else 8.dp,
|
||||
top = 8.dp,
|
||||
end = 8.dp,
|
||||
end = if (content.isEmpty()) 0.dp else 8.dp,
|
||||
bottom = 8.dp
|
||||
)
|
||||
.width(56.dp),
|
||||
.onFocusChanged {
|
||||
if (it.isFocused) {
|
||||
onClick()
|
||||
} else {
|
||||
focusManager.clearFocus()
|
||||
}
|
||||
},
|
||||
value = content,
|
||||
onValueChange = {},
|
||||
onValueChange = { onValueChange(it) },
|
||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.onSecondaryContainer),
|
||||
textStyle = MaterialTheme.typography.titleSmall.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
color = if (selected) {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
} else {
|
||||
MaterialTheme.colorScheme.outline
|
||||
},
|
||||
),
|
||||
singleLine = true,
|
||||
decorationBox = { innerTextField ->
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (content.isEmpty()) {
|
||||
Text(
|
||||
text = placeholder,
|
||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
)
|
||||
}
|
||||
}
|
||||
innerTextField()
|
||||
},
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = {
|
||||
focusManager.clearFocus()
|
||||
onKeyboardAction()
|
||||
}
|
||||
)
|
||||
),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue
Block a user