From 1fb7477b5c8f384394a93e8bd82690ebd661dd2a Mon Sep 17 00:00:00 2001 From: AmirmahdiNourkazemi Date: Mon, 4 May 2026 13:20:04 +0330 Subject: [PATCH] feat: add btn nav --- app/build.gradle.kts | 6 +- .../java/com/approagency/drug/MainActivity.kt | 30 +- .../approagency/drug/data/dto/DarmanModel.kt | 13 + .../drug/data/remote/DrugApiService.kt | 5 + .../data/repository/DrugRepositoryImpl.kt | 20 ++ .../java/com/approagency/drug/di/AppModule.kt | 13 +- .../drug/domain/repository/DrugRepository.kt | 4 + .../drug/domain/usecase/GetDarmanUseCase.kt | 12 + .../domain/usecase/GetDrugDetailUseCase.kt | 12 + .../com/approagency/drug/navigation/Graph.kt | 24 ++ .../approagency/drug/navigation/NavRoutes.kt | 9 + .../presentation/common/CustomModalDialog.kt | 1 - .../drug/presentation/common/loading.kt | 15 +- .../drug/presentation/common/primaryButton.kt | 5 +- .../presentation/components/DarmanContent.kt | 156 ++++++++++ .../components/DrugListContent.kt | 152 +++++++++ .../components/EmptyStateContent.kt | 40 +++ .../presentation/components/ShowDrugDetail.kt | 225 ++++++++++++++ .../drug/presentation/screens/HomeScreen.kt | 294 ++++++++++++------ .../drug/presentation/screens/LabScreen.kt | 12 + .../drug/presentation/screens/RootScreen.kt | 128 ++++++++ .../presentation/viewModel/HomeViewModel.kt | 119 ++++++- .../com/approagency/drug/ui/theme/Theme.kt | 76 ++++- gradle/libs.versions.toml | 7 +- gradle/wrapper/gradle-wrapper.properties | 4 +- settings.gradle.kts | 10 +- 26 files changed, 1240 insertions(+), 152 deletions(-) create mode 100644 app/src/main/java/com/approagency/drug/data/dto/DarmanModel.kt create mode 100644 app/src/main/java/com/approagency/drug/domain/usecase/GetDarmanUseCase.kt create mode 100644 app/src/main/java/com/approagency/drug/domain/usecase/GetDrugDetailUseCase.kt create mode 100644 app/src/main/java/com/approagency/drug/navigation/Graph.kt create mode 100644 app/src/main/java/com/approagency/drug/navigation/NavRoutes.kt create mode 100644 app/src/main/java/com/approagency/drug/presentation/components/DarmanContent.kt create mode 100644 app/src/main/java/com/approagency/drug/presentation/components/DrugListContent.kt create mode 100644 app/src/main/java/com/approagency/drug/presentation/components/EmptyStateContent.kt create mode 100644 app/src/main/java/com/approagency/drug/presentation/components/ShowDrugDetail.kt create mode 100644 app/src/main/java/com/approagency/drug/presentation/screens/LabScreen.kt create mode 100644 app/src/main/java/com/approagency/drug/presentation/screens/RootScreen.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2271fea..7f77823 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -50,6 +50,7 @@ dependencies { implementation(libs.androidx.compose.ui.graphics) implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.compose.material3) + implementation(libs.androidx.material3) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) @@ -57,7 +58,7 @@ dependencies { androidTestImplementation(libs.androidx.compose.ui.test.junit4) debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.test.manifest) - + implementation(libs.androidx.compose.material.icons.core) // ViewModel implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.androidx.lifecycle.viewmodel.ktx) @@ -78,4 +79,7 @@ dependencies { // Koin for DI implementation(libs.koin.android) implementation(libs.koin.androidx.compose.v410) + + //navigation + implementation(libs.androidx.navigation.compose) } \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/MainActivity.kt b/app/src/main/java/com/approagency/drug/MainActivity.kt index 6183f4d..4700cc0 100644 --- a/app/src/main/java/com/approagency/drug/MainActivity.kt +++ b/app/src/main/java/com/approagency/drug/MainActivity.kt @@ -9,14 +9,25 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.LayoutDirection +import androidx.navigation.compose.rememberNavController +import com.approagency.drug.navigation.AppNavGraph import com.approagency.drug.presentation.screens.HomeScreen import com.approagency.drug.ui.theme.DrugTheme +import com.vada.caller.ui.theme.dime class MainActivity : ComponentActivity() { @OptIn(ExperimentalMaterial3Api::class) @@ -25,18 +36,15 @@ class MainActivity : ComponentActivity() { enableEdgeToEdge() setContent { DrugTheme { - Scaffold(modifier = Modifier.fillMaxSize() , topBar = { - TopAppBar( - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.primaryContainer, - titleContentColor = MaterialTheme.colorScheme.primary, - ), - title = { - Text("Small Top App Bar") - } + val navController = rememberNavController() + val snackbarHostState = remember { SnackbarHostState() } + val scope = rememberCoroutineScope() + Scaffold(modifier = Modifier.fillMaxSize(), + ) { innerPadding -> + AppNavGraph( + navController = navController, + modifier = Modifier.padding(innerPadding) ) - },) { innerPadding -> - HomeScreen(modifier = Modifier.padding(innerPadding)) } } } diff --git a/app/src/main/java/com/approagency/drug/data/dto/DarmanModel.kt b/app/src/main/java/com/approagency/drug/data/dto/DarmanModel.kt new file mode 100644 index 0000000..449df41 --- /dev/null +++ b/app/src/main/java/com/approagency/drug/data/dto/DarmanModel.kt @@ -0,0 +1,13 @@ +package com.approagency.drug.data.dto + +data class DarmanModel( + val success : String, + val message:String, + val data: List +) + +data class DarmanList( + val cod : Int, + val nam_fa:String, + val nam_en:String, +) \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/data/remote/DrugApiService.kt b/app/src/main/java/com/approagency/drug/data/remote/DrugApiService.kt index e521a7e..4ff099c 100644 --- a/app/src/main/java/com/approagency/drug/data/remote/DrugApiService.kt +++ b/app/src/main/java/com/approagency/drug/data/remote/DrugApiService.kt @@ -1,5 +1,6 @@ package com.approagency.drug.data.remote +import com.approagency.drug.data.dto.DarmanModel import com.approagency.drug.data.dto.DrugListResponse import com.approagency.drug.data.dto.DrugModels import retrofit2.http.GET @@ -21,4 +22,8 @@ interface DrugApiService { suspend fun getDrugDetail( @Path("cod") cod: Int ): DrugModels + + + @GET("drugs/goroh-darmani") + suspend fun getGorohDaroei(): DarmanModel } \ 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 772b87b..8bbb131 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 @@ -1,6 +1,8 @@ package com.approagency.drug.data.repository +import com.approagency.drug.data.dto.DarmanModel import com.approagency.drug.data.dto.DrugListResponse +import com.approagency.drug.data.dto.DrugModels import com.approagency.drug.data.remote.DrugApiService import com.approagency.drug.domain.model.DrugSearchParams import com.approagency.drug.domain.repository.DrugRepository @@ -16,4 +18,22 @@ class DrugRepositoryImpl( Result.failure(e) } } + + override suspend fun drugDetail(cod: Int): Result { + return try { + val response= apiService.getDrugDetail(cod = cod) + return Result.success(response) + }catch (e: Exception){ + Result.failure(e) + } + } + + override suspend fun getGorohDaroei(): Result { + return try { + val response = apiService.getGorohDaroei() + return Result.success(response) + }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 a5ab769..0399a9e 100644 --- a/app/src/main/java/com/approagency/drug/di/AppModule.kt +++ b/app/src/main/java/com/approagency/drug/di/AppModule.kt @@ -5,6 +5,8 @@ import android.app.Application import com.approagency.drug.data.remote.DrugApiService import com.approagency.drug.data.repository.DrugRepositoryImpl import com.approagency.drug.domain.repository.DrugRepository +import com.approagency.drug.domain.usecase.GetDarmanUseCase +import com.approagency.drug.domain.usecase.GetDrugDetailUseCase import com.approagency.drug.domain.usecase.GetDrugSearchUseCase import com.approagency.drug.presentation.viewModel.HomeViewModel import com.approagency.drug.utils.Config @@ -34,10 +36,19 @@ val appModule= module { //UseCase single { GetDrugSearchUseCase(get()) + + } + + single{ + GetDrugDetailUseCase(get()) + } + + single { + GetDarmanUseCase(get()) } //view model viewModel { - HomeViewModel(get()) + HomeViewModel(get() , get() , get()) } } \ 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 bcee857..ac7a2a5 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 @@ -1,8 +1,12 @@ package com.approagency.drug.domain.repository +import com.approagency.drug.data.dto.DarmanModel import com.approagency.drug.data.dto.DrugListResponse +import com.approagency.drug.data.dto.DrugModels import com.approagency.drug.domain.model.DrugSearchParams interface DrugRepository { suspend fun searchDrug(params: DrugSearchParams): Result + suspend fun drugDetail(cod:Int): Result + suspend fun getGorohDaroei(): Result } \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/domain/usecase/GetDarmanUseCase.kt b/app/src/main/java/com/approagency/drug/domain/usecase/GetDarmanUseCase.kt new file mode 100644 index 0000000..f36aa61 --- /dev/null +++ b/app/src/main/java/com/approagency/drug/domain/usecase/GetDarmanUseCase.kt @@ -0,0 +1,12 @@ +package com.approagency.drug.domain.usecase + +import com.approagency.drug.data.dto.DarmanModel +import com.approagency.drug.domain.repository.DrugRepository + +class GetDarmanUseCase ( + private val repository: DrugRepository +){ + suspend operator fun invoke (): Result{ + return repository.getGorohDaroei() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/domain/usecase/GetDrugDetailUseCase.kt b/app/src/main/java/com/approagency/drug/domain/usecase/GetDrugDetailUseCase.kt new file mode 100644 index 0000000..38b5201 --- /dev/null +++ b/app/src/main/java/com/approagency/drug/domain/usecase/GetDrugDetailUseCase.kt @@ -0,0 +1,12 @@ +package com.approagency.drug.domain.usecase + +import com.approagency.drug.data.dto.DrugModels +import com.approagency.drug.domain.repository.DrugRepository + +class GetDrugDetailUseCase + ( + private val repository: DrugRepository){ + suspend operator fun invoke(cod:Int): Result{ + return repository.drugDetail(cod) + } +} \ 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 new file mode 100644 index 0000000..6c62357 --- /dev/null +++ b/app/src/main/java/com/approagency/drug/navigation/Graph.kt @@ -0,0 +1,24 @@ +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/NavRoutes.kt b/app/src/main/java/com/approagency/drug/navigation/NavRoutes.kt new file mode 100644 index 0000000..45712d4 --- /dev/null +++ b/app/src/main/java/com/approagency/drug/navigation/NavRoutes.kt @@ -0,0 +1,9 @@ +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/presentation/common/CustomModalDialog.kt b/app/src/main/java/com/approagency/drug/presentation/common/CustomModalDialog.kt index d95bc43..8d20cce 100644 --- a/app/src/main/java/com/approagency/drug/presentation/common/CustomModalDialog.kt +++ b/app/src/main/java/com/approagency/drug/presentation/common/CustomModalDialog.kt @@ -24,7 +24,6 @@ fun CustomModalDialog( ){ Surface( modifier = modifier - .fillMaxWidth() .wrapContentHeight(), shape = MaterialTheme.shapes.medium, color = MaterialTheme.colorScheme.surface, diff --git a/app/src/main/java/com/approagency/drug/presentation/common/loading.kt b/app/src/main/java/com/approagency/drug/presentation/common/loading.kt index ae69728..f66a966 100644 --- a/app/src/main/java/com/approagency/drug/presentation/common/loading.kt +++ b/app/src/main/java/com/approagency/drug/presentation/common/loading.kt @@ -1,18 +1,25 @@ package com.approagency.drug.presentation.common import android.content.res.Resources.Theme +import androidx.compose.ui.graphics.Color import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import com.vada.caller.ui.theme.dime @Composable -fun Loading() { +fun Loading( +color: Color = MaterialTheme.colorScheme.onPrimary, +modifier: Modifier = Modifier +) { CircularProgressIndicator( - color = MaterialTheme.colorScheme.onPrimary, - modifier = Modifier, - strokeWidth = 1.dp + color = color, + modifier = modifier, + strokeWidth = 2.dp ) } \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/presentation/common/primaryButton.kt b/app/src/main/java/com/approagency/drug/presentation/common/primaryButton.kt index 92bbb55..bb1ee46 100644 --- a/app/src/main/java/com/approagency/drug/presentation/common/primaryButton.kt +++ b/app/src/main/java/com/approagency/drug/presentation/common/primaryButton.kt @@ -1,5 +1,6 @@ package com.approagency.drug.presentation.common +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.material3.Button @@ -8,6 +9,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @Composable @@ -29,8 +31,7 @@ fun PrimaryButton( }, ) { if (isLoading == true) { - Loading( - ) + Text("... در حال جستجو" , textAlign = TextAlign.Right) } else { Text(text) } diff --git a/app/src/main/java/com/approagency/drug/presentation/components/DarmanContent.kt b/app/src/main/java/com/approagency/drug/presentation/components/DarmanContent.kt new file mode 100644 index 0000000..24893c7 --- /dev/null +++ b/app/src/main/java/com/approagency/drug/presentation/components/DarmanContent.kt @@ -0,0 +1,156 @@ +package com.approagency.drug.presentation.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +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.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.twotone.KeyboardArrowLeft +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.AbsoluteAlignment +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.approagency.drug.data.dto.DarmanList +import com.approagency.drug.data.dto.DarmanModel +import com.approagency.drug.data.dto.DrugDetail +import com.approagency.drug.data.dto.DrugListResponse +import com.approagency.drug.presentation.common.CustomBox +import com.approagency.drug.presentation.common.PrimaryButton +import com.vada.caller.ui.theme.dime +import kotlin.collections.emptyList + +@Composable +fun DarmanContent( + darmanData: Result?, + onDarmanClick: (Int) -> Unit, + onRetryClick:()-> Unit, +){ + val drugs = darmanData?.getOrNull()?.data ?: emptyList() + if (drugs.isEmpty()){ + CustomBox( + child = { + Column { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier + .fillMaxWidth() + .padding(all = MaterialTheme.dime.sm), + ) { + Text( + text = "مشکل در دریافت اطلاعات", + fontWeight = FontWeight.W500 + ) + Spacer(modifier = Modifier.width(MaterialTheme.dime.xs)) + Icon( + imageVector = Icons.Default.Search, + contentDescription = "Search" + ) + } + PrimaryButton( + text = "تلاش دوباره", + height = 45, + isLoading = false, + onClick = { + onRetryClick() + } + ) + } + } + ) + }else { + LazyColumn( + + ){ + items(drugs.count() , itemContent ={ + i -> DarmanItem( + darman = darmanData?.getOrNull()!!.data[i] , + onClick = { onDarmanClick(drugs[i].cod!!) } + ) + } ) + } + + } + + +} + +@Composable +fun DarmanItem( + darman: DarmanList, // Adjust this based on your actual data class + onClick: (Int?) -> Unit +) { + CustomBox( + modifier = Modifier + .padding(bottom = MaterialTheme.dime.sm) + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + onClick(darman.cod) + }, + child = { + Row( + modifier = Modifier.padding(all = MaterialTheme.dime.lg), + ) { + Column ( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.Start + ) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = AbsoluteAlignment.Right +// horizontalArrangement = Arrangement.SpaceBetween + ) { + Text(darman.nam_fa.toString() , style = MaterialTheme.typography.labelLarge , textAlign = TextAlign.Right ) + Text(darman.nam_en.toString() , style = MaterialTheme.typography.bodySmall) + } + Row ( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + TextButton( + onClick = { + onClick(darman.cod) + } + ) { + Row( + horizontalArrangement = Arrangement.Center + ) { + Icon( + imageVector = Icons.AutoMirrored.TwoTone.KeyboardArrowLeft + , contentDescription = "see more" + ) + Text("مشاهده لیست") + } + } + } + } + + } + } + ) +// Button( +// onClick = onClick, +// modifier = Modifier +// .fillMaxWidth() +// .padding(vertical = 4.dp) +// ) { +// drug.nam_fa?.let { Text(text = it) } // Adjust based on your drug model +// } +} \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/presentation/components/DrugListContent.kt b/app/src/main/java/com/approagency/drug/presentation/components/DrugListContent.kt new file mode 100644 index 0000000..b392b62 --- /dev/null +++ b/app/src/main/java/com/approagency/drug/presentation/components/DrugListContent.kt @@ -0,0 +1,152 @@ +package com.approagency.drug.presentation.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +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.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.twotone.KeyboardArrowLeft +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import com.approagency.drug.data.dto.DrugDetail +import com.approagency.drug.data.dto.DrugListResponse +import com.approagency.drug.presentation.common.CustomBox +import com.vada.caller.ui.theme.dime + +@Composable +fun DrugListContent( + drugsData: Result?, + onDrugClick: (Int) -> Unit +) { + val drugs = drugsData?.getOrNull()?.data ?: emptyList() + + if (drugs.isEmpty()) { + CustomBox( + child = { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier + .fillMaxWidth() + .padding(all = MaterialTheme.dime.sm), + ) { + Text( + text = "نتیجه‌ای یافت نشد", + fontWeight = FontWeight.W500 + ) + Spacer(modifier = Modifier.width(MaterialTheme.dime.xs)) + Icon( + imageVector = Icons.Default.Search, + contentDescription = "Search" + ) + } + } + ) + } else { + LazyColumn( + + ){ + items(drugs.count() , itemContent ={ + i -> DrugItem( + drug = drugs[i], + onClick = { onDrugClick(drugs[i].cod!!) } + ) + } ) + } + + } +} + + +@Composable +fun DrugItem( + drug: DrugDetail, // Adjust this based on your actual data class + onClick: (Int?) -> Unit +) { + CustomBox( + modifier = Modifier + .padding(bottom = MaterialTheme.dime.sm) + .fillMaxWidth() + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + onClick(drug.cod) + }, + child = { + Row( + modifier = Modifier.padding(all = MaterialTheme.dime.lg), + ) { + Column ( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.Start + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text(drug.nam_en.toString() , style = MaterialTheme.typography.bodySmall) + Text(drug.nam_fa.toString() , style = MaterialTheme.typography.labelLarge,textAlign = TextAlign.Right) + } + if (drug.goroh_darmani != null ) + + + Row( + modifier = Modifier.fillMaxWidth() + , horizontalArrangement = Arrangement.End + ) {Text( + "گروه دارویی: ${drug.goroh_darmani.nam_fa.toString()}", + style = MaterialTheme.typography.labelMedium, + textAlign = TextAlign.Right + ) } + + + + Row ( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + TextButton( + onClick = { + onClick(drug.cod) + } + ) { + Row( + horizontalArrangement = Arrangement.Center + ) { + Icon( + imageVector = Icons.AutoMirrored.TwoTone.KeyboardArrowLeft + , contentDescription = "see more" + ) + Text("مشاهده جزییات") + } + } + } + } + + } + } + ) +// Button( +// onClick = onClick, +// modifier = Modifier +// .fillMaxWidth() +// .padding(vertical = 4.dp) +// ) { +// drug.nam_fa?.let { Text(text = it) } // Adjust based on your drug model +// } +} \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/presentation/components/EmptyStateContent.kt b/app/src/main/java/com/approagency/drug/presentation/components/EmptyStateContent.kt new file mode 100644 index 0000000..6889bd5 --- /dev/null +++ b/app/src/main/java/com/approagency/drug/presentation/components/EmptyStateContent.kt @@ -0,0 +1,40 @@ +package com.approagency.drug.presentation.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.approagency.drug.presentation.common.CustomBox +import com.vada.caller.ui.theme.dime + +@Composable +fun EmptyStateContent() { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + , verticalArrangement = Arrangement.Center + ) { + Icon( + imageVector = Icons.Default.Search, + contentDescription = "Search", + modifier = Modifier.size(MaterialTheme.dime.iconLg) + ) + CustomBox( + child = { + Text( + text = "برای جستجو، نام دارو را وارد کنید", + modifier = Modifier.padding(MaterialTheme.dime.sm) + ) + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/presentation/components/ShowDrugDetail.kt b/app/src/main/java/com/approagency/drug/presentation/components/ShowDrugDetail.kt new file mode 100644 index 0000000..bf4f9bb --- /dev/null +++ b/app/src/main/java/com/approagency/drug/presentation/components/ShowDrugDetail.kt @@ -0,0 +1,225 @@ +package com.approagency.drug.presentation.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items // Import items if you were to use it for lists within LazyColumn +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.LayoutDirection +import com.approagency.drug.data.dto.DrugDetail // Import DrugDetail directly +import com.approagency.drug.data.dto.DrugModels +import com.approagency.drug.presentation.common.CustomModalBottomSheet +import com.approagency.drug.presentation.viewModel.DrugDetailState +import com.approagency.drug.presentation.viewModel.HomeViewModel +import com.vada.caller.ui.theme.dime // Assuming this is your custom spacing/dimension object + +@Composable +@OptIn(ExperimentalMaterial3Api::class) +fun DrugDetailSheet( + viewModel: HomeViewModel, + state: DrugDetailState // Assuming you pass the state containing the detail +) { + val detail: DrugDetail? = state.drugDetail?.getOrNull()?.data + + // Only show the sheet if detail is not null + if (detail != null) { + CustomModalBottomSheet( + onDismiss = { + viewModel.closeDetail() + }, + content = { + // Use a LazyColumn for scrollable content inside the sheet + + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = MaterialTheme.dime.lg), + contentPadding = PaddingValues(bottom = MaterialTheme.dime.lg) // Add padding to the bottom of the content for better scrolling + ) { + // --- 1. Main Title --- + item { // Use item {} for single composables within LazyColumn + Text( + text = detail.nam_fa ?: "جزئیات دارو", + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(bottom = MaterialTheme.dime.md) + ) + Text( + text = detail.nam_en ?: "N/A (English Name)", + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(bottom = MaterialTheme.dime.lg) + ) + + // Divider for visual separation + HorizontalDivider(Modifier.padding(bottom = MaterialTheme.dime.md)) + } + + // --- 2. Key Groupings --- + // Goroh Darmani (Pharmacological Group) + detail.goroh_darmani?.let { group -> + item { + DetailRow( + title = "گروه درمانی", + value = group.nam_fa ?: "نامشخص" + ) + } + group.nam_en?.let { enName -> + item { + DetailRow( + title = "Group (EN)", + value = enName + ) + } + } + } + + // Goroh Darmanei (Therapeutic Group) + detail.goroh_daroei?.let { group -> + item { + DetailRow( + title = "گروه دارویی", + value = group.nam ?: "نامشخص" + ) + } + } + + // Goroh Farmakologic (Pharmacological Class) + detail.goroh_farmakologic_cod?.let { cod -> + // NOTE: You might need a separate API call or mapping here + // if cod needs to be translated to a name. For now, we show the code. + item { + DetailRow( + title = "کلاس فارماکولوژیک", + value = cod.toString() + ) + } + } + + item { // Add a divider as a separate item + HorizontalDivider(Modifier.padding(vertical = MaterialTheme.dime.md)) + } + + // --- 3. Usage & Mechanism --- + detail.mavaredmasraf?.let { usage -> + item { + DetailSection(title = "موارد مصرف", content = usage) + } + } + + detail.mekanismtasir?.let { mechanism -> + item { + DetailSection(title = "مکانیسم اثر", content = mechanism) + } + } + + // --- 4. Warnings & Storage --- + detail.hoshdar?.let { warning -> + item { + DetailSection( + title = "هشدارها", + content = warning, + isWarning = true + ) + } + } + + detail.sharayetnegahdari?.let { storage -> + item { + DetailSection( + title = "شرایط نگهداری", + content = storage + ) + } + } + } + } + } + ) + } +} + +// --- Helper Composables for Clean Code --- + +@Composable +fun DetailRow( + title: String, + value: String +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = MaterialTheme.dime.sm), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = title, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Right + ) + Text( + text = value, + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.End, + // Ensure text takes up available space and doesn't push other elements. + // Using weight(1f, fill = false) is good for aligning to the end if there's space. + modifier = Modifier.weight(1f, fill = false) + ) + } +} + +@Composable +fun DetailSection( + title: String, + content: String, + isWarning: Boolean = false +) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = MaterialTheme.dime.md) + ) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + color = if (isWarning) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(bottom = MaterialTheme.dime.sm) + ) + // Use a text block that respects line breaks (usually done with \n) + Text( + text = content, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + // Consider adding softWrap = true if content might overflow + // softWrap = true + ) + } + // Add divider after each section for better visual separation + HorizontalDivider(Modifier.padding(top = MaterialTheme.dime.sm)) +} + +// Make sure your DrugDetailState in ViewModel looks something like this: +// data class DrugDetailState( +// val isLoading: Boolean = false, +// val drugDetail: DrugModels? = null, // Changed to DrugModels? to match data access +// val isDetailVisible: Boolean = false, +// val error: String? = null +// ) diff --git a/app/src/main/java/com/approagency/drug/presentation/screens/HomeScreen.kt b/app/src/main/java/com/approagency/drug/presentation/screens/HomeScreen.kt index 81a8533..3cdb403 100644 --- a/app/src/main/java/com/approagency/drug/presentation/screens/HomeScreen.kt +++ b/app/src/main/java/com/approagency/drug/presentation/screens/HomeScreen.kt @@ -1,7 +1,14 @@ package com.approagency.drug.presentation.screens +import android.R +import android.R.attr.fontWeight +import android.content.res.Resources import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.snapping.SnapPosition +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -10,18 +17,35 @@ 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.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.twotone.KeyboardArrowLeft +import androidx.compose.material.icons.filled.Info +import androidx.compose.material.icons.filled.KeyboardArrowLeft +import androidx.compose.material.icons.filled.KeyboardArrowRight import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.twotone.Info +import androidx.compose.material.icons.twotone.KeyboardArrowLeft +import androidx.compose.material.icons.twotone.KeyboardArrowRight import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -29,170 +53,240 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController import com.approagency.drug.data.dto.DrugDetail import com.approagency.drug.data.dto.DrugListResponse import com.approagency.drug.domain.model.DrugSearchParams +import com.approagency.drug.presentation.common.CustomBox +import com.approagency.drug.presentation.common.CustomModalBottomSheet import com.approagency.drug.presentation.common.CustomModalDialog import com.approagency.drug.presentation.common.Loading import com.approagency.drug.presentation.common.PrimaryButton -import com.approagency.drug.presentation.viewModel.DrugSearchState +import com.approagency.drug.presentation.components.DarmanContent +import com.approagency.drug.presentation.components.DrugDetailSheet +import com.approagency.drug.presentation.components.DrugListContent +import com.approagency.drug.presentation.components.EmptyStateContent +import com.approagency.drug.presentation.viewModel.CombineState +import com.approagency.drug.presentation.viewModel.HomeEvent import com.approagency.drug.presentation.viewModel.HomeViewModel +import com.vada.caller.ui.theme.Dime import com.vada.caller.ui.theme.dime +import kotlinx.coroutines.flow.SharedFlow import org.koin.androidx.compose.koinViewModel +@OptIn(ExperimentalMaterial3Api::class) @Composable fun HomeScreen( + navController : NavController, modifier: Modifier, viewModel: HomeViewModel = koinViewModel() ) { val state by viewModel.uiState.collectAsState() + val focusManager = LocalFocusManager.current + val keyboardController = LocalSoftwareKeyboardController.current + if (state.drugDetailState.isDetailVisible) { + DrugDetailSheet( + viewModel = viewModel, + state = state.drugDetailState + ) + } HomeContent( modifier = modifier, state = state, - onSearch = { value -> viewModel.searchDrugs(DrugSearchParams(query = value)) }, + viewModelEvent = viewModel.event, + onSearch = { value -> + if (value is Int) { + viewModel.searchDrugs(DrugSearchParams(healGroup = value)) + } else if (value is String) { + viewModel.searchDrugs(DrugSearchParams(query = value)) + } + keyboardController?.hide() + focusManager.clearFocus()}, onRetry = { - viewModel.clearError() + viewModel.getDarmani() }, - onDrugClick = { drugId -> println(drugId) } + onDrugClick = { drugId -> viewModel.drugDetail(drugId , allDrugs = state.drugSearchState.drugsData) } ) } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun HomeContent( - state: DrugSearchState, - onSearch: (String) -> Unit, + state: CombineState, + viewModelEvent: SharedFlow, + onSearch: (Any) -> Unit, onRetry: () -> Unit, - onDrugClick: (String) -> Unit, + onDrugClick: (Int) -> Unit, modifier: Modifier = Modifier ) { var search by remember { mutableStateOf("") } Column( modifier = modifier - .fillMaxSize() - .padding(16.dp), - horizontalAlignment = Alignment.Start + .fillMaxSize().padding(horizontal = MaterialTheme.dime.md) + .padding(top = MaterialTheme.dime.md), + horizontalAlignment = Alignment.Start, ) { - Column { + Column ( + verticalArrangement = Arrangement.Center + ){ CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { - OutlinedTextField( - value = search, - shape = MaterialTheme.shapes.large, - onValueChange = { value -> - search = value - }, - label = { Text("جستجوی دارو") }, - modifier = Modifier.fillMaxWidth(), - - trailingIcon = { - IconButton(onClick = { - if (search.isNotBlank()) { - onSearch(search) + Box( + modifier = Modifier + .fillMaxWidth() + .height(45.dp) // Your desired height + .background( + color = MaterialTheme.colorScheme.surfaceVariant, + shape =MaterialTheme.shapes.medium + ) + .border( + width = 1.dp, + color = MaterialTheme.colorScheme.primaryContainer, + shape =MaterialTheme.shapes.medium + ) + .padding(horizontal = MaterialTheme.dime.md, vertical = MaterialTheme.dime.sm) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxSize() + ) { + BasicTextField( + value = search, + onValueChange = { search = it }, + modifier = Modifier.weight(1f), + singleLine = true, + textStyle = MaterialTheme.typography.bodyLarge, + decorationBox = { innerTextField -> + Box { + if (search.isEmpty()) { + Text( + "جستجوی دارو", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + innerTextField() + } } - }) { + ) + IconButton( + onClick = { + if (search.isNotBlank()) { + onSearch(search) + } + }, + modifier = Modifier.size(32.dp) + ) { Icon( imageVector = Icons.Default.Search, - contentDescription = "Search" + contentDescription = "Search", + modifier = Modifier.size(18.dp) ) } } - ) + } } Spacer(modifier = Modifier.height(MaterialTheme.dime.xs)) PrimaryButton( text = "جستجو", - height = 45, - isLoading = state.isLoading, + height = 40, + isLoading = state.drugSearchState.isLoading, onClick = { if (search.isNotBlank()) { onSearch(search) } } ) + Spacer(modifier = Modifier.height(MaterialTheme.dime.xs)) + Box(modifier = Modifier.fillMaxSize()) { - when { - state.isLoading ->{ - CustomModalDialog( - onDismissRequest = { - - }, - content = { - Column { - Loading() - Text("در حال جستجو...") - } + // Always draw the data if available + if (state.drugSearchState.drugsData != null && !state.showDarmanList) { + Column { + Row( + horizontalArrangement = Arrangement.End, + modifier = Modifier.fillMaxWidth() + ) { + TextButton( + onClick = { + onRetry() + } + ) { + Text("مشاهده تمامی گروه های دارویی") + Icon( + imageVector = Icons.Default.KeyboardArrowRight, + contentDescription = "back" + ) + Spacer(modifier = Modifier.width(MaterialTheme.dime.sm)) + } } + DrugListContent( + drugsData = state.drugSearchState.drugsData, + onDrugClick = onDrugClick + ) + } + } + else if (state.darmanState.getDarmani != null ){ + Column { + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + Text("لیست گروه های دارویی" , style = MaterialTheme.typography.labelLarge.copy( + color = MaterialTheme.colorScheme.primary + ) ,modifier= Modifier.padding(MaterialTheme.dime.md)) + } + + DarmanContent( + darmanData = state.darmanState.getDarmani, + onDarmanClick = { value -> + onSearch(value) + }, + onRetryClick = { + onRetry() + } + ) + } + } else { + EmptyStateContent() + } + + + + // Draw the loading dialog on top if needed + if (state.drugSearchState.isLoading || state.drugDetailState.isLoading || state.darmanState.isLoading) { + CustomModalDialog( + onDismissRequest = { /* maybe disable dismiss while loading */ }, + content = { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Loading(color = MaterialTheme.colorScheme.primary) + Spacer(modifier = Modifier.height(MaterialTheme.dime.sm)) + Text("در حال جستجو..." , textAlign = TextAlign.Right) + } + }, +// modifier = Modifier +// .matchParentSize() +// .background(MaterialTheme.colorScheme.background.copy(alpha = 0.5f)) ) } - state.drugsData != null ->{ - DrugListContent( - drugsData = state.drugsData, - onDrugClick = onDrugClick - ) - }else -> { - EmptyStateContent() - } } + + } } } -@Composable -fun DrugListContent( - drugsData: Result?, - onDrugClick: (String) -> Unit -) { - val drugs = drugsData?.getOrNull()?.data ?: emptyList() - if (drugs.isEmpty()) { - Text( - text = "نتیجه‌ای یافت نشد", - modifier = Modifier.padding(16.dp) - ) - } else { - // Display drug list - Column { - drugs.forEach { drug -> - DrugItem( - drug = drug, - onClick = { onDrugClick(drug.cod.toString()) } - ) - } - } - } -} -@Composable -fun DrugItem( - drug: DrugDetail, // Adjust this based on your actual data class - onClick: () -> Unit -) { -Row() { - Text(drug.nam_fa.toString()) -} -// Button( -// onClick = onClick, -// modifier = Modifier -// .fillMaxWidth() -// .padding(vertical = 4.dp) -// ) { -// drug.nam_fa?.let { Text(text = it) } // Adjust based on your drug model -// } -} -@Composable -fun EmptyStateContent() { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally - ) { - Text( - text = "برای جستجو، نام دارو را وارد کنید", - modifier = Modifier.padding(32.dp) - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/presentation/screens/LabScreen.kt b/app/src/main/java/com/approagency/drug/presentation/screens/LabScreen.kt new file mode 100644 index 0000000..d360b0a --- /dev/null +++ b/app/src/main/java/com/approagency/drug/presentation/screens/LabScreen.kt @@ -0,0 +1,12 @@ +package com.approagency.drug.presentation.screens + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.approagency.drug.presentation.viewModel.HomeViewModel +import org.koin.androidx.compose.koinViewModel + +@Composable +fun LabScreen( + modifier: Modifier, + viewModel: HomeViewModel = koinViewModel() +){} \ No newline at end of file diff --git a/app/src/main/java/com/approagency/drug/presentation/screens/RootScreen.kt b/app/src/main/java/com/approagency/drug/presentation/screens/RootScreen.kt new file mode 100644 index 0000000..fb82de9 --- /dev/null +++ b/app/src/main/java/com/approagency/drug/presentation/screens/RootScreen.kt @@ -0,0 +1,128 @@ +package com.approagency.drug.presentation.screens + +import android.annotation.SuppressLint +import android.app.Activity +import android.graphics.drawable.Icon +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawingPadding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Build +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarDefaults +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationBarItemDefaults +import androidx.compose.material3.Scaffold +import androidx.compose.material3.ScaffoldDefaults +import androidx.compose.material3.SegmentedButtonDefaults.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +@SuppressLint("ConfigurationScreenWidthHeight") +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun RootScreen(navHostController: NavHostController, modifier: Modifier){ + val pages = listOf( + "دارو" to Icons.Default.Search, + "آزمایش" to Icons.Default.Build + ) + var seletedTab by remember { mutableStateOf(0) } + val context = LocalContext.current + val activity = context as Activity + + Scaffold ( + contentWindowInsets = ScaffoldDefaults.contentWindowInsets, + topBar = { CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl){ + TopAppBar( + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + titleContentColor = MaterialTheme.colorScheme.primary, + ), + title = { + Text("جستجوی دارویی" , textAlign = TextAlign.Right , style = MaterialTheme.typography.titleLarge , fontWeight = FontWeight.W700 ) + } + ) + }}, +// modifier = modifier.fillMaxSize(), + bottomBar = { + NavigationBar( + + containerColor = MaterialTheme.colorScheme.surface, + modifier = Modifier + .fillMaxWidth() + .height(110.dp).padding(bottom = 2.dp).clip(MaterialTheme.shapes.extraLarge), // 👈 کوچیک‌تر از حالت پیش‌فرض + tonalElevation = 8.dp, // subtle shadow + windowInsets = WindowInsets.navigationBars + .only(WindowInsetsSides.Bottom) + ) { + pages.forEachIndexed { index, item -> + val selected = seletedTab == index + + NavigationBarItem( + colors = NavigationBarItemDefaults.colors( + selectedIconColor = MaterialTheme.colorScheme.onPrimary, + selectedTextColor = MaterialTheme.colorScheme.primary, + indicatorColor = MaterialTheme.colorScheme.primary, + unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant, + unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant + ), + selected = selected, + onClick = { seletedTab = index }, + + icon = { + Icon( + imageVector = item.second, + contentDescription = item.first, + modifier = Modifier.size(16.dp) + ) + }, + + label = { + Text( + item.first, + style = MaterialTheme.typography.labelMedium + ) + }, + + alwaysShowLabel = true // 👈 فقط وقتی select شد label نشون بده + ) + } + } + } + ) { + padding -> when (seletedTab) { + 0 -> HomeScreen( navController = navHostController,Modifier.padding(padding).consumeWindowInsets(padding)) + 1 -> LabScreen(Modifier.padding(padding)) + } + } +} diff --git a/app/src/main/java/com/approagency/drug/presentation/viewModel/HomeViewModel.kt b/app/src/main/java/com/approagency/drug/presentation/viewModel/HomeViewModel.kt index c89a6e3..f873cfe 100644 --- a/app/src/main/java/com/approagency/drug/presentation/viewModel/HomeViewModel.kt +++ b/app/src/main/java/com/approagency/drug/presentation/viewModel/HomeViewModel.kt @@ -3,9 +3,15 @@ package com.approagency.drug.presentation.viewModel import android.net.http.HttpException import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.approagency.drug.data.dto.DarmanModel +import com.approagency.drug.data.dto.DrugDetail import com.approagency.drug.data.dto.DrugListResponse +import com.approagency.drug.data.dto.DrugModels import com.approagency.drug.domain.model.DrugSearchParams +import com.approagency.drug.domain.usecase.GetDarmanUseCase +import com.approagency.drug.domain.usecase.GetDrugDetailUseCase import com.approagency.drug.domain.usecase.GetDrugSearchUseCase +import com.approagency.drug.presentation.screens.HomeScreen import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -14,24 +20,53 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch class HomeViewModel ( - private val getDrugSearchUseCase: GetDrugSearchUseCase + private val getDrugSearchUseCase: GetDrugSearchUseCase, + private val getDrugDetailUseCase: GetDrugDetailUseCase, + private val getDarmanUseCase: GetDarmanUseCase ): ViewModel() { - private val _uiState = MutableStateFlow(DrugSearchState()) - val uiState : StateFlow = _uiState - + private val _uiState = MutableStateFlow(CombineState()) + val uiState : StateFlow = _uiState // Add SharedFlow for one-time events private val _event = MutableSharedFlow() val event = _event.asSharedFlow() + init { + getDarmani() + } + fun getDarmani(){ + _uiState.update { it.copy(darmanState = it.darmanState.copy( + isLoading = true + ) , showDarmanList = true ) } + viewModelScope.launch { + try { + val result = getDarmanUseCase.invoke() + _uiState.update { + it.copy( + darmanState = it.darmanState.copy( + isLoading = false, + getDarmani = result + ) + ) + } + } catch (e: HttpException){ + handleError(e.message) + } + } + } fun searchDrugs(drugSearchParams: DrugSearchParams) { - _uiState.update { it.copy(isLoading = true) } + _uiState.update { it.copy(drugSearchState = it.drugSearchState.copy( + isLoading = true + ) , showDarmanList = false) } viewModelScope.launch { try { val result = getDrugSearchUseCase.invoke(drugSearchParams) _uiState.update { it.copy( - isLoading = false, - drugsData = result + drugSearchState = it.drugSearchState.copy( + isLoading = false, + drugsData = result + ) + ) } } catch (e: HttpException) { handleError(e.message) @@ -41,26 +76,90 @@ class HomeViewModel ( } } + fun drugDetail(cod: Int , allDrugs:Result?) { + _uiState.update { it.copy(drugDetailState = it.drugDetailState.copy( + isLoading = true + ) , showDarmanList = false) } + viewModelScope.launch { + try { + val result = getDrugDetailUseCase.invoke(cod) + _uiState.update { + it.copy( + drugDetailState = it.drugDetailState.copy( + isLoading = false, + drugDetail = result, + isDetailVisible = true + ), + drugSearchState = it.drugSearchState.copy( + isLoading = false, + drugsData = allDrugs + ) + ) + } + println(result) + } catch (e: HttpException) { + handleError(e.message) + }catch (e: Exception) { + handleError(e.localizedMessage ?: "Unknown error") + } + } + } + fun closeDetail() { + _uiState.update { + it.copy( + drugDetailState = it.drugDetailState.copy( + isDetailVisible = false, + drugDetail = null // optional, depends if you want to keep last detail cached + ) + ) + } + } + private suspend fun handleError(message: String?) { - _uiState.update { it.copy(isLoading = false) } + _uiState.update { it.copy(drugSearchState = it.drugSearchState.copy( + isLoading = false + ) , drugDetailState = it.drugDetailState.copy( + isLoading = false + )) } _event.emit(HomeEvent.ShowError(message ?: "Error occurred")) } // Clear error when consumed fun clearError() { - _uiState.update { it.copy(error = null) } +// _uiState.update { it.copy(error = null) } } } sealed class HomeEvent { data class ShowError(val message: String) : HomeEvent() - data class NavigateToDetails(val drugId: String) : HomeEvent() + data class ShowDetailDrug(val drugDetail:Result?): HomeEvent() } +data class CombineState( + val drugSearchState: DrugSearchState = DrugSearchState() , + val drugDetailState: DrugDetailState =DrugDetailState(), + val darmanState: GetDarmaniState = GetDarmaniState(), + val showDarmanList: Boolean = true + ) + data class DrugSearchState( val isLoading: Boolean = false, val drugsData: Result? = null, + val drugDetail: Result? = null, + val error:String? = null +) + +data class DrugDetailState( + val isLoading: Boolean = false, + val drugDetail:Result? = null, + val isDetailVisible: Boolean = false, + val error:String? = null +) + +data class GetDarmaniState( + val isLoading: Boolean = false, + val getDarmani:Result? = null, val error:String? = null ) diff --git a/app/src/main/java/com/approagency/drug/ui/theme/Theme.kt b/app/src/main/java/com/approagency/drug/ui/theme/Theme.kt index 7f13901..a74f0a5 100644 --- a/app/src/main/java/com/approagency/drug/ui/theme/Theme.kt +++ b/app/src/main/java/com/approagency/drug/ui/theme/Theme.kt @@ -11,38 +11,84 @@ import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import com.fabirt.podcastapp.ui.theme.Shapes import com.vada.caller.ui.theme.Dime import com.vada.caller.ui.theme.LocalDime +object MedicalColors { + val Primary = Color(0xFF0F766E) + val PrimaryContainer = Color(0xFFD7F3EF) + + val Secondary = Color(0xFF5B8E8A) + val SecondaryContainer = Color(0xFFE3F3F1) + + val Tertiary = Color(0xFF8FBFB8) + + val Background = Color(0xFFF8FCFB) + val Surface = Color(0xFFFFFFFF) + + val DarkBackground = Color(0xFF0F1720) + val DarkSurface = Color(0xFF17232B) + + val OnDark = Color(0xFFF5FAF9) + val OnLight = Color(0xFF102A2A) + + val Error = Color(0xFFD64545) + + val Outline = Color(0xFFD0E2DF) +} private val DarkColorScheme = darkColorScheme( - primary = Purple80, - secondary = PurpleGrey80, - tertiary = Pink80 + primary = Color(0xFF5DD4C3), + onPrimary = Color(0xFF003733), + + secondary = Color(0xFF8CC9C1), + onSecondary = Color(0xFF0D2B29), + + tertiary = Color(0xFFA7DCD4), + onTertiary = Color(0xFF102A2A), + + background = MedicalColors.DarkBackground, + onBackground = MedicalColors.OnDark, + + surface = MedicalColors.DarkSurface, + onSurface = MedicalColors.OnDark, + + primaryContainer = Color(0xFF124A45), + secondaryContainer = Color(0xFF1D3D3A), + + error = Color(0xFFFF8A80) ) private val LightColorScheme = lightColorScheme( - primary = Purple40, - secondary = PurpleGrey40, - tertiary = Pink40 - - /* Other default colors to override - background = Color(0xFFFFFBFE), - surface = Color(0xFFFFFBFE), + primary = MedicalColors.Primary, onPrimary = Color.White, + + secondary = MedicalColors.Secondary, onSecondary = Color.White, - onTertiary = Color.White, - onBackground = Color(0xFF1C1B1F), - onSurface = Color(0xFF1C1B1F), - */ + + tertiary = MedicalColors.Tertiary, + onTertiary = MedicalColors.OnLight, + + background = MedicalColors.Background, + onBackground = MedicalColors.OnLight, + + surface = MedicalColors.Surface, + onSurface = MedicalColors.OnLight, + + primaryContainer = MedicalColors.PrimaryContainer, + secondaryContainer = MedicalColors.SecondaryContainer, + + outline = MedicalColors.Outline, + error = MedicalColors.Error ) @Composable fun DrugTheme( darkTheme: Boolean = isSystemInDarkTheme(), // Dynamic color is available on Android 12+ - dynamicColor: Boolean = true, + dynamicColor: Boolean = false, content: @Composable () -> Unit ) { val colorScheme = when { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4238ff9..ca186a2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,6 +18,9 @@ koinAndroid = "4.1.0" koinAndroidxCompose = "4.1.0" lifecycleViewmodelKtx = "2.9.4" +navigationCompose = "2.8.5" +material3 = "1.4.0" + [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } junit = { group = "junit", name = "junit", version.ref = "junit" } @@ -33,6 +36,7 @@ androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "u androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } +androidx-compose-material-icons-core = { group = "androidx.compose.material", name = "material-icons-core" } androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleRuntimeKtx" } koin-android = { module = "io.insert-koin:koin-android", version.ref = "koinAndroid" } @@ -45,7 +49,8 @@ gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" } location-services = { group = "com.google.android.gms", name = "play-services-location", version.ref = "location" } androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" } - +androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } +androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3b34275..1aa1982 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,8 +1,8 @@ #Thu Feb 26 19:18:21 IRST 2026 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://maven.myket.ir/gradle/distributions/gradle-8.13-bin.zip networkTimeout=10000 -validateDistributionUrl=true +validateDistributionUrl=false zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts index 52a61aa..5e7abcc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,6 @@ pluginManagement { repositories { + maven { url = uri("https://maven.myket.ir") } google { content { includeGroupByRegex("com\\.android.*") @@ -7,15 +8,16 @@ pluginManagement { includeGroupByRegex("androidx.*") } } - mavenCentral() - gradlePluginPortal() + // mavenCentral() + // gradlePluginPortal() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { - google() - mavenCentral() + //google() + //mavenCentral() + maven { url = uri("https://maven.myket.ir") } } }