diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index adb9b9c..6092165 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -30,7 +30,7 @@ jobs: - name: Load Google Service file env: - data: ${{ secrets.GOOGLE_SERVICES_JSON}} + data: ${{secrets.GOOGLE_SERVICES_JSON}} run: echo $DATA | base64 -di > app/google-services.json - name: Grant execute permission for gradlew diff --git a/app/src/main/java/com/example/rememberme/presentation/addperson/AddPersonEvents.kt b/app/src/main/java/com/example/rememberme/presentation/addperson/AddPersonEvents.kt index 2a9ced2..3d38520 100644 --- a/app/src/main/java/com/example/rememberme/presentation/addperson/AddPersonEvents.kt +++ b/app/src/main/java/com/example/rememberme/presentation/addperson/AddPersonEvents.kt @@ -1,4 +1,7 @@ package com.example.rememberme.presentation.addperson + +import androidx.annotation.DrawableRes + sealed class AddPersonEvents { data class OnFirstNameChange(val firstName: String) : AddPersonEvents() data class OnSecondNameChange(val secondName: String) : AddPersonEvents() @@ -6,6 +9,6 @@ sealed class AddPersonEvents { data class OnTimeChange(val time: String) : AddPersonEvents() data class OnNoteChange(val note: String?) : AddPersonEvents() data class OnGenderChange(val gender: String) : AddPersonEvents() - data class OnAvatarChange(val avatar: String) : AddPersonEvents() + data class OnAvatarChange(@DrawableRes val avatar: Int) : AddPersonEvents() data object OnSavePerson : AddPersonEvents() } \ 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 50c2466..12c2ba1 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 @@ -3,41 +3,66 @@ package com.example.rememberme.presentation.addperson import android.content.res.Configuration.UI_MODE_NIGHT_YES +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth 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.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.RadioButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue 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 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.CustomOutlinedTextField import com.example.rememberme.ui.theme.RememberMeTheme +import kotlinx.coroutines.launch +@OptIn(ExperimentalMaterial3Api::class) @Composable fun AddPersonScreen( viewModel: AddPersonViewModel = hiltViewModel(), popUp: () -> Unit, ) { val uiState = viewModel.uiState.collectAsState().value + val sheetState = rememberModalBottomSheetState() + val scope = rememberCoroutineScope() + var showBottomSheet by remember { mutableStateOf(false) } + var selectedAvatarResId by remember { mutableIntStateOf(R.drawable.ic_m1) } AddPersonContent( - uiState, + uiState = uiState, onFirstNameChange = { viewModel.onEvent(AddPersonEvents.OnFirstNameChange(it)) }, onSecondNameChange = { viewModel.onEvent(AddPersonEvents.OnSecondNameChange(it)) }, onPlaceChange = { viewModel.onEvent(AddPersonEvents.OnPlaceChange(it)) }, @@ -45,9 +70,32 @@ fun AddPersonScreen( onNoteChange = { viewModel.onEvent(AddPersonEvents.OnNoteChange(it)) }, onGenderChange = { viewModel.onEvent(AddPersonEvents.OnGenderChange(it)) }, onAvatarChange = { viewModel.onEvent(AddPersonEvents.OnAvatarChange(it)) }, - onSavePerson = { viewModel.onEvent(AddPersonEvents.OnSavePerson) }, + onSavePerson = { + viewModel.onEvent(AddPersonEvents.OnSavePerson) + popUp() + }, + onAvatarPickerClick = { + showBottomSheet = true + scope.launch { sheetState.show() } + }, + selectedAvatarResId = selectedAvatarResId, popUp = popUp + ) + + if (showBottomSheet) { + AvatarPicker( + onAvatarSelected = { + selectedAvatarResId = it + viewModel.onEvent(AddPersonEvents.OnAvatarChange(it)) + scope.launch { sheetState.hide() } + showBottomSheet = false + }, + onDismissRequest = { + scope.launch { sheetState.hide() } + showBottomSheet = false + } ) + } } @Composable @@ -59,29 +107,32 @@ fun AddPersonContent( onTimeChange: (String) -> Unit, onNoteChange: (String) -> Unit, onGenderChange: (String) -> Unit, - onAvatarChange: (String) -> Unit, + onAvatarChange: (Int) -> Unit, onSavePerson: () -> Unit, + onAvatarPickerClick: () -> Unit, + selectedAvatarResId: Int, popUp: () -> Unit ) { Scaffold( topBar = { TopAppBar( - title = { - Text(text = "Add Person") - }, + title = { Text(text = "Add Person") }, navigationIcon = { IconButton(onClick = { popUp() }) { - Icon(imageVector = Icons.AutoMirrored.Default.ArrowBack, contentDescription = null) + Icon( + imageVector = Icons.AutoMirrored.Default.ArrowBack, + contentDescription = null + ) } } ) } - ) { + ) { paddingValues -> Column( modifier = Modifier .fillMaxSize() .padding(horizontal = 8.dp) - .padding(paddingValues = it) + .padding(paddingValues) ) { CustomOutlinedTextField( value = uiState.firstName, @@ -107,16 +158,28 @@ fun AddPersonContent( selectedGender = uiState.gender, onGenderSelected = onGenderChange ) - CustomOutlinedTextField( - value = uiState.avatar.toString(), - onValueChange = onAvatarChange, - label = "Avatar" - ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(id = selectedAvatarResId), + contentDescription = null, + modifier = Modifier.size(70.dp) + ) + OutlinedButton(onClick = onAvatarPickerClick) { + Text(text = "Pick an avatar") + } + } CustomOutlinedTextField( value = uiState.note ?: "", onValueChange = onNoteChange, label = "Note" ) + Spacer(modifier = Modifier.size(16.dp)) CustomButton( onClick = onSavePerson, text = "Save" @@ -125,6 +188,63 @@ fun AddPersonContent( } } +@Composable +fun AvatarPicker( + onAvatarSelected: (Int) -> Unit, + onDismissRequest: () -> Unit +) { + ModalBottomSheet(onDismissRequest = onDismissRequest) { + val listOfMaleAvatars = remember { + listOf( + R.drawable.ic_m1, + R.drawable.ic_m2, + R.drawable.ic_m3, + R.drawable.ic_m4, + R.drawable.ic_m5 + ) + } + val listOfFemaleAvatars = remember { + listOf( + R.drawable.ic_f1, + R.drawable.ic_f2, + R.drawable.ic_f3, + R.drawable.ic_f4, + R.drawable.ic_f5 + ) + } + val allAvatars = remember { + listOfMaleAvatars + listOfFemaleAvatars + } + Column( + modifier = Modifier.padding(16.dp) + ) { + Text( + "Pick an Avatar", + style = MaterialTheme.typography.displaySmall + ) + Spacer(modifier = Modifier.size(16.dp)) + LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 90.dp)) { + items(allAvatars.size) { index -> + Image( + painter = painterResource(id = allAvatars[index]), + contentDescription = null, + modifier = Modifier + .size(70.dp) + .padding(vertical = 4.dp) + .clickable { onAvatarSelected(allAvatars[index]) } + ) + } + } + Spacer(modifier = Modifier.size(16.dp)) + Button( + onClick = { /* Save button action if needed */ }, + modifier = Modifier.align(Alignment.CenterHorizontally) + ) { + Text(text = "Save") + } + } + } +} @Composable fun GenderRadioButton( @@ -138,20 +258,20 @@ fun GenderRadioButton( Row( horizontalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth() - ){ - genderOptions.forEach { gender -> - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - RadioButton( - selected = (gender == selectedGender), - onClick = { onGenderSelected(gender) } - ) - Text(text = gender, modifier = Modifier.padding(start = 4.dp)) + ) { + genderOptions.forEach { gender -> + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + RadioButton( + selected = (gender == selectedGender), + onClick = { onGenderSelected(gender) } + ) + Text(text = gender, modifier = Modifier.padding(start = 4.dp)) + } } } } - } } @Preview(showBackground = true) @@ -169,6 +289,8 @@ fun AddPersonContentPreview() { onGenderChange = {}, onAvatarChange = {}, onSavePerson = {}, + onAvatarPickerClick = {}, + selectedAvatarResId = R.drawable.ic_m1, 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 9141b8f..7b34827 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 @@ -7,5 +7,5 @@ data class AddPersonUiState( val time: String = "", val note: String? = null, val gender: String = "", - val avatar: String = "" + val avatar: Int = 0, ) \ 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 205c79a..43c19c9 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 @@ -1,8 +1,8 @@ package com.example.rememberme.presentation.addperson +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.example.rememberme.R import com.example.rememberme.domain.model.People import com.example.rememberme.domain.usecases.people.PeopleUseCases import dagger.hilt.android.lifecycle.HiltViewModel @@ -47,6 +47,7 @@ class AddPersonViewModel @Inject constructor( } is AddPersonEvents.OnAvatarChange -> { + Log.i(TAG, "onEvent: OnAvatarChange -> ${event.avatar}") _uiState.value = _uiState.value.copy(avatar = event.avatar) } @@ -67,10 +68,14 @@ class AddPersonViewModel @Inject constructor( note = _uiState.value.note, gender = _uiState.value.gender, // TODO: get avatar from the ui - avatar = R.drawable.ic_m1 + avatar = _uiState.value.avatar ) peopleUseCases.insertPerson(person) } } } + companion object { + private const val TAG = "AddPersonViewModel" + } + } \ No newline at end of file