refactor: push down more data operations to SQL
This commit is contained in:
@@ -39,8 +39,11 @@ class SavedPostsWidget() : GlanceAppWidget() {
|
||||
|
||||
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||
val posts =
|
||||
(context.applicationContext as ClawApplication).appGraph.savedPostsRepository.savedPosts
|
||||
val postWindow = posts.first().take(50).map(UIPost::fromSavedPost).toImmutableList()
|
||||
(context.applicationContext as ClawApplication)
|
||||
.appGraph
|
||||
.savedPostsRepository
|
||||
.getRecentPosts(50)
|
||||
val postWindow = posts.first().map(UIPost::fromSavedPost).toImmutableList()
|
||||
provideContent {
|
||||
GlanceTheme(
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) GlanceTheme.colors
|
||||
|
||||
@@ -40,7 +40,6 @@ import java.io.OutputStream
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
@@ -87,9 +86,18 @@ class ClawViewModel(
|
||||
)
|
||||
.flow
|
||||
val savedPosts = savedPostsRepository.savedPosts.map { it.map(UIPost.Companion::fromSavedPost) }
|
||||
val savedPostsCount = savedPosts.map { it.size.toLong() }
|
||||
val savedPostsCount = savedPostsRepository.savedPosts.map { it.size.toLong() }
|
||||
val savedPostsByMonth
|
||||
get() = savedPosts.map(::groupSavedPosts)
|
||||
get() =
|
||||
savedPostsRepository.savedPostsSortedByDate.map { posts ->
|
||||
posts
|
||||
.map(UIPost.Companion::fromSavedPost)
|
||||
.groupBy { post ->
|
||||
val time = post.createdAt.toLocalDateTime()
|
||||
"${time.month.name.lowercase().capitalize(Locale.current)} ${time.year}"
|
||||
}
|
||||
.toImmutableMap()
|
||||
}
|
||||
|
||||
var searchQuery by mutableStateOf("")
|
||||
|
||||
@@ -101,27 +109,6 @@ class ClawViewModel(
|
||||
viewModelScope.launch { readPostsRepository.readPosts.collectLatest { _readPosts = it } }
|
||||
}
|
||||
|
||||
private fun groupSavedPosts(items: List<UIPost>): ImmutableMap<String, List<UIPost>> {
|
||||
val sorted =
|
||||
items.sortedWith { post1, post2 ->
|
||||
val post1Date = post1.createdAt.toLocalDateTime()
|
||||
val post2Date = post2.createdAt.toLocalDateTime()
|
||||
if (post2Date.isBefore(post1Date)) {
|
||||
-1
|
||||
} else if (post2Date.isAfter(post1Date)) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
return sorted
|
||||
.groupBy { post ->
|
||||
val time = post.createdAt.toLocalDateTime()
|
||||
"${time.month.name.lowercase().capitalize(Locale.current)} ${time.year}"
|
||||
}
|
||||
.toImmutableMap()
|
||||
}
|
||||
|
||||
fun toggleSave(post: UIPost) {
|
||||
viewModelScope.launch {
|
||||
savedPostsRepository.toggleSave(post)
|
||||
|
||||
+8
-2
@@ -17,7 +17,6 @@ import dev.msfjarvis.claw.model.toSavedPost
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.github.aakira.napier.Napier
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Inject
|
||||
@@ -27,12 +26,19 @@ class SavedPostsRepository(
|
||||
@DatabaseWriteDispatcher private val writeDispatcher: CoroutineDispatcher,
|
||||
) {
|
||||
val savedPosts = savedPostQueries.selectAllPosts().asFlow().mapToList(readDispatcher)
|
||||
val savedPostsSortedByDate =
|
||||
savedPostQueries.selectAllPostsSortedByDate().asFlow().mapToList(readDispatcher)
|
||||
|
||||
fun getPostsFromLastNDays(days: Long) =
|
||||
savedPostQueries.selectPostsFromLastNDays(days.toString()).asFlow().mapToList(readDispatcher)
|
||||
|
||||
fun getRecentPosts(limit: Long) =
|
||||
savedPostQueries.selectRecentPosts(limit).asFlow().mapToList(readDispatcher)
|
||||
|
||||
suspend fun toggleSave(post: UIPost) {
|
||||
if (savedPosts.firstOrNull().orEmpty().any { it.shortId == post.shortId }) {
|
||||
val exists =
|
||||
withContext(readDispatcher) { savedPostQueries.postExists(post.shortId).executeAsOne() }
|
||||
if (exists) {
|
||||
Napier.d(tag = TAG) { "Removing post: ${post.shortId}" }
|
||||
withContext(writeDispatcher) { savedPostQueries.deletePost(post.shortId) }
|
||||
} else {
|
||||
|
||||
@@ -42,3 +42,21 @@ deletePost:
|
||||
DELETE
|
||||
FROM SavedPost
|
||||
WHERE shortId = ?;
|
||||
|
||||
postExists:
|
||||
SELECT EXISTS(
|
||||
SELECT 1
|
||||
FROM SavedPost
|
||||
WHERE shortId = ?
|
||||
);
|
||||
|
||||
selectRecentPosts:
|
||||
SELECT *
|
||||
FROM SavedPost
|
||||
ORDER BY datetime(createdAt) DESC
|
||||
LIMIT :limit;
|
||||
|
||||
selectAllPostsSortedByDate:
|
||||
SELECT *
|
||||
FROM SavedPost
|
||||
ORDER BY datetime(createdAt) DESC;
|
||||
|
||||
+89
@@ -137,6 +137,95 @@ class SavedPostQueriesTest {
|
||||
assertThat(postsFromLast30Days.map { it.shortId }).doesNotContain("old_1")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `postExists returns true when post exists`() {
|
||||
val post = createTestData(1).first()
|
||||
postQueries.insertOrReplacePost(post)
|
||||
|
||||
val exists = postQueries.postExists("test_id_1").executeAsOne()
|
||||
|
||||
assertThat(exists).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `postExists returns false when post does not exist`() {
|
||||
val exists = postQueries.postExists("nonexistent_id").executeAsOne()
|
||||
|
||||
assertThat(exists).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `selectRecentPosts returns correct number of posts`() {
|
||||
val posts = createTestData(10)
|
||||
posts.forEach { postQueries.insertOrReplacePost(it) }
|
||||
|
||||
val recentPosts = postQueries.selectRecentPosts(5).executeAsList()
|
||||
|
||||
assertThat(recentPosts).hasSize(5)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `selectRecentPosts returns all posts when limit exceeds count`() {
|
||||
val posts = createTestData(3)
|
||||
posts.forEach { postQueries.insertOrReplacePost(it) }
|
||||
|
||||
val recentPosts = postQueries.selectRecentPosts(10).executeAsList()
|
||||
|
||||
assertThat(recentPosts).hasSize(3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `selectRecentPosts returns posts in descending date order`() {
|
||||
val now = Instant.now()
|
||||
val formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME
|
||||
fun dateOffset(daysToSubtract: Long): String {
|
||||
return formatter.format(
|
||||
now.minus(daysToSubtract, ChronoUnit.DAYS).atOffset(java.time.ZoneOffset.UTC)
|
||||
)
|
||||
}
|
||||
|
||||
val oldest = createPostWithDate(id = "oldest", createdAt = dateOffset(30))
|
||||
val middle = createPostWithDate(id = "middle", createdAt = dateOffset(15))
|
||||
val newest = createPostWithDate(id = "newest", createdAt = dateOffset(1))
|
||||
|
||||
postQueries.insertOrReplacePost(oldest)
|
||||
postQueries.insertOrReplacePost(middle)
|
||||
postQueries.insertOrReplacePost(newest)
|
||||
|
||||
val recentPosts = postQueries.selectRecentPosts(10).executeAsList()
|
||||
|
||||
assertThat(recentPosts.map { it.shortId })
|
||||
.containsExactly("newest", "middle", "oldest")
|
||||
.inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `selectAllPostsSortedByDate returns posts in descending date order`() {
|
||||
val now = Instant.now()
|
||||
val formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME
|
||||
fun dateOffset(daysToSubtract: Long): String {
|
||||
return formatter.format(
|
||||
now.minus(daysToSubtract, ChronoUnit.DAYS).atOffset(java.time.ZoneOffset.UTC)
|
||||
)
|
||||
}
|
||||
|
||||
val post1 = createPostWithDate(id = "post_1", createdAt = dateOffset(20))
|
||||
val post2 = createPostWithDate(id = "post_2", createdAt = dateOffset(5))
|
||||
val post3 = createPostWithDate(id = "post_3", createdAt = dateOffset(10))
|
||||
val post4 = createPostWithDate(id = "post_4", createdAt = dateOffset(1))
|
||||
|
||||
postQueries.insertOrReplacePost(post1)
|
||||
postQueries.insertOrReplacePost(post2)
|
||||
postQueries.insertOrReplacePost(post3)
|
||||
postQueries.insertOrReplacePost(post4)
|
||||
|
||||
val sortedPosts = postQueries.selectAllPostsSortedByDate().executeAsList()
|
||||
|
||||
assertThat(sortedPosts.map { it.shortId })
|
||||
.containsExactly("post_4", "post_2", "post_3", "post_1")
|
||||
.inOrder()
|
||||
}
|
||||
|
||||
private fun createTestData(count: Int): ArrayList<SavedPost> {
|
||||
val posts = arrayListOf<SavedPost>()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user