feat: clean

This commit is contained in:
2026-06-08 19:58:26 +03:30
parent 0ae8fa19bc
commit 16128e5304
8 changed files with 756 additions and 691 deletions
@@ -24,7 +24,7 @@ import com.approagency.pharmacy.presentation.viewModel.HomeViewModel
import com.approagency.pharmacy.presentation.viewModel.LabViewModel import com.approagency.pharmacy.presentation.viewModel.LabViewModel
import com.approagency.pharmacy.presentation.viewModel.PharmacyViewModel import com.approagency.pharmacy.presentation.viewModel.PharmacyViewModel
import com.approagency.pharmacy.utils.Config import com.approagency.pharmacy.utils.Config
import com.approgency.drug.presentation.viewModel.SearchViewModel import com.approagency.pharmacy.presentation.viewModel.SearchViewModel
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
@@ -6,9 +6,6 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.LocationOn
import androidx.compose.material.icons.filled.Phone
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -17,16 +14,11 @@ import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.approagency.pharmacy.domain.model.PharmacyItem
import com.approagency.pharmacy.presentation.common.CustomBox
import com.approagency.pharmacy.presentation.common.CustomModalBottomSheet import com.approagency.pharmacy.presentation.common.CustomModalBottomSheet
import com.approagency.pharmacy.presentation.common.Loading import com.approagency.pharmacy.presentation.common.Loading
import com.approagency.pharmacy.presentation.viewModel.PharmacyDetailState
import com.approagency.pharmacy.presentation.viewModel.PharmacyState import com.approagency.pharmacy.presentation.viewModel.PharmacyState
import com.approagency.pharmacy.presentation.viewModel.PharmacyViewModel import com.approagency.pharmacy.presentation.viewModel.PharmacyViewModel
import com.approagency.pharmacy.utils.provinces import com.approagency.pharmacy.utils.provinces
import com.vada.caller.ui.theme.LocalDime
import com.vada.caller.ui.theme.dime import com.vada.caller.ui.theme.dime
@SuppressLint("ConfigurationScreenWidthHeight") @SuppressLint("ConfigurationScreenWidthHeight")
@@ -199,145 +191,3 @@ fun PharmacyBottomSheet(
} }
} }
} }
@Composable
fun PharmacyResultItem(
pharmacy: PharmacyItem, viewModel: PharmacyViewModel, modifier: Modifier = Modifier
) {
val dime = LocalDime.current
var showDetails by remember { mutableStateOf(false) }
// گرفتن State مخصوص این داروخانه از Map
val detailStates by viewModel.pharmacyDetailStates.collectAsState()
val detailState = detailStates[pharmacy.pharmacyUrl] ?: PharmacyDetailState.Idle
// Reset detail state when item changes
LaunchedEffect(pharmacy.pharmacyUrl) {
println(pharmacy.pharmacyUrl)
showDetails = false
}
// Load detail when expanded
LaunchedEffect(showDetails) {
if (showDetails) {
// فقط اگر Idle هست (هنوز لود نشده) درخواست بده
if (detailState is PharmacyDetailState.Idle) {
println(pharmacy.pharmacyUrl)
viewModel.loadPharmacyDetail(pharmacy.pharmacyUrl)
}
}
}
CustomBox(
modifier = modifier.fillMaxWidth(),
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(dime.md),
verticalArrangement = Arrangement.spacedBy(dime.xs)
) {
// نام برند
Text(
text = pharmacy.brandName,
fontSize = MaterialTheme.typography.labelSmall.fontSize,
color = MaterialTheme.colorScheme.primary
)
// نام داروخانه
Text(
text = pharmacy.pharmacyName,
fontWeight = FontWeight.Bold,
fontSize = MaterialTheme.typography.labelLarge.fontSize
)
// موقعیت
// موقعیت و دکمه اطلاعات تماس
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = "${pharmacy.province} - ${pharmacy.city}",
fontSize = MaterialTheme.typography.bodySmall.fontSize,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
TextButton(
onClick = { showDetails = !showDetails }) {
Text(
text = if (showDetails) "بستن" else "اطلاعات تماس",
fontSize = MaterialTheme.typography.bodySmall.fontSize
)
}
}
if (showDetails) {
when (detailState) {
is PharmacyDetailState.Loading -> {
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth()
) {
Loading(size = 24.dp)
}
}
is PharmacyDetailState.Success -> {
Column(
verticalArrangement = Arrangement.spacedBy(dime.sm)
) {
HorizontalDivider(modifier = Modifier.padding(vertical = MaterialTheme.dime.xs))
// آدرس
Row(
verticalAlignment = Alignment.Top,
horizontalArrangement = Arrangement.spacedBy(dime.sm)
) {
Icon(
imageVector = Icons.Default.LocationOn,
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = MaterialTheme.colorScheme.primary
)
Text(
text = detailState.detail.address,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
lineHeight = 18.sp
)
}
// تلفن
if (detailState.detail.phone.isNotBlank()) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(dime.sm)
) {
Icon(
imageVector = Icons.Default.Phone,
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = MaterialTheme.colorScheme.primary
)
Text(
text = detailState.detail.phone,
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
fontWeight = FontWeight.Medium
)
}
}
}
}
is PharmacyDetailState.Error -> {
Text(
text = "خطا در دریافت اطلاعات تماس",
fontSize = MaterialTheme.typography.bodySmall.fontSize,
color = MaterialTheme.colorScheme.error
)
}
else -> {}
}
}
}
}
}
@@ -0,0 +1,174 @@
package com.approagency.pharmacy.presentation.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.LocationOn
import androidx.compose.material.icons.filled.Phone
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
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.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.approagency.pharmacy.domain.model.PharmacyItem
import com.approagency.pharmacy.presentation.common.CustomBox
import com.approagency.pharmacy.presentation.common.Loading
import com.approagency.pharmacy.presentation.viewModel.PharmacyDetailState
import com.approagency.pharmacy.presentation.viewModel.PharmacyViewModel
import com.vada.caller.ui.theme.LocalDime
import com.vada.caller.ui.theme.dime
@Composable
fun PharmacyResultItem(
pharmacy: PharmacyItem, viewModel: PharmacyViewModel, modifier: Modifier = Modifier
) {
val dime = LocalDime.current
var showDetails by remember { mutableStateOf(false) }
// گرفتن State مخصوص این داروخانه از Map
val detailStates by viewModel.pharmacyDetailStates.collectAsState()
val detailState = detailStates[pharmacy.pharmacyUrl] ?: PharmacyDetailState.Idle
// Reset detail state when item changes
LaunchedEffect(pharmacy.pharmacyUrl) {
showDetails = false
}
// Load detail when expanded
LaunchedEffect(showDetails) {
if (showDetails) {
// فقط اگر Idle هست (هنوز لود نشده) درخواست بده
if (detailState is PharmacyDetailState.Idle) {
viewModel.loadPharmacyDetail(pharmacy.pharmacyUrl)
}
}
}
CustomBox(
modifier = modifier.fillMaxWidth(),
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(dime.md),
verticalArrangement = Arrangement.spacedBy(dime.xs)
) {
// نام برند
Text(
text = pharmacy.brandName,
fontSize = MaterialTheme.typography.labelSmall.fontSize,
color = MaterialTheme.colorScheme.primary
)
// نام داروخانه
Text(
text = pharmacy.pharmacyName,
fontWeight = FontWeight.Bold,
fontSize = MaterialTheme.typography.labelLarge.fontSize
)
// موقعیت و دکمه اطلاعات تماس
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = "${pharmacy.province} - ${pharmacy.city}",
fontSize = MaterialTheme.typography.bodySmall.fontSize,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
TextButton(
onClick = { showDetails = !showDetails }) {
Text(
text = if (showDetails) "بستن" else "اطلاعات تماس",
fontSize = MaterialTheme.typography.bodySmall.fontSize
)
}
}
if (showDetails) {
when (detailState) {
is PharmacyDetailState.Loading -> {
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth()
) {
Loading(size = 24.dp)
}
}
is PharmacyDetailState.Success -> {
Column(
verticalArrangement = Arrangement.spacedBy(dime.sm)
) {
HorizontalDivider(modifier = Modifier.padding(vertical = MaterialTheme.dime.xs))
// آدرس
Row(
verticalAlignment = Alignment.Top,
horizontalArrangement = Arrangement.spacedBy(dime.sm)
) {
Icon(
imageVector = Icons.Default.LocationOn,
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = MaterialTheme.colorScheme.primary
)
Text(
text = detailState.detail.address,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
lineHeight = 18.sp
)
}
// تلفن
if (detailState.detail.phone.isNotBlank()) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(dime.sm)
) {
Icon(
imageVector = Icons.Default.Phone,
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = MaterialTheme.colorScheme.primary
)
Text(
text = detailState.detail.phone,
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
fontWeight = FontWeight.Medium
)
}
}
}
}
is PharmacyDetailState.Error -> {
Text(
text = "خطا در دریافت اطلاعات تماس",
fontSize = MaterialTheme.typography.bodySmall.fontSize,
color = MaterialTheme.colorScheme.error
)
}
else -> {}
}
}
}
}
}
@@ -0,0 +1,179 @@
package com.approagency.pharmacy.presentation.components.drugdetail
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Info
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.vada.caller.ui.theme.LocalDime
import com.vada.caller.ui.theme.dime
/**
* بلوک‌های مشترکِ ظاهری صفحه‌ی جزئیات دارو.
* این کامپوزبل‌ها در فایل‌های تب (DrugDetailTabs) استفاده می‌شوند.
*/
/** کارت متنی با عنوانِ سربرگ‌دار و بدنه‌ی توضیحات. */
@Composable
internal fun InfoSectionCard(
title: String,
content: String,
modifier: Modifier = Modifier
) {
val dime = LocalDime.current
DetailCard(modifier = modifier) {
SectionHeader(title = title)
Spacer(modifier = Modifier.height(dime.sm))
Text(
text = content,
style = MaterialTheme.typography.bodyMedium,
lineHeight = 22.sp,
textAlign = TextAlign.Justify,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
/**
* کارت پایه‌ی صفحه‌ی جزئیات — ظاهر یکدست با بقیه‌ی برنامه:
* سطح روشن + قاب نازک هم‌رنگ تم، بدون سایه‌ی سنگین.
*/
@Composable
internal fun DetailCard(
modifier: Modifier = Modifier,
content: @Composable ColumnScope.() -> Unit
) {
val shape = MaterialTheme.shapes.large
Column(
modifier = modifier
.fillMaxWidth()
.clip(shape)
.background(MaterialTheme.colorScheme.surfaceContainerLowest)
.border(
width = 1.dp,
color = MaterialTheme.colorScheme.outlineVariant,
shape = shape
)
.padding(MaterialTheme.dime.md),
content = content
)
}
/** سربرگ بخش با نوار تأکید کوچک کنار عنوان. */
@Composable
internal fun SectionHeader(
title: String,
modifier: Modifier = Modifier
) {
val dime = LocalDime.current
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(dime.sm)
) {
Box(
modifier = Modifier
.size(width = 4.dp, height = 18.dp)
.clip(RoundedCornerShape(dime.xxs))
.background(MaterialTheme.colorScheme.primary)
)
Text(
text = title,
style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
)
}
}
/** حالت «اطلاعاتی ثبت نشده» با آیکن، یکدست در همه‌ی تب‌ها. */
@Composable
internal fun DetailEmptyState(
text: String,
modifier: Modifier = Modifier
) {
val dime = LocalDime.current
Column(
modifier = modifier
.fillMaxWidth()
.padding(vertical = dime.xxl, horizontal = dime.lg),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(dime.sm)
) {
Icon(
imageVector = Icons.Default.Info,
contentDescription = null,
modifier = Modifier.size(40.dp),
tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
)
Text(
text = text,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center
)
}
}
/** ردیف «برچسب: مقدار» با تایپوگرافی یکدست. */
@Composable
internal fun LabeledRow(
label: String,
value: String,
modifier: Modifier = Modifier
) {
Row(modifier = modifier) {
Text(
text = "$label: ",
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onSurface
)
Text(
text = value,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.weight(1f)
)
}
}
/** برچسب کوچک رنگی (مثل «پرخطر» / «حیاتی»). */
@Composable
internal fun TagChip(
text: String,
container: Color,
onContainer: Color
) {
Text(
text = text,
style = MaterialTheme.typography.labelSmall,
color = onContainer,
modifier = Modifier
.clip(RoundedCornerShape(MaterialTheme.dime.xs))
.background(container)
.padding(horizontal = MaterialTheme.dime.sm, vertical = MaterialTheme.dime.xxs)
)
}
@@ -0,0 +1,389 @@
package com.approagency.pharmacy.presentation.components.drugdetail
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Info
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.approagency.pharmacy.domain.model.DrugDetail
import com.approagency.pharmacy.domain.model.GenericInfo
import com.vada.caller.ui.theme.LocalDime
/** کارت اطلاعات سازنده (برای صفحات برند). */
@Composable
internal fun ManufacturerInfoCard(
manufacturer: String,
genericInfo: GenericInfo?,
modifier: Modifier = Modifier
) {
val dime = LocalDime.current
val onContainer = MaterialTheme.colorScheme.onSecondaryContainer
Column(
modifier = modifier
.fillMaxWidth()
.clip(MaterialTheme.shapes.large)
.background(MaterialTheme.colorScheme.secondaryContainer)
.padding(dime.md),
verticalArrangement = Arrangement.spacedBy(dime.sm)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(dime.sm)
) {
Icon(
imageVector = Icons.Default.Info,
contentDescription = null,
modifier = Modifier.size(18.dp),
tint = onContainer
)
Text(
text = "سازنده:",
style = MaterialTheme.typography.labelLarge,
color = onContainer
)
Text(
text = manufacturer,
style = MaterialTheme.typography.bodyMedium,
color = onContainer
)
}
if (genericInfo != null) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(dime.sm)
) {
Icon(
imageVector = Icons.Default.Info,
contentDescription = null,
modifier = Modifier.size(18.dp),
tint = onContainer
)
Text(
text = "ماده موثره:",
style = MaterialTheme.typography.labelLarge,
color = onContainer
)
Text(
text = genericInfo.persianName,
style = MaterialTheme.typography.bodyMedium,
color = onContainer
)
}
}
}
}
/** تب «عمومی». */
@Composable
internal fun GeneralInfoTabContent(drugDetail: DrugDetail) {
val dime = LocalDime.current
Column(verticalArrangement = Arrangement.spacedBy(dime.md)) {
if (drugDetail.isGeneric) {
// ========== صفحات ژنریک (Generic) ==========
// اگر generalInfo وجود دارد (از EtelaatOmomiVaTakhasosi یا EtelaatOmomi)
if (!drugDetail.generalInfo.isNullOrBlank()) {
InfoSectionCard(
title = "اطلاعات عمومی",
content = drugDetail.generalInfo
)
}
// اگر specializedInfo وجود دارد (از EtelaatTakhasosiContent)
else if (!drugDetail.specializedInfo.isNullOrBlank()) {
InfoSectionCard(
title = "اطلاعات تخصصی",
content = drugDetail.specializedInfo
)
}
// در غیر این صورت، اطلاعات پراکنده را نمایش بده
else {
// Categories Card
if (drugDetail.drugClass != null || drugDetail.therapeuticClass != null) {
DetailCard {
Column(verticalArrangement = Arrangement.spacedBy(dime.xs)) {
drugDetail.drugClass?.let {
LabeledRow(label = "طبقه بندی مارتیندل", value = it)
}
drugDetail.therapeuticClass?.let {
LabeledRow(label = "طبقه درمانی", value = it)
}
}
}
}
// Pregnancy Card
if (drugDetail.pregnancyCategory != null || drugDetail.pregnancyDescription != null) {
val onContainer = MaterialTheme.colorScheme.onTertiaryContainer
Column(
modifier = Modifier
.fillMaxWidth()
.clip(MaterialTheme.shapes.large)
.background(MaterialTheme.colorScheme.tertiaryContainer)
.padding(dime.md),
verticalArrangement = Arrangement.spacedBy(dime.xs)
) {
Text(
text = "مصرف در بارداری",
style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.Bold,
color = onContainer
)
drugDetail.pregnancyCategory?.let {
Text(
text = "گروه: $it",
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Medium,
color = onContainer
)
}
drugDetail.pregnancyDescription?.let {
Text(
text = it,
style = MaterialTheme.typography.bodySmall,
lineHeight = 18.sp,
color = onContainer.copy(alpha = 0.9f)
)
}
}
}
// موارد مصرف
drugDetail.usage?.let {
InfoSectionCard(title = "موارد مصرف", content = it)
}
// موارد منع مصرف
drugDetail.contraindications?.let {
InfoSectionCard(title = "موارد منع مصرف", content = it)
}
// عوارض جانبی
drugDetail.sideEffects?.let {
InfoSectionCard(title = "عوارض جانبی", content = it)
}
// هشدارها
drugDetail.warnings?.let {
InfoSectionCard(title = "هشدارها", content = it)
}
// توصیه‌های دارویی
drugDetail.recommendations?.let {
InfoSectionCard(title = "توصیه‌های دارویی", content = it)
}
}
// اگر هیچ اطلاعاتی وجود نداشت
if (drugDetail.drugClass == null &&
drugDetail.therapeuticClass == null &&
drugDetail.pregnancyCategory == null &&
drugDetail.usage == null &&
drugDetail.contraindications == null &&
drugDetail.sideEffects == null &&
drugDetail.warnings == null &&
drugDetail.generalInfo.isNullOrBlank() &&
drugDetail.specializedInfo.isNullOrBlank()
) {
DetailEmptyState(text = "اطلاعات عمومی برای این دارو ثبت نشده است")
}
} else {
// ========== صفحات برند (Brand) ==========
// نمایش اطلاعات تخصصی برند که در بخش brandAttrPersDesc است
drugDetail.usage?.let {
InfoSectionCard(title = "موارد مصرف", content = it)
}
drugDetail.contraindications?.let {
InfoSectionCard(title = "موارد منع مصرف", content = it)
}
drugDetail.sideEffects?.let {
InfoSectionCard(title = "عوارض جانبی", content = it)
}
drugDetail.warnings?.let {
InfoSectionCard(title = "هشدارها", content = it)
}
drugDetail.recommendations?.let {
InfoSectionCard(title = "توصیه‌های دارویی", content = it)
}
if (drugDetail.usage == null &&
drugDetail.contraindications == null &&
drugDetail.sideEffects == null &&
drugDetail.warnings == null
) {
DetailEmptyState(
text = "اطلاعات عمومی برای این برند ثبت نشده است.\nبرای مشاهده اطلاعات کامل، به صفحه داروی ژنریک مراجعه کنید."
)
}
}
}
}
/** تب «تخصصی». */
@Composable
internal fun SpecializedInfoTabContent(drugDetail: DrugDetail) {
val dime = LocalDime.current
Column(verticalArrangement = Arrangement.spacedBy(dime.md)) {
// برای صفحات برند، اطلاعات تخصصی معمولاً در قسمت brandAttrPersDesc است
// که در پارسر ما به صورت usage, contraindications, sideEffects, warnings, recommendations ذخیره شده
drugDetail.usage?.let {
InfoSectionCard(title = "موارد مصرف", content = it)
}
drugDetail.mechanism?.let {
InfoSectionCard(title = "مکانیسم اثر", content = it)
}
drugDetail.pharmacokinetics?.let {
InfoSectionCard(title = "فارماکوکینتیک", content = it)
}
drugDetail.contraindications?.let {
InfoSectionCard(title = "موارد منع مصرف", content = it)
}
drugDetail.sideEffects?.let {
InfoSectionCard(title = "عوارض جانبی", content = it)
}
drugDetail.interactions?.let {
InfoSectionCard(title = "تداخلات دارویی", content = it)
}
drugDetail.warnings?.let {
InfoSectionCard(title = "هشدارها", content = it)
}
drugDetail.recommendations?.let {
InfoSectionCard(title = "توصیه‌های دارویی", content = it)
}
// اگر هیچ اطلاعات تخصصی وجود نداشت
if (drugDetail.usage == null &&
drugDetail.mechanism == null &&
drugDetail.contraindications == null &&
drugDetail.sideEffects == null &&
drugDetail.warnings == null
) {
DetailEmptyState(
text = if (drugDetail.isGeneric)
"اطلاعات تخصصی برای این دارو ثبت نشده است"
else
"اطلاعات تخصصی برای این برند ثبت نشده است.\nبرای مشاهده اطلاعات کامل، به صفحه داروی ژنریک مراجعه کنید."
)
}
}
}
/** تب «اشکال دارویی». */
@Composable
internal fun DosageFormsTabContent(drugDetail: DrugDetail) {
val dime = LocalDime.current
val dosageForms = drugDetail.dosageForms
if (dosageForms.isEmpty()) {
DetailEmptyState(
text = if (drugDetail.isGeneric)
"اشکال دارویی برای این دارو ثبت نشده است"
else
"سایر اشکال دارویی این برند ثبت نشده است"
)
} else {
Column(verticalArrangement = Arrangement.spacedBy(dime.sm)) {
// اگر صفحه برند است، عنوان متفاوت نشان بده
if (!drugDetail.isGeneric) {
SectionHeader(
title = "سایر محصولات این برند",
modifier = Modifier.padding(bottom = dime.xs)
)
}
dosageForms.forEach { form ->
DetailCard {
Text(
text = form.persianName,
style = MaterialTheme.typography.titleSmall
)
if (form.englishName.isNotBlank()) {
Spacer(modifier = Modifier.height(dime.xxs))
Text(
text = form.englishName,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
// نشانگرهای خطر
if (form.isHighRisk || form.isVital) {
Spacer(modifier = Modifier.height(dime.sm))
Row(horizontalArrangement = Arrangement.spacedBy(dime.xs)) {
if (form.isHighRisk) {
TagChip(
text = "پرخطر",
container = MaterialTheme.colorScheme.errorContainer,
onContainer = MaterialTheme.colorScheme.onErrorContainer
)
}
if (form.isVital) {
TagChip(
text = "حیاتی",
container = MaterialTheme.colorScheme.primaryContainer,
onContainer = MaterialTheme.colorScheme.onPrimaryContainer
)
}
}
}
}
}
}
}
}
/** تب «اسامی تجاری». */
@Composable
internal fun BrandNamesTabContent(drugDetail: DrugDetail) {
val dime = LocalDime.current
if (drugDetail.isGeneric && drugDetail.brandNames.isEmpty()) {
DetailEmptyState(text = "اسامی تجاری برای این دارو ثبت نشده است")
} else if (drugDetail.isGeneric && drugDetail.brandNames.isNotEmpty()) {
Column(verticalArrangement = Arrangement.spacedBy(dime.sm)) {
drugDetail.brandNames.forEach { brand ->
DetailCard {
Text(
text = brand.persianName,
style = MaterialTheme.typography.titleSmall
)
brand.manufacturer?.let {
Spacer(modifier = Modifier.height(dime.xs))
LabeledRow(label = "تولید کننده", value = it)
}
brand.importer?.let {
Spacer(modifier = Modifier.height(dime.xxs))
LabeledRow(label = "وارد کننده", value = it)
}
}
}
}
} else {
// صفحه برند است - اسامی تجاری برای برند معنی ندارد
DetailEmptyState(
text = "این صفحه مربوط به یک محصول تجاری است. برای مشاهده اسامی تجاری سایر برندها، به صفحه داروی ژنریک مراجعه کنید."
)
}
}
@@ -1,24 +1,14 @@
package com.approagency.pharmacy.presentation.screens package com.approagency.pharmacy.presentation.screens
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize 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.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Info
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@@ -38,24 +28,22 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController import androidx.navigation.NavController
import com.approagency.pharmacy.domain.model.DrugDetail
import com.approagency.pharmacy.presentation.common.ErrorState import com.approagency.pharmacy.presentation.common.ErrorState
import com.approagency.pharmacy.presentation.common.Loading import com.approagency.pharmacy.presentation.common.Loading
import com.approagency.pharmacy.presentation.components.drugdetail.BrandNamesTabContent
import com.approagency.pharmacy.presentation.components.drugdetail.DosageFormsTabContent
import com.approagency.pharmacy.presentation.components.drugdetail.GeneralInfoTabContent
import com.approagency.pharmacy.presentation.components.drugdetail.ManufacturerInfoCard
import com.approagency.pharmacy.presentation.components.drugdetail.SpecializedInfoTabContent
import com.approagency.pharmacy.presentation.viewModel.DrugDetailViewModel import com.approagency.pharmacy.presentation.viewModel.DrugDetailViewModel
import com.approagency.pharmacy.presentation.viewModel.DrugDetailYabState import com.approagency.pharmacy.presentation.viewModel.DrugDetailYabState
import com.vada.caller.ui.theme.LocalDime import com.vada.caller.ui.theme.LocalDime
import com.vada.caller.ui.theme.dime
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -140,7 +128,7 @@ fun DrugDetailScreen(
val drugDetail = state.drugDetail val drugDetail = state.drugDetail
Column(modifier = Modifier.fillMaxSize()) { Column(modifier = Modifier.fillMaxSize()) {
// Manufacturer Info Card (برای صفحات برند) // کارت سازنده (برای صفحات برند)
if (!drugDetail.isGeneric && drugDetail.manufacturer != null) { if (!drugDetail.isGeneric && drugDetail.manufacturer != null) {
ManufacturerInfoCard( ManufacturerInfoCard(
manufacturer = drugDetail.manufacturer!!, manufacturer = drugDetail.manufacturer!!,
@@ -152,7 +140,6 @@ fun DrugDetailScreen(
) )
} }
// Tab Row
TabRow( TabRow(
selectedTabIndex = selectedTab, selectedTabIndex = selectedTab,
containerColor = MaterialTheme.colorScheme.surface, containerColor = MaterialTheme.colorScheme.surface,
@@ -174,7 +161,6 @@ fun DrugDetailScreen(
} }
} }
// Tab Content
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -183,21 +169,10 @@ fun DrugDetailScreen(
verticalArrangement = Arrangement.spacedBy(dime.md) verticalArrangement = Arrangement.spacedBy(dime.md)
) { ) {
when (selectedTab) { when (selectedTab) {
0 -> { 0 -> item { GeneralInfoTabContent(drugDetail) }
item { GeneralInfoTabContent(drugDetail) } 1 -> item { SpecializedInfoTabContent(drugDetail) }
} 2 -> item { DosageFormsTabContent(drugDetail) }
3 -> item { BrandNamesTabContent(drugDetail) }
1 -> {
item { SpecializedInfoTabContent(drugDetail) }
}
2 -> {
item { DosageFormsTabContent(drugDetail) }
}
3 -> {
item { BrandNamesTabContent(drugDetail) }
}
} }
} }
} }
@@ -219,505 +194,3 @@ fun DrugDetailScreen(
} }
} }
} }
@Composable
fun ManufacturerInfoCard(
manufacturer: String,
genericInfo: com.approagency.pharmacy.domain.model.GenericInfo?,
modifier: Modifier = Modifier
) {
val dime = LocalDime.current
val onContainer = MaterialTheme.colorScheme.onSecondaryContainer
Column(
modifier = modifier
.fillMaxWidth()
.clip(MaterialTheme.shapes.large)
.background(MaterialTheme.colorScheme.secondaryContainer)
.padding(dime.md),
verticalArrangement = Arrangement.spacedBy(dime.sm)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(dime.sm)
) {
Icon(
imageVector = Icons.Default.Info,
contentDescription = null,
modifier = Modifier.size(18.dp),
tint = onContainer
)
Text(
text = "سازنده:",
style = MaterialTheme.typography.labelLarge,
color = onContainer
)
Text(
text = manufacturer,
style = MaterialTheme.typography.bodyMedium,
color = onContainer
)
}
if (genericInfo != null) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(dime.sm)
) {
Icon(
imageVector = Icons.Default.Info,
contentDescription = null,
modifier = Modifier.size(18.dp),
tint = onContainer
)
Text(
text = "ماده موثره:",
style = MaterialTheme.typography.labelLarge,
color = onContainer
)
Text(
text = genericInfo.persianName,
style = MaterialTheme.typography.bodyMedium,
color = onContainer
)
}
}
}
}
// آپدیت GeneralInfoTabContent برای صفحات Generic
@Composable
fun GeneralInfoTabContent(drugDetail: DrugDetail) {
val dime = LocalDime.current
Column(verticalArrangement = Arrangement.spacedBy(dime.md)) {
if (drugDetail.isGeneric) {
// ========== صفحات ژنریک (Generic) ==========
// اگر generalInfo وجود دارد (از EtelaatOmomiVaTakhasosi یا EtelaatOmomi)
if (!drugDetail.generalInfo.isNullOrBlank()) {
InfoSectionCard(
title = "اطلاعات عمومی",
content = drugDetail.generalInfo
)
}
// اگر specializedInfo وجود دارد (از EtelaatTakhasosiContent)
else if (!drugDetail.specializedInfo.isNullOrBlank()) {
InfoSectionCard(
title = "اطلاعات تخصصی",
content = drugDetail.specializedInfo
)
}
// در غیر این صورت، اطلاعات پراکنده را نمایش بده
else {
// Categories Card
if (drugDetail.drugClass != null || drugDetail.therapeuticClass != null) {
DetailCard {
Column(verticalArrangement = Arrangement.spacedBy(dime.xs)) {
drugDetail.drugClass?.let {
LabeledRow(label = "طبقه بندی مارتیندل", value = it)
}
drugDetail.therapeuticClass?.let {
LabeledRow(label = "طبقه درمانی", value = it)
}
}
}
}
// Pregnancy Card
if (drugDetail.pregnancyCategory != null || drugDetail.pregnancyDescription != null) {
val onContainer = MaterialTheme.colorScheme.onTertiaryContainer
Column(
modifier = Modifier
.fillMaxWidth()
.clip(MaterialTheme.shapes.large)
.background(MaterialTheme.colorScheme.tertiaryContainer)
.padding(dime.md),
verticalArrangement = Arrangement.spacedBy(dime.xs)
) {
Text(
text = "مصرف در بارداری",
style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.Bold,
color = onContainer
)
drugDetail.pregnancyCategory?.let {
Text(
text = "گروه: $it",
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Medium,
color = onContainer
)
}
drugDetail.pregnancyDescription?.let {
Text(
text = it,
style = MaterialTheme.typography.bodySmall,
lineHeight = 18.sp,
color = onContainer.copy(alpha = 0.9f)
)
}
}
}
// موارد مصرف
drugDetail.usage?.let {
InfoSectionCard(title = "موارد مصرف", content = it)
}
// موارد منع مصرف
drugDetail.contraindications?.let {
InfoSectionCard(title = "موارد منع مصرف", content = it)
}
// عوارض جانبی
drugDetail.sideEffects?.let {
InfoSectionCard(title = "عوارض جانبی", content = it)
}
// هشدارها
drugDetail.warnings?.let {
InfoSectionCard(title = "هشدارها", content = it)
}
// توصیه‌های دارویی
drugDetail.recommendations?.let {
InfoSectionCard(title = "توصیه‌های دارویی", content = it)
}
}
// اگر هیچ اطلاعاتی وجود نداشت
if (drugDetail.drugClass == null &&
drugDetail.therapeuticClass == null &&
drugDetail.pregnancyCategory == null &&
drugDetail.usage == null &&
drugDetail.contraindications == null &&
drugDetail.sideEffects == null &&
drugDetail.warnings == null &&
drugDetail.generalInfo.isNullOrBlank() &&
drugDetail.specializedInfo.isNullOrBlank()
) {
DetailEmptyState(text = "اطلاعات عمومی برای این دارو ثبت نشده است")
}
} else {
// ========== صفحات برند (Brand) ==========
// نمایش اطلاعات تخصصی برند که در بخش brandAttrPersDesc است
drugDetail.usage?.let {
InfoSectionCard(title = "موارد مصرف", content = it)
}
drugDetail.contraindications?.let {
InfoSectionCard(title = "موارد منع مصرف", content = it)
}
drugDetail.sideEffects?.let {
InfoSectionCard(title = "عوارض جانبی", content = it)
}
drugDetail.warnings?.let {
InfoSectionCard(title = "هشدارها", content = it)
}
drugDetail.recommendations?.let {
InfoSectionCard(title = "توصیه‌های دارویی", content = it)
}
if (drugDetail.usage == null &&
drugDetail.contraindications == null &&
drugDetail.sideEffects == null &&
drugDetail.warnings == null
) {
DetailEmptyState(
text = "اطلاعات عمومی برای این برند ثبت نشده است.\nبرای مشاهده اطلاعات کامل، به صفحه داروی ژنریک مراجعه کنید."
)
}
}
}
}
@Composable
fun SpecializedInfoTabContent(drugDetail: DrugDetail) {
val dime = LocalDime.current
Column(verticalArrangement = Arrangement.spacedBy(dime.md)) {
// برای صفحات برند، اطلاعات تخصصی معمولاً در قسمت brandAttrPersDesc است
// که در پارسر ما به صورت usage, contraindications, sideEffects, warnings, recommendations ذخیره شده
drugDetail.usage?.let {
InfoSectionCard(title = "موارد مصرف", content = it)
}
drugDetail.mechanism?.let {
InfoSectionCard(title = "مکانیسم اثر", content = it)
}
drugDetail.pharmacokinetics?.let {
InfoSectionCard(title = "فارماکوکینتیک", content = it)
}
drugDetail.contraindications?.let {
InfoSectionCard(title = "موارد منع مصرف", content = it)
}
drugDetail.sideEffects?.let {
InfoSectionCard(title = "عوارض جانبی", content = it)
}
drugDetail.interactions?.let {
InfoSectionCard(title = "تداخلات دارویی", content = it)
}
drugDetail.warnings?.let {
InfoSectionCard(title = "هشدارها", content = it)
}
drugDetail.recommendations?.let {
InfoSectionCard(title = "توصیه‌های دارویی", content = it)
}
// اگر هیچ اطلاعات تخصصی وجود نداشت
if (drugDetail.usage == null &&
drugDetail.mechanism == null &&
drugDetail.contraindications == null &&
drugDetail.sideEffects == null &&
drugDetail.warnings == null
) {
DetailEmptyState(
text = if (drugDetail.isGeneric)
"اطلاعات تخصصی برای این دارو ثبت نشده است"
else
"اطلاعات تخصصی برای این برند ثبت نشده است.\nبرای مشاهده اطلاعات کامل، به صفحه داروی ژنریک مراجعه کنید."
)
}
}
}
@Composable
fun DosageFormsTabContent(drugDetail: DrugDetail) {
val dime = LocalDime.current
val dosageForms = drugDetail.dosageForms
if (dosageForms.isEmpty()) {
DetailEmptyState(
text = if (drugDetail.isGeneric)
"اشکال دارویی برای این دارو ثبت نشده است"
else
"سایر اشکال دارویی این برند ثبت نشده است"
)
} else {
Column(verticalArrangement = Arrangement.spacedBy(dime.sm)) {
// اگر صفحه برند است، عنوان متفاوت نشان بده
if (!drugDetail.isGeneric) {
SectionHeader(
title = "سایر محصولات این برند",
modifier = Modifier.padding(bottom = dime.xs)
)
}
dosageForms.forEach { form ->
DetailCard {
Text(
text = form.persianName,
style = MaterialTheme.typography.titleSmall
)
if (form.englishName.isNotBlank()) {
Spacer(modifier = Modifier.height(dime.xxs))
Text(
text = form.englishName,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
// نشانگرهای خطر
if (form.isHighRisk || form.isVital) {
Spacer(modifier = Modifier.height(dime.sm))
Row(horizontalArrangement = Arrangement.spacedBy(dime.xs)) {
if (form.isHighRisk) {
TagChip(
text = "پرخطر",
container = MaterialTheme.colorScheme.errorContainer,
onContainer = MaterialTheme.colorScheme.onErrorContainer
)
}
if (form.isVital) {
TagChip(
text = "حیاتی",
container = MaterialTheme.colorScheme.primaryContainer,
onContainer = MaterialTheme.colorScheme.onPrimaryContainer
)
}
}
}
}
}
}
}
}
@Composable
fun BrandNamesTabContent(drugDetail: DrugDetail) {
val dime = LocalDime.current
if (drugDetail.isGeneric && drugDetail.brandNames.isEmpty()) {
DetailEmptyState(text = "اسامی تجاری برای این دارو ثبت نشده است")
} else if (drugDetail.isGeneric && drugDetail.brandNames.isNotEmpty()) {
Column(verticalArrangement = Arrangement.spacedBy(dime.sm)) {
drugDetail.brandNames.forEach { brand ->
DetailCard {
Text(
text = brand.persianName,
style = MaterialTheme.typography.titleSmall
)
brand.manufacturer?.let {
Spacer(modifier = Modifier.height(dime.xs))
LabeledRow(label = "تولید کننده", value = it)
}
brand.importer?.let {
Spacer(modifier = Modifier.height(dime.xxs))
LabeledRow(label = "وارد کننده", value = it)
}
}
}
}
} else {
// صفحه برند است - اسامی تجاری برای برند معنی ندارد
DetailEmptyState(
text = "این صفحه مربوط به یک محصول تجاری است. برای مشاهده اسامی تجاری سایر برندها، به صفحه داروی ژنریک مراجعه کنید."
)
}
}
@Composable
fun InfoSectionCard(
title: String,
content: String,
modifier: Modifier = Modifier
) {
val dime = LocalDime.current
DetailCard(modifier = modifier) {
SectionHeader(title = title)
Spacer(modifier = Modifier.height(dime.sm))
Text(
text = content,
style = MaterialTheme.typography.bodyMedium,
lineHeight = 22.sp,
textAlign = TextAlign.Justify,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
/**
* کارت پایه‌ی صفحه‌ی جزئیات — ظاهر یکدست با بقیه‌ی برنامه:
* سطح روشن + قاب نازک هم‌رنگ تم، بدون سایه‌ی سنگین.
*/
@Composable
private fun DetailCard(
modifier: Modifier = Modifier,
content: @Composable ColumnScope.() -> Unit
) {
val shape = MaterialTheme.shapes.large
Column(
modifier = modifier
.fillMaxWidth()
.clip(shape)
.background(MaterialTheme.colorScheme.surfaceContainerLowest)
.border(
width = 1.dp,
color = MaterialTheme.colorScheme.outlineVariant,
shape = shape
)
.padding(MaterialTheme.dime.md),
content = content
)
}
/** سربرگ بخش با نوار تأکید کوچک کنار عنوان. */
@Composable
private fun SectionHeader(
title: String,
modifier: Modifier = Modifier
) {
val dime = LocalDime.current
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(dime.sm)
) {
Box(
modifier = Modifier
.size(width = 4.dp, height = 18.dp)
.clip(RoundedCornerShape(dime.xxs))
.background(MaterialTheme.colorScheme.primary)
)
Text(
text = title,
style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
)
}
}
/** حالت «اطلاعاتی ثبت نشده» با آیکن، یکدست در همه‌ی تب‌ها. */
@Composable
private fun DetailEmptyState(
text: String,
modifier: Modifier = Modifier
) {
val dime = LocalDime.current
Column(
modifier = modifier
.fillMaxWidth()
.padding(vertical = dime.xxl, horizontal = dime.lg),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(dime.sm)
) {
Icon(
imageVector = Icons.Default.Info,
contentDescription = null,
modifier = Modifier.size(40.dp),
tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
)
Text(
text = text,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center
)
}
}
/** ردیف «برچسب: مقدار» با تایپوگرافی یکدست. */
@Composable
private fun LabeledRow(
label: String,
value: String,
modifier: Modifier = Modifier
) {
Row(modifier = modifier) {
Text(
text = "$label: ",
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onSurface
)
Text(
text = value,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.weight(1f)
)
}
}
/** برچسب کوچک رنگی (مثل «پرخطر» / «حیاتی»). */
@Composable
private fun TagChip(
text: String,
container: Color,
onContainer: Color
) {
Text(
text = text,
style = MaterialTheme.typography.labelSmall,
color = onContainer,
modifier = Modifier
.clip(RoundedCornerShape(MaterialTheme.dime.xs))
.background(container)
.padding(horizontal = MaterialTheme.dime.sm, vertical = MaterialTheme.dime.xxs)
)
}
@@ -37,8 +37,8 @@ import com.approagency.pharmacy.presentation.components.DrugDetailBottomSheet
import com.approagency.pharmacy.presentation.components.PharmacyBottomSheet import com.approagency.pharmacy.presentation.components.PharmacyBottomSheet
import com.approagency.pharmacy.presentation.viewModel.DrugDetailViewModel import com.approagency.pharmacy.presentation.viewModel.DrugDetailViewModel
import com.approagency.pharmacy.presentation.viewModel.PharmacyViewModel import com.approagency.pharmacy.presentation.viewModel.PharmacyViewModel
import com.approgency.drug.presentation.viewModel.SearchState import com.approagency.pharmacy.presentation.viewModel.SearchState
import com.approgency.drug.presentation.viewModel.SearchViewModel import com.approagency.pharmacy.presentation.viewModel.SearchViewModel
import com.vada.caller.ui.theme.LocalDime import com.vada.caller.ui.theme.LocalDime
import com.vada.caller.ui.theme.dime import com.vada.caller.ui.theme.dime
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@@ -1,4 +1,4 @@
package com.approgency.drug.presentation.viewModel package com.approagency.pharmacy.presentation.viewModel
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf