Compare commits

..
2 Commits
Author SHA1 Message Date
Amirmahdi 1971708b3f feat: remove code 2026-06-14 15:31:13 +03:30
Amirmahdi 27dc08fa68 feat: remove drug.approagncy api 2026-06-14 08:18:01 +03:30
11 changed files with 391 additions and 229 deletions
-1
View File
@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK">
Binary file not shown.
@@ -0,0 +1,185 @@
package com.approagency.pharmacy.data.local
import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import com.approagency.pharmacy.data.dto.DarmanList
import com.approagency.pharmacy.data.dto.DarmanModel
import com.approagency.pharmacy.data.dto.DrugDetail
import com.approagency.pharmacy.data.dto.DrugListResponse
import com.approagency.pharmacy.data.dto.DrugModels
import com.approagency.pharmacy.data.dto.GorohDaroei
import com.approagency.pharmacy.data.dto.GorohDarmani
import com.approagency.pharmacy.data.dto.Meta
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.FileOutputStream
import kotlin.math.ceil
/**
* منبعِ دادهٔ دارو روی پایگاه‌دادهٔ همراهِ اپ (`assets/DrugStore.db`).
*
* تنها منبعِ اطلاعاتِ دارو (جست‌وجو، جزئیات، گروه‌های درمانی) است و فقط خواندنی
* است. از SQLite خام استفاده می‌کند (نه Room) تا اعتبارسنجیِ سخت‌گیرانهٔ شِمای
* Room روی این دیتابیسِ ثابتِ ازپیش‌ساخته (با کلیدهای خارجی) دردسر نسازد.
*/
class LocalDrugDataSource(private val context: Context) {
@Volatile
private var db: SQLiteDatabase? = null
private fun database(): SQLiteDatabase {
db?.let { return it }
return synchronized(this) {
db ?: openReadOnly().also { db = it }
}
}
private fun openReadOnly(): SQLiteDatabase {
val out = context.getDatabasePath(DB_NAME)
if (!out.exists()) {
out.parentFile?.mkdirs()
context.assets.open(DB_NAME).use { input ->
FileOutputStream(out).use { output -> input.copyTo(output) }
}
}
return SQLiteDatabase.openDatabase(out.path, null, SQLiteDatabase.OPEN_READONLY)
}
/** جست‌وجوی دارو با فیلترهای نام، گروه دارویی و گروه درمانی. */
suspend fun searchDrugs(
query: String?,
drugGroup: Int?,
healGroup: Int?,
perPage: Int
): DrugListResponse = withContext(Dispatchers.IO) {
val where = StringBuilder("1=1")
val args = mutableListOf<String>()
if (!query.isNullOrBlank()) {
where.append(" AND (d.nam_fa LIKE ? OR d.nam_en LIKE ?)")
args.add("%$query%"); args.add("%$query%")
}
if (drugGroup != null) { where.append(" AND d.goroh_daroei_cod = ?"); args.add(drugGroup.toString()) }
if (healGroup != null) { where.append(" AND d.goroh_darmani_cod = ?"); args.add(healGroup.toString()) }
val total = database().rawQuery(
"SELECT COUNT(*) FROM DrugInfo d WHERE $where", args.toTypedArray()
).use { c -> if (c.moveToFirst()) c.getInt(0) else 0 }
val items = database().rawQuery(
"$SELECT_WITH_RELATIONS WHERE $where ORDER BY d.nam_fa LIMIT ?",
(args + perPage.toString()).toTypedArray()
).use { c -> buildList { while (c.moveToNext()) add(c.toDrugDetail()) } }
DrugListResponse(
success = true,
message = null,
data = items,
meta = Meta(
current_page = 1,
last_page = if (perPage > 0) ceil(total.toDouble() / perPage).toInt() else 1,
per_page = perPage,
total = total
)
)
}
/** جزئیات یک دارو بر اساس [cod]. اگر یافت نشود استثناء پرتاب می‌شود. */
suspend fun drugDetail(cod: Int): DrugModels = withContext(Dispatchers.IO) {
val detail = database().rawQuery(
"$SELECT_WITH_RELATIONS WHERE d.cod = ?", arrayOf(cod.toString())
).use { c -> if (c.moveToFirst()) c.toDrugDetail() else null }
?: throw NoSuchElementException("دارویی با شناسهٔ $cod در پایگاه‌دادهٔ محلی یافت نشد.")
DrugModels(success = true, message = null, data = detail, meta = null)
}
/** فهرست گروه‌های درمانی (goroh_darmani). */
suspend fun darmanGroups(): DarmanModel = withContext(Dispatchers.IO) {
val groups = database().rawQuery(
"SELECT cod, nam_fa, nam_en FROM goroh_darmani ORDER BY nam_fa", null
).use { c ->
buildList {
while (c.moveToNext()) {
add(
DarmanList(
cod = c.getInt(0),
nam_fa = c.getStringOrEmpty(1),
nam_en = c.getStringOrEmpty(2)
)
)
}
}
}
DarmanModel(success = "true", message = "", data = groups)
}
private fun Cursor.toDrugDetail(): DrugDetail {
val daroeiCod = getIntOrNull("goroh_daroei_cod")
val darmaniCod = getIntOrNull("goroh_darmani_cod")
return DrugDetail(
cod = getIntOrNull("cod"),
goroh_darmani_detail_cod = getIntOrNull("goroh_darmani_detail_cod"),
goroh_daroei_cod = daroeiCod,
goroh_farmakologic_cod = getIntOrNull("goroh_farmakologic_cod"),
goroh_darmani_cod = darmaniCod,
nam_fa = getStringOrNull("nam_fa"),
nam_en = getStringOrNull("nam_en"),
mavaredmasraf = getStringOrNull("mavaredmasraf"),
meghdarmasraf = getStringOrNull("meghdarmasraf"),
masrafdarhamelegi = getStringOrNull("masrafdarhamelegi"),
masrafdarshirdehi = getStringOrNull("masrafdarshirdehi"),
manemasraf = getStringOrNull("manemasraf"),
avarez = getStringOrNull("avarez"),
tadakhol = getStringOrNull("tadakhol"),
mekanismtasir = getStringOrNull("mekanismtasir"),
nokte = getStringOrNull("nokte"),
hoshdar = getStringOrNull("hoshdar"),
sharayetnegahdari = getStringOrNull("sharayetnegahdari"),
ashkal_daroei = getStringOrNull("ashkal_daroei"),
created_at = null,
updated_at = null,
goroh_daroei = getStringOrNull("gd_nam")?.let { GorohDaroei(cod = daroeiCod, nam = it, created_at = null, updated_at = null) },
goroh_darmani = getStringOrNull("gm_nam_fa")?.let {
GorohDarmani(
cod = darmaniCod,
nam_fa = it,
nam_en = getStringOrNull("gm_nam_en"),
giyahi_ya_shimiyaei = getStringOrNull("gm_giyahi"),
image = getStringOrNull("gm_image"),
created_at = null,
updated_at = null
)
},
goroh_darmani_detail = null
)
}
private fun Cursor.getStringOrNull(name: String): String? {
val i = getColumnIndex(name)
return if (i >= 0 && !isNull(i)) getString(i) else null
}
private fun Cursor.getStringOrEmpty(index: Int): String =
if (!isNull(index)) getString(index) else ""
private fun Cursor.getIntOrNull(name: String): Int? {
val i = getColumnIndex(name)
return if (i >= 0 && !isNull(i)) getInt(i) else null
}
private companion object {
const val DB_NAME = "DrugStore.db"
const val SELECT_WITH_RELATIONS = """
SELECT d.*,
gd.nam AS gd_nam,
gm.nam_fa AS gm_nam_fa,
gm.nam_en AS gm_nam_en,
gm.giyahi_ya_shimiyaei AS gm_giyahi,
gm.image AS gm_image
FROM DrugInfo d
LEFT JOIN goroh_daroei gd ON gd.cod = d.goroh_daroei_cod
LEFT JOIN goroh_darmani gm ON gm.cod = d.goroh_darmani_cod
"""
}
}
@@ -1,30 +0,0 @@
package com.approagency.pharmacy.data.remote
import com.approagency.pharmacy.data.dto.DarmanModel
import com.approagency.pharmacy.data.dto.DrugListResponse
import com.approagency.pharmacy.data.dto.DrugModels
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query
interface DrugApiService {
@GET("drugs/search")
suspend fun getDrugs(
@Query("q")query: String?,
@Query("per_page")perPage:Int? = 20,
@Query("with_relations")withRelations: Boolean? =true,
@Query("goroh_daroei_cod")drugGroup:Int?,
@Query("goroh_darmani_cod")healGroup:Int?,
): DrugListResponse
@GET("drugs/{cod}")
suspend fun getDrugDetail(
@Path("cod") cod: Int
): DrugModels
@GET("drugs/goroh-darmani")
suspend fun getGorohDaroei(): DarmanModel
}
@@ -3,8 +3,8 @@ package com.approagency.pharmacy.data.repository
import com.approagency.pharmacy.data.dto.DarmanModel
import com.approagency.pharmacy.data.dto.DrugListResponse
import com.approagency.pharmacy.data.dto.DrugModels
import com.approagency.pharmacy.data.local.LocalDrugDataSource
import com.approagency.pharmacy.data.remote.DarooyabApiService
import com.approagency.pharmacy.data.remote.DrugApiService
import com.approagency.pharmacy.data.remote.DrugDetailParser
import com.approagency.pharmacy.data.remote.DrugHtmlParser
import com.approagency.pharmacy.data.remote.PharmacyHtmlParser
@@ -20,34 +20,39 @@ import kotlinx.coroutines.withContext
import java.io.IOException
class DrugRepositoryImpl(
private val apiService: DrugApiService,
private val darooyabApiService: DarooyabApiService,
private val parser: DrugHtmlParser,
private val detailParser: DrugDetailParser,
private val localDrugDataSource: LocalDrugDataSource,
): DrugRepository {
// اطلاعات دارو همیشه از پایگاه‌دادهٔ محلیِ همراهِ اپ (assets/DrugStore.db) خوانده می‌شود.
override suspend fun searchDrug(params: DrugSearchParams): Result<DrugListResponse> {
return try {
val response = apiService.getDrugs(query = params.query , perPage = params.perPage , withRelations = params.withRelations , healGroup = params.healGroup , drugGroup = params.drugGroup )
return Result.success(response)
}catch (e: Exception){
Result.success(
localDrugDataSource.searchDrugs(
query = params.query,
drugGroup = params.drugGroup,
healGroup = params.healGroup,
perPage = params.perPage
)
)
} catch (e: Exception) {
Result.failure(e)
}
}
override suspend fun drugDetail(cod: Int): Result<DrugModels> {
return try {
val response= apiService.getDrugDetail(cod = cod)
return Result.success(response)
}catch (e: Exception){
Result.success(localDrugDataSource.drugDetail(cod))
} catch (e: Exception) {
Result.failure(e)
}
}
override suspend fun getGorohDaroei(): Result<DarmanModel> {
return try {
val response = apiService.getGorohDaroei()
return Result.success(response)
}catch (e: Exception){
return try {
Result.success(localDrugDataSource.darmanGroups())
} catch (e: Exception) {
Result.failure(e)
}
}
@@ -2,12 +2,12 @@ package com.approagency.pharmacy.di
import com.approagency.pharmacy.data.billing.StubPurchaseGateway
import com.approagency.pharmacy.data.local.LocalDrugDataSource
import com.approagency.pharmacy.data.local.SessionManager
import com.approagency.pharmacy.data.local.database.LabDatabase
import com.approagency.pharmacy.data.remote.ApproApiService
import com.approagency.pharmacy.data.remote.AuthInterceptor
import com.approagency.pharmacy.data.remote.DarooyabApiService
import com.approagency.pharmacy.data.remote.DrugApiService
import com.approagency.pharmacy.data.remote.DrugDetailParser
import com.approagency.pharmacy.data.remote.DrugHtmlParser
import com.approagency.pharmacy.data.repository.AuthRepositoryImpl
@@ -47,7 +47,6 @@ import retrofit2.converter.scalars.ScalarsConverterFactory
import java.net.CookieManager
import java.net.CookiePolicy
import java.util.concurrent.TimeUnit
val jsonRetrofitQualifier = qualifier("jsonRetrofit")
val scalarRetrofitQualifier = qualifier("scalarRetrofit")
val authRetrofitQualifier = qualifier("authRetrofit")
val appModule= module {
@@ -76,19 +75,6 @@ val appModule= module {
.build()
}
single<Retrofit>(jsonRetrofitQualifier) { // Add qualifier
Retrofit.Builder()
.baseUrl(Config.BASE_URL)
.client(get<OkHttpClient>())
.addConverterFactory(GsonConverterFactory.create())
.build()
}
single<DrugApiService> {
val retrofit: Retrofit = get(jsonRetrofitQualifier) // Get qualified instance
retrofit.create(DrugApiService::class.java)
}
// ========== Retrofit for DAROOYAB Website ==========
single<Retrofit>(scalarRetrofitQualifier) { // Add qualifier
Retrofit.Builder()
@@ -135,9 +121,12 @@ val appModule= module {
factory { DrugHtmlParser() }
factory { DrugDetailParser() }
// منبع آفلاین دارو (assets/DrugStore.db) برای fallback هنگام خطای شبکه
single { LocalDrugDataSource(androidContext()) }
//repo
single<DrugRepository> {
DrugRepositoryImpl(get<DrugApiService>() , get<DarooyabApiService>() , get() , get())
DrugRepositoryImpl(get<DarooyabApiService>() , get() , get() , get())
}
@@ -20,7 +20,7 @@ fun AppNavGraph() {
NavHost(
navController = navController,
startDestination = "home"
startDestination = "search"
) {
// ───────────────
@@ -26,6 +26,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
@@ -70,7 +71,7 @@ fun BottomBar(navController: NavHostController) {
.clickable {
if (!selected) {
navController.navigate(item.first) {
popUpTo("home") {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
@@ -1,75 +1,98 @@
package com.approagency.pharmacy.presentation.components
import androidx.compose.foundation.clickable
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.LocalPharmacy
import androidx.compose.material.icons.filled.Medication
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.sp
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import com.approagency.pharmacy.domain.model.DrugSearchResult
import com.approagency.pharmacy.presentation.common.CustomBox
import com.vada.caller.ui.theme.LocalDime
import com.vada.caller.ui.theme.dime
@Composable
fun DaroYabSearchResult(
drug: DrugSearchResult,
onClickDetail: (DrugSearchResult) -> Unit,
onClickDrugStore:(DrugSearchResult) ->Unit,
onClickDrugStore: (DrugSearchResult) -> Unit,
modifier: Modifier = Modifier
) {
val dime = LocalDime.current
CustomBox (
CustomBox(
modifier = modifier
.padding(bottom = MaterialTheme.dime.sm)
.fillMaxWidth()
.clickable { onClickDetail(drug) },
) {
Row (
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
Column(
modifier = Modifier.padding(dime.lg),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.End
modifier = Modifier
.fillMaxWidth()
.padding(MaterialTheme.dime.lg),
verticalArrangement = Arrangement.spacedBy(MaterialTheme.dime.xs)
) {
Text(
text = drug.persianName,
style = MaterialTheme.typography.labelLarge
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
drug.englishName?.let {
drug.englishName?.takeIf { it.isNotBlank() }?.let {
Text(
text = it,
fontSize = 12.sp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
Text(
text = "کد: ${drug.genericId}",
fontSize = 10.sp,
color = MaterialTheme.colorScheme.primary
)
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
TextButton(onClick = {onClickDetail(drug)} , content = {
Text("اطلاعات دارویی")
})
TextButton(onClick = {onClickDrugStore(drug)} , content = {
Text("موجودی داروخانه")
})
}
Spacer(Modifier.height(MaterialTheme.dime.sm))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.dime.sm)
) {
Button(
onClick = { onClickDrugStore(drug) },
modifier = Modifier
.weight(1f)
.height(44.dp)
) {
Icon(
imageVector = Icons.Filled.LocalPharmacy,
contentDescription = null,
modifier = Modifier.size(18.dp)
)
Spacer(Modifier.size(MaterialTheme.dime.xs))
Text("موجودی داروخانه", style = MaterialTheme.typography.labelLarge)
}
OutlinedButton(
onClick = { onClickDetail(drug) },
modifier = Modifier
.weight(1f)
.height(44.dp)
) {
Icon(
imageVector = Icons.Filled.Medication,
contentDescription = null,
modifier = Modifier.size(18.dp)
)
Spacer(Modifier.size(MaterialTheme.dime.xs))
Text("اطلاعات دارویی", style = MaterialTheme.typography.labelLarge)
}
}
}
}
}
@@ -3,13 +3,13 @@ package com.approagency.pharmacy.presentation.components
import android.annotation.SuppressLint
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
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.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.LayoutDirection
@@ -33,161 +33,152 @@ fun PharmacyBottomSheet(
) {
val scope = rememberCoroutineScope()
val sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = false, confirmValueChange = { true })
skipPartiallyExpanded = true, confirmValueChange = { true }
)
var selectedProvinceId by remember { mutableStateOf("40") } // پیش‌فرض تهران
var showProvinceDropdown by remember { mutableStateOf(false) }
val pharmacyState by viewModel.state.collectAsState()
// جستجوی اولیه با استان تهران — با تغییر دارو دوباره اجرا می‌شود
// (در صورت خالی بودن شناسه، ViewModel وضعیت قبلی را پاک و خطا نمایش می‌دهد)
// شیت بلند ثابت می‌شود تا لیست داخلی به‌درستی اسکرول شود (نه اینکه درگِ شیت
// جای اسکرولِ لیست را بگیرد) و حالت‌های لودینگ/خالی/خطا وسط دیده شوند.
val sheetHeight = (LocalConfiguration.current.screenHeightDp * 0.85f).dp
LaunchedEffect(genericDrugId, brandIrc) {
viewModel.search(genericDrugId, brandIrc, "40")
}
if (isOpen) {
CustomModalBottomSheet(
sheetState = sheetState, onDismiss = onDismiss, scope = scope
) {
CustomModalBottomSheet(sheetState = sheetState, onDismiss = onDismiss, scope = scope) {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
LazyColumn(
Column(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
.heightIn(min = 400.dp)
.padding(MaterialTheme.dime.md),
verticalArrangement = Arrangement.spacedBy(MaterialTheme.dime.xl)
.height(sheetHeight)
.padding(MaterialTheme.dime.lg)
) {
item {
Text(
text = "جستجوی دارو در داروخانه‌ها",
fontWeight = FontWeight.Bold,
fontSize = MaterialTheme.typography.titleMedium.fontSize
)
}
// عنوان
// سربرگ ثابت
Text(
text = "موجودی دارو در داروخانه‌ها",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
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()
.menuAnchor(),
shape = MaterialTheme.shapes.large
Spacer(Modifier.height(MaterialTheme.dime.md))
ProvinceSelector(
selectedProvinceId = selectedProvinceId,
expanded = showProvinceDropdown,
onExpandedChange = { showProvinceDropdown = it },
onProvinceSelected = { id ->
selectedProvinceId = id
showProvinceDropdown = false
viewModel.search(genericDrugId, brandIrc, id)
}
)
Spacer(Modifier.height(MaterialTheme.dime.md))
// ناحیه‌ی محتوا فضای باقی‌مانده را پر می‌کند؛ سربرگ بالا ثابت می‌ماند.
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
contentAlignment = Alignment.Center
) {
when (val state = pharmacyState) {
PharmacyState.Loading -> Loading(
modifier = Modifier.fillMaxSize(),
message = "در حال جستجو..."
)
ExposedDropdownMenu(
expanded = showProvinceDropdown,
onDismissRequest = { showProvinceDropdown = false }) {
provinces.forEach { province ->
DropdownMenuItem(text = { Text(province.name) }, onClick = {
selectedProvinceId = province.id
showProvinceDropdown = false
// جستجو با استان جدید
viewModel.search(genericDrugId, brandIrc, province.id)
})
}
}
}
}
// انتخاب استان
// نمایش نتایج
when (val state = pharmacyState) {
PharmacyState.Loading -> {
item {
Loading(
modifier = Modifier
.fillMaxWidth()
.height(400.dp),
message = "در حال جستجو..."
)
}
}
is PharmacyState.Success -> {
if (state.items.isEmpty()) {
item {
Box(
modifier = Modifier
.fillMaxWidth()
.height(400.dp),
contentAlignment = Alignment.Center
is PharmacyState.Success -> {
if (state.items.isEmpty()) {
EmptyMessage("هیچ داروخانه‌ای یافت نشد")
} else {
LazyColumn(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(MaterialTheme.dime.sm)
) {
Text(
text = "هیچ داروخانه‌ای یافت نشد",
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
}
} else {
items(state.items) { pharmacy ->
PharmacyResultItem(pharmacy = pharmacy, viewModel)
}
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
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = state.message,
color = MaterialTheme.colorScheme.error,
fontSize = MaterialTheme.typography.bodyMedium.fontSize
)
Spacer(modifier = Modifier.height(MaterialTheme.dime.sm))
TextButton(
onClick = { viewModel.retry() }) {
Text("تلاش مجدد")
items(state.items) { pharmacy ->
PharmacyResultItem(pharmacy = pharmacy, viewModel)
}
item {
Text(
text = "تعداد کل: ${state.items.size} داروخانه",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(vertical = MaterialTheme.dime.sm)
)
}
}
}
}
}
is PharmacyState.Error -> Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = state.message,
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodyMedium
)
Spacer(Modifier.height(MaterialTheme.dime.sm))
TextButton(onClick = { viewModel.retry() }) { Text("تلاش مجدد") }
}
PharmacyState.Idle -> {}
PharmacyState.Idle -> {}
}
}
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ProvinceSelector(
selectedProvinceId: String,
expanded: Boolean,
onExpandedChange: (Boolean) -> Unit,
onProvinceSelected: (String) -> Unit
) {
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = onExpandedChange
) {
OutlinedTextField(
value = provinces.find { it.id == selectedProvinceId }?.name ?: "انتخاب استان",
onValueChange = {},
readOnly = true,
label = { Text("استان") },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
modifier = Modifier
.fillMaxWidth()
.menuAnchor(),
shape = MaterialTheme.shapes.large
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { onExpandedChange(false) }
) {
provinces.forEach { province ->
DropdownMenuItem(
text = { Text(province.name) },
onClick = { onProvinceSelected(province.id) }
)
}
}
}
}
@Composable
private fun EmptyMessage(message: String) {
Text(
text = message,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
@@ -1,7 +1,6 @@
package com.approagency.pharmacy.utils
object Config {
const val BASE_URL = "https://drug.approagency.ir/api/"
const val Darro_Url = "https://www.darooyab.ir/"
/** بک‌اند اشتراک/احراز هویت آپرواجنسی (لاگین، وضعیت، محصولات، خرید). */