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