Use localized language
This commit is contained in:
@@ -26,6 +26,9 @@ class App : Application() {
|
||||
@Inject
|
||||
lateinit var rssHelper: RssHelper
|
||||
|
||||
@Inject
|
||||
lateinit var stringsRepository: StringsRepository
|
||||
|
||||
@Inject
|
||||
lateinit var accountRepository: AccountRepository
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package me.ash.reader
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
fun Date.formatToString(
|
||||
context: Context,
|
||||
onlyHourMinute: Boolean? = false,
|
||||
atHourMinute: Boolean? = false,
|
||||
): String {
|
||||
val locale = ConfigurationCompat.getLocales(context.resources.configuration)[0]
|
||||
val df = DateFormat.getDateInstance(DateFormat.FULL, locale)
|
||||
return when {
|
||||
onlyHourMinute == true -> {
|
||||
SimpleDateFormat("HH:mm", locale).format(this)
|
||||
}
|
||||
atHourMinute == true -> {
|
||||
context.getString(
|
||||
R.string.date_at_time,
|
||||
df.format(this),
|
||||
SimpleDateFormat("HH:mm", locale).format(this),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
df.format(this).run {
|
||||
when (this) {
|
||||
df.format(Date()) -> context.getString(R.string.today)
|
||||
df.format(
|
||||
Calendar.getInstance().apply {
|
||||
time = Date()
|
||||
add(Calendar.DAY_OF_MONTH, -1)
|
||||
}.time
|
||||
) -> context.getString(R.string.yesterday)
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package me.ash.reader
|
||||
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
object DateTimeExt {
|
||||
|
||||
const val HH_MM_SS = "HH:mm:ss"
|
||||
const val HH_MM = "HH:mm"
|
||||
const val MM_SS = "mm:ss"
|
||||
const val YYYY_MM_DD_HH_MM_SS = "yyyy年MM月dd日 HH:mm:ss"
|
||||
const val YYYY_MM_DD_HH_MM = "yyyy年MM月dd日 HH:mm"
|
||||
const val YYYY_MM_DD = "yyyy年MM月dd日"
|
||||
const val YYYY_MM = "yyyy年MM月"
|
||||
const val YYYY = "yyyy年"
|
||||
const val MM = "MM月"
|
||||
const val DD = "dd日"
|
||||
|
||||
/**
|
||||
* Returns a date-time [String] format from a [Date] object.
|
||||
*/
|
||||
fun Date.toString(pattern: String, simpleDate: Boolean? = false): String {
|
||||
return if (simpleDate == true) {
|
||||
val format = if (pattern == YYYY_MM_DD) {
|
||||
""
|
||||
} else {
|
||||
SimpleDateFormat(
|
||||
pattern.replace(YYYY_MM_DD, "")
|
||||
).format(this)
|
||||
}
|
||||
when (this.toString(YYYY_MM_DD)) {
|
||||
Date().toString(YYYY_MM_DD) -> {
|
||||
"今天${format}"
|
||||
}
|
||||
Calendar.getInstance().apply {
|
||||
time = Date()
|
||||
add(Calendar.DAY_OF_MONTH, -1)
|
||||
}.time.toString(YYYY_MM_DD) -> {
|
||||
"昨天${format}"
|
||||
}
|
||||
else -> SimpleDateFormat(pattern).format(this)
|
||||
}
|
||||
} else {
|
||||
SimpleDateFormat(pattern).format(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a [Date] object parsed from a date-time [String].
|
||||
*/
|
||||
fun String.toDate(pattern: String? = null): Date =
|
||||
SimpleDateFormat((pattern ?: YYYY_MM_DD_HH_MM_SS)).parse(this)
|
||||
}
|
||||
@@ -8,8 +8,6 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
||||
class Filter(
|
||||
var index: Int,
|
||||
var name: String,
|
||||
var description: String,
|
||||
var important: Int,
|
||||
var icon: ImageVector,
|
||||
) {
|
||||
@@ -20,22 +18,16 @@ class Filter(
|
||||
companion object {
|
||||
val Starred = Filter(
|
||||
index = 0,
|
||||
name = "Starred",
|
||||
description = " Starred Items",
|
||||
important = 13,
|
||||
icon = Icons.Rounded.StarOutline,
|
||||
)
|
||||
val Unread = Filter(
|
||||
index = 1,
|
||||
name = "Unread",
|
||||
description = " Unread Items",
|
||||
important = 666,
|
||||
icon = Icons.Outlined.FiberManualRecord,
|
||||
)
|
||||
val All = Filter(
|
||||
index = 2,
|
||||
name = "All",
|
||||
description = " Unread Items",
|
||||
important = 666,
|
||||
icon = Icons.Rounded.Subject,
|
||||
)
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package me.ash.reader.data.constant
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.FiberManualRecord
|
||||
import androidx.compose.material.icons.rounded.Star
|
||||
import androidx.compose.material.icons.rounded.Subject
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
||||
class NavigationBarItem(
|
||||
var title: String,
|
||||
var icon: ImageVector,
|
||||
) {
|
||||
companion object {
|
||||
val Starred = NavigationBarItem("STARRED", Icons.Rounded.Star)
|
||||
val Unread = NavigationBarItem("UNREAD", Icons.Rounded.FiberManualRecord)
|
||||
val All = NavigationBarItem("ALL", Icons.Rounded.Subject)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package me.ash.reader.data.constant
|
||||
|
||||
object Symbol {
|
||||
const val NOTHING: String = "Null"
|
||||
const val Unknown: String = "Unknown"
|
||||
const val NOTIFICATION_CHANNEL_GROUP_ARTICLE_UPDATE: String = "article.update"
|
||||
const val EXTRA_ARTICLE_ID: String = "article.id"
|
||||
}
|
||||
@@ -2,14 +2,11 @@ package me.ash.reader.data.repository
|
||||
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import me.ash.reader.DataStoreKeys
|
||||
import me.ash.reader.*
|
||||
import me.ash.reader.data.account.Account
|
||||
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.get
|
||||
import me.ash.reader.spacerDollar
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountRepository @Inject constructor(
|
||||
@@ -29,8 +26,10 @@ class AccountRepository @Inject constructor(
|
||||
}
|
||||
|
||||
suspend fun addDefaultAccount(): Account {
|
||||
val readYouString = context.getString(R.string.read_you)
|
||||
val defaultString = context.getString(R.string.defaults)
|
||||
return Account(
|
||||
name = "Read You",
|
||||
name = readYouString,
|
||||
type = Account.Type.LOCAL,
|
||||
).apply {
|
||||
id = accountDao.insert(this).toInt()
|
||||
@@ -38,8 +37,8 @@ class AccountRepository @Inject constructor(
|
||||
if (groupDao.queryAll(it.id!!).isEmpty()) {
|
||||
groupDao.insert(
|
||||
Group(
|
||||
id = it.id!!.spacerDollar("0"),
|
||||
name = "默认",
|
||||
id = it.id!!.spacerDollar(readYouString + defaultString),
|
||||
name = defaultString,
|
||||
accountId = it.id!!,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,11 +19,12 @@ import me.ash.reader.*
|
||||
import me.ash.reader.data.account.AccountDao
|
||||
import me.ash.reader.data.article.Article
|
||||
import me.ash.reader.data.article.ArticleDao
|
||||
import me.ash.reader.data.constant.Symbol
|
||||
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.RssNetworkDataSource
|
||||
import me.ash.reader.ui.page.common.ExtraName
|
||||
import me.ash.reader.ui.page.common.NotificationGroupName
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -119,7 +120,7 @@ class LocalRssRepository @Inject constructor(
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
notificationManager.createNotificationChannel(
|
||||
NotificationChannel(
|
||||
Symbol.NOTIFICATION_CHANNEL_GROUP_ARTICLE_UPDATE,
|
||||
NotificationGroupName.ARTICLE_UPDATE,
|
||||
"文章更新",
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
)
|
||||
@@ -132,9 +133,9 @@ class LocalRssRepository @Inject constructor(
|
||||
if (feedNotificationMap[article.feedId] == true) {
|
||||
val builder = NotificationCompat.Builder(
|
||||
context,
|
||||
Symbol.NOTIFICATION_CHANNEL_GROUP_ARTICLE_UPDATE
|
||||
NotificationGroupName.ARTICLE_UPDATE
|
||||
).setSmallIcon(R.drawable.ic_launcher_foreground)
|
||||
.setGroup(Symbol.NOTIFICATION_CHANNEL_GROUP_ARTICLE_UPDATE)
|
||||
.setGroup(NotificationGroupName.ARTICLE_UPDATE)
|
||||
.setContentTitle(article.title)
|
||||
.setContentText(article.shortDescription)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
@@ -146,7 +147,7 @@ class LocalRssRepository @Inject constructor(
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
putExtra(
|
||||
Symbol.EXTRA_ARTICLE_ID,
|
||||
ExtraName.ARTICLE_ID,
|
||||
ids[index].toInt()
|
||||
)
|
||||
},
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package me.ash.reader.data.repository
|
||||
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class StringsRepository @Inject constructor(
|
||||
@ApplicationContext
|
||||
private val context: Context,
|
||||
) {
|
||||
fun getString(resId: Int) = context.getString(resId)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package me.ash.reader.ui.extension
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import me.ash.reader.R
|
||||
import me.ash.reader.data.constant.Filter
|
||||
|
||||
@Composable
|
||||
fun Filter.getName(): String = when (this) {
|
||||
Filter.Unread -> stringResource(R.string.unread)
|
||||
Filter.Starred -> stringResource(R.string.starred)
|
||||
else -> stringResource(R.string.all)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Filter.getDesc(): String = when (this) {
|
||||
Filter.Unread -> stringResource(R.string.unread_desc, this.important)
|
||||
Filter.Starred -> stringResource(R.string.starred_desc, this.important)
|
||||
else -> stringResource(R.string.unread_desc, this.important)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package me.ash.reader.ui.page.common
|
||||
|
||||
object ExtraName {
|
||||
const val ARTICLE_ID: String = "article.id"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package me.ash.reader.ui.page.common
|
||||
|
||||
object NotificationGroupName {
|
||||
const val ARTICLE_UPDATE: String = "article.update"
|
||||
}
|
||||
@@ -29,13 +29,15 @@ import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
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.pager.ExperimentalPagerApi
|
||||
import com.google.accompanist.pager.PagerState
|
||||
import me.ash.reader.R
|
||||
import me.ash.reader.data.constant.Filter
|
||||
import me.ash.reader.data.constant.NavigationBarItem
|
||||
import me.ash.reader.ui.extension.getName
|
||||
import me.ash.reader.ui.widget.CanBeDisabledIconButton
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
@@ -153,10 +155,10 @@ private fun FilterBar(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
listOf(
|
||||
NavigationBarItem.Starred,
|
||||
NavigationBarItem.Unread,
|
||||
NavigationBarItem.All
|
||||
).forEachIndexed { index, item ->
|
||||
Filter.Starred,
|
||||
Filter.Unread,
|
||||
Filter.All
|
||||
).forEach { item ->
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
@@ -175,39 +177,33 @@ private fun FilterBar(
|
||||
.clip(CircleShape)
|
||||
.clickable(onClick = {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
onSelected(
|
||||
when (index) {
|
||||
0 -> Filter.Starred
|
||||
1 -> Filter.Unread
|
||||
else -> Filter.All
|
||||
}
|
||||
)
|
||||
onSelected(item)
|
||||
})
|
||||
.background(
|
||||
if (filter.index == index) {
|
||||
if (filter == item) {
|
||||
MaterialTheme.colorScheme.inverseOnSurface
|
||||
} else {
|
||||
Color.Unspecified
|
||||
}
|
||||
)
|
||||
) {
|
||||
if (filter.index == index) {
|
||||
if (filter == item) {
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
Icon(
|
||||
modifier = Modifier.size(
|
||||
if (Filter.Unread.index == index) {
|
||||
15
|
||||
if (filter == item) {
|
||||
15.dp
|
||||
} else {
|
||||
19
|
||||
}.dp
|
||||
19.dp
|
||||
}
|
||||
),
|
||||
imageVector = item.icon,
|
||||
contentDescription = item.title,
|
||||
contentDescription = item.getName(),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = item.title,
|
||||
text = item.getName().uppercase(),
|
||||
fontSize = 11.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
@@ -216,14 +212,14 @@ private fun FilterBar(
|
||||
} else {
|
||||
Icon(
|
||||
modifier = Modifier.size(
|
||||
if (Filter.Unread.index == index) {
|
||||
if (item.isUnread()) {
|
||||
15
|
||||
} else {
|
||||
19
|
||||
}.dp
|
||||
),
|
||||
imageVector = item.icon,
|
||||
contentDescription = item.title,
|
||||
contentDescription = item.getName(),
|
||||
tint = MaterialTheme.colorScheme.outline,
|
||||
)
|
||||
}
|
||||
@@ -261,7 +257,7 @@ private fun ReaderBar(
|
||||
} else {
|
||||
Icons.Outlined.Circle
|
||||
},
|
||||
contentDescription = "Mark Unread",
|
||||
contentDescription = stringResource(if (isUnread) R.string.mark_as_read else R.string.mark_as_unread),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
) {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
@@ -275,7 +271,7 @@ private fun ReaderBar(
|
||||
} else {
|
||||
Icons.Rounded.StarBorder
|
||||
},
|
||||
contentDescription = "Starred",
|
||||
contentDescription = stringResource(if (isStarred) R.string.mark_as_unstar else R.string.mark_as_starred),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
) {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
@@ -306,7 +302,7 @@ private fun ReaderBar(
|
||||
} else {
|
||||
Icons.Outlined.Article
|
||||
},
|
||||
contentDescription = "Full Content Parsing",
|
||||
contentDescription = stringResource(R.string.parse_full_content),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
) {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
|
||||
@@ -16,9 +16,10 @@ import androidx.navigation.NavHostController
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import me.ash.reader.data.constant.Symbol
|
||||
import me.ash.reader.ui.page.common.NotificationGroupName
|
||||
import me.ash.reader.ui.extension.collectAsStateValue
|
||||
import me.ash.reader.ui.extension.findActivity
|
||||
import me.ash.reader.ui.page.common.ExtraName
|
||||
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
|
||||
@@ -42,7 +43,7 @@ fun HomePage(
|
||||
LaunchedEffect(Unit) {
|
||||
context.findActivity()?.let { activity ->
|
||||
activity.intent?.let { intent ->
|
||||
intent.extras?.get(Symbol.EXTRA_ARTICLE_ID)?.let {
|
||||
intent.extras?.get(ExtraName.ARTICLE_ID)?.let {
|
||||
readViewModel.dispatch(ReadViewAction.ScrollToItem(2))
|
||||
scope.launch {
|
||||
val article =
|
||||
@@ -60,7 +61,7 @@ fun HomePage(
|
||||
)
|
||||
}
|
||||
}
|
||||
intent.extras?.remove(Symbol.EXTRA_ARTICLE_ID)
|
||||
intent.extras?.remove(ExtraName.ARTICLE_ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,15 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import me.ash.reader.data.constant.Symbol
|
||||
import me.ash.reader.R
|
||||
import me.ash.reader.ui.extension.collectAsStateValue
|
||||
import me.ash.reader.ui.extension.getDesc
|
||||
import me.ash.reader.ui.extension.getName
|
||||
import me.ash.reader.ui.page.home.HomeViewAction
|
||||
import me.ash.reader.ui.page.home.HomeViewModel
|
||||
import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeDialog
|
||||
@@ -77,7 +80,7 @@ fun FeedsPage(
|
||||
IconButton(onClick = { }) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
contentDescription = stringResource(R.string.back),
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
@@ -90,7 +93,7 @@ fun FeedsPage(
|
||||
Icon(
|
||||
modifier = Modifier.rotate(if (syncState.isSyncing) angle else 0f),
|
||||
imageVector = Icons.Rounded.Refresh,
|
||||
contentDescription = "Refresh",
|
||||
contentDescription = stringResource(R.string.refresh),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
@@ -99,7 +102,7 @@ fun FeedsPage(
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Add,
|
||||
contentDescription = "Subscribe",
|
||||
contentDescription = stringResource(R.string.subscribe),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
@@ -121,20 +124,20 @@ fun FeedsPage(
|
||||
end = 24.dp,
|
||||
bottom = 24.dp
|
||||
),
|
||||
text = viewState.account?.name ?: Symbol.Unknown,
|
||||
text = viewState.account?.name ?: stringResource(R.string.unknown),
|
||||
style = MaterialTheme.typography.displaySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
item {
|
||||
Banner(
|
||||
title = viewState.filter.name,
|
||||
desc = "${viewState.filter.important}${viewState.filter.description}",
|
||||
title = filterState.filter.getName(),
|
||||
desc = filterState.filter.getDesc(),
|
||||
icon = viewState.filter.icon,
|
||||
action = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.KeyboardArrowRight,
|
||||
contentDescription = "Goto",
|
||||
contentDescription = stringResource(R.string.go_to),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
},
|
||||
@@ -159,7 +162,7 @@ fun FeedsPage(
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Subtitle(
|
||||
modifier = Modifier.padding(start = 28.dp),
|
||||
text = "Feeds"
|
||||
text = stringResource(R.string.feeds)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import me.ash.reader.R
|
||||
import me.ash.reader.data.feed.Feed
|
||||
|
||||
@Composable
|
||||
@@ -37,7 +39,7 @@ fun GroupItem(
|
||||
.clip(RoundedCornerShape(32.dp))
|
||||
.background(MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.14f))
|
||||
.clickable { groupOnClick() }
|
||||
.padding(vertical = 22.dp)
|
||||
.padding(top = 22.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
@@ -64,12 +66,12 @@ fun GroupItem(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (expanded) Icons.Rounded.ExpandLess else Icons.Rounded.ExpandMore,
|
||||
contentDescription = if (expanded) "Expand Less" else "Expand More",
|
||||
contentDescription = stringResource(if (expanded) R.string.expand_less else R.string.expand_more),
|
||||
tint = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(22.dp))
|
||||
AnimatedVisibility(
|
||||
visible = expanded,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
|
||||
@@ -13,9 +13,11 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.flowlayout.FlowRow
|
||||
import com.google.accompanist.flowlayout.MainAxisAlignment
|
||||
import me.ash.reader.R
|
||||
import me.ash.reader.data.group.Group
|
||||
import me.ash.reader.ui.widget.SelectionChip
|
||||
import me.ash.reader.ui.widget.SelectionEditorChip
|
||||
@@ -25,11 +27,11 @@ import me.ash.reader.ui.widget.Subtitle
|
||||
fun ResultViewPage(
|
||||
link: String = "",
|
||||
groups: List<Group> = emptyList(),
|
||||
selectedNotificationPreset: Boolean = false,
|
||||
selectedFullContentParsePreset: Boolean = false,
|
||||
selectedAllowNotificationPreset: Boolean = false,
|
||||
selectedParseFullContentPreset: Boolean = false,
|
||||
selectedGroupId: String = "",
|
||||
notificationPresetOnClick: () -> Unit = {},
|
||||
fullContentParsePresetOnClick: () -> Unit = {},
|
||||
allowNotificationPresetOnClick: () -> Unit = {},
|
||||
parseFullContentPresetOnClick: () -> Unit = {},
|
||||
groupOnClick: (groupId: String) -> Unit = {},
|
||||
onKeyboardAction: () -> Unit = {},
|
||||
) {
|
||||
@@ -42,10 +44,10 @@ fun ResultViewPage(
|
||||
Spacer(modifier = Modifier.height(26.dp))
|
||||
|
||||
Preset(
|
||||
selectedNotificationPreset = selectedNotificationPreset,
|
||||
selectedFullContentParsePreset = selectedFullContentParsePreset,
|
||||
notificationPresetOnClick = notificationPresetOnClick,
|
||||
fullContentParsePresetOnClick = fullContentParsePresetOnClick,
|
||||
selectedAllowNotificationPreset = selectedAllowNotificationPreset,
|
||||
selectedParseFullContentPreset = selectedParseFullContentPreset,
|
||||
allowNotificationPresetOnClick = allowNotificationPresetOnClick,
|
||||
parseFullContentPresetOnClick = parseFullContentPresetOnClick,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(26.dp))
|
||||
|
||||
@@ -78,12 +80,12 @@ private fun Link(
|
||||
|
||||
@Composable
|
||||
private fun Preset(
|
||||
selectedNotificationPreset: Boolean = false,
|
||||
selectedFullContentParsePreset: Boolean = false,
|
||||
notificationPresetOnClick: () -> Unit = {},
|
||||
fullContentParsePresetOnClick: () -> Unit = {},
|
||||
selectedAllowNotificationPreset: Boolean = false,
|
||||
selectedParseFullContentPreset: Boolean = false,
|
||||
allowNotificationPresetOnClick: () -> Unit = {},
|
||||
parseFullContentPresetOnClick: () -> Unit = {},
|
||||
) {
|
||||
Subtitle(text = "预设")
|
||||
Subtitle(text = stringResource(R.string.preset))
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
FlowRow(
|
||||
mainAxisAlignment = MainAxisAlignment.Start,
|
||||
@@ -92,35 +94,35 @@ private fun Preset(
|
||||
) {
|
||||
SelectionChip(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
content = "接收通知",
|
||||
selected = selectedNotificationPreset,
|
||||
content = stringResource(R.string.allow_notification),
|
||||
selected = selectedAllowNotificationPreset,
|
||||
selectedIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Notifications,
|
||||
contentDescription = "Check",
|
||||
contentDescription = stringResource(R.string.allow_notification),
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.size(18.dp),
|
||||
)
|
||||
},
|
||||
) {
|
||||
notificationPresetOnClick()
|
||||
allowNotificationPresetOnClick()
|
||||
}
|
||||
SelectionChip(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
content = "全文解析",
|
||||
selected = selectedFullContentParsePreset,
|
||||
content = stringResource(R.string.parse_full_content),
|
||||
selected = selectedParseFullContentPreset,
|
||||
selectedIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Article,
|
||||
contentDescription = "Check",
|
||||
contentDescription = stringResource(R.string.parse_full_content),
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.size(18.dp),
|
||||
)
|
||||
},
|
||||
) {
|
||||
fullContentParsePresetOnClick()
|
||||
parseFullContentPresetOnClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +134,7 @@ private fun AddToGroup(
|
||||
groupOnClick: (groupId: String) -> Unit = {},
|
||||
onKeyboardAction: () -> Unit = {},
|
||||
) {
|
||||
Subtitle(text = "添加到组")
|
||||
Subtitle(text = stringResource(R.string.add_to_group))
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
FlowRow(
|
||||
mainAxisAlignment = MainAxisAlignment.Start,
|
||||
@@ -151,7 +153,7 @@ private fun AddToGroup(
|
||||
|
||||
SelectionEditorChip(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
content = "新建分组",
|
||||
content = stringResource(R.string.new_group),
|
||||
selected = false,
|
||||
onKeyboardAction = onKeyboardAction,
|
||||
) {
|
||||
|
||||
@@ -22,11 +22,17 @@ 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.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.google.accompanist.pager.PagerState
|
||||
import kotlinx.coroutines.delay
|
||||
import me.ash.reader.R
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
fun SearchViewPage(
|
||||
pagerState: PagerState,
|
||||
inputContent: String = "",
|
||||
errorMessage: String = "",
|
||||
onValueChange: (String) -> Unit = {},
|
||||
@@ -51,11 +57,11 @@ fun SearchViewPage(
|
||||
),
|
||||
value = inputContent,
|
||||
onValueChange = {
|
||||
onValueChange(it)
|
||||
if (pagerState.currentPage == 0) onValueChange(it)
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "订阅源或站点链接",
|
||||
text = stringResource(R.string.feed_or_site_url),
|
||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f)
|
||||
)
|
||||
},
|
||||
@@ -68,7 +74,7 @@ fun SearchViewPage(
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Close,
|
||||
contentDescription = "Clear",
|
||||
contentDescription = stringResource(R.string.clear),
|
||||
tint = MaterialTheme.colorScheme.outline.copy(alpha = 0.5f),
|
||||
)
|
||||
}
|
||||
@@ -77,7 +83,7 @@ fun SearchViewPage(
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.ContentPaste,
|
||||
contentDescription = "Paste",
|
||||
contentDescription = stringResource(R.string.paste),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,12 +11,11 @@ import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
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.*
|
||||
import me.ash.reader.R
|
||||
import me.ash.reader.ui.extension.collectAsStateValue
|
||||
import me.ash.reader.ui.widget.Dialog
|
||||
import java.io.InputStream
|
||||
@@ -61,14 +60,14 @@ fun SubscribeDialog(
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.RssFeed,
|
||||
contentDescription = "Subscribe",
|
||||
contentDescription = stringResource(R.string.subscribe),
|
||||
)
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
when (viewState.pagerState.currentPage) {
|
||||
0 -> "订阅"
|
||||
else -> viewState.feed?.name ?: "未知"
|
||||
0 -> stringResource(R.string.subscribe)
|
||||
else -> viewState.feed?.name ?: stringResource(R.string.unknown)
|
||||
}
|
||||
)
|
||||
},
|
||||
@@ -88,15 +87,15 @@ fun SubscribeDialog(
|
||||
},
|
||||
link = viewState.inputContent,
|
||||
groups = groupsState.value,
|
||||
selectedNotificationPreset = viewState.notificationPreset,
|
||||
selectedFullContentParsePreset = viewState.fullContentParsePreset,
|
||||
selectedAllowNotificationPreset = viewState.allowNotificationPreset,
|
||||
selectedParseFullContentPreset = viewState.parseFullContentPreset,
|
||||
selectedGroupId = viewState.selectedGroupId,
|
||||
pagerState = viewState.pagerState,
|
||||
notificationPresetOnClick = {
|
||||
viewModel.dispatch(SubscribeViewAction.ChangeNotificationPreset)
|
||||
allowNotificationPresetOnClick = {
|
||||
viewModel.dispatch(SubscribeViewAction.ChangeAllowNotificationPreset)
|
||||
},
|
||||
fullContentParsePresetOnClick = {
|
||||
viewModel.dispatch(SubscribeViewAction.ChangeFullContentParsePreset)
|
||||
parseFullContentPresetOnClick = {
|
||||
viewModel.dispatch(SubscribeViewAction.ChangeParseFullContentPreset)
|
||||
},
|
||||
groupOnClick = {
|
||||
viewModel.dispatch(SubscribeViewAction.SelectedGroup(it))
|
||||
@@ -116,7 +115,7 @@ fun SubscribeDialog(
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = "搜索",
|
||||
text = stringResource(R.string.search),
|
||||
color = if (viewState.inputContent.isNotEmpty()) {
|
||||
Color.Unspecified
|
||||
} else {
|
||||
@@ -131,7 +130,7 @@ fun SubscribeDialog(
|
||||
viewModel.dispatch(SubscribeViewAction.Subscribe)
|
||||
}
|
||||
) {
|
||||
Text("订阅")
|
||||
Text(stringResource(R.string.subscribe))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,7 +144,7 @@ fun SubscribeDialog(
|
||||
viewModel.dispatch(SubscribeViewAction.Hide)
|
||||
}
|
||||
) {
|
||||
Text("导入OPML文件")
|
||||
Text(text = stringResource(R.string.import_from_opml))
|
||||
}
|
||||
}
|
||||
1 -> {
|
||||
@@ -154,7 +153,7 @@ fun SubscribeDialog(
|
||||
viewModel.dispatch(SubscribeViewAction.Hide)
|
||||
}
|
||||
) {
|
||||
Text("取消")
|
||||
Text(text = stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-26
@@ -9,12 +9,13 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import me.ash.reader.R
|
||||
import me.ash.reader.data.article.Article
|
||||
import me.ash.reader.data.constant.Symbol
|
||||
import me.ash.reader.data.feed.Feed
|
||||
import me.ash.reader.data.group.Group
|
||||
import me.ash.reader.data.repository.RssHelper
|
||||
import me.ash.reader.data.repository.RssRepository
|
||||
import me.ash.reader.data.repository.StringsRepository
|
||||
import me.ash.reader.formatUrl
|
||||
import me.ash.reader.ui.extension.animateScrollToPage
|
||||
import javax.inject.Inject
|
||||
@@ -24,9 +25,10 @@ import javax.inject.Inject
|
||||
class SubscribeViewModel @Inject constructor(
|
||||
private val rssRepository: RssRepository,
|
||||
private val rssHelper: RssHelper,
|
||||
private val stringsRepository: StringsRepository,
|
||||
) : ViewModel() {
|
||||
private val _viewState = MutableStateFlow(SubScribeViewState())
|
||||
val viewState: StateFlow<SubScribeViewState> = _viewState.asStateFlow()
|
||||
private val _viewState = MutableStateFlow(SubscribeViewState())
|
||||
val viewState: StateFlow<SubscribeViewState> = _viewState.asStateFlow()
|
||||
|
||||
fun dispatch(action: SubscribeViewAction) {
|
||||
when (action) {
|
||||
@@ -36,10 +38,10 @@ class SubscribeViewModel @Inject constructor(
|
||||
is SubscribeViewAction.Hide -> changeVisible(false)
|
||||
is SubscribeViewAction.Input -> inputLink(action.content)
|
||||
is SubscribeViewAction.Search -> search(action.scope)
|
||||
is SubscribeViewAction.ChangeNotificationPreset ->
|
||||
changeNotificationPreset()
|
||||
is SubscribeViewAction.ChangeFullContentParsePreset ->
|
||||
changeFullContentParsePreset()
|
||||
is SubscribeViewAction.ChangeAllowNotificationPreset ->
|
||||
changeAllowNotificationPreset()
|
||||
is SubscribeViewAction.ChangeParseFullContentPreset ->
|
||||
changeParseFullContentPreset()
|
||||
is SubscribeViewAction.SelectedGroup -> selectedGroup(action.groupId)
|
||||
is SubscribeViewAction.Subscribe -> subscribe()
|
||||
}
|
||||
@@ -48,6 +50,7 @@ class SubscribeViewModel @Inject constructor(
|
||||
private fun init() {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
title = stringsRepository.getString(R.string.subscribe),
|
||||
groups = rssRepository.get().pullGroups()
|
||||
)
|
||||
}
|
||||
@@ -57,13 +60,13 @@ class SubscribeViewModel @Inject constructor(
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
visible = false,
|
||||
title = "订阅",
|
||||
title = stringsRepository.getString(R.string.subscribe),
|
||||
errorMessage = "",
|
||||
inputContent = "",
|
||||
feed = null,
|
||||
articles = emptyList(),
|
||||
notificationPreset = false,
|
||||
fullContentParsePreset = false,
|
||||
allowNotificationPreset = false,
|
||||
parseFullContentPreset = false,
|
||||
selectedGroupId = "",
|
||||
groups = emptyFlow(),
|
||||
)
|
||||
@@ -78,8 +81,8 @@ class SubscribeViewModel @Inject constructor(
|
||||
rssRepository.get().subscribe(
|
||||
feed.copy(
|
||||
groupId = groupId,
|
||||
isNotification = _viewState.value.notificationPreset,
|
||||
isFullContent = _viewState.value.fullContentParsePreset,
|
||||
isNotification = _viewState.value.allowNotificationPreset,
|
||||
isFullContent = _viewState.value.parseFullContentPreset,
|
||||
), articles
|
||||
)
|
||||
changeVisible(false)
|
||||
@@ -94,18 +97,18 @@ class SubscribeViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeFullContentParsePreset() {
|
||||
private fun changeParseFullContentPreset() {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
fullContentParsePreset = !_viewState.value.fullContentParsePreset
|
||||
parseFullContentPreset = !_viewState.value.parseFullContentPreset
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeNotificationPreset() {
|
||||
private fun changeAllowNotificationPreset() {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
notificationPreset = !_viewState.value.notificationPreset
|
||||
allowNotificationPreset = !_viewState.value.allowNotificationPreset
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -122,7 +125,7 @@ class SubscribeViewModel @Inject constructor(
|
||||
}
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
title = "搜索中",
|
||||
title = stringsRepository.getString(R.string.searching),
|
||||
)
|
||||
}
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
@@ -130,7 +133,7 @@ class SubscribeViewModel @Inject constructor(
|
||||
if (rssRepository.get().isExist(_viewState.value.inputContent)) {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
errorMessage = "已订阅",
|
||||
errorMessage = stringsRepository.getString(R.string.already_subscribed),
|
||||
)
|
||||
}
|
||||
return@launch
|
||||
@@ -147,8 +150,8 @@ class SubscribeViewModel @Inject constructor(
|
||||
e.printStackTrace()
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
title = "订阅",
|
||||
errorMessage = e.message ?: Symbol.Unknown,
|
||||
title = stringsRepository.getString(R.string.subscribe),
|
||||
errorMessage = e.message ?: stringsRepository.getString(R.string.unknown),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -173,15 +176,15 @@ class SubscribeViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
data class SubScribeViewState(
|
||||
data class SubscribeViewState(
|
||||
val visible: Boolean = false,
|
||||
val title: String = "订阅",
|
||||
val title: String = "",
|
||||
val errorMessage: String = "",
|
||||
val inputContent: String = "",
|
||||
val feed: Feed? = null,
|
||||
val articles: List<Article> = emptyList(),
|
||||
val notificationPreset: Boolean = false,
|
||||
val fullContentParsePreset: Boolean = false,
|
||||
val allowNotificationPreset: Boolean = false,
|
||||
val parseFullContentPreset: Boolean = false,
|
||||
val selectedGroupId: String = "",
|
||||
val groups: Flow<List<Group>> = emptyFlow(),
|
||||
val pagerState: PagerState = PagerState(),
|
||||
@@ -202,8 +205,8 @@ sealed class SubscribeViewAction {
|
||||
val scope: CoroutineScope,
|
||||
) : SubscribeViewAction()
|
||||
|
||||
object ChangeNotificationPreset : SubscribeViewAction()
|
||||
object ChangeFullContentParsePreset : SubscribeViewAction()
|
||||
object ChangeAllowNotificationPreset : SubscribeViewAction()
|
||||
object ChangeParseFullContentPreset : SubscribeViewAction()
|
||||
|
||||
data class SelectedGroup(
|
||||
val groupId: String
|
||||
|
||||
@@ -19,12 +19,12 @@ fun SubscribeViewPager(
|
||||
onSearchKeyboardAction: () -> Unit = {},
|
||||
link: String = "",
|
||||
groups: List<Group> = emptyList(),
|
||||
selectedNotificationPreset: Boolean = false,
|
||||
selectedFullContentParsePreset: Boolean = false,
|
||||
selectedAllowNotificationPreset: Boolean = false,
|
||||
selectedParseFullContentPreset: Boolean = false,
|
||||
selectedGroupId: String = "",
|
||||
pagerState: PagerState = com.google.accompanist.pager.rememberPagerState(),
|
||||
notificationPresetOnClick: () -> Unit = {},
|
||||
fullContentParsePresetOnClick: () -> Unit = {},
|
||||
allowNotificationPresetOnClick: () -> Unit = {},
|
||||
parseFullContentPresetOnClick: () -> Unit = {},
|
||||
groupOnClick: (groupId: String) -> Unit = {},
|
||||
onResultKeyboardAction: () -> Unit = {},
|
||||
) {
|
||||
@@ -35,6 +35,7 @@ fun SubscribeViewPager(
|
||||
composableList = listOf(
|
||||
{
|
||||
SearchViewPage(
|
||||
pagerState = pagerState,
|
||||
inputContent = inputContent,
|
||||
errorMessage = errorMessage,
|
||||
onValueChange = onValueChange,
|
||||
@@ -45,11 +46,11 @@ fun SubscribeViewPager(
|
||||
ResultViewPage(
|
||||
link = link,
|
||||
groups = groups,
|
||||
selectedNotificationPreset = selectedNotificationPreset,
|
||||
selectedFullContentParsePreset = selectedFullContentParsePreset,
|
||||
selectedAllowNotificationPreset = selectedAllowNotificationPreset,
|
||||
selectedParseFullContentPreset = selectedParseFullContentPreset,
|
||||
selectedGroupId = selectedGroupId,
|
||||
notificationPresetOnClick = notificationPresetOnClick,
|
||||
fullContentParsePresetOnClick = fullContentParsePresetOnClick,
|
||||
allowNotificationPresetOnClick = allowNotificationPresetOnClick,
|
||||
parseFullContentPresetOnClick = parseFullContentPresetOnClick,
|
||||
groupOnClick = groupOnClick,
|
||||
onKeyboardAction = onResultKeyboardAction,
|
||||
)
|
||||
|
||||
@@ -12,11 +12,11 @@ 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.platform.LocalContext
|
||||
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
|
||||
import me.ash.reader.formatToString
|
||||
|
||||
@Composable
|
||||
fun ArticleItem(
|
||||
@@ -24,6 +24,7 @@ fun ArticleItem(
|
||||
articleWithFeed: ArticleWithFeed,
|
||||
onClick: (ArticleWithFeed) -> Unit = {},
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
@@ -44,7 +45,7 @@ fun ArticleItem(
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
Text(
|
||||
text = articleWithFeed.article.date.toString(DateTimeExt.HH_MM),
|
||||
text = articleWithFeed.article.date.formatToString(context, onlyHourMinute = true),
|
||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package me.ash.reader.ui.page.home.flow
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
@@ -8,9 +9,8 @@ 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.formatToString
|
||||
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
|
||||
@@ -18,6 +18,7 @@ import me.ash.reader.ui.page.home.read.ReadViewModel
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
fun LazyListScope.generateArticleList(
|
||||
context: Context,
|
||||
pagingItems: LazyPagingItems<ArticleWithFeed>?,
|
||||
readViewModel: ReadViewModel,
|
||||
homeViewModel: HomeViewModel,
|
||||
@@ -27,8 +28,7 @@ fun LazyListScope.generateArticleList(
|
||||
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)
|
||||
val currentItemDay = currentItem.article.date.formatToString(context)
|
||||
if (lastItemDay != currentItemDay) {
|
||||
if (itemIndex != 0) {
|
||||
item { Spacer(modifier = Modifier.height(40.dp)) }
|
||||
|
||||
@@ -14,13 +14,17 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.R
|
||||
import me.ash.reader.ui.extension.collectAsStateValue
|
||||
import me.ash.reader.ui.extension.getName
|
||||
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
|
||||
@@ -37,6 +41,7 @@ fun FlowPage(
|
||||
homeViewModel: HomeViewModel = hiltViewModel(),
|
||||
readViewModel: ReadViewModel = hiltViewModel(),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val viewState = viewModel.viewState.collectAsStateValue()
|
||||
val filterState = homeViewModel.filterState.collectAsStateValue()
|
||||
@@ -66,7 +71,7 @@ fun FlowPage(
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
contentDescription = stringResource(R.string.back),
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
@@ -75,14 +80,14 @@ fun FlowPage(
|
||||
IconButton(onClick = {}) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.DoneAll,
|
||||
contentDescription = "Read All",
|
||||
contentDescription = stringResource(R.string.mark_all_as_read),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
IconButton(onClick = {}) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Search,
|
||||
contentDescription = "Search",
|
||||
contentDescription = stringResource(R.string.search),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
@@ -106,7 +111,7 @@ fun FlowPage(
|
||||
text = when {
|
||||
filterState.group != null -> filterState.group.name
|
||||
filterState.feed != null -> filterState.feed.name
|
||||
else -> filterState.filter.name
|
||||
else -> filterState.filter.getName()
|
||||
},
|
||||
style = MaterialTheme.typography.displaySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
@@ -114,7 +119,7 @@ fun FlowPage(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
generateArticleList(pagingItems, readViewModel, homeViewModel, scope)
|
||||
generateArticleList(context, pagingItems, readViewModel, homeViewModel, scope)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -11,10 +11,9 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
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.data.article.Article
|
||||
import me.ash.reader.data.feed.Feed
|
||||
import me.ash.reader.formatToString
|
||||
import me.ash.reader.ui.extension.roundClick
|
||||
|
||||
@Composable
|
||||
@@ -34,7 +33,7 @@ fun Header(
|
||||
) {
|
||||
Column(modifier = Modifier.padding(10.dp)) {
|
||||
Text(
|
||||
text = article.date.toString(DateTimeExt.YYYY_MM_DD_HH_MM, true),
|
||||
text = article.date.formatToString(context, atHourMinute = true),
|
||||
fontSize = 12.sp,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
fontWeight = FontWeight.Medium,
|
||||
|
||||
@@ -13,7 +13,9 @@ import androidx.compose.material3.SmallTopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import me.ash.reader.R
|
||||
|
||||
@Composable
|
||||
fun ReadPageTopBar(
|
||||
@@ -30,7 +32,7 @@ fun ReadPageTopBar(
|
||||
Icon(
|
||||
modifier = Modifier.size(28.dp),
|
||||
imageVector = Icons.Rounded.Close,
|
||||
contentDescription = "Back",
|
||||
contentDescription = stringResource(R.string.back),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,10 +14,12 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavHostController
|
||||
import me.ash.reader.R
|
||||
import me.ash.reader.ui.extension.paddingFixedHorizontal
|
||||
import me.ash.reader.ui.extension.roundClick
|
||||
import me.ash.reader.ui.widget.TopTitleBox
|
||||
@@ -49,7 +51,7 @@ fun SettingsPage(
|
||||
IconButton(onClick = { navController.popBackStack() }) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.ArrowBackIosNew,
|
||||
contentDescription = "Back",
|
||||
contentDescription = stringResource(R.string.back),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import me.ash.reader.R
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
@@ -33,7 +35,7 @@ fun SelectionChip(
|
||||
selectedIcon: @Composable () -> Unit = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Check,
|
||||
contentDescription = "Check",
|
||||
contentDescription = stringResource(R.string.selected),
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.size(18.dp)
|
||||
@@ -87,7 +89,7 @@ fun SelectionEditorChip(
|
||||
selectedIcon: @Composable () -> Unit = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Check,
|
||||
contentDescription = "Check",
|
||||
contentDescription = stringResource(R.string.selected),
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.size(16.dp)
|
||||
|
||||
Reference in New Issue
Block a user