diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c61d32fd..ac23d51d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -35,7 +35,6 @@ dependencies { implementation(Dependencies.AndroidX.Compose.foundationLayout) implementation(Dependencies.AndroidX.Compose.lifecycleViewModel) implementation(Dependencies.AndroidX.Compose.material) - implementation(Dependencies.AndroidX.Compose.navigation) implementation(Dependencies.AndroidX.Compose.paging) implementation(Dependencies.AndroidX.Compose.runtime) implementation(Dependencies.AndroidX.Compose.ui) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index c08703b0..baf2deda 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -3,3 +3,4 @@ -dontwarn androidx.compose.animation.tooling.ComposeAnimation -dontobfuscate -dontoptimize +-keep class dev.msfjarvis.lobsters.model.** { *; } diff --git a/app/src/androidTest/java/dev/msfjarvis/lobsters/BottomNavigationLayoutTest.kt b/app/src/androidTest/java/dev/msfjarvis/lobsters/BottomNavigationLayoutTest.kt deleted file mode 100644 index d5aa53e2..00000000 --- a/app/src/androidTest/java/dev/msfjarvis/lobsters/BottomNavigationLayoutTest.kt +++ /dev/null @@ -1,115 +0,0 @@ -package dev.msfjarvis.lobsters - -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.test.assertContentDescriptionEquals -import androidx.compose.ui.test.assertCountEquals -import androidx.compose.ui.test.assertHasClickAction -import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.assertIsNotSelected -import androidx.compose.ui.test.assertIsSelected -import androidx.compose.ui.test.assertTextEquals -import androidx.compose.ui.test.junit4.createComposeRule -import androidx.compose.ui.test.onChildAt -import androidx.compose.ui.test.onChildren -import androidx.compose.ui.test.onNodeWithTag -import androidx.compose.ui.test.performClick -import dev.msfjarvis.lobsters.ui.main.LobstersBottomNav -import dev.msfjarvis.lobsters.ui.navigation.Destination -import dev.msfjarvis.lobsters.ui.theme.LobstersTheme -import org.junit.Before -import org.junit.Rule -import org.junit.Test - - -class BottomNavigationLayoutTest { - - @get:Rule - val composeTestRule = createComposeRule() - - @Before - fun setUp() { - composeTestRule.setContent { - LobstersTheme { - var mutableDestination by remember { mutableStateOf(Destination.startDestination) } - - LobstersBottomNav( - currentDestination = mutableDestination, - navigateToDestination = { mutableDestination = it }, - jumpToIndex = {} - ) - } - } - } - - @Test - fun bottomNavItemCountTest() { - // Test to make sure total items are equal to enum objects present in Destination - composeTestRule.onNodeWithTag("LobstersBottomNav") - .assertExists() - .assertIsDisplayed() - .onChildren() - .assertCountEquals(Destination.values().size) - } - - @Test - fun bottomNavItemTest() { - // Check hottest BottomNavItem is rendered correctly - composeTestRule.onNodeWithTag("LobstersBottomNav") - .assertExists() - .assertIsDisplayed() - .onChildAt(0) - .assertTextEquals("Hottest") - .assertContentDescriptionEquals("Hottest") - .assertHasClickAction() - - // Check saved BottomNavItem is rendered correctly - composeTestRule.onNodeWithTag("LobstersBottomNav") - .assertExists() - .assertIsDisplayed() - .onChildAt(1) - .assertTextEquals("Saved") - .assertContentDescriptionEquals("Saved") - .assertHasClickAction() - } - - @Test - fun bottomNavItemSelectedTest() { - // Check hottest BottomNav item is selected - composeTestRule.onNodeWithTag("LobstersBottomNav") - .assertExists() - .assertIsDisplayed() - .onChildAt(0) - .assertIsSelected() - .assertTextEquals("Hottest") - - // Check saved BottomNav item is not selected - composeTestRule.onNodeWithTag("LobstersBottomNav") - .assertExists() - .assertIsDisplayed() - .onChildAt(1) - .assertIsNotSelected() - - // Select the saved BottomNav item - composeTestRule.onNodeWithTag("LobstersBottomNav") - .onChildAt(1) - .performClick() - - // Check hottest BottomNav item is not selected - composeTestRule.onNodeWithTag("LobstersBottomNav") - .assertExists() - .assertIsDisplayed() - .onChildAt(0) - .assertIsNotSelected() - - // Check saved BottomNav item is selected - composeTestRule.onNodeWithTag("LobstersBottomNav") - .assertExists() - .assertIsDisplayed() - .onChildAt(1) - .assertIsSelected() - .assertTextEquals("Saved") - } -} 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 new file mode 100644 index 00000000..be83cd7b --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/lobsters/ui/main/LobstersApp.kt @@ -0,0 +1,75 @@ +package dev.msfjarvis.lobsters.ui.main + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.BackdropScaffold +import androidx.compose.material.BackdropScaffoldDefaults +import androidx.compose.material.BackdropValue +import androidx.compose.material.Text +import androidx.compose.material.rememberBackdropScaffoldState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.paging.compose.collectAsLazyPagingItems +import dev.msfjarvis.lobsters.R +import dev.msfjarvis.lobsters.ui.posts.HottestPosts +import dev.msfjarvis.lobsters.ui.posts.SavedPosts +import dev.msfjarvis.lobsters.ui.viewmodel.LobstersViewModel + +@Composable +fun LobstersApp() { + val viewModel: LobstersViewModel = viewModel() + val scaffoldState = rememberBackdropScaffoldState(initialValue = BackdropValue.Concealed) + BackdropScaffold( + scaffoldState = scaffoldState, + gesturesEnabled = false, + appBar = { + Spacer(modifier = Modifier.height(BackdropScaffoldDefaults.PeekHeight.times(0.3f))) + Text( + text = stringResource(id = R.string.app_name), + textAlign = TextAlign.Center, + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(modifier = Modifier.height(BackdropScaffoldDefaults.PeekHeight.times(0.3f))) + }, + backLayerContent = { BackgroundLayerContent(viewModel) }, + ) { + ForegroundLayerContent(viewModel) + } +} + +@Composable +fun ForegroundLayerContent( + viewModel: LobstersViewModel, +) { + val hottestPosts = viewModel.posts.collectAsLazyPagingItems() + val hottestPostsListState = rememberLazyListState() + + HottestPosts( + posts = hottestPosts, + listState = hottestPostsListState, + isPostSaved = viewModel::isPostSaved, + saveAction = viewModel::toggleSave, + ) +} + +@Composable +fun BackgroundLayerContent( + viewModel: LobstersViewModel, +) { + val savedPosts by viewModel.savedPosts.collectAsState() + SavedPosts( + posts = savedPosts, + saveAction = viewModel::toggleSave, + ) +} diff --git a/app/src/main/java/dev/msfjarvis/lobsters/ui/main/MainActivity.kt b/app/src/main/java/dev/msfjarvis/lobsters/ui/main/MainActivity.kt index e85fccc2..0ffb0ffd 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/ui/main/MainActivity.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/ui/main/MainActivity.kt @@ -3,39 +3,12 @@ package dev.msfjarvis.lobsters.ui.main import android.os.Bundle import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity -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.runtime.Composable import androidx.compose.runtime.Providers -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.compose.KEY_ROUTE -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.currentBackStackEntryAsState -import androidx.navigation.compose.navigate -import androidx.navigation.compose.rememberNavController -import androidx.paging.compose.collectAsLazyPagingItems import dagger.hilt.android.AndroidEntryPoint -import dev.msfjarvis.lobsters.ui.navigation.Destination -import dev.msfjarvis.lobsters.ui.posts.HottestPosts -import dev.msfjarvis.lobsters.ui.posts.SavedPosts import dev.msfjarvis.lobsters.ui.theme.LobstersTheme import dev.msfjarvis.lobsters.ui.urllauncher.LocalUrlLauncher import dev.msfjarvis.lobsters.ui.urllauncher.UrlLauncher -import dev.msfjarvis.lobsters.ui.viewmodel.LobstersViewModel -import dev.msfjarvis.lobsters.util.IconResource import javax.inject.Inject -import kotlinx.coroutines.launch @AndroidEntryPoint class MainActivity : AppCompatActivity() { @@ -52,88 +25,3 @@ class MainActivity : AppCompatActivity() { } } } - -@Composable -fun LobstersApp() { - val viewModel: LobstersViewModel = viewModel() - val coroutineScope = rememberCoroutineScope() - val navController = rememberNavController() - val hottestPosts = viewModel.posts.collectAsLazyPagingItems() - val savedPosts by viewModel.savedPosts.collectAsState() - val hottestPostsListState = rememberLazyListState() - - val navBackStackEntry by navController.currentBackStackEntryAsState() - val currentRoute = - navBackStackEntry?.arguments?.getString(KEY_ROUTE) ?: Destination.startDestination.route - val currentDestination = Destination.getDestinationFromRoute(currentRoute) - val navigateToDestination: (destination: Destination) -> Unit = { destination -> - navController.navigate(destination.route) { - launchSingleTop = true - popUpTo(navController.graph.startDestination) { inclusive = false } - } - } - val jumpToIndex: (Int) -> Unit = { - coroutineScope.launch { - hottestPostsListState.snapToItemIndex(it) - } - } - - Scaffold( - bottomBar = { - LobstersBottomNav( - currentDestination, - navigateToDestination, - jumpToIndex, - ) - }, - ) { innerPadding -> - NavHost(navController, startDestination = Destination.startDestination.route) { - composable(Destination.Hottest.route) { - HottestPosts( - posts = hottestPosts, - listState = hottestPostsListState, - isPostSaved = viewModel::isPostSaved, - saveAction = viewModel::toggleSave, - modifier = Modifier.padding(bottom = innerPadding.calculateBottomPadding()), - ) - } - composable(Destination.Saved.route) { - SavedPosts( - posts = savedPosts, - saveAction = viewModel::toggleSave, - modifier = Modifier.padding(bottom = innerPadding.calculateBottomPadding()), - ) - } - } - } -} - -@Composable -fun LobstersBottomNav( - currentDestination: Destination, - navigateToDestination: (destination: Destination) -> Unit, - jumpToIndex: (index: Int) -> Unit, -) { - BottomNavigation(modifier = Modifier.testTag("LobstersBottomNav")) { - Destination.values().forEach { screen -> - BottomNavigationItem( - icon = { - IconResource( - resourceId = screen.badgeRes, - contentDescription = stringResource(screen.labelRes), - ) - }, - label = { Text(stringResource(id = screen.labelRes)) }, - selected = currentDestination == screen, - alwaysShowLabels = false, - onClick = { - if (screen != currentDestination) { - navigateToDestination(screen) - } else if (screen.route == Destination.Hottest.route) { - jumpToIndex(0) - } - } - ) - } - } -} diff --git a/app/src/main/java/dev/msfjarvis/lobsters/ui/navigation/Destination.kt b/app/src/main/java/dev/msfjarvis/lobsters/ui/navigation/Destination.kt deleted file mode 100644 index 44d5708a..00000000 --- a/app/src/main/java/dev/msfjarvis/lobsters/ui/navigation/Destination.kt +++ /dev/null @@ -1,26 +0,0 @@ -package dev.msfjarvis.lobsters.ui.navigation - -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import dev.msfjarvis.lobsters.R - -/** - * Destinations for navigation within the app. - */ -enum class Destination( - val route: String, - @StringRes val labelRes: Int, - @DrawableRes val badgeRes: Int, -) { - Hottest("hottest", R.string.hottest_posts, R.drawable.ic_whatshot_24px), - Saved("saved", R.string.saved_posts, R.drawable.ic_favorite_24px), - ; - - companion object { - val startDestination = Hottest - - fun getDestinationFromRoute(route: String): Destination { - return values().firstOrNull { it.route == route } ?: error("Incorrect route passed") - } - } -} diff --git a/buildSrc/src/main/java/BaseProjectConfig.kt b/buildSrc/src/main/java/BaseProjectConfig.kt index e2731179..7edea789 100644 --- a/buildSrc/src/main/java/BaseProjectConfig.kt +++ b/buildSrc/src/main/java/BaseProjectConfig.kt @@ -11,6 +11,7 @@ import org.gradle.api.tasks.Delete import org.gradle.api.tasks.testing.Test import org.gradle.api.tasks.testing.logging.TestLogEvent import org.gradle.api.tasks.wrapper.Wrapper +import org.gradle.kotlin.dsl.register import org.gradle.kotlin.dsl.repositories import org.gradle.kotlin.dsl.withType import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -21,7 +22,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile */ internal fun Project.configureForRootProject() { // register task for cleaning the build directory in the root project - tasks.register("clean", Delete::class.java) { + tasks.register("clean") { delete(rootProject.buildDir) } tasks.withType { diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 39bdca41..129f3365 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -43,7 +43,6 @@ object Dependencies { const val foundationLayout = "androidx.compose.foundation:foundation-layout:$COMPOSE_VERSION" const val lifecycleViewModel = "androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha01" const val material = "androidx.compose.material:material:$COMPOSE_VERSION" - const val navigation = "androidx.navigation:navigation-compose:1.0.0-alpha07" const val paging = "androidx.paging:paging-compose:1.0.0-alpha07" const val runtime = "androidx.compose.runtime:runtime:$COMPOSE_VERSION" const val ui = "androidx.compose.ui:ui:$COMPOSE_VERSION"