From d09102aef13d44838d17eb53feca8538f0fa285a Mon Sep 17 00:00:00 2001 From: Devendra Varma Date: Mon, 2 Oct 2023 00:54:01 +0530 Subject: [PATCH 1/2] migrate account feature from xml to compose --- app/build.gradle | 1 + .../presentation/account/AccountFragment.kt | 68 ++----- .../presentation/account/AccountScreen.kt | 102 ++++++++++ .../account/widgets/AccountContentView.kt | 137 +++++++++++++ .../account/widgets/AccountHeaderView.kt | 108 ++++++++++ .../core/widgets/PrimaryButton.kt | 2 +- .../presentation/utils/Converter.kt | 4 +- .../presentation/utils/DensityUtils.kt | 17 ++ app/src/main/res/layout/fragment_account.xml | 189 ------------------ app/src/main/res/navigation/navigation.xml | 9 +- gradle/libs.versions.toml | 1 + 11 files changed, 390 insertions(+), 248 deletions(-) create mode 100644 app/src/main/java/com/hieuwu/groceriesstore/presentation/account/AccountScreen.kt create mode 100644 app/src/main/java/com/hieuwu/groceriesstore/presentation/account/widgets/AccountContentView.kt create mode 100644 app/src/main/java/com/hieuwu/groceriesstore/presentation/account/widgets/AccountHeaderView.kt create mode 100644 app/src/main/java/com/hieuwu/groceriesstore/presentation/utils/DensityUtils.kt delete mode 100644 app/src/main/res/layout/fragment_account.xml diff --git a/app/build.gradle b/app/build.gradle index 1530dd51..b586a52a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -82,6 +82,7 @@ dependencies { implementation libs.glide annotationProcessor libs.glide.compiler implementation libs.lottie + implementation libs.lottie.compose implementation libs.bundles.retrofit implementation libs.gson diff --git a/app/src/main/java/com/hieuwu/groceriesstore/presentation/account/AccountFragment.kt b/app/src/main/java/com/hieuwu/groceriesstore/presentation/account/AccountFragment.kt index 58b4b777..fbf695e6 100644 --- a/app/src/main/java/com/hieuwu/groceriesstore/presentation/account/AccountFragment.kt +++ b/app/src/main/java/com/hieuwu/groceriesstore/presentation/account/AccountFragment.kt @@ -5,48 +5,30 @@ 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.FragmentAccountBinding import com.hieuwu.groceriesstore.presentation.AuthActivity import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.launch @AndroidEntryPoint class AccountFragment : Fragment() { - private lateinit var binding: FragmentAccountBinding - private val viewModel: AccountViewModel by viewModels() - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - binding = DataBindingUtil.inflate( - inflater, R.layout.fragment_account, container, false - ) - - binding.viewModel = viewModel - binding.lifecycleOwner = this - setObserver() - setEventListener() - - return binding.root - } - - private fun setObserver() { - viewLifecycleOwner.lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { - viewModel.user.collect {} - } + ): View { + + return ComposeView(requireContext()).apply { + setContent { + AccountScreen( + onSignInClick = ::navigateToAuthentication, + onProfileSettingsClick = ::navigateToProfileSettings, + onNotificationSettingsClick = ::navigateToNotificationsSettings, + onOrderHistorySettingsClick = ::navigateToOrderHistory, + ) } } } @@ -56,27 +38,15 @@ class AccountFragment : Fragment() { startActivity(intent) } - private fun setEventListener() { - with(binding) { - profileLayout.setOnClickListener { - findNavController().navigate(R.id.action_accountFragment_to_updateProfileFragment) - } - - notificationSettingLayout.setOnClickListener { - findNavController().navigate(R.id.action_accountFragment_to_notificationSettingsFragment) - } - - orderHistoryLayout.setOnClickListener { - findNavController().navigate(R.id.action_accountFragment_to_orderHistoryFragment) - } + private fun navigateToProfileSettings() { + findNavController().navigate(R.id.action_accountFragment_to_updateProfileFragment) + } - signoutButton.setOnClickListener { - viewModel?.signOut() - } + private fun navigateToNotificationsSettings() { + findNavController().navigate(R.id.action_accountFragment_to_notificationSettingsFragment) + } - signinButton.setOnClickListener { - navigateToAuthentication() - } - } + private fun navigateToOrderHistory() { + findNavController().navigate(R.id.action_accountFragment_to_orderHistoryFragment) } } diff --git a/app/src/main/java/com/hieuwu/groceriesstore/presentation/account/AccountScreen.kt b/app/src/main/java/com/hieuwu/groceriesstore/presentation/account/AccountScreen.kt new file mode 100644 index 00000000..c4198b18 --- /dev/null +++ b/app/src/main/java/com/hieuwu/groceriesstore/presentation/account/AccountScreen.kt @@ -0,0 +1,102 @@ +package com.hieuwu.groceriesstore.presentation.account + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +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.res.dimensionResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.hilt.navigation.compose.hiltViewModel +import com.hieuwu.groceriesstore.R +import com.hieuwu.groceriesstore.domain.models.UserModel +import com.hieuwu.groceriesstore.presentation.account.widgets.AccountContentView +import com.hieuwu.groceriesstore.presentation.account.widgets.AccountHeaderView + +@Composable +fun AccountScreen( + modifier: Modifier = Modifier, + viewModel: AccountViewModel = hiltViewModel(), + onSignInClick: (() -> Unit), + onProfileSettingsClick: (() -> Unit), + onNotificationSettingsClick: (() -> Unit), + onOrderHistorySettingsClick: (() -> Unit), +) { + val user = viewModel.user.collectAsState() + + AccountScreenView( + modifier = modifier, + user = user.value, + onSignInClick = onSignInClick, + onProfileSettingsClick = onProfileSettingsClick, + onNotificationSettingsClick = onNotificationSettingsClick, + onOrderHistorySettingsClick = onOrderHistorySettingsClick, + onSignOutClick = { viewModel.signOut() }, + ) +} + +@Composable +private fun AccountScreenView( + modifier: Modifier = Modifier, + user: UserModel? = null, + onSignInClick: () -> Unit, + onProfileSettingsClick: () -> Unit, + onNotificationSettingsClick: () -> Unit, + onOrderHistorySettingsClick: () -> Unit, + onSignOutClick: () -> Unit, +) { + Scaffold { contentPadding -> + Column( + modifier = modifier.padding(contentPadding), + verticalArrangement = Arrangement.Top + ) { + + AccountHeaderView(user = user) + AccountContentView( + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.margin_medium)), + user = user, + onSignInClick = onSignInClick, + onProfileSettingsClick = onProfileSettingsClick, + onNotificationSettingsClick = onNotificationSettingsClick, + onOrderHistorySettingsClick = onOrderHistorySettingsClick, + onSignOutClick = onSignOutClick, + ) + } + } +} + +internal val DemoUser = UserModel( + "0", "J.K. Rowling", "j.k@rowling.com", + phone = "+1234567890", + address = "Earth", + isOrderCreatedNotiEnabled = false, + isPromotionNotiEnabled = false, + isDataRefreshedNotiEnabled = false +) + +@Preview(showSystemUi = true) +@Composable +private fun SignedInAccountScreen() { + AccountScreenView( + user = DemoUser, + onSignInClick = { /*TODO*/ }, + onProfileSettingsClick = { /*TODO*/ }, + onNotificationSettingsClick = { /*TODO*/ }, + onOrderHistorySettingsClick = { /*TODO*/ }, + onSignOutClick = { /*TODO*/ }, + ) +} + +@Preview(showSystemUi = true) +@Composable +private fun SignedOutAccountScreen() { + AccountScreenView( + onSignInClick = { /*TODO*/ }, + onProfileSettingsClick = { /*TODO*/ }, + onNotificationSettingsClick = { /*TODO*/ }, + onOrderHistorySettingsClick = { /*TODO*/ }, + onSignOutClick = { /*TODO*/ }, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/hieuwu/groceriesstore/presentation/account/widgets/AccountContentView.kt b/app/src/main/java/com/hieuwu/groceriesstore/presentation/account/widgets/AccountContentView.kt new file mode 100644 index 00000000..d6774a0a --- /dev/null +++ b/app/src/main/java/com/hieuwu/groceriesstore/presentation/account/widgets/AccountContentView.kt @@ -0,0 +1,137 @@ +package com.hieuwu.groceriesstore.presentation.account.widgets + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +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 +import com.hieuwu.groceriesstore.presentation.core.widgets.PrimaryButton +import com.hieuwu.groceriesstore.presentation.core.widgets.SecondaryButton + +@Composable +fun AccountContentView( + modifier: Modifier = Modifier, + user: UserModel? = null, + onSignInClick: () -> Unit, + onProfileSettingsClick: () -> Unit, + onNotificationSettingsClick: () -> Unit, + onOrderHistorySettingsClick: () -> Unit, + onSignOutClick: () -> Unit, +) { + if (user != null) { + AccountContentSignedInView( + modifier = modifier, + onProfileSettingsClick = onProfileSettingsClick, + onNotificationSettingsClick = onNotificationSettingsClick, + onOrderHistorySettingsClick = onOrderHistorySettingsClick, + onSignOutClick = onSignOutClick, + ) + } else { + AccountContentSignedOutView( + modifier = modifier.padding(vertical = dimensionResource(id = R.dimen.margin_medium)), + onSignInClick = onSignInClick + ) + } +} + +@Composable +fun AccountContentSignedInView( + modifier: Modifier = Modifier, + onProfileSettingsClick: (() -> Unit), + onNotificationSettingsClick: (() -> Unit), + onOrderHistorySettingsClick: (() -> Unit), + onSignOutClick: (() -> Unit), +) { + val actions = listOf( + R.string.profile_information to onProfileSettingsClick, + R.string.notification_settings to onNotificationSettingsClick, + R.string.order_history_settings to onOrderHistorySettingsClick, + ) + for (action in actions) { + Row( + modifier = modifier + .fillMaxWidth() + .padding(top = dimensionResource(id = R.dimen.margin_small)), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(id = action.first), + fontWeight = FontWeight.Bold, + color = Color(0xFF808080), + maxLines = 1 + ) + Image( + modifier = Modifier.size(dimensionResource(id = R.dimen.icon_small)), + painter = painterResource(id = R.drawable.ic_baseline_chevron_right_24), + contentDescription = null, + ) + } + } + + SecondaryButton( + modifier = modifier + .fillMaxWidth() + .padding(vertical = dimensionResource(id = R.dimen.margin_medium)), + onClick = onSignOutClick + ) { + Text(text = stringResource(id = R.string.sign_out)) + } +} + +@Composable +fun AccountContentSignedOutView( + modifier: Modifier = Modifier, + onSignInClick: (() -> Unit), +) { + PrimaryButton( + modifier = modifier.fillMaxWidth(), + onClick = onSignInClick + ) { + Text(text = stringResource(id = R.string.sign_in)) + } +} + +@Preview(showBackground = true) +@Composable +private fun AccountContentViewSignedInStatePreview() { + Column { + AccountContentView( + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.margin_medium)), + user = DemoUser, + onSignInClick = {}, + onProfileSettingsClick = {}, + onNotificationSettingsClick = {}, + onOrderHistorySettingsClick = {}, + ) {} + } +} + +@Preview +@Composable +private fun AccountContentViewSignedOutStatePreview() { + Column { + AccountContentView( + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.margin_medium)), + onSignInClick = {}, + onProfileSettingsClick = {}, + onNotificationSettingsClick = {}, + onOrderHistorySettingsClick = {}, + ) {} + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hieuwu/groceriesstore/presentation/account/widgets/AccountHeaderView.kt b/app/src/main/java/com/hieuwu/groceriesstore/presentation/account/widgets/AccountHeaderView.kt new file mode 100644 index 00000000..857e87f3 --- /dev/null +++ b/app/src/main/java/com/hieuwu/groceriesstore/presentation/account/widgets/AccountHeaderView.kt @@ -0,0 +1,108 @@ +package com.hieuwu.groceriesstore.presentation.account.widgets + +import androidx.compose.foundation.background +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.layout.size +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.airbnb.lottie.compose.LottieAnimation +import com.airbnb.lottie.compose.LottieCompositionSpec +import com.airbnb.lottie.compose.LottieConstants +import com.airbnb.lottie.compose.rememberLottieComposition +import com.hieuwu.groceriesstore.R +import com.hieuwu.groceriesstore.domain.models.UserModel +import com.hieuwu.groceriesstore.presentation.account.DemoUser +import com.hieuwu.groceriesstore.presentation.utils.Converter +import com.hieuwu.groceriesstore.presentation.utils.textUnit + +@Composable +fun AccountHeaderView( + modifier: Modifier = Modifier, + user: UserModel? = null, +) { + Column( + modifier = modifier + .fillMaxWidth() + .background(color = colorResource(id = R.color.colorPrimary)) + .padding(dimensionResource(id = R.dimen.margin_medium)), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.Start, + ) { + AccountHeaderTitle( + modifier = Modifier.padding(top = dimensionResource(id = R.dimen.margin_medium)), + user = user + ) + if (user != null) { + AccountHeaderSignedInView(user = user) + } else { + AccountHeaderSignedOutView( + modifier = Modifier.align(Alignment.CenterHorizontally) + ) + } + } +} + +@Composable +fun AccountHeaderTitle( + modifier: Modifier = Modifier, + user: UserModel? = null, +) { + Text( + modifier = modifier, + text = Converter.stringToEmpTy(user?.name), + fontSize = textUnit(id = R.dimen.text_big_bold), + fontWeight = FontWeight.Bold, + color = Color.White, + ) +} + +@Composable +fun AccountHeaderSignedInView( + modifier: Modifier = Modifier, + user: UserModel +) { + val userDetails = listOf(user.address, user.email, user.phone) + for (userDetail in userDetails) { + Text( + modifier = modifier, + text = userDetail.orEmpty(), + color = Color.White, + ) + } +} + +@Composable +fun AccountHeaderSignedOutView(modifier: Modifier = Modifier) { + val composition by rememberLottieComposition( + LottieCompositionSpec.Asset("list_empty.json") + ) + LottieAnimation( + modifier = modifier.size(200.dp), + composition = composition, + iterations = LottieConstants.IterateForever, + ) +} + +@Preview +@Composable +private fun AccountHeaderViewSignedInStatePreview() { + AccountHeaderView(user = DemoUser) +} + +@Preview +@Composable +private fun AccountHeaderViewSignedOutStatePreview() { + AccountHeaderView() +} \ No newline at end of file diff --git a/app/src/main/java/com/hieuwu/groceriesstore/presentation/core/widgets/PrimaryButton.kt b/app/src/main/java/com/hieuwu/groceriesstore/presentation/core/widgets/PrimaryButton.kt index 51fa0e93..6768b477 100644 --- a/app/src/main/java/com/hieuwu/groceriesstore/presentation/core/widgets/PrimaryButton.kt +++ b/app/src/main/java/com/hieuwu/groceriesstore/presentation/core/widgets/PrimaryButton.kt @@ -28,7 +28,7 @@ fun PrimaryButton( onClick = onClick, shape = RoundedCornerShape(8.dp), colors = ButtonDefaults.buttonColors( - containerColor = colorResource(id = R.color.colorPrimaryDark), + containerColor = colorResource(id = R.color.colorPrimary), disabledContainerColor = colorResource(id = R.color.light_gray), contentColor = Color.White, ), diff --git a/app/src/main/java/com/hieuwu/groceriesstore/presentation/utils/Converter.kt b/app/src/main/java/com/hieuwu/groceriesstore/presentation/utils/Converter.kt index 275f0b49..02bc8585 100644 --- a/app/src/main/java/com/hieuwu/groceriesstore/presentation/utils/Converter.kt +++ b/app/src/main/java/com/hieuwu/groceriesstore/presentation/utils/Converter.kt @@ -20,9 +20,7 @@ object Converter { @InverseMethod("stringToEmpTy") @JvmStatic - fun stringToEmpTy( - value: String? - ): String? { + fun stringToEmpTy(value: String?): String { return if (value.isNullOrEmpty()) return "Not signed in" else value } diff --git a/app/src/main/java/com/hieuwu/groceriesstore/presentation/utils/DensityUtils.kt b/app/src/main/java/com/hieuwu/groceriesstore/presentation/utils/DensityUtils.kt new file mode 100644 index 00000000..61a3b508 --- /dev/null +++ b/app/src/main/java/com/hieuwu/groceriesstore/presentation/utils/DensityUtils.kt @@ -0,0 +1,17 @@ +package com.hieuwu.groceriesstore.presentation.utils + +import androidx.annotation.DimenRes +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.unit.TextUnit + +@Composable +@ReadOnlyComposable +fun textUnit(@DimenRes id: Int): TextUnit { + val dimension = dimensionResource(id = id) + return with(LocalDensity.current) { + dimension.toSp() + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_account.xml b/app/src/main/res/layout/fragment_account.xml deleted file mode 100644 index 5a668c42..00000000 --- a/app/src/main/res/layout/fragment_account.xml +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - -