mirror of https://github.com/readrops/Readrops.git
Restore Order By Item.id in TimelineTab main query
This commit is contained in:
parent
e6c880a79f
commit
cf9d307f00
|
@ -103,6 +103,8 @@ class LocalRSSRepository(
|
|||
}
|
||||
}
|
||||
|
||||
// sort by date
|
||||
newItems.sort()
|
||||
database.itemDao().insert(newItems)
|
||||
.zip(newItems)
|
||||
.forEach { (id, item) -> item.id = id.toInt() }
|
||||
|
|
|
@ -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 = {}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -170,4 +170,11 @@
|
|||
<string name="downloaded_file">Fichier téléchargé !</string>
|
||||
<string name="provide_full_url">Merci de fournir l\'URL entière de l\'API</string>
|
||||
<string name="provide_root_url">Merci de fournir l\'URL racine du service</string>
|
||||
<string name="order_field_tooltip">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.</string>
|
||||
<string name="date">Date</string>
|
||||
<string name="identifier">Identifiant</string>
|
||||
<string name="ascending">Ascendant</string>
|
||||
<string name="descending">Descendant</string>
|
||||
<string name="order_by">Ordonner par</string>
|
||||
<string name="with_direction">Avec comme direction</string>
|
||||
</resources>
|
|
@ -180,4 +180,11 @@
|
|||
<string name="downloaded_file">Downloaded file!</string>
|
||||
<string name="provide_full_url">Please provide the full API URL</string>
|
||||
<string name="provide_root_url">Please provide the service root URL</string>
|
||||
<string name="order_field_tooltip">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.</string>
|
||||
<string name="date">Date</string>
|
||||
<string name="identifier">Identifier</string>
|
||||
<string name="ascending">Ascending</string>
|
||||
<string name="descending">Descending</string>
|
||||
<string name="order_by">Order by</string>
|
||||
<string name="with_direction">With direction</string>
|
||||
</resources>
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
)
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue