Test Commit

pull/1875/head
moheeeetgupta 4 months ago
parent 689ef92e41
commit 6169aaa7eb

@ -16,16 +16,49 @@
package com.google.samples.apps.nowinandroid
import android.annotation.SuppressLint
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.paddingFromBaseline
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Spa
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@ -151,6 +184,7 @@ class MainActivity : ComponentActivity() {
disableDynamicTheming = themeSettings.disableDynamicTheming,
) {
NiaApp(appState)
MySootheAppPortrait()
}
}
}
@ -188,3 +222,674 @@ data class ThemeSettings(
val androidTheme: Boolean,
val disableDynamicTheming: Boolean,
)
@Composable
fun HomeScreen(modifier: Modifier = Modifier) {
Column(
modifier
.verticalScroll(rememberScrollState())
) {
Spacer(Modifier.height(16.dp))
SearchBar(Modifier.padding(horizontal = 16.dp))
HomeSection(title ="Align Your Body") {
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
// This is a simplified helper. For a production app,
// you'd use a robust number-to-words utility or a pre-generated list.
fun numberToWords(n: Int): String {
val units = listOf("", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten",
"Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen")
val tens = listOf("", "", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety")
return when {
n < 0 -> "Minus " + numberToWords(-n)
n == 0 -> "Zero" // Though the loop is 1..100
n < 20 -> units[n]
n < 100 -> {
val tenPart = tens[n / 10]
val unitPart = units[n % 10]
if (unitPart.isNotEmpty()) "$tenPart $unitPart" else tenPart
}
n == 100 -> "One Hundred"
else -> n.toString() // Fallback for numbers > 100, though not needed for this loop
}
}
for (i in 1..100) {
Text(
text = numberToWords(i),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(vertical = 4.dp)
)
}
}
}
HomeSection(title = "Favorite Collections") {
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
// This is a simplified helper. For a production app,
// you'd use a robust number-to-words utility or a pre-generated list.
fun numberToWords(n: Int): String {
val units = listOf("", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten",
"Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen")
val tens = listOf("", "", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety")
return when {
n < 0 -> "Minus " + numberToWords(-n)
n == 0 -> "Zero" // Though the loop is 1..100
n < 20 -> units[n]
n < 100 -> {
val tenPart = tens[n / 10]
val unitPart = units[n % 10]
if (unitPart.isNotEmpty()) "$tenPart $unitPart" else tenPart
}
n == 100 -> "One Hundred"
else -> n.toString() // Fallback for numbers > 100, though not needed for this loop
}
}
for (i in 1..100) {
Text(
text = numberToWords(i),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(vertical = 4.dp)
)
}
}
}
Spacer(Modifier.height(16.dp))
}
}
@Composable
fun HomeSection(
title: String,
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Column(modifier) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier
.padding(horizontal = 16.dp)
.paddingFromBaseline(top = 40.dp, bottom = 16.dp)
)
content()
}
}
@SuppressLint("DesignSystem")
@Composable
fun SearchBar(
modifier: Modifier = Modifier
) {
TextField(
value = "",
onValueChange = {},
leadingIcon = {
Icon(
imageVector = Icons.Default.Search,
contentDescription = null
)
},
colors = TextFieldDefaults.colors(
unfocusedContainerColor = MaterialTheme.colorScheme.surface,
focusedContainerColor = MaterialTheme.colorScheme.surface
),
placeholder = {
Text("Search")
},
modifier = modifier
.fillMaxWidth()
.heightIn(min = 56.dp)
)
}
@SuppressLint("DesignSystem")
@Composable
private fun SootheBottomNavigation(modifier: Modifier = Modifier) {
NavigationBar(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
modifier = modifier
) {
NavigationBarItem(
icon = {
Icon(
imageVector = Icons.Default.Spa,
contentDescription = null
)
},
label = {
Text("Home")
},
selected = true,
onClick = {}
)
NavigationBarItem(
icon = {
Icon(
imageVector = Icons.Default.AccountCircle,
contentDescription = null
)
},
label = {
Text("Profile")
},
selected = false,
onClick = {}
)
}
}
@SuppressLint("DesignSystem")
@Composable
fun MySootheAppPortrait() {
NiaTheme {
var tabIndex by remember { mutableStateOf(0) }
val tabs = listOf(
"HOME", "RETURN ORDER", "RETURN ORDER V2",
"RETURN ORDER V3", "RETURN ORDER V4", "RETURN ORDER V5",
"RETURN ORDER V6", "RETURN ORDER V7", "RETURN ORDER V8",
"RETURN ORDER V9", "RETURN ORDER V10", "RETURN ORDER V11",
"RETURN ORDER V12"
)
Scaffold(
bottomBar = { SootheBottomNavigation() }
) { padding ->
Column(
modifier = Modifier.padding(padding)
) {
TabRow(selectedTabIndex = tabIndex) {
tabs.forEachIndexed { index, title ->
Tab(
text = { Text(title) },
selected = tabIndex == index,
onClick = { tabIndex = index }
)
}
}
when (tabIndex) {
0 -> HomeScreen()
1 -> {
val orderItem = OrderItem(
id = "123",
name = "Floral Print Dress",
imageUrl = R.drawable.ic_card_icon,
price = 599,
size = "M",
quantity = 1
)
val refundOptions = listOf(
RefundOption(
id = RefundMethodType.MEESHO_APP,
title = "Meesho App",
subtitle = "Instant refund",
icon = R.drawable.ic_card_icon,
getAmount = 599,
extraAmount = 50,
isSelected = true
),
RefundOption(
id = RefundMethodType.BANK_UPI,
title = "Bank/UPI",
subtitle = "3-5 business days",
icon = R.drawable.ic_card_icon,
getAmount = 599,
isSelected = false
)
)
val state = rememberReturnOrderState(
initialOrderItem = orderItem,
initialRefundOptions = refundOptions
)
ReturnOrderScreen(
state = state,
onNavigateBack = {}
)
}
2 -> {
val orderItemV2 = OrderItemV2(
id = "123",
name = "Floral Print Dress",
imageUrl = R.drawable.ic_card_icon,
price = 599,
size = "M",
quantity = 1
)
val refundOptionsV2 = listOf(
RefundOptionV2(
id = RefundMethodTypeV2.MEESHO_APP,
title = "Meesho App",
subtitle = "Instant refund",
icon = R.drawable.ic_card_icon,
getAmount = 599,
extraAmount = 50,
isSelected = true
),
RefundOptionV2(
id = RefundMethodTypeV2.BANK_UPI,
title = "Bank/UPI",
subtitle = "3-5 business days",
icon = R.drawable.ic_card_icon,
getAmount = 599,
isSelected = false
)
)
val stateV2 = rememberReturnOrderStateV2(
initialOrderItem = orderItemV2,
initialRefundOptions = refundOptionsV2
)
ReturnOrderScreenV2(
state = stateV2,
onNavigateBack = {}
)
}
3 -> {
val orderItemV3 = OrderItemV3(
id = "123",
name = "Floral Print Dress",
imageUrl = R.drawable.ic_card_icon,
price = 599,
size = "M",
quantity = 1
)
val refundOptionsV3 = listOf(
RefundOptionV3(
id = RefundMethodTypeV3.MEESHO_APP,
title = "Meesho App",
subtitle = "Instant refund",
icon = R.drawable.ic_card_icon,
getAmount = 599,
extraAmount = 50,
isSelected = true
),
RefundOptionV3(
id = RefundMethodTypeV3.BANK_UPI,
title = "Bank/UPI",
subtitle = "3-5 business days",
icon = R.drawable.ic_card_icon,
getAmount = 599,
isSelected = false
)
)
val stateV3 = rememberReturnOrderStateV3(
initialOrderItem = orderItemV3,
initialRefundOptions = refundOptionsV3
)
ReturnOrderScreenV3(
state = stateV3,
onNavigateBack = {}
)
}
4 -> {
val orderItemV4 = OrderItemV4(
id = "123",
name = "Floral Print Dress",
imageUrl = R.drawable.ic_card_icon,
price = 599,
size = "M",
quantity = 1
)
val refundOptionsV4 = listOf(
RefundOptionV4(
id = RefundMethodTypeV4.MEESHO_APP,
title = "Meesho App",
subtitle = "Instant refund",
icon = R.drawable.ic_card_icon,
getAmount = 599,
extraAmount = 50,
isSelected = true
),
RefundOptionV4(
id = RefundMethodTypeV4.BANK_UPI,
title = "Bank/UPI",
subtitle = "3-5 business days",
icon = R.drawable.ic_card_icon,
getAmount = 599,
isSelected = false
)
)
val stateV4 = rememberReturnOrderStateV4(
initialOrderItem = orderItemV4,
initialRefundOptions = refundOptionsV4
)
ReturnOrderScreenV4(
state = stateV4,
onNavigateBack = {}
)
}
5 -> {
val orderItemV5 = OrderItemV5(
id = "123",
name = "Floral Print Dress",
imageUrl = R.drawable.ic_card_icon,
price = 599,
size = "M",
quantity = 1
)
val refundOptionsV5 = listOf(
RefundOptionV5(
id = RefundMethodTypeV5.MEESHO_APP,
title = "Meesho App",
subtitle = "Instant refund",
icon = R.drawable.ic_card_icon,
getAmount = 599,
extraAmount = 50,
isSelected = true
),
RefundOptionV5(
id = RefundMethodTypeV5.BANK_UPI,
title = "Bank/UPI",
subtitle = "3-5 business days",
icon = R.drawable.ic_card_icon,
getAmount = 599,
isSelected = false
)
)
val stateV5 = rememberReturnOrderStateV5(
initialOrderItem = orderItemV5,
initialRefundOptions = refundOptionsV5
)
ReturnOrderScreenV5(
state = stateV5,
onNavigateBack = {}
)
}
6 -> {
val orderItemV6 = OrderItemV6(
id = "123",
name = "Floral Print Dress",
imageUrl = R.drawable.ic_card_icon,
price = 599,
size = "M",
quantity = 1
)
val refundOptionsV6 = listOf(
RefundOptionV6(
id = RefundMethodTypeV6.MEESHO_APP,
title = "Meesho App",
subtitle = "Instant refund",
icon = R.drawable.ic_card_icon,
getAmount = 599,
extraAmount = 50,
isSelected = true
),
RefundOptionV6(
id = RefundMethodTypeV6.BANK_UPI,
title = "Bank/UPI",
subtitle = "3-5 business days",
icon = R.drawable.ic_card_icon,
getAmount = 599,
isSelected = false
)
)
val stateV6 = rememberReturnOrderStateV6(
initialOrderItem = orderItemV6,
initialRefundOptions = refundOptionsV6
)
ReturnOrderScreenV6(
state = stateV6,
onNavigateBack = {}
)
}
7 -> {
val orderItemV7 = OrderItemV7(
id = "123",
name = "Floral Print Dress",
imageUrl = R.drawable.ic_card_icon,
price = 599,
size = "M",
quantity = 1
)
val refundOptionsV7 = listOf(
RefundOptionV7(
id = RefundMethodTypeV7.MEESHO_APP,
title = "Meesho App",
subtitle = "Instant refund",
icon = R.drawable.ic_card_icon,
getAmount = 599,
extraAmount = 50,
isSelected = true
),
RefundOptionV7(
id = RefundMethodTypeV7.BANK_UPI,
title = "Bank/UPI",
subtitle = "3-5 business days",
icon = R.drawable.ic_card_icon,
getAmount = 599,
isSelected = false
)
)
val stateV7 = rememberReturnOrderStateV7(
initialOrderItem = orderItemV7,
initialRefundOptions = refundOptionsV7
)
ReturnOrderScreenV7(
state = stateV7,
onNavigateBack = {}
)
}
8 -> {
val orderItemV8 = OrderItemV8(
id = "123",
name = "Floral Print Dress",
imageUrl = R.drawable.ic_card_icon,
price = 599,
size = "M",
quantity = 1
)
val refundOptionsV8 = listOf(
RefundOptionV8(
id = RefundMethodTypeV8.MEESHO_APP,
title = "Meesho App",
subtitle = "Instant refund",
icon = R.drawable.ic_card_icon,
getAmount = 599,
extraAmount = 50,
isSelected = true
),
RefundOptionV8(
id = RefundMethodTypeV8.BANK_UPI,
title = "Bank/UPI",
subtitle = "3-5 business days",
icon = R.drawable.ic_card_icon,
getAmount = 599,
isSelected = false
)
)
val stateV8 = rememberReturnOrderStateV8(
initialOrderItem = orderItemV8,
initialRefundOptions = refundOptionsV8
)
ReturnOrderScreenV8(
state = stateV8,
onNavigateBack = {}
)
}
9 -> {
val orderItemV9 = OrderItemV9(
id = "123",
name = "Floral Print Dress",
imageUrl = R.drawable.ic_card_icon,
price = 599,
size = "M",
quantity = 1
)
val refundOptionsV9 = listOf(
RefundOptionV9(
id = RefundMethodTypeV9.MEESHO_APP,
title = "Meesho App",
subtitle = "Instant refund",
icon = R.drawable.ic_card_icon,
getAmount = 599,
extraAmount = 50,
isSelected = true
),
RefundOptionV9(
id = RefundMethodTypeV9.BANK_UPI,
title = "Bank/UPI",
subtitle = "3-5 business days",
icon = R.drawable.ic_card_icon,
getAmount = 599,
isSelected = false
)
)
val stateV9 = rememberReturnOrderStateV9(
initialOrderItem = orderItemV9,
initialRefundOptions = refundOptionsV9
)
ReturnOrderScreenV9(
state = stateV9,
onNavigateBack = {}
)
}
10 -> {
val orderItemV10 = OrderItemV10(
id = "123",
name = "Floral Print Dress",
imageUrl = R.drawable.ic_card_icon,
price = 599,
size = "M",
quantity = 1
)
val refundOptionsV10 = listOf(
RefundOptionV10(
id = RefundMethodTypeV10.MEESHO_APP,
title = "Meesho App",
subtitle = "Instant refund",
icon = R.drawable.ic_card_icon,
getAmount = 599,
extraAmount = 50,
isSelected = true
),
RefundOptionV10(
id = RefundMethodTypeV10.BANK_UPI,
title = "Bank/UPI",
subtitle = "3-5 business days",
icon = R.drawable.ic_card_icon,
getAmount = 599,
isSelected = false
)
)
val stateV10 = rememberReturnOrderStateV10(
initialOrderItem = orderItemV10,
initialRefundOptions = refundOptionsV10
)
ReturnOrderScreenV10(
state = stateV10,
onNavigateBack = {}
)
}
11 -> {
val orderItemV11 = OrderItemV11(
id = "123",
name = "Floral Print Dress",
imageUrl = R.drawable.ic_card_icon,
price = 599,
size = "M",
quantity = 1
)
val refundOptionsV11 = listOf(
RefundOptionV11(
id = RefundMethodTypeV11.MEESHO_APP,
title = "Meesho App",
subtitle = "Instant refund",
icon = R.drawable.ic_card_icon,
getAmount = 599,
extraAmount = 50,
isSelected = true
),
RefundOptionV11(
id = RefundMethodTypeV11.BANK_UPI,
title = "Bank/UPI",
subtitle = "3-5 business days",
icon = R.drawable.ic_card_icon,
getAmount = 599,
isSelected = false
)
)
val stateV11 = rememberReturnOrderStateV11(
initialOrderItem = orderItemV11,
initialRefundOptions = refundOptionsV11
)
ReturnOrderScreenV11(
state = stateV11,
onNavigateBack = {}
)
}
12 -> {
val orderItemV12 = OrderItemV12(
id = "123",
name = "Floral Print Dress",
imageUrl = R.drawable.ic_card_icon,
price = 599,
size = "M",
quantity = 1
)
val refundOptionsV12 = listOf(
RefundOptionV12(
id = RefundMethodTypeV12.MEESHO_APP,
title = "Meesho App",
subtitle = "Instant refund",
icon = R.drawable.ic_card_icon,
getAmount = 599,
extraAmount = 50,
isSelected = true
),
RefundOptionV12(
id = RefundMethodTypeV12.BANK_UPI,
title = "Bank/UPI",
subtitle = "3-5 business days",
icon = R.drawable.ic_card_icon,
getAmount = 599,
isSelected = false
)
)
val stateV12 = rememberReturnOrderStateV12(
initialOrderItem = orderItemV12,
initialRefundOptions = refundOptionsV12
)
ReturnOrderScreenV12(
state = stateV12,
onNavigateBack = {}
)
}
}
}
}
}
}

@ -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…
Cancel
Save