diff --git a/.gitignore b/.gitignore index 5e3a6720..547e63d0 100644 --- a/.gitignore +++ b/.gitignore @@ -103,6 +103,7 @@ obj/ .idea/dynamic.xml .idea/uiDesigner.xml .idea/assetWizardSettings.xml +.idea/deploymentTargetDropDown.xml # OS-specific files .DS_Store diff --git a/app/screenshots/debug/dev.msfjarvis.lobsters.ui.main.LobstersTopBarTest_doesNotShowRefreshIconWhenOnSavedPostsScreen_DarkTheme.png b/app/screenshots/debug/dev.msfjarvis.lobsters.ui.main.LobstersTopBarTest_doesNotShowRefreshIconWhenOnSavedPostsScreen_DarkTheme.png new file mode 100644 index 00000000..db9e4bad Binary files /dev/null and b/app/screenshots/debug/dev.msfjarvis.lobsters.ui.main.LobstersTopBarTest_doesNotShowRefreshIconWhenOnSavedPostsScreen_DarkTheme.png differ diff --git a/app/screenshots/debug/dev.msfjarvis.lobsters.ui.main.LobstersTopBarTest_doesNotShowRefreshIconWhenOnSavedPostsScreen_LightTheme.png b/app/screenshots/debug/dev.msfjarvis.lobsters.ui.main.LobstersTopBarTest_doesNotShowRefreshIconWhenOnSavedPostsScreen_LightTheme.png new file mode 100644 index 00000000..b19f1d4c Binary files /dev/null and b/app/screenshots/debug/dev.msfjarvis.lobsters.ui.main.LobstersTopBarTest_doesNotShowRefreshIconWhenOnSavedPostsScreen_LightTheme.png differ diff --git a/app/screenshots/debug/dev.msfjarvis.lobsters.ui.main.LobstersTopBarTest_showsRefreshIconWhenOnHottestPostsScreen_DarkTheme.png b/app/screenshots/debug/dev.msfjarvis.lobsters.ui.main.LobstersTopBarTest_showsRefreshIconWhenOnHottestPostsScreen_DarkTheme.png new file mode 100644 index 00000000..d5ad3fa9 Binary files /dev/null and b/app/screenshots/debug/dev.msfjarvis.lobsters.ui.main.LobstersTopBarTest_showsRefreshIconWhenOnHottestPostsScreen_DarkTheme.png differ diff --git a/app/screenshots/debug/dev.msfjarvis.lobsters.ui.main.LobstersTopBarTest_showsRefreshIconWhenOnHottestPostsScreen_LightTheme.png b/app/screenshots/debug/dev.msfjarvis.lobsters.ui.main.LobstersTopBarTest_showsRefreshIconWhenOnHottestPostsScreen_LightTheme.png new file mode 100644 index 00000000..59102fc1 Binary files /dev/null and b/app/screenshots/debug/dev.msfjarvis.lobsters.ui.main.LobstersTopBarTest_showsRefreshIconWhenOnHottestPostsScreen_LightTheme.png differ diff --git a/app/src/androidTest/java/dev/msfjarvis/lobsters/ui/main/LobstersTopBarTest.kt b/app/src/androidTest/java/dev/msfjarvis/lobsters/ui/main/LobstersTopBarTest.kt new file mode 100644 index 00000000..2923418d --- /dev/null +++ b/app/src/androidTest/java/dev/msfjarvis/lobsters/ui/main/LobstersTopBarTest.kt @@ -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()) + } +} diff --git a/app/src/main/java/dev/msfjarvis/lobsters/data/remote/LobstersPagingSource.kt b/app/src/main/java/dev/msfjarvis/lobsters/data/remote/LobstersPagingSource.kt index 766d4f88..813936b6 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/data/remote/LobstersPagingSource.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/data/remote/LobstersPagingSource.kt @@ -4,9 +4,8 @@ import androidx.paging.PagingSource import androidx.paging.PagingState import dev.msfjarvis.lobsters.data.repo.LobstersRepository import dev.msfjarvis.lobsters.model.LobstersPost -import javax.inject.Inject -class LobstersPagingSource @Inject constructor( +class LobstersPagingSource constructor( private val lobstersRepository: LobstersRepository, ) : PagingSource() { diff --git a/app/src/main/java/dev/msfjarvis/lobsters/ui/main/LobstersApp.kt b/app/src/main/java/dev/msfjarvis/lobsters/ui/main/LobstersApp.kt index 0febc3df..a64680d5 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/ui/main/LobstersApp.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/ui/main/LobstersApp.kt @@ -1,11 +1,14 @@ 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.lazy.rememberLazyListState import androidx.compose.material.BottomNavigation import androidx.compose.material.BottomNavigationItem import androidx.compose.material.Scaffold import androidx.compose.material.Text +import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -13,6 +16,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.compose.KEY_ROUTE import androidx.navigation.compose.NavHost @@ -21,6 +25,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.navigate import androidx.navigation.compose.rememberNavController import androidx.paging.compose.collectAsLazyPagingItems +import dev.msfjarvis.lobsters.R import dev.msfjarvis.lobsters.ui.navigation.Destination import dev.msfjarvis.lobsters.ui.posts.HottestPosts import dev.msfjarvis.lobsters.ui.posts.SavedPosts @@ -51,6 +56,12 @@ fun LobstersApp() { } Scaffold( + topBar = { + LobstersTopBar( + currentDestination = currentDestination, + reloadPosts = { viewModel.reloadPosts() }, + ) + }, bottomBar = { LobstersBottomNav( 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 fun LobstersBottomNav( currentDestination: Destination, diff --git a/app/src/main/java/dev/msfjarvis/lobsters/ui/viewmodel/LobstersViewModel.kt b/app/src/main/java/dev/msfjarvis/lobsters/ui/viewmodel/LobstersViewModel.kt index c4e2fd4a..ffbf6e3b 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/ui/viewmodel/LobstersViewModel.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/ui/viewmodel/LobstersViewModel.kt @@ -19,13 +19,13 @@ import kotlinx.coroutines.launch @HiltViewModel class LobstersViewModel @Inject constructor( private val lobstersRepository: LobstersRepository, - private val pagingSource: LobstersPagingSource, ) : ViewModel() { private val _savedPosts = MutableStateFlow>(emptyList()) val savedPosts = _savedPosts.asStateFlow() val posts = Pager(PagingConfig(25)) { - pagingSource + LobstersPagingSource(lobstersRepository).also { pagingSource = it } }.flow.cachedIn(viewModelScope) + private var pagingSource: LobstersPagingSource? = null init { lobstersRepository.isCacheReady.onEach { ready -> @@ -35,6 +35,10 @@ class LobstersViewModel @Inject constructor( }.launchIn(viewModelScope) } + fun reloadPosts() { + pagingSource?.invalidate() + } + fun toggleSave(post: SavedPost) { viewModelScope.launch { val isSaved = lobstersRepository.isPostSaved(post.shortId) diff --git a/app/src/main/res/drawable/ic_refresh_24px.xml b/app/src/main/res/drawable/ic_refresh_24px.xml new file mode 100644 index 00000000..843368df --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 94b2c294..c6140868 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -8,4 +8,5 @@ %1$s\'s avatar Add to saved posts Remove from saved posts + Refresh posts