fix String.inc() and String.dec() not being inverse operations (#2355)
This commit is contained in:
parent
60c32b3370
commit
b145fc9d50
4 changed files with 80 additions and 53 deletions
|
@ -143,7 +143,7 @@ class CachedTimelineViewModel @Inject constructor(
|
||||||
|
|
||||||
val nextPlaceholderId = timelineDao.getNextPlaceholderIdAfter(activeAccount.id, placeholderId)
|
val nextPlaceholderId = timelineDao.getNextPlaceholderIdAfter(activeAccount.id, placeholderId)
|
||||||
|
|
||||||
val response = api.homeTimeline(maxId = placeholderId.inc(), sinceId = nextPlaceholderId, limit = 20).await()
|
val response = api.homeTimeline(maxId = placeholderId.inc(), sinceId = nextPlaceholderId, limit = LOAD_AT_ONCE).await()
|
||||||
|
|
||||||
val statuses = response.body()
|
val statuses = response.body()
|
||||||
if (!response.isSuccessful || statuses == null) {
|
if (!response.isSuccessful || statuses == null) {
|
||||||
|
|
|
@ -17,27 +17,39 @@ fun randomAlphanumericString(count: Int): String {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We sort statuses by ID. Something we need to invent some ID for placeholder.
|
// We sort statuses by ID. Something we need to invent some ID for placeholder.
|
||||||
// Not sure if inc()/dec() should be made `operator` or not
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* "Increment" string so that during sorting it's bigger than [this].
|
* "Increment" string so that during sorting it's bigger than [this]. Inverse operation to [dec].
|
||||||
*/
|
*/
|
||||||
fun String.inc(): String {
|
fun String.inc(): String {
|
||||||
// We assume that we will stay in the safe range for now
|
|
||||||
val builder = this.toCharArray()
|
val builder = this.toCharArray()
|
||||||
builder[lastIndex] = builder[lastIndex].inc()
|
var i = builder.lastIndex
|
||||||
|
|
||||||
|
while (i >= 0) {
|
||||||
|
if (builder[i] < 'z') {
|
||||||
|
builder[i] = builder[i].inc()
|
||||||
return String(builder)
|
return String(builder)
|
||||||
|
} else {
|
||||||
|
builder[i] = '0'
|
||||||
|
}
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
return String(
|
||||||
|
CharArray(builder.size + 1) { index ->
|
||||||
|
if (index == 0) '0' else builder[index - 1]
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* "Decrement" string so that during sorting it's smaller than [this].
|
* "Decrement" string so that during sorting it's smaller than [this]. Inverse operation to [inc].
|
||||||
*/
|
*/
|
||||||
fun String.dec(): String {
|
fun String.dec(): String {
|
||||||
if (this.isEmpty()) return this
|
if (this.isEmpty()) return this
|
||||||
|
|
||||||
val builder = this.toCharArray()
|
val builder = this.toCharArray()
|
||||||
var i = builder.lastIndex
|
var i = builder.lastIndex
|
||||||
while (i > 0) {
|
while (i >= 0) {
|
||||||
if (builder[i] > '0') {
|
if (builder[i] > '0') {
|
||||||
builder[i] = builder[i].dec()
|
builder[i] = builder[i].dec()
|
||||||
return String(builder)
|
return String(builder)
|
||||||
|
@ -46,12 +58,7 @@ fun String.dec(): String {
|
||||||
}
|
}
|
||||||
i--
|
i--
|
||||||
}
|
}
|
||||||
return if (builder[0] > '1') {
|
return String(builder.copyOfRange(1, builder.size))
|
||||||
builder[0] = builder[0].dec()
|
|
||||||
String(builder)
|
|
||||||
} else {
|
|
||||||
String(builder.copyOfRange(1, builder.size))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,15 +78,6 @@ fun String.isLessThan(other: String): Boolean {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.idCompareTo(other: String): Int {
|
|
||||||
return when {
|
|
||||||
this === other -> 0
|
|
||||||
this.length < other.length -> -1
|
|
||||||
this.length > other.length -> 1
|
|
||||||
else -> this.compareTo(other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Spanned.trimTrailingWhitespace(): Spanned {
|
fun Spanned.trimTrailingWhitespace(): Spanned {
|
||||||
var i = length
|
var i = length
|
||||||
do {
|
do {
|
||||||
|
|
|
@ -27,22 +27,37 @@ class StringUtilsTest {
|
||||||
@Test
|
@Test
|
||||||
fun inc() {
|
fun inc() {
|
||||||
listOf(
|
listOf(
|
||||||
|
"10786565059022968z" to "107865650590229690",
|
||||||
"122" to "123",
|
"122" to "123",
|
||||||
"12A" to "12B",
|
"12A" to "12B",
|
||||||
"1" to "2"
|
"11z" to "120",
|
||||||
|
"0zz" to "100",
|
||||||
|
"zz" to "000",
|
||||||
|
"4zzbz" to "4zzc0",
|
||||||
|
"" to "0",
|
||||||
|
"1" to "2",
|
||||||
|
"0" to "1",
|
||||||
|
"AGdxwSQqT3pW4xrLJA" to "AGdxwSQqT3pW4xrLJB",
|
||||||
|
"AGdfqi1HnlBFVl0tkz" to "AGdfqi1HnlBFVl0tl0"
|
||||||
).forEach { (l, r) -> assertEquals("$l + 1 = $r", r, l.inc()) }
|
).forEach { (l, r) -> assertEquals("$l + 1 = $r", r, l.inc()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun dec() {
|
fun dec() {
|
||||||
listOf(
|
listOf(
|
||||||
|
"" to "",
|
||||||
|
"107865650590229690" to "10786565059022968z",
|
||||||
"123" to "122",
|
"123" to "122",
|
||||||
"12B" to "12A",
|
"12B" to "12A",
|
||||||
"120" to "11z",
|
"120" to "11z",
|
||||||
"100" to "zz",
|
"100" to "0zz",
|
||||||
|
"000" to "zz",
|
||||||
|
"4zzc0" to "4zzbz",
|
||||||
"0" to "",
|
"0" to "",
|
||||||
"" to "",
|
"2" to "1",
|
||||||
"2" to "1"
|
"1" to "0",
|
||||||
|
"AGdxwSQqT3pW4xrLJB" to "AGdxwSQqT3pW4xrLJA",
|
||||||
|
"AGdfqi1HnlBFVl0tl0" to "AGdfqi1HnlBFVl0tkz"
|
||||||
).forEach { (l, r) -> assertEquals("$l - 1 = $r", r, l.dec()) }
|
).forEach { (l, r) -> assertEquals("$l - 1 = $r", r, l.dec()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,7 +144,8 @@ class TimelineDaoTest {
|
||||||
makeStatus(statusId = 1)
|
makeStatus(statusId = 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
timelineDao.deleteRange(1, newStatuses.last().first.serverId, newStatuses.first().first.serverId)
|
val deletedCount = timelineDao.deleteRange(1, newStatuses.last().first.serverId, newStatuses.first().first.serverId)
|
||||||
|
assertEquals(3, deletedCount)
|
||||||
|
|
||||||
for ((status, author, reblogAuthor) in newStatuses) {
|
for ((status, author, reblogAuthor) in newStatuses) {
|
||||||
timelineDao.insertAccount(author)
|
timelineDao.insertAccount(author)
|
||||||
|
@ -169,9 +170,11 @@ class TimelineDaoTest {
|
||||||
fun deleteRange() = runBlocking {
|
fun deleteRange() = runBlocking {
|
||||||
val statuses = listOf(
|
val statuses = listOf(
|
||||||
makeStatus(statusId = 100),
|
makeStatus(statusId = 100),
|
||||||
|
makeStatus(statusId = 50),
|
||||||
makeStatus(statusId = 15),
|
makeStatus(statusId = 15),
|
||||||
makeStatus(statusId = 14),
|
makeStatus(statusId = 14),
|
||||||
makeStatus(statusId = 13),
|
makeStatus(statusId = 13),
|
||||||
|
makeStatus(statusId = 13, accountId = 2),
|
||||||
makeStatus(statusId = 12),
|
makeStatus(statusId = 12),
|
||||||
makeStatus(statusId = 11),
|
makeStatus(statusId = 11),
|
||||||
makeStatus(statusId = 9)
|
makeStatus(statusId = 9)
|
||||||
|
@ -185,20 +188,31 @@ class TimelineDaoTest {
|
||||||
timelineDao.insertStatus(status)
|
timelineDao.insertStatus(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
timelineDao.deleteRange(1, "12", "14")
|
assertEquals(3, timelineDao.deleteRange(1, "12", "14"))
|
||||||
|
assertEquals(0, timelineDao.deleteRange(1, "80", "80"))
|
||||||
|
assertEquals(0, timelineDao.deleteRange(1, "60", "80"))
|
||||||
|
assertEquals(0, timelineDao.deleteRange(1, "5", "8"))
|
||||||
|
assertEquals(0, timelineDao.deleteRange(1, "101", "1000"))
|
||||||
|
assertEquals(1, timelineDao.deleteRange(1, "50", "50"))
|
||||||
|
|
||||||
val pagingSource = timelineDao.getStatusesForAccount(1)
|
val loadParams: PagingSource.LoadParams<Int> = PagingSource.LoadParams.Refresh(null, 100, false)
|
||||||
val loadResult = pagingSource.load(PagingSource.LoadParams.Refresh(null, 100, false))
|
|
||||||
val loadedStatuses = (loadResult as PagingSource.LoadResult.Page).data
|
|
||||||
|
|
||||||
val remainingStatuses = listOf(
|
val statusesAccount1 = (timelineDao.getStatusesForAccount(1).load(loadParams) as PagingSource.LoadResult.Page).data
|
||||||
|
val statusesAccount2 = (timelineDao.getStatusesForAccount(2).load(loadParams) as PagingSource.LoadResult.Page).data
|
||||||
|
|
||||||
|
val remainingStatusesAccount1 = listOf(
|
||||||
makeStatus(statusId = 100),
|
makeStatus(statusId = 100),
|
||||||
makeStatus(statusId = 15),
|
makeStatus(statusId = 15),
|
||||||
makeStatus(statusId = 11),
|
makeStatus(statusId = 11),
|
||||||
makeStatus(statusId = 9)
|
makeStatus(statusId = 9)
|
||||||
)
|
)
|
||||||
|
|
||||||
assertStatuses(remainingStatuses, loadedStatuses)
|
val remainingStatusesAccount2 = listOf(
|
||||||
|
makeStatus(statusId = 13, accountId = 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertStatuses(remainingStatusesAccount1, statusesAccount1)
|
||||||
|
assertStatuses(remainingStatusesAccount2, statusesAccount2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -325,7 +339,7 @@ class TimelineDaoTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals("99", timelineDao.getNextPlaceholderIdAfter(1, "1000"))
|
assertEquals("99", timelineDao.getNextPlaceholderIdAfter(1, "1000"))
|
||||||
assertEquals("94", timelineDao.getNextPlaceholderIdAfter(1, "97"))
|
assertEquals("94", timelineDao.getNextPlaceholderIdAfter(1, "99"))
|
||||||
assertNull(timelineDao.getNextPlaceholderIdAfter(1, "90"))
|
assertNull(timelineDao.getNextPlaceholderIdAfter(1, "90"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,28 +378,28 @@ class TimelineDaoTest {
|
||||||
domain: String = "mastodon.example"
|
domain: String = "mastodon.example"
|
||||||
): Triple<TimelineStatusEntity, TimelineAccountEntity, TimelineAccountEntity?> {
|
): Triple<TimelineStatusEntity, TimelineAccountEntity, TimelineAccountEntity?> {
|
||||||
val author = TimelineAccountEntity(
|
val author = TimelineAccountEntity(
|
||||||
authorServerId,
|
serverId = authorServerId,
|
||||||
accountId,
|
timelineUserId = accountId,
|
||||||
"localUsername@$domain",
|
localUsername = "localUsername@$domain",
|
||||||
"username@$domain",
|
username = "username@$domain",
|
||||||
"displayName",
|
displayName = "displayName",
|
||||||
"blah",
|
url = "blah",
|
||||||
"avatar",
|
avatar = "avatar",
|
||||||
"[\"tusky\": \"http://tusky.cool/emoji.jpg\"]",
|
emojis = "[\"tusky\": \"http://tusky.cool/emoji.jpg\"]",
|
||||||
false
|
bot = false
|
||||||
)
|
)
|
||||||
|
|
||||||
val reblogAuthor = if (reblog) {
|
val reblogAuthor = if (reblog) {
|
||||||
TimelineAccountEntity(
|
TimelineAccountEntity(
|
||||||
"R$authorServerId",
|
serverId = "R$authorServerId",
|
||||||
accountId,
|
timelineUserId = accountId,
|
||||||
"RlocalUsername",
|
localUsername = "RlocalUsername",
|
||||||
"Rusername",
|
username = "Rusername",
|
||||||
"RdisplayName",
|
displayName = "RdisplayName",
|
||||||
"Rblah",
|
url = "Rblah",
|
||||||
"Ravatar",
|
avatar = "Ravatar",
|
||||||
"[]",
|
emojis = "[]",
|
||||||
false
|
bot = false
|
||||||
)
|
)
|
||||||
} else null
|
} else null
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue