Skip to content

Commit

Permalink
use states and events in PersonDetails
Browse files Browse the repository at this point in the history
  • Loading branch information
omer358 committed Jun 19, 2024
1 parent 28c4bda commit 9d4ef68
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 29 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ dependencies {
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.lifecycle.runtime.compose.android)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//package com.example.rememberme.data.repository
//
//import com.example.rememberme.R
//import com.example.rememberme.data.PeopleRepositoryImpl
//import com.example.rememberme.domain.model.People
//import com.example.remindme.database.PeopleDao
//import kotlinx.coroutines.ExperimentalCoroutinesApi
//import kotlinx.coroutines.test.runTest
//import org.junit.Assert.assertEquals
//import org.junit.Before
//import org.junit.Test
//import org.mockito.MockitoAnnotations
//import javax.inject.Inject
//
//@ExperimentalCoroutinesApi
//class PeopleRepositoryImplTest {
//
// @Inject
// private lateinit var peopleDao: PeopleDao
//
// private lateinit var peopleRepository: PeopleRepositoryImpl
//
// @Before
// fun setUp() {
// MockitoAnnotations.initMocks(this::class)
// peopleRepository = PeopleRepositoryImpl(peopleDao)
// }
//
// @Test
// fun getAllPeopleShouldReturnListOfPeople() = runTest {
// // Arrange
// val peopleList = listOf(
// People(
// 1, "John", secondName = "Doe", place = "", time = "", gender = "",avatar= R.drawable.ic_m3,),
// People(1, "John", secondName = "Doe", place = "", time = "", gender = "",avatar= R.drawable.ic_m3,)
// )
// peopleRepository.insertPeople(peopleList[0])
// peopleRepository.insertPeople(peopleList[1])
//
// // Act
// val result = peopleRepository.getAllPeople()
//
// // Assert
// result.collect {
// assertEquals(peopleList, it)
// }
// }
//
//// @Test
//// fun insertPeopleShouldCallInsertPeopleOnDao() = runBlocking {
//// // Arrange
//// val person = People(1, "John", secondName = "Doe", place = "", time = "", gender = "",avatar= R.drawable.ic_m3,)
////
//// // Act
//// peopleRepository.insertPeople(person)
////
//// // Assert
//// Mockito.verify(peopleDao, Mockito.times(1)).insertPeople(person)
//// }
////
//// @Test
//// fun `deletePeople should call deletePeople on dao`() = runBlocking {
//// // Arrange
//// val person = People(1, "John", secondName = "Doe", place = "", time = "", gender = "",avatar= R.drawable.ic_m3,)
////
//// // Act
//// peopleRepository.deletePeople(person)
////
//// // Assert
//// Mockito.verify(peopleDao, Mockito.times(1)).deletePeople(person)
//// }
////
//// @Test
//// fun `updatePeople should call updatePeople on dao`() = runBlocking {
//// // Arrange
//// val person = People(1, "John Doe")
////
//// // Act
//// peopleRepository.updatePeople(person)
////
//// // Assert
//// Mockito.verify(peopleDao, Mockito.times(1)).updatePeople(person)
//// }
////
//// @Test
//// fun `deleteAllPeople should call deleteAllPeople on dao`() = runBlocking {
//// // Act
//// peopleRepository.deleteAllPeople()
////
//// // Assert
//// Mockito.verify(peopleDao, Mockito.times(1)).deleteAllPeople()
//// }
////
//// @Test
//// fun `getPeopleById should return person`() = runBlocking {
//// // Arrange
//// val person = People(1, "John Doe")
//// Mockito.`when`(peopleDao.getPeopleById(1)).thenReturn(person)
////
//// // Act
//// val result = peopleRepository.getPeopleById(1)
////
//// // Assert
//// assertEquals(person, result)
//// }
////
//// @Test
//// fun `getPeopleByName should return list of people`() = runBlocking {
//// // Arrange
//// val peopleList = listOf(People(1, "John Doe"), People(2, "John Smith"))
//// Mockito.`when`(peopleDao.getPeopleByName("John")).thenReturn(peopleList)
////
//// // Act
//// val result = peopleRepository.getPeopleByName("John")
////
//// // Assert
//// assertEquals(peopleList, result)
//// }
//}
Original file line number Diff line number Diff line change
Expand Up @@ -18,45 +18,106 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.Card
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.example.rememberme.R
import com.example.rememberme.domain.model.People
import com.example.rememberme.ui.theme.RememberMeTheme
import kotlinx.coroutines.launch

private const val TAG = "PersonDetails"

@Composable
fun PersonDetailsScreen(
viewModel: PersonDetailsViewModel = hiltViewModel(),
personId: Long
personId: Long,
navigateUp: () -> Unit
) {
viewModel.getPerson(personId)
val person = viewModel.person.collectAsState().value
//TODO: Check and see why the object is null and then become non-null
if (person != null) {
Log.d(TAG, "PersonDetailsScreen: $person")
PersonDetailsContent(person)
} else {
Log.e(TAG, "PersonDetailsScreen: Person not found")
val uiState = viewModel.uiState.collectAsStateWithLifecycle()
val coroutineScope = rememberCoroutineScope()


LaunchedEffect(personId) {
coroutineScope.launch {
viewModel.onEvent(PersonDetailsEvent.LoadPerson(personId))
}
}
when {
uiState.value.isLoading -> {
LoadingIndicator()
Log.d(TAG, "PersonDetailsScreen: Loading")
}
uiState.value.error != null -> {
Log.e(TAG, "PersonDetailsScreen: Error - ${uiState.value.error}")
ErrorContent(uiState.value.error!!)
}
uiState.value.person != null -> {
Log.d(TAG, "PersonDetailsScreen: ${uiState.value.person}")
PersonDetailsContent(uiState.value.person!!, navigateUp)
}
else -> {
Log.e(TAG, "PersonDetailsScreen: Person not found")
// Optionally, you can add a UI to show "Person not found"
}
}
}

@Composable
fun ErrorContent(error: String) {
// TODO: Implement a custom Error animation in the future
Column {
Icon(
imageVector = Icons.Default.Warning,
contentDescription = null,
modifier = Modifier
.size(120.dp)
.align(Alignment.CenterHorizontally)
)
Spacer(modifier = Modifier.height(16.dp))
Text(text = error, style = MaterialTheme.typography.headlineLarge)
}
}

@Composable
fun LoadingIndicator() {
// TODO: Implement a custom Loading animation in the future
Column (
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
){
CircularProgressIndicator()
Spacer(modifier = Modifier.height(16.dp))
Text(text = "Loading...")
}
}

@Composable
fun PersonDetailsContent(
person: People,
navigateUp: () -> Unit,
modifier: Modifier = Modifier
) {
val scrollState = rememberScrollState()
Expand All @@ -81,7 +142,27 @@ fun PersonDetailsContent(
.fillMaxWidth()
.height(200.dp)

)
) {
IconButton(
modifier = Modifier
.align(Alignment.TopStart)
.padding(top = 16.dp, start = 8.dp),
colors = IconButtonColors(
contentColor = MaterialTheme.colorScheme.onPrimary,
disabledContentColor = MaterialTheme.colorScheme.onPrimary,
containerColor = Color.Transparent,
disabledContainerColor = Color.Transparent
),
onClick = {
Log.d(TAG, "PersonDetailsContent: Clicked")
navigateUp()
}) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = null
)
}
}
Box(
modifier = Modifier
.padding(start = 8.dp)
Expand Down Expand Up @@ -153,7 +234,6 @@ fun PersonDetailsContent(
}
}
}

}

@Preview(showBackground = true)
Expand All @@ -162,14 +242,27 @@ fun PersonDetailsContent(
fun PersonDetailsContentPreview() {
RememberMeTheme {
PersonDetailsContent(
People(
person = People(
firstName = "William",
secondName = "Doe",
avatar = R.drawable.ic_m3,
time = "10:00 AM",
gender = "Male",
time = "12:00",
place = "Home",
gender = "Male"
)
avatar = R.drawable.ic_m4
),
navigateUp = {
Log.d(TAG, "PersonDetailsContentPreview: Clicked")
}
)
}
}
}

@Preview(showBackground = true)
@Preview(uiMode = UI_MODE_NIGHT_YES)
@Composable
fun LoadingIndicatorContentPreview() {
RememberMeTheme {
LoadingIndicator()
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example.rememberme.presentation.details

sealed class PersonDetailsEvent {
data class LoadPerson(val personId: Long) : PersonDetailsEvent()
data object NavigateUp : PersonDetailsEvent()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.rememberme.presentation.details

import com.example.rememberme.domain.model.People

data class PersonDetailsUiState(
val person: People? = null,
val isLoading: Boolean = false,
val error: String? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,41 @@ package com.example.rememberme.presentation.details

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.rememberme.domain.model.People
import com.example.rememberme.domain.usecases.people.PeopleUseCases
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject

@HiltViewModel
class PersonDetailsViewModel @Inject constructor(
private val peopleUseCases: PeopleUseCases
) : ViewModel() {
private val _person = MutableStateFlow<People?>(null)
val person = _person
private val _uiState = MutableStateFlow(PersonDetailsUiState())
val uiState: StateFlow<PersonDetailsUiState> = _uiState

fun getPerson(id: Long) {
fun onEvent(event: PersonDetailsEvent) {
when (event) {
is PersonDetailsEvent.LoadPerson -> {
loadPerson(event.personId)
}
is PersonDetailsEvent.NavigateUp -> {
// Handle navigation
}
}
}

private fun loadPerson(personId: Long) {
_uiState.value = _uiState.value.copy(isLoading = true)
viewModelScope.launch {
withContext(Dispatchers.IO) {
peopleUseCases.getPersonById(id).collect {
_person.value = it
try {
peopleUseCases.getPersonById(personId).collect { person ->
_uiState.value = PersonDetailsUiState(person = person)
}
} catch (e: Exception) {
_uiState.value = PersonDetailsUiState(error = e.message)
}
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ fun NavGraph(
if (personId != null) {
PersonDetailsScreen(
personId = personId,
navigateUp = {
navController.navigateUp()
}
)
}
}
Expand Down
Loading

0 comments on commit 9d4ef68

Please sign in to comment.