diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..9c3d95a
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 8fabff5..8b3f102 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index d7916a5..a4f09e2 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,6 +1,7 @@
+
-
+
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 9661ac7..c8397c9 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/java/com/approagency/drug/data/remote/PharmacyHtmlParser.kt b/app/src/main/java/com/approagency/drug/data/remote/PharmacyHtmlParser.kt
index 011073e..00db616 100644
--- a/app/src/main/java/com/approagency/drug/data/remote/PharmacyHtmlParser.kt
+++ b/app/src/main/java/com/approagency/drug/data/remote/PharmacyHtmlParser.kt
@@ -1,4 +1,5 @@
package com.approagency.drug.data.remote
+
import com.approagency.drug.domain.model.PharmacyItem
import org.jsoup.Jsoup
@@ -7,31 +8,48 @@ object PharmacyHtmlParser {
fun parse(html: String): List {
val items = mutableListOf()
val doc = Jsoup.parse(html)
- val rows = doc.select("table#TBL_patientReferral tbody tr")
+ val table = doc.select("table#TBL_patientReferral").first() ?: return emptyList()
+
+ // تشخیص ساختار بر اساس سربرگ جدول (یک بار برای کل جدول)
+ val hasGuaranteeColumn = hasGuaranteeColumnInHeader(table)
+
+ val rows = table.select("tbody tr")
for (row in rows) {
val cells = row.select("td")
if (cells.size < 3) continue
- // ستون 0: اطلاعات برند
- val brandCell = cells[0]
+ // ایندکس ستونها بر اساس وجود ستون تضمین
+ val brandIndex = if (hasGuaranteeColumn) 1 else 0
+ val pharmacyIndex = if (hasGuaranteeColumn) 2 else 1
+ val locationIndex = if (hasGuaranteeColumn) 3 else 2
+
+ // ستون برند
+ val brandCell = cells[brandIndex]
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 pharmacyCell = cells[pharmacyIndex]
+ val pharmacyLinks = pharmacyCell.select("a")
+
+ // پیدا کردن لینک داروخانه (لینکی که "اطلاعات تماس" نباشد)
+ val pharmacyLink = pharmacyLinks.firstOrNull {
+ !it.text().contains("اطلاعات تماس")
+ }
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() ?: ""
+ // ستون موقعیت
+ val locationCell = cells[locationIndex]
+ val locationText = locationCell.text().trim()
+
+ // استخراج استان و شهر
+ val province = extractProvince(locationText)
+ val city = extractCity(locationText)
if (brandName.isNotBlank() && pharmacyName.isNotBlank()) {
items.add(
@@ -52,6 +70,32 @@ object PharmacyHtmlParser {
return items
}
+ /**
+ * تشخیص وجود ستون تضمین موجودی با بررسی سربرگ جدول
+ */
+ private fun hasGuaranteeColumnInHeader(table: org.jsoup.nodes.Element): Boolean {
+ val headers = table.select("thead th")
+ if (headers.isEmpty()) return false
+
+ // بررسی میکنیم که آیا اولین ستون حاوی متن "تضمین" است
+ val firstHeaderText = headers.firstOrNull()?.text()?.trim() ?: ""
+ return firstHeaderText.contains("تضمین") || firstHeaderText.contains("موجودی")
+ }
+
+ private fun extractProvince(locationText: String): String {
+ // الگو: "استان تهران" یا "استان اصفهان"
+ val provincePattern = "استان\\s*(\\S+)".toRegex()
+ val match = provincePattern.find(locationText)
+ return match?.groupValues?.get(1)?.trim() ?: ""
+ }
+
+ private fun extractCity(locationText: String): String {
+ // الگو: "شهر تهران" یا "شهر اصفهان" یا "شهر پردیس"
+ val cityPattern = "شهر\\s*(.+)".toRegex()
+ val match = cityPattern.find(locationText)
+ return match?.groupValues?.get(1)?.trim() ?: ""
+ }
+
private fun extractIdFromUrl(url: String, pattern: String): String {
val regex = pattern.toRegex()
return regex.find(url)?.groupValues?.get(1) ?: ""
diff --git a/app/src/main/java/com/approagency/drug/presentation/components/PharmacyBottomSheet.kt b/app/src/main/java/com/approagency/drug/presentation/components/PharmacyBottomSheet.kt
index 70ea88c..2b3eed8 100644
--- a/app/src/main/java/com/approagency/drug/presentation/components/PharmacyBottomSheet.kt
+++ b/app/src/main/java/com/approagency/drug/presentation/components/PharmacyBottomSheet.kt
@@ -1,6 +1,7 @@
package com.approagency.drug.presentation.components
+import android.annotation.SuppressLint
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@@ -8,6 +9,7 @@ import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.LayoutDirection
@@ -15,12 +17,15 @@ import androidx.compose.ui.unit.dp
import com.approagency.drug.domain.model.PharmacyItem
import com.approagency.drug.presentation.common.CustomBox
import com.approagency.drug.presentation.common.CustomModalBottomSheet
+import com.approagency.drug.presentation.common.Loading
import com.approagency.drug.presentation.viewModel.PharmacyState
import com.approagency.drug.presentation.viewModel.PharmacyViewModel
import com.approagency.drug.utils.provinces
import com.vada.caller.ui.theme.LocalDime
+import com.vada.caller.ui.theme.dime
import kotlinx.coroutines.launch
+@SuppressLint("ConfigurationScreenWidthHeight")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PharmacyBottomSheet(
@@ -55,135 +60,153 @@ fun PharmacyBottomSheet(
scope = scope
) {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
- Column(
- modifier = Modifier
- .fillMaxWidth()
- .padding(16.dp),
- verticalArrangement = Arrangement.spacedBy(16.dp)
+ LazyColumn(
+ modifier = Modifier.fillMaxHeight()
+ .fillMaxWidth().heightIn(min = 400.dp)
+ .padding(MaterialTheme.dime.md),
+ verticalArrangement = Arrangement.spacedBy(MaterialTheme.dime.xl)
) {
- // عنوان
- 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.large
+ item {
+ Text(
+ text = "جستجوی دارو در داروخانهها",
+ fontWeight = FontWeight.Bold,
+ fontSize = MaterialTheme.typography.titleMedium.fontSize
)
-
- 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(
+ item {
+ 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()
- .height(200.dp),
- contentAlignment = Alignment.Center
+ .menuAnchor(),
+ shape = MaterialTheme.shapes.large
+ )
+
+ ExposedDropdownMenu(
+ expanded = showProvinceDropdown,
+ onDismissRequest = { showProvinceDropdown = false }
) {
- Column(horizontalAlignment = Alignment.CenterHorizontally) {
- CircularProgressIndicator(
- modifier = Modifier.size(32.dp)
- )
- Spacer(modifier = Modifier.height(8.dp))
- Text(
- text = "در حال جستجو...",
- fontSize = MaterialTheme.typography.bodySmall.fontSize
+ provinces.forEach { province ->
+ DropdownMenuItem(
+ text = { Text(province.name) },
+ onClick = {
+ selectedProvinceId = province.id
+ showProvinceDropdown = false
+ // جستجو با استان جدید
+ viewModel.search(genericDrugId, province.id)
+ }
)
}
}
}
+ }
+ // انتخاب استان
+
+
+
+
+ // نمایش نتایج
+ when (val state = pharmacyState) {
+ PharmacyState.Loading -> {
+ item {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(400.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Loading()
+ Spacer(modifier = Modifier.height(MaterialTheme.dime.xl))
+ Text(
+ text = "در حال جستجو...",
+ fontSize = MaterialTheme.typography.bodySmall.fontSize
+ )
+ }
+ }
+ }
+
+ }
is PharmacyState.Success -> {
if (state.items.isEmpty()) {
+ item {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(400.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = "هیچ داروخانهای یافت نشد",
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
+ )
+ }
+ }
+
+ } else {
+ items(state.items)
+ { pharmacy ->
+ PharmacyResultItem(pharmacy = pharmacy)
+ }
+ item {
+ Spacer(modifier = Modifier.height(MaterialTheme.dime.md))
+ Text(
+ text = "تعداد کل: ${state.items.size} داروخانه",
+ fontSize = MaterialTheme.typography.bodySmall.fontSize,
+ color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f),
+ modifier = Modifier.padding(bottom = MaterialTheme.dime.xs)
+ )
+ }
+// LazyColumn(
+// verticalArrangement = Arrangement.spacedBy(8.dp),
+// modifier = Modifier,
+// ) {
+// items(state.items) { pharmacy ->
+// PharmacyResultItem(pharmacy = pharmacy)
+// }
+//
+//
+// }
+
+ }
+ }
+
+ is PharmacyState.Error -> {
+ item {
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.weight(1f),
- ) {
- items(state.items) { pharmacy ->
- PharmacyResultItem(pharmacy = pharmacy)
- }
-
- item {
- Spacer(modifier = Modifier.height(16.dp))
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
- text = "تعداد کل: ${state.items.size} داروخانه",
- fontSize = MaterialTheme.typography.bodySmall.fontSize,
- color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f),
- modifier = Modifier.padding(bottom = 8.dp)
+ text = state.message,
+ color = MaterialTheme.colorScheme.error,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize
)
+ Spacer(modifier = Modifier.height(8.dp))
+ TextButton(
+ onClick = { viewModel.retry() }
+ ) {
+ Text("تلاش مجدد")
+ }
}
}
}
- }
- 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 -> {}
@@ -201,7 +224,7 @@ fun PharmacyResultItem(
) {
val dime = LocalDime.current
- CustomBox (
+ CustomBox(
modifier = modifier.fillMaxWidth(),
) {
Column(