feat: add pharmacy feature

This commit is contained in:
2026-06-04 20:46:54 +03:30
parent e0e4bb4eef
commit 8516405daf
13 changed files with 510 additions and 16 deletions
@@ -32,4 +32,20 @@ interface DarooyabApiService {
suspend fun getDrugDetail( suspend fun getDrugDetail(
@Path(value = "detailUrl", encoded = true) detailUrl: String @Path(value = "detailUrl", encoded = true) detailUrl: String
): 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.DrugApiService
import com.approagency.drug.data.remote.DrugDetailParser import com.approagency.drug.data.remote.DrugDetailParser
import com.approagency.drug.data.remote.DrugHtmlParser 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.DaroYabParams
import com.approagency.drug.domain.model.DaroYabSearchResult import com.approagency.drug.domain.model.DaroYabSearchResult
import com.approagency.drug.domain.model.DrugDetail import com.approagency.drug.domain.model.DrugDetail
import com.approagency.drug.domain.model.DrugSearchParams import com.approagency.drug.domain.model.DrugSearchParams
import com.approagency.drug.domain.model.DrugSearchResult import com.approagency.drug.domain.model.DrugSearchResult
import com.approagency.drug.domain.model.PharmacyItem
import com.approagency.drug.domain.repository.DrugRepository import com.approagency.drug.domain.repository.DrugRepository
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext 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.GetDarmanUseCase
import com.approagency.drug.domain.usecase.GetDrugDetailUseCase import com.approagency.drug.domain.usecase.GetDrugDetailUseCase
import com.approagency.drug.domain.usecase.GetDrugSearchUseCase 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.GetTestGroupUseCase
import com.approagency.drug.domain.usecase.GetTestItemByGroupId import com.approagency.drug.domain.usecase.GetTestItemByGroupId
import com.approagency.drug.domain.usecase.SearchDrugsYabUseCase 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.DrugDetailViewModel
import com.approagency.drug.presentation.viewModel.HomeViewModel import com.approagency.drug.presentation.viewModel.HomeViewModel
import com.approagency.drug.presentation.viewModel.LabViewModel import com.approagency.drug.presentation.viewModel.LabViewModel
import com.approagency.drug.presentation.viewModel.PharmacyViewModel
import com.approagency.drug.utils.Config import com.approagency.drug.utils.Config
import com.approgency.drug.presentation.viewModel.SearchViewModel import com.approgency.drug.presentation.viewModel.SearchViewModel
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@@ -119,6 +121,10 @@ val appModule= module {
GetTestGroupUseCase(get()) GetTestGroupUseCase(get())
} }
single {
GetPharmaciesUseCase(get())
}
single { single {
GetTestItemByGroupId(get()) GetTestItemByGroupId(get())
} }
@@ -142,6 +148,9 @@ val appModule= module {
viewModel { viewModel {
SearchViewModel(get()) SearchViewModel(get())
} }
viewModel {
PharmacyViewModel(get())
}
viewModel { viewModel {
LabViewModel(get() , get() , get()) 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.DrugDetail
import com.approagency.drug.domain.model.DrugSearchParams import com.approagency.drug.domain.model.DrugSearchParams
import com.approagency.drug.domain.model.DrugSearchResult import com.approagency.drug.domain.model.DrugSearchResult
import com.approagency.drug.domain.model.PharmacyItem
interface DrugRepository { interface DrugRepository {
suspend fun searchDrug(params: DrugSearchParams): Result<DrugListResponse> suspend fun searchDrug(params: DrugSearchParams): Result<DrugListResponse>
@@ -15,5 +16,6 @@ interface DrugRepository {
suspend fun getGorohDaroei(): Result<DarmanModel> suspend fun getGorohDaroei(): Result<DarmanModel>
suspend fun searchDrugs(params:DaroYabParams):Result<DaroYabSearchResult> suspend fun searchDrugs(params:DaroYabParams):Result<DaroYabSearchResult>
suspend fun getDrugDetailFromYab(detailUrl: String):Result<DrugDetail> 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.NavType
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.navigation
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument import androidx.navigation.navArgument
import com.approagency.drug.presentation.screens.DrugDetailScreen import com.approagency.drug.presentation.screens.DrugDetailScreen
@@ -36,7 +35,7 @@ fun AppNavGraph() {
composable("search") { composable("search") {
MainContainer(navController) { MainContainer(navController) {
SearchScreen(navController) SearchScreen(navController,)
} }
} }
@@ -27,7 +27,7 @@ import com.vada.caller.ui.theme.dime
fun DaroYabSearchResult( fun DaroYabSearchResult(
drug: DrugSearchResult, drug: DrugSearchResult,
onClickDetail: (DrugSearchResult) -> Unit, onClickDetail: (DrugSearchResult) -> Unit,
onClickDrugStore:() ->Unit, onClickDrugStore:(DrugSearchResult) ->Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val dime = LocalDime.current val dime = LocalDime.current
@@ -71,7 +71,7 @@ fun DaroYabSearchResult(
TextButton(onClick = {onClickDetail(drug)} , content = { TextButton(onClick = {onClickDetail(drug)} , content = {
Text("اطلاعات دارویی") Text("اطلاعات دارویی")
}) })
TextButton(onClick = {onClickDetail(drug)} , content = { TextButton(onClick = {onClickDrugStore(drug)} , content = {
Text("موجودی داروخانه") Text("موجودی داروخانه")
}) })
} }
@@ -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.annotation.SuppressLint
import android.app.Activity 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.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Build 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.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarDefaults
import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults import androidx.compose.material3.NavigationBarItemDefaults
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.ScaffoldDefaults import androidx.compose.material3.ScaffoldDefaults
import androidx.compose.material3.SegmentedButtonDefaults.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
@@ -41,7 +34,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@@ -123,7 +115,10 @@ fun RootScreen(navHostController: NavHostController, modifier: Modifier){
padding -> when (seletedTab) { padding -> when (seletedTab) {
0 -> HomeScreen( navController = navHostController,Modifier.padding(padding).consumeWindowInsets(padding)) 0 -> HomeScreen( navController = navHostController,Modifier.padding(padding).consumeWindowInsets(padding))
1 -> LabScreen(Modifier.padding(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.Modifier
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.navigation.NavController import androidx.navigation.NavController
import com.approagency.drug.domain.model.DrugSearchResult
import com.approagency.drug.navigation.Screen import com.approagency.drug.navigation.Screen
import com.approagency.drug.presentation.common.CustomTextFilled import com.approagency.drug.presentation.common.CustomTextFilled
import com.approagency.drug.presentation.common.EmptySearchState 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.common.PrimaryButton
import com.approagency.drug.presentation.components.DaroYabSearchResult import com.approagency.drug.presentation.components.DaroYabSearchResult
import com.approagency.drug.presentation.components.DrugDetailBottomSheet 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.DrugDetailViewModel
import com.approagency.drug.presentation.viewModel.PharmacyViewModel
import com.approgency.drug.presentation.viewModel.SearchState import com.approgency.drug.presentation.viewModel.SearchState
import com.approgency.drug.presentation.viewModel.SearchViewModel import com.approgency.drug.presentation.viewModel.SearchViewModel
import com.vada.caller.ui.theme.LocalDime import com.vada.caller.ui.theme.LocalDime
@@ -46,7 +49,8 @@ fun SearchScreen(
navController: NavController, navController: NavController,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: SearchViewModel = koinViewModel(), viewModel: SearchViewModel = koinViewModel(),
drugDetailViewModel: DrugDetailViewModel = koinViewModel() // اضافه کنید drugDetailViewModel: DrugDetailViewModel = koinViewModel(),
pharmacyViewModel: PharmacyViewModel = koinViewModel(),
) { ) {
val dime = LocalDime.current val dime = LocalDime.current
val keyboardController = LocalSoftwareKeyboardController.current val keyboardController = LocalSoftwareKeyboardController.current
@@ -54,6 +58,10 @@ fun SearchScreen(
val state by viewModel.searchState.collectAsState() val state by viewModel.searchState.collectAsState()
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
var selectedDrugUrl by remember { mutableStateOf<String?>(null) } 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) // Auto-search for testing (remove in production)
// LaunchedEffect(Unit) { // LaunchedEffect(Unit) {
// if (searchText.isEmpty()) { // if (searchText.isEmpty()) {
@@ -118,6 +126,22 @@ fun SearchScreen(
) )
Spacer(modifier = Modifier.height(MaterialTheme.dime.md)) 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 // Results area
when (val currentState = state) { when (val currentState = state) {
is SearchState.Loading -> { is SearchState.Loading -> {
@@ -148,7 +172,10 @@ fun SearchScreen(
) )
) )
}, },
onClickDrugStore = {} onClickDrugStore = { selectedDrug ->
selectedDrugForPharmacy = selectedDrug
showPharmacyBottomSheet = true
}
) )
} }
item { 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)
}
}
}