diff --git a/app/src/main/java/com/readrops/app/repositories/LocalRSSRepository.kt b/app/src/main/java/com/readrops/app/repositories/LocalRSSRepository.kt
index 032de40f..756a5e23 100644
--- a/app/src/main/java/com/readrops/app/repositories/LocalRSSRepository.kt
+++ b/app/src/main/java/com/readrops/app/repositories/LocalRSSRepository.kt
@@ -103,6 +103,8 @@ class LocalRSSRepository(
}
}
+ // sort by date
+ newItems.sort()
database.itemDao().insert(newItems)
.zip(newItems)
.forEach { (id, item) -> item.id = id.toInt() }
diff --git a/app/src/main/java/com/readrops/app/timelime/FilterBottomSheet.kt b/app/src/main/java/com/readrops/app/timelime/FilterBottomSheet.kt
index 48f5b7e5..a5b0cb05 100644
--- a/app/src/main/java/com/readrops/app/timelime/FilterBottomSheet.kt
+++ b/app/src/main/java/com/readrops/app/timelime/FilterBottomSheet.kt
@@ -2,41 +2,66 @@ package com.readrops.app.timelime
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Info
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
+import androidx.compose.material3.RichTooltip
+import androidx.compose.material3.SegmentedButton
+import androidx.compose.material3.SegmentedButtonDefaults
+import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Text
+import androidx.compose.material3.TooltipBox
+import androidx.compose.material3.TooltipDefaults
+import androidx.compose.material3.rememberTooltipState
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.readrops.app.R
+import com.readrops.app.util.DefaultPreview
import com.readrops.app.util.theme.LargeSpacer
+import com.readrops.app.util.theme.MediumSpacer
+import com.readrops.app.util.theme.ReadropsTheme
import com.readrops.app.util.theme.ShortSpacer
import com.readrops.app.util.theme.spacing
+import com.readrops.db.filters.OrderField
import com.readrops.db.filters.OrderType
import com.readrops.db.filters.QueryFilters
+import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FilterBottomSheet(
- onSetShowReadItemsState: () -> Unit,
- onSetSortTypeState: () -> Unit,
filters: QueryFilters,
- onDismiss: () -> Unit,
+ onSetShowReadItems: () -> Unit,
+ onSetOrderField: () -> Unit,
+ onSetOrderType: () -> Unit,
+ onDismiss: () -> Unit
) {
+ val tooltipState = rememberTooltipState(isPersistent = true)
+ val coroutineScope = rememberCoroutineScope()
+
ModalBottomSheet(
- onDismissRequest = onDismiss
+ onDismissRequest = onDismiss,
+ //sheetState = rememberStandardBottomSheetState()
) {
Column(
modifier = Modifier.padding(MaterialTheme.spacing.mediumSpacing)
) {
Text(
- text = stringResource(R.string.filters)
+ text = stringResource(R.string.filters),
+ style = MaterialTheme.typography.titleMedium
)
ShortSpacer()
@@ -45,11 +70,11 @@ fun FilterBottomSheet(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
- .clickable(onClick = onSetShowReadItemsState)
+ .clickable(onClick = onSetShowReadItems)
) {
Checkbox(
checked = filters.showReadItems,
- onCheckedChange = { onSetShowReadItemsState() }
+ onCheckedChange = { onSetShowReadItems() }
)
ShortSpacer()
@@ -61,25 +86,102 @@ fun FilterBottomSheet(
ShortSpacer()
- Row(
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier
- .fillMaxWidth()
- .clickable(onClick = onSetSortTypeState)
+ Column(
+ modifier = Modifier.width(IntrinsicSize.Max)
) {
- Checkbox(
- checked = filters.orderType == OrderType.ASC,
- onCheckedChange = { onSetSortTypeState() }
- )
+ Row(
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(text = stringResource(R.string.order_by))
+
+ TooltipBox(
+ positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
+ tooltip = {
+ RichTooltip(
+ title = { Text(text = stringResource(id = R.string.order_by)) }
+ ) {
+ Text(
+ text = stringResource(R.string.order_field_tooltip),
+ )
+ }
+ },
+ state = tooltipState
+ ) {
+ IconButton(
+ onClick = {
+ coroutineScope.launch {
+ tooltipState.show()
+ }
+ }
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Info,
+ contentDescription = null
+ )
+ }
+ }
+ }
+
+ SingleChoiceSegmentedButtonRow(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ SegmentedButton(
+ selected = filters.orderField == OrderField.ID,
+ onClick = onSetOrderField,
+ shape = SegmentedButtonDefaults.itemShape(index = 0, count = 2)
+ ) {
+ Text(text = stringResource(R.string.identifier))
+ }
+
+ SegmentedButton(
+ selected = filters.orderField == OrderField.DATE,
+ onClick = onSetOrderField,
+ shape = SegmentedButtonDefaults.itemShape(index = 1, count = 2)
+ ) {
+ Text(text = stringResource(R.string.date))
+ }
+ }
+
+ MediumSpacer()
+
+ Text(text = stringResource(R.string.with_direction))
ShortSpacer()
- Text(
- text = stringResource(R.string.show_oldest_articles_first)
- )
+ SingleChoiceSegmentedButtonRow {
+ SegmentedButton(
+ selected = filters.orderType == OrderType.ASC,
+ onClick = onSetOrderType,
+ shape = SegmentedButtonDefaults.itemShape(index = 0, count = 2)
+ ) {
+ Text(text = stringResource(R.string.ascending))
+ }
+
+ SegmentedButton(
+ selected = filters.orderType == OrderType.DESC,
+ onClick = onSetOrderType,
+ shape = SegmentedButtonDefaults.itemShape(index = 1, count = 2)
+ ) {
+ Text(text = stringResource(R.string.descending))
+ }
+ }
}
LargeSpacer()
}
}
+}
+
+@DefaultPreview
+@Composable
+private fun FilterBottomSheetPreview() {
+ ReadropsTheme {
+ FilterBottomSheet(
+ onSetShowReadItems = {},
+ onSetOrderType = {},
+ onSetOrderField = {},
+ filters = QueryFilters(),
+ onDismiss = {}
+ )
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/readrops/app/timelime/TimelineScreenModel.kt b/app/src/main/java/com/readrops/app/timelime/TimelineScreenModel.kt
index 7c79e049..25917a78 100644
--- a/app/src/main/java/com/readrops/app/timelime/TimelineScreenModel.kt
+++ b/app/src/main/java/com/readrops/app/timelime/TimelineScreenModel.kt
@@ -21,6 +21,7 @@ import com.readrops.db.entities.Feed
import com.readrops.db.entities.Folder
import com.readrops.db.entities.Item
import com.readrops.db.filters.MainFilter
+import com.readrops.db.filters.OrderField
import com.readrops.db.filters.OrderType
import com.readrops.db.filters.QueryFilters
import com.readrops.db.filters.SubFilter
@@ -374,6 +375,18 @@ class TimelineScreenModel(
}
}
+ fun setOrderFieldState(orderField: OrderField) {
+ _timelineState.update {
+ it.copy(
+ filters = updateFilters {
+ it.filters.copy(
+ orderField = orderField
+ )
+ }
+ )
+ }
+ }
+
fun setOrderTypeState(orderType: OrderType) {
_timelineState.update {
it.copy(
diff --git a/app/src/main/java/com/readrops/app/timelime/TimelineTab.kt b/app/src/main/java/com/readrops/app/timelime/TimelineTab.kt
index cdeb2365..2574029d 100644
--- a/app/src/main/java/com/readrops/app/timelime/TimelineTab.kt
+++ b/app/src/main/java/com/readrops/app/timelime/TimelineTab.kt
@@ -68,6 +68,7 @@ import com.readrops.app.util.components.RefreshScreen
import com.readrops.app.util.components.dialog.TwoChoicesDialog
import com.readrops.app.util.theme.spacing
import com.readrops.db.filters.MainFilter
+import com.readrops.db.filters.OrderField
import com.readrops.db.filters.OrderType
import com.readrops.db.filters.SubFilter
import com.readrops.db.pojo.ItemWithFeed
@@ -208,10 +209,19 @@ object TimelineTab : Tab {
is DialogState.FilterSheet -> {
FilterBottomSheet(
filters = state.filters,
- onSetShowReadItemsState = {
+ onSetShowReadItems = {
screenModel.setShowReadItemsState(!state.filters.showReadItems)
},
- onSetSortTypeState = {
+ onSetOrderField = {
+ screenModel.setOrderFieldState(
+ if (state.filters.orderField == OrderField.ID) {
+ OrderField.DATE
+ } else {
+ OrderField.ID
+ }
+ )
+ },
+ onSetOrderType = {
screenModel.setOrderTypeState(
if (state.filters.orderType == OrderType.DESC) {
OrderType.ASC
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index c775275b..341518d4 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -170,4 +170,11 @@
Fichier téléchargé !
Merci de fournir l\'URL entière de l\'API
Merci de fournir l\'URL racine du service
+ Les flux RSS contiennent toujours d\'anciens articles qu\'il est peu probable de voir apparaître dans votre timeline si vous la classez par date. Le classement par identifiant d\'article vous permet d\'afficher tous les nouveaux articles insérés, quelle que soit leur date.
+ Date
+ Identifiant
+ Ascendant
+ Descendant
+ Ordonner par
+ Avec comme direction
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 63743968..67e856c3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -180,4 +180,11 @@
Downloaded file!
Please provide the full API URL
Please provide the service root URL
+ RSS feeds always contain old articles it is unlikely you will see in your timeline if you order it by date. Ordering by article identifier lets you show all new inserted articles whatever date they might have.
+ Date
+ Identifier
+ Ascending
+ Descending
+ Order by
+ With direction
\ No newline at end of file
diff --git a/db/src/androidTest/java/com/readrops/db/ItemsQueryBuilderTest.kt b/db/src/androidTest/java/com/readrops/db/ItemsQueryBuilderTest.kt
index 6472b86a..544478fa 100644
--- a/db/src/androidTest/java/com/readrops/db/ItemsQueryBuilderTest.kt
+++ b/db/src/androidTest/java/com/readrops/db/ItemsQueryBuilderTest.kt
@@ -4,11 +4,12 @@ import android.content.Context
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.readrops.db.filters.OrderType
import com.readrops.db.filters.MainFilter
+import com.readrops.db.filters.OrderField
+import com.readrops.db.filters.OrderType
+import com.readrops.db.filters.QueryFilters
import com.readrops.db.filters.SubFilter
import com.readrops.db.queries.ItemsQueryBuilder
-import com.readrops.db.filters.QueryFilters
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertTrue
import org.junit.After
@@ -42,7 +43,7 @@ class ItemsQueryBuilderTest {
with(query.sql) {
assertTrue(contains("Feed.account_id = 1"))
- assertTrue(contains("pub_date DESC"))
+ assertTrue(contains("Item.id DESC"))
assertFalse(contains("read = 0 And"))
}
@@ -51,8 +52,11 @@ class ItemsQueryBuilderTest {
@Test
fun feedFilterCaseTest() {
- val queryFilters = QueryFilters(accountId = 1, subFilter = SubFilter.FEED,
- feedId = 15)
+ val queryFilters = QueryFilters(
+ accountId = 1,
+ subFilter = SubFilter.FEED,
+ feedId = 15
+ )
val query = ItemsQueryBuilder.buildItemsQuery(queryFilters)
database.query(query)
@@ -82,8 +86,12 @@ class ItemsQueryBuilderTest {
@Test
fun oldestSortCaseTest() {
- val queryFilters = QueryFilters(accountId = 1, orderType = OrderType.ASC,
- showReadItems = false)
+ val queryFilters = QueryFilters(
+ accountId = 1,
+ orderType = OrderType.ASC,
+ orderField = OrderField.DATE,
+ showReadItems = false
+ )
val query = ItemsQueryBuilder.buildItemsQuery(queryFilters)
database.query(query)
@@ -92,12 +100,15 @@ class ItemsQueryBuilderTest {
assertTrue(contains("read = 0"))
assertTrue(contains("pub_date ASC"))
}
-
}
@Test
fun separateStateTest() {
- val queryFilters = QueryFilters(accountId = 1, showReadItems = false, mainFilter = MainFilter.STARS)
+ val queryFilters = QueryFilters(
+ accountId = 1,
+ showReadItems = false,
+ mainFilter = MainFilter.STARS
+ )
val query = ItemsQueryBuilder.buildItemsQuery(queryFilters, true)
database.query(query)
diff --git a/db/src/main/java/com/readrops/db/filters/Filters.kt b/db/src/main/java/com/readrops/db/filters/Filters.kt
index 99affbef..85541a4d 100644
--- a/db/src/main/java/com/readrops/db/filters/Filters.kt
+++ b/db/src/main/java/com/readrops/db/filters/Filters.kt
@@ -12,6 +12,11 @@ enum class SubFilter {
ALL
}
+enum class OrderField {
+ DATE,
+ ID
+}
+
enum class OrderType {
DESC,
ASC
@@ -24,5 +29,6 @@ data class QueryFilters(
val accountId: Int = 0,
val mainFilter: MainFilter = MainFilter.ALL,
val subFilter: SubFilter = SubFilter.ALL,
+ val orderField: OrderField = OrderField.ID,
val orderType: OrderType = OrderType.DESC,
)
\ No newline at end of file
diff --git a/db/src/main/java/com/readrops/db/queries/ItemsQueryBuilder.kt b/db/src/main/java/com/readrops/db/queries/ItemsQueryBuilder.kt
index 8b401cab..785f210b 100644
--- a/db/src/main/java/com/readrops/db/queries/ItemsQueryBuilder.kt
+++ b/db/src/main/java/com/readrops/db/queries/ItemsQueryBuilder.kt
@@ -3,6 +3,7 @@ package com.readrops.db.queries
import androidx.sqlite.db.SupportSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQueryBuilder
import com.readrops.db.filters.MainFilter
+import com.readrops.db.filters.OrderField
import com.readrops.db.filters.OrderType
import com.readrops.db.filters.QueryFilters
import com.readrops.db.filters.SubFilter
@@ -40,10 +41,6 @@ object ItemsQueryBuilder {
private const val SEPARATE_STATE_JOIN =
"LEFT JOIN ItemState On Item.remote_id = ItemState.remote_id"
- private const val ORDER_BY_DESC = "pub_date DESC"
-
- private const val ORDER_BY_ASC = "pub_date ASC"
-
fun buildItemsQuery(queryFilters: QueryFilters, separateState: Boolean): SupportSQLiteQuery =
buildQuery(queryFilters, separateState)
@@ -55,13 +52,14 @@ object ItemsQueryBuilder {
if (accountId == 0)
throw IllegalArgumentException("AccountId must be greater than 0")
- if (queryFilters.subFilter == SubFilter.FEED && feedId == 0)
+ if (subFilter == SubFilter.FEED && feedId == 0)
throw IllegalArgumentException("FeedId must be greater than 0 if current filter is FEED_FILTER")
- val columns = if (separateState)
+ val columns = if (separateState) {
COLUMNS.plus(SEPARATE_STATE_COLUMNS)
- else
+ } else {
COLUMNS.plus(OTHER_COLUMNS)
+ }
val selectAllJoin =
if (separateState) SELECT_ALL_JOIN + SEPARATE_STATE_JOIN else SELECT_ALL_JOIN
@@ -69,7 +67,7 @@ object ItemsQueryBuilder {
SupportSQLiteQueryBuilder.builder(selectAllJoin).run {
columns(columns)
selection(buildWhereClause(this@with, separateState), null)
- orderBy(if (orderType == OrderType.DESC) this@ItemsQueryBuilder.ORDER_BY_DESC else this@ItemsQueryBuilder.ORDER_BY_ASC)
+ orderBy(buildOrderByClause(orderField, orderType))
create()
}
@@ -108,5 +106,18 @@ object ItemsQueryBuilder {
toString()
}
+ private fun buildOrderByClause(orderField: OrderField, orderType: OrderType): String {
+ return buildString {
+ when (orderField) {
+ OrderField.ID -> append("Item.id ")
+ else -> append("pub_date ")
+ }
+
+ when (orderType) {
+ OrderType.DESC -> append("DESC")
+ else -> append("ASC")
+ }
+ }
+ }
}