Skip to content

Commit 92e31d5

Browse files
authored
Merge pull request #1169 from DimensionDev/feature/pagingkey
migrate timeline remote mediator paging key to database
2 parents f2bcd17 + 831e465 commit 92e31d5

File tree

60 files changed

+694
-890
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+694
-890
lines changed

shared/src/commonMain/kotlin/dev/dimension/flare/common/BaseRemoteMediator.kt

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,28 @@ internal abstract class BaseTimelineRemoteMediator(
5151
loadType: LoadType,
5252
state: PagingState<Int, DbPagingTimelineWithStatus>,
5353
): MediatorResult {
54-
val result = timeline(loadType, state)
54+
val request: Request =
55+
when (loadType) {
56+
LoadType.REFRESH -> Request.Refresh
57+
LoadType.PREPEND -> {
58+
val previousKey =
59+
database.pagingTimelineDao().getPagingKey(pagingKey)?.prevKey
60+
?: return MediatorResult.Success(endOfPaginationReached = true)
61+
Request.Prepend(previousKey)
62+
}
63+
LoadType.APPEND -> {
64+
val nextKey =
65+
database.pagingTimelineDao().getPagingKey(pagingKey)?.nextKey
66+
?: return MediatorResult.Success(endOfPaginationReached = true)
67+
Request.Append(nextKey)
68+
}
69+
}
70+
71+
val result =
72+
timeline(
73+
pageSize = state.config.pageSize,
74+
request = request,
75+
)
5576
database.connect {
5677
if (loadType == LoadType.REFRESH) {
5778
result.data.groupBy { it.timeline.pagingKey }.keys.forEach { key ->
@@ -60,27 +81,60 @@ internal abstract class BaseTimelineRemoteMediator(
6081
.delete(pagingKey = key)
6182
}
6283
database.pagingTimelineDao().deletePagingKey(pagingKey)
84+
database.pagingTimelineDao().insertPagingKey(
85+
DbPagingKey(
86+
pagingKey = pagingKey,
87+
nextKey = result.nextKey,
88+
prevKey = result.previousKey,
89+
),
90+
)
91+
} else if (loadType == LoadType.PREPEND && result.previousKey != null) {
92+
database.pagingTimelineDao().updatePagingKeyPrevKey(
93+
pagingKey = pagingKey,
94+
prevKey = result.previousKey,
95+
)
96+
} else if (loadType == LoadType.APPEND && result.nextKey != null) {
97+
database.pagingTimelineDao().updatePagingKeyNextKey(
98+
pagingKey = pagingKey,
99+
nextKey = result.nextKey,
100+
)
63101
}
64102
saveToDatabase(database, result.data)
65-
database
66-
.pagingTimelineDao()
67-
.insertPagingKey(DbPagingKey(pagingKey = pagingKey, nextKey = result.nextKey))
68103
}
69104
return MediatorResult.Success(
70-
endOfPaginationReached = result.endOfPaginationReached,
105+
endOfPaginationReached =
106+
result.endOfPaginationReached ||
107+
when (loadType) {
108+
LoadType.REFRESH -> false
109+
LoadType.PREPEND -> result.previousKey == null
110+
LoadType.APPEND -> result.nextKey == null
111+
},
71112
)
72113
}
73114

74115
abstract suspend fun timeline(
75-
loadType: LoadType,
76-
state: PagingState<Int, DbPagingTimelineWithStatus>,
116+
pageSize: Int,
117+
request: Request,
77118
): Result
78119

79120
data class Result(
80121
val endOfPaginationReached: Boolean,
81122
val data: List<DbPagingTimelineWithStatus> = emptyList(),
82123
val nextKey: String? = null,
124+
val previousKey: String? = null,
83125
)
126+
127+
sealed interface Request {
128+
data object Refresh : Request
129+
130+
data class Prepend(
131+
val previousKey: String,
132+
) : Request
133+
134+
data class Append(
135+
val nextKey: String,
136+
) : Request
137+
}
84138
}
85139

86140
internal fun interface BaseTimelinePagingSourceFactory<T : Any> : BaseTimelineLoader {

shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/CacaheDatabase.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,9 @@ internal expect object CacheDatabaseConstructor : RoomDatabaseConstructor<CacheD
5858
override fun initialize(): CacheDatabase
5959
}
6060

61-
internal suspend fun RoomDatabase.connect(block: suspend () -> Unit) {
61+
internal suspend fun <R> RoomDatabase.connect(block: suspend () -> R): R =
6262
useWriterConnection {
6363
it.immediateTransaction {
6464
block.invoke()
6565
}
6666
}
67-
}

shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/PagingTimelineDao.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,16 @@ internal interface PagingTimelineDao {
117117

118118
@Query("DELETE FROM DbPagingKey WHERE pagingKey = :pagingKey")
119119
suspend fun deletePagingKey(pagingKey: String)
120+
121+
@Query("UPDATE DbPagingKey SET nextKey = :nextKey WHERE pagingKey = :pagingKey")
122+
suspend fun updatePagingKeyNextKey(
123+
pagingKey: String,
124+
nextKey: String,
125+
)
126+
127+
@Query("UPDATE DbPagingKey SET prevKey = :prevKey WHERE pagingKey = :pagingKey")
128+
suspend fun updatePagingKeyPrevKey(
129+
pagingKey: String,
130+
prevKey: String,
131+
)
120132
}

shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/VVO.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ internal object VVO {
4545
}
4646
}
4747

48-
internal fun Status.toDbPagingTimeline(
48+
internal suspend fun Status.toDbPagingTimeline(
4949
accountKey: MicroBlogKey,
5050
pagingKey: String,
51-
sortIdProvider: (Status) -> Long = { it.createdAt?.toEpochMilliseconds() ?: 0L },
51+
sortIdProvider: suspend (Status) -> Long = { it.createdAt?.toEpochMilliseconds() ?: 0L },
5252
): DbPagingTimelineWithStatus =
5353
createDbPagingTimelineWithStatus(
5454
accountKey = accountKey,
@@ -86,10 +86,10 @@ private fun Status.toDbStatus(accountKey: MicroBlogKey): DbStatus =
8686
createdAt = createdAt ?: Clock.System.now(),
8787
)
8888

89-
internal fun Comment.toDbPagingTimeline(
89+
internal suspend fun Comment.toDbPagingTimeline(
9090
accountKey: MicroBlogKey,
9191
pagingKey: String,
92-
sortIdProvider: (Comment) -> Long = { it.createdAt?.toEpochMilliseconds() ?: 0L },
92+
sortIdProvider: suspend (Comment) -> Long = { it.createdAt?.toEpochMilliseconds() ?: 0L },
9393
): DbPagingTimelineWithStatus =
9494
createDbPagingTimelineWithStatus(
9595
accountKey = accountKey,

shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/XQT.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,14 @@ internal fun XQTTimeline.toDbPagingTimeline(
235235
tweets.tweetResults.result?.getRetweet()?.toDbStatusWithUser(accountKey)?.let {
236236
ReferenceType.Retweet to listOfNotNull(it)
237237
},
238-
tweets.tweetResults.result?.getQuoted()?.toDbStatusWithUser(accountKey)?.let {
239-
ReferenceType.Quote to listOfNotNull(it)
240-
},
238+
(
239+
tweets.tweetResults.result
240+
?.getRetweet()
241+
?.getQuoted() ?: tweets.tweetResults.result?.getQuoted()
242+
)?.toDbStatusWithUser(accountKey)
243+
?.let {
244+
ReferenceType.Quote to listOfNotNull(it)
245+
},
241246
parents
242247
.mapNotNull { it.tweets.toDbStatusWithUser(accountKey) }
243248
.takeIf { it.isNotEmpty() }
Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
package dev.dimension.flare.data.datasource.bluesky
22

33
import androidx.paging.ExperimentalPagingApi
4-
import androidx.paging.LoadType
5-
import androidx.paging.PagingState
64
import app.bsky.feed.GetFeedQueryParams
75
import dev.dimension.flare.common.BaseTimelineRemoteMediator
86
import dev.dimension.flare.data.database.cache.CacheDatabase
97
import dev.dimension.flare.data.database.cache.mapper.toDbPagingTimeline
10-
import dev.dimension.flare.data.database.cache.model.DbPagingTimelineWithStatus
118
import dev.dimension.flare.data.network.bluesky.BlueskyService
129
import dev.dimension.flare.model.MicroBlogKey
1310
import sh.christian.ozone.api.AtUri
@@ -22,51 +19,50 @@ internal class FeedTimelineRemoteMediator(
2219
database = database,
2320
) {
2421
override val pagingKey = "feed_timeline_$uri"
25-
var cursor: String? = null
2622

2723
override suspend fun timeline(
28-
loadType: LoadType,
29-
state: PagingState<Int, DbPagingTimelineWithStatus>,
24+
pageSize: Int,
25+
request: Request,
3026
): Result {
3127
val response =
32-
when (loadType) {
33-
LoadType.REFRESH ->
28+
when (request) {
29+
Request.Refresh ->
3430
service
3531
.getFeed(
3632
GetFeedQueryParams(
3733
feed = AtUri(atUri = uri),
38-
limit = state.config.pageSize.toLong(),
34+
limit = pageSize.toLong(),
3935
),
4036
).maybeResponse()
4137

42-
LoadType.PREPEND -> {
38+
is Request.Prepend -> {
4339
return Result(
4440
endOfPaginationReached = true,
4541
)
4642
}
4743

48-
LoadType.APPEND -> {
44+
is Request.Append -> {
4945
service
5046
.getFeed(
5147
GetFeedQueryParams(
5248
feed = AtUri(atUri = uri),
53-
limit = state.config.pageSize.toLong(),
54-
cursor = cursor,
49+
limit = pageSize.toLong(),
50+
cursor = request.nextKey,
5551
),
5652
).maybeResponse()
5753
}
5854
} ?: return Result(
5955
endOfPaginationReached = true,
6056
)
61-
cursor = response.cursor
6257

6358
return Result(
64-
endOfPaginationReached = cursor == null,
59+
endOfPaginationReached = response.cursor == null,
6560
data =
6661
response.feed.toDbPagingTimeline(
6762
accountKey = accountKey,
6863
pagingKey = pagingKey,
6964
),
65+
nextKey = response.cursor,
7066
)
7167
}
7268
}

shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/HomeTimelineRemoteMediator.kt

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
package dev.dimension.flare.data.datasource.bluesky
22

33
import androidx.paging.ExperimentalPagingApi
4-
import androidx.paging.LoadType
5-
import androidx.paging.PagingState
64
import app.bsky.feed.GetTimelineQueryParams
75
import dev.dimension.flare.common.BaseTimelineRemoteMediator
86
import dev.dimension.flare.common.InAppNotification
97
import dev.dimension.flare.common.Message
108
import dev.dimension.flare.data.database.cache.CacheDatabase
119
import dev.dimension.flare.data.database.cache.mapper.toDbPagingTimeline
12-
import dev.dimension.flare.data.database.cache.model.DbPagingTimelineWithStatus
1310
import dev.dimension.flare.data.network.bluesky.BlueskyService
1411
import dev.dimension.flare.data.repository.LoginExpiredException
1512
import dev.dimension.flare.model.MicroBlogKey
@@ -24,48 +21,49 @@ internal class HomeTimelineRemoteMediator(
2421
) : BaseTimelineRemoteMediator(
2522
database = database,
2623
) {
27-
var cursor: String? = null
2824
override val pagingKey: String = "home_$accountKey"
2925

26+
override suspend fun initialize(): InitializeAction = InitializeAction.SKIP_INITIAL_REFRESH
27+
3028
override suspend fun timeline(
31-
loadType: LoadType,
32-
state: PagingState<Int, DbPagingTimelineWithStatus>,
29+
pageSize: Int,
30+
request: Request,
3331
): Result {
3432
val response =
35-
when (loadType) {
36-
LoadType.PREPEND -> return Result(
33+
when (request) {
34+
is Request.Prepend -> return Result(
3735
endOfPaginationReached = true,
3836
)
3937

40-
LoadType.REFRESH -> {
38+
Request.Refresh -> {
4139
service
4240
.getTimeline(
4341
GetTimelineQueryParams(
44-
limit = state.config.pageSize.toLong(),
42+
limit = pageSize.toLong(),
4543
),
4644
).maybeResponse()
4745
}
4846

49-
LoadType.APPEND -> {
47+
is Request.Append -> {
5048
service
5149
.getTimeline(
5250
GetTimelineQueryParams(
53-
limit = state.config.pageSize.toLong(),
54-
cursor = cursor,
51+
limit = pageSize.toLong(),
52+
cursor = request.nextKey,
5553
),
5654
).maybeResponse()
5755
}
5856
} ?: return Result(
5957
endOfPaginationReached = true,
6058
)
61-
cursor = response.cursor
6259
return Result(
63-
endOfPaginationReached = cursor == null,
60+
endOfPaginationReached = response.cursor == null,
6461
data =
6562
response.feed.toDbPagingTimeline(
6663
accountKey = accountKey,
6764
pagingKey = pagingKey,
6865
),
66+
nextKey = response.cursor,
6967
)
7068
}
7169

0 commit comments

Comments
 (0)