generated from julioromano/skeleton-android
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Works only online (no cache). - Graphics to be improved.
- Loading branch information
1 parent
a6540a6
commit 4ec3b4e
Showing
15 changed files
with
448 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
plugins { | ||
id("conventions.android") | ||
} | ||
|
||
android { | ||
namespace = "net.marcoromano.tmdb.movie" | ||
} | ||
|
||
dependencies { | ||
implementation(projects.database.public) | ||
implementation(projects.httpapi.public) | ||
implementation(libs.coilCompose) | ||
implementation(libs.androidx.pagingRuntime) | ||
implementation(libs.androidx.pagingCompose) | ||
implementation(libs.square.sqlDelightAndroidPaging3) | ||
implementation(libs.square.sqlDelightCoroutines) | ||
testImplementation(libs.square.turbine) | ||
testImplementation(projects.database.fake) | ||
testImplementation(projects.httpapi.fake) | ||
} |
29 changes: 29 additions & 0 deletions
29
movie/src/androidTest/kotlin/net/marcoromano/tmdb/movie/FeatureScreenIntegrationTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package net.marcoromano.tmdb.movie | ||
|
||
import androidx.compose.ui.test.assertTextEquals | ||
import androidx.compose.ui.test.junit4.createComposeRule | ||
import androidx.compose.ui.test.onNodeWithContentDescription | ||
import androidx.compose.ui.test.performClick | ||
import androidx.compose.ui.test.performTextInput | ||
import org.junit.Rule | ||
import org.junit.Test | ||
|
||
internal class FeatureScreenIntegrationTest { | ||
|
||
@get:Rule | ||
internal val rule = createComposeRule() | ||
|
||
@Test | ||
internal fun buttonSendsTextFieldContentToGreetHandler() { | ||
rule.setContent { | ||
MovieScreen() | ||
} | ||
|
||
rule.onNodeWithContentDescription("Insert name") | ||
.performTextInput("Mario") | ||
rule.onNodeWithContentDescription("Greet button") | ||
.performClick() | ||
rule.onNodeWithContentDescription("Greeting") | ||
.assertTextEquals("Hello, Mario") | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
movie/src/androidTest/kotlin/net/marcoromano/tmdb/movie/FeatureScreenUnitTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package net.marcoromano.tmdb.movie | ||
|
||
import androidx.compose.ui.test.assertTextEquals | ||
import androidx.compose.ui.test.junit4.createComposeRule | ||
import androidx.compose.ui.test.onNodeWithContentDescription | ||
import androidx.compose.ui.test.performClick | ||
import androidx.compose.ui.test.performTextInput | ||
import org.junit.Rule | ||
import org.junit.Test | ||
import kotlin.test.assertEquals | ||
|
||
internal class FeatureScreenUnitTest { | ||
|
||
@get:Rule | ||
internal val rule = createComposeRule() | ||
|
||
@Test | ||
internal fun greetingIsDisplayed() { | ||
rule.setContent { | ||
MovieScreen( | ||
state = TrendingState(greeting = "SomeGreeting"), | ||
greet = {}, | ||
) | ||
} | ||
|
||
rule.onNodeWithContentDescription("Greeting") | ||
.assertTextEquals("SomeGreeting") | ||
} | ||
|
||
@Test | ||
internal fun buttonSendsTextFieldContentToGreetHandler() { | ||
var text = "" | ||
rule.setContent { | ||
MovieScreen( | ||
state = TrendingState(), | ||
greet = { text = it }, | ||
) | ||
} | ||
|
||
rule.onNodeWithContentDescription("Insert name") | ||
.performTextInput("Mario") | ||
rule.onNodeWithContentDescription("Greet button") | ||
.performClick() | ||
assertEquals("Mario", text) | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
movie/src/main/kotlin/net/marcoromano/tmdb/movie/DemoMovie.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package net.marcoromano.tmdb.movie | ||
|
||
import net.marcoromano.tmdb.httpapi.Movie | ||
|
||
internal fun demoMovie() = Movie( | ||
id = 261414, | ||
title = "Larry David: Curb Your Enthusiasm", | ||
poster_path = "/m9QE3XkVc9hjDgpRTVoMpTNtfOY.jpg", | ||
overview = "Mock documentary about Seinfeld writer Larry David featuring contributions from " + | ||
"his friends and colleagues. Larry makes a return to stand-up comedy and prepares to film " + | ||
"a television special for HBO. This is the original special that gave birth to the " + | ||
"long-running award-winning HBO series.", | ||
vote_average = 7.6, | ||
runtime = 59, | ||
backdrop_path = "/jG8qyfFdfbxmvmYEYFUx64toKIp.jpg", | ||
popularity = 6.57, | ||
release_date = "1999-10-17", | ||
) |
33 changes: 33 additions & 0 deletions
33
movie/src/main/kotlin/net/marcoromano/tmdb/movie/MovieNavigation.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package net.marcoromano.tmdb.movie | ||
|
||
import androidx.compose.animation.ExperimentalAnimationApi | ||
import androidx.navigation.NavController | ||
import androidx.navigation.NavGraphBuilder | ||
import androidx.navigation.NavType | ||
import androidx.navigation.navArgument | ||
import com.google.accompanist.navigation.animation.composable | ||
|
||
public object MovieNavigation { | ||
@OptIn(ExperimentalAnimationApi::class) | ||
public fun navGraphBuilder( | ||
navGraphBuilder: NavGraphBuilder, | ||
navBack: () -> Unit, | ||
) { | ||
navGraphBuilder.composable( | ||
route = "movie/{id}", | ||
arguments = listOf( | ||
navArgument(name = "id") { | ||
type = NavType.LongType | ||
}, | ||
), | ||
) { | ||
MovieScreen( | ||
navBack = navBack, | ||
) | ||
} | ||
} | ||
|
||
public fun navigate(navController: NavController, id: Long) { | ||
navController.navigate("movie/$id") | ||
} | ||
} |
103 changes: 103 additions & 0 deletions
103
movie/src/main/kotlin/net/marcoromano/tmdb/movie/MovieScreen.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package net.marcoromano.tmdb.movie | ||
|
||
import android.content.res.Configuration | ||
import androidx.compose.foundation.layout.Box | ||
import androidx.compose.foundation.layout.fillMaxSize | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.lazy.LazyColumn | ||
import androidx.compose.material.icons.Icons | ||
import androidx.compose.material.icons.filled.ArrowBack | ||
import androidx.compose.material3.CircularProgressIndicator | ||
import androidx.compose.material3.ExperimentalMaterial3Api | ||
import androidx.compose.material3.Icon | ||
import androidx.compose.material3.IconButton | ||
import androidx.compose.material3.LargeTopAppBar | ||
import androidx.compose.material3.Scaffold | ||
import androidx.compose.material3.Text | ||
import androidx.compose.material3.TopAppBarDefaults | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.LaunchedEffect | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.input.nestedscroll.nestedScroll | ||
import androidx.compose.ui.res.stringResource | ||
import androidx.compose.ui.tooling.preview.Preview | ||
import androidx.hilt.navigation.compose.hiltViewModel | ||
import androidx.lifecycle.compose.collectAsStateWithLifecycle | ||
import net.marcoromano.tmdb.movie.widgets.Movie | ||
|
||
@Composable | ||
internal fun MovieScreen( | ||
navBack: () -> Unit, | ||
) { | ||
val vm = hiltViewModel<MovieViewModel>() | ||
val state by vm.state.collectAsStateWithLifecycle() | ||
LaunchedEffect(Unit) { | ||
if (state.movie == null) vm.load() | ||
} | ||
MovieScreen( | ||
state = state, | ||
navBack = navBack, | ||
) | ||
} | ||
|
||
@OptIn(ExperimentalMaterial3Api::class) | ||
@Composable | ||
private fun MovieScreen( | ||
state: MovieState, | ||
navBack: () -> Unit, | ||
) { | ||
val behavior = TopAppBarDefaults.enterAlwaysScrollBehavior() | ||
Scaffold( | ||
modifier = Modifier.nestedScroll(behavior.nestedScrollConnection), | ||
topBar = { | ||
LargeTopAppBar( | ||
title = { Text(text = state.movie?.title ?: stringResource(R.string.movie)) }, | ||
navigationIcon = { | ||
IconButton(onClick = navBack) { | ||
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null) | ||
} | ||
}, | ||
scrollBehavior = behavior, | ||
) | ||
}, | ||
) { paddingValues -> | ||
if (state.isLoading) { | ||
Box( | ||
modifier = Modifier | ||
.padding(paddingValues) | ||
.fillMaxSize(), | ||
contentAlignment = Alignment.Center, | ||
) { | ||
CircularProgressIndicator( | ||
modifier = Modifier.fillMaxSize(fraction = 0.5f), | ||
) | ||
} | ||
} else { | ||
LazyColumn( | ||
modifier = Modifier.padding(paddingValues), | ||
) { | ||
state.movie?.let { | ||
item { | ||
Movie( | ||
movie = it, | ||
) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
@Preview(name = "Day mode") | ||
@Preview(name = "Night mode", uiMode = Configuration.UI_MODE_NIGHT_YES) | ||
@Composable | ||
private fun Preview() { | ||
MovieScreen( | ||
state = MovieState( | ||
movie = demoMovie(), | ||
), | ||
navBack = {}, | ||
) | ||
} |
9 changes: 9 additions & 0 deletions
9
movie/src/main/kotlin/net/marcoromano/tmdb/movie/MovieState.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package net.marcoromano.tmdb.movie | ||
|
||
import net.marcoromano.tmdb.httpapi.Movie | ||
|
||
internal data class MovieState( | ||
val isLoading: Boolean = false, | ||
val errors: Map<String, Throwable> = emptyMap(), | ||
val movie: Movie? = null, | ||
) |
50 changes: 50 additions & 0 deletions
50
movie/src/main/kotlin/net/marcoromano/tmdb/movie/MovieViewModel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package net.marcoromano.tmdb.movie | ||
|
||
import androidx.lifecycle.SavedStateHandle | ||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.viewModelScope | ||
import dagger.hilt.android.lifecycle.HiltViewModel | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.SharingStarted | ||
import kotlinx.coroutines.flow.combine | ||
import kotlinx.coroutines.flow.stateIn | ||
import kotlinx.coroutines.flow.update | ||
import kotlinx.coroutines.launch | ||
import net.marcoromano.tmdb.httpapi.HttpApi | ||
import net.marcoromano.tmdb.httpapi.Movie | ||
import java.util.UUID | ||
import javax.inject.Inject | ||
|
||
@HiltViewModel | ||
internal class MovieViewModel @Inject constructor( | ||
private val savedStateHandle: SavedStateHandle, | ||
private val httpApi: HttpApi, | ||
) : ViewModel() { | ||
private val isLoading = MutableStateFlow(false) | ||
private val errors = MutableStateFlow<Map<String, Throwable>>(emptyMap()) | ||
private val movie = MutableStateFlow<Movie?>(null) | ||
|
||
val state = combine(isLoading, errors, movie, ::MovieState) | ||
.stateIn(viewModelScope, SharingStarted.Lazily, MovieState()) | ||
|
||
fun load() { | ||
viewModelScope.launch { | ||
isLoading.value = true | ||
runCatching { | ||
httpApi.movie(movieId = savedStateHandle["id"]!!) | ||
}.onSuccess { | ||
movie.value = it | ||
isLoading.value = false | ||
}.onFailure { throwable -> | ||
errors.update { | ||
it + (UUID.randomUUID().toString() to throwable) | ||
} | ||
isLoading.value = false | ||
} | ||
} | ||
} | ||
|
||
fun consumeError(uuid: String) { | ||
errors.update { it - uuid } | ||
} | ||
} |
Oops, something went wrong.