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