/*
* Episteme Reader - A native Android document reader.
* Copyright (C) 2026 Episteme
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* mail: epistemereader@gmail.com
*/
package com.aryan.reader
import android.app.Activity
import timber.log.Timber
import android.widget.Toast
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Info
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
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.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
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.TextAlign
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.aryan.reader.data.ProductDetailsEntity
import kotlinx.coroutines.launch
import java.text.NumberFormat
import java.util.Currency
@Suppress("KotlinConstantConditions")
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun ProScreen(
viewModel: MainViewModel,
onNavigateBack: () -> Unit
) {
val context = LocalContext.current
val proUpgradeState by viewModel.proUpgradeState.collectAsState()
val uiState by viewModel.uiState.collectAsState()
var showExistingPurchaseDialog by remember { mutableStateOf(false) }
var showSignInRequiredDialog by remember { mutableStateOf(false) }
// Removed Free Tab, so tabCount is max 2
val tabCount = if (BuildConfig.FLAVOR == "pro") 2 else 1
val pagerState = rememberPagerState(initialPage = 0, pageCount = { tabCount })
var selectedTabIndex by remember { mutableIntStateOf(0) }
val scope = rememberCoroutineScope()
LaunchedEffect(pagerState.currentPage) {
selectedTabIndex = pagerState.currentPage
}
LaunchedEffect(selectedTabIndex) {
scope.launch {
pagerState.animateScrollToPage(selectedTabIndex)
}
}
// Default to the Credits tab if they already own Pro
LaunchedEffect(uiState.isProUser) {
if (uiState.isProUser && BuildConfig.FLAVOR == "pro") {
selectedTabIndex = 1
}
}
LaunchedEffect(proUpgradeState.error) {
proUpgradeState.error?.let {
Toast.makeText(context, it, Toast.LENGTH_LONG).show()
viewModel.clearBillingError()
}
}
if (showExistingPurchaseDialog) {
ExistingPurchaseDialog(onDismiss = { showExistingPurchaseDialog = false })
}
if (showSignInRequiredDialog) {
SignInRequiredDialog(
onSignInClick = {
scope.launch {
context.findActivity()?.let { activity ->
viewModel.signIn(activity)
}
}
showSignInRequiredDialog = false
},
onDismiss = { showSignInRequiredDialog = false }
)
}
Scaffold(
topBar = {
TopAppBar(
title = { },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.action_back))
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.background
)
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(top = paddingValues.calculateTopPadding(), start = 16.dp, end = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top
) {
Spacer(modifier = Modifier.height(16.dp))
// Tabs
TabRow(
selectedTabIndex = selectedTabIndex,
modifier = Modifier
.fillMaxWidth()
.clip(MaterialTheme.shapes.extraLarge)
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)),
indicator = {},
divider = {}
) {
Tab(
selected = selectedTabIndex == 0,
onClick = { selectedTabIndex = 0 },
modifier = Modifier
.height(56.dp)
.clip(CircleShape)
.background(
if (selectedTabIndex == 0) MaterialTheme.colorScheme.surface else Color.Transparent
)
.border(
width = if (selectedTabIndex == 0) 2.dp else 0.dp,
color = if (selectedTabIndex == 0) MaterialTheme.colorScheme.primary else Color.Transparent,
shape = CircleShape
),
text = {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
painter = painterResource(id = R.drawable.crown),
contentDescription = stringResource(R.string.drawer_pro_unlocked),
modifier = Modifier.size(16.dp),
tint = if (selectedTabIndex == 0) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.width(4.dp))
AutoSizeText(stringResource(R.string.drawer_pro_unlocked),
style = LocalTextStyle.current.copy(
color = if (selectedTabIndex == 0) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant,
fontWeight = FontWeight.SemiBold
)
)
}
},
selectedContentColor = MaterialTheme.colorScheme.primary,
unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant
)
if (BuildConfig.FLAVOR == "pro") {
Tab(
selected = selectedTabIndex == 1,
onClick = { selectedTabIndex = 1 },
modifier = Modifier
.height(56.dp)
.clip(CircleShape)
.background(if (selectedTabIndex == 1) MaterialTheme.colorScheme.surface else Color.Transparent)
.border(
width = if (selectedTabIndex == 1) 2.dp else 0.dp,
color = if (selectedTabIndex == 1) MaterialTheme.colorScheme.primary else Color.Transparent,
shape = CircleShape
),
text = {
AutoSizeText(stringResource(R.string.credits_tab),
style = LocalTextStyle.current.copy(
color = if (selectedTabIndex == 1) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant,
fontWeight = FontWeight.SemiBold
)
)
},
selectedContentColor = MaterialTheme.colorScheme.primary,
unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
Spacer(modifier = Modifier.height(16.dp))
HorizontalPager(
state = pagerState,
modifier = Modifier.fillMaxWidth().fillMaxHeight(),
userScrollEnabled = true
) { page ->
when (page) {
0 -> {
ProTierCard(
isProUser = uiState.isProUser,
isUserSignedIn = uiState.currentUser != null,
proUpgradeState = proUpgradeState,
onUpgradeClick = {
(context as? Activity)?.let {
viewModel.launchPurchaseFlow(it)
}
},
onShowExistingPurchaseDialog = { showExistingPurchaseDialog = true },
onSignInRequiredClick = { showSignInRequiredDialog = true })
}
1 -> {
if (BuildConfig.FLAVOR == "pro") {
CreditTierCard(
credits = uiState.credits,
creditProducts = proUpgradeState.creditProducts,
isVerifying = proUpgradeState.isVerifying,
isUserSignedIn = uiState.currentUser != null,
onSignInRequiredClick = { showSignInRequiredDialog = true },
onBuyCredits = { productId ->
(context as? Activity)?.let { viewModel.launchPurchaseFlow(it, productId) }
})
}
}
}
}
}
}
}
@Composable
private fun ProTierCard(
isProUser: Boolean,
isUserSignedIn: Boolean,
proUpgradeState: ProUpgradeState,
onUpgradeClick: () -> Unit,
onShowExistingPurchaseDialog: () -> Unit,
onSignInRequiredClick: () -> Unit
) {
val productDetails = proUpgradeState.productDetails
val billingClientReady = proUpgradeState.billingClientReady
val localPurchaseExistsForOtherAccount = !isProUser && proUpgradeState.hasAccountConflict
var originalFormattedPrice by remember { mutableStateOf("$9.99") }
LaunchedEffect(productDetails) {
productDetails?.let { details ->
val priceAmountMicros = details.priceAmountMicros
val priceCurrencyCode = details.currencyCode
val originalPriceMicros = priceAmountMicros * 2
val currencyFormatter = NumberFormat.getCurrencyInstance().apply {
try {
currency = Currency.getInstance(priceCurrencyCode)
} catch (e: IllegalArgumentException) {
Timber.e(e, "Invalid currency code: $priceCurrencyCode")
}
}
originalFormattedPrice = currencyFormatter.format(originalPriceMicros / 1_000_000.0)
}
}
Card(
modifier = Modifier.fillMaxWidth().fillMaxHeight(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
)
) {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
painter = painterResource(id = R.drawable.crown),
contentDescription = stringResource(R.string.drawer_pro_unlocked),
modifier = Modifier.size(24.dp),
tint = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.width(8.dp))
Text(stringResource(R.string.drawer_pro_unlocked),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Bold
)
}
Spacer(modifier = Modifier.height(8.dp))
if (!isProUser) {
val formattedPrice = productDetails?.formattedPrice
if (formattedPrice != null) {
Text(
text = buildAnnotatedString {
withStyle(style = SpanStyle(textDecoration = TextDecoration.LineThrough)) {
append(originalFormattedPrice)
}
append(" " + stringResource(R.string.pro_sale_off)) },
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = formattedPrice,
style = MaterialTheme.typography.displaySmall.copy(fontSize = 48.sp),
fontWeight = FontWeight.Bold
)
} else {
Text(stringResource(R.string.loading_price),
style = MaterialTheme.typography.displaySmall.copy(fontSize = 32.sp),
fontWeight = FontWeight.Bold
)
}
Row(verticalAlignment = Alignment.CenterVertically) {
Text(stringResource(R.string.one_time_payment),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.width(8.dp))
Surface(
shape = MaterialTheme.shapes.extraSmall,
color = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.onPrimaryContainer
) {
Text(stringResource(R.string.lifetime_access),
style = MaterialTheme.typography.labelSmall,
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp)
)
}
}
Spacer(modifier = Modifier.height(16.dp))
}
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start
) {
Text(stringResource(R.string.pro_includes),
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.SemiBold,
modifier = Modifier.padding(bottom = 8.dp)
)
FeatureListItem(iconRes = R.drawable.cloud_sync, text = stringResource(R.string.feature_cloud_sync))
Text(stringResource(R.string.feature_cloud_sync_desc),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(start = 36.dp, bottom = 8.dp)
)
FeatureListItem(iconRes = R.drawable.summarize, text = stringResource(R.string.feature_summarize))
Text(stringResource(R.string.feature_summarize_desc),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(start = 36.dp, bottom = 8.dp)
)
FeatureListItem(iconRes = R.drawable.dictionary, text = stringResource(R.string.feature_smart_dict))
Text(stringResource(R.string.feature_smart_dict_desc),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(start = 36.dp, bottom = 8.dp)
)
FeatureListItem(iconRes = R.drawable.chat_bubble, text = stringResource(R.string.feature_priority))
Text(stringResource(R.string.feature_priority_desc),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(start = 36.dp, bottom = 8.dp)
)
}
Spacer(modifier = Modifier.height(16.dp))
if (isProUser) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxWidth()
.height(48.dp)
.background(
MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.4f),
MaterialTheme.shapes.medium
)
) {
Icon(
painter = painterResource(id = R.drawable.crown),
contentDescription = stringResource(R.string.pro_unlocked),
tint = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.width(8.dp))
Text(stringResource(R.string.pro_unlocked),
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.SemiBold,
color = MaterialTheme.colorScheme.primary
)
}
} else {
when {
!isUserSignedIn -> {
Button(
onClick = onSignInRequiredClick,
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
shape = MaterialTheme.shapes.medium,
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f),
contentColor = MaterialTheme.colorScheme.onPrimary
)
) {
AutoSizeText(stringResource(R.string.sign_in_required), style = LocalTextStyle.current.copy(fontSize = 16.sp, fontWeight = FontWeight.SemiBold))
}
}
proUpgradeState.isVerifying -> {
Button(
onClick = {},
enabled = false,
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
shape = MaterialTheme.shapes.medium,
) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
strokeWidth = 2.dp,
color = LocalContentColor.current
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text(stringResource(R.string.verifying_purchase))
}
}
productDetails != null -> {
Button(
onClick = {
Timber.d("Upgrade button clicked.")
onUpgradeClick()
},
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
shape = MaterialTheme.shapes.medium,
enabled = billingClientReady
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
painter = painterResource(id = R.drawable.crown),
contentDescription = stringResource(R.string.drawer_pro_unlocked),
modifier = Modifier.size(20.dp)
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
AutoSizeText(stringResource(R.string.get_lifetime_access), style = LocalTextStyle.current.copy(fontSize = 16.sp, fontWeight = FontWeight.SemiBold))
}
}
}
!billingClientReady -> {
Box(modifier = Modifier.height(48.dp), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
}
else -> {
Text(stringResource(R.string.upgrade_unavailable),
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.height(48.dp)
)
}
}
// Text area below button
Spacer(modifier = Modifier.height(16.dp)) // Increased spacing
when {
!isUserSignedIn -> {
Text(stringResource(R.string.sign_in_to_purchase),
style = MaterialTheme.typography.bodySmall,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
proUpgradeState.isVerifying -> {
Text(stringResource(R.string.verifying_purchase_desc),
style = MaterialTheme.typography.bodySmall,
textAlign = TextAlign.Center
)
}
localPurchaseExistsForOtherAccount -> {
TextButton(onClick = onShowExistingPurchaseDialog) {
Icon(
imageVector = Icons.Default.Info,
contentDescription = stringResource(R.string.info),
modifier = Modifier.size(18.dp)
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
AutoSizeText(stringResource(R.string.existing_purchase_found))
}
}
else -> {
LegalText(prefixText = stringResource(R.string.legal_by_purchasing))
}
}
}
}
}
}
@Composable
private fun FeatureListItem(@androidx.annotation.DrawableRes iconRes: Int? = null, icon: ImageVector? = null, text: String) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (iconRes != null) {
Icon(
painter = painterResource(id = iconRes),
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(20.dp)
)
} else if (icon != null) {
Icon(
imageVector = icon,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(20.dp)
)
} else {
Icon(
imageVector = Icons.Default.Info,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(20.dp)
)
}
Spacer(modifier = Modifier.width(16.dp))
Text(text = text, style = MaterialTheme.typography.bodyLarge)
}
}
@Composable
fun ExistingPurchaseDialog(onDismiss: () -> Unit) {
AlertDialog(
onDismissRequest = onDismiss,
icon = { Icon(Icons.Default.Info, contentDescription = null) },
title = { Text(stringResource(R.string.existing_purchase_found)) },
text = { Text(stringResource(R.string.dialog_existing_purchase_desc)) },
confirmButton = {
TextButton(onClick = onDismiss) { Text(stringResource(R.string.action_ok)) }
}
)
}
@Composable
fun SignInRequiredDialog(onSignInClick: () -> Unit, onDismiss: () -> Unit) {
AlertDialog(
onDismissRequest = onDismiss,
icon = { Icon(painter = painterResource(id = R.drawable.crown), contentDescription = null) },
title = { Text(stringResource(R.string.sign_in_required)) },
text = { Text(stringResource(R.string.dialog_sign_in_required_desc)) },
confirmButton = {
TextButton(onClick = onSignInClick) { Text(stringResource(R.string.drawer_sign_in)) }
},
dismissButton = {
TextButton(onClick = onDismiss) { Text(stringResource(R.string.action_not_now)) }
}
)
}
@Composable
private fun CreditTierCard(
credits: Int,
creditProducts: List,
isVerifying: Boolean,
isUserSignedIn: Boolean,
onSignInRequiredClick: () -> Unit,
onBuyCredits: (String) -> Unit
) {
Card(
modifier = Modifier.fillMaxWidth().fillMaxHeight(),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
) {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(stringResource(R.string.credits_title), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "$credits",
style = MaterialTheme.typography.displaySmall.copy(fontSize = 48.sp),
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
)
Text(stringResource(R.string.credits_available), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
Spacer(modifier = Modifier.height(24.dp))
if (isVerifying) {
CircularProgressIndicator(modifier = Modifier.padding(16.dp))
Text(stringResource(R.string.verifying_purchase), style = MaterialTheme.typography.bodySmall)
} else if (creditProducts.isEmpty()) {
Text(stringResource(R.string.loading_price), modifier = Modifier.padding(16.dp))
} else {
creditProducts.forEach { product ->
OutlinedCard(
modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp),
onClick = {
if (isUserSignedIn) onBuyCredits(product.productId)
else onSignInRequiredClick()
},
border = BorderStroke(1.dp, MaterialTheme.colorScheme.primary.copy(alpha = 0.5f))
) {
Row(
modifier = Modifier.fillMaxWidth().padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column(modifier = Modifier.weight(1f).padding(end = 12.dp)) {
Text(product.name, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyLarge)
if (product.description.isNotBlank()) {
Text(product.description, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
}
}
Button(
onClick = {
if (isUserSignedIn) onBuyCredits(product.productId)
else onSignInRequiredClick()
},
modifier = Modifier.wrapContentWidth()
) {
Text(product.formattedPrice)
}
}
}
}
}
if (!isUserSignedIn) {
Spacer(modifier = Modifier.height(8.dp))
Text(
stringResource(R.string.sign_in_to_purchase_credits),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Medium
)
}
Spacer(modifier = Modifier.height(32.dp))
HorizontalDivider(color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f))
Spacer(modifier = Modifier.height(16.dp))
Text(
stringResource(R.string.credits_estimated_cost_breakdown),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
modifier = Modifier.align(Alignment.Start)
)
Spacer(modifier = Modifier.height(16.dp))
CostBreakdownItem(
iconRes = R.drawable.text_to_speech,
title = stringResource(R.string.credits_cloud_tts_title),
description = stringResource(R.string.credits_cloud_tts_desc)
)
CostBreakdownItem(
iconRes = R.drawable.summarize,
title = stringResource(R.string.credits_ai_summaries_title),
description = stringResource(R.string.credits_ai_summaries_desc)
)
Spacer(modifier = Modifier.height(24.dp))
}
}
}
@Composable
private fun CostBreakdownItem(
@androidx.annotation.DrawableRes iconRes: Int,
title: String,
description: String
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
verticalAlignment = Alignment.Top
) {
Icon(
painter = painterResource(id = iconRes),
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(24.dp).padding(top = 2.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Column {
Text(text = title, style = MaterialTheme.typography.bodyLarge, fontWeight = FontWeight.SemiBold)
Spacer(modifier = Modifier.height(2.dp))
Text(
text = description,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
lineHeight = 18.sp
)
}
}
}