|
3 | 3 | [](https://github.com/opatry/android-dev-challenge-compose-week1/actions/workflows/Check.yaml) |
4 | 4 |
|
5 | 5 | ## :scroll: Description |
6 | | -<!--- Describe your app in one or two sentences --> |
7 | 6 |
|
| 7 | +This is a simple master/detail app with a fake list of clickable data displaying more detailed information about it. |
8 | 8 |
|
9 | 9 | ## :bulb: Motivation and Context |
10 | | -<!--- Optionally point readers to interesting parts of your submission. --> |
11 | | -<!--- What are you especially proud of? --> |
12 | 10 |
|
| 11 | +My goal was mainly to see how Jetpack Compose handles Navigation and Multiple form factor/layouts. |
| 12 | + |
| 13 | +I'm proud of my work because I started late (was in holiday when challenge was first announced) and didn't spent a lot of time on this. |
| 14 | +Still, I managed to submit a working version on time implementing Jetpack navigation and dedicated support of tablet. |
| 15 | + |
| 16 | +--- |
| 17 | + |
| 18 | +I used MVVM, Unidirectional Data Flow and UI State pattern. |
| 19 | +Each state has its own composable, see `CatsScreen.kt`. |
| 20 | + |
| 21 | +<details> |
| 22 | +<summary>Show me the code!</summary> |
| 23 | + |
| 24 | +```kotlin |
| 25 | +@Composable |
| 26 | +fun CatsScreen(viewModel: CatsViewModel, selectedCat: CatModel?, onCatSelected: (CatModel) -> Unit) { |
| 27 | + Scaffold( |
| 28 | + topBar = { /* */ }, |
| 29 | + content = { |
| 30 | + val state by viewModel.catsState.observeAsState(CatsScreenState.Loading) |
| 31 | + CatsStateDispatcher(uiState = state, selectedCat, onCatSelected) |
| 32 | + } |
| 33 | + ) |
| 34 | +} |
| 35 | + |
| 36 | +@Composable |
| 37 | +fun CatsStateDispatcher(uiState: CatsScreenState, selectedCat: CatModel?, onCatSelected: (CatModel) -> Unit) { |
| 38 | + when (uiState) { |
| 39 | + CatsScreenState.Loading -> LoadingCatsContent() |
| 40 | + is CatsScreenState.Error -> ErrorCatsContent(uiState.cause) |
| 41 | + CatsScreenState.Empty -> EmptyCatsContent() |
| 42 | + is CatsScreenState.Loaded -> LoadedCatsContent(uiState.cats, selectedCat, onCatSelected) |
| 43 | + } |
| 44 | +} |
| 45 | +``` |
| 46 | + |
| 47 | +</details> |
| 48 | + |
| 49 | +--- |
| 50 | + |
| 51 | +I implemented a `MainLayout` composable to choose how to present the UI depending on device configuration, see `MainActivity.kt/MainLayout`. |
| 52 | + |
| 53 | +<details> |
| 54 | +<summary>Show me the code!</summary> |
| 55 | + |
| 56 | +```kotlin |
| 57 | +sealed class NavRoute(val path: String) { |
| 58 | + object CatsList : NavRoute("cats") |
| 59 | + object CatDetails : NavRoute("cat.details") |
| 60 | +} |
| 61 | + |
| 62 | +@Composable |
| 63 | +fun MainLayout(catRepository: CatRepository = (CatRepository((FakeCatDataSource())))) { |
| 64 | + val catsViewModel = viewModel<CatsViewModel>(factory = CatsViewModelFactory(catRepository)) |
| 65 | + Surface(color = MaterialTheme.colors.background) { |
| 66 | + if (booleanResource(R.bool.is_tablet)) { |
| 67 | + var selectedCatUUID by rememberSaveable { mutableStateOf<UUID?>(null) } |
| 68 | + val selectedCat = selectedCatUUID?.let { uuid -> |
| 69 | + catsViewModel.findCatByUUID(uuid) |
| 70 | + } |
| 71 | + if (booleanResource(R.bool.is_portrait)) { |
| 72 | + MainLayoutTabletPortrait(catsViewModel, selectedCat) { cat -> |
| 73 | + selectedCatUUID = cat.uuid |
| 74 | + } |
| 75 | + } else { |
| 76 | + MainLayoutTabletLandscape(catsViewModel, selectedCat) { cat -> |
| 77 | + selectedCatUUID = cat.uuid |
| 78 | + } |
| 79 | + } |
| 80 | + } else { |
| 81 | + val navController = rememberNavController() |
| 82 | + NavHost(navController, startDestination = NavRoute.CatsList.path) { |
| 83 | + composable(NavRoute.CatsList.path) { |
| 84 | + CatsScreen(catsViewModel, null) { cat -> |
| 85 | + navController.navigate("${NavRoute.CatDetails.path}/${cat.uuid}") |
| 86 | + } |
| 87 | + } |
| 88 | + composable("${NavRoute.CatDetails.path}/{uuid}") { backStackEntry -> |
| 89 | + val uuid = UUID.fromString(backStackEntry.arguments?.get("uuid") as String) |
| 90 | + val cat = catsViewModel.findCatByUUID(uuid) |
| 91 | + CatDetailsScreen(cat) { navController.popBackStack() } |
| 92 | + } |
| 93 | + } |
| 94 | + } |
| 95 | + } |
| 96 | +} |
| 97 | +``` |
| 98 | +</details> |
| 99 | + |
| 100 | +I didn't see guidelines regarding how to handle tablet and side-by-side layouts, maybe there is something better 🤷♂️. |
13 | 101 |
|
14 | 102 | ## :camera_flash: Screenshots |
15 | 103 |
|
16 | | -<img src="/results/screenshot_1.png" width="260"> <img src="/results/screenshot_2.png" width="260"> |
| 104 | +## 🌞 Light Mode |
| 105 | +List | Details | Tablet |
| 106 | +--- | --- | --- | |
| 107 | +<img src="/results/screenshot_1.png" width="260"> | <img src="/results/screenshot_2.png" width="260"> | <img src="/results/screenshot_3.png" width="520"> |
| 108 | + |
| 109 | +<br /> |
| 110 | + |
| 111 | +## 🌚 Dark Mode |
| 112 | +List | Details | Tablet |
| 113 | +--- | --- | --- | |
| 114 | +<img src="/results/screenshot_1_dark.png" width="260"> | <img src="/results/screenshot_2_dark.png" width="260"> | <img src="/results/screenshot_3_dark.png" width="520"> |
| 115 | + |
| 116 | +<br /> |
| 117 | + |
17 | 118 |
|
18 | 119 | ## License |
19 | 120 | ``` |
|
0 commit comments