From 9bf9d5d58fba374261b2711e56b5e368aec7524a Mon Sep 17 00:00:00 2001 From: Devendra Varma Date: Wed, 4 Oct 2023 02:39:06 +0530 Subject: [PATCH] migrate update profile feature from xml to compose --- .../updateprofile/UpdateProfileFragment.kt | 64 ++--------- .../updateprofile/UpdateProfileScreen.kt | 74 +++++++++++++ .../updateprofile/UpdateProfileViewModel.kt | 72 ++++-------- .../widgets/UpdateProfileAppBar.kt | 70 ++++++++++++ .../widgets/UpdateProfileEditField.kt | 64 +++++++++++ .../widgets/UpdateProfileScreenContent.kt | 103 ++++++++++++++++++ .../res/layout/fragment_update_profile.xml | 80 -------------- app/src/main/res/values/strings.xml | 2 + 8 files changed, 345 insertions(+), 184 deletions(-) create mode 100644 app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/UpdateProfileScreen.kt create mode 100644 app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/widgets/UpdateProfileAppBar.kt create mode 100644 app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/widgets/UpdateProfileEditField.kt create mode 100644 app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/widgets/UpdateProfileScreenContent.kt delete mode 100644 app/src/main/res/layout/fragment_update_profile.xml diff --git a/app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/UpdateProfileFragment.kt b/app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/UpdateProfileFragment.kt index 3e42077a..768deef7 100644 --- a/app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/UpdateProfileFragment.kt +++ b/app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/UpdateProfileFragment.kt @@ -4,89 +4,49 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.databinding.DataBindingUtil +import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController import com.hieuwu.groceriesstore.R -import com.hieuwu.groceriesstore.databinding.FragmentUpdateProfileBinding import com.hieuwu.groceriesstore.utilities.showMessageSnackBar import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch @AndroidEntryPoint class UpdateProfileFragment : Fragment() { - lateinit var binding: FragmentUpdateProfileBinding private val viewModel: UpdateProfileViewModel by viewModels() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - binding = DataBindingUtil.inflate( - inflater, - R.layout.fragment_update_profile, - container, - false - ) - binding.lifecycleOwner = this - - binding.viewModel = viewModel + ): View { + return ComposeView(requireContext()).apply { + setContent { + UpdateProfileScreen(onBackClick = ::navigateUp) + } + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) setObserver() - setEventListener() - - return binding.root } - private fun setEventListener() { - binding.toolbar.setNavigationOnClickListener { - findNavController().navigateUp() - } - - binding.toolbar.setOnMenuItemClickListener { item -> - when (item.itemId) { - R.id.action_save -> { - // Update user data to backend - viewModel.updateUserProfile() - true - } - else -> false - } - } + private fun navigateUp() { + findNavController().navigateUp() } private fun setObserver() { viewModel.isDoneUpdate.observe(viewLifecycleOwner) { if (it != null) { if (it == true) { - R.string.add_to_basket showMessageSnackBar(getString(R.string.update_profile_successfully)) } else { showMessageSnackBar(getString(R.string.update_profile_failed)) } } } - - viewLifecycleOwner.lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { - viewModel.user.collect{ - if (it != null) { - viewModel.email = it.email - viewModel.name = it.name - viewModel.phoneNumber = it.phone - viewModel.address = it.address - } - } - } - } - } } } diff --git a/app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/UpdateProfileScreen.kt b/app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/UpdateProfileScreen.kt new file mode 100644 index 00000000..3c675c97 --- /dev/null +++ b/app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/UpdateProfileScreen.kt @@ -0,0 +1,74 @@ +package com.hieuwu.groceriesstore.presentation.updateprofile + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.hilt.navigation.compose.hiltViewModel +import com.hieuwu.groceriesstore.domain.models.UserModel +import com.hieuwu.groceriesstore.presentation.account.DemoUser +import com.hieuwu.groceriesstore.presentation.updateprofile.widgets.UpdateProfileAppBar +import com.hieuwu.groceriesstore.presentation.updateprofile.widgets.UpdateProfileScreenContent + +@Composable +fun UpdateProfileScreen( + modifier: Modifier = Modifier, + onBackClick: () -> Unit = {}, + viewModel: UpdateProfileViewModel = hiltViewModel(), +) { + val user = viewModel.user.collectAsState() + + UpdateProfileScreenView( + modifier = modifier, + user = user.value, + onBackClick = onBackClick, + onSaveClick = { viewModel.updateUserProfile() }, + onNameChanged = { viewModel.name = it }, + onPhoneChanged = { viewModel.phoneNumber = it }, + onEmailChanged = { viewModel.email = it }, + onAddressChanged = { viewModel.address = it }, + ) +} + +@Composable +private fun UpdateProfileScreenView( + modifier: Modifier = Modifier, + user: UserModel?, + onBackClick: () -> Unit = {}, + onSaveClick: () -> Unit = {}, + onNameChanged: (String) -> Unit = {}, + onPhoneChanged: (String) -> Unit = {}, + onEmailChanged: (String) -> Unit = {}, + onAddressChanged: (String) -> Unit = {}, +) { + + Scaffold( + modifier = modifier, + topBar = { + UpdateProfileAppBar( + onBackClick = onBackClick, + onSaveClick = onSaveClick, + ) + } + ) { contentPadding -> + UpdateProfileScreenContent( + modifier = Modifier.padding(contentPadding), + user = user, + onNameChanged = onNameChanged, + onPhoneChanged = onPhoneChanged, + onEmailChanged = onEmailChanged, + onAddressChanged = onAddressChanged, + ) + } +} + +@Preview(showSystemUi = true) +@Composable +private fun UpdateProfileScreenPreview() { + UpdateProfileScreenView( + user = DemoUser, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/UpdateProfileViewModel.kt b/app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/UpdateProfileViewModel.kt index dbaa3b2d..7254b03c 100644 --- a/app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/UpdateProfileViewModel.kt +++ b/app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/UpdateProfileViewModel.kt @@ -1,74 +1,34 @@ package com.hieuwu.groceriesstore.presentation.updateprofile -import androidx.databinding.Bindable import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope -import com.hieuwu.groceriesstore.BR import com.hieuwu.groceriesstore.domain.models.UserModel import com.hieuwu.groceriesstore.domain.usecases.GetProfileUseCase import com.hieuwu.groceriesstore.domain.usecases.UpdateProfileUseCase import com.hieuwu.groceriesstore.presentation.utils.ObservableViewModel import dagger.hilt.android.lifecycle.HiltViewModel -import java.lang.Exception import kotlinx.coroutines.flow.MutableStateFlow -import javax.inject.Inject import kotlinx.coroutines.launch +import javax.inject.Inject @HiltViewModel class UpdateProfileViewModel @Inject constructor( private val updateProfileUseCase: UpdateProfileUseCase, private val getProfileUseCase: GetProfileUseCase, - - ) : +) : ObservableViewModel() { private val _user: MutableStateFlow = MutableStateFlow(null) val user: MutableStateFlow get() = _user - private var _name: String? = null - var name: String? - @Bindable - get() { - return _name - } - set(value) { - _name = value - notifyPropertyChanged(BR.name) - } + var name: String? = null - private var _email: String? = null - var email: String? - @Bindable - get() { - return _email - } - set(value) { - _email = value - notifyPropertyChanged(BR.email) - } + var email: String? = null - private var _phoneNumber: String? = null - var phoneNumber: String? - @Bindable - get() { - return _phoneNumber - } - set(value) { - _phoneNumber = value - notifyPropertyChanged(BR.phoneNumber) - } + var phoneNumber: String? = null - private var _address: String? = null - var address: String? - @Bindable - get() { - return _address - } - set(value) { - _address = value - notifyPropertyChanged(BR.address) - } + var address: String? = null private val _isDoneUpdate = MutableLiveData(null) val isDoneUpdate: LiveData @@ -80,12 +40,20 @@ class UpdateProfileViewModel @Inject constructor( private fun getCurrentUser() { viewModelScope.launch { - getProfileUseCase.execute(GetProfileUseCase.Input()).result.collect{ - _user.value = it + getProfileUseCase.execute(GetProfileUseCase.Input()).result.collect { + setUserProperties(it) + _user.value = it } } } + private fun setUserProperties(user: UserModel?) { + name = user?.name + phoneNumber = user?.phone + email = user?.email + address = user?.address + } + fun updateUserProfile() { val id = _user.value!!.id try { @@ -93,10 +61,10 @@ class UpdateProfileViewModel @Inject constructor( updateProfileUseCase.execute( UpdateProfileUseCase.Input( userId = id, - name = _name!!, - email = _email!!, - phone = _phoneNumber!!, - address = _address!! + name = name!!, + email = email!!, + phone = phoneNumber!!, + address = address!! ) ) } diff --git a/app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/widgets/UpdateProfileAppBar.kt b/app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/widgets/UpdateProfileAppBar.kt new file mode 100644 index 00000000..9cf00f18 --- /dev/null +++ b/app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/widgets/UpdateProfileAppBar.kt @@ -0,0 +1,70 @@ +package com.hieuwu.groceriesstore.presentation.updateprofile.widgets + +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import com.hieuwu.groceriesstore.R + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun UpdateProfileAppBar( + modifier: Modifier = Modifier, + onBackClick: () -> Unit, + onSaveClick: () -> Unit +) { + CenterAlignedTopAppBar( + modifier = modifier, + navigationIcon = { + IconButton(onClick = onBackClick) { + Icon( + painter = painterResource(id = R.drawable.ic_detail_back), + tint = Color.Unspecified, + contentDescription = null, + ) + } + }, + title = { + Text( + text = stringResource(R.string.update_profile), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = Color.White, + style = MaterialTheme.typography.titleLarge + ) + }, + actions = { + TextButton(onClick = onSaveClick) { + Text( + text = stringResource(R.string.save).uppercase(), + style = MaterialTheme.typography.titleMedium, + color = Color.White, + ) + } + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = colorResource(id = R.color.colorPrimary) + ), + ) +} + +@Preview +@Composable +private fun UpdateProfileAppBarPreview() { + UpdateProfileAppBar( + onBackClick = {}, + onSaveClick = {}, + ) +} diff --git a/app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/widgets/UpdateProfileEditField.kt b/app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/widgets/UpdateProfileEditField.kt new file mode 100644 index 00000000..34928799 --- /dev/null +++ b/app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/widgets/UpdateProfileEditField.kt @@ -0,0 +1,64 @@ +package com.hieuwu.groceriesstore.presentation.updateprofile.widgets + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.tooling.preview.Preview +import com.hieuwu.groceriesstore.R + +@Composable +fun UpdateProfileEditField( + modifier: Modifier = Modifier, + value: String, + onValueChange: (String) -> Unit, + singleLine: Boolean = false, + maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, + placeholder: String? = null, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, +) { + MaterialTheme( + colorScheme = MaterialTheme.colorScheme.copy( + primary = colorResource(id = R.color.colorPrimary), + surfaceVariant = Color.Transparent, + ), + ) { + TextField( + modifier = modifier, + value = value, + onValueChange = onValueChange, + keyboardOptions = keyboardOptions, + singleLine = singleLine, + maxLines = maxLines, + placeholder = placeholder?.let { + @Composable { + Text( + text = placeholder, + color = colorResource(id = R.color.light_gray) + ) + } + } + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun UpdateProfileEditFieldPreview() { + var demoText by remember { mutableStateOf("") } + UpdateProfileEditField( + modifier = Modifier.fillMaxWidth(), + value = demoText, + placeholder = "Demo Text", + onValueChange = { demoText = it } + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/widgets/UpdateProfileScreenContent.kt b/app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/widgets/UpdateProfileScreenContent.kt new file mode 100644 index 00000000..85f53322 --- /dev/null +++ b/app/src/main/java/com/hieuwu/groceriesstore/presentation/updateprofile/widgets/UpdateProfileScreenContent.kt @@ -0,0 +1,103 @@ +package com.hieuwu.groceriesstore.presentation.updateprofile.widgets + +import androidx.compose.foundation.layout.Arrangement +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.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import com.hieuwu.groceriesstore.R +import com.hieuwu.groceriesstore.domain.models.UserModel +import com.hieuwu.groceriesstore.presentation.account.DemoUser + +@Composable +fun UpdateProfileScreenContent( + modifier: Modifier = Modifier, + user: UserModel?, + onNameChanged: (String) -> Unit = {}, + onPhoneChanged: (String) -> Unit = {}, + onEmailChanged: (String) -> Unit = {}, + onAddressChanged: (String) -> Unit = {}, +) { + Column( + modifier = modifier.padding( + horizontal = dimensionResource(id = R.dimen.margin_medium), + vertical = dimensionResource(id = R.dimen.margin_small), + ), + verticalArrangement = Arrangement.spacedBy( + dimensionResource(id = R.dimen.margin_small) + ) + ) { + + var name by remember { mutableStateOf(user?.name) } + var phone by remember { mutableStateOf(user?.phone) } + var email by remember { mutableStateOf(user?.email) } + var address by remember { mutableStateOf(user?.address) } + + UpdateProfileEditField( + modifier = Modifier.fillMaxWidth(), + value = name.orEmpty(), + placeholder = stringResource(id = R.string.name), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + singleLine = true, + onValueChange = { + onNameChanged(it) + name = it + } + ) + UpdateProfileEditField( + modifier = Modifier.fillMaxWidth(), + value = phone.orEmpty(), + placeholder = stringResource(id = R.string.phone_number), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone), + singleLine = true, + onValueChange = { + onPhoneChanged(it) + phone = it + } + ) + UpdateProfileEditField( + modifier = Modifier.fillMaxWidth(), + value = email.orEmpty(), + placeholder = stringResource(id = R.string.email), + singleLine = true, + onValueChange = { + onEmailChanged(it) + email = it + } + ) + UpdateProfileEditField( + modifier = Modifier.fillMaxWidth(), + value = address.orEmpty(), + placeholder = stringResource(id = R.string.address), + maxLines = 3, + onValueChange = { + onAddressChanged(it) + address = it + } + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun UpdateProfileScreenContentFilledPreview() { + UpdateProfileScreenContent(user = DemoUser) +} + +@Preview(showBackground = true) +@Composable +private fun UpdateProfileScreenContentEmptyPreview() { + UpdateProfileScreenContent( + user = DemoUser.copy(name = "", email = "", phone = "", address = "") + ) +} diff --git a/app/src/main/res/layout/fragment_update_profile.xml b/app/src/main/res/layout/fragment_update_profile.xml deleted file mode 100644 index cb907c15..00000000 --- a/app/src/main/res/layout/fragment_update_profile.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 26d9af53..3ede0840 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -78,4 +78,6 @@ Delete Welcome to our store Get your gorceries in as fast as one hour + Update Profile + Save \ No newline at end of file