Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a70924cab | ||
|
|
ae362c2e03 | ||
|
|
83ca0e6167 | ||
|
|
71977c5b2c | ||
|
|
047d6badb0 | ||
|
|
5c0b5bb4f7 | ||
|
|
7762733da2 | ||
|
|
9f705ca02a |
@@ -54,7 +54,7 @@ jobs:
|
||||
echo VERSION="${VERSION}" >> $GITHUB_ENV
|
||||
|
||||
- name: Update changelog
|
||||
uses: msfjarvis/keep-a-changelog-new-release@bf43ae42f4e42f137572950af426efcf6e777207 # 2.1.0
|
||||
uses: msfjarvis/keep-a-changelog-new-release@78cddbe3549f1c9263a2d754446d3765e865c518 # v2.1.1
|
||||
with:
|
||||
tag: v${{ env.VERSION }}
|
||||
version: ${{ env.VERSION }}
|
||||
|
||||
+10
-1
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.43.0] - 2024-03-17
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix crash when trying to open a comments-only post from
|
||||
the home screen widget
|
||||
|
||||
## [1.42.0] - 2024-03-16
|
||||
|
||||
### Fixed
|
||||
@@ -368,7 +375,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
* Initial Play Store release
|
||||
|
||||
[Unreleased]: https://github.com/msfjarvis/compose-lobsters/compare/v1.42.0...HEAD
|
||||
[Unreleased]: https://github.com/msfjarvis/compose-lobsters/compare/v1.43.0...HEAD
|
||||
|
||||
[1.43.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.42.0...v1.43.0
|
||||
|
||||
[1.42.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.41.0...v1.42.0
|
||||
|
||||
|
||||
@@ -43,9 +43,10 @@ fun WidgetListEntry(post: UIPost, modifier: GlanceModifier = GlanceModifier) {
|
||||
val titleStyle = MaterialTheme.typography.titleMedium
|
||||
val commentsAction =
|
||||
actionStartActivity<MainActivity>(actionParametersOf(destinationKey to post.shortId))
|
||||
// If the URL starts with a /, it's a relative URL and we should open the comments page directly.
|
||||
val postAction =
|
||||
if (post.url.isNotEmpty()) actionStartActivity(Intent(Intent.ACTION_VIEW, Uri.parse(post.url)))
|
||||
else commentsAction
|
||||
if (post.url.startsWith('/')) commentsAction
|
||||
else actionStartActivity(Intent(Intent.ACTION_VIEW, Uri.parse(post.url)))
|
||||
Box(modifier.padding(8.dp)) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2021-2023 Harsh Shandilya.
|
||||
* Copyright © 2021-2024 Harsh Shandilya.
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
@@ -13,8 +13,12 @@ import com.slack.eithernet.ApiResult.Success
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import dev.msfjarvis.claw.android.viewmodel.ReadPostsRepository
|
||||
import dev.msfjarvis.claw.android.viewmodel.SavedPostsRepository
|
||||
import dev.msfjarvis.claw.core.injection.IODispatcher
|
||||
import dev.msfjarvis.claw.model.LobstersPost
|
||||
import dev.msfjarvis.claw.model.UIPost
|
||||
import dev.msfjarvis.claw.model.toUIPost
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -24,15 +28,25 @@ class LobstersPagingSource
|
||||
constructor(
|
||||
@Assisted private val remoteFetcher: RemoteFetcher<LobstersPost>,
|
||||
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
) : PagingSource<Int, LobstersPost>() {
|
||||
private val savedPostsRepository: SavedPostsRepository,
|
||||
private val readPostsRepository: ReadPostsRepository,
|
||||
) : PagingSource<Int, UIPost>() {
|
||||
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, LobstersPost> {
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, UIPost> {
|
||||
val page = params.key ?: STARTING_PAGE_INDEX
|
||||
return when (val result = withContext(ioDispatcher) { remoteFetcher.getItemsAtPage(page) }) {
|
||||
is Success ->
|
||||
LoadResult.Page(
|
||||
itemsBefore = (page - 1) * PAGE_SIZE,
|
||||
data = result.value,
|
||||
data =
|
||||
result.value.map {
|
||||
it
|
||||
.toUIPost()
|
||||
.copy(
|
||||
isSaved = savedPostsRepository.isPostSaved(it.shortId),
|
||||
isRead = readPostsRepository.isPostRead(it.shortId),
|
||||
)
|
||||
},
|
||||
prevKey = if (page == STARTING_PAGE_INDEX) null else page - 1,
|
||||
nextKey = page + 1,
|
||||
)
|
||||
@@ -43,7 +57,7 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getRefreshKey(state: PagingState<Int, LobstersPost>): Int? {
|
||||
override fun getRefreshKey(state: PagingState<Int, UIPost>): Int? {
|
||||
return state.anchorPosition?.let { anchorPosition ->
|
||||
(anchorPosition / PAGE_SIZE).coerceAtLeast(STARTING_PAGE_INDEX)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2021-2023 Harsh Shandilya.
|
||||
* Copyright © 2021-2024 Harsh Shandilya.
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
@@ -14,9 +14,12 @@ import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import dev.msfjarvis.claw.android.paging.LobstersPagingSource.Companion.PAGE_SIZE
|
||||
import dev.msfjarvis.claw.android.paging.LobstersPagingSource.Companion.STARTING_PAGE_INDEX
|
||||
import dev.msfjarvis.claw.android.viewmodel.ReadPostsRepository
|
||||
import dev.msfjarvis.claw.android.viewmodel.SavedPostsRepository
|
||||
import dev.msfjarvis.claw.api.LobstersSearchApi
|
||||
import dev.msfjarvis.claw.core.injection.IODispatcher
|
||||
import dev.msfjarvis.claw.model.LobstersPost
|
||||
import dev.msfjarvis.claw.model.UIPost
|
||||
import dev.msfjarvis.claw.model.toUIPost
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -33,14 +36,16 @@ constructor(
|
||||
private val searchApi: LobstersSearchApi,
|
||||
@Assisted private val queryProvider: () -> String,
|
||||
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
) : PagingSource<Int, LobstersPost>() {
|
||||
override fun getRefreshKey(state: PagingState<Int, LobstersPost>): Int? {
|
||||
private val savedPostsRepository: SavedPostsRepository,
|
||||
private val readPostsRepository: ReadPostsRepository,
|
||||
) : PagingSource<Int, UIPost>() {
|
||||
override fun getRefreshKey(state: PagingState<Int, UIPost>): Int? {
|
||||
return state.anchorPosition?.let { anchorPosition ->
|
||||
(anchorPosition / PAGE_SIZE).coerceAtLeast(STARTING_PAGE_INDEX)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, LobstersPost> {
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, UIPost> {
|
||||
val searchQuery = queryProvider()
|
||||
// If there is no query, we don't need to call the API at all.
|
||||
if (searchQuery.isEmpty()) {
|
||||
@@ -55,7 +60,15 @@ constructor(
|
||||
val nextKey = if (result.value.isEmpty()) null else page + 1
|
||||
LoadResult.Page(
|
||||
itemsBefore = (page - 1) * PAGE_SIZE,
|
||||
data = result.value,
|
||||
data =
|
||||
result.value.map {
|
||||
it
|
||||
.toUIPost()
|
||||
.copy(
|
||||
isSaved = savedPostsRepository.isPostSaved(it.shortId),
|
||||
isRead = readPostsRepository.isPostRead(it.shortId),
|
||||
)
|
||||
},
|
||||
prevKey = if (page == STARTING_PAGE_INDEX) null else page - 1,
|
||||
nextKey = nextKey,
|
||||
)
|
||||
|
||||
@@ -18,9 +18,7 @@ import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import androidx.paging.map
|
||||
import com.deliveryhero.whetstone.app.ApplicationScope
|
||||
import com.deliveryhero.whetstone.viewmodel.ContributesViewModel
|
||||
import com.slack.eithernet.ApiResult.Failure
|
||||
@@ -35,10 +33,8 @@ import dev.msfjarvis.claw.api.LobstersApi
|
||||
import dev.msfjarvis.claw.core.injection.IODispatcher
|
||||
import dev.msfjarvis.claw.core.injection.MainDispatcher
|
||||
import dev.msfjarvis.claw.model.Comment
|
||||
import dev.msfjarvis.claw.model.LobstersPost
|
||||
import dev.msfjarvis.claw.model.UIPost
|
||||
import dev.msfjarvis.claw.model.fromSavedPost
|
||||
import dev.msfjarvis.claw.model.toSavedPost
|
||||
import dev.msfjarvis.claw.model.toUIPost
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
@@ -79,7 +75,6 @@ constructor(
|
||||
pagingSourceFactory = { pagingSourceFactory.create(api::getHottestPosts) },
|
||||
)
|
||||
.flow
|
||||
.map(::mapToUIPost)
|
||||
.cachedIn(viewModelScope)
|
||||
val newestPosts =
|
||||
Pager(
|
||||
@@ -88,7 +83,6 @@ constructor(
|
||||
pagingSourceFactory = { pagingSourceFactory.create(api::getNewestPosts) },
|
||||
)
|
||||
.flow
|
||||
.map(::mapToUIPost)
|
||||
.cachedIn(viewModelScope)
|
||||
val searchResults =
|
||||
Pager(
|
||||
@@ -97,7 +91,6 @@ constructor(
|
||||
pagingSourceFactory = { searchPagingSourceFactory.create { searchQuery } },
|
||||
)
|
||||
.flow
|
||||
.map(::mapToUIPost)
|
||||
val savedPosts = savedPostsRepository.savedPosts.map { it.map(UIPost.Companion::fromSavedPost) }
|
||||
val savedPostsByMonth
|
||||
get() = savedPosts.map(::groupSavedPosts)
|
||||
@@ -112,13 +105,6 @@ constructor(
|
||||
viewModelScope.launch { readPostsRepository.readPosts.collectLatest { _readPosts = it } }
|
||||
}
|
||||
|
||||
private fun mapToUIPost(pagingData: PagingData<LobstersPost>): PagingData<UIPost> {
|
||||
return pagingData.map { post ->
|
||||
val uiPost = post.toUIPost()
|
||||
uiPost.copy(isSaved = isPostSaved(uiPost), isRead = isPostRead(uiPost))
|
||||
}
|
||||
}
|
||||
|
||||
private fun groupSavedPosts(items: List<UIPost>): ImmutableMap<String, List<UIPost>> {
|
||||
val sorted =
|
||||
items.sortedWith { post1, post2 ->
|
||||
@@ -140,22 +126,9 @@ constructor(
|
||||
.toImmutableMap()
|
||||
}
|
||||
|
||||
private fun isPostSaved(post: UIPost): Boolean {
|
||||
return _savedPosts.contains(post.shortId)
|
||||
}
|
||||
|
||||
private fun isPostRead(post: UIPost): Boolean {
|
||||
return _readPosts.contains(post.shortId)
|
||||
}
|
||||
|
||||
fun toggleSave(post: UIPost) {
|
||||
viewModelScope.launch(ioDispatcher) {
|
||||
val saved = isPostSaved(post)
|
||||
if (saved) {
|
||||
savedPostsRepository.removePost(post.toSavedPost())
|
||||
} else {
|
||||
savedPostsRepository.savePost(post.toSavedPost())
|
||||
}
|
||||
viewModelScope.launch {
|
||||
savedPostsRepository.toggleSave(post)
|
||||
withContext(mainDispatcher) { SavedPostsWidget(savedPosts).updateAll(getApplication()) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,4 +25,8 @@ constructor(
|
||||
suspend fun markRead(postId: String) {
|
||||
withContext(dbDispatcher) { readPostsQueries.markRead(postId) }
|
||||
}
|
||||
|
||||
suspend fun isPostRead(shortId: String): Boolean {
|
||||
return withContext(dbDispatcher) { readPostsQueries.isPostRead(shortId).executeAsOne() }
|
||||
}
|
||||
}
|
||||
|
||||
+13
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2021-2023 Harsh Shandilya.
|
||||
* Copyright © 2021-2024 Harsh Shandilya.
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
@@ -11,6 +11,8 @@ import app.cash.sqldelight.coroutines.mapToList
|
||||
import dev.msfjarvis.claw.core.injection.DatabaseDispatcher
|
||||
import dev.msfjarvis.claw.database.local.SavedPost
|
||||
import dev.msfjarvis.claw.database.local.SavedPostQueries
|
||||
import dev.msfjarvis.claw.model.UIPost
|
||||
import dev.msfjarvis.claw.model.toSavedPost
|
||||
import io.github.aakira.napier.Napier
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
@@ -24,9 +26,14 @@ constructor(
|
||||
) {
|
||||
val savedPosts = savedPostQueries.selectAllPosts().asFlow().mapToList(dbDispatcher)
|
||||
|
||||
suspend fun savePost(post: SavedPost) {
|
||||
Napier.d(tag = TAG) { "Saving post: ${post.shortId}" }
|
||||
withContext(dbDispatcher) { savedPostQueries.insertOrReplacePost(post) }
|
||||
suspend fun toggleSave(post: UIPost) {
|
||||
if (post.isSaved) {
|
||||
Napier.d(tag = TAG) { "Removing post: ${post.shortId}" }
|
||||
withContext(dbDispatcher) { savedPostQueries.deletePost(post.shortId) }
|
||||
} else {
|
||||
Napier.d(tag = TAG) { "Saving post: ${post.shortId}" }
|
||||
withContext(dbDispatcher) { savedPostQueries.insertOrReplacePost(post.toSavedPost()) }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun savePosts(posts: List<SavedPost>) {
|
||||
@@ -38,9 +45,8 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun removePost(post: SavedPost) {
|
||||
Napier.d(tag = TAG) { "Removing post: ${post.shortId}" }
|
||||
withContext(dbDispatcher) { savedPostQueries.deletePost(post.shortId) }
|
||||
suspend fun isPostSaved(postId: String): Boolean {
|
||||
return withContext(dbDispatcher) { savedPostQueries.isPostSaved(postId).executeAsOne() }
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# This file was automatically generated by 'versioning-plugin'. DO NOT EDIT MANUALLY.
|
||||
#
|
||||
versioning-plugin.versionCode=14200
|
||||
versioning-plugin.versionName=1.42.0
|
||||
versioning-plugin.versionCode=14300
|
||||
versioning-plugin.versionName=1.43.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2022-2023 Harsh Shandilya.
|
||||
* Copyright © 2022-2024 Harsh Shandilya.
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
@@ -92,7 +92,9 @@ interface OkHttpModule {
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun provideSentryInterceptor(): Interceptor {
|
||||
return SentryOkHttpInterceptor()
|
||||
// Disable capturing of failed requests since there is no real upstream for this app,
|
||||
// if something is going wrong it is almost always someone else's problem.
|
||||
return SentryOkHttpInterceptor(captureFailedRequests = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,13 @@ CREATE TABLE ReadPosts(
|
||||
id TEXT NOT NULL PRIMARY KEY
|
||||
);
|
||||
|
||||
isPostRead:
|
||||
SELECT EXISTS(
|
||||
SELECT 1
|
||||
FROM ReadPosts
|
||||
WHERE id = ?
|
||||
) AS isRead;
|
||||
|
||||
selectAllPosts:
|
||||
SELECT *
|
||||
FROM ReadPosts;
|
||||
|
||||
@@ -14,6 +14,13 @@ CREATE TABLE IF NOT EXISTS SavedPost(
|
||||
description TEXT NOT NULL DEFAULT ""
|
||||
);
|
||||
|
||||
isPostSaved:
|
||||
SELECT EXISTS(
|
||||
SELECT 1
|
||||
FROM SavedPost
|
||||
WHERE shortId = ?
|
||||
) AS isSaved;
|
||||
|
||||
insertOrReplacePost:
|
||||
INSERT OR REPLACE
|
||||
INTO SavedPost
|
||||
|
||||
@@ -11,7 +11,7 @@ glance = "1.0.0"
|
||||
junit = "5.10.2"
|
||||
konvert = "3.0.1"
|
||||
kotlin = "1.9.23"
|
||||
kotlinResult = "1.1.20"
|
||||
kotlinResult = "2.0.0"
|
||||
lifecycle = "2.7.0"
|
||||
retrofit = "2.9.0"
|
||||
richtext = "1.0.0-alpha01"
|
||||
|
||||
Reference in New Issue
Block a user