From 2d427ff66976c16af97c5c2231b252abb9647bfc Mon Sep 17 00:00:00 2001 From: omer358 Date: Tue, 25 Jun 2024 20:12:22 +0400 Subject: [PATCH 1/3] add validation for the GenderRadioButton create custom ErrorText --- .../com/example/rememberme/di/AppModule.kt | 4 +++- .../usecases/add_person/AddPersonUseCases.kt | 3 ++- .../ValidateGenderSelectionUseCase.kt | 20 ++++++++++++++++ .../presentation/addperson/AddPersonScreen.kt | 24 +++++++++++++++---- .../addperson/AddPersonUiState.kt | 1 + .../addperson/AddPersonViewModel.kt | 16 ++++++++----- .../common/composables/CustomErrorText.kt | 18 ++++++++++++++ .../composables/CustomOutLinedTextField.kt | 8 +------ 8 files changed, 74 insertions(+), 20 deletions(-) create mode 100644 app/src/main/java/com/example/rememberme/domain/usecases/add_person/ValidateGenderSelectionUseCase.kt create mode 100644 app/src/main/java/com/example/rememberme/presentation/common/composables/CustomErrorText.kt diff --git a/app/src/main/java/com/example/rememberme/di/AppModule.kt b/app/src/main/java/com/example/rememberme/di/AppModule.kt index ab806b6..8ca94ee 100644 --- a/app/src/main/java/com/example/rememberme/di/AppModule.kt +++ b/app/src/main/java/com/example/rememberme/di/AppModule.kt @@ -7,6 +7,7 @@ import com.example.rememberme.data.local.PeopleDatabase import com.example.rememberme.domain.repository.PeopleRepository import com.example.rememberme.domain.usecases.add_person.AddPersonUseCases import com.example.rememberme.domain.usecases.add_person.ValidateFirstNameUseCase +import com.example.rememberme.domain.usecases.add_person.ValidateGenderSelectionUseCase import com.example.rememberme.domain.usecases.add_person.ValidatePlaceUseCase import com.example.rememberme.domain.usecases.add_person.ValidateSecondNameUseCase import com.example.rememberme.domain.usecases.add_person.ValidateTimeUseCase @@ -72,7 +73,8 @@ object AppModule { validateFirstNameUseCase = ValidateFirstNameUseCase(), validateSecondNameUseCase = ValidateSecondNameUseCase(), validatePlaceUseCase = ValidatePlaceUseCase(), - validateTimeUseCase = ValidateTimeUseCase() + validateTimeUseCase = ValidateTimeUseCase(), + validateGenderSelectionUseCase = ValidateGenderSelectionUseCase() ) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/rememberme/domain/usecases/add_person/AddPersonUseCases.kt b/app/src/main/java/com/example/rememberme/domain/usecases/add_person/AddPersonUseCases.kt index 7328fdb..d9d9bae 100644 --- a/app/src/main/java/com/example/rememberme/domain/usecases/add_person/AddPersonUseCases.kt +++ b/app/src/main/java/com/example/rememberme/domain/usecases/add_person/AddPersonUseCases.kt @@ -4,5 +4,6 @@ data class AddPersonUseCases( val validateFirstNameUseCase: ValidateFirstNameUseCase, val validateSecondNameUseCase: ValidateSecondNameUseCase, val validatePlaceUseCase: ValidatePlaceUseCase, - val validateTimeUseCase: ValidateTimeUseCase + val validateTimeUseCase: ValidateTimeUseCase, + val validateGenderSelectionUseCase: ValidateGenderSelectionUseCase ) diff --git a/app/src/main/java/com/example/rememberme/domain/usecases/add_person/ValidateGenderSelectionUseCase.kt b/app/src/main/java/com/example/rememberme/domain/usecases/add_person/ValidateGenderSelectionUseCase.kt new file mode 100644 index 0000000..bb24b59 --- /dev/null +++ b/app/src/main/java/com/example/rememberme/domain/usecases/add_person/ValidateGenderSelectionUseCase.kt @@ -0,0 +1,20 @@ +package com.example.rememberme.domain.usecases.add_person + +import android.util.Log +import com.example.rememberme.domain.model.ValidationResult + +class ValidateGenderSelectionUseCase { + operator fun invoke(gender: String): ValidationResult { + if (gender.isBlank()) { + Log.d(TAG, "invoke: gender is null") + return ValidationResult(successful = false, errorMessage = "please, Select the gender!") + } else { + Log.d(TAG, "invoke: gender is not null") + return ValidationResult(successful = true) + } + } + + companion object { + private const val TAG = "ValidateGenderSelection" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/rememberme/presentation/addperson/AddPersonScreen.kt b/app/src/main/java/com/example/rememberme/presentation/addperson/AddPersonScreen.kt index 48f771b..2e30b2b 100644 --- a/app/src/main/java/com/example/rememberme/presentation/addperson/AddPersonScreen.kt +++ b/app/src/main/java/com/example/rememberme/presentation/addperson/AddPersonScreen.kt @@ -1,14 +1,16 @@ -@file:OptIn(ExperimentalMaterial3Api::class) +@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3Api::class) package com.example.rememberme.presentation.addperson import android.app.DatePickerDialog import android.app.TimePickerDialog import android.content.res.Configuration.UI_MODE_NIGHT_YES +import android.util.Log import android.view.ContextThemeWrapper import android.widget.DatePicker import android.widget.TimePicker import androidx.compose.foundation.Image +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -20,6 +22,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.Button @@ -52,12 +56,14 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.example.rememberme.R import com.example.rememberme.presentation.common.composables.CustomButton +import com.example.rememberme.presentation.common.composables.CustomErrorText import com.example.rememberme.presentation.common.composables.CustomOutlinedTextField import com.example.rememberme.ui.theme.RememberMeTheme import kotlinx.coroutines.launch import java.util.Calendar @OptIn(ExperimentalMaterial3Api::class) +private const val TAG = "AddPersonScreen" @Composable fun AddPersonScreen( viewModel: AddPersonViewModel = hiltViewModel(), @@ -90,7 +96,7 @@ fun AddPersonScreen( scope.launch { sheetState.show() } }, selectedAvatarResId = selectedAvatarResId, - popUp = popUp + popUp = popUp, ) if (showBottomSheet) { @@ -121,7 +127,8 @@ fun AddPersonContent( onSavePerson: () -> Unit, onAvatarPickerClick: () -> Unit, selectedAvatarResId: Int, - popUp: () -> Unit + popUp: () -> Unit, + scrollState: ScrollState = rememberScrollState() ) { Scaffold( topBar = { @@ -143,6 +150,7 @@ fun AddPersonContent( .fillMaxSize() .padding(horizontal = 16.dp) .padding(paddingValues) + .verticalScroll(scrollState) ) { CustomOutlinedTextField( value = uiState.firstName, @@ -172,6 +180,7 @@ fun AddPersonContent( ) GenderRadioButton( selectedGender = uiState.gender, + errorMessage = uiState.genderError, onGenderSelected = onGenderChange ) Row( @@ -323,8 +332,10 @@ fun AvatarPicker( fun GenderRadioButton( selectedGender: String, onGenderSelected: (String) -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + errorMessage: String? ) { + Log.d(TAG, "GenderRadioButton: $errorMessage") val genderOptions = listOf("Male", "Female") Column(modifier = modifier) { Text("Gender", modifier = Modifier.padding(bottom = 4.dp)) @@ -344,6 +355,9 @@ fun GenderRadioButton( } } } + if (errorMessage != null) { + CustomErrorText(errorText = errorMessage) + } } } @@ -363,7 +377,7 @@ fun AddPersonContentPreview() { onSavePerson = {}, onAvatarPickerClick = {}, selectedAvatarResId = R.drawable.ic_m1, - popUp = {} + popUp = {}, ) } } diff --git a/app/src/main/java/com/example/rememberme/presentation/addperson/AddPersonUiState.kt b/app/src/main/java/com/example/rememberme/presentation/addperson/AddPersonUiState.kt index d99c549..9abdf2e 100644 --- a/app/src/main/java/com/example/rememberme/presentation/addperson/AddPersonUiState.kt +++ b/app/src/main/java/com/example/rememberme/presentation/addperson/AddPersonUiState.kt @@ -13,5 +13,6 @@ data class AddPersonUiState( val timeError: String? = null, val note: String? = null, val gender: String = "", + val genderError: String? = null, val avatar: Int = R.drawable.ic_f1, ) \ No newline at end of file diff --git a/app/src/main/java/com/example/rememberme/presentation/addperson/AddPersonViewModel.kt b/app/src/main/java/com/example/rememberme/presentation/addperson/AddPersonViewModel.kt index d15a63e..f0b0e92 100644 --- a/app/src/main/java/com/example/rememberme/presentation/addperson/AddPersonViewModel.kt +++ b/app/src/main/java/com/example/rememberme/presentation/addperson/AddPersonViewModel.kt @@ -29,17 +29,17 @@ class AddPersonViewModel @Inject constructor( when (event) { is AddPersonEvents.OnFirstNameChange -> { Log.i(TAG, "onEvent: OnFirstNameChange -> ${event.firstName}") - _uiState.value = _uiState.value.copy(firstName = event.firstName) + _uiState.value = _uiState.value.copy(firstName = event.firstName, firstNameError = null) } is AddPersonEvents.OnSecondNameChange -> { Log.i(TAG, "onEvent: OnSecondNameChange -> ${event.secondName}") - _uiState.value = _uiState.value.copy(secondName = event.secondName) + _uiState.value = _uiState.value.copy(secondName = event.secondName, secondNameError = null) } is AddPersonEvents.OnPlaceChange -> { Log.i(TAG, "onEvent: OnPlaceChange -> ${event.place}") - _uiState.value = _uiState.value.copy(place = event.place) + _uiState.value = _uiState.value.copy(place = event.place, placeError = null) } is AddPersonEvents.OnTimeChange -> { @@ -54,7 +54,7 @@ class AddPersonViewModel @Inject constructor( is AddPersonEvents.OnGenderChange -> { Log.i(TAG, "onEvent: OnGenderChange -> ${event.gender}") - _uiState.value = _uiState.value.copy(gender = event.gender) + _uiState.value = _uiState.value.copy(gender = event.gender, genderError = null) } is AddPersonEvents.OnAvatarChange -> { @@ -76,20 +76,24 @@ class AddPersonViewModel @Inject constructor( addPersonUseCases.validateSecondNameUseCase(_uiState.value.secondName) val placeResult = addPersonUseCases.validatePlaceUseCase(_uiState.value.place) val timeResult = addPersonUseCases.validateTimeUseCase(_uiState.value.time) - + val genderResult = addPersonUseCases.validateGenderSelectionUseCase(_uiState.value.gender) + Log.d(TAG, "savePerson: $genderResult") val hasError = listOf( firstNameResult, secondNameResult, placeResult, timeResult, + genderResult ).any { !it.successful } if (hasError) { Log.e(TAG, "savePerson: There are some unvalidated inputs") + Log.e(TAG, "savePerson: ${_uiState.value}") _uiState.value = _uiState.value.copy( firstNameError = firstNameResult.errorMessage, secondNameError = secondNameResult.errorMessage, placeError = placeResult.errorMessage, - timeError = timeResult.errorMessage + timeError = timeResult.errorMessage, + genderError = genderResult.errorMessage ) _isPersonSaved.value = false diff --git a/app/src/main/java/com/example/rememberme/presentation/common/composables/CustomErrorText.kt b/app/src/main/java/com/example/rememberme/presentation/common/composables/CustomErrorText.kt new file mode 100644 index 0000000..04b1dc8 --- /dev/null +++ b/app/src/main/java/com/example/rememberme/presentation/common/composables/CustomErrorText.kt @@ -0,0 +1,18 @@ +package com.example.rememberme.presentation.common.composables + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun CustomErrorText(errorText: String){ + Text( + text = errorText, + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.bodySmall, + modifier = Modifier.padding(start = 16.dp) + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/rememberme/presentation/common/composables/CustomOutLinedTextField.kt b/app/src/main/java/com/example/rememberme/presentation/common/composables/CustomOutLinedTextField.kt index b976d5f..2d10c93 100644 --- a/app/src/main/java/com/example/rememberme/presentation/common/composables/CustomOutLinedTextField.kt +++ b/app/src/main/java/com/example/rememberme/presentation/common/composables/CustomOutLinedTextField.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -46,12 +45,7 @@ fun CustomOutlinedTextField( isError = isError, ) if (isError && errorText != null) { - Text( - text = errorText, - color = MaterialTheme.colorScheme.error, - style = MaterialTheme.typography.bodySmall, - modifier = Modifier.padding(start = 16.dp) - ) + CustomErrorText(errorText = errorText) } } } From d0f496fbdad5ce43b50fff92735e03cc460b2a5a Mon Sep 17 00:00:00 2001 From: omer358 Date: Wed, 26 Jun 2024 01:47:24 +0400 Subject: [PATCH 2/3] Resolves #21 --- .../peopleList/PeopleListScreen.kt | 27 ++++++---- .../peopleList/composable/EmptyStateScreen.kt | 52 +++++++++++++++++++ .../res/drawable/ic_empty_state_vector.xml | 27 +--------- 3 files changed, 70 insertions(+), 36 deletions(-) create mode 100644 app/src/main/java/com/example/rememberme/presentation/peopleList/composable/EmptyStateScreen.kt diff --git a/app/src/main/java/com/example/rememberme/presentation/peopleList/PeopleListScreen.kt b/app/src/main/java/com/example/rememberme/presentation/peopleList/PeopleListScreen.kt index 3851a66..255c532 100644 --- a/app/src/main/java/com/example/rememberme/presentation/peopleList/PeopleListScreen.kt +++ b/app/src/main/java/com/example/rememberme/presentation/peopleList/PeopleListScreen.kt @@ -26,6 +26,7 @@ import com.example.rememberme.R import com.example.rememberme.data.FakeDataSource import com.example.rememberme.domain.model.People import com.example.rememberme.presentation.common.composables.PeopleListItem +import com.example.rememberme.presentation.peopleList.composable.EmptyStateScreen import com.example.rememberme.ui.theme.RememberMeTheme @Composable @@ -33,7 +34,7 @@ fun PeopleScreen( modifier: Modifier = Modifier, viewModel: PeopleViewModel = hiltViewModel(), navigateToDetailScreen: (Long) -> Unit, - navigateToAddNewPersonScreen:() -> Unit + navigateToAddNewPersonScreen: () -> Unit ) { val people = viewModel.people.collectAsState(initial = emptyList()) PeopleScreenContent(people, modifier, navigateToDetailScreen, navigateToAddNewPersonScreen) @@ -69,16 +70,20 @@ fun PeopleScreenContent( } } ) { it -> - LazyColumn( - modifier = modifier - .padding(it) - .fillMaxSize() - .testTag("PeopleListScreen") - ) { - items(count = peopleState.value.size) { index -> - PeopleListItem(peopleState.value[index], { personId -> - navigateToDetailScreen(personId) - }) + if (peopleState.value.isEmpty()) { + EmptyStateScreen(modifier = modifier.padding(paddingValues = it)) + } else { + LazyColumn( + modifier = modifier + .padding(it) + .fillMaxSize() + .testTag("PeopleListScreen") + ) { + items(count = peopleState.value.size) { index -> + PeopleListItem(peopleState.value[index], { personId -> + navigateToDetailScreen(personId) + }) + } } } } diff --git a/app/src/main/java/com/example/rememberme/presentation/peopleList/composable/EmptyStateScreen.kt b/app/src/main/java/com/example/rememberme/presentation/peopleList/composable/EmptyStateScreen.kt new file mode 100644 index 0000000..b431edc --- /dev/null +++ b/app/src/main/java/com/example/rememberme/presentation/peopleList/composable/EmptyStateScreen.kt @@ -0,0 +1,52 @@ +package com.example.rememberme.presentation.peopleList.composable + +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.example.rememberme.R +import com.example.rememberme.ui.theme.RememberMeTheme + +@Composable +fun EmptyStateScreen( + modifier: Modifier = Modifier +){ + Column ( + modifier = modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ){ + Image( + painter = painterResource(id = R.drawable.ic_empty_state_vector), + contentDescription = null, + modifier = Modifier.size(300.dp) + ) + Text( + text = "Go out and Be social!", + style = MaterialTheme.typography.titleLarge + ) + } + +} + +@Preview(showBackground = true) +@Preview(uiMode = UI_MODE_NIGHT_YES) +@Composable +fun EmptyStateScreenPreview(){ + RememberMeTheme { + Surface { + EmptyStateScreen() + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_empty_state_vector.xml b/app/src/main/res/drawable/ic_empty_state_vector.xml index a6e5fbe..54eeca1 100644 --- a/app/src/main/res/drawable/ic_empty_state_vector.xml +++ b/app/src/main/res/drawable/ic_empty_state_vector.xml @@ -1,11 +1,10 @@ - - - - - - - - - - - - - - - - - - - - - - From 2af8ac6d3c49a286e3939752c9d70617ce160bec Mon Sep 17 00:00:00 2001 From: omer358 Date: Wed, 26 Jun 2024 01:49:05 +0400 Subject: [PATCH 3/3] removes google-services.json from .gitignore --- .gitignore | 1 - app/google-services.json | 29 +++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 app/google-services.json diff --git a/.gitignore b/.gitignore index a56ccc7..7ea9ae8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,6 @@ .externalNativeBuild .cxx local.properties -app/google-services.json # Built application files *.apk diff --git a/app/google-services.json b/app/google-services.json new file mode 100644 index 0000000..ef1b650 --- /dev/null +++ b/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "567382024079", + "project_id": "remember-me-bcd80", + "storage_bucket": "remember-me-bcd80.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:567382024079:android:797e9faec882ed07ec75fa", + "android_client_info": { + "package_name": "com.example.rememberme" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyBLLhJ9ymIEvsinNLYSr4lXdhO5mv0NMIE" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file