151: Use pull-to-refresh for reloading r=msfjarvis a=msfjarvis

bors r+

Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
bors[bot] 2021-03-12 21:16:22 +00:00 committed by GitHub
commit 2a543d3aad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 43 additions and 139 deletions

View file

@ -55,6 +55,7 @@ dependencies {
implementation(Dependencies.ThirdParty.accompanist) implementation(Dependencies.ThirdParty.accompanist)
implementation(Dependencies.ThirdParty.composeFlowLayout) implementation(Dependencies.ThirdParty.composeFlowLayout)
implementation(Dependencies.ThirdParty.Moshi.lib) implementation(Dependencies.ThirdParty.Moshi.lib)
implementation(Dependencies.ThirdParty.pullToRefresh)
implementation(Dependencies.ThirdParty.Retrofit.moshi) implementation(Dependencies.ThirdParty.Retrofit.moshi)
implementation(Dependencies.ThirdParty.SQLDelight.androidDriver) implementation(Dependencies.ThirdParty.SQLDelight.androidDriver)
testImplementation(Dependencies.Testing.junit) testImplementation(Dependencies.Testing.junit)

View file

@ -1,74 +0,0 @@
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())
}
}

View file

@ -1,14 +1,11 @@
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
@ -16,7 +13,6 @@ 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
@ -25,7 +21,6 @@ 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
@ -56,12 +51,6 @@ fun LobstersApp() {
} }
Scaffold( Scaffold(
topBar = {
LobstersTopBar(
currentDestination = currentDestination,
reloadPosts = { viewModel.reloadPosts() },
)
},
bottomBar = { bottomBar = {
LobstersBottomNav( LobstersBottomNav(
currentDestination, currentDestination,
@ -75,9 +64,10 @@ fun LobstersApp() {
HottestPosts( HottestPosts(
posts = hottestPosts, posts = hottestPosts,
listState = hottestPostsListState, listState = hottestPostsListState,
modifier = Modifier.padding(bottom = innerPadding.calculateBottomPadding()),
isPostSaved = viewModel::isPostSaved, isPostSaved = viewModel::isPostSaved,
saveAction = viewModel::toggleSave, saveAction = viewModel::toggleSave,
modifier = Modifier.padding(bottom = innerPadding.calculateBottomPadding()), refreshAction = viewModel::reloadPosts,
) )
} }
composable(Destination.Saved.route) { composable(Destination.Saved.route) {
@ -91,33 +81,6 @@ 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,

View file

@ -11,6 +11,7 @@ import androidx.compose.ui.Modifier
import androidx.paging.LoadState import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.items import androidx.paging.compose.items
import com.puculek.pulltorefresh.PullToRefresh
import dev.msfjarvis.lobsters.data.local.SavedPost import dev.msfjarvis.lobsters.data.local.SavedPost
import dev.msfjarvis.lobsters.model.LobstersPost import dev.msfjarvis.lobsters.model.LobstersPost
import dev.msfjarvis.lobsters.ui.urllauncher.LocalUrlLauncher import dev.msfjarvis.lobsters.ui.urllauncher.LocalUrlLauncher
@ -20,39 +21,51 @@ import dev.msfjarvis.lobsters.util.toDbModel
fun HottestPosts( fun HottestPosts(
posts: LazyPagingItems<LobstersPost>, posts: LazyPagingItems<LobstersPost>,
listState: LazyListState, listState: LazyListState,
isPostSaved: (String) -> Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
isPostSaved: (String) -> Boolean,
saveAction: (SavedPost) -> Unit, saveAction: (SavedPost) -> Unit,
refreshAction: () -> Unit,
) { ) {
val urlLauncher = LocalUrlLauncher.current val urlLauncher = LocalUrlLauncher.current
var isRefreshing by mutableStateOf(false)
if (posts.loadState.refresh == LoadState.Loading) { PullToRefresh(
LazyColumn { isRefreshing = isRefreshing,
items(15) { onRefresh = {
LoadingLobstersItem() if (posts.loadState.refresh != LoadState.Loading) {
isRefreshing = isRefreshing.not()
refreshAction()
} }
} },
} else { ) {
LazyColumn( if (posts.loadState.refresh == LoadState.Loading) {
state = listState, LazyColumn {
modifier = Modifier.then(modifier), items(15) {
) { LoadingLobstersItem()
items(posts) { item -> }
if (item != null) { }
@Suppress("NAME_SHADOWING") } else {
val item = item.toDbModel() LazyColumn(
var isSaved by remember(item.shortId) { mutableStateOf(isPostSaved(item.shortId)) } state = listState,
modifier = Modifier.then(modifier),
) {
items(posts) { item ->
if (item != null) {
@Suppress("NAME_SHADOWING")
val item = item.toDbModel()
var isSaved by remember(item.shortId) { mutableStateOf(isPostSaved(item.shortId)) }
LobstersItem( LobstersItem(
post = item, post = item,
isSaved = isSaved, isSaved = isSaved,
onClick = { urlLauncher.launch(item.url.ifEmpty { item.commentsUrl }) }, onClick = { urlLauncher.launch(item.url.ifEmpty { item.commentsUrl }) },
onLongClick = { urlLauncher.launch(item.commentsUrl) }, onLongClick = { urlLauncher.launch(item.commentsUrl) },
onSaveButtonClick = { onSaveButtonClick = {
isSaved = isSaved.not() isSaved = isSaved.not()
saveAction.invoke(item) saveAction.invoke(item)
}, },
) )
}
} }
} }
} }

View file

@ -67,6 +67,7 @@ object Dependencies {
const val accompanist = "dev.chrisbanes.accompanist:accompanist-coil:0.6.2" const val accompanist = "dev.chrisbanes.accompanist:accompanist-coil:0.6.2"
const val composeFlowLayout = "com.star-zero:compose-flowlayout:0.0.1" const val composeFlowLayout = "com.star-zero:compose-flowlayout:0.0.1"
const val pullToRefresh = "com.puculek.pulltorefresh:pull-to-refresh-compose:1.0.0"
object Moshi { object Moshi {