mirror of
https://github.com/msfjarvis/compose-lobsters
synced 2025-08-17 23:47:02 +05:30
Merge #137
137: Add support for refreshing hottest posts r=msfjarvis a=msfjarvis Fixes #133 bors r+ Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
commit
790e785dd3
11 changed files with 130 additions and 4 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -103,6 +103,7 @@ obj/
|
||||||
.idea/dynamic.xml
|
.idea/dynamic.xml
|
||||||
.idea/uiDesigner.xml
|
.idea/uiDesigner.xml
|
||||||
.idea/assetWizardSettings.xml
|
.idea/assetWizardSettings.xml
|
||||||
|
.idea/deploymentTargetDropDown.xml
|
||||||
|
|
||||||
# OS-specific files
|
# OS-specific files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 6.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 8 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.9 KiB |
|
@ -0,0 +1,74 @@
|
||||||
|
package dev.msfjarvis.lobsters.ui.main
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.asAndroidBitmap
|
||||||
|
import androidx.compose.ui.test.captureToImage
|
||||||
|
import androidx.compose.ui.test.junit4.createComposeRule
|
||||||
|
import androidx.compose.ui.test.onRoot
|
||||||
|
import com.karumi.shot.ScreenshotTest
|
||||||
|
import dev.msfjarvis.lobsters.ui.DarkTestTheme
|
||||||
|
import dev.msfjarvis.lobsters.ui.LightTestTheme
|
||||||
|
import dev.msfjarvis.lobsters.ui.navigation.Destination
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class LobstersTopBarTest : ScreenshotTest {
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val composeTestRule = createComposeRule()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun showsRefreshIconWhenOnHottestPostsScreen_DarkTheme() {
|
||||||
|
composeTestRule.setContent {
|
||||||
|
DarkTestTheme {
|
||||||
|
LobstersTopBar(
|
||||||
|
currentDestination = Destination.Hottest,
|
||||||
|
reloadPosts = { /*TODO*/ }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compareScreenshot(composeTestRule.onRoot().captureToImage().asAndroidBitmap())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun showsRefreshIconWhenOnHottestPostsScreen_LightTheme() {
|
||||||
|
composeTestRule.setContent {
|
||||||
|
LightTestTheme {
|
||||||
|
LobstersTopBar(
|
||||||
|
currentDestination = Destination.Hottest,
|
||||||
|
reloadPosts = { /*TODO*/ }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compareScreenshot(composeTestRule.onRoot().captureToImage().asAndroidBitmap())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun doesNotShowRefreshIconWhenOnSavedPostsScreen_DarkTheme() {
|
||||||
|
composeTestRule.setContent {
|
||||||
|
DarkTestTheme {
|
||||||
|
LobstersTopBar(
|
||||||
|
currentDestination = Destination.Saved,
|
||||||
|
reloadPosts = { /*TODO*/ }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compareScreenshot(composeTestRule.onRoot().captureToImage().asAndroidBitmap())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun doesNotShowRefreshIconWhenOnSavedPostsScreen_LightTheme() {
|
||||||
|
composeTestRule.setContent {
|
||||||
|
LightTestTheme {
|
||||||
|
LobstersTopBar(
|
||||||
|
currentDestination = Destination.Saved,
|
||||||
|
reloadPosts = { /*TODO*/ }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compareScreenshot(composeTestRule.onRoot().captureToImage().asAndroidBitmap())
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,9 +4,8 @@ import androidx.paging.PagingSource
|
||||||
import androidx.paging.PagingState
|
import androidx.paging.PagingState
|
||||||
import dev.msfjarvis.lobsters.data.repo.LobstersRepository
|
import dev.msfjarvis.lobsters.data.repo.LobstersRepository
|
||||||
import dev.msfjarvis.lobsters.model.LobstersPost
|
import dev.msfjarvis.lobsters.model.LobstersPost
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class LobstersPagingSource @Inject constructor(
|
class LobstersPagingSource constructor(
|
||||||
private val lobstersRepository: LobstersRepository,
|
private val lobstersRepository: LobstersRepository,
|
||||||
) : PagingSource<Int, LobstersPost>() {
|
) : PagingSource<Int, LobstersPost>() {
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
package dev.msfjarvis.lobsters.ui.main
|
package dev.msfjarvis.lobsters.ui.main
|
||||||
|
|
||||||
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.BottomNavigation
|
import androidx.compose.material.BottomNavigation
|
||||||
import androidx.compose.material.BottomNavigationItem
|
import androidx.compose.material.BottomNavigationItem
|
||||||
import androidx.compose.material.Scaffold
|
import androidx.compose.material.Scaffold
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
@ -13,6 +16,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.compose.KEY_ROUTE
|
import androidx.navigation.compose.KEY_ROUTE
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
|
@ -21,6 +25,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
import androidx.navigation.compose.navigate
|
import androidx.navigation.compose.navigate
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
import androidx.paging.compose.collectAsLazyPagingItems
|
||||||
|
import dev.msfjarvis.lobsters.R
|
||||||
import dev.msfjarvis.lobsters.ui.navigation.Destination
|
import dev.msfjarvis.lobsters.ui.navigation.Destination
|
||||||
import dev.msfjarvis.lobsters.ui.posts.HottestPosts
|
import dev.msfjarvis.lobsters.ui.posts.HottestPosts
|
||||||
import dev.msfjarvis.lobsters.ui.posts.SavedPosts
|
import dev.msfjarvis.lobsters.ui.posts.SavedPosts
|
||||||
|
@ -51,6 +56,12 @@ fun LobstersApp() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
LobstersTopBar(
|
||||||
|
currentDestination = currentDestination,
|
||||||
|
reloadPosts = { viewModel.reloadPosts() },
|
||||||
|
)
|
||||||
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
LobstersBottomNav(
|
LobstersBottomNav(
|
||||||
currentDestination,
|
currentDestination,
|
||||||
|
@ -80,6 +91,33 @@ fun LobstersApp() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalAnimationApi::class)
|
||||||
|
@Composable
|
||||||
|
fun LobstersTopBar(
|
||||||
|
currentDestination: Destination,
|
||||||
|
reloadPosts: () -> Unit,
|
||||||
|
) {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.app_name),
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
if (currentDestination == Destination.Hottest) {
|
||||||
|
IconResource(
|
||||||
|
resourceId = R.drawable.ic_refresh_24px,
|
||||||
|
contentDescription = stringResource(id = R.string.refresh_posts_content_description),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 8.dp, vertical = 8.dp)
|
||||||
|
.clickable { reloadPosts() },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LobstersBottomNav(
|
fun LobstersBottomNav(
|
||||||
currentDestination: Destination,
|
currentDestination: Destination,
|
||||||
|
|
|
@ -19,13 +19,13 @@ import kotlinx.coroutines.launch
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class LobstersViewModel @Inject constructor(
|
class LobstersViewModel @Inject constructor(
|
||||||
private val lobstersRepository: LobstersRepository,
|
private val lobstersRepository: LobstersRepository,
|
||||||
private val pagingSource: LobstersPagingSource,
|
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val _savedPosts = MutableStateFlow<List<SavedPost>>(emptyList())
|
private val _savedPosts = MutableStateFlow<List<SavedPost>>(emptyList())
|
||||||
val savedPosts = _savedPosts.asStateFlow()
|
val savedPosts = _savedPosts.asStateFlow()
|
||||||
val posts = Pager(PagingConfig(25)) {
|
val posts = Pager(PagingConfig(25)) {
|
||||||
pagingSource
|
LobstersPagingSource(lobstersRepository).also { pagingSource = it }
|
||||||
}.flow.cachedIn(viewModelScope)
|
}.flow.cachedIn(viewModelScope)
|
||||||
|
private var pagingSource: LobstersPagingSource? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
lobstersRepository.isCacheReady.onEach { ready ->
|
lobstersRepository.isCacheReady.onEach { ready ->
|
||||||
|
@ -35,6 +35,10 @@ class LobstersViewModel @Inject constructor(
|
||||||
}.launchIn(viewModelScope)
|
}.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun reloadPosts() {
|
||||||
|
pagingSource?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
fun toggleSave(post: SavedPost) {
|
fun toggleSave(post: SavedPost) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val isSaved = lobstersRepository.isPostSaved(post.shortId)
|
val isSaved = lobstersRepository.isPostSaved(post.shortId)
|
||||||
|
|
9
app/src/main/res/drawable/ic_refresh_24px.xml
Normal file
9
app/src/main/res/drawable/ic_refresh_24px.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z" />
|
||||||
|
</vector>
|
|
@ -8,4 +8,5 @@
|
||||||
<string name="avatar_content_description">%1$s\'s avatar</string>
|
<string name="avatar_content_description">%1$s\'s avatar</string>
|
||||||
<string name="add_to_saved_posts">Add to saved posts</string>
|
<string name="add_to_saved_posts">Add to saved posts</string>
|
||||||
<string name="remove_from_saved_posts">Remove from saved posts</string>
|
<string name="remove_from_saved_posts">Remove from saved posts</string>
|
||||||
|
<string name="refresh_posts_content_description">Refresh posts</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue