Optimize the flow page

This commit is contained in:
Ash 2022-05-24 17:47:24 +08:00
parent ba3620d84f
commit 66094f8075
25 changed files with 230 additions and 199 deletions

View File

@ -0,0 +1,10 @@
package me.ash.reader.data.constant
object ElevationTokens {
const val Level0 = 0
const val Level1 = 1
const val Level2 = 3
const val Level3 = 6
const val Level4 = 8
const val Level5 = 12
}

View File

@ -1,9 +1,6 @@
package me.ash.reader.data.entity package me.ash.reader.data.entity
import androidx.room.ColumnInfo import androidx.room.*
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import java.util.* import java.util.*
@Entity( @Entity(
@ -45,4 +42,7 @@ data class Article(
var isStarred: Boolean = false, var isStarred: Boolean = false,
@ColumnInfo(defaultValue = "false") @ColumnInfo(defaultValue = "false")
var isReadLater: Boolean = false, var isReadLater: Boolean = false,
) ) {
@Ignore
var dateString: String? = null
}

View File

@ -4,17 +4,18 @@ import android.content.Context
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.ash.reader.data.constant.ElevationTokens
import me.ash.reader.ui.ext.DataStoreKeys import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put import me.ash.reader.ui.ext.put
sealed class FeedsFilterBarTonalElevationPreference(val value: Int) : Preference() { sealed class FeedsFilterBarTonalElevationPreference(val value: Int) : Preference() {
object Level0 : FeedsFilterBarTonalElevationPreference(0) object Level0 : FeedsFilterBarTonalElevationPreference(ElevationTokens.Level0)
object Level1 : FeedsFilterBarTonalElevationPreference(1) object Level1 : FeedsFilterBarTonalElevationPreference(ElevationTokens.Level1)
object Level2 : FeedsFilterBarTonalElevationPreference(3) object Level2 : FeedsFilterBarTonalElevationPreference(ElevationTokens.Level2)
object Level3 : FeedsFilterBarTonalElevationPreference(6) object Level3 : FeedsFilterBarTonalElevationPreference(ElevationTokens.Level3)
object Level4 : FeedsFilterBarTonalElevationPreference(8) object Level4 : FeedsFilterBarTonalElevationPreference(ElevationTokens.Level4)
object Level5 : FeedsFilterBarTonalElevationPreference(12) object Level5 : FeedsFilterBarTonalElevationPreference(ElevationTokens.Level5)
override fun put(context: Context, scope: CoroutineScope) { override fun put(context: Context, scope: CoroutineScope) {
scope.launch { scope.launch {
@ -27,12 +28,12 @@ sealed class FeedsFilterBarTonalElevationPreference(val value: Int) : Preference
fun getDesc(context: Context): String = fun getDesc(context: Context): String =
when (this) { when (this) {
Level0 -> "Level 0 (0dp)" Level0 -> "Level 0 (${ElevationTokens.Level0}dp)"
Level1 -> "Level 1 (1dp)" Level1 -> "Level 1 (${ElevationTokens.Level1}dp)"
Level2 -> "Level 2 (3dp)" Level2 -> "Level 2 (${ElevationTokens.Level2}dp)"
Level3 -> "Level 3 (6dp)" Level3 -> "Level 3 (${ElevationTokens.Level3}dp)"
Level4 -> "Level 4 (8dp)" Level4 -> "Level 4 (${ElevationTokens.Level4}dp)"
Level5 -> "Level 5 (12dp)" Level5 -> "Level 5 (${ElevationTokens.Level5}dp)"
} }
companion object { companion object {
@ -41,13 +42,14 @@ sealed class FeedsFilterBarTonalElevationPreference(val value: Int) : Preference
fun fromPreferences(preferences: Preferences) = fun fromPreferences(preferences: Preferences) =
when (preferences[DataStoreKeys.FeedsFilterBarTonalElevation.key]) { when (preferences[DataStoreKeys.FeedsFilterBarTonalElevation.key]) {
0 -> Level0 ElevationTokens.Level0 -> Level0
1 -> Level1 ElevationTokens.Level1 -> Level1
3 -> Level2 ElevationTokens.Level2 -> Level2
6 -> Level3 ElevationTokens.Level3 -> Level3
8 -> Level4 ElevationTokens.Level4 -> Level4
12 -> Level5 ElevationTokens.Level5 -> Level5
else -> default else -> default
} }
} }
} }

View File

@ -4,17 +4,18 @@ import android.content.Context
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.ash.reader.data.constant.ElevationTokens
import me.ash.reader.ui.ext.DataStoreKeys import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put import me.ash.reader.ui.ext.put
sealed class FeedsGroupListTonalElevationPreference(val value: Int) : Preference() { sealed class FeedsGroupListTonalElevationPreference(val value: Int) : Preference() {
object Level0 : FeedsGroupListTonalElevationPreference(0) object Level0 : FeedsGroupListTonalElevationPreference(ElevationTokens.Level0)
object Level1 : FeedsGroupListTonalElevationPreference(1) object Level1 : FeedsGroupListTonalElevationPreference(ElevationTokens.Level1)
object Level2 : FeedsGroupListTonalElevationPreference(3) object Level2 : FeedsGroupListTonalElevationPreference(ElevationTokens.Level2)
object Level3 : FeedsGroupListTonalElevationPreference(6) object Level3 : FeedsGroupListTonalElevationPreference(ElevationTokens.Level3)
object Level4 : FeedsGroupListTonalElevationPreference(8) object Level4 : FeedsGroupListTonalElevationPreference(ElevationTokens.Level4)
object Level5 : FeedsGroupListTonalElevationPreference(12) object Level5 : FeedsGroupListTonalElevationPreference(ElevationTokens.Level5)
override fun put(context: Context, scope: CoroutineScope) { override fun put(context: Context, scope: CoroutineScope) {
scope.launch { scope.launch {
@ -27,12 +28,12 @@ sealed class FeedsGroupListTonalElevationPreference(val value: Int) : Preference
fun getDesc(context: Context): String = fun getDesc(context: Context): String =
when (this) { when (this) {
Level0 -> "Level 0 (0dp)" Level0 -> "Level 0 (${ElevationTokens.Level0}dp)"
Level1 -> "Level 1 (1dp)" Level1 -> "Level 1 (${ElevationTokens.Level1}dp)"
Level2 -> "Level 2 (3dp)" Level2 -> "Level 2 (${ElevationTokens.Level2}dp)"
Level3 -> "Level 3 (6dp)" Level3 -> "Level 3 (${ElevationTokens.Level3}dp)"
Level4 -> "Level 4 (8dp)" Level4 -> "Level 4 (${ElevationTokens.Level4}dp)"
Level5 -> "Level 5 (12dp)" Level5 -> "Level 5 (${ElevationTokens.Level5}dp)"
} }
companion object { companion object {
@ -41,12 +42,12 @@ sealed class FeedsGroupListTonalElevationPreference(val value: Int) : Preference
fun fromPreferences(preferences: Preferences) = fun fromPreferences(preferences: Preferences) =
when (preferences[DataStoreKeys.FeedsGroupListTonalElevation.key]) { when (preferences[DataStoreKeys.FeedsGroupListTonalElevation.key]) {
0 -> Level0 ElevationTokens.Level0 -> Level0
1 -> Level1 ElevationTokens.Level1 -> Level1
3 -> Level2 ElevationTokens.Level2 -> Level2
6 -> Level3 ElevationTokens.Level3 -> Level3
8 -> Level4 ElevationTokens.Level4 -> Level4
12 -> Level5 ElevationTokens.Level5 -> Level5
else -> default else -> default
} }
} }

View File

@ -4,17 +4,18 @@ import android.content.Context
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.ash.reader.data.constant.ElevationTokens
import me.ash.reader.ui.ext.DataStoreKeys import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put import me.ash.reader.ui.ext.put
sealed class FeedsTopBarTonalElevationPreference(val value: Int) : Preference() { sealed class FeedsTopBarTonalElevationPreference(val value: Int) : Preference() {
object Level0 : FeedsTopBarTonalElevationPreference(0) object Level0 : FeedsTopBarTonalElevationPreference(ElevationTokens.Level0)
object Level1 : FeedsTopBarTonalElevationPreference(1) object Level1 : FeedsTopBarTonalElevationPreference(ElevationTokens.Level1)
object Level2 : FeedsTopBarTonalElevationPreference(3) object Level2 : FeedsTopBarTonalElevationPreference(ElevationTokens.Level2)
object Level3 : FeedsTopBarTonalElevationPreference(6) object Level3 : FeedsTopBarTonalElevationPreference(ElevationTokens.Level3)
object Level4 : FeedsTopBarTonalElevationPreference(8) object Level4 : FeedsTopBarTonalElevationPreference(ElevationTokens.Level4)
object Level5 : FeedsTopBarTonalElevationPreference(12) object Level5 : FeedsTopBarTonalElevationPreference(ElevationTokens.Level5)
override fun put(context: Context, scope: CoroutineScope) { override fun put(context: Context, scope: CoroutineScope) {
scope.launch { scope.launch {
@ -27,12 +28,12 @@ sealed class FeedsTopBarTonalElevationPreference(val value: Int) : Preference()
fun getDesc(context: Context): String = fun getDesc(context: Context): String =
when (this) { when (this) {
Level0 -> "Level 0 (0dp)" Level0 -> "Level 0 (${ElevationTokens.Level0}dp)"
Level1 -> "Level 1 (1dp)" Level1 -> "Level 1 (${ElevationTokens.Level1}dp)"
Level2 -> "Level 2 (3dp)" Level2 -> "Level 2 (${ElevationTokens.Level2}dp)"
Level3 -> "Level 3 (6dp)" Level3 -> "Level 3 (${ElevationTokens.Level3}dp)"
Level4 -> "Level 4 (8dp)" Level4 -> "Level 4 (${ElevationTokens.Level4}dp)"
Level5 -> "Level 5 (12dp)" Level5 -> "Level 5 (${ElevationTokens.Level5}dp)"
} }
companion object { companion object {
@ -41,12 +42,12 @@ sealed class FeedsTopBarTonalElevationPreference(val value: Int) : Preference()
fun fromPreferences(preferences: Preferences) = fun fromPreferences(preferences: Preferences) =
when (preferences[DataStoreKeys.FeedsTopBarTonalElevation.key]) { when (preferences[DataStoreKeys.FeedsTopBarTonalElevation.key]) {
0 -> Level0 ElevationTokens.Level0 -> Level0
1 -> Level1 ElevationTokens.Level1 -> Level1
3 -> Level2 ElevationTokens.Level2 -> Level2
6 -> Level3 ElevationTokens.Level3 -> Level3
8 -> Level4 ElevationTokens.Level4 -> Level4
12 -> Level5 ElevationTokens.Level5 -> Level5
else -> default else -> default
} }
} }

View File

@ -4,17 +4,18 @@ import android.content.Context
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.ash.reader.data.constant.ElevationTokens
import me.ash.reader.ui.ext.DataStoreKeys import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put import me.ash.reader.ui.ext.put
sealed class FlowArticleListTonalElevationPreference(val value: Int) : Preference() { sealed class FlowArticleListTonalElevationPreference(val value: Int) : Preference() {
object Level0 : FlowArticleListTonalElevationPreference(0) object Level0 : FlowArticleListTonalElevationPreference(ElevationTokens.Level0)
object Level1 : FlowArticleListTonalElevationPreference(1) object Level1 : FlowArticleListTonalElevationPreference(ElevationTokens.Level1)
object Level2 : FlowArticleListTonalElevationPreference(3) object Level2 : FlowArticleListTonalElevationPreference(ElevationTokens.Level2)
object Level3 : FlowArticleListTonalElevationPreference(6) object Level3 : FlowArticleListTonalElevationPreference(ElevationTokens.Level3)
object Level4 : FlowArticleListTonalElevationPreference(8) object Level4 : FlowArticleListTonalElevationPreference(ElevationTokens.Level4)
object Level5 : FlowArticleListTonalElevationPreference(12) object Level5 : FlowArticleListTonalElevationPreference(ElevationTokens.Level5)
override fun put(context: Context, scope: CoroutineScope) { override fun put(context: Context, scope: CoroutineScope) {
scope.launch { scope.launch {
@ -27,12 +28,12 @@ sealed class FlowArticleListTonalElevationPreference(val value: Int) : Preferenc
fun getDesc(context: Context): String = fun getDesc(context: Context): String =
when (this) { when (this) {
Level0 -> "Level 0 (0dp)" Level0 -> "Level 0 (${ElevationTokens.Level0}dp)"
Level1 -> "Level 1 (1dp)" Level1 -> "Level 1 (${ElevationTokens.Level1}dp)"
Level2 -> "Level 2 (3dp)" Level2 -> "Level 2 (${ElevationTokens.Level2}dp)"
Level3 -> "Level 3 (6dp)" Level3 -> "Level 3 (${ElevationTokens.Level3}dp)"
Level4 -> "Level 4 (8dp)" Level4 -> "Level 4 (${ElevationTokens.Level4}dp)"
Level5 -> "Level 5 (12dp)" Level5 -> "Level 5 (${ElevationTokens.Level5}dp)"
} }
companion object { companion object {
@ -41,12 +42,12 @@ sealed class FlowArticleListTonalElevationPreference(val value: Int) : Preferenc
fun fromPreferences(preferences: Preferences) = fun fromPreferences(preferences: Preferences) =
when (preferences[DataStoreKeys.FlowArticleListTonalElevation.key]) { when (preferences[DataStoreKeys.FlowArticleListTonalElevation.key]) {
0 -> Level0 ElevationTokens.Level0 -> Level0
1 -> Level1 ElevationTokens.Level1 -> Level1
3 -> Level2 ElevationTokens.Level2 -> Level2
6 -> Level3 ElevationTokens.Level3 -> Level3
8 -> Level4 ElevationTokens.Level4 -> Level4
12 -> Level5 ElevationTokens.Level5 -> Level5
else -> default else -> default
} }
} }

View File

@ -4,17 +4,18 @@ import android.content.Context
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.ash.reader.data.constant.ElevationTokens
import me.ash.reader.ui.ext.DataStoreKeys import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put import me.ash.reader.ui.ext.put
sealed class FlowFilterBarTonalElevationPreference(val value: Int) : Preference() { sealed class FlowFilterBarTonalElevationPreference(val value: Int) : Preference() {
object Level0 : FlowFilterBarTonalElevationPreference(0) object Level0 : FlowFilterBarTonalElevationPreference(ElevationTokens.Level0)
object Level1 : FlowFilterBarTonalElevationPreference(1) object Level1 : FlowFilterBarTonalElevationPreference(ElevationTokens.Level1)
object Level2 : FlowFilterBarTonalElevationPreference(3) object Level2 : FlowFilterBarTonalElevationPreference(ElevationTokens.Level2)
object Level3 : FlowFilterBarTonalElevationPreference(6) object Level3 : FlowFilterBarTonalElevationPreference(ElevationTokens.Level3)
object Level4 : FlowFilterBarTonalElevationPreference(8) object Level4 : FlowFilterBarTonalElevationPreference(ElevationTokens.Level4)
object Level5 : FlowFilterBarTonalElevationPreference(12) object Level5 : FlowFilterBarTonalElevationPreference(ElevationTokens.Level5)
override fun put(context: Context, scope: CoroutineScope) { override fun put(context: Context, scope: CoroutineScope) {
scope.launch { scope.launch {
@ -27,12 +28,12 @@ sealed class FlowFilterBarTonalElevationPreference(val value: Int) : Preference(
fun getDesc(context: Context): String = fun getDesc(context: Context): String =
when (this) { when (this) {
Level0 -> "Level 0 (0dp)" Level0 -> "Level 0 (${ElevationTokens.Level0}dp)"
Level1 -> "Level 1 (1dp)" Level1 -> "Level 1 (${ElevationTokens.Level1}dp)"
Level2 -> "Level 2 (3dp)" Level2 -> "Level 2 (${ElevationTokens.Level2}dp)"
Level3 -> "Level 3 (6dp)" Level3 -> "Level 3 (${ElevationTokens.Level3}dp)"
Level4 -> "Level 4 (8dp)" Level4 -> "Level 4 (${ElevationTokens.Level4}dp)"
Level5 -> "Level 5 (12dp)" Level5 -> "Level 5 (${ElevationTokens.Level5}dp)"
} }
companion object { companion object {
@ -41,12 +42,12 @@ sealed class FlowFilterBarTonalElevationPreference(val value: Int) : Preference(
fun fromPreferences(preferences: Preferences) = fun fromPreferences(preferences: Preferences) =
when (preferences[DataStoreKeys.FlowFilterBarTonalElevation.key]) { when (preferences[DataStoreKeys.FlowFilterBarTonalElevation.key]) {
0 -> Level0 ElevationTokens.Level0 -> Level0
1 -> Level1 ElevationTokens.Level1 -> Level1
3 -> Level2 ElevationTokens.Level2 -> Level2
6 -> Level3 ElevationTokens.Level3 -> Level3
8 -> Level4 ElevationTokens.Level4 -> Level4
12 -> Level5 ElevationTokens.Level5 -> Level5
else -> default else -> default
} }
} }

View File

@ -4,17 +4,18 @@ import android.content.Context
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.ash.reader.data.constant.ElevationTokens
import me.ash.reader.ui.ext.DataStoreKeys import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put import me.ash.reader.ui.ext.put
sealed class FlowTopBarTonalElevationPreference(val value: Int) : Preference() { sealed class FlowTopBarTonalElevationPreference(val value: Int) : Preference() {
object Level0 : FlowTopBarTonalElevationPreference(0) object Level0 : FlowTopBarTonalElevationPreference(ElevationTokens.Level0)
object Level1 : FlowTopBarTonalElevationPreference(1) object Level1 : FlowTopBarTonalElevationPreference(ElevationTokens.Level1)
object Level2 : FlowTopBarTonalElevationPreference(3) object Level2 : FlowTopBarTonalElevationPreference(ElevationTokens.Level2)
object Level3 : FlowTopBarTonalElevationPreference(6) object Level3 : FlowTopBarTonalElevationPreference(ElevationTokens.Level3)
object Level4 : FlowTopBarTonalElevationPreference(8) object Level4 : FlowTopBarTonalElevationPreference(ElevationTokens.Level4)
object Level5 : FlowTopBarTonalElevationPreference(12) object Level5 : FlowTopBarTonalElevationPreference(ElevationTokens.Level5)
override fun put(context: Context, scope: CoroutineScope) { override fun put(context: Context, scope: CoroutineScope) {
scope.launch { scope.launch {
@ -27,12 +28,12 @@ sealed class FlowTopBarTonalElevationPreference(val value: Int) : Preference() {
fun getDesc(context: Context): String = fun getDesc(context: Context): String =
when (this) { when (this) {
Level0 -> "Level 0 (0dp)" Level0 -> "Level 0 (${ElevationTokens.Level0}dp)"
Level1 -> "Level 1 (1dp)" Level1 -> "Level 1 (${ElevationTokens.Level1}dp)"
Level2 -> "Level 2 (3dp)" Level2 -> "Level 2 (${ElevationTokens.Level2}dp)"
Level3 -> "Level 3 (6dp)" Level3 -> "Level 3 (${ElevationTokens.Level3}dp)"
Level4 -> "Level 4 (8dp)" Level4 -> "Level 4 (${ElevationTokens.Level4}dp)"
Level5 -> "Level 5 (12dp)" Level5 -> "Level 5 (${ElevationTokens.Level5}dp)"
} }
companion object { companion object {
@ -41,12 +42,12 @@ sealed class FlowTopBarTonalElevationPreference(val value: Int) : Preference() {
fun fromPreferences(preferences: Preferences) = fun fromPreferences(preferences: Preferences) =
when (preferences[DataStoreKeys.FlowTopBarTonalElevation.key]) { when (preferences[DataStoreKeys.FlowTopBarTonalElevation.key]) {
0 -> Level0 ElevationTokens.Level0 -> Level0
1 -> Level1 ElevationTokens.Level1 -> Level1
3 -> Level2 ElevationTokens.Level2 -> Level2
6 -> Level3 ElevationTokens.Level3 -> Level3
8 -> Level4 ElevationTokens.Level4 -> Level4
12 -> Level5 ElevationTokens.Level5 -> Level5
else -> default else -> default
} }
} }

View File

@ -54,7 +54,7 @@ class OpmlRepository @Inject constructor(
Opml( Opml(
"2.0", "2.0",
Head( Head(
accountDao.queryById(context.currentAccountId).name, accountDao.queryById(context.currentAccountId)?.name,
Date().toString(), null, null, null, Date().toString(), null, null, null,
null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null,

View File

@ -21,8 +21,6 @@ import net.dankito.readability4j.extended.Readability4JExtended
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import java.net.URL import java.net.URL
import java.text.ParsePosition
import java.text.SimpleDateFormat
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -86,7 +84,12 @@ class RssHelper @Inject constructor(
return withContext(dispatcherIO) { return withContext(dispatcherIO) {
val a = mutableListOf<Article>() val a = mutableListOf<Article>()
val accountId = context.currentAccountId val accountId = context.currentAccountId
val parseRss: SyndFeed = SyndFeedInput().build(XmlReader(URL(feed.url))) val parseRss: SyndFeed = SyndFeedInput().build(
XmlReader(URL(feed.url).openConnection().apply {
connectTimeout = 5000
readTimeout = 5000
})
)
parseRss.entries.forEach { parseRss.entries.forEach {
if (latestLink != null && latestLink == it.link) return@withContext a if (latestLink != null && latestLink == it.link) return@withContext a
val desc = it.description?.value val desc = it.description?.value
@ -111,13 +114,13 @@ class RssHelper @Inject constructor(
date = it.publishedDate ?: it.updatedDate ?: Date(), date = it.publishedDate ?: it.updatedDate ?: Date(),
title = Html.fromHtml(it.title.toString()).toString(), title = Html.fromHtml(it.title.toString()).toString(),
author = it.author, author = it.author,
rawDescription = (desc ?: content) ?: "", rawDescription = (content ?: desc) ?: "",
shortDescription = (Readability4JExtended("", desc ?: content ?: "") shortDescription = (Readability4JExtended("", desc ?: content ?: "")
.parse().textContent ?: "") .parse().textContent ?: "")
.take(100) .take(100)
.trim(), .trim(),
fullContent = content, fullContent = content,
img = findImg((desc ?: content) ?: ""), img = findImg((content ?: desc) ?: ""),
link = it.link ?: "", link = it.link ?: "",
) )
) )

View File

@ -11,6 +11,13 @@ class StringsRepository @Inject constructor(
private val context: Context, private val context: Context,
) { ) {
fun getString(resId: Int, vararg formatArgs: Any) = context.getString(resId, *formatArgs) fun getString(resId: Int, vararg formatArgs: Any) = context.getString(resId, *formatArgs)
fun getQuantityString(resId: Int, quantity: Int, vararg formatArgs: Any) = context.resources.getQuantityString(resId, quantity, *formatArgs)
fun formatAsString(date: Date?) = date?.formatAsString(context) fun getQuantityString(resId: Int, quantity: Int, vararg formatArgs: Any) =
context.resources.getQuantityString(resId, quantity, *formatArgs)
fun formatAsString(
date: Date?,
onlyHourMinute: Boolean? = false,
atHourMinute: Boolean? = false
) = date?.formatAsString(context, onlyHourMinute, atHourMinute)
} }

View File

@ -16,7 +16,7 @@ import coil.size.Scale
import coil.size.Size import coil.size.Size
import me.ash.reader.R import me.ash.reader.R
val Size_1000 = Size(1000, 1000) val SIZE_1000 = Size(1000, 1000)
@Composable @Composable
fun RYAsyncImage( fun RYAsyncImage(

View File

@ -50,7 +50,12 @@ class HomeViewModel @Inject constructor(
fun fetchArticles() { fun fetchArticles() {
_homeUiState.update { _homeUiState.update {
it.copy( it.copy(
pagingData = Pager(PagingConfig(pageSize = 50)) { pagingData = Pager(
config = PagingConfig(
pageSize = 100,
enablePlaceholders = false,
)
) {
if (_homeUiState.value.searchContent.isNotBlank()) { if (_homeUiState.value.searchContent.isNotBlank()) {
rssRepository.get().searchArticles( rssRepository.get().searchArticles(
content = _homeUiState.value.searchContent.trim(), content = _homeUiState.value.searchContent.trim(),
@ -68,7 +73,14 @@ class HomeViewModel @Inject constructor(
) )
} }
}.flow.map { }.flow.map {
it.map { FlowItemView.Article(it) }.insertSeparators { before, after -> it.map {
FlowItemView.Article(it.apply {
article.dateString = stringsRepository.formatAsString(
date = article.date,
onlyHourMinute = true
)
})
}.insertSeparators { before, after ->
val beforeDate = val beforeDate =
stringsRepository.formatAsString(before?.articleWithFeed?.article?.date) stringsRepository.formatAsString(before?.articleWithFeed?.article?.date)
val afterDate = val afterDate =

View File

@ -4,16 +4,13 @@ import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.async
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.R
import me.ash.reader.data.entity.Article import me.ash.reader.data.entity.Article
import me.ash.reader.data.entity.Feed import me.ash.reader.data.entity.Feed
import me.ash.reader.data.entity.Group import me.ash.reader.data.entity.Group
import me.ash.reader.data.module.DispatcherIO
import me.ash.reader.data.repository.OpmlRepository import me.ash.reader.data.repository.OpmlRepository
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
@ -28,8 +25,6 @@ class SubscribeViewModel @Inject constructor(
private val rssRepository: RssRepository, private val rssRepository: RssRepository,
private val rssHelper: RssHelper, private val rssHelper: RssHelper,
private val stringsRepository: StringsRepository, private val stringsRepository: StringsRepository,
@DispatcherIO
private val dispatcherIO: CoroutineDispatcher,
) : ViewModel() { ) : ViewModel() {
private val _subscribeUiState = MutableStateFlow(SubscribeUiState()) private val _subscribeUiState = MutableStateFlow(SubscribeUiState())
val subscribeUiState: StateFlow<SubscribeUiState> = _subscribeUiState.asStateFlow() val subscribeUiState: StateFlow<SubscribeUiState> = _subscribeUiState.asStateFlow()
@ -55,7 +50,7 @@ class SubscribeViewModel @Inject constructor(
} }
fun importFromInputStream(inputStream: InputStream) { fun importFromInputStream(inputStream: InputStream) {
viewModelScope.launch(dispatcherIO) { viewModelScope.launch {
try { try {
opmlRepository.saveToDatabase(inputStream) opmlRepository.saveToDatabase(inputStream)
rssRepository.get().doSync() rssRepository.get().doSync()
@ -68,13 +63,10 @@ class SubscribeViewModel @Inject constructor(
fun subscribe() { fun subscribe() {
val feed = _subscribeUiState.value.feed ?: return val feed = _subscribeUiState.value.feed ?: return
val articles = _subscribeUiState.value.articles val articles = _subscribeUiState.value.articles
viewModelScope.launch(dispatcherIO) { viewModelScope.launch {
val groupId = async {
_subscribeUiState.value.selectedGroupId
}
rssRepository.get().subscribe( rssRepository.get().subscribe(
feed.copy( feed.copy(
groupId = groupId.await(), groupId = _subscribeUiState.value.selectedGroupId,
isNotification = _subscribeUiState.value.allowNotificationPreset, isNotification = _subscribeUiState.value.allowNotificationPreset,
isFullContent = _subscribeUiState.value.parseFullContentPreset, isFullContent = _subscribeUiState.value.parseFullContentPreset,
), articles ), articles
@ -123,7 +115,7 @@ class SubscribeViewModel @Inject constructor(
fun search() { fun search() {
searchJob?.cancel() searchJob?.cancel()
viewModelScope.launch(dispatcherIO) { viewModelScope.launch {
try { try {
_subscribeUiState.update { _subscribeUiState.update {
it.copy( it.copy(

View File

@ -2,7 +2,6 @@ package me.ash.reader.ui.page.home.flow
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Star import androidx.compose.material.icons.rounded.Star
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -23,10 +22,10 @@ import coil.size.Scale
import me.ash.reader.R import me.ash.reader.R
import me.ash.reader.data.entity.ArticleWithFeed import me.ash.reader.data.entity.ArticleWithFeed
import me.ash.reader.data.preference.* import me.ash.reader.data.preference.*
import me.ash.reader.ui.component.base.RYAsyncImage
import me.ash.reader.ui.ext.formatAsString
import me.ash.reader.ui.component.FeedIcon import me.ash.reader.ui.component.FeedIcon
import me.ash.reader.ui.component.base.Size_1000 import me.ash.reader.ui.component.base.RYAsyncImage
import me.ash.reader.ui.component.base.SIZE_1000
import me.ash.reader.ui.theme.SHAPE_20
@Composable @Composable
fun ArticleItem( fun ArticleItem(
@ -43,7 +42,7 @@ fun ArticleItem(
Column( Column(
modifier = Modifier modifier = Modifier
.padding(horizontal = 12.dp) .padding(horizontal = 12.dp)
.clip(RoundedCornerShape(20.dp)) .clip(SHAPE_20)
.clickable { onClick(articleWithFeed) } .clickable { onClick(articleWithFeed) }
.padding(horizontal = 12.dp, vertical = 12.dp) .padding(horizontal = 12.dp, vertical = 12.dp)
.alpha(if (articleWithFeed.article.isStarred || articleWithFeed.article.isUnread) 1f else 0.5f), .alpha(if (articleWithFeed.article.isStarred || articleWithFeed.article.isUnread) 1f else 0.5f),
@ -80,21 +79,20 @@ fun ArticleItem(
if (articleWithFeed.article.isStarred) { if (articleWithFeed.article.isStarred) {
Icon( Icon(
modifier = Modifier modifier = Modifier
.alpha(0.7f)
.size(14.dp) .size(14.dp)
.padding(end = 2.dp), .padding(end = 2.dp),
imageVector = Icons.Rounded.Star, imageVector = Icons.Rounded.Star,
contentDescription = stringResource(R.string.starred), contentDescription = stringResource(R.string.starred),
tint = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f), tint = MaterialTheme.colorScheme.outline,
) )
} }
// Date // Date
Text( Text(
text = articleWithFeed.article.date.formatAsString( modifier = Modifier.alpha(0.7f),
context, text = articleWithFeed.article.dateString ?: "",
onlyHourMinute = true color = MaterialTheme.colorScheme.outline,
),
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
) )
} }
@ -128,8 +126,9 @@ fun ArticleItem(
// Description // Description
if (articleListDesc.value && articleWithFeed.article.shortDescription.isNotBlank()) { if (articleListDesc.value && articleWithFeed.article.shortDescription.isNotBlank()) {
Text( Text(
modifier = Modifier.alpha(0.7f),
text = articleWithFeed.article.shortDescription, text = articleWithFeed.article.shortDescription,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f), color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
maxLines = 2, maxLines = 2,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
@ -143,11 +142,11 @@ fun ArticleItem(
modifier = Modifier modifier = Modifier
.padding(start = 10.dp) .padding(start = 10.dp)
.size(80.dp) .size(80.dp)
.clip(RoundedCornerShape(20.dp)), .clip(SHAPE_20),
data = articleWithFeed.article.img, data = articleWithFeed.article.img,
scale = Scale.FILL, scale = Scale.FILL,
precision = Precision.INEXACT, precision = Precision.INEXACT,
size = Size_1000, size = SIZE_1000,
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
) )
} }

View File

@ -13,8 +13,8 @@ import me.ash.reader.data.entity.ArticleWithFeed
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
fun LazyListScope.ArticleList( fun LazyListScope.ArticleList(
pagingItems: LazyPagingItems<FlowItemView>, pagingItems: LazyPagingItems<FlowItemView>,
articleListFeedIcon: Boolean, isShowFeedIcon: Boolean,
articleListDateStickyHeader: Boolean, isShowStickyHeader: Boolean,
articleListTonalElevation: Int, articleListTonalElevation: Int,
onClick: (ArticleWithFeed) -> Unit = {}, onClick: (ArticleWithFeed) -> Unit = {},
) { ) {
@ -31,13 +31,13 @@ fun LazyListScope.ArticleList(
} }
is FlowItemView.Date -> { is FlowItemView.Date -> {
if (item.showSpacer) item { Spacer(modifier = Modifier.height(40.dp)) } if (item.showSpacer) item { Spacer(modifier = Modifier.height(40.dp)) }
if (articleListDateStickyHeader) { if (isShowStickyHeader) {
stickyHeader(key = item.date) { stickyHeader(key = item.date) {
StickyHeader(item.date, articleListFeedIcon, articleListTonalElevation) StickyHeader(item.date, isShowFeedIcon, articleListTonalElevation)
} }
} else { } else {
item(key = item.date) { item(key = item.date) {
StickyHeader(item.date, articleListFeedIcon, articleListTonalElevation) StickyHeader(item.date, isShowFeedIcon, articleListTonalElevation)
} }
} }
} }

View File

@ -33,7 +33,6 @@ import me.ash.reader.ui.component.base.RYScaffold
import me.ash.reader.ui.component.base.SwipeRefresh import me.ash.reader.ui.component.base.SwipeRefresh
import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.ext.collectAsStateValue
import me.ash.reader.ui.page.common.RouteName import me.ash.reader.ui.page.common.RouteName
import me.ash.reader.ui.page.home.FilterState
import me.ash.reader.ui.page.home.HomeViewModel import me.ash.reader.ui.page.home.HomeViewModel
@OptIn( @OptIn(
@ -165,7 +164,15 @@ fun FlowPage(
state = listState, state = listState,
) { ) {
item { item {
DisplayTextHeader(filterUiState, isSyncing, articleListFeedIcon.value) DisplayText(
modifier = Modifier.padding(start = if (articleListFeedIcon.value) 30.dp else 0.dp),
text = when {
filterUiState.group != null -> filterUiState.group.name
filterUiState.feed != null -> filterUiState.feed.name
else -> filterUiState.filter.getName()
},
desc = if (isSyncing) stringResource(R.string.syncing) else "",
)
RYExtensibleVisibility(visible = markAsRead) { RYExtensibleVisibility(visible = markAsRead) {
Spacer(modifier = Modifier.height((56 + 24 + 10).dp)) Spacer(modifier = Modifier.height((56 + 24 + 10).dp))
} }
@ -217,8 +224,8 @@ fun FlowPage(
} }
ArticleList( ArticleList(
pagingItems = pagingItems, pagingItems = pagingItems,
articleListFeedIcon = articleListFeedIcon.value, isShowFeedIcon = articleListFeedIcon.value,
articleListDateStickyHeader = articleListDateStickyHeader.value, isShowStickyHeader = articleListDateStickyHeader.value,
articleListTonalElevation = articleListTonalElevation.value, articleListTonalElevation = articleListTonalElevation.value,
) { ) {
onSearch = false onSearch = false
@ -248,20 +255,3 @@ fun FlowPage(
} }
) )
} }
@Composable
private fun DisplayTextHeader(
filterState: FilterState,
isSyncing: Boolean,
articleListFeedIcon: Boolean,
) {
DisplayText(
modifier = Modifier.padding(start = if (articleListFeedIcon) 30.dp else 0.dp),
text = when {
filterState.group != null -> filterState.group.name
filterState.feed != null -> filterState.feed.name
else -> filterState.filter.getName()
},
desc = if (isSyncing) stringResource(R.string.syncing) else "",
)
}

View File

@ -11,7 +11,6 @@ import kotlinx.coroutines.launch
import me.ash.reader.data.entity.ArticleWithFeed import me.ash.reader.data.entity.ArticleWithFeed
import me.ash.reader.data.repository.RssRepository import me.ash.reader.data.repository.RssRepository
import java.util.* import java.util.*
import javax.annotation.concurrent.Immutable
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@ -77,10 +76,7 @@ enum class MarkAsReadBefore {
All, All,
} }
@Immutable
sealed class FlowItemView { sealed class FlowItemView {
@Immutable
class Article(val articleWithFeed: ArticleWithFeed) : FlowItemView() class Article(val articleWithFeed: ArticleWithFeed) : FlowItemView()
@Immutable
class Date(val date: String, val showSpacer: Boolean) : FlowItemView() class Date(val date: String, val showSpacer: Boolean) : FlowItemView()
} }

View File

@ -12,19 +12,19 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
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.alpha
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.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.BaselineShift
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import me.ash.reader.R import me.ash.reader.R
import me.ash.reader.data.constant.ElevationTokens
@Composable @Composable
fun SearchBar( fun SearchBar(
modifier: Modifier = Modifier,
value: String, value: String,
placeholder: String = "", placeholder: String = "",
focusRequester: FocusRequester = remember { FocusRequester() }, focusRequester: FocusRequester = remember { FocusRequester() },
@ -39,7 +39,7 @@ fun SearchBar(
.padding(horizontal = 24.dp) .padding(horizontal = 24.dp)
.fillMaxWidth(), .fillMaxWidth(),
shape = CircleShape, shape = CircleShape,
tonalElevation = 3.dp tonalElevation = ElevationTokens.Level2.dp
) { ) {
Row( Row(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
@ -62,6 +62,7 @@ fun SearchBar(
.fillMaxWidth() .fillMaxWidth()
.focusRequester(focusRequester), .focusRequester(focusRequester),
colors = TextFieldDefaults.textFieldColors( colors = TextFieldDefaults.textFieldColors(
textColor = MaterialTheme.colorScheme.onSurfaceVariant,
containerColor = Color.Transparent, containerColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent, focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent,
@ -70,17 +71,13 @@ fun SearchBar(
onValueChange = { onValueChange(it) }, onValueChange = { onValueChange(it) },
placeholder = { placeholder = {
Text( Text(
modifier = Modifier.alpha(0.7f),
text = placeholder, text = placeholder,
style = MaterialTheme.typography.bodyLarge, style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy( color = MaterialTheme.colorScheme.onSurfaceVariant,
alpha = 0.7f
),
) )
}, },
textStyle = MaterialTheme.typography.bodyLarge.copy( textStyle = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
baselineShift = BaselineShift(0.1f)
),
singleLine = true, singleLine = true,
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done imeAction = ImeAction.Done

View File

@ -15,8 +15,8 @@ import me.ash.reader.ui.theme.palette.onDark
@Composable @Composable
fun StickyHeader( fun StickyHeader(
currentItemDay: String, dateString: String,
articleListFeedIcon: Boolean, isShowFeedIcon: Boolean,
articleListTonalElevation: Int, articleListTonalElevation: Int,
) { ) {
Row( Row(
@ -30,10 +30,10 @@ fun StickyHeader(
) { ) {
Text( Text(
modifier = Modifier.padding( modifier = Modifier.padding(
start = if (articleListFeedIcon) 54.dp else 24.dp, start = if (isShowFeedIcon) 54.dp else 24.dp,
bottom = 4.dp bottom = 4.dp
), ),
text = currentItemDay, text = dateString,
color = MaterialTheme.colorScheme.primary, color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.labelLarge, style = MaterialTheme.typography.labelLarge,
) )

View File

@ -18,7 +18,7 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class ReadingViewModel @Inject constructor( class ReadingViewModel @Inject constructor(
val rssRepository: RssRepository, private val rssRepository: RssRepository,
private val rssHelper: RssHelper, private val rssHelper: RssHelper,
) : ViewModel() { ) : ViewModel() {
private val _readingUiState = MutableStateFlow(ReadingUiState()) private val _readingUiState = MutableStateFlow(ReadingUiState())

View File

@ -0,0 +1,15 @@
package me.ash.reader.ui.theme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Shapes
import androidx.compose.ui.unit.dp
val Shapes = Shapes(
extraSmall = RoundedCornerShape(4.0.dp),
small = RoundedCornerShape(8.0.dp),
medium = RoundedCornerShape(12.0.dp),
large = RoundedCornerShape(16.0.dp),
extraLarge = RoundedCornerShape(28.0.dp)
)
val SHAPE_20 = RoundedCornerShape(20.0.dp)

View File

@ -40,7 +40,8 @@ fun AppTheme(
if (useDarkTheme) dynamicDarkColorScheme() if (useDarkTheme) dynamicDarkColorScheme()
else dynamicLightColorScheme(), else dynamicLightColorScheme(),
typography = AppTypography, typography = AppTypography,
content = content shapes = Shapes,
content = content,
) )
} }
} }

View File

@ -5,5 +5,6 @@
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19v-4.58l0.99,0.99 4,-4 4,4 4,-3.99L19,12.43L19,19zM19,9.59l-1.01,-1.01 -4,4.01 -4,-4 -4,4 -0.99,-1L5,5h14v4.59z" android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19v-4.58l0.99,0.99 4,-4 4,4 4,-3.99L19,12.43L19,19zM19,9.59l-1.01,-1.01 -4,4.01 -4,-4 -4,4 -0.99,-1L5,5h14v4.59z"
android:fillColor="#000000"/> android:fillColor="#000000"
android:fillAlpha="0.3"/>
</vector> </vector>

View File

@ -5,5 +5,6 @@
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:pathData="M8,2c-1.1,0 -2,0.9 -2,2v3.17c0,0.53 0.21,1.04 0.59,1.42L10,12l-3.42,3.42c-0.37,0.38 -0.58,0.89 -0.58,1.42L6,20c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2v-3.16c0,-0.53 -0.21,-1.04 -0.58,-1.41L14,12l3.41,-3.4c0.38,-0.38 0.59,-0.89 0.59,-1.42L18,4c0,-1.1 -0.9,-2 -2,-2L8,2zM16,16.5L16,19c0,0.55 -0.45,1 -1,1L9,20c-0.55,0 -1,-0.45 -1,-1v-2.5l4,-4 4,4zM12,11.5l-4,-4L8,5c0,-0.55 0.45,-1 1,-1h6c0.55,0 1,0.45 1,1v2.5l-4,4z" android:pathData="M8,2c-1.1,0 -2,0.9 -2,2v3.17c0,0.53 0.21,1.04 0.59,1.42L10,12l-3.42,3.42c-0.37,0.38 -0.58,0.89 -0.58,1.42L6,20c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2v-3.16c0,-0.53 -0.21,-1.04 -0.58,-1.41L14,12l3.41,-3.4c0.38,-0.38 0.59,-0.89 0.59,-1.42L18,4c0,-1.1 -0.9,-2 -2,-2L8,2zM16,16.5L16,19c0,0.55 -0.45,1 -1,1L9,20c-0.55,0 -1,-0.45 -1,-1v-2.5l4,-4 4,4zM12,11.5l-4,-4L8,5c0,-0.55 0.45,-1 1,-1h6c0.55,0 1,0.45 1,1v2.5l-4,4z"
android:fillColor="#000000"/> android:fillColor="#000000"
android:fillAlpha="0.3"/>
</vector> </vector>