Skip to content

Issue/11508 coupons card 2 #11543

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 29, 2024
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ fun CouponDetailsScreen(
actions = {
IconButton(onClick = { showMenu = !showMenu }) {
Icon(
Icons.Filled.MoreVert,
imageVector = Icons.Filled.MoreVert,
contentDescription = "Coupons Menu",
tint = colorResource(id = R.color.action_menu_fg_selector)
tint = MaterialTheme.colors.primary
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an inconsistency that I noticed, we were using the wrong color for the menu button here.

)
}
DropdownMenu(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,52 @@
package com.woocommerce.android.ui.dashboard.coupons

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.fillMaxWidth
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.material.ContentAlpha
import androidx.compose.material.Divider
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.navigation.navOptions
import com.woocommerce.android.R
import com.woocommerce.android.extensions.navigateSafely
import com.woocommerce.android.model.DashboardWidget
import com.woocommerce.android.model.DashboardWidget.Type.COUPONS
import com.woocommerce.android.ui.analytics.ranges.StatsTimeRange
import com.woocommerce.android.ui.analytics.ranges.StatsTimeRangeSelection.SelectionType
import com.woocommerce.android.ui.compose.animations.SkeletonView
import com.woocommerce.android.ui.compose.rememberNavController
import com.woocommerce.android.ui.compose.viewModelWithFactory
import com.woocommerce.android.ui.coupons.CouponListFragmentDirections
import com.woocommerce.android.ui.dashboard.DashboardDateRangeHeader
import com.woocommerce.android.ui.dashboard.DashboardFragmentDirections
import com.woocommerce.android.ui.dashboard.DashboardViewModel
import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardWidgetMenu
import com.woocommerce.android.ui.dashboard.WidgetCard
import com.woocommerce.android.ui.dashboard.WidgetError
import com.woocommerce.android.ui.dashboard.coupons.DashboardCouponsViewModel.DateRangeState
import com.woocommerce.android.ui.dashboard.coupons.DashboardCouponsViewModel.State
import com.woocommerce.android.ui.dashboard.coupons.DashboardCouponsViewModel.State.Error
import com.woocommerce.android.ui.dashboard.coupons.DashboardCouponsViewModel.State.Loaded
import com.woocommerce.android.ui.dashboard.coupons.DashboardCouponsViewModel.State.Loading
import com.woocommerce.android.ui.dashboard.defaultHideMenuEntry
import com.woocommerce.android.viewmodel.MultiLiveEvent
import java.util.Date

Expand Down Expand Up @@ -57,6 +77,11 @@ fun DashboardCouponsCard(
viewState = viewState,
onTabSelected = viewModel::onTabSelected,
onCustomRangeClick = viewModel::onEditCustomRangeTapped,
onViewAllClick = viewModel::onViewAllClicked,
onCouponClick = viewModel::onCouponClicked,
onHideClick = { parentViewModel.onHideWidgetClicked(DashboardWidget.Type.COUPONS) },
onRetryClick = viewModel::onRetryClicked,
onContactSupportClick = parentViewModel::onContactSupportClicked,
modifier = modifier
)
}
Expand All @@ -75,6 +100,33 @@ private fun HandleEvents(
is DashboardCouponsViewModel.OpenDatePicker -> {
openDatePicker(event.fromDate.time, event.toDate.time)
}

DashboardCouponsViewModel.ViewAllCoupons -> {
navController.navigateSafely(
DashboardFragmentDirections.actionDashboardToCouponListFragment()
)
}

is DashboardCouponsViewModel.ViewCouponDetails -> {
navController.navigateSafely(
DashboardFragmentDirections.actionDashboardToCouponListFragment()
)
navController.navigateSafely(
directions = CouponListFragmentDirections.actionCouponListFragmentToCouponDetailsFragment(
couponId = event.couponId
),
skipThrottling = true,
navOptions = navOptions {
popUpTo(R.id.dashboard)
anim {
enter = R.anim.default_enter_anim
exit = R.anim.default_exit_anim
popEnter = R.anim.default_pop_enter_anim
popExit = R.anim.default_pop_exit_anim
}
}
Comment on lines +111 to +127
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of duplicating the couponDetailsFragment destination, I decided to just perform two navigation actions to show the details.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that when I tap on a coupon and go back, the list isn't shown like for the reviews, but I'm back at the dashboard screen.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's intentional, since we don't have to handle the undo behavior like in the reviews.

)
}
}
}

Expand All @@ -92,38 +144,62 @@ private fun DashboardCouponsCard(
viewState: State,
onTabSelected: (SelectionType) -> Unit,
onCustomRangeClick: () -> Unit,
onViewAllClick: () -> Unit,
onCouponClick: (Long) -> Unit,
onHideClick: () -> Unit,
onRetryClick: () -> Unit,
onContactSupportClick: () -> Unit,
modifier: Modifier = Modifier
) {
WidgetCard(
titleResource = COUPONS.titleResource,
menu = DashboardWidgetMenu(emptyList()),
menu = DashboardWidgetMenu(
listOf(
DashboardWidget.Type.COUPONS.defaultHideMenuEntry {
onHideClick()
}
)
),
button = DashboardViewModel.DashboardWidgetAction(
titleResource = R.string.dashboard_coupons_view_all_button,
action = onViewAllClick
),
isError = viewState is Error,
modifier = modifier
) {
Column {
DashboardDateRangeHeader(
rangeSelection = dateRangeState.rangeSelection,
dateFormatted = dateRangeState.rangeFormatted,
onCustomRangeClick = onCustomRangeClick,
onTabSelected = onTabSelected,
modifier = Modifier.fillMaxWidth()
)
if (viewState !is Error) {
DashboardDateRangeHeader(
rangeSelection = dateRangeState.rangeSelection,
dateFormatted = dateRangeState.rangeFormatted,
onCustomRangeClick = onCustomRangeClick,
onTabSelected = onTabSelected,
modifier = Modifier.fillMaxWidth()
)

Divider()
Divider()
}

when (viewState) {
is Loading -> {
CircularProgressIndicator()
is State.Loading -> {
CouponsLoading()
}

is Loaded -> {
DashboardCouponsList(viewState)
is State.Loaded -> {
if (viewState.coupons.isEmpty()) {
CouponsEmptyView()
} else {
DashboardCouponsList(
state = viewState,
onCouponClick = onCouponClick
)
}
}

is Error -> {
is State.Error -> {
WidgetError(
onContactSupportClicked = { /*TODO*/ },
onRetryClicked = { /*TODO*/ }
onContactSupportClicked = onContactSupportClick,
onRetryClicked = onRetryClick
)
}
Comment on lines 200 to 204
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, we will need to differentiate between the two types of errors: a generic error, or when WCAdmin is disabled, this differentiation will be added during the work on #11544

}
Expand All @@ -133,13 +209,143 @@ private fun DashboardCouponsCard(

@Composable
private fun DashboardCouponsList(
state: Loaded,
state: State.Loaded,
onCouponClick: (Long) -> Unit,
modifier: Modifier = Modifier
) {
Column(modifier.padding(vertical = 8.dp)) {
Header(
Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
)
Spacer(modifier = Modifier.height(8.dp))
state.coupons.forEach { couponUiModel ->
CouponListItem(
couponUiModel = couponUiModel,
onClick = { onCouponClick(couponUiModel.id) }
)
}
}
}

@Composable
private fun CouponListItem(
couponUiModel: DashboardCouponsViewModel.CouponUiModel,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Column(modifier) {
state.coupons.forEach {
Text(text = it.code)
Divider()
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = modifier
.clickable(onClick = onClick)
.padding(top = 8.dp)
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
Text(
text = couponUiModel.code,
style = MaterialTheme.typography.subtitle1
)
Text(
text = couponUiModel.uses.toString(),
style = MaterialTheme.typography.subtitle1
)
}
Text(
text = couponUiModel.description,
style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.medium),
modifier = Modifier.padding(horizontal = 16.dp)
)
Spacer(modifier = Modifier)
Divider(modifier = Modifier.padding(start = 16.dp))
}
}

@Composable
private fun CouponsLoading(
modifier: Modifier = Modifier
) {
Column(modifier.padding(vertical = 8.dp)) {
Header(
Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
)
Spacer(modifier = Modifier.height(8.dp))
repeat(3) {
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.padding(top = 8.dp)
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
SkeletonView(width = 260.dp, height = 16.dp)
SkeletonView(width = 40.dp, height = 16.dp)
}
SkeletonView(
modifier = Modifier
.size(width = 120.dp, height = 16.dp)
.padding(horizontal = 16.dp)
)
Spacer(modifier = Modifier)
Divider(Modifier.padding(start = 16.dp))
}
}
}
}

@Composable
private fun CouponsEmptyView(
modifier: Modifier = Modifier
) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
.fillMaxWidth()
.padding(16.dp)
) {
Image(
painter = painterResource(id = R.drawable.img_empty_coupon_list),
contentDescription = null,
modifier = Modifier
.sizeIn(maxWidth = 160.dp, maxHeight = 160.dp)
.padding(vertical = 16.dp)
)

Text(
text = stringResource(id = R.string.dashboard_coupons_card_empty_view_message),
style = MaterialTheme.typography.body1,
textAlign = TextAlign.Center
)
}
}

@Composable
private fun Header(modifier: Modifier = Modifier) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = modifier
) {
Text(
text = stringResource(id = R.string.dashboard_coupons_card_header_coupons),
style = MaterialTheme.typography.subtitle2,
color = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.medium)
)
Text(
text = stringResource(id = R.string.dashboard_coupons_card_header_uses),
style = MaterialTheme.typography.subtitle2,
color = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.medium)
)
}
}
Loading
Loading