Use localized language
This commit is contained in:
parent
e9f7ac85ab
commit
582e0f8148
|
@ -21,9 +21,9 @@
|
|||
- [x] 全文解析
|
||||
- [x] 过滤未读、星标
|
||||
- [x] Feed 分组
|
||||
- [x] 本地化
|
||||
- [ ] 文章搜索
|
||||
- [ ] 偏好设置
|
||||
- [ ] 本地化
|
||||
- [ ] 发布 APK
|
||||
- [ ] 小组件
|
||||
- [ ] ...
|
||||
|
|
|
@ -21,9 +21,9 @@ The following are the progress made so far and the goals to be worked on in the
|
|||
- [x] Full Content Parsing
|
||||
- [x] Filter unread and starred
|
||||
- [x] Feed Grouping
|
||||
- [x] Localization
|
||||
- [ ] Search for articles
|
||||
- [ ] Preference settings
|
||||
- [ ] Localization
|
||||
- [ ] Release APK
|
||||
- [ ] Widget
|
||||
- [ ] ...
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
android:name=".App"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:label="@string/read_you"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
|
|
|
@ -26,6 +26,9 @@ class App : Application() {
|
|||
@Inject
|
||||
lateinit var rssHelper: RssHelper
|
||||
|
||||
@Inject
|
||||
lateinit var stringsRepository: StringsRepository
|
||||
|
||||
@Inject
|
||||
lateinit var accountRepository: AccountRepository
|
||||
|
||||
|
|
42
app/src/main/java/me/ash/reader/DateExt.kt
Normal file
42
app/src/main/java/me/ash/reader/DateExt.kt
Normal file
|
@ -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)
|
||||
}
|
20
app/src/main/java/me/ash/reader/ui/extension/FilterExt.kt
Normal file
20
app/src/main/java/me/ash/reader/ui/extension/FilterExt.kt
Normal file
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
41
app/src/main/res/values-zh-rCN/strings.xml
Normal file
41
app/src/main/res/values-zh-rCN/strings.xml
Normal file
|
@ -0,0 +1,41 @@
|
|||
<resources>
|
||||
<string name="read_you">Read You</string>
|
||||
<string name="all">全部</string>
|
||||
<string name="all_desc">共 %1$d 项</string>
|
||||
<string name="unread">未读</string>
|
||||
<string name="unread_desc">%1$d 项未读</string>
|
||||
<string name="starred">已加星标</string>
|
||||
<string name="starred_desc">%1$d 项已加星标</string>
|
||||
<string name="feeds">分组</string>
|
||||
<string name="expand_less">收缩</string>
|
||||
<string name="expand_more">展开</string>
|
||||
<string name="confirm">确认</string>
|
||||
<string name="cancel">取消</string>
|
||||
<string name="defaults">默认</string>
|
||||
<string name="unknown">未知</string>
|
||||
<string name="back">返回</string>
|
||||
<string name="go_to">转到</string>
|
||||
<string name="refresh">刷新</string>
|
||||
<string name="search">搜索</string>
|
||||
<string name="searching">搜索中…</string>
|
||||
<string name="subscribe">订阅</string>
|
||||
<string name="already_subscribed">已有订阅</string>
|
||||
<string name="clear">清空</string>
|
||||
<string name="paste">粘贴</string>
|
||||
<string name="feed_or_site_url">订阅源或站点链接</string>
|
||||
<string name="import_from_opml">导入 OPML 文件</string>
|
||||
<string name="preset">预设</string>
|
||||
<string name="selected">已选择</string>
|
||||
<string name="allow_notification">允许通知</string>
|
||||
<string name="parse_full_content">全文解析</string>
|
||||
<string name="add_to_group">添加到组</string>
|
||||
<string name="new_group">新建分组</string>
|
||||
<string name="today">今天</string>
|
||||
<string name="yesterday">昨天</string>
|
||||
<string name="date_at_time">%1$s %2$s</string>
|
||||
<string name="mark_as_read">标记为已读</string>
|
||||
<string name="mark_all_as_read">全部标记为已读</string>
|
||||
<string name="mark_as_unread">标记为未读</string>
|
||||
<string name="mark_as_starred">标记为已加星标</string>
|
||||
<string name="mark_as_unstar">标记为未加星标</string>
|
||||
</resources>
|
|
@ -1,3 +1,41 @@
|
|||
<resources>
|
||||
<string name="app_name">Reader</string>
|
||||
<string name="read_you">Read You</string>
|
||||
<string name="all">All</string>
|
||||
<string name="all_desc">%1$d All Items</string>
|
||||
<string name="unread">Unread</string>
|
||||
<string name="unread_desc"> %1$d Unread Items</string>
|
||||
<string name="starred">Starred</string>
|
||||
<string name="starred_desc">%1$d Starred Items</string>
|
||||
<string name="feeds">Feeds</string>
|
||||
<string name="expand_less">Expand Less</string>
|
||||
<string name="expand_more">Expand More</string>
|
||||
<string name="confirm">Confirm</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="defaults">Default</string>
|
||||
<string name="unknown">Unknown</string>
|
||||
<string name="back">Back</string>
|
||||
<string name="go_to">Goto</string>
|
||||
<string name="refresh">Refresh</string>
|
||||
<string name="search">Search</string>
|
||||
<string name="searching">Searching…</string>
|
||||
<string name="subscribe">Subscribe</string>
|
||||
<string name="already_subscribed">Already subscribed</string>
|
||||
<string name="clear">Clear</string>
|
||||
<string name="paste">Paste</string>
|
||||
<string name="feed_or_site_url">Feed or Site URL</string>
|
||||
<string name="import_from_opml">Import from OPML</string>
|
||||
<string name="preset">Preset</string>
|
||||
<string name="selected">Selected</string>
|
||||
<string name="allow_notification">Allow Notification</string>
|
||||
<string name="parse_full_content">Parse Full Content</string>
|
||||
<string name="add_to_group">Add to Group</string>
|
||||
<string name="new_group">New Group</string>
|
||||
<string name="today">Today</string>
|
||||
<string name="yesterday">Yesterday</string>
|
||||
<string name="date_at_time">%1$s At %2$s</string>
|
||||
<string name="mark_as_read">Mark as Read</string>
|
||||
<string name="mark_all_as_read">Mark All as Read</string>
|
||||
<string name="mark_as_unread">Mark as Unread</string>
|
||||
<string name="mark_as_starred">Mark as Starred</string>
|
||||
<string name="mark_as_unstar">Mark as Unstar</string>
|
||||
</resources>
|
Loading…
Reference in New Issue
Block a user