parent
689ef92e41
commit
6169aaa7eb
@ -0,0 +1,475 @@
|
|||||||
|
package com.google.samples.apps.nowinandroid
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.google.samples.apps.nowinandroid.RefundMethodType.MEESHO_APP
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionState.Error
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionState.Idle
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionState.Loading
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionState.Success
|
||||||
|
|
||||||
|
data class OrderItem(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val imageUrl: Int, // Using Drawable Res ID for simplicity
|
||||||
|
val price: Int,
|
||||||
|
val size: String,
|
||||||
|
val quantity: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
// Representing different refund methods
|
||||||
|
enum class RefundMethodType {
|
||||||
|
MEESHO_APP, BANK_UPI
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RefundOption(
|
||||||
|
val id: RefundMethodType,
|
||||||
|
val title: String,
|
||||||
|
val subtitle: String? = null,
|
||||||
|
val icon: Int, // Drawable Res ID
|
||||||
|
val getAmount: Int,
|
||||||
|
val extraAmount: Int? = null,
|
||||||
|
val isSelected: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- State Holder (Can be hoisted to a ViewModel in a real app) ---
|
||||||
|
// This manages the state of the screen
|
||||||
|
class ReturnOrderState(
|
||||||
|
initialOrderItem: OrderItem,
|
||||||
|
initialRefundOptions: List<RefundOption>,
|
||||||
|
initialSelectedOption: RefundMethodType? = null // Start with no selection or pre-select
|
||||||
|
) {
|
||||||
|
var orderItem by mutableStateOf(initialOrderItem)
|
||||||
|
private set // Can only be modified internally or via specific functions
|
||||||
|
|
||||||
|
var refundOptions by mutableStateOf(initialRefundOptions)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var selectedRefundMethod by mutableStateOf(initialSelectedOption)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var submissionState by mutableStateOf<SubmissionState>(Idle)
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun selectRefundMethod(type: RefundMethodType) {
|
||||||
|
selectedRefundMethod = type
|
||||||
|
refundOptions = refundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == type)
|
||||||
|
}
|
||||||
|
// Reset submission state if user changes selection after an error
|
||||||
|
if (submissionState is Error) {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitRefund() {
|
||||||
|
if (selectedRefundMethod == null) {
|
||||||
|
submissionState = Error("Please select a refund method.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
submissionState = Loading
|
||||||
|
// --- Simulate Network Call ---
|
||||||
|
// In a real app, launch a coroutine and call repository/usecase
|
||||||
|
// viewModelScope.launch { ... }
|
||||||
|
// For now, just simulate success/failure after a delay
|
||||||
|
// Handler(Looper.getMainLooper()).postDelayed({
|
||||||
|
// submissionState = if (Random.nextBoolean()) SubmissionState.Success
|
||||||
|
// else SubmissionState.Error("Refund submission failed. Please try again.")
|
||||||
|
// }, 2000)
|
||||||
|
println("Submitting refund for method: $selectedRefundMethod")
|
||||||
|
// For preview, let's just set to Success immediately for simplicity
|
||||||
|
submissionState = Success
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetSubmissionState() {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberReturnOrderState(
|
||||||
|
initialOrderItem: OrderItem,
|
||||||
|
initialRefundOptions: List<RefundOption>,
|
||||||
|
initialSelectedOption: RefundMethodType? = MEESHO_APP // Pre-select Meesho
|
||||||
|
): ReturnOrderState {
|
||||||
|
return remember {
|
||||||
|
ReturnOrderState(
|
||||||
|
initialOrderItem = initialOrderItem,
|
||||||
|
initialRefundOptions = initialRefundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == initialSelectedOption) // Set initial selection state
|
||||||
|
},
|
||||||
|
initialSelectedOption = initialSelectedOption
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Sealed class for Submission State (more robust error/loading handling) ---
|
||||||
|
sealed class SubmissionState {
|
||||||
|
object Idle : SubmissionState()
|
||||||
|
object Loading : SubmissionState()
|
||||||
|
object Success : SubmissionState()
|
||||||
|
data class Error(val message: String) : SubmissionState()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Main Screen Composable ---
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReturnOrderScreen(
|
||||||
|
state: ReturnOrderState,
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
// In a real app, submission would likely trigger a ViewModel function
|
||||||
|
// onSubmit: (RefundMethodType) -> Unit
|
||||||
|
) {
|
||||||
|
val snackbarHostState = SnackbarHostState()
|
||||||
|
|
||||||
|
// Handle submission state changes (e.g., show Snackbar for errors)
|
||||||
|
LaunchedEffect(state.submissionState) {
|
||||||
|
when (val submission = state.submissionState) {
|
||||||
|
is Error -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = submission.message,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
// Optionally reset state after showing error
|
||||||
|
// state.resetSubmissionState()
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = "Refund submitted successfully!",
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
// Potentially navigate away or show success screen
|
||||||
|
// onNavigateBack() // Example: Navigate back on success
|
||||||
|
}
|
||||||
|
else -> {} // Idle or Loading
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
|
bottomBar = {
|
||||||
|
SubmitButton(
|
||||||
|
isLoading = state.submissionState == Loading,
|
||||||
|
isEnabled = state.selectedRefundMethod != null && state.submissionState != Loading,
|
||||||
|
onClick = { state.submitRefund() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues) // Apply scaffold padding
|
||||||
|
.verticalScroll(rememberScrollState()) // Make content scrollable
|
||||||
|
.padding(bottom = 80.dp) // Add padding to avoid overlap with bottom bar
|
||||||
|
) {
|
||||||
|
OrderItemSummary(item = state.orderItem)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
RefundMethodSelection(
|
||||||
|
options = state.refundOptions,
|
||||||
|
selectedMethod = state.selectedRefundMethod,
|
||||||
|
onOptionSelected = { state.selectRefundMethod(it) }
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// Show info box only when Meesho App refund is selected
|
||||||
|
if (state.selectedRefundMethod == MEESHO_APP) {
|
||||||
|
MeeshoInfoBox(onLearnMoreClick = { /* TODO: Handle Learn More click */ })
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OrderItemSummary(item: OrderItem) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = item.imageUrl), // Replace with actual image loading
|
||||||
|
contentDescription = item.name,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(60.dp)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.border(1.dp, Color.LightGray.copy(alpha = 0.5f), RoundedCornerShape(4.dp)),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
item.name,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
"₹${item.price}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Size: ${item.size}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Qty: ${item.quantity}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RefundMethodSelection(
|
||||||
|
options: List<RefundOption>,
|
||||||
|
selectedMethod: RefundMethodType?,
|
||||||
|
onOptionSelected: (RefundMethodType) -> Unit
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||||
|
Text(
|
||||||
|
"Choose your refund method",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp) // Spacing between cards
|
||||||
|
) {
|
||||||
|
options.forEach { option ->
|
||||||
|
RefundOptionCard(
|
||||||
|
option = option.copy(isSelected = option.id == selectedMethod), // Ensure isSelected reflects state
|
||||||
|
onClick = { onOptionSelected(option.id) },
|
||||||
|
modifier = Modifier.weight(1f) // Make cards share width equally
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun RefundOptionCard(
|
||||||
|
option: RefundOption,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val borderColor = if (option.isSelected) Color(0xFF8A2BE2) else Color.LightGray // Purple when selected
|
||||||
|
val borderWidth = if (option.isSelected) 1.5.dp else 1.dp
|
||||||
|
val backgroundColor = Color.White // Or MaterialTheme.colors.surface
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = modifier
|
||||||
|
.border(borderWidth, borderColor, RoundedCornerShape(8.dp))
|
||||||
|
.clip(RoundedCornerShape(8.dp)) // Clip needed for click effect ripple
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Box { // Use Box to overlay the checkmark
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(), // Ensure column takes width for alignment
|
||||||
|
horizontalAlignment = Alignment.Start // Align content to the start
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = option.icon),
|
||||||
|
contentDescription = null, // Decorative icon
|
||||||
|
modifier = Modifier.size(28.dp), // Adjust size as needed
|
||||||
|
tint = Color.Unspecified // Use original icon colors if applicable
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(
|
||||||
|
option.title,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color.DarkGray
|
||||||
|
)
|
||||||
|
option.subtitle?.let {
|
||||||
|
Text(
|
||||||
|
it,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Divider(modifier = Modifier.padding(vertical = 8.dp), color = Color.LightGray.copy(alpha = 0.5f)) // Subtle divider
|
||||||
|
Text(
|
||||||
|
"Get ₹${option.getAmount}",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
option.extraAmount?.let { extra ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
.background(
|
||||||
|
Color(0xFFE6F9E6),
|
||||||
|
RoundedCornerShape(4.dp)
|
||||||
|
) // Light green background
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Extra ₹$extra",
|
||||||
|
color = Color(0xFF006400), // Dark green text
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add Spacer at the bottom if extraAmount is null to maintain some height consistency
|
||||||
|
if (option.extraAmount == null) {
|
||||||
|
Spacer(modifier = Modifier.height(28.dp)) // Adjust height to match the 'Extra' box approx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selection Indicator (Checkmark)
|
||||||
|
if (option.isSelected) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
|
contentDescription = "Selected",
|
||||||
|
tint = Color(0xFF8A2BE2), // Purple checkmark
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
.padding(6.dp)
|
||||||
|
.size(20.dp)
|
||||||
|
.background(Color.White, CircleShape) // White background behind check
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MeeshoInfoBox(onLearnMoreClick: () -> Unit) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Use it on your next order",
|
||||||
|
color = Color.White,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
buildAnnotatedString {
|
||||||
|
withStyle(style = SpanStyle(
|
||||||
|
color = Color.White,
|
||||||
|
textDecoration = TextDecoration.Underline)
|
||||||
|
) {
|
||||||
|
append("Learn more")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable(onClick = onLearnMoreClick),
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun SubmitButton(
|
||||||
|
isLoading: Boolean,
|
||||||
|
isEnabled: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp) // Padding around the button
|
||||||
|
.height(48.dp), // Standard button height
|
||||||
|
enabled = isEnabled && !isLoading, // Disable if not selected OR loading
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
color = Color.White,
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
"Submit",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,446 @@
|
|||||||
|
package com.google.samples.apps.nowinandroid
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.google.samples.apps.nowinandroid.RefundMethodTypeV10.MEESHO_APP
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV10.Error
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV10.Idle
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV10.Loading
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV10.Success
|
||||||
|
|
||||||
|
data class OrderItemV10(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val imageUrl: Int,
|
||||||
|
val price: Int,
|
||||||
|
val size: String,
|
||||||
|
val quantity: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class RefundMethodTypeV10 {
|
||||||
|
MEESHO_APP, BANK_UPI
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RefundOptionV10(
|
||||||
|
val id: RefundMethodTypeV10,
|
||||||
|
val title: String,
|
||||||
|
val subtitle: String? = null,
|
||||||
|
val icon: Int,
|
||||||
|
val getAmount: Int,
|
||||||
|
val extraAmount: Int? = null,
|
||||||
|
val isSelected: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
class ReturnOrderStateV10(
|
||||||
|
initialOrderItem: OrderItemV10,
|
||||||
|
initialRefundOptions: List<RefundOptionV10>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV10? = null
|
||||||
|
) {
|
||||||
|
var orderItem by mutableStateOf(initialOrderItem)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var refundOptions by mutableStateOf(initialRefundOptions)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var selectedRefundMethod by mutableStateOf(initialSelectedOption)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var submissionState by mutableStateOf<SubmissionStateV10>(Idle)
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun selectRefundMethod(type: RefundMethodTypeV10) {
|
||||||
|
selectedRefundMethod = type
|
||||||
|
refundOptions = refundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == type)
|
||||||
|
}
|
||||||
|
if (submissionState is Error) {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitRefund() {
|
||||||
|
if (selectedRefundMethod == null) {
|
||||||
|
submissionState = Error("Please select a refund method.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
submissionState = Loading
|
||||||
|
println("Submitting refund for method: $selectedRefundMethod")
|
||||||
|
submissionState = Success
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetSubmissionState() {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberReturnOrderStateV10(
|
||||||
|
initialOrderItem: OrderItemV10,
|
||||||
|
initialRefundOptions: List<RefundOptionV10>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV10? = MEESHO_APP
|
||||||
|
): ReturnOrderStateV10 {
|
||||||
|
return remember {
|
||||||
|
ReturnOrderStateV10(
|
||||||
|
initialOrderItem = initialOrderItem,
|
||||||
|
initialRefundOptions = initialRefundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == initialSelectedOption)
|
||||||
|
},
|
||||||
|
initialSelectedOption = initialSelectedOption
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class SubmissionStateV10 {
|
||||||
|
object Idle : SubmissionStateV10()
|
||||||
|
object Loading : SubmissionStateV10()
|
||||||
|
object Success : SubmissionStateV10()
|
||||||
|
data class Error(val message: String) : SubmissionStateV10()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReturnOrderScreenV10(
|
||||||
|
state: ReturnOrderStateV10,
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
) {
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
LaunchedEffect(state.submissionState) {
|
||||||
|
when (val submission = state.submissionState) {
|
||||||
|
is Error -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = submission.message,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = "Refund submitted successfully!",
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
|
bottomBar = {
|
||||||
|
SubmitButtonV10(
|
||||||
|
isLoading = state.submissionState == Loading,
|
||||||
|
isEnabled = state.selectedRefundMethod != null && state.submissionState != Loading,
|
||||||
|
onClick = { state.submitRefund() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(bottom = 80.dp)
|
||||||
|
) {
|
||||||
|
OrderItemSummaryV10(item = state.orderItem)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
RefundMethodSelectionV10(
|
||||||
|
options = state.refundOptions,
|
||||||
|
selectedMethod = state.selectedRefundMethod,
|
||||||
|
onOptionSelected = { state.selectRefundMethod(it) }
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
if (state.selectedRefundMethod == MEESHO_APP) {
|
||||||
|
MeeshoInfoBoxV10(onLearnMoreClick = { /* TODO */ })
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OrderItemSummaryV10(item: OrderItemV10) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = item.imageUrl),
|
||||||
|
contentDescription = item.name,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(60.dp)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.border(1.dp, Color.LightGray.copy(alpha = 0.5f), RoundedCornerShape(4.dp)),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
item.name,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
"₹${item.price}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Size: ${item.size}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Qty: ${item.quantity}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RefundMethodSelectionV10(
|
||||||
|
options: List<RefundOptionV10>,
|
||||||
|
selectedMethod: RefundMethodTypeV10?,
|
||||||
|
onOptionSelected: (RefundMethodTypeV10) -> Unit
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||||
|
Text(
|
||||||
|
"Choose your refund method",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
options.forEach { option ->
|
||||||
|
RefundOptionCardV10(
|
||||||
|
option = option.copy(isSelected = option.id == selectedMethod),
|
||||||
|
onClick = { onOptionSelected(option.id) },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun RefundOptionCardV10(
|
||||||
|
option: RefundOptionV10,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val borderColor = if (option.isSelected) Color(0xFF8A2BE2) else Color.LightGray
|
||||||
|
val borderWidth = if (option.isSelected) 1.5.dp else 1.dp
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = modifier
|
||||||
|
.border(borderWidth, borderColor, RoundedCornerShape(8.dp))
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Box {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.Start
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = option.icon),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(28.dp),
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(
|
||||||
|
option.title,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color.DarkGray
|
||||||
|
)
|
||||||
|
option.subtitle?.let {
|
||||||
|
Text(
|
||||||
|
it,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Divider(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
|
color = Color.LightGray.copy(alpha = 0.5f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Get ₹${option.getAmount}",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
option.extraAmount?.let { extra ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
.background(Color(0xFFE6F9E6), RoundedCornerShape(4.dp))
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Extra ₹$extra",
|
||||||
|
color = Color(0xFF006400),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (option.extraAmount == null) {
|
||||||
|
Spacer(modifier = Modifier.height(28.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.isSelected) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
|
contentDescription = "Selected",
|
||||||
|
tint = Color(0xFF8A2BE2),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
.padding(6.dp)
|
||||||
|
.size(20.dp)
|
||||||
|
.background(Color.White, CircleShape)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MeeshoInfoBoxV10(onLearnMoreClick: () -> Unit) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Use it on your next order",
|
||||||
|
color = Color.White,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
buildAnnotatedString {
|
||||||
|
withStyle(
|
||||||
|
style = SpanStyle(
|
||||||
|
color = Color.White,
|
||||||
|
textDecoration = TextDecoration.Underline
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
append("Learn more")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable(onClick = onLearnMoreClick),
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun SubmitButtonV10(
|
||||||
|
isLoading: Boolean,
|
||||||
|
isEnabled: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
.height(48.dp),
|
||||||
|
enabled = isEnabled && !isLoading,
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
color = Color.White,
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
"Submit",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,446 @@
|
|||||||
|
package com.google.samples.apps.nowinandroid
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.google.samples.apps.nowinandroid.RefundMethodTypeV11.MEESHO_APP
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV11.Error
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV11.Idle
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV11.Loading
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV11.Success
|
||||||
|
|
||||||
|
data class OrderItemV11(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val imageUrl: Int,
|
||||||
|
val price: Int,
|
||||||
|
val size: String,
|
||||||
|
val quantity: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class RefundMethodTypeV11 {
|
||||||
|
MEESHO_APP, BANK_UPI
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RefundOptionV11(
|
||||||
|
val id: RefundMethodTypeV11,
|
||||||
|
val title: String,
|
||||||
|
val subtitle: String? = null,
|
||||||
|
val icon: Int,
|
||||||
|
val getAmount: Int,
|
||||||
|
val extraAmount: Int? = null,
|
||||||
|
val isSelected: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
class ReturnOrderStateV11(
|
||||||
|
initialOrderItem: OrderItemV11,
|
||||||
|
initialRefundOptions: List<RefundOptionV11>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV11? = null
|
||||||
|
) {
|
||||||
|
var orderItem by mutableStateOf(initialOrderItem)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var refundOptions by mutableStateOf(initialRefundOptions)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var selectedRefundMethod by mutableStateOf(initialSelectedOption)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var submissionState by mutableStateOf<SubmissionStateV11>(Idle)
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun selectRefundMethod(type: RefundMethodTypeV11) {
|
||||||
|
selectedRefundMethod = type
|
||||||
|
refundOptions = refundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == type)
|
||||||
|
}
|
||||||
|
if (submissionState is Error) {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitRefund() {
|
||||||
|
if (selectedRefundMethod == null) {
|
||||||
|
submissionState = Error("Please select a refund method.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
submissionState = Loading
|
||||||
|
println("Submitting refund for method: $selectedRefundMethod")
|
||||||
|
submissionState = Success
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetSubmissionState() {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberReturnOrderStateV11(
|
||||||
|
initialOrderItem: OrderItemV11,
|
||||||
|
initialRefundOptions: List<RefundOptionV11>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV11? = MEESHO_APP
|
||||||
|
): ReturnOrderStateV11 {
|
||||||
|
return remember {
|
||||||
|
ReturnOrderStateV11(
|
||||||
|
initialOrderItem = initialOrderItem,
|
||||||
|
initialRefundOptions = initialRefundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == initialSelectedOption)
|
||||||
|
},
|
||||||
|
initialSelectedOption = initialSelectedOption
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class SubmissionStateV11 {
|
||||||
|
object Idle : SubmissionStateV11()
|
||||||
|
object Loading : SubmissionStateV11()
|
||||||
|
object Success : SubmissionStateV11()
|
||||||
|
data class Error(val message: String) : SubmissionStateV11()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReturnOrderScreenV11(
|
||||||
|
state: ReturnOrderStateV11,
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
) {
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
LaunchedEffect(state.submissionState) {
|
||||||
|
when (val submission = state.submissionState) {
|
||||||
|
is Error -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = submission.message,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = "Refund submitted successfully!",
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
|
bottomBar = {
|
||||||
|
SubmitButtonV11(
|
||||||
|
isLoading = state.submissionState == Loading,
|
||||||
|
isEnabled = state.selectedRefundMethod != null && state.submissionState != Loading,
|
||||||
|
onClick = { state.submitRefund() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(bottom = 80.dp)
|
||||||
|
) {
|
||||||
|
OrderItemSummaryV11(item = state.orderItem)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
RefundMethodSelectionV11(
|
||||||
|
options = state.refundOptions,
|
||||||
|
selectedMethod = state.selectedRefundMethod,
|
||||||
|
onOptionSelected = { state.selectRefundMethod(it) }
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
if (state.selectedRefundMethod == MEESHO_APP) {
|
||||||
|
MeeshoInfoBoxV11(onLearnMoreClick = { /* TODO */ })
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OrderItemSummaryV11(item: OrderItemV11) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = item.imageUrl),
|
||||||
|
contentDescription = item.name,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(60.dp)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.border(1.dp, Color.LightGray.copy(alpha = 0.5f), RoundedCornerShape(4.dp)),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
item.name,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
"₹${item.price}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Size: ${item.size}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Qty: ${item.quantity}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RefundMethodSelectionV11(
|
||||||
|
options: List<RefundOptionV11>,
|
||||||
|
selectedMethod: RefundMethodTypeV11?,
|
||||||
|
onOptionSelected: (RefundMethodTypeV11) -> Unit
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||||
|
Text(
|
||||||
|
"Choose your refund method",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
options.forEach { option ->
|
||||||
|
RefundOptionCardV11(
|
||||||
|
option = option.copy(isSelected = option.id == selectedMethod),
|
||||||
|
onClick = { onOptionSelected(option.id) },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun RefundOptionCardV11(
|
||||||
|
option: RefundOptionV11,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val borderColor = if (option.isSelected) Color(0xFF8A2BE2) else Color.LightGray
|
||||||
|
val borderWidth = if (option.isSelected) 1.5.dp else 1.dp
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = modifier
|
||||||
|
.border(borderWidth, borderColor, RoundedCornerShape(8.dp))
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Box {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.Start
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = option.icon),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(28.dp),
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(
|
||||||
|
option.title,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color.DarkGray
|
||||||
|
)
|
||||||
|
option.subtitle?.let {
|
||||||
|
Text(
|
||||||
|
it,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Divider(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
|
color = Color.LightGray.copy(alpha = 0.5f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Get ₹${option.getAmount}",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
option.extraAmount?.let { extra ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
.background(Color(0xFFE6F9E6), RoundedCornerShape(4.dp))
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Extra ₹$extra",
|
||||||
|
color = Color(0xFF006400),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (option.extraAmount == null) {
|
||||||
|
Spacer(modifier = Modifier.height(28.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.isSelected) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
|
contentDescription = "Selected",
|
||||||
|
tint = Color(0xFF8A2BE2),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
.padding(6.dp)
|
||||||
|
.size(20.dp)
|
||||||
|
.background(Color.White, CircleShape)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MeeshoInfoBoxV11(onLearnMoreClick: () -> Unit) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Use it on your next order",
|
||||||
|
color = Color.White,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
buildAnnotatedString {
|
||||||
|
withStyle(
|
||||||
|
style = SpanStyle(
|
||||||
|
color = Color.White,
|
||||||
|
textDecoration = TextDecoration.Underline
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
append("Learn more")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable(onClick = onLearnMoreClick),
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun SubmitButtonV11(
|
||||||
|
isLoading: Boolean,
|
||||||
|
isEnabled: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
.height(48.dp),
|
||||||
|
enabled = isEnabled && !isLoading,
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
color = Color.White,
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
"Submit",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,446 @@
|
|||||||
|
package com.google.samples.apps.nowinandroid
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.google.samples.apps.nowinandroid.RefundMethodTypeV12.MEESHO_APP
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV12.Error
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV12.Idle
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV12.Loading
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV12.Success
|
||||||
|
|
||||||
|
data class OrderItemV12(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val imageUrl: Int,
|
||||||
|
val price: Int,
|
||||||
|
val size: String,
|
||||||
|
val quantity: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class RefundMethodTypeV12 {
|
||||||
|
MEESHO_APP, BANK_UPI
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RefundOptionV12(
|
||||||
|
val id: RefundMethodTypeV12,
|
||||||
|
val title: String,
|
||||||
|
val subtitle: String? = null,
|
||||||
|
val icon: Int,
|
||||||
|
val getAmount: Int,
|
||||||
|
val extraAmount: Int? = null,
|
||||||
|
val isSelected: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
class ReturnOrderStateV12(
|
||||||
|
initialOrderItem: OrderItemV12,
|
||||||
|
initialRefundOptions: List<RefundOptionV12>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV12? = null
|
||||||
|
) {
|
||||||
|
var orderItem by mutableStateOf(initialOrderItem)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var refundOptions by mutableStateOf(initialRefundOptions)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var selectedRefundMethod by mutableStateOf(initialSelectedOption)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var submissionState by mutableStateOf<SubmissionStateV12>(Idle)
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun selectRefundMethod(type: RefundMethodTypeV12) {
|
||||||
|
selectedRefundMethod = type
|
||||||
|
refundOptions = refundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == type)
|
||||||
|
}
|
||||||
|
if (submissionState is Error) {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitRefund() {
|
||||||
|
if (selectedRefundMethod == null) {
|
||||||
|
submissionState = Error("Please select a refund method.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
submissionState = Loading
|
||||||
|
println("Submitting refund for method: $selectedRefundMethod")
|
||||||
|
submissionState = Success
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetSubmissionState() {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberReturnOrderStateV12(
|
||||||
|
initialOrderItem: OrderItemV12,
|
||||||
|
initialRefundOptions: List<RefundOptionV12>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV12? = MEESHO_APP
|
||||||
|
): ReturnOrderStateV12 {
|
||||||
|
return remember {
|
||||||
|
ReturnOrderStateV12(
|
||||||
|
initialOrderItem = initialOrderItem,
|
||||||
|
initialRefundOptions = initialRefundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == initialSelectedOption)
|
||||||
|
},
|
||||||
|
initialSelectedOption = initialSelectedOption
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class SubmissionStateV12 {
|
||||||
|
object Idle : SubmissionStateV12()
|
||||||
|
object Loading : SubmissionStateV12()
|
||||||
|
object Success : SubmissionStateV12()
|
||||||
|
data class Error(val message: String) : SubmissionStateV12()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReturnOrderScreenV12(
|
||||||
|
state: ReturnOrderStateV12,
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
) {
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
LaunchedEffect(state.submissionState) {
|
||||||
|
when (val submission = state.submissionState) {
|
||||||
|
is Error -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = submission.message,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = "Refund submitted successfully!",
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
|
bottomBar = {
|
||||||
|
SubmitButtonV12(
|
||||||
|
isLoading = state.submissionState == Loading,
|
||||||
|
isEnabled = state.selectedRefundMethod != null && state.submissionState != Loading,
|
||||||
|
onClick = { state.submitRefund() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(bottom = 80.dp)
|
||||||
|
) {
|
||||||
|
OrderItemSummaryV12(item = state.orderItem)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
RefundMethodSelectionV12(
|
||||||
|
options = state.refundOptions,
|
||||||
|
selectedMethod = state.selectedRefundMethod,
|
||||||
|
onOptionSelected = { state.selectRefundMethod(it) }
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
if (state.selectedRefundMethod == MEESHO_APP) {
|
||||||
|
MeeshoInfoBoxV12(onLearnMoreClick = { /* TODO */ })
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OrderItemSummaryV12(item: OrderItemV12) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = item.imageUrl),
|
||||||
|
contentDescription = item.name,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(60.dp)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.border(1.dp, Color.LightGray.copy(alpha = 0.5f), RoundedCornerShape(4.dp)),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
item.name,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
"₹${item.price}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Size: ${item.size}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Qty: ${item.quantity}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RefundMethodSelectionV12(
|
||||||
|
options: List<RefundOptionV12>,
|
||||||
|
selectedMethod: RefundMethodTypeV12?,
|
||||||
|
onOptionSelected: (RefundMethodTypeV12) -> Unit
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||||
|
Text(
|
||||||
|
"Choose your refund method",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
options.forEach { option ->
|
||||||
|
RefundOptionCardV12(
|
||||||
|
option = option.copy(isSelected = option.id == selectedMethod),
|
||||||
|
onClick = { onOptionSelected(option.id) },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun RefundOptionCardV12(
|
||||||
|
option: RefundOptionV12,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val borderColor = if (option.isSelected) Color(0xFF8A2BE2) else Color.LightGray
|
||||||
|
val borderWidth = if (option.isSelected) 1.5.dp else 1.dp
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = modifier
|
||||||
|
.border(borderWidth, borderColor, RoundedCornerShape(8.dp))
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Box {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.Start
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = option.icon),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(28.dp),
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(
|
||||||
|
option.title,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color.DarkGray
|
||||||
|
)
|
||||||
|
option.subtitle?.let {
|
||||||
|
Text(
|
||||||
|
it,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Divider(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
|
color = Color.LightGray.copy(alpha = 0.5f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Get ₹${option.getAmount}",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
option.extraAmount?.let { extra ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
.background(Color(0xFFE6F9E6), RoundedCornerShape(4.dp))
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Extra ₹$extra",
|
||||||
|
color = Color(0xFF006400),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (option.extraAmount == null) {
|
||||||
|
Spacer(modifier = Modifier.height(28.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.isSelected) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
|
contentDescription = "Selected",
|
||||||
|
tint = Color(0xFF8A2BE2),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
.padding(6.dp)
|
||||||
|
.size(20.dp)
|
||||||
|
.background(Color.White, CircleShape)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MeeshoInfoBoxV12(onLearnMoreClick: () -> Unit) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Use it on your next order",
|
||||||
|
color = Color.White,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
buildAnnotatedString {
|
||||||
|
withStyle(
|
||||||
|
style = SpanStyle(
|
||||||
|
color = Color.White,
|
||||||
|
textDecoration = TextDecoration.Underline
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
append("Learn more")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable(onClick = onLearnMoreClick),
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun SubmitButtonV12(
|
||||||
|
isLoading: Boolean,
|
||||||
|
isEnabled: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
.height(48.dp),
|
||||||
|
enabled = isEnabled && !isLoading,
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
color = Color.White,
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
"Submit",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,475 @@
|
|||||||
|
package com.google.samples.apps.nowinandroid
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.google.samples.apps.nowinandroid.RefundMethodTypeV2.MEESHO_APP
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV2.Error
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV2.Idle
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV2.Loading
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV2.Success
|
||||||
|
|
||||||
|
data class OrderItemV2(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val imageUrl: Int, // Using Drawable Res ID for simplicity
|
||||||
|
val price: Int,
|
||||||
|
val size: String,
|
||||||
|
val quantity: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
// Representing different refund methods
|
||||||
|
enum class RefundMethodTypeV2 {
|
||||||
|
MEESHO_APP, BANK_UPI
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RefundOptionV2(
|
||||||
|
val id: RefundMethodTypeV2,
|
||||||
|
val title: String,
|
||||||
|
val subtitle: String? = null,
|
||||||
|
val icon: Int, // Drawable Res ID
|
||||||
|
val getAmount: Int,
|
||||||
|
val extraAmount: Int? = null,
|
||||||
|
val isSelected: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- State Holder (Can be hoisted to a ViewModel in a real app) ---
|
||||||
|
// This manages the state of the screen
|
||||||
|
class ReturnOrderStateV2(
|
||||||
|
initialOrderItem: OrderItemV2,
|
||||||
|
initialRefundOptions: List<RefundOptionV2>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV2? = null // Start with no selection or pre-select
|
||||||
|
) {
|
||||||
|
var orderItem by mutableStateOf(initialOrderItem)
|
||||||
|
private set // Can only be modified internally or via specific functions
|
||||||
|
|
||||||
|
var refundOptions by mutableStateOf(initialRefundOptions)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var selectedRefundMethod by mutableStateOf(initialSelectedOption)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var submissionState by mutableStateOf<SubmissionStateV2>(Idle)
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun selectRefundMethod(type: RefundMethodTypeV2) {
|
||||||
|
selectedRefundMethod = type
|
||||||
|
refundOptions = refundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == type)
|
||||||
|
}
|
||||||
|
// Reset submission state if user changes selection after an error
|
||||||
|
if (submissionState is Error) {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitRefund() {
|
||||||
|
if (selectedRefundMethod == null) {
|
||||||
|
submissionState = Error("Please select a refund method.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
submissionState = Loading
|
||||||
|
// --- Simulate Network Call ---
|
||||||
|
// In a real app, launch a coroutine and call repository/usecase
|
||||||
|
// viewModelScope.launch { ... }
|
||||||
|
// For now, just simulate success/failure after a delay
|
||||||
|
// Handler(Looper.getMainLooper()).postDelayed({
|
||||||
|
// submissionState = if (Random.nextBoolean()) SubmissionStateV2.Success
|
||||||
|
// else SubmissionStateV2.Error("Refund submission failed. Please try again.")
|
||||||
|
// }, 2000)
|
||||||
|
println("Submitting refund for method: $selectedRefundMethod")
|
||||||
|
// For preview, let's just set to Success immediately for simplicity
|
||||||
|
submissionState = Success
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetSubmissionState() {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberReturnOrderStateV2(
|
||||||
|
initialOrderItem: OrderItemV2,
|
||||||
|
initialRefundOptions: List<RefundOptionV2>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV2? = MEESHO_APP // Pre-select Meesho
|
||||||
|
): ReturnOrderStateV2 {
|
||||||
|
return remember {
|
||||||
|
ReturnOrderStateV2(
|
||||||
|
initialOrderItem = initialOrderItem,
|
||||||
|
initialRefundOptions = initialRefundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == initialSelectedOption) // Set initial selection state
|
||||||
|
},
|
||||||
|
initialSelectedOption = initialSelectedOption
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Sealed class for Submission State (more robust error/loading handling) ---
|
||||||
|
sealed class SubmissionStateV2 {
|
||||||
|
object Idle : SubmissionStateV2()
|
||||||
|
object Loading : SubmissionStateV2()
|
||||||
|
object Success : SubmissionStateV2()
|
||||||
|
data class Error(val message: String) : SubmissionStateV2()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Main Screen Composable ---
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReturnOrderScreenV2(
|
||||||
|
state: ReturnOrderStateV2,
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
// In a real app, submission would likely trigger a ViewModel function
|
||||||
|
// onSubmit: (RefundMethodTypeV2) -> Unit
|
||||||
|
) {
|
||||||
|
val snackbarHostState = SnackbarHostState()
|
||||||
|
|
||||||
|
// Handle submission state changes (e.g., show Snackbar for errors)
|
||||||
|
LaunchedEffect(state.submissionState) {
|
||||||
|
when (val submission = state.submissionState) {
|
||||||
|
is Error -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = submission.message,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
// Optionally reset state after showing error
|
||||||
|
// state.resetSubmissionState()
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = "Refund submitted successfully!",
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
// Potentially navigate away or show success screen
|
||||||
|
// onNavigateBack() // Example: Navigate back on success
|
||||||
|
}
|
||||||
|
else -> {} // Idle or Loading
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
|
bottomBar = {
|
||||||
|
SubmitButtonV2(
|
||||||
|
isLoading = state.submissionState == Loading,
|
||||||
|
isEnabled = state.selectedRefundMethod != null && state.submissionState != Loading,
|
||||||
|
onClick = { state.submitRefund() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues) // Apply scaffold padding
|
||||||
|
.verticalScroll(rememberScrollState()) // Make content scrollable
|
||||||
|
.padding(bottom = 80.dp) // Add padding to avoid overlap with bottom bar
|
||||||
|
) {
|
||||||
|
OrderItemSummaryV2(item = state.orderItem)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
RefundMethodSelectionV2(
|
||||||
|
options = state.refundOptions,
|
||||||
|
selectedMethod = state.selectedRefundMethod,
|
||||||
|
onOptionSelected = { state.selectRefundMethod(it) }
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// Show info box only when Meesho App refund is selected
|
||||||
|
if (state.selectedRefundMethod == MEESHO_APP) {
|
||||||
|
MeeshoInfoBoxV2(onLearnMoreClick = { /* TODO: Handle Learn More click */ })
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OrderItemSummaryV2(item: OrderItemV2) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = item.imageUrl), // Replace with actual image loading
|
||||||
|
contentDescription = item.name,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(60.dp)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.border(1.dp, Color.LightGray.copy(alpha = 0.5f), RoundedCornerShape(4.dp)),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
item.name,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
"₹${item.price}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Size: ${item.size}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Qty: ${item.quantity}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RefundMethodSelectionV2(
|
||||||
|
options: List<RefundOptionV2>,
|
||||||
|
selectedMethod: RefundMethodTypeV2?,
|
||||||
|
onOptionSelected: (RefundMethodTypeV2) -> Unit
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||||
|
Text(
|
||||||
|
"Choose your refund method",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp) // Spacing between cards
|
||||||
|
) {
|
||||||
|
options.forEach { option ->
|
||||||
|
RefundOptionCardV2(
|
||||||
|
option = option.copy(isSelected = option.id == selectedMethod), // Ensure isSelected reflects state
|
||||||
|
onClick = { onOptionSelected(option.id) },
|
||||||
|
modifier = Modifier.weight(1f) // Make cards share width equally
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun RefundOptionCardV2(
|
||||||
|
option: RefundOptionV2,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val borderColor = if (option.isSelected) Color(0xFF8A2BE2) else Color.LightGray // Purple when selected
|
||||||
|
val borderWidth = if (option.isSelected) 1.5.dp else 1.dp
|
||||||
|
val backgroundColor = Color.White // Or MaterialTheme.colors.surface
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = modifier
|
||||||
|
.border(borderWidth, borderColor, RoundedCornerShape(8.dp))
|
||||||
|
.clip(RoundedCornerShape(8.dp)) // Clip needed for click effect ripple
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Box { // Use Box to overlay the checkmark
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(), // Ensure column takes width for alignment
|
||||||
|
horizontalAlignment = Alignment.Start // Align content to the start
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = option.icon),
|
||||||
|
contentDescription = null, // Decorative icon
|
||||||
|
modifier = Modifier.size(28.dp), // Adjust size as needed
|
||||||
|
tint = Color.Unspecified // Use original icon colors if applicable
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(
|
||||||
|
option.title,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color.DarkGray
|
||||||
|
)
|
||||||
|
option.subtitle?.let {
|
||||||
|
Text(
|
||||||
|
it,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Divider(modifier = Modifier.padding(vertical = 8.dp), color = Color.LightGray.copy(alpha = 0.5f)) // Subtle divider
|
||||||
|
Text(
|
||||||
|
"Get ₹${option.getAmount}",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
option.extraAmount?.let { extra ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
.background(
|
||||||
|
Color(0xFFE6F9E6),
|
||||||
|
RoundedCornerShape(4.dp)
|
||||||
|
) // Light green background
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Extra ₹$extra",
|
||||||
|
color = Color(0xFF006400), // Dark green text
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add Spacer at the bottom if extraAmount is null to maintain some height consistency
|
||||||
|
if (option.extraAmount == null) {
|
||||||
|
Spacer(modifier = Modifier.height(28.dp)) // Adjust height to match the 'Extra' box approx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selection Indicator (Checkmark)
|
||||||
|
if (option.isSelected) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
|
contentDescription = "Selected",
|
||||||
|
tint = Color(0xFF8A2BE2), // Purple checkmark
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
.padding(6.dp)
|
||||||
|
.size(20.dp)
|
||||||
|
.background(Color.White, CircleShape) // White background behind check
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MeeshoInfoBoxV2(onLearnMoreClick: () -> Unit) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Use it on your next order",
|
||||||
|
color = Color.White,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
buildAnnotatedString {
|
||||||
|
withStyle(style = SpanStyle(
|
||||||
|
color = Color.White,
|
||||||
|
textDecoration = TextDecoration.Underline)
|
||||||
|
) {
|
||||||
|
append("Learn more")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable(onClick = onLearnMoreClick),
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun SubmitButtonV2(
|
||||||
|
isLoading: Boolean,
|
||||||
|
isEnabled: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp) // Padding around the button
|
||||||
|
.height(48.dp), // Standard button height
|
||||||
|
enabled = isEnabled && !isLoading, // Disable if not selected OR loading
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
color = Color.White,
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
"Submit",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,446 @@
|
|||||||
|
package com.google.samples.apps.nowinandroid
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.google.samples.apps.nowinandroid.RefundMethodTypeV3.MEESHO_APP
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV3.Error
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV3.Idle
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV3.Loading
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV3.Success
|
||||||
|
|
||||||
|
data class OrderItemV3(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val imageUrl: Int,
|
||||||
|
val price: Int,
|
||||||
|
val size: String,
|
||||||
|
val quantity: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class RefundMethodTypeV3 {
|
||||||
|
MEESHO_APP, BANK_UPI
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RefundOptionV3(
|
||||||
|
val id: RefundMethodTypeV3,
|
||||||
|
val title: String,
|
||||||
|
val subtitle: String? = null,
|
||||||
|
val icon: Int,
|
||||||
|
val getAmount: Int,
|
||||||
|
val extraAmount: Int? = null,
|
||||||
|
val isSelected: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
class ReturnOrderStateV3(
|
||||||
|
initialOrderItem: OrderItemV3,
|
||||||
|
initialRefundOptions: List<RefundOptionV3>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV3? = null
|
||||||
|
) {
|
||||||
|
var orderItem by mutableStateOf(initialOrderItem)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var refundOptions by mutableStateOf(initialRefundOptions)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var selectedRefundMethod by mutableStateOf(initialSelectedOption)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var submissionState by mutableStateOf<SubmissionStateV3>(Idle)
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun selectRefundMethod(type: RefundMethodTypeV3) {
|
||||||
|
selectedRefundMethod = type
|
||||||
|
refundOptions = refundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == type)
|
||||||
|
}
|
||||||
|
if (submissionState is Error) {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitRefund() {
|
||||||
|
if (selectedRefundMethod == null) {
|
||||||
|
submissionState = Error("Please select a refund method.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
submissionState = Loading
|
||||||
|
println("Submitting refund for method: $selectedRefundMethod")
|
||||||
|
submissionState = Success
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetSubmissionState() {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberReturnOrderStateV3(
|
||||||
|
initialOrderItem: OrderItemV3,
|
||||||
|
initialRefundOptions: List<RefundOptionV3>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV3? = MEESHO_APP
|
||||||
|
): ReturnOrderStateV3 {
|
||||||
|
return remember {
|
||||||
|
ReturnOrderStateV3(
|
||||||
|
initialOrderItem = initialOrderItem,
|
||||||
|
initialRefundOptions = initialRefundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == initialSelectedOption)
|
||||||
|
},
|
||||||
|
initialSelectedOption = initialSelectedOption
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class SubmissionStateV3 {
|
||||||
|
object Idle : SubmissionStateV3()
|
||||||
|
object Loading : SubmissionStateV3()
|
||||||
|
object Success : SubmissionStateV3()
|
||||||
|
data class Error(val message: String) : SubmissionStateV3()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReturnOrderScreenV3(
|
||||||
|
state: ReturnOrderStateV3,
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
) {
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
LaunchedEffect(state.submissionState) {
|
||||||
|
when (val submission = state.submissionState) {
|
||||||
|
is Error -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = submission.message,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = "Refund submitted successfully!",
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
|
bottomBar = {
|
||||||
|
SubmitButtonV3(
|
||||||
|
isLoading = state.submissionState == Loading,
|
||||||
|
isEnabled = state.selectedRefundMethod != null && state.submissionState != Loading,
|
||||||
|
onClick = { state.submitRefund() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(bottom = 80.dp)
|
||||||
|
) {
|
||||||
|
OrderItemSummaryV3(item = state.orderItem)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
RefundMethodSelectionV3(
|
||||||
|
options = state.refundOptions,
|
||||||
|
selectedMethod = state.selectedRefundMethod,
|
||||||
|
onOptionSelected = { state.selectRefundMethod(it) }
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
if (state.selectedRefundMethod == MEESHO_APP) {
|
||||||
|
MeeshoInfoBoxV3(onLearnMoreClick = { /* TODO */ })
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OrderItemSummaryV3(item: OrderItemV3) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = item.imageUrl),
|
||||||
|
contentDescription = item.name,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(60.dp)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.border(1.dp, Color.LightGray.copy(alpha = 0.5f), RoundedCornerShape(4.dp)),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
item.name,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
"₹${item.price}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Size: ${item.size}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Qty: ${item.quantity}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RefundMethodSelectionV3(
|
||||||
|
options: List<RefundOptionV3>,
|
||||||
|
selectedMethod: RefundMethodTypeV3?,
|
||||||
|
onOptionSelected: (RefundMethodTypeV3) -> Unit
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||||
|
Text(
|
||||||
|
"Choose your refund method",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
options.forEach { option ->
|
||||||
|
RefundOptionCardV3(
|
||||||
|
option = option.copy(isSelected = option.id == selectedMethod),
|
||||||
|
onClick = { onOptionSelected(option.id) },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun RefundOptionCardV3(
|
||||||
|
option: RefundOptionV3,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val borderColor = if (option.isSelected) Color(0xFF8A2BE2) else Color.LightGray
|
||||||
|
val borderWidth = if (option.isSelected) 1.5.dp else 1.dp
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = modifier
|
||||||
|
.border(borderWidth, borderColor, RoundedCornerShape(8.dp))
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Box {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.Start
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = option.icon),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(28.dp),
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(
|
||||||
|
option.title,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color.DarkGray
|
||||||
|
)
|
||||||
|
option.subtitle?.let {
|
||||||
|
Text(
|
||||||
|
it,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Divider(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
|
color = Color.LightGray.copy(alpha = 0.5f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Get ₹${option.getAmount}",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
option.extraAmount?.let { extra ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
.background(Color(0xFFE6F9E6), RoundedCornerShape(4.dp))
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Extra ₹$extra",
|
||||||
|
color = Color(0xFF006400),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (option.extraAmount == null) {
|
||||||
|
Spacer(modifier = Modifier.height(28.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.isSelected) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
|
contentDescription = "Selected",
|
||||||
|
tint = Color(0xFF8A2BE2),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
.padding(6.dp)
|
||||||
|
.size(20.dp)
|
||||||
|
.background(Color.White, CircleShape)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MeeshoInfoBoxV3(onLearnMoreClick: () -> Unit) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Use it on your next order",
|
||||||
|
color = Color.White,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
buildAnnotatedString {
|
||||||
|
withStyle(
|
||||||
|
style = SpanStyle(
|
||||||
|
color = Color.White,
|
||||||
|
textDecoration = TextDecoration.Underline
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
append("Learn more")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable(onClick = onLearnMoreClick),
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun SubmitButtonV3(
|
||||||
|
isLoading: Boolean,
|
||||||
|
isEnabled: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
.height(48.dp),
|
||||||
|
enabled = isEnabled && !isLoading,
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
color = Color.White,
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
"Submit",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,446 @@
|
|||||||
|
package com.google.samples.apps.nowinandroid
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.google.samples.apps.nowinandroid.RefundMethodTypeV4.MEESHO_APP
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV4.Error
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV4.Idle
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV4.Loading
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV4.Success
|
||||||
|
|
||||||
|
data class OrderItemV4(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val imageUrl: Int,
|
||||||
|
val price: Int,
|
||||||
|
val size: String,
|
||||||
|
val quantity: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class RefundMethodTypeV4 {
|
||||||
|
MEESHO_APP, BANK_UPI
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RefundOptionV4(
|
||||||
|
val id: RefundMethodTypeV4,
|
||||||
|
val title: String,
|
||||||
|
val subtitle: String? = null,
|
||||||
|
val icon: Int,
|
||||||
|
val getAmount: Int,
|
||||||
|
val extraAmount: Int? = null,
|
||||||
|
val isSelected: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
class ReturnOrderStateV4(
|
||||||
|
initialOrderItem: OrderItemV4,
|
||||||
|
initialRefundOptions: List<RefundOptionV4>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV4? = null
|
||||||
|
) {
|
||||||
|
var orderItem by mutableStateOf(initialOrderItem)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var refundOptions by mutableStateOf(initialRefundOptions)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var selectedRefundMethod by mutableStateOf(initialSelectedOption)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var submissionState by mutableStateOf<SubmissionStateV4>(Idle)
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun selectRefundMethod(type: RefundMethodTypeV4) {
|
||||||
|
selectedRefundMethod = type
|
||||||
|
refundOptions = refundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == type)
|
||||||
|
}
|
||||||
|
if (submissionState is Error) {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitRefund() {
|
||||||
|
if (selectedRefundMethod == null) {
|
||||||
|
submissionState = Error("Please select a refund method.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
submissionState = Loading
|
||||||
|
println("Submitting refund for method: $selectedRefundMethod")
|
||||||
|
submissionState = Success
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetSubmissionState() {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberReturnOrderStateV4(
|
||||||
|
initialOrderItem: OrderItemV4,
|
||||||
|
initialRefundOptions: List<RefundOptionV4>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV4? = MEESHO_APP
|
||||||
|
): ReturnOrderStateV4 {
|
||||||
|
return remember {
|
||||||
|
ReturnOrderStateV4(
|
||||||
|
initialOrderItem = initialOrderItem,
|
||||||
|
initialRefundOptions = initialRefundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == initialSelectedOption)
|
||||||
|
},
|
||||||
|
initialSelectedOption = initialSelectedOption
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class SubmissionStateV4 {
|
||||||
|
object Idle : SubmissionStateV4()
|
||||||
|
object Loading : SubmissionStateV4()
|
||||||
|
object Success : SubmissionStateV4()
|
||||||
|
data class Error(val message: String) : SubmissionStateV4()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReturnOrderScreenV4(
|
||||||
|
state: ReturnOrderStateV4,
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
) {
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
LaunchedEffect(state.submissionState) {
|
||||||
|
when (val submission = state.submissionState) {
|
||||||
|
is Error -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = submission.message,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = "Refund submitted successfully!",
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
|
bottomBar = {
|
||||||
|
SubmitButtonV4(
|
||||||
|
isLoading = state.submissionState == Loading,
|
||||||
|
isEnabled = state.selectedRefundMethod != null && state.submissionState != Loading,
|
||||||
|
onClick = { state.submitRefund() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(bottom = 80.dp)
|
||||||
|
) {
|
||||||
|
OrderItemSummaryV4(item = state.orderItem)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
RefundMethodSelectionV4(
|
||||||
|
options = state.refundOptions,
|
||||||
|
selectedMethod = state.selectedRefundMethod,
|
||||||
|
onOptionSelected = { state.selectRefundMethod(it) }
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
if (state.selectedRefundMethod == MEESHO_APP) {
|
||||||
|
MeeshoInfoBoxV4(onLearnMoreClick = { /* TODO */ })
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OrderItemSummaryV4(item: OrderItemV4) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = item.imageUrl),
|
||||||
|
contentDescription = item.name,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(60.dp)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.border(1.dp, Color.LightGray.copy(alpha = 0.5f), RoundedCornerShape(4.dp)),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
item.name,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
"₹${item.price}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Size: ${item.size}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Qty: ${item.quantity}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RefundMethodSelectionV4(
|
||||||
|
options: List<RefundOptionV4>,
|
||||||
|
selectedMethod: RefundMethodTypeV4?,
|
||||||
|
onOptionSelected: (RefundMethodTypeV4) -> Unit
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||||
|
Text(
|
||||||
|
"Choose your refund method",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
options.forEach { option ->
|
||||||
|
RefundOptionCardV4(
|
||||||
|
option = option.copy(isSelected = option.id == selectedMethod),
|
||||||
|
onClick = { onOptionSelected(option.id) },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun RefundOptionCardV4(
|
||||||
|
option: RefundOptionV4,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val borderColor = if (option.isSelected) Color(0xFF8A2BE2) else Color.LightGray
|
||||||
|
val borderWidth = if (option.isSelected) 1.5.dp else 1.dp
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = modifier
|
||||||
|
.border(borderWidth, borderColor, RoundedCornerShape(8.dp))
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Box {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.Start
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = option.icon),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(28.dp),
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(
|
||||||
|
option.title,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color.DarkGray
|
||||||
|
)
|
||||||
|
option.subtitle?.let {
|
||||||
|
Text(
|
||||||
|
it,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Divider(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
|
color = Color.LightGray.copy(alpha = 0.5f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Get ₹${option.getAmount}",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
option.extraAmount?.let { extra ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
.background(Color(0xFFE6F9E6), RoundedCornerShape(4.dp))
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Extra ₹$extra",
|
||||||
|
color = Color(0xFF006400),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (option.extraAmount == null) {
|
||||||
|
Spacer(modifier = Modifier.height(28.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.isSelected) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
|
contentDescription = "Selected",
|
||||||
|
tint = Color(0xFF8A2BE2),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
.padding(6.dp)
|
||||||
|
.size(20.dp)
|
||||||
|
.background(Color.White, CircleShape)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MeeshoInfoBoxV4(onLearnMoreClick: () -> Unit) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Use it on your next order",
|
||||||
|
color = Color.White,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
buildAnnotatedString {
|
||||||
|
withStyle(
|
||||||
|
style = SpanStyle(
|
||||||
|
color = Color.White,
|
||||||
|
textDecoration = TextDecoration.Underline
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
append("Learn more")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable(onClick = onLearnMoreClick),
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun SubmitButtonV4(
|
||||||
|
isLoading: Boolean,
|
||||||
|
isEnabled: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
.height(48.dp),
|
||||||
|
enabled = isEnabled && !isLoading,
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
color = Color.White,
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
"Submit",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,447 @@
|
|||||||
|
package com.google.samples.apps.nowinandroid
|
||||||
|
|
||||||
|
// Same imports as V4...
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.google.samples.apps.nowinandroid.RefundMethodTypeV5.MEESHO_APP
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV5.Error
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV5.Idle
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV5.Loading
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV5.Success
|
||||||
|
|
||||||
|
data class OrderItemV5(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val imageUrl: Int,
|
||||||
|
val price: Int,
|
||||||
|
val size: String,
|
||||||
|
val quantity: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class RefundMethodTypeV5 {
|
||||||
|
MEESHO_APP, BANK_UPI
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RefundOptionV5(
|
||||||
|
val id: RefundMethodTypeV5,
|
||||||
|
val title: String,
|
||||||
|
val subtitle: String? = null,
|
||||||
|
val icon: Int,
|
||||||
|
val getAmount: Int,
|
||||||
|
val extraAmount: Int? = null,
|
||||||
|
val isSelected: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
class ReturnOrderStateV5(
|
||||||
|
initialOrderItem: OrderItemV5,
|
||||||
|
initialRefundOptions: List<RefundOptionV5>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV5? = null
|
||||||
|
) {
|
||||||
|
var orderItem by mutableStateOf(initialOrderItem)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var refundOptions by mutableStateOf(initialRefundOptions)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var selectedRefundMethod by mutableStateOf(initialSelectedOption)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var submissionState by mutableStateOf<SubmissionStateV5>(Idle)
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun selectRefundMethod(type: RefundMethodTypeV5) {
|
||||||
|
selectedRefundMethod = type
|
||||||
|
refundOptions = refundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == type)
|
||||||
|
}
|
||||||
|
if (submissionState is Error) {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitRefund() {
|
||||||
|
if (selectedRefundMethod == null) {
|
||||||
|
submissionState = Error("Please select a refund method.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
submissionState = Loading
|
||||||
|
println("Submitting refund for method: $selectedRefundMethod")
|
||||||
|
submissionState = Success
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetSubmissionState() {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberReturnOrderStateV5(
|
||||||
|
initialOrderItem: OrderItemV5,
|
||||||
|
initialRefundOptions: List<RefundOptionV5>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV5? = MEESHO_APP
|
||||||
|
): ReturnOrderStateV5 {
|
||||||
|
return remember {
|
||||||
|
ReturnOrderStateV5(
|
||||||
|
initialOrderItem = initialOrderItem,
|
||||||
|
initialRefundOptions = initialRefundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == initialSelectedOption)
|
||||||
|
},
|
||||||
|
initialSelectedOption = initialSelectedOption
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class SubmissionStateV5 {
|
||||||
|
object Idle : SubmissionStateV5()
|
||||||
|
object Loading : SubmissionStateV5()
|
||||||
|
object Success : SubmissionStateV5()
|
||||||
|
data class Error(val message: String) : SubmissionStateV5()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReturnOrderScreenV5(
|
||||||
|
state: ReturnOrderStateV5,
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
) {
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
LaunchedEffect(state.submissionState) {
|
||||||
|
when (val submission = state.submissionState) {
|
||||||
|
is Error -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = submission.message,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = "Refund submitted successfully!",
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
|
bottomBar = {
|
||||||
|
SubmitButtonV5(
|
||||||
|
isLoading = state.submissionState == Loading,
|
||||||
|
isEnabled = state.selectedRefundMethod != null && state.submissionState != Loading,
|
||||||
|
onClick = { state.submitRefund() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(bottom = 80.dp)
|
||||||
|
) {
|
||||||
|
OrderItemSummaryV5(item = state.orderItem)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
RefundMethodSelectionV5(
|
||||||
|
options = state.refundOptions,
|
||||||
|
selectedMethod = state.selectedRefundMethod,
|
||||||
|
onOptionSelected = { state.selectRefundMethod(it) }
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
if (state.selectedRefundMethod == MEESHO_APP) {
|
||||||
|
MeeshoInfoBoxV5(onLearnMoreClick = { /* TODO */ })
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OrderItemSummaryV5(item: OrderItemV5) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = item.imageUrl),
|
||||||
|
contentDescription = item.name,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(60.dp)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.border(1.dp, Color.LightGray.copy(alpha = 0.5f), RoundedCornerShape(4.dp)),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
item.name,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
"₹${item.price}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Size: ${item.size}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Qty: ${item.quantity}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RefundMethodSelectionV5(
|
||||||
|
options: List<RefundOptionV5>,
|
||||||
|
selectedMethod: RefundMethodTypeV5?,
|
||||||
|
onOptionSelected: (RefundMethodTypeV5) -> Unit
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||||
|
Text(
|
||||||
|
"Choose your refund method",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
options.forEach { option ->
|
||||||
|
RefundOptionCardV5(
|
||||||
|
option = option.copy(isSelected = option.id == selectedMethod),
|
||||||
|
onClick = { onOptionSelected(option.id) },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun RefundOptionCardV5(
|
||||||
|
option: RefundOptionV5,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val borderColor = if (option.isSelected) Color(0xFF8A2BE2) else Color.LightGray
|
||||||
|
val borderWidth = if (option.isSelected) 1.5.dp else 1.dp
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = modifier
|
||||||
|
.border(borderWidth, borderColor, RoundedCornerShape(8.dp))
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Box {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.Start
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = option.icon),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(28.dp),
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(
|
||||||
|
option.title,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color.DarkGray
|
||||||
|
)
|
||||||
|
option.subtitle?.let {
|
||||||
|
Text(
|
||||||
|
it,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Divider(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
|
color = Color.LightGray.copy(alpha = 0.5f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Get ₹${option.getAmount}",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
option.extraAmount?.let { extra ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
.background(Color(0xFFE6F9E6), RoundedCornerShape(4.dp))
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Extra ₹$extra",
|
||||||
|
color = Color(0xFF006400),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (option.extraAmount == null) {
|
||||||
|
Spacer(modifier = Modifier.height(28.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.isSelected) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
|
contentDescription = "Selected",
|
||||||
|
tint = Color(0xFF8A2BE2),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
.padding(6.dp)
|
||||||
|
.size(20.dp)
|
||||||
|
.background(Color.White, CircleShape)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MeeshoInfoBoxV5(onLearnMoreClick: () -> Unit) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Use it on your next order",
|
||||||
|
color = Color.White,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
buildAnnotatedString {
|
||||||
|
withStyle(
|
||||||
|
style = SpanStyle(
|
||||||
|
color = Color.White,
|
||||||
|
textDecoration = TextDecoration.Underline
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
append("Learn more")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable(onClick = onLearnMoreClick),
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun SubmitButtonV5(
|
||||||
|
isLoading: Boolean,
|
||||||
|
isEnabled: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
.height(48.dp),
|
||||||
|
enabled = isEnabled && !isLoading,
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
color = Color.White,
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
"Submit",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,446 @@
|
|||||||
|
package com.google.samples.apps.nowinandroid
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.google.samples.apps.nowinandroid.RefundMethodTypeV6.MEESHO_APP
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV6.Error
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV6.Idle
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV6.Loading
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV6.Success
|
||||||
|
|
||||||
|
data class OrderItemV6(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val imageUrl: Int,
|
||||||
|
val price: Int,
|
||||||
|
val size: String,
|
||||||
|
val quantity: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class RefundMethodTypeV6 {
|
||||||
|
MEESHO_APP, BANK_UPI
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RefundOptionV6(
|
||||||
|
val id: RefundMethodTypeV6,
|
||||||
|
val title: String,
|
||||||
|
val subtitle: String? = null,
|
||||||
|
val icon: Int,
|
||||||
|
val getAmount: Int,
|
||||||
|
val extraAmount: Int? = null,
|
||||||
|
val isSelected: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
class ReturnOrderStateV6(
|
||||||
|
initialOrderItem: OrderItemV6,
|
||||||
|
initialRefundOptions: List<RefundOptionV6>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV6? = null
|
||||||
|
) {
|
||||||
|
var orderItem by mutableStateOf(initialOrderItem)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var refundOptions by mutableStateOf(initialRefundOptions)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var selectedRefundMethod by mutableStateOf(initialSelectedOption)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var submissionState by mutableStateOf<SubmissionStateV6>(Idle)
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun selectRefundMethod(type: RefundMethodTypeV6) {
|
||||||
|
selectedRefundMethod = type
|
||||||
|
refundOptions = refundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == type)
|
||||||
|
}
|
||||||
|
if (submissionState is Error) {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitRefund() {
|
||||||
|
if (selectedRefundMethod == null) {
|
||||||
|
submissionState = Error("Please select a refund method.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
submissionState = Loading
|
||||||
|
println("Submitting refund for method: $selectedRefundMethod")
|
||||||
|
submissionState = Success
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetSubmissionState() {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberReturnOrderStateV6(
|
||||||
|
initialOrderItem: OrderItemV6,
|
||||||
|
initialRefundOptions: List<RefundOptionV6>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV6? = MEESHO_APP
|
||||||
|
): ReturnOrderStateV6 {
|
||||||
|
return remember {
|
||||||
|
ReturnOrderStateV6(
|
||||||
|
initialOrderItem = initialOrderItem,
|
||||||
|
initialRefundOptions = initialRefundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == initialSelectedOption)
|
||||||
|
},
|
||||||
|
initialSelectedOption = initialSelectedOption
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class SubmissionStateV6 {
|
||||||
|
object Idle : SubmissionStateV6()
|
||||||
|
object Loading : SubmissionStateV6()
|
||||||
|
object Success : SubmissionStateV6()
|
||||||
|
data class Error(val message: String) : SubmissionStateV6()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReturnOrderScreenV6(
|
||||||
|
state: ReturnOrderStateV6,
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
) {
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
LaunchedEffect(state.submissionState) {
|
||||||
|
when (val submission = state.submissionState) {
|
||||||
|
is Error -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = submission.message,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = "Refund submitted successfully!",
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
|
bottomBar = {
|
||||||
|
SubmitButtonV6(
|
||||||
|
isLoading = state.submissionState == Loading,
|
||||||
|
isEnabled = state.selectedRefundMethod != null && state.submissionState != Loading,
|
||||||
|
onClick = { state.submitRefund() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(bottom = 80.dp)
|
||||||
|
) {
|
||||||
|
OrderItemSummaryV6(item = state.orderItem)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
RefundMethodSelectionV6(
|
||||||
|
options = state.refundOptions,
|
||||||
|
selectedMethod = state.selectedRefundMethod,
|
||||||
|
onOptionSelected = { state.selectRefundMethod(it) }
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
if (state.selectedRefundMethod == MEESHO_APP) {
|
||||||
|
MeeshoInfoBoxV6(onLearnMoreClick = { /* TODO */ })
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OrderItemSummaryV6(item: OrderItemV6) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = item.imageUrl),
|
||||||
|
contentDescription = item.name,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(60.dp)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.border(1.dp, Color.LightGray.copy(alpha = 0.5f), RoundedCornerShape(4.dp)),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
item.name,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
"₹${item.price}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Size: ${item.size}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Qty: ${item.quantity}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RefundMethodSelectionV6(
|
||||||
|
options: List<RefundOptionV6>,
|
||||||
|
selectedMethod: RefundMethodTypeV6?,
|
||||||
|
onOptionSelected: (RefundMethodTypeV6) -> Unit
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||||
|
Text(
|
||||||
|
"Choose your refund method",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
options.forEach { option ->
|
||||||
|
RefundOptionCardV6(
|
||||||
|
option = option.copy(isSelected = option.id == selectedMethod),
|
||||||
|
onClick = { onOptionSelected(option.id) },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun RefundOptionCardV6(
|
||||||
|
option: RefundOptionV6,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val borderColor = if (option.isSelected) Color(0xFF8A2BE2) else Color.LightGray
|
||||||
|
val borderWidth = if (option.isSelected) 1.5.dp else 1.dp
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = modifier
|
||||||
|
.border(borderWidth, borderColor, RoundedCornerShape(8.dp))
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Box {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.Start
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = option.icon),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(28.dp),
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(
|
||||||
|
option.title,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color.DarkGray
|
||||||
|
)
|
||||||
|
option.subtitle?.let {
|
||||||
|
Text(
|
||||||
|
it,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Divider(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
|
color = Color.LightGray.copy(alpha = 0.5f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Get ₹${option.getAmount}",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
option.extraAmount?.let { extra ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
.background(Color(0xFFE6F9E6), RoundedCornerShape(4.dp))
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Extra ₹$extra",
|
||||||
|
color = Color(0xFF006400),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (option.extraAmount == null) {
|
||||||
|
Spacer(modifier = Modifier.height(28.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.isSelected) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
|
contentDescription = "Selected",
|
||||||
|
tint = Color(0xFF8A2BE2),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
.padding(6.dp)
|
||||||
|
.size(20.dp)
|
||||||
|
.background(Color.White, CircleShape)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MeeshoInfoBoxV6(onLearnMoreClick: () -> Unit) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Use it on your next order",
|
||||||
|
color = Color.White,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
buildAnnotatedString {
|
||||||
|
withStyle(
|
||||||
|
style = SpanStyle(
|
||||||
|
color = Color.White,
|
||||||
|
textDecoration = TextDecoration.Underline
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
append("Learn more")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable(onClick = onLearnMoreClick),
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun SubmitButtonV6(
|
||||||
|
isLoading: Boolean,
|
||||||
|
isEnabled: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
.height(48.dp),
|
||||||
|
enabled = isEnabled && !isLoading,
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
color = Color.White,
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
"Submit",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,446 @@
|
|||||||
|
package com.google.samples.apps.nowinandroid
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.google.samples.apps.nowinandroid.RefundMethodTypeV7.MEESHO_APP
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV7.Error
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV7.Idle
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV7.Loading
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV7.Success
|
||||||
|
|
||||||
|
data class OrderItemV7(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val imageUrl: Int,
|
||||||
|
val price: Int,
|
||||||
|
val size: String,
|
||||||
|
val quantity: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class RefundMethodTypeV7 {
|
||||||
|
MEESHO_APP, BANK_UPI
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RefundOptionV7(
|
||||||
|
val id: RefundMethodTypeV7,
|
||||||
|
val title: String,
|
||||||
|
val subtitle: String? = null,
|
||||||
|
val icon: Int,
|
||||||
|
val getAmount: Int,
|
||||||
|
val extraAmount: Int? = null,
|
||||||
|
val isSelected: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
class ReturnOrderStateV7(
|
||||||
|
initialOrderItem: OrderItemV7,
|
||||||
|
initialRefundOptions: List<RefundOptionV7>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV7? = null
|
||||||
|
) {
|
||||||
|
var orderItem by mutableStateOf(initialOrderItem)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var refundOptions by mutableStateOf(initialRefundOptions)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var selectedRefundMethod by mutableStateOf(initialSelectedOption)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var submissionState by mutableStateOf<SubmissionStateV7>(Idle)
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun selectRefundMethod(type: RefundMethodTypeV7) {
|
||||||
|
selectedRefundMethod = type
|
||||||
|
refundOptions = refundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == type)
|
||||||
|
}
|
||||||
|
if (submissionState is Error) {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitRefund() {
|
||||||
|
if (selectedRefundMethod == null) {
|
||||||
|
submissionState = Error("Please select a refund method.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
submissionState = Loading
|
||||||
|
println("Submitting refund for method: $selectedRefundMethod")
|
||||||
|
submissionState = Success
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetSubmissionState() {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberReturnOrderStateV7(
|
||||||
|
initialOrderItem: OrderItemV7,
|
||||||
|
initialRefundOptions: List<RefundOptionV7>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV7? = MEESHO_APP
|
||||||
|
): ReturnOrderStateV7 {
|
||||||
|
return remember {
|
||||||
|
ReturnOrderStateV7(
|
||||||
|
initialOrderItem = initialOrderItem,
|
||||||
|
initialRefundOptions = initialRefundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == initialSelectedOption)
|
||||||
|
},
|
||||||
|
initialSelectedOption = initialSelectedOption
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class SubmissionStateV7 {
|
||||||
|
object Idle : SubmissionStateV7()
|
||||||
|
object Loading : SubmissionStateV7()
|
||||||
|
object Success : SubmissionStateV7()
|
||||||
|
data class Error(val message: String) : SubmissionStateV7()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReturnOrderScreenV7(
|
||||||
|
state: ReturnOrderStateV7,
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
) {
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
LaunchedEffect(state.submissionState) {
|
||||||
|
when (val submission = state.submissionState) {
|
||||||
|
is Error -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = submission.message,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = "Refund submitted successfully!",
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
|
bottomBar = {
|
||||||
|
SubmitButtonV7(
|
||||||
|
isLoading = state.submissionState == Loading,
|
||||||
|
isEnabled = state.selectedRefundMethod != null && state.submissionState != Loading,
|
||||||
|
onClick = { state.submitRefund() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(bottom = 80.dp)
|
||||||
|
) {
|
||||||
|
OrderItemSummaryV7(item = state.orderItem)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
RefundMethodSelectionV7(
|
||||||
|
options = state.refundOptions,
|
||||||
|
selectedMethod = state.selectedRefundMethod,
|
||||||
|
onOptionSelected = { state.selectRefundMethod(it) }
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
if (state.selectedRefundMethod == MEESHO_APP) {
|
||||||
|
MeeshoInfoBoxV7(onLearnMoreClick = { /* TODO */ })
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OrderItemSummaryV7(item: OrderItemV7) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = item.imageUrl),
|
||||||
|
contentDescription = item.name,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(60.dp)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.border(1.dp, Color.LightGray.copy(alpha = 0.5f), RoundedCornerShape(4.dp)),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
item.name,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
"₹${item.price}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Size: ${item.size}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Qty: ${item.quantity}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RefundMethodSelectionV7(
|
||||||
|
options: List<RefundOptionV7>,
|
||||||
|
selectedMethod: RefundMethodTypeV7?,
|
||||||
|
onOptionSelected: (RefundMethodTypeV7) -> Unit
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||||
|
Text(
|
||||||
|
"Choose your refund method",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
options.forEach { option ->
|
||||||
|
RefundOptionCardV7(
|
||||||
|
option = option.copy(isSelected = option.id == selectedMethod),
|
||||||
|
onClick = { onOptionSelected(option.id) },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun RefundOptionCardV7(
|
||||||
|
option: RefundOptionV7,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val borderColor = if (option.isSelected) Color(0xFF8A2BE2) else Color.LightGray
|
||||||
|
val borderWidth = if (option.isSelected) 1.5.dp else 1.dp
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = modifier
|
||||||
|
.border(borderWidth, borderColor, RoundedCornerShape(8.dp))
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Box {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.Start
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = option.icon),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(28.dp),
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(
|
||||||
|
option.title,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color.DarkGray
|
||||||
|
)
|
||||||
|
option.subtitle?.let {
|
||||||
|
Text(
|
||||||
|
it,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Divider(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
|
color = Color.LightGray.copy(alpha = 0.5f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Get ₹${option.getAmount}",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
option.extraAmount?.let { extra ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
.background(Color(0xFFE6F9E6), RoundedCornerShape(4.dp))
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Extra ₹$extra",
|
||||||
|
color = Color(0xFF006400),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (option.extraAmount == null) {
|
||||||
|
Spacer(modifier = Modifier.height(28.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.isSelected) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
|
contentDescription = "Selected",
|
||||||
|
tint = Color(0xFF8A2BE2),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
.padding(6.dp)
|
||||||
|
.size(20.dp)
|
||||||
|
.background(Color.White, CircleShape)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MeeshoInfoBoxV7(onLearnMoreClick: () -> Unit) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Use it on your next order",
|
||||||
|
color = Color.White,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
buildAnnotatedString {
|
||||||
|
withStyle(
|
||||||
|
style = SpanStyle(
|
||||||
|
color = Color.White,
|
||||||
|
textDecoration = TextDecoration.Underline
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
append("Learn more")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable(onClick = onLearnMoreClick),
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun SubmitButtonV7(
|
||||||
|
isLoading: Boolean,
|
||||||
|
isEnabled: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
.height(48.dp),
|
||||||
|
enabled = isEnabled && !isLoading,
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
color = Color.White,
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
"Submit",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,446 @@
|
|||||||
|
package com.google.samples.apps.nowinandroid
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.google.samples.apps.nowinandroid.RefundMethodTypeV8.MEESHO_APP
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV8.Error
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV8.Idle
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV8.Loading
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV8.Success
|
||||||
|
|
||||||
|
data class OrderItemV8(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val imageUrl: Int,
|
||||||
|
val price: Int,
|
||||||
|
val size: String,
|
||||||
|
val quantity: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class RefundMethodTypeV8 {
|
||||||
|
MEESHO_APP, BANK_UPI
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RefundOptionV8(
|
||||||
|
val id: RefundMethodTypeV8,
|
||||||
|
val title: String,
|
||||||
|
val subtitle: String? = null,
|
||||||
|
val icon: Int,
|
||||||
|
val getAmount: Int,
|
||||||
|
val extraAmount: Int? = null,
|
||||||
|
val isSelected: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
class ReturnOrderStateV8(
|
||||||
|
initialOrderItem: OrderItemV8,
|
||||||
|
initialRefundOptions: List<RefundOptionV8>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV8? = null
|
||||||
|
) {
|
||||||
|
var orderItem by mutableStateOf(initialOrderItem)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var refundOptions by mutableStateOf(initialRefundOptions)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var selectedRefundMethod by mutableStateOf(initialSelectedOption)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var submissionState by mutableStateOf<SubmissionStateV8>(Idle)
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun selectRefundMethod(type: RefundMethodTypeV8) {
|
||||||
|
selectedRefundMethod = type
|
||||||
|
refundOptions = refundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == type)
|
||||||
|
}
|
||||||
|
if (submissionState is Error) {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitRefund() {
|
||||||
|
if (selectedRefundMethod == null) {
|
||||||
|
submissionState = Error("Please select a refund method.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
submissionState = Loading
|
||||||
|
println("Submitting refund for method: $selectedRefundMethod")
|
||||||
|
submissionState = Success
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetSubmissionState() {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberReturnOrderStateV8(
|
||||||
|
initialOrderItem: OrderItemV8,
|
||||||
|
initialRefundOptions: List<RefundOptionV8>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV8? = MEESHO_APP
|
||||||
|
): ReturnOrderStateV8 {
|
||||||
|
return remember {
|
||||||
|
ReturnOrderStateV8(
|
||||||
|
initialOrderItem = initialOrderItem,
|
||||||
|
initialRefundOptions = initialRefundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == initialSelectedOption)
|
||||||
|
},
|
||||||
|
initialSelectedOption = initialSelectedOption
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class SubmissionStateV8 {
|
||||||
|
object Idle : SubmissionStateV8()
|
||||||
|
object Loading : SubmissionStateV8()
|
||||||
|
object Success : SubmissionStateV8()
|
||||||
|
data class Error(val message: String) : SubmissionStateV8()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReturnOrderScreenV8(
|
||||||
|
state: ReturnOrderStateV8,
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
) {
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
LaunchedEffect(state.submissionState) {
|
||||||
|
when (val submission = state.submissionState) {
|
||||||
|
is Error -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = submission.message,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = "Refund submitted successfully!",
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
|
bottomBar = {
|
||||||
|
SubmitButtonV8(
|
||||||
|
isLoading = state.submissionState == Loading,
|
||||||
|
isEnabled = state.selectedRefundMethod != null && state.submissionState != Loading,
|
||||||
|
onClick = { state.submitRefund() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(bottom = 80.dp)
|
||||||
|
) {
|
||||||
|
OrderItemSummaryV8(item = state.orderItem)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
RefundMethodSelectionV8(
|
||||||
|
options = state.refundOptions,
|
||||||
|
selectedMethod = state.selectedRefundMethod,
|
||||||
|
onOptionSelected = { state.selectRefundMethod(it) }
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
if (state.selectedRefundMethod == MEESHO_APP) {
|
||||||
|
MeeshoInfoBoxV8(onLearnMoreClick = { /* TODO */ })
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OrderItemSummaryV8(item: OrderItemV8) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = item.imageUrl),
|
||||||
|
contentDescription = item.name,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(60.dp)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.border(1.dp, Color.LightGray.copy(alpha = 0.5f), RoundedCornerShape(4.dp)),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
item.name,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
"₹${item.price}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Size: ${item.size}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Qty: ${item.quantity}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RefundMethodSelectionV8(
|
||||||
|
options: List<RefundOptionV8>,
|
||||||
|
selectedMethod: RefundMethodTypeV8?,
|
||||||
|
onOptionSelected: (RefundMethodTypeV8) -> Unit
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||||
|
Text(
|
||||||
|
"Choose your refund method",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
options.forEach { option ->
|
||||||
|
RefundOptionCardV8(
|
||||||
|
option = option.copy(isSelected = option.id == selectedMethod),
|
||||||
|
onClick = { onOptionSelected(option.id) },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun RefundOptionCardV8(
|
||||||
|
option: RefundOptionV8,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val borderColor = if (option.isSelected) Color(0xFF8A2BE2) else Color.LightGray
|
||||||
|
val borderWidth = if (option.isSelected) 1.5.dp else 1.dp
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = modifier
|
||||||
|
.border(borderWidth, borderColor, RoundedCornerShape(8.dp))
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Box {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.Start
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = option.icon),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(28.dp),
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(
|
||||||
|
option.title,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color.DarkGray
|
||||||
|
)
|
||||||
|
option.subtitle?.let {
|
||||||
|
Text(
|
||||||
|
it,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Divider(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
|
color = Color.LightGray.copy(alpha = 0.5f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Get ₹${option.getAmount}",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
option.extraAmount?.let { extra ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
.background(Color(0xFFE6F9E6), RoundedCornerShape(4.dp))
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Extra ₹$extra",
|
||||||
|
color = Color(0xFF006400),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (option.extraAmount == null) {
|
||||||
|
Spacer(modifier = Modifier.height(28.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.isSelected) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
|
contentDescription = "Selected",
|
||||||
|
tint = Color(0xFF8A2BE2),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
.padding(6.dp)
|
||||||
|
.size(20.dp)
|
||||||
|
.background(Color.White, CircleShape)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MeeshoInfoBoxV8(onLearnMoreClick: () -> Unit) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Use it on your next order",
|
||||||
|
color = Color.White,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
buildAnnotatedString {
|
||||||
|
withStyle(
|
||||||
|
style = SpanStyle(
|
||||||
|
color = Color.White,
|
||||||
|
textDecoration = TextDecoration.Underline
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
append("Learn more")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable(onClick = onLearnMoreClick),
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun SubmitButtonV8(
|
||||||
|
isLoading: Boolean,
|
||||||
|
isEnabled: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
.height(48.dp),
|
||||||
|
enabled = isEnabled && !isLoading,
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
color = Color.White,
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
"Submit",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,446 @@
|
|||||||
|
package com.google.samples.apps.nowinandroid
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.google.samples.apps.nowinandroid.RefundMethodTypeV9.MEESHO_APP
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV9.Error
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV9.Idle
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV9.Loading
|
||||||
|
import com.google.samples.apps.nowinandroid.SubmissionStateV9.Success
|
||||||
|
|
||||||
|
data class OrderItemV9(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val imageUrl: Int,
|
||||||
|
val price: Int,
|
||||||
|
val size: String,
|
||||||
|
val quantity: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class RefundMethodTypeV9 {
|
||||||
|
MEESHO_APP, BANK_UPI
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RefundOptionV9(
|
||||||
|
val id: RefundMethodTypeV9,
|
||||||
|
val title: String,
|
||||||
|
val subtitle: String? = null,
|
||||||
|
val icon: Int,
|
||||||
|
val getAmount: Int,
|
||||||
|
val extraAmount: Int? = null,
|
||||||
|
val isSelected: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
class ReturnOrderStateV9(
|
||||||
|
initialOrderItem: OrderItemV9,
|
||||||
|
initialRefundOptions: List<RefundOptionV9>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV9? = null
|
||||||
|
) {
|
||||||
|
var orderItem by mutableStateOf(initialOrderItem)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var refundOptions by mutableStateOf(initialRefundOptions)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var selectedRefundMethod by mutableStateOf(initialSelectedOption)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var submissionState by mutableStateOf<SubmissionStateV9>(Idle)
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun selectRefundMethod(type: RefundMethodTypeV9) {
|
||||||
|
selectedRefundMethod = type
|
||||||
|
refundOptions = refundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == type)
|
||||||
|
}
|
||||||
|
if (submissionState is Error) {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitRefund() {
|
||||||
|
if (selectedRefundMethod == null) {
|
||||||
|
submissionState = Error("Please select a refund method.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
submissionState = Loading
|
||||||
|
println("Submitting refund for method: $selectedRefundMethod")
|
||||||
|
submissionState = Success
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetSubmissionState() {
|
||||||
|
submissionState = Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberReturnOrderStateV9(
|
||||||
|
initialOrderItem: OrderItemV9,
|
||||||
|
initialRefundOptions: List<RefundOptionV9>,
|
||||||
|
initialSelectedOption: RefundMethodTypeV9? = MEESHO_APP
|
||||||
|
): ReturnOrderStateV9 {
|
||||||
|
return remember {
|
||||||
|
ReturnOrderStateV9(
|
||||||
|
initialOrderItem = initialOrderItem,
|
||||||
|
initialRefundOptions = initialRefundOptions.map {
|
||||||
|
it.copy(isSelected = it.id == initialSelectedOption)
|
||||||
|
},
|
||||||
|
initialSelectedOption = initialSelectedOption
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class SubmissionStateV9 {
|
||||||
|
object Idle : SubmissionStateV9()
|
||||||
|
object Loading : SubmissionStateV9()
|
||||||
|
object Success : SubmissionStateV9()
|
||||||
|
data class Error(val message: String) : SubmissionStateV9()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReturnOrderScreenV9(
|
||||||
|
state: ReturnOrderStateV9,
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
) {
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
LaunchedEffect(state.submissionState) {
|
||||||
|
when (val submission = state.submissionState) {
|
||||||
|
is Error -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = submission.message,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = "Refund submitted successfully!",
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
|
bottomBar = {
|
||||||
|
SubmitButtonV9(
|
||||||
|
isLoading = state.submissionState == Loading,
|
||||||
|
isEnabled = state.selectedRefundMethod != null && state.submissionState != Loading,
|
||||||
|
onClick = { state.submitRefund() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(bottom = 80.dp)
|
||||||
|
) {
|
||||||
|
OrderItemSummaryV9(item = state.orderItem)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
RefundMethodSelectionV9(
|
||||||
|
options = state.refundOptions,
|
||||||
|
selectedMethod = state.selectedRefundMethod,
|
||||||
|
onOptionSelected = { state.selectRefundMethod(it) }
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
if (state.selectedRefundMethod == MEESHO_APP) {
|
||||||
|
MeeshoInfoBoxV9(onLearnMoreClick = { /* TODO */ })
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OrderItemSummaryV9(item: OrderItemV9) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = item.imageUrl),
|
||||||
|
contentDescription = item.name,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(60.dp)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.border(1.dp, Color.LightGray.copy(alpha = 0.5f), RoundedCornerShape(4.dp)),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
item.name,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
"₹${item.price}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Size: ${item.size}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("•", style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
"Qty: ${item.quantity}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RefundMethodSelectionV9(
|
||||||
|
options: List<RefundOptionV9>,
|
||||||
|
selectedMethod: RefundMethodTypeV9?,
|
||||||
|
onOptionSelected: (RefundMethodTypeV9) -> Unit
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||||
|
Text(
|
||||||
|
"Choose your refund method",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
options.forEach { option ->
|
||||||
|
RefundOptionCardV9(
|
||||||
|
option = option.copy(isSelected = option.id == selectedMethod),
|
||||||
|
onClick = { onOptionSelected(option.id) },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun RefundOptionCardV9(
|
||||||
|
option: RefundOptionV9,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val borderColor = if (option.isSelected) Color(0xFF8A2BE2) else Color.LightGray
|
||||||
|
val borderWidth = if (option.isSelected) 1.5.dp else 1.dp
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = modifier
|
||||||
|
.border(borderWidth, borderColor, RoundedCornerShape(8.dp))
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Box {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.Start
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = option.icon),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(28.dp),
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(
|
||||||
|
option.title,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color.DarkGray
|
||||||
|
)
|
||||||
|
option.subtitle?.let {
|
||||||
|
Text(
|
||||||
|
it,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Divider(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
|
color = Color.LightGray.copy(alpha = 0.5f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Get ₹${option.getAmount}",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
option.extraAmount?.let { extra ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
.background(Color(0xFFE6F9E6), RoundedCornerShape(4.dp))
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Extra ₹$extra",
|
||||||
|
color = Color(0xFF006400),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (option.extraAmount == null) {
|
||||||
|
Spacer(modifier = Modifier.height(28.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.isSelected) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
|
contentDescription = "Selected",
|
||||||
|
tint = Color(0xFF8A2BE2),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
.padding(6.dp)
|
||||||
|
.size(20.dp)
|
||||||
|
.background(Color.White, CircleShape)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MeeshoInfoBoxV9(onLearnMoreClick: () -> Unit) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Use it on your next order",
|
||||||
|
color = Color.White,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
buildAnnotatedString {
|
||||||
|
withStyle(
|
||||||
|
style = SpanStyle(
|
||||||
|
color = Color.White,
|
||||||
|
textDecoration = TextDecoration.Underline
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
append("Learn more")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable(onClick = onLearnMoreClick),
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DesignSystem")
|
||||||
|
@Composable
|
||||||
|
fun SubmitButtonV9(
|
||||||
|
isLoading: Boolean,
|
||||||
|
isEnabled: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
.height(48.dp),
|
||||||
|
enabled = isEnabled && !isLoading,
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
color = Color.White,
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
"Submit",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="16dp" android:height="12dp" android:viewportWidth="16" android:viewportHeight="12">
|
||||||
|
<path android:pathData="M14.222 1.8H1.778c-0.246 0-0.445 0.209-0.445 0.467V3.6h13.334V2.267c0-0.258-0.199-0.467-0.445-0.467zM1.333 9.733V6h13.334v3.733c0 0.258-0.199 0.467-0.445 0.467H1.778c-0.246 0-0.445-0.209-0.445-0.467zM1.778 0.4C0.796 0.4 0 1.236 0 2.267v7.466C0 10.764 0.796 11.6 1.778 11.6h12.444C15.204 11.6 16 10.764 16 9.733V2.267C16 1.236 15.204 0.4 14.222 0.4H1.778zM3.8 7.6c-0.331 0-0.6 0.269-0.6 0.6 0 0.331 0.269 0.6 0.6 0.6h2.4c0.331 0 0.6-0.269 0.6-0.6 0-0.331-0.269-0.6-0.6-0.6H3.8z" android:fillColor="#616173" android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
Loading…
Reference in new issue