feat: add pharmacy feature
This commit is contained in:
@@ -32,4 +32,20 @@ interface DarooyabApiService {
|
||||
suspend fun getDrugDetail(
|
||||
@Path(value = "detailUrl", encoded = true) detailUrl: String
|
||||
): String
|
||||
|
||||
|
||||
@POST("Home/partialPatientReferralList")
|
||||
@FormUrlEncoded
|
||||
@Headers(
|
||||
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36",
|
||||
"X-Requested-With: XMLHttpRequest",
|
||||
"Accept: */*",
|
||||
"Accept-Language: en-US,en;q=0.9,fa;q=0.8"
|
||||
)
|
||||
suspend fun getPharmacies(
|
||||
@Field("brandIrc") brandIrc: String,
|
||||
@Field("genericDrugId") genericDrugId: String,
|
||||
@Field("provId") provId: String,
|
||||
@Field("cityId") cityId: String
|
||||
): String // returns HTML
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.approagency.drug.data.remote
|
||||
import com.approagency.drug.domain.model.PharmacyItem
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
object PharmacyHtmlParser {
|
||||
|
||||
fun parse(html: String): List<PharmacyItem> {
|
||||
val items = mutableListOf<PharmacyItem>()
|
||||
val doc = Jsoup.parse(html)
|
||||
val rows = doc.select("table#TBL_patientReferral tbody tr")
|
||||
|
||||
for (row in rows) {
|
||||
val cells = row.select("td")
|
||||
if (cells.size < 3) continue
|
||||
|
||||
// ستون 0: اطلاعات برند
|
||||
val brandCell = cells[0]
|
||||
val brandLink = brandCell.select("a").first()
|
||||
val brandName = brandLink?.text()?.trim() ?: ""
|
||||
val brandUrl = brandLink?.attr("href")?.trim() ?: ""
|
||||
val brandId = extractIdFromUrl(brandUrl, "/B-(\\d+)/?")
|
||||
|
||||
// ستون 1: اطلاعات داروخانه
|
||||
val pharmacyCell = cells[1]
|
||||
val pharmacyLink = pharmacyCell.select("a").first()
|
||||
val pharmacyName = pharmacyLink?.text()?.trim() ?: ""
|
||||
val pharmacyUrl = pharmacyLink?.attr("href")?.trim() ?: ""
|
||||
val pharmacyId = extractIdFromUrl(pharmacyUrl, "/ph-(\\d+)/?")
|
||||
|
||||
// ستون 2: موقعیت
|
||||
val locationText = cells[2].text().trim()
|
||||
val parts = locationText.split("شهر")
|
||||
val province = parts.getOrNull(0)?.replace("استان", "")?.trim() ?: ""
|
||||
val city = parts.getOrNull(1)?.trim() ?: ""
|
||||
|
||||
if (brandName.isNotBlank() && pharmacyName.isNotBlank()) {
|
||||
items.add(
|
||||
PharmacyItem(
|
||||
brandName = brandName,
|
||||
brandUrl = brandUrl,
|
||||
brandId = brandId,
|
||||
pharmacyName = pharmacyName,
|
||||
pharmacyUrl = pharmacyUrl,
|
||||
pharmacyId = pharmacyId,
|
||||
province = province,
|
||||
city = city
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
private fun extractIdFromUrl(url: String, pattern: String): String {
|
||||
val regex = pattern.toRegex()
|
||||
return regex.find(url)?.groupValues?.get(1) ?: ""
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,13 @@ import com.approagency.drug.data.remote.DarooyabApiService
|
||||
import com.approagency.drug.data.remote.DrugApiService
|
||||
import com.approagency.drug.data.remote.DrugDetailParser
|
||||
import com.approagency.drug.data.remote.DrugHtmlParser
|
||||
import com.approagency.drug.data.remote.PharmacyHtmlParser
|
||||
import com.approagency.drug.domain.model.DaroYabParams
|
||||
import com.approagency.drug.domain.model.DaroYabSearchResult
|
||||
import com.approagency.drug.domain.model.DrugDetail
|
||||
import com.approagency.drug.domain.model.DrugSearchParams
|
||||
import com.approagency.drug.domain.model.DrugSearchResult
|
||||
import com.approagency.drug.domain.model.PharmacyItem
|
||||
import com.approagency.drug.domain.repository.DrugRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -89,4 +91,23 @@ class DrugRepositoryImpl(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun searchPharmacies(
|
||||
genericDrugId: String,
|
||||
provId: String
|
||||
): List<PharmacyItem> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val html = darooyabApiService.getPharmacies(
|
||||
brandIrc = "0",
|
||||
genericDrugId = genericDrugId,
|
||||
provId = provId,
|
||||
cityId = "0"
|
||||
)
|
||||
PharmacyHtmlParser.parse(html)
|
||||
} catch (e: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import com.approagency.drug.domain.usecase.DrugDetailYabUseCase
|
||||
import com.approagency.drug.domain.usecase.GetDarmanUseCase
|
||||
import com.approagency.drug.domain.usecase.GetDrugDetailUseCase
|
||||
import com.approagency.drug.domain.usecase.GetDrugSearchUseCase
|
||||
import com.approagency.drug.domain.usecase.GetPharmaciesUseCase
|
||||
import com.approagency.drug.domain.usecase.GetTestGroupUseCase
|
||||
import com.approagency.drug.domain.usecase.GetTestItemByGroupId
|
||||
import com.approagency.drug.domain.usecase.SearchDrugsYabUseCase
|
||||
@@ -21,6 +22,7 @@ import com.approagency.drug.domain.usecase.SearchTestsUseCase
|
||||
import com.approagency.drug.presentation.viewModel.DrugDetailViewModel
|
||||
import com.approagency.drug.presentation.viewModel.HomeViewModel
|
||||
import com.approagency.drug.presentation.viewModel.LabViewModel
|
||||
import com.approagency.drug.presentation.viewModel.PharmacyViewModel
|
||||
import com.approagency.drug.utils.Config
|
||||
import com.approgency.drug.presentation.viewModel.SearchViewModel
|
||||
import okhttp3.OkHttpClient
|
||||
@@ -119,6 +121,10 @@ val appModule= module {
|
||||
GetTestGroupUseCase(get())
|
||||
}
|
||||
|
||||
single {
|
||||
GetPharmaciesUseCase(get())
|
||||
}
|
||||
|
||||
single {
|
||||
GetTestItemByGroupId(get())
|
||||
}
|
||||
@@ -142,6 +148,9 @@ val appModule= module {
|
||||
viewModel {
|
||||
SearchViewModel(get())
|
||||
}
|
||||
viewModel {
|
||||
PharmacyViewModel(get())
|
||||
}
|
||||
|
||||
viewModel {
|
||||
LabViewModel(get() , get() , get())
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.approagency.drug.domain.model
|
||||
|
||||
data class PharmacySearchRequest(
|
||||
val brandIrc: String = "0", // برای صفحات برند
|
||||
val genericDrugId: String, // برای صفحات ژنریک
|
||||
val provId: String, // ID استان (0 برای همه)
|
||||
val cityId: String = "0" // ID شهر (0 برای همه)
|
||||
)
|
||||
|
||||
data class PharmacyItem(
|
||||
val brandName: String, // نام برند دارو
|
||||
val brandUrl: String, // لینک صفحه برند
|
||||
val brandId: String, // IRC برند
|
||||
|
||||
val pharmacyName: String, // نام داروخانه
|
||||
val pharmacyUrl: String, // لینک داروخانه
|
||||
val pharmacyId: String, // ID داروخانه
|
||||
|
||||
val province: String, // نام استان
|
||||
val city: String // نام شهر
|
||||
)
|
||||
@@ -8,6 +8,7 @@ import com.approagency.drug.domain.model.DaroYabSearchResult
|
||||
import com.approagency.drug.domain.model.DrugDetail
|
||||
import com.approagency.drug.domain.model.DrugSearchParams
|
||||
import com.approagency.drug.domain.model.DrugSearchResult
|
||||
import com.approagency.drug.domain.model.PharmacyItem
|
||||
|
||||
interface DrugRepository {
|
||||
suspend fun searchDrug(params: DrugSearchParams): Result<DrugListResponse>
|
||||
@@ -15,5 +16,6 @@ interface DrugRepository {
|
||||
suspend fun getGorohDaroei(): Result<DarmanModel>
|
||||
suspend fun searchDrugs(params:DaroYabParams):Result<DaroYabSearchResult>
|
||||
suspend fun getDrugDetailFromYab(detailUrl: String):Result<DrugDetail>
|
||||
suspend fun searchPharmacies(genericDrugId: String,provId: String): List<PharmacyItem>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.approagency.drug.domain.usecase
|
||||
|
||||
import com.approagency.drug.domain.model.PharmacyItem
|
||||
import com.approagency.drug.domain.repository.DrugRepository
|
||||
|
||||
class GetPharmaciesUseCase(
|
||||
private val repository: DrugRepository
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
genericDrugId: String,
|
||||
provinceId: String
|
||||
): List<PharmacyItem> {
|
||||
return repository.searchPharmacies(genericDrugId, provinceId)
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.navigation
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.navArgument
|
||||
import com.approagency.drug.presentation.screens.DrugDetailScreen
|
||||
@@ -36,7 +35,7 @@ fun AppNavGraph() {
|
||||
|
||||
composable("search") {
|
||||
MainContainer(navController) {
|
||||
SearchScreen(navController)
|
||||
SearchScreen(navController,)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ import com.vada.caller.ui.theme.dime
|
||||
fun DaroYabSearchResult(
|
||||
drug: DrugSearchResult,
|
||||
onClickDetail: (DrugSearchResult) -> Unit,
|
||||
onClickDrugStore:() ->Unit,
|
||||
onClickDrugStore:(DrugSearchResult) ->Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val dime = LocalDime.current
|
||||
@@ -71,7 +71,7 @@ fun DaroYabSearchResult(
|
||||
TextButton(onClick = {onClickDetail(drug)} , content = {
|
||||
Text("اطلاعات دارویی")
|
||||
})
|
||||
TextButton(onClick = {onClickDetail(drug)} , content = {
|
||||
TextButton(onClick = {onClickDrugStore(drug)} , content = {
|
||||
Text("موجودی داروخانه")
|
||||
})
|
||||
}
|
||||
|
||||
+269
@@ -0,0 +1,269 @@
|
||||
package com.approagency.drug.presentation.components
|
||||
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.approagency.drug.domain.model.PharmacyItem
|
||||
import com.approagency.drug.presentation.common.CustomModalBottomSheet
|
||||
import com.approagency.drug.presentation.viewModel.PharmacyState
|
||||
import com.approagency.drug.presentation.viewModel.PharmacyViewModel
|
||||
import com.vada.caller.ui.theme.LocalDime
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun PharmacyBottomSheet(
|
||||
viewModel: PharmacyViewModel,
|
||||
genericDrugId: String,
|
||||
isOpen: Boolean,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val sheetState = rememberModalBottomSheetState(
|
||||
skipPartiallyExpanded = true,
|
||||
confirmValueChange = { true }
|
||||
)
|
||||
|
||||
// لیست استانها (همان لیست HTML)
|
||||
val provinces = listOf(
|
||||
Province("0", "همه استان ها"),
|
||||
Province("1", "اصفهان"),
|
||||
Province("40", "تهران"),
|
||||
Province("137", "آذربایجان شرقی"),
|
||||
Province("138", "آذربایجان غربی"),
|
||||
Province("139", "اردبیل"),
|
||||
Province("140", "البرز"),
|
||||
Province("141", "ایلام"),
|
||||
Province("142", "بوشهر"),
|
||||
Province("143", "چهارمحال و بختیاری"),
|
||||
Province("144", "خراسان جنوبی"),
|
||||
Province("145", "خراسان رضوی"),
|
||||
Province("146", "خراسان شمالی"),
|
||||
Province("147", "خوزستان"),
|
||||
Province("148", "زنجان"),
|
||||
Province("149", "سمنان"),
|
||||
Province("150", "سیستان و بلوچستان"),
|
||||
Province("151", "فارس"),
|
||||
Province("152", "قزوین"),
|
||||
Province("153", "قم"),
|
||||
Province("154", "کردستان"),
|
||||
Province("155", "کرمان"),
|
||||
Province("156", "کرمانشاه"),
|
||||
Province("157", "کهگیلویه و بویراحمد"),
|
||||
Province("158", "گلستان"),
|
||||
Province("159", "گیلان"),
|
||||
Province("160", "لرستان"),
|
||||
Province("161", "مازندران"),
|
||||
Province("162", "مرکزی"),
|
||||
Province("163", "هرمزگان"),
|
||||
Province("164", "همدان"),
|
||||
Province("165", "یزد")
|
||||
)
|
||||
|
||||
var selectedProvinceId by remember { mutableStateOf("40") } // پیشفرض تهران
|
||||
var showProvinceDropdown by remember { mutableStateOf(false) }
|
||||
|
||||
val pharmacyState by viewModel.state.collectAsState()
|
||||
|
||||
// جستجوی اولیه با استان تهران
|
||||
LaunchedEffect(Unit) {
|
||||
if (genericDrugId.isNotBlank()) {
|
||||
viewModel.search(genericDrugId, "40")
|
||||
}
|
||||
}
|
||||
|
||||
if (isOpen) {
|
||||
CustomModalBottomSheet(
|
||||
sheetState = sheetState,
|
||||
onDismiss = onDismiss,
|
||||
scope = scope
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
// عنوان
|
||||
Text(
|
||||
text = "جستجوی دارو در داروخانهها",
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = MaterialTheme.typography.titleMedium.fontSize
|
||||
)
|
||||
|
||||
// انتخاب استان
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = showProvinceDropdown,
|
||||
onExpandedChange = { showProvinceDropdown = it }
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = provinces.find { it.id == selectedProvinceId }?.name ?: "انتخاب استان",
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = showProvinceDropdown) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.menuAnchor(),
|
||||
shape = MaterialTheme.shapes.small
|
||||
)
|
||||
|
||||
ExposedDropdownMenu(
|
||||
expanded = showProvinceDropdown,
|
||||
onDismissRequest = { showProvinceDropdown = false }
|
||||
) {
|
||||
provinces.forEach { province ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(province.name) },
|
||||
onClick = {
|
||||
selectedProvinceId = province.id
|
||||
showProvinceDropdown = false
|
||||
// جستجو با استان جدید
|
||||
viewModel.search(genericDrugId, province.id)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// نمایش نتایج
|
||||
when (val state = pharmacyState) {
|
||||
PharmacyState.Loading -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(200.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(32.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = "در حال جستجو...",
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is PharmacyState.Success -> {
|
||||
if (state.items.isEmpty()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(200.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "هیچ داروخانهای یافت نشد",
|
||||
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.heightIn(max = 400.dp)
|
||||
) {
|
||||
items(state.items) { pharmacy ->
|
||||
PharmacyResultItem(pharmacy = pharmacy)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = "تعداد کل: ${state.items.size} داروخانه",
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f),
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is PharmacyState.Error -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(200.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text(
|
||||
text = state.message,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
fontSize = MaterialTheme.typography.bodyMedium.fontSize
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
TextButton(
|
||||
onClick = { viewModel.retry() }
|
||||
) {
|
||||
Text("تلاش مجدد")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PharmacyState.Idle -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PharmacyResultItem(
|
||||
pharmacy: PharmacyItem,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val dime = LocalDime.current
|
||||
|
||||
Card(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(dime.md),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
// نام برند
|
||||
Text(
|
||||
text = pharmacy.brandName,
|
||||
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
|
||||
// نام داروخانه
|
||||
Text(
|
||||
text = pharmacy.pharmacyName,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = MaterialTheme.typography.bodyLarge.fontSize
|
||||
)
|
||||
|
||||
// موقعیت
|
||||
Text(
|
||||
text = "${pharmacy.province} - ${pharmacy.city}",
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class Province(
|
||||
val id: String,
|
||||
val name: String
|
||||
)
|
||||
@@ -2,19 +2,14 @@ package com.approagency.drug.presentation.screens
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.graphics.drawable.Icon
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Build
|
||||
@@ -24,12 +19,10 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarDefaults
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.NavigationBarItemDefaults
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.ScaffoldDefaults
|
||||
import androidx.compose.material3.SegmentedButtonDefaults.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
@@ -41,7 +34,6 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@@ -123,7 +115,10 @@ fun RootScreen(navHostController: NavHostController, modifier: Modifier){
|
||||
padding -> when (seletedTab) {
|
||||
0 -> HomeScreen( navController = navHostController,Modifier.padding(padding).consumeWindowInsets(padding))
|
||||
1 -> LabScreen(Modifier.padding(padding))
|
||||
2 -> SearchScreen(navHostController,modifier = Modifier.padding(padding).consumeWindowInsets(padding))
|
||||
2 -> SearchScreen(
|
||||
navHostController,
|
||||
modifier = Modifier.padding(padding).consumeWindowInsets(padding),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.navigation.NavController
|
||||
import com.approagency.drug.domain.model.DrugSearchResult
|
||||
import com.approagency.drug.navigation.Screen
|
||||
import com.approagency.drug.presentation.common.CustomTextFilled
|
||||
import com.approagency.drug.presentation.common.EmptySearchState
|
||||
@@ -33,7 +34,9 @@ import com.approagency.drug.presentation.common.LoadingMoreIndicator
|
||||
import com.approagency.drug.presentation.common.PrimaryButton
|
||||
import com.approagency.drug.presentation.components.DaroYabSearchResult
|
||||
import com.approagency.drug.presentation.components.DrugDetailBottomSheet
|
||||
import com.approagency.drug.presentation.components.PharmacyBottomSheet
|
||||
import com.approagency.drug.presentation.viewModel.DrugDetailViewModel
|
||||
import com.approagency.drug.presentation.viewModel.PharmacyViewModel
|
||||
import com.approgency.drug.presentation.viewModel.SearchState
|
||||
import com.approgency.drug.presentation.viewModel.SearchViewModel
|
||||
import com.vada.caller.ui.theme.LocalDime
|
||||
@@ -46,7 +49,8 @@ fun SearchScreen(
|
||||
navController: NavController,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: SearchViewModel = koinViewModel(),
|
||||
drugDetailViewModel: DrugDetailViewModel = koinViewModel() // اضافه کنید
|
||||
drugDetailViewModel: DrugDetailViewModel = koinViewModel(),
|
||||
pharmacyViewModel: PharmacyViewModel = koinViewModel(),
|
||||
) {
|
||||
val dime = LocalDime.current
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
@@ -54,6 +58,10 @@ fun SearchScreen(
|
||||
val state by viewModel.searchState.collectAsState()
|
||||
val lazyListState = rememberLazyListState()
|
||||
var selectedDrugUrl by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
var showPharmacyBottomSheet by remember { mutableStateOf(false) }
|
||||
var selectedDrugForPharmacy by remember { mutableStateOf<DrugSearchResult?>(null) }
|
||||
|
||||
// Auto-search for testing (remove in production)
|
||||
// LaunchedEffect(Unit) {
|
||||
// if (searchText.isEmpty()) {
|
||||
@@ -118,6 +126,22 @@ fun SearchScreen(
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(MaterialTheme.dime.md))
|
||||
|
||||
if (showPharmacyBottomSheet && selectedDrugForPharmacy != null) {
|
||||
// استخراج genericDrugId از URL
|
||||
// مثال: /G-799/Fexofenadine -> 799
|
||||
val genericId = extractGenericIdFromUrl(selectedDrugForPharmacy?.detailPageUrl ?: "")
|
||||
|
||||
PharmacyBottomSheet(
|
||||
viewModel = pharmacyViewModel,
|
||||
genericDrugId = genericId,
|
||||
isOpen = showPharmacyBottomSheet,
|
||||
onDismiss = {
|
||||
showPharmacyBottomSheet = false
|
||||
selectedDrugForPharmacy = null
|
||||
}
|
||||
)
|
||||
}
|
||||
// Results area
|
||||
when (val currentState = state) {
|
||||
is SearchState.Loading -> {
|
||||
@@ -148,7 +172,10 @@ fun SearchScreen(
|
||||
)
|
||||
)
|
||||
},
|
||||
onClickDrugStore = {}
|
||||
onClickDrugStore = { selectedDrug ->
|
||||
selectedDrugForPharmacy = selectedDrug
|
||||
showPharmacyBottomSheet = true
|
||||
}
|
||||
)
|
||||
}
|
||||
item {
|
||||
@@ -172,7 +199,10 @@ fun SearchScreen(
|
||||
)
|
||||
)
|
||||
},
|
||||
onClickDrugStore = {}
|
||||
onClickDrugStore = { selectedDrug ->
|
||||
selectedDrugForPharmacy = selectedDrug
|
||||
showPharmacyBottomSheet = true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -206,3 +236,9 @@ fun SearchScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// تابع کمکی برای استخراج ID از URL
|
||||
private fun extractGenericIdFromUrl(url: String): String {
|
||||
val pattern = "/G-(\\d+)/".toRegex()
|
||||
return pattern.find(url)?.groupValues?.get(1) ?: ""
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.approagency.drug.presentation.viewModel
|
||||
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.approagency.drug.domain.model.PharmacyItem
|
||||
import com.approagency.drug.domain.usecase.GetPharmaciesUseCase
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
sealed class PharmacyState {
|
||||
object Idle : PharmacyState()
|
||||
object Loading : PharmacyState()
|
||||
data class Success(val items: List<PharmacyItem>) : PharmacyState()
|
||||
data class Error(val message: String) : PharmacyState()
|
||||
}
|
||||
|
||||
class PharmacyViewModel(
|
||||
private val getPharmaciesUseCase: GetPharmaciesUseCase
|
||||
) : ViewModel() {
|
||||
|
||||
private val _state = MutableStateFlow<PharmacyState>(PharmacyState.Idle)
|
||||
val state: StateFlow<PharmacyState> = _state.asStateFlow()
|
||||
|
||||
private var currentGenericDrugId: String = ""
|
||||
private var currentProvinceId: String = "0"
|
||||
|
||||
fun search(genericDrugId: String, provinceId: String) {
|
||||
if (genericDrugId.isBlank()) return
|
||||
|
||||
currentGenericDrugId = genericDrugId
|
||||
currentProvinceId = provinceId
|
||||
|
||||
viewModelScope.launch {
|
||||
_state.value = PharmacyState.Loading
|
||||
try {
|
||||
val result = getPharmaciesUseCase(genericDrugId, provinceId)
|
||||
_state.value = PharmacyState.Success(result)
|
||||
} catch (e: Exception) {
|
||||
_state.value = PharmacyState.Error(e.message ?: "خطا در دریافت اطلاعات")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun retry() {
|
||||
if (currentGenericDrugId.isNotBlank()) {
|
||||
search(currentGenericDrugId, currentProvinceId)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user