feat: remove drug.approagncy api

This commit is contained in:
2026-06-14 08:18:01 +03:30
parent 1ed75fb823
commit 27dc08fa68
9 changed files with 210 additions and 62 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,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/"
/** بک‌اند اشتراک/احراز هویت آپرواجنسی (لاگین، وضعیت، محصولات، خرید). */