From 6d89abc56d57987f45a8c2ed22ea32b86d9c8ddd Mon Sep 17 00:00:00 2001 From: Amirmahdi Nourkazemi Date: Wed, 27 May 2026 15:34:45 +0330 Subject: [PATCH] feat: add detail screen and nav fixed --- .../java/com/approagency/drug/MainActivity.kt | 4 +- .../drug/data/remote/DrugDetailParser.kt | 370 ++++++++++++ .../data/repository/DrugRepositoryImpl.kt | 17 +- .../java/com/approagency/drug/di/AppModule.kt | 15 +- .../drug/domain/model/DrugDetail.kt | 70 +++ .../drug/domain/repository/DrugRepository.kt | 3 + .../domain/usecase/DrugDetailYabUseCase.kt | 14 + .../domain/usecase/SearchDrugsYabUseCase.kt | 2 +- .../drug/navigation/AppNavGraph.kt | 23 + .../approagency/drug/navigation/BottomBar.kt | 123 ++++ .../com/approagency/drug/navigation/Graph.kt | 24 - .../drug/navigation/MainContainer.kt | 37 ++ .../approagency/drug/navigation/NavRoutes.kt | 9 - .../com/approagency/drug/navigation/Routes.kt | 32 + .../drug/navigation/graphs/HomeGraph.kt | 28 + .../drug/navigation/graphs/LabGraph.kt | 25 + .../drug/navigation/graphs/SearchGraph.kt | 51 ++ .../presentation/components/showYabDetail.kt | 516 ++++++++++++++++ .../presentation/screens/DrugDetailScreen.kt | 564 ++++++++++++++++++ .../drug/presentation/screens/SearchScreen.kt | 63 +- .../viewModel/DrugDetailViewModel.kt | 40 ++ .../presentation/viewModel/SearchViewModel.kt | 8 + 22 files changed, 1969 insertions(+), 69 deletions(-) create mode 100644 app/src/main/java/com/approagency/drug/data/remote/DrugDetailParser.kt create mode 100644 app/src/main/java/com/approagency/drug/domain/model/DrugDetail.kt create mode 100644 app/src/main/java/com/approagency/drug/domain/usecase/DrugDetailYabUseCase.kt create mode 100644 app/src/main/java/com/approagency/drug/navigation/AppNavGraph.kt create mode 100644 app/src/main/java/com/approagency/drug/navigation/BottomBar.kt delete mode 100644 app/src/main/java/com/approagency/drug/navigation/Graph.kt create mode 100644 app/src/main/java/com/approagency/drug/navigation/MainContainer.kt delete mode 100644 app/src/main/java/com/approagency/drug/navigation/NavRoutes.kt create mode 100644 app/src/main/java/com/approagency/drug/navigation/Routes.kt create mode 100644 app/src/main/java/com/approagency/drug/navigation/graphs/HomeGraph.kt create mode 100644 app/src/main/java/com/approagency/drug/navigation/graphs/LabGraph.kt create mode 100644 app/src/main/java/com/approagency/drug/navigation/graphs/SearchGraph.kt create mode 100644 app/src/main/java/com/approagency/drug/presentation/components/showYabDetail.kt create mode 100644 app/src/main/java/com/approagency/drug/presentation/screens/DrugDetailScreen.kt create mode 100644 app/src/main/java/com/approagency/drug/presentation/viewModel/DrugDetailViewModel.kt diff --git a/app/src/main/java/com/approagency/drug/MainActivity.kt b/app/src/main/java/com/approagency/drug/MainActivity.kt index 4700cc0..4331143 100644 --- a/app/src/main/java/com/approagency/drug/MainActivity.kt +++ b/app/src/main/java/com/approagency/drug/MainActivity.kt @@ -42,8 +42,8 @@ class MainActivity : ComponentActivity() { Scaffold(modifier = Modifier.fillMaxSize(), ) { innerPadding -> AppNavGraph( - navController = navController, - modifier = Modifier.padding(innerPadding) +// navController = navController, +// modifier = Modifier.padding(innerPadding) ) } } diff --git a/app/src/main/java/com/approagency/drug/data/remote/DrugDetailParser.kt b/app/src/main/java/com/approagency/drug/data/remote/DrugDetailParser.kt new file mode 100644 index 0000000..c862449 --- /dev/null +++ b/app/src/main/java/com/approagency/drug/data/remote/DrugDetailParser.kt @@ -0,0 +1,370 @@ +package com.approagency.drug.data.remote + +import com.approagency.drug.domain.model.* +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element + +class DrugDetailParser { + + fun parseDrugDetail(html: String): DrugDetail { + val document = Jsoup.parse(html) + + // اطلاعات پایه + val genericId = extractGenericId(document) + val persianName = extractPersianName(document) + val englishName = extractEnglishName(document) + + // استخراج تمام بخش‌ها به صورت داینامیک + val sections = extractAllSections(document) + + return DrugDetail( + genericId = genericId, + persianName = persianName, + englishName = englishName, + drugClass = extractDrugClass(document), + therapeuticClass = extractTherapeuticClass(document), + usage = sections["usage"] ?: extractSectionByText(document, "موارد مصرف"), + mechanism = sections["mechanism"] ?: extractSectionByText(document, "مکانیسم اثر"), + pharmacokinetics = sections["pharmacokinetics"] ?: extractSectionByText(document, "فارماکوکینتیک"), + contraindications = sections["contraindications"] ?: extractSectionByText(document, "منع مصرف"), + sideEffects = sections["sideEffects"] ?: extractSectionByText(document, "عوارض جانبی"), + interactions = sections["interactions"] ?: extractSectionByText(document, "تداخلات دارویی"), + warnings = sections["warnings"] ?: extractSectionByText(document, "هشدار"), + recommendations = sections["recommendations"] ?: extractSectionByText(document, "توصیه"), + pregnancyCategory = extractPregnancyCategory(document), + pregnancyDescription = extractPregnancyDescription(document), + dosageForms = extractDosageForms(document), + brandNames = extractBrandNames(document), + similarDrugs = extractSimilarDrugs(document), + categories = extractCategories(document), + comments = extractComments(document) + ) + } + + private fun extractGenericId(document: Document): String { + val urlElement = document.select("link[rel=canonical]").first() + val url = urlElement?.attr("href") ?: "" + val regex = "/G-(\\d+)/".toRegex() + return regex.find(url)?.groupValues?.get(1) ?: "" + } + + private fun extractPersianName(document: Document): String { + val titleElement = document.select("h1.EnglishNumericFont").first() + val fullText = titleElement?.text() ?: "" + return fullText.replace("چیست و برای چه مواردی استفاده می شود؟", "").trim() + } + + private fun extractEnglishName(document: Document): String { + val englishLabel = document.select("label.EnglishTopLabel").first() + return englishLabel?.text()?.trim() ?: "" + } + + private fun extractDrugClass(document: Document): String? { + val classElement = document.select("#divExtraInfo > div:first-child a.ahref_Generic").first() + return classElement?.text()?.trim() + } + + private fun extractTherapeuticClass(document: Document): String? { + val therapeuticElement = document.select("#divExtraInfo > div:last-child a.ahref_Generic").first() + return therapeuticElement?.text()?.trim() + } + + /** + * استخراج بخش‌ها با استفاده از ID (روش قبلی) + */ + private fun extractAllSections(document: Document): Map { + val sections = mutableMapOf() + + // نقشه ID به کلید + val idToKey = mapOf( + "0" to "usage", + "1" to "mechanism", + "2" to "pharmacokinetics", + "3" to "contraindications", + "4" to "sideEffects", + "5" to "interactions", + "6" to "warnings", + "7" to "recommendations" + ) + + for ((id, key) in idToKey) { + val section = extractSectionById(document, id) + if (!section.isNullOrBlank()) { + sections[key] = section + } + } + + return sections + } + + private fun extractSectionById(document: Document, sectionId: String): String? { + val sectionElement = document.select("h2.h2_TabTitle#${sectionId}").first() + if (sectionElement == null) return null + + val content = StringBuilder() + var nextElement = sectionElement.nextElementSibling() + + while (nextElement != null && !nextElement.select("h2.h2_TabTitle").hasText()) { + if (nextElement.tagName() == "p" || nextElement.tagName() == "div") { + val text = cleanHtmlText(nextElement.text()) + if (text.isNotBlank()) { + content.append(text).append("\n\n") + } + } + nextElement = nextElement.nextElementSibling() + } + + return content.toString().trim().takeIf { it.isNotEmpty() } + } + + /** + * استخراج بخش با جستجوی متن عنوان (روش جایگزین) + */ + private fun extractSectionByText(document: Document, titleKeyword: String): String? { + // جستجوی هدر حاوی کلمه کلیدی + val header = document.select("h2.h2_TabTitle, h3").firstOrNull { + it.text().contains(titleKeyword, ignoreCase = true) + } ?: return null + + val content = StringBuilder() + var nextElement = header.nextElementSibling() + + while (nextElement != null && !nextElement.select("h2.h2_TabTitle, h3").hasText()) { + if (nextElement.tagName() == "p" || nextElement.tagName() == "div") { + val text = cleanHtmlText(nextElement.text()) + if (text.isNotBlank()) { + content.append(text).append("\n\n") + } + } + nextElement = nextElement.nextElementSibling() + } + + return content.toString().trim().takeIf { it.isNotEmpty() } + } + + private fun cleanHtmlText(text: String): String { + return text + .replace(Regex("\\s+"), " ") + .trim() + } + + private fun extractPregnancyCategory(document: Document): String? { + val categoryElement = document.select("#UseInPregnancy .EnglishNumericFont, #UseInPregnancy > div.EnglishNumericFont").first() + return categoryElement?.text()?.trim() + } + + private fun extractPregnancyDescription(document: Document): String? { + val descElement = document.select("#UseInPregnancy p, #UseInPregnancy .alert").first() + val text = descElement?.text()?.trim() + // اگر متن "مصرف در بارداری ثبت نشده است" باشد، null برگردان + return if (text.isNullOrBlank() || text.contains("ثبت نشده")) null else text + } + + private fun extractDosageForms(document: Document): List { + val forms = mutableListOf() + + // بررسی وجود جدول اشکال دارویی + val table = document.select("#TBL_AshkalDarooyi").first() + if (table == null) { + println("No dosage forms table found") + return forms + } + + val rows = table.select("tbody tr").filter { !it.hasClass("showMoreRow") && it.id() != "showMoreRow" } + + for (row in rows) { + try { + val cells = row.select("td") + if (cells.size >= 2) { + val persianNameElement = cells[1].select("h3").first() + val englishNameElement = cells[1].select("label.EnglishNumericFont").first() + + // بررسی اینکه آیا داده معتبر است + val persianName = persianNameElement?.text()?.trim() + if (persianName.isNullOrBlank()) continue + + forms.add( + DosageForm( + code = cells[0].text().trim(), + persianName = persianName, + englishName = englishNameElement?.text()?.trim() ?: "", + isHighRisk = cells.getOrNull(2)?.hasText() == true, + temperature = cells.getOrNull(3)?.text()?.takeIf { it.isNotBlank() }, + isVital = cells.getOrNull(4)?.hasText() == true, + warningLabel = cells.getOrNull(5)?.text()?.takeIf { it.isNotBlank() } + ) + ) + } + } catch (e: Exception) { + println("Error parsing dosage form: ${e.message}") + } + } + + return forms + } + + private fun extractBrandNames(document: Document): List { + val brands = mutableListOf() + + // استخراج از بخش اسامی تجاری فارسی + val persianRows = document.select("#PersCommertialDrugs .tableCommertial tbody tr.tr_persian") + + // اگر ردیفی وجود نداشت، بررسی کن که آیا پیام "ثبت نشده" وجود دارد + if (persianRows.isEmpty()) { + val noDataMessage = document.select("#PersCommertialDrugs .alert") + if (noDataMessage.isNotEmpty()) { + println("No brand names available: ${noDataMessage.text()}") + } + return brands + } + + for (row in persianRows) { + try { + val linkElement = row.select("td:first-child a.ahref_Generic").first() + val persianName = linkElement?.text()?.trim() ?: continue + val detailUrl = "https://www.darooyab.ir${linkElement.attr("href")}" + + val manufacturerElement = row.select("td:eq(1) a.ahref_Generic").first() + val manufacturer = manufacturerElement?.text()?.trim() + + val importerElement = row.select("td:eq(2) a.ahref_Generic").first() + val importer = importerElement?.text()?.trim() + + brands.add( + BrandName( + persianName = persianName, + englishName = "", + manufacturer = manufacturer, + importer = importer, + detailUrl = detailUrl + ) + ) + } catch (e: Exception) { + println("Error parsing brand name: ${e.message}") + } + } + + return brands + } + + private fun extractSimilarDrugs(document: Document): List { + val drugs = mutableListOf() + + // بررسی وجود جدول داروهای هم گروه + val table = document.select("table.tableGroups").first() + if (table == null) { + println("No similar drugs table found") + return drugs + } + + val rows = table.select("tbody tr").filter { + !it.hasClass("hidden-row") && it.select("a#toggleButton").isEmpty() + } + + for (row in rows) { + try { + val cells = row.select("td") + for (cell in cells) { + val link = cell.select("a.ahref_Generic").first() + if (link != null && link.text().isNotBlank()) { + val href = link.attr("href") + val genericIdRegex = "/G-(\\d+)/".toRegex() + val genericId = genericIdRegex.find(href)?.groupValues?.get(1) ?: "" + + drugs.add( + SimilarDrug( + persianName = link.text().trim(), + englishName = null, + genericId = genericId, + detailUrl = "https://www.darooyab.ir$href" + ) + ) + } + } + } catch (e: Exception) { + println("Error parsing similar drug: ${e.message}") + } + } + + return drugs + } + + private fun extractCategories(document: Document): DrugCategories? { + val martindaleLink = document.select("#divExtraInfo > div:first-child a.ahref_Generic").first() + val martindale = martindaleLink?.text()?.trim() + val martindaleUrl = martindaleLink?.attr("href")?.let { "https://www.darooyab.ir$it" } + + // بررسی طبقه بندی درمانی (ممکن است "بدون طبقه بندی درمانی" باشد) + val therapeuticLinks = document.select("#divExtraInfo > div:last-child a.ahref_Generic") + val therapeutic = therapeuticLinks.mapNotNull { it.text().trim().takeIf { text -> + text != "بدون طبقه بندی درمانی" && text.isNotBlank() + } } + val therapeuticUrls = therapeuticLinks.map { "https://www.darooyab.ir${it.attr("href")}" } + + return if (martindale != null || therapeutic.isNotEmpty()) { + DrugCategories( + martindale = martindale, + martindaleUrl = martindaleUrl, + therapeutic = therapeutic.ifEmpty { null }, + therapeuticUrls = therapeuticUrls.ifEmpty { null } + ) + } else null + } + + private fun extractComments(document: Document): List { + val comments = mutableListOf() + val commentElements = document.select("#CommentContent .comment") + + for (element in commentElements) { + try { + val authorElement = element.select("span").first() + val author = authorElement?.text()?.replace("(", "")?.replace(")", "")?.trim() ?: "ناشناس" + + val date = authorElement?.text()?.let { + val regex = "\\((\\d{4}/\\d{1,2}/\\d{1,2})\\)".toRegex() + regex.find(it)?.groupValues?.get(1) ?: "" + } ?: "" + + val textElement = element.select("p.commentText").first() + val text = textElement?.text()?.trim() ?: "" + + if (text.isBlank()) continue + + val responseElement = element.select(".responseComment").first() + val response = responseElement?.let { parseCommentResponse(it) } + + comments.add( + Comment( + author = author, + date = date, + text = text, + response = response + ) + ) + } catch (e: Exception) { + println("Error parsing comment: ${e.message}") + } + } + + return comments + } + + private fun parseCommentResponse(element: Element): CommentResponse { + val doctorLink = element.select("a").first() + val doctorName = doctorLink?.text()?.trim() ?: "" + val doctorUrl = doctorLink?.attr("href")?.let { "https://www.darooyab.ir$it" } + + val doctorText = element.select("span").first()?.text()?.trim() ?: "" + val doctorTitle = doctorText.substringAfter(" - ").takeIf { it.isNotBlank() } ?: "" + + val responseText = element.select("p.commentText").last()?.text()?.trim() ?: "" + + return CommentResponse( + doctorName = doctorName, + doctorTitle = doctorTitle, + text = responseText, + doctorUrl = doctorUrl + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/data/repository/DrugRepositoryImpl.kt b/app/src/main/java/com/approagency/drug/data/repository/DrugRepositoryImpl.kt index b3a8b08..5e8e0a3 100644 --- a/app/src/main/java/com/approagency/drug/data/repository/DrugRepositoryImpl.kt +++ b/app/src/main/java/com/approagency/drug/data/repository/DrugRepositoryImpl.kt @@ -5,9 +5,11 @@ import com.approagency.drug.data.dto.DrugListResponse import com.approagency.drug.data.dto.DrugModels 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.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.repository.DrugRepository @@ -18,7 +20,8 @@ import java.io.IOException class DrugRepositoryImpl( private val apiService: DrugApiService, private val darooyabApiService: DarooyabApiService, - private val parser: DrugHtmlParser + private val parser: DrugHtmlParser, + private val detailParser: DrugDetailParser, ): DrugRepository { override suspend fun searchDrug(params: DrugSearchParams): Result { return try { @@ -74,4 +77,16 @@ class DrugRepositoryImpl( } } } + + override suspend fun getDrugDetailFromYab(detailUrl: String): Result { + return withContext(Dispatchers.IO) { + try { + val html = darooyabApiService.getDrugDetail(detailUrl) + val detail = detailParser.parseDrugDetail(html) + Result.success(detail) + } catch (e: Exception) { + Result.failure(e) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/di/AppModule.kt b/app/src/main/java/com/approagency/drug/di/AppModule.kt index b8402dd..f86dbd2 100644 --- a/app/src/main/java/com/approagency/drug/di/AppModule.kt +++ b/app/src/main/java/com/approagency/drug/di/AppModule.kt @@ -5,10 +5,12 @@ import android.app.Application import com.approagency.drug.data.local.database.LabDatabase 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.repository.DrugRepositoryImpl import com.approagency.drug.data.repository.LabRepositoryImpl import com.approagency.drug.domain.repository.DrugRepository +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 @@ -16,6 +18,7 @@ import com.approagency.drug.domain.usecase.GetTestGroupUseCase import com.approagency.drug.domain.usecase.GetTestItemByGroupId import com.approagency.drug.domain.usecase.SearchDrugsYabUseCase 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.utils.Config @@ -86,10 +89,10 @@ val appModule= module { factory { DrugHtmlParser() } - + factory { DrugDetailParser() } //repo single { - DrugRepositoryImpl(get() , get() , get()) + DrugRepositoryImpl(get() , get() , get() , get()) } @@ -122,6 +125,14 @@ val appModule= module { single { LabRepositoryImpl(get(), get()) } + single { + DrugDetailYabUseCase(get()) + } + + viewModel { + DrugDetailViewModel(get()) + } + single { SearchTestsUseCase(get()) } //view model viewModel { diff --git a/app/src/main/java/com/approagency/drug/domain/model/DrugDetail.kt b/app/src/main/java/com/approagency/drug/domain/model/DrugDetail.kt new file mode 100644 index 0000000..bdb5415 --- /dev/null +++ b/app/src/main/java/com/approagency/drug/domain/model/DrugDetail.kt @@ -0,0 +1,70 @@ +package com.approagency.drug.domain.model + +data class DrugDetail( + val genericId: String, + val persianName: String, + val englishName: String, + val drugClass: String?, + val therapeuticClass: String?, + val usage: String?, + val mechanism: String?, + val pharmacokinetics: String?, + val contraindications: String?, + val sideEffects: String?, + val interactions: String?, + val warnings: String?, + val recommendations: String?, + val pregnancyCategory: String?, + val pregnancyDescription: String?, + val dosageForms: List, + val brandNames: List, + val similarDrugs: List, + val categories: DrugCategories?, + val comments: List +) + +data class DosageForm( + val code: String, + val persianName: String, + val englishName: String, + val isHighRisk: Boolean, + val temperature: String?, + val isVital: Boolean, + val warningLabel: String? +) + +data class BrandName( + val persianName: String, + val englishName: String, + val manufacturer: String?, + val importer: String?, + val detailUrl: String +) + +data class SimilarDrug( + val persianName: String, + val englishName: String?, + val genericId: String, + val detailUrl: String +) + +data class DrugCategories( + val martindale: String?, + val martindaleUrl: String?, + val therapeutic: List?, + val therapeuticUrls: List? +) + +data class Comment( + val author: String, + val date: String, + val text: String, + val response: CommentResponse? +) + +data class CommentResponse( + val doctorName: String, + val doctorTitle: String, + val text: String, + val doctorUrl: String? +) \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/domain/repository/DrugRepository.kt b/app/src/main/java/com/approagency/drug/domain/repository/DrugRepository.kt index e3c9591..0ae75ef 100644 --- a/app/src/main/java/com/approagency/drug/domain/repository/DrugRepository.kt +++ b/app/src/main/java/com/approagency/drug/domain/repository/DrugRepository.kt @@ -5,6 +5,7 @@ import com.approagency.drug.data.dto.DrugListResponse import com.approagency.drug.data.dto.DrugModels 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 @@ -13,4 +14,6 @@ interface DrugRepository { suspend fun drugDetail(cod:Int): Result suspend fun getGorohDaroei(): Result suspend fun searchDrugs(params:DaroYabParams):Result + suspend fun getDrugDetailFromYab(detailUrl: String):Result + } \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/domain/usecase/DrugDetailYabUseCase.kt b/app/src/main/java/com/approagency/drug/domain/usecase/DrugDetailYabUseCase.kt new file mode 100644 index 0000000..9bbc1be --- /dev/null +++ b/app/src/main/java/com/approagency/drug/domain/usecase/DrugDetailYabUseCase.kt @@ -0,0 +1,14 @@ +package com.approagency.drug.domain.usecase + +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.repository.DrugRepository + +class DrugDetailYabUseCase ( + private val repository: DrugRepository +) { + suspend operator fun invoke(detailUrl: String):Result { + return repository.getDrugDetailFromYab(detailUrl) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/domain/usecase/SearchDrugsYabUseCase.kt b/app/src/main/java/com/approagency/drug/domain/usecase/SearchDrugsYabUseCase.kt index d3dcd78..05029f0 100644 --- a/app/src/main/java/com/approagency/drug/domain/usecase/SearchDrugsYabUseCase.kt +++ b/app/src/main/java/com/approagency/drug/domain/usecase/SearchDrugsYabUseCase.kt @@ -14,4 +14,4 @@ class SearchDrugsYabUseCase ( val params = DaroYabParams(query = query, pageNumber = pageNumber) return repository.searchDrugs(params) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/approagency/drug/navigation/AppNavGraph.kt b/app/src/main/java/com/approagency/drug/navigation/AppNavGraph.kt new file mode 100644 index 0000000..2839b4b --- /dev/null +++ b/app/src/main/java/com/approagency/drug/navigation/AppNavGraph.kt @@ -0,0 +1,23 @@ +package com.approagency.drug.navigation + +import androidx.compose.runtime.Composable +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController + +@Composable +fun AppNavGraph() { + + val navController = rememberNavController() + + NavHost( + navController = navController, + startDestination = "main_container" + ) { + + composable("main_container") { + + MainContainer() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/navigation/BottomBar.kt b/app/src/main/java/com/approagency/drug/navigation/BottomBar.kt new file mode 100644 index 0000000..f0b012f --- /dev/null +++ b/app/src/main/java/com/approagency/drug/navigation/BottomBar.kt @@ -0,0 +1,123 @@ +package com.approagency.drug.navigation + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Build +import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.rounded.Refresh +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationBarItemDefaults +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +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.NavDestination.Companion.hierarchy +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.NavHostController +import androidx.navigation.compose.currentBackStackEntryAsState + +@Composable +fun BottomBar( + navController: NavHostController +) { + val items = listOf( + Triple(MainRoute.HomeGraph, "دارو", Icons.Default.Search), + Triple(MainRoute.SearchGraph, "سرچ", Icons.Rounded.Refresh), + Triple(MainRoute.LabGraph, "آزمایش", Icons.Default.Build) + ) + + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentDestination = navBackStackEntry?.destination + Surface( + tonalElevation = 2.dp, + color = MaterialTheme.colorScheme.surface, + modifier = Modifier + .navigationBarsPadding() + .height(50.dp) + ) { + Row( + modifier = Modifier.fillMaxSize(), + verticalAlignment = Alignment.CenterVertically + ) { + + items.forEach { item -> + + val selected = currentDestination + ?.hierarchy + ?.any { it.route == item.first.route } == true + + val color = if (selected) + MaterialTheme.colorScheme.primary + else + MaterialTheme.colorScheme.onSurfaceVariant + + val background = if (selected) + MaterialTheme.colorScheme.primary.copy(alpha = 0.12f) + else + Color.Transparent + + Box( + modifier = Modifier + .weight(1f) + .padding(horizontal = 8.dp , vertical = 4.dp) + .clip(MaterialTheme.shapes.large) + .background(background) + .clickable { + navController.navigate(item.first.route) { + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + } + .padding(horizontal = 10.dp, vertical = 10.dp), + contentAlignment = Alignment.Center + ) { + + Row( + verticalAlignment = Alignment.CenterVertically + ) { + + Icon( + imageVector = item.third, + contentDescription = item.second, + tint = color, + modifier = Modifier.size(22.dp) + ) + + Spacer(modifier = Modifier.width(6.dp)) + + Text( + text = item.second, + color = color, + style = MaterialTheme.typography.labelMedium + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/navigation/Graph.kt b/app/src/main/java/com/approagency/drug/navigation/Graph.kt deleted file mode 100644 index 97c2b7b..0000000 --- a/app/src/main/java/com/approagency/drug/navigation/Graph.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.approagency.drug.navigation - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.navigation.NavController -import androidx.navigation.NavHostController -import androidx.navigation.compose.NavHost -import com.approagency.drug.presentation.screens.HomeScreen -import androidx.navigation.compose.composable -import com.approagency.drug.presentation.screens.RootScreen - -@Composable -fun AppNavGraph(navController: NavHostController, modifier: Modifier = Modifier) { - NavHost( - navController = navController , - startDestination = NavRoutes.Home.route - ){ - composable(NavRoutes.Home.route) { - RootScreen( - navHostController = navController , modifier = Modifier - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/navigation/MainContainer.kt b/app/src/main/java/com/approagency/drug/navigation/MainContainer.kt new file mode 100644 index 0000000..e01a36b --- /dev/null +++ b/app/src/main/java/com/approagency/drug/navigation/MainContainer.kt @@ -0,0 +1,37 @@ +package com.approagency.drug.navigation + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.rememberNavController +import com.approagency.drug.navigation.graphs.homeGraph +import com.approagency.drug.navigation.graphs.labGraph +import com.approagency.drug.navigation.graphs.searchGraph + +@Composable +fun MainContainer() { + + val navController = rememberNavController() + + Scaffold( + bottomBar = { + BottomBar(navController) + } + ) { padding -> + + NavHost( + navController = navController, + startDestination = MainRoute.HomeGraph.route, + modifier = Modifier.padding(padding) + ) { + + homeGraph(navController) + + searchGraph(navController) + + labGraph(navController) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/navigation/NavRoutes.kt b/app/src/main/java/com/approagency/drug/navigation/NavRoutes.kt deleted file mode 100644 index a866313..0000000 --- a/app/src/main/java/com/approagency/drug/navigation/NavRoutes.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.approagency.drug.navigation - -abstract class NavRoutes (val route:String) { - object Home: NavRoutes("home") - object Detail: NavRoutes("detail/{cod}"){ - fun createRoute(cod: Int) = "detail/$cod" - } - object Lab: NavRoutes("lab") -} \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/navigation/Routes.kt b/app/src/main/java/com/approagency/drug/navigation/Routes.kt new file mode 100644 index 0000000..4d9bdb6 --- /dev/null +++ b/app/src/main/java/com/approagency/drug/navigation/Routes.kt @@ -0,0 +1,32 @@ +package com.approagency.drug.navigation + +import android.net.Uri + +sealed class MainRoute( + val route: String +) { + + object HomeGraph : MainRoute("home_graph") + + object SearchGraph : MainRoute("search_graph") + + object LabGraph : MainRoute("lab_graph") +} + +sealed class Screen( + val route: String +) { + + object Home : Screen("home") + + object Search : Screen("search") + + object Lab : Screen("lab") + + object DrugDetail : Screen("drug_detail/{detailUrl}") { + + fun createRoute(detailUrl: String): String { + return "drug_detail/${Uri.encode(detailUrl)}" + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/navigation/graphs/HomeGraph.kt b/app/src/main/java/com/approagency/drug/navigation/graphs/HomeGraph.kt new file mode 100644 index 0000000..fac7490 --- /dev/null +++ b/app/src/main/java/com/approagency/drug/navigation/graphs/HomeGraph.kt @@ -0,0 +1,28 @@ +package com.approagency.drug.navigation.graphs + +import androidx.compose.ui.Modifier +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.compose.composable +import androidx.navigation.navigation +import com.approagency.drug.navigation.MainRoute +import com.approagency.drug.navigation.Screen +import com.approagency.drug.presentation.screens.HomeScreen + +fun NavGraphBuilder.homeGraph( + navController: NavHostController +) { + + navigation( + route = MainRoute.HomeGraph.route, + startDestination = Screen.Home.route + ) { + + composable(Screen.Home.route) { + + HomeScreen( + navController = navController , modifier = Modifier + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/navigation/graphs/LabGraph.kt b/app/src/main/java/com/approagency/drug/navigation/graphs/LabGraph.kt new file mode 100644 index 0000000..89b61f0 --- /dev/null +++ b/app/src/main/java/com/approagency/drug/navigation/graphs/LabGraph.kt @@ -0,0 +1,25 @@ +package com.approagency.drug.navigation.graphs + +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.compose.composable +import androidx.navigation.navigation +import com.approagency.drug.navigation.MainRoute +import com.approagency.drug.navigation.Screen +import com.approagency.drug.presentation.screens.LabScreen + +fun NavGraphBuilder.labGraph( + navController: NavHostController +) { + + navigation( + route = MainRoute.LabGraph.route, + startDestination = Screen.Lab.route + ) { + + composable(Screen.Lab.route) { + + LabScreen() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/navigation/graphs/SearchGraph.kt b/app/src/main/java/com/approagency/drug/navigation/graphs/SearchGraph.kt new file mode 100644 index 0000000..0a259b1 --- /dev/null +++ b/app/src/main/java/com/approagency/drug/navigation/graphs/SearchGraph.kt @@ -0,0 +1,51 @@ +package com.approagency.drug.navigation.graphs + +import android.net.Uri +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.NavType +import androidx.navigation.compose.composable +import androidx.navigation.navArgument +import androidx.navigation.navigation +import com.approagency.drug.navigation.MainRoute +import com.approagency.drug.navigation.Screen +import com.approagency.drug.presentation.screens.DrugDetailScreen +import com.approagency.drug.presentation.screens.SearchScreen + +fun NavGraphBuilder.searchGraph( + navController: NavHostController +) { + + navigation( + route = MainRoute.SearchGraph.route, + startDestination = Screen.Search.route + ) { + + composable(Screen.Search.route) { + + SearchScreen( + navController = navController + ) + } + + composable( + route = Screen.DrugDetail.route, + arguments = listOf( + navArgument("detailUrl") { + type = NavType.StringType + } + ) + ) { + + val detailUrl = + Uri.decode( + it.arguments?.getString("detailUrl") ?: "" + ) + + DrugDetailScreen( + navController = navController, + detailUrl = detailUrl + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/presentation/components/showYabDetail.kt b/app/src/main/java/com/approagency/drug/presentation/components/showYabDetail.kt new file mode 100644 index 0000000..de08c02 --- /dev/null +++ b/app/src/main/java/com/approagency/drug/presentation/components/showYabDetail.kt @@ -0,0 +1,516 @@ +package com.approagency.drug.presentation.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Divider +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilterChip +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.approagency.drug.R +import com.approagency.drug.domain.model.DrugDetail +import com.approagency.drug.presentation.common.CustomModalBottomSheet +import com.approagency.drug.presentation.common.ErrorState +import com.approagency.drug.presentation.viewModel.DrugDetailViewModel +import com.approagency.drug.presentation.viewModel.DrugDetailYabState +import com.vada.caller.ui.theme.LocalDime +import com.vada.caller.ui.theme.dime +import kotlinx.coroutines.delay + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DrugDetailBottomSheet( + detailUrl: String?, + onDismiss: () -> Unit, + viewModel: DrugDetailViewModel +) { + val dime = LocalDime.current + val detailState by viewModel.detailState.collectAsStateWithLifecycle() + + // Load detail when sheet opens with a valid URL + LaunchedEffect(detailUrl) { + if (detailUrl != null) { + viewModel.loadDrugDetail(detailUrl) + } + } + + // Reset when sheet is closed + LaunchedEffect(detailUrl) { + if (detailUrl == null) { + viewModel.reset() + } + } + + if (detailUrl != null) { + CustomModalBottomSheet( + onDismiss = { + onDismiss() + viewModel.reset() + } + ) { + Surface( + modifier = Modifier.fillMaxWidth(), + color = MaterialTheme.colorScheme.surface + ) { + when (val state = detailState) { + DrugDetailYabState.Idle -> { + // Initial state, nothing to show yet + } + + DrugDetailYabState.Loading -> { + Box( + modifier = Modifier + .fillMaxWidth() + .height(400.dp) + .padding(dime.xl), + contentAlignment = Alignment.Center + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + CircularProgressIndicator( + color = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.height(dime.md)) + Text( + text = "در حال دریافت اطلاعات دارو...", + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f) + ) + } + } + } + + is DrugDetailYabState.Success -> { + DrugDetailContent( + drugDetail = state.drugDetail, + modifier = Modifier.padding(bottom = dime.lg) + ) + } + + is DrugDetailYabState.Error -> { + Box( + modifier = Modifier + .fillMaxWidth() + .height(300.dp) + .padding(dime.xl), + contentAlignment = Alignment.Center + ) { + ErrorState( + message = state.message, + onRetry = { viewModel.loadDrugDetail(detailUrl) } + ) + } + } + } + } + } + } +} + +@Composable +fun DrugDetailContent( + drugDetail: DrugDetail, + modifier: Modifier = Modifier +) { + val dime = LocalDime.current + var selectedTab by remember { mutableStateOf(0) } + val tabs = listOf("عمومی", "تخصصی", "اشکال دارویی", "اسامی تجاری") + + Column( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = dime.md) + ) { + // Header with close button + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = dime.md), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier.weight(1f) + ) { + Text( + text = drugDetail.persianName, + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.primary + ) + if (drugDetail.englishName.isNotEmpty()) { + Text( + text = drugDetail.englishName, + fontSize = 14.sp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f) + ) + } + } + } + + // Categories + if (drugDetail.drugClass != null || drugDetail.therapeuticClass != null) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = dime.sm), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f) + ), + shape = RoundedCornerShape(dime.sm) + ) { + Column( + modifier = Modifier.padding(dime.sm) + ) { + drugDetail.drugClass?.let { + Row { + Text( + text = "طبقه بندی: ", + fontWeight = FontWeight.Bold, + fontSize = 13.sp + ) + Text( + text = it, + fontSize = 13.sp, + color = MaterialTheme.colorScheme.primary + ) + } + } + drugDetail.therapeuticClass?.let { + Spacer(modifier = Modifier.height(4.dp)) + Row { + Text( + text = "طبقه درمانی: ", + fontWeight = FontWeight.Bold, + fontSize = 13.sp + ) + Text( + text = it, + fontSize = 13.sp, + color = MaterialTheme.colorScheme.primary + ) + } + } + } + } + } + + // Tabs + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = dime.sm) + ) { + tabs.forEachIndexed { index, title -> + androidx.compose.material3.Tab( + selected = selectedTab == index, + onClick = { selectedTab = index }, + modifier = Modifier.weight(1f), + text = { + Text( + text = title, + fontSize = 13.sp, + fontWeight = if (selectedTab == index) FontWeight.Bold else FontWeight.Normal + ) + } + ) + } + } + + Divider(color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)) + + // Tab content + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .height(500.dp) + .padding(vertical = dime.md) + ) { + when (selectedTab) { + 0 -> { + // General Information + item { + GeneralInfoTab(drugDetail) + } + } + 1 -> { + // Specialized Information + drugDetail.usage?.let { + item { + InfoSection(title = "موارد مصرف", content = it) + } + } + drugDetail.mechanism?.let { + item { + InfoSection(title = "مکانیسم اثر", content = it) + } + } + drugDetail.contraindications?.let { + item { + InfoSection(title = "موارد منع مصرف", content = it) + } + } + drugDetail.sideEffects?.let { + item { + InfoSection(title = "عوارض جانبی", content = it) + } + } + drugDetail.interactions?.let { + item { + InfoSection(title = "تداخلات دارویی", content = it) + } + } + drugDetail.warnings?.let { + item { + InfoSection(title = "هشدارها", content = it) + } + } + drugDetail.recommendations?.let { + item { + InfoSection(title = "توصیه‌های دارویی", content = it) + } + } + } + 2 -> { + // Dosage Forms + if (drugDetail.dosageForms.isNotEmpty()) { + item { + DosageFormsSection(dosageForms = drugDetail.dosageForms) + } + } + } + 3 -> { + // Brand Names + if (drugDetail.brandNames.isNotEmpty()) { + item { + BrandNamesSection(brandNames = drugDetail.brandNames) + } + } + } + } + } + } +} + +@Composable +fun GeneralInfoTab(drugDetail: DrugDetail) { + val dime = LocalDime.current + + Column { + // Pregnancy Info + if (drugDetail.pregnancyCategory != null || drugDetail.pregnancyDescription != null) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = dime.md), + colors = CardDefaults.cardColors( + containerColor = Color(0xFFFFF3E0) + ) + ) { + Column(modifier = Modifier.padding(dime.md)) { + Text( + text = "مصرف در بارداری", + fontWeight = FontWeight.Bold, + fontSize = 16.sp, + color = Color(0xFFE65100) + ) + Spacer(modifier = Modifier.height(dime.xs)) + drugDetail.pregnancyCategory?.let { + Text( + text = "گروه: $it", + fontSize = 14.sp, + fontWeight = FontWeight.Medium + ) + } + drugDetail.pregnancyDescription?.let { + Spacer(modifier = Modifier.height(dime.xs)) + Text( + text = it, + fontSize = 13.sp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f) + ) + } + } + } + } + + // Usage + drugDetail.usage?.let { + InfoSection(title = "موارد مصرف", content = it) + } + + // Contraindications + drugDetail.contraindications?.let { + InfoSection(title = "موارد منع مصرف", content = it) + } + + // Side Effects + drugDetail.sideEffects?.let { + InfoSection(title = "عوارض جانبی", content = it) + } + + // Warnings + drugDetail.warnings?.let { + InfoSection(title = "هشدارها", content = it) + } + } +} + +@Composable +fun InfoSection( + title: String, + content: String, + modifier: Modifier = Modifier +) { + val dime = LocalDime.current + + Column( + modifier = modifier + .fillMaxWidth() + .padding(bottom = dime.md) + ) { + Text( + text = title, + fontWeight = FontWeight.Bold, + fontSize = 16.sp, + color = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.height(dime.xs)) + Divider(color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)) + Spacer(modifier = Modifier.height(dime.xs)) + Text( + text = content, + fontSize = 14.sp, + lineHeight = 22.sp, + textAlign = TextAlign.Justify, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.9f) + ) + } +} + +@Composable +fun DosageFormsSection(dosageForms: List) { + val dime = LocalDime.current + + Column { + Text( + text = "اشکال دارویی", + fontWeight = FontWeight.Bold, + fontSize = 16.sp, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(bottom = dime.sm) + ) + + dosageForms.forEach { form -> + Card( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = dime.sm), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column(modifier = Modifier.padding(dime.sm)) { + Text( + text = form.persianName, + fontWeight = FontWeight.Bold, + fontSize = 14.sp + ) + Text( + text = form.englishName, + fontSize = 12.sp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + ) + Row( + modifier = Modifier.padding(top = dime.xs), + horizontalArrangement = Arrangement.spacedBy(dime.sm) + ) { + if (form.isHighRisk) { + Card( + onClick = { }, + content = { Text("پرخطر", fontSize = 10.sp) }, + + ) + } + if (form.isVital) { + Card( + onClick = { }, + content = { Text("حیاتی", fontSize = 10.sp) }, + + ) + } + } + } + } + } + } +} + +@Composable +fun BrandNamesSection(brandNames: List) { + val dime = LocalDime.current + + Column { + Text( + text = "اسامی تجاری", + fontWeight = FontWeight.Bold, + fontSize = 16.sp, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(bottom = dime.sm) + ) + + brandNames.forEach { brand -> + Card( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = dime.sm), + elevation = CardDefaults.cardElevation(defaultElevation = 1.dp) + ) { + Column(modifier = Modifier.padding(dime.sm)) { + Text( + text = brand.persianName, + fontWeight = FontWeight.Medium, + fontSize = 14.sp + ) + brand.manufacturer?.let { + Text( + text = "تولید کننده: $it", + fontSize = 12.sp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/presentation/screens/DrugDetailScreen.kt b/app/src/main/java/com/approagency/drug/presentation/screens/DrugDetailScreen.kt new file mode 100644 index 0000000..f657d5d --- /dev/null +++ b/app/src/main/java/com/approagency/drug/presentation/screens/DrugDetailScreen.kt @@ -0,0 +1,564 @@ +package com.approagency.drug.presentation.screens + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +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.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.filled.FavoriteBorder +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Divider +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Tab +import androidx.compose.material3.TabRow +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavController +import com.approagency.drug.domain.model.DrugDetail +import com.approagency.drug.presentation.common.ErrorState +import com.approagency.drug.presentation.viewModel.DrugDetailViewModel +import com.approagency.drug.presentation.viewModel.DrugDetailYabState +import com.vada.caller.ui.theme.LocalDime +import com.vada.caller.ui.theme.dime +import org.koin.androidx.compose.koinViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DrugDetailScreen( + navController: NavController, + detailUrl: String, + viewModel: DrugDetailViewModel = koinViewModel() +) { + val dime = LocalDime.current + val detailState by viewModel.detailState.collectAsStateWithLifecycle() + var selectedTab by remember { mutableIntStateOf(0) } + val tabs = listOf("عمومی", "تخصصی", "اشکال دارویی", "اسامی تجاری") + + LaunchedEffect(detailUrl) { + viewModel.loadDrugDetail(detailUrl) + } + + Scaffold( + topBar = { + TopAppBar( + title = { + when (val state = detailState) { + is DrugDetailYabState.Success -> { + Column { + Text( + text = state.drugDetail.persianName.take(20), + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + maxLines = 1 + ) + if (state.drugDetail.englishName.isNotEmpty()) { + Text( + text = state.drugDetail.englishName.take(25), + fontSize = 12.sp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), + maxLines = 1 + ) + } + } + } + else -> { + Text("جزئیات دارو", fontSize = 16.sp) + } + } + }, + navigationIcon = { + IconButton(onClick = { navController.popBackStack() }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "بازگشت" + ) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.surface + ) + ) + } + ) { paddingValues -> + Box( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + when (val state = detailState) { + DrugDetailYabState.Idle -> { + // Initial state + } + + DrugDetailYabState.Loading -> { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + CircularProgressIndicator( + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(48.dp) + ) + Spacer(modifier = Modifier.height(dime.md)) + Text( + text = "در حال دریافت اطلاعات دارو...", + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f) + ) + } + } + } + + is DrugDetailYabState.Success -> { + Column( + modifier = Modifier.fillMaxSize() + ) { + // Tab Row + TabRow( + selectedTabIndex = selectedTab, + containerColor = MaterialTheme.colorScheme.surface, + contentColor = MaterialTheme.colorScheme.primary + ) { + tabs.forEachIndexed { index, title -> + Tab( + selected = selectedTab == index, + onClick = { selectedTab = index }, + text = { + Text( + text = title, + fontSize = 14.sp, + fontWeight = if (selectedTab == index) FontWeight.Bold else FontWeight.Normal + ) + } + ) + } + } + + // Tab Content + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = dime.md) + .padding(top = dime.md), + verticalArrangement = Arrangement.spacedBy(dime.md) + ) { + when (selectedTab) { + 0 -> { + item { GeneralInfoTabContent(state.drugDetail) } + } + 1 -> { + item { SpecializedInfoTabContent(state.drugDetail) } + } + 2 -> { + item { DosageFormsTabContent(state.drugDetail.dosageForms) } + } + 3 -> { + item { BrandNamesTabContent(state.drugDetail.brandNames) } + } + } + } + } + } + + is DrugDetailYabState.Error -> { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + ErrorState( + message = state.message, + onRetry = { viewModel.loadDrugDetail(detailUrl) } + ) + } + } + } + } + } +} + +@Composable +fun GeneralInfoTabContent(drugDetail: DrugDetail) { + val dime = LocalDime.current + + Column(verticalArrangement = Arrangement.spacedBy(dime.md)) { + // Categories Card + if (drugDetail.drugClass != null || drugDetail.therapeuticClass != null) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.2f) + ), + shape = RoundedCornerShape(dime.sm) + ) { + Column( + modifier = Modifier.padding(dime.md), + verticalArrangement = Arrangement.spacedBy(dime.xs) + ) { + drugDetail.drugClass?.let { + Row { + Text( + text = "طبقه بندی مارتیندل: ", + fontWeight = FontWeight.Bold, + fontSize = 14.sp + ) + Text( + text = it, + fontSize = 14.sp, + color = MaterialTheme.colorScheme.primary + ) + } + } + drugDetail.therapeuticClass?.let { + Row { + Text( + text = "طبقه درمانی: ", + fontWeight = FontWeight.Bold, + fontSize = 14.sp + ) + Text( + text = it, + fontSize = 14.sp, + color = MaterialTheme.colorScheme.primary + ) + } + } + } + } + } + + // Pregnancy Card + if (drugDetail.pregnancyCategory != null || drugDetail.pregnancyDescription != null) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = Color(0xFFFFF3E0) + ), + shape = RoundedCornerShape(dime.sm) + ) { + Column( + modifier = Modifier.padding(dime.md), + verticalArrangement = Arrangement.spacedBy(dime.xs) + ) { + Text( + text = "مصرف در بارداری", + fontWeight = FontWeight.Bold, + fontSize = 16.sp, + color = Color(0xFFE65100) + ) + drugDetail.pregnancyCategory?.let { + Text( + text = "گروه: $it", + fontSize = 14.sp, + fontWeight = FontWeight.Medium + ) + } + drugDetail.pregnancyDescription?.let { + Text( + text = it, + fontSize = 13.sp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f) + ) + } + } + } + } + + // Usage Section + drugDetail.usage?.let { + InfoSectionCard(title = "موارد مصرف", content = it) + } + + // Contraindications Section + drugDetail.contraindications?.let { + InfoSectionCard(title = "موارد منع مصرف", content = it) + } + + // Side Effects Section + drugDetail.sideEffects?.let { + InfoSectionCard(title = "عوارض جانبی", content = it) + } + + // Warnings Section + drugDetail.warnings?.let { + InfoSectionCard(title = "هشدارها", content = it) + } + } +} + +@Composable +fun SpecializedInfoTabContent(drugDetail: DrugDetail) { + Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { + drugDetail.usage?.let { + InfoSectionCard(title = "موارد مصرف", content = it) + } + drugDetail.mechanism?.let { + InfoSectionCard(title = "مکانیسم اثر", content = it) + } + drugDetail.pharmacokinetics?.let { + InfoSectionCard(title = "فارماکوکینتیک", content = it) + } + drugDetail.contraindications?.let { + InfoSectionCard(title = "موارد منع مصرف", content = it) + } + drugDetail.sideEffects?.let { + InfoSectionCard(title = "عوارض جانبی", content = it) + } + drugDetail.interactions?.let { + InfoSectionCard(title = "تداخلات دارویی", content = it) + } + drugDetail.warnings?.let { + InfoSectionCard(title = "هشدارها", content = it) + } + drugDetail.recommendations?.let { + InfoSectionCard(title = "توصیه‌های دارویی", content = it) + } + + if (drugDetail.usage == null && + drugDetail.mechanism == null && + drugDetail.contraindications == null && + drugDetail.sideEffects == null + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(32.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "اطلاعات تخصصی برای این دارو ثبت نشده است", + fontSize = 14.sp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + textAlign = TextAlign.Center + ) + } + } + } +} + +@Composable +fun DosageFormsTabContent(dosageForms: List) { + val dime = LocalDime.current + + if (dosageForms.isEmpty()) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(32.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "اشکال دارویی برای این دارو ثبت نشده است", + fontSize = 14.sp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + textAlign = TextAlign.Center + ) + } + } else { + Column(verticalArrangement = Arrangement.spacedBy(dime.sm)) { + dosageForms.forEach { form -> + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), + shape = RoundedCornerShape(dime.sm) + ) { + Column( + modifier = Modifier.padding(dime.md), + verticalArrangement = Arrangement.spacedBy(dime.xs) + ) { + Text( + text = form.persianName, + fontWeight = FontWeight.Bold, + fontSize = 15.sp + ) + if (form.englishName.isNotBlank()) { + Text( + text = form.englishName, + fontSize = 12.sp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + ) + } + Row( + horizontalArrangement = Arrangement.spacedBy(dime.sm) + ) { + if (form.isHighRisk) { + androidx.compose.material3.Surface( + shape = RoundedCornerShape(4.dp), + color = Color(0xFFFFEBEE), + modifier = Modifier.padding(vertical = 4.dp) + ) { + Text( + text = "پرخطر", + fontSize = 10.sp, + color = Color(0xFFC62828), + modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp) + ) + } + } + if (form.isVital) { + androidx.compose.material3.Surface( + shape = RoundedCornerShape(4.dp), + color = Color(0xFFE8F5E9), + modifier = Modifier.padding(vertical = 4.dp) + ) { + Text( + text = "حیاتی", + fontSize = 10.sp, + color = Color(0xFF2E7D32), + modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp) + ) + } + } + } + if (!form.warningLabel.isNullOrBlank()) { + Text( + text = "⚠️ ${form.warningLabel}", + fontSize = 11.sp, + color = Color(0xFFE65100), + modifier = Modifier.padding(top = dime.xs) + ) + } + } + } + } + } + } +} + +@Composable +fun BrandNamesTabContent(brandNames: List) { + val dime = LocalDime.current + + if (brandNames.isEmpty()) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(32.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "اسامی تجاری برای این دارو ثبت نشده است", + fontSize = 14.sp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + textAlign = TextAlign.Center + ) + } + } else { + Column(verticalArrangement = Arrangement.spacedBy(dime.sm)) { + brandNames.forEach { brand -> + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 1.dp), + shape = RoundedCornerShape(dime.sm) + ) { + Column( + modifier = Modifier.padding(dime.md), + verticalArrangement = Arrangement.spacedBy(dime.xs) + ) { + Text( + text = brand.persianName, + fontWeight = FontWeight.Medium, + fontSize = 14.sp + ) + brand.manufacturer?.let { + Row { + Text( + text = "تولید کننده: ", + fontSize = 12.sp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + ) + Text( + text = it, + fontSize = 12.sp, + color = MaterialTheme.colorScheme.primary + ) + } + } + brand.importer?.let { + Row { + Text( + text = "وارد کننده: ", + fontSize = 12.sp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + ) + Text( + text = it, + fontSize = 12.sp, + color = MaterialTheme.colorScheme.primary + ) + } + } + } + } + } + } + } +} + +@Composable +fun InfoSectionCard( + title: String, + content: String, + modifier: Modifier = Modifier +) { + val dime = LocalDime.current + + Card( + modifier = modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 1.dp), + shape = RoundedCornerShape(dime.sm) + ) { + Column( + modifier = Modifier.padding(dime.md), + verticalArrangement = Arrangement.spacedBy(dime.xs) + ) { + Text( + text = title, + fontWeight = FontWeight.Bold, + fontSize = 16.sp, + color = MaterialTheme.colorScheme.primary + ) + Divider(color = MaterialTheme.colorScheme.outline.copy(alpha = 0.2f)) + Text( + text = content, + fontSize = 14.sp, + lineHeight = 22.sp, + textAlign = TextAlign.Justify, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.85f) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/presentation/screens/SearchScreen.kt b/app/src/main/java/com/approagency/drug/presentation/screens/SearchScreen.kt index 6d4ea11..fec7b0b 100644 --- a/app/src/main/java/com/approagency/drug/presentation/screens/SearchScreen.kt +++ b/app/src/main/java/com/approagency/drug/presentation/screens/SearchScreen.kt @@ -1,21 +1,16 @@ package com.approagency.drug.presentation.screens -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.Button -import androidx.compose.material3.Card import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -27,14 +22,9 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.navigation.NavController -import com.approagency.drug.domain.model.DaroYabSearchResult -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 import com.approagency.drug.presentation.common.EndOfListIndicator @@ -42,12 +32,12 @@ import com.approagency.drug.presentation.common.ErrorState 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.PaginationControls +import com.approagency.drug.presentation.components.DrugDetailBottomSheet +import com.approagency.drug.presentation.viewModel.DrugDetailViewModel import com.approgency.drug.presentation.viewModel.SearchState import com.approgency.drug.presentation.viewModel.SearchViewModel import com.vada.caller.ui.theme.LocalDime import com.vada.caller.ui.theme.dime -import kotlinx.coroutines.flow.collect import org.koin.androidx.compose.koinViewModel @@ -55,22 +45,29 @@ import org.koin.androidx.compose.koinViewModel fun SearchScreen( navController: NavController, modifier: Modifier = Modifier, - viewModel: SearchViewModel = koinViewModel() + viewModel: SearchViewModel = koinViewModel(), + drugDetailViewModel: DrugDetailViewModel = koinViewModel() // اضافه کنید ) { val dime = LocalDime.current val keyboardController = LocalSoftwareKeyboardController.current - var searchText by remember { mutableStateOf("") } + val searchText = viewModel.searchText val state by viewModel.searchState.collectAsState() val lazyListState = rememberLazyListState() - + var selectedDrugUrl by remember { mutableStateOf(null) } // Auto-search for testing (remove in production) - LaunchedEffect(Unit) { - if (searchText.isEmpty()) { - searchText = "انتی" - viewModel.searchDrugs(searchText) - } +// LaunchedEffect(Unit) { +// if (searchText.isEmpty()) { +// searchText = "انتی" +// viewModel.searchDrugs(searchText) +// } +// } + if (selectedDrugUrl != null) { + DrugDetailBottomSheet( + detailUrl = selectedDrugUrl, + onDismiss = { selectedDrugUrl = null }, + viewModel = drugDetailViewModel + ) } - // Detect when user scrolls to the bottom to load more LaunchedEffect(lazyListState) { snapshotFlow { lazyListState.layoutInfo.visibleItemsInfo.lastOrNull()?.index } @@ -95,7 +92,7 @@ fun SearchScreen( // Search input CustomTextFilled( value = searchText, - onValueChange = { searchText = it }, + onValueChange = { viewModel.updateSearchText(it) }, onSearch = { query -> if (query.isNotBlank()) { keyboardController?.hide() @@ -105,7 +102,7 @@ fun SearchScreen( placeholder = "جستجوی دارو", showClearButton = true, showSearchButton = true, - autoSearch = true, // Set to true if you want search while typing + autoSearch = false, height = 45 ) Spacer(modifier = Modifier.height(MaterialTheme.dime.xs)) @@ -144,9 +141,12 @@ fun SearchScreen( items(currentState.currentItems) { drug -> DaroYabSearchResult( drug = drug, - onClickDetail = { selectedDrug ->println(selectedDrug.detailPageUrl) - // Navigate to drug detail - // navController.navigate("drug_detail/${selectedDrug.genericId}") + onClickDetail = { selectedDrug -> + navController.navigate( + Screen.DrugDetail.createRoute( + selectedDrug.detailPageUrl + ) + ) }, onClickDrugStore = {} ) @@ -165,9 +165,12 @@ fun SearchScreen( items(currentState.drugs) { drug -> DaroYabSearchResult( drug = drug, - onClickDetail = { selectedDrug -> println(selectedDrug.detailPageUrl) - // Navigate to drug detail - // navController.navigate("drug_detail/${selectedDrug.genericId}") + onClickDetail = { selectedDrug -> + navController.navigate( + Screen.DrugDetail.createRoute( + selectedDrug.detailPageUrl + ) + ) }, onClickDrugStore = {} ) diff --git a/app/src/main/java/com/approagency/drug/presentation/viewModel/DrugDetailViewModel.kt b/app/src/main/java/com/approagency/drug/presentation/viewModel/DrugDetailViewModel.kt new file mode 100644 index 0000000..b82e3e9 --- /dev/null +++ b/app/src/main/java/com/approagency/drug/presentation/viewModel/DrugDetailViewModel.kt @@ -0,0 +1,40 @@ +package com.approagency.drug.presentation.viewModel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.approagency.drug.domain.model.DrugDetail +import com.approagency.drug.domain.usecase.DrugDetailYabUseCase +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +class DrugDetailViewModel( + private val drugDetailUseCase: DrugDetailYabUseCase +) : ViewModel() { + + private val _detailState = MutableStateFlow(DrugDetailYabState.Idle) + val detailState: StateFlow = _detailState.asStateFlow() + + fun loadDrugDetail(detailUrl: String) { + viewModelScope.launch { + _detailState.value = DrugDetailYabState.Loading + val result = drugDetailUseCase(detailUrl) + _detailState.value = when { + result.isSuccess -> DrugDetailYabState.Success(result.getOrNull()!!) + else -> DrugDetailYabState.Error(result.exceptionOrNull()?.message ?: "خطا در دریافت اطلاعات") + } + } + } + + fun reset() { + _detailState.value = DrugDetailYabState.Idle + } +} + +sealed class DrugDetailYabState { + object Idle : DrugDetailYabState() + object Loading : DrugDetailYabState() + data class Success(val drugDetail: DrugDetail) : DrugDetailYabState() + data class Error(val message: String) : DrugDetailYabState() +} \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/presentation/viewModel/SearchViewModel.kt b/app/src/main/java/com/approagency/drug/presentation/viewModel/SearchViewModel.kt index 11afd4f..35a27ad 100644 --- a/app/src/main/java/com/approagency/drug/presentation/viewModel/SearchViewModel.kt +++ b/app/src/main/java/com/approagency/drug/presentation/viewModel/SearchViewModel.kt @@ -1,5 +1,8 @@ package com.approgency.drug.presentation.viewModel +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.approagency.drug.domain.model.DrugSearchResult @@ -16,7 +19,12 @@ class SearchViewModel( private val _searchState = MutableStateFlow(SearchState.Idle) val searchState: StateFlow = _searchState.asStateFlow() + var searchText by mutableStateOf("") + private set + fun updateSearchText(value: String) { + searchText = value + } private var currentQuery = "" private var currentPage = 1 private var totalPages = 1