feat: add test items and group tests
This commit is contained in:
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="CompilerConfiguration">
|
<component name="CompilerConfiguration">
|
||||||
<bytecodeTargetLevel target="21" />
|
<bytecodeTargetLevel target="17" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
Generated
+2
-1
@@ -1,6 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|||||||
@@ -31,11 +31,11 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "11"
|
jvmTarget = "17"
|
||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose = true
|
compose = true
|
||||||
|
|||||||
@@ -29,4 +29,18 @@ interface TestGroupDao {
|
|||||||
|
|
||||||
@Query("DELETE FROM testGroup")
|
@Query("DELETE FROM testGroup")
|
||||||
suspend fun deleteAllGroups()
|
suspend fun deleteAllGroups()
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT * FROM testGroup
|
||||||
|
WHERE Fname LIKE '%' || :query || '%'
|
||||||
|
OR Ename LIKE '%' || :query || '%'
|
||||||
|
OR Detail LIKE '%' || :query || '%'
|
||||||
|
ORDER BY
|
||||||
|
CASE
|
||||||
|
WHEN Fname LIKE '%' || :query || '%' THEN 1
|
||||||
|
WHEN Ename LIKE '%' || :query || '%' THEN 2
|
||||||
|
ELSE 3
|
||||||
|
END
|
||||||
|
""")
|
||||||
|
fun searchGroups(query: String): Flow<List<TestGroupEntity>>
|
||||||
}
|
}
|
||||||
@@ -20,8 +20,18 @@ interface TestItemDao {
|
|||||||
|
|
||||||
@Query("""
|
@Query("""
|
||||||
SELECT ti.* FROM testItem ti
|
SELECT ti.* FROM testItem ti
|
||||||
INNER JOIN testGroup tg ON ti.Group_Id = tg.Id
|
LEFT JOIN testGroup tg ON ti.Group_Id = tg.Id
|
||||||
WHERE tg.Isparent = '0' AND ti.Title LIKE '%' || :searchQuery || '%'
|
WHERE ti.Title LIKE '%' || :searchQuery || '%'
|
||||||
|
OR ti.Normal_Value LIKE '%' || :searchQuery || '%'
|
||||||
|
OR ti.Detail LIKE '%' || :searchQuery || '%'
|
||||||
|
OR tg.Fname LIKE '%' || :searchQuery || '%'
|
||||||
|
OR tg.Ename LIKE '%' || :searchQuery || '%'
|
||||||
|
ORDER BY
|
||||||
|
CASE
|
||||||
|
WHEN ti.Title LIKE '%' || :searchQuery || '%' THEN 1
|
||||||
|
WHEN tg.Fname LIKE '%' || :searchQuery || '%' THEN 2
|
||||||
|
ELSE 3
|
||||||
|
END
|
||||||
""")
|
""")
|
||||||
fun searchTestItems(searchQuery: String): Flow<List<TestItemEntity>>
|
fun searchTestItems(searchQuery: String): Flow<List<TestItemEntity>>
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.approagency.drug.data.local.dao.TestItemDao
|
|||||||
import com.approagency.drug.data.local.entities.TestGroupEntity
|
import com.approagency.drug.data.local.entities.TestGroupEntity
|
||||||
import com.approagency.drug.data.local.entities.TestItemEntity
|
import com.approagency.drug.data.local.entities.TestItemEntity
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
|
||||||
class LabRepositoryImpl (
|
class LabRepositoryImpl (
|
||||||
private val testGroupDao: TestGroupDao,
|
private val testGroupDao: TestGroupDao,
|
||||||
@@ -21,4 +22,14 @@ class LabRepositoryImpl (
|
|||||||
fun getItemsByGroupId(groupId: Int): Flow<List<TestItemEntity>> = testItemDao.getItemsByGroupId(groupId)
|
fun getItemsByGroupId(groupId: Int): Flow<List<TestItemEntity>> = testItemDao.getItemsByGroupId(groupId)
|
||||||
suspend fun getItemById(itemId: Int): TestItemEntity? = testItemDao.getItemById(itemId)
|
suspend fun getItemById(itemId: Int): TestItemEntity? = testItemDao.getItemById(itemId)
|
||||||
fun searchTestItems(query: String): Flow<List<TestItemEntity>> = testItemDao.searchTestItems(query)
|
fun searchTestItems(query: String): Flow<List<TestItemEntity>> = testItemDao.searchTestItems(query)
|
||||||
|
|
||||||
|
|
||||||
|
fun searchGroupsAndItems(searchQuery: String): Flow<Pair<List<TestGroupEntity>, List<TestItemEntity>>> {
|
||||||
|
return combine(
|
||||||
|
testGroupDao.searchGroups(searchQuery),
|
||||||
|
testItemDao.searchTestItems(searchQuery)
|
||||||
|
) { groups, items ->
|
||||||
|
Pair(groups, items)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,7 @@ import com.approagency.drug.domain.usecase.GetDrugDetailUseCase
|
|||||||
import com.approagency.drug.domain.usecase.GetDrugSearchUseCase
|
import com.approagency.drug.domain.usecase.GetDrugSearchUseCase
|
||||||
import com.approagency.drug.domain.usecase.GetTestGroupUseCase
|
import com.approagency.drug.domain.usecase.GetTestGroupUseCase
|
||||||
import com.approagency.drug.domain.usecase.GetTestItemByGroupId
|
import com.approagency.drug.domain.usecase.GetTestItemByGroupId
|
||||||
|
import com.approagency.drug.domain.usecase.SearchTestsUseCase
|
||||||
import com.approagency.drug.presentation.viewModel.HomeViewModel
|
import com.approagency.drug.presentation.viewModel.HomeViewModel
|
||||||
import com.approagency.drug.presentation.viewModel.LabViewModel
|
import com.approagency.drug.presentation.viewModel.LabViewModel
|
||||||
import com.approagency.drug.utils.Config
|
import com.approagency.drug.utils.Config
|
||||||
@@ -68,12 +69,14 @@ val appModule= module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
single { LabRepositoryImpl(get(), get()) }
|
single { LabRepositoryImpl(get(), get()) }
|
||||||
|
|
||||||
|
single { SearchTestsUseCase(get()) }
|
||||||
//view model
|
//view model
|
||||||
viewModel {
|
viewModel {
|
||||||
HomeViewModel(get() , get() , get())
|
HomeViewModel(get() , get() , get())
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel {
|
viewModel {
|
||||||
LabViewModel(get() , get())
|
LabViewModel(get() , get() , get())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,22 @@
|
|||||||
package com.approagency.drug.domain.model
|
package com.approagency.drug.domain.model
|
||||||
|
|
||||||
|
import com.approagency.drug.data.local.entities.TestGroupEntity
|
||||||
|
|
||||||
data class TestGroup(
|
data class TestGroup(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val ename: String?,
|
val ename: String?,
|
||||||
val fname: String?,
|
val fname: String?,
|
||||||
val detail: String?,
|
val detail: String?,
|
||||||
val isParent: Boolean
|
val isParent: String?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
fun TestGroupEntity.toTestGroup(): TestGroup {
|
||||||
|
return TestGroup(
|
||||||
|
id = this.id,
|
||||||
|
ename = this.ename,
|
||||||
|
fname = this.fname,
|
||||||
|
detail = this.detail,
|
||||||
|
isParent = this.isParent
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.approagency.drug.domain.model
|
package com.approagency.drug.domain.model
|
||||||
|
|
||||||
|
import com.approagency.drug.data.local.entities.TestItemEntity
|
||||||
|
|
||||||
data class TestItem(
|
data class TestItem(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val groupId: Int,
|
val groupId: Int,
|
||||||
@@ -7,3 +9,12 @@ data class TestItem(
|
|||||||
val normalValue: String?,
|
val normalValue: String?,
|
||||||
val detail: String?
|
val detail: String?
|
||||||
)
|
)
|
||||||
|
fun TestItemEntity.toTestItem(): TestItem {
|
||||||
|
return TestItem(
|
||||||
|
id = this.id,
|
||||||
|
groupId = this.groupId,
|
||||||
|
title = this.title,
|
||||||
|
normalValue = this.normalValue,
|
||||||
|
detail = this.detail
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ class GetTestGroupUseCase (
|
|||||||
ename = entity.ename,
|
ename = entity.ename,
|
||||||
fname = entity.fname,
|
fname = entity.fname,
|
||||||
detail = entity.detail,
|
detail = entity.detail,
|
||||||
isParent = entity.isParent == "1"
|
isParent = entity.isParent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.approagency.drug.domain.usecase
|
||||||
|
|
||||||
|
|
||||||
|
import com.approagency.drug.data.repository.LabRepositoryImpl
|
||||||
|
import com.approagency.drug.domain.model.TestGroup
|
||||||
|
import com.approagency.drug.domain.model.TestItem
|
||||||
|
import com.approagency.drug.domain.model.toTestGroup
|
||||||
|
import com.approagency.drug.domain.model.toTestItem
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
class SearchTestsUseCase(
|
||||||
|
private val repository: LabRepositoryImpl
|
||||||
|
) {
|
||||||
|
operator fun invoke(query: String): Flow<Pair<List<TestGroup>, List<TestItem>>> {
|
||||||
|
return repository.searchGroupsAndItems(query).map { (groups, items) ->
|
||||||
|
Pair(
|
||||||
|
groups.map { it.toTestGroup() },
|
||||||
|
items.map { it.toTestItem() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
package com.approagency.drug.presentation.common
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
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.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.filled.Close
|
||||||
|
import androidx.compose.material.icons.filled.Search
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
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.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.vada.caller.ui.theme.dime
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CustomTextFilled(
|
||||||
|
value: String = "",
|
||||||
|
onValueChange: (String) -> Unit,
|
||||||
|
onSearch: (String) -> Unit = {},
|
||||||
|
placeholder: String = "جستجو...",
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
readOnly: Boolean = false,
|
||||||
|
singleLine: Boolean = true,
|
||||||
|
showClearButton: Boolean = true,
|
||||||
|
showSearchButton: Boolean = true,
|
||||||
|
autoSearch: Boolean = false,
|
||||||
|
searchDelay: Long = 500L,
|
||||||
|
keyboardOptions: KeyboardOptions = KeyboardOptions(
|
||||||
|
imeAction = ImeAction.Search
|
||||||
|
),
|
||||||
|
keyboardActions: KeyboardActions = KeyboardActions(
|
||||||
|
onSearch = {
|
||||||
|
if (value.isNotBlank()) {
|
||||||
|
onSearch(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
textStyle: androidx.compose.ui.text.TextStyle = MaterialTheme.typography.bodyLarge,
|
||||||
|
hintStyle: androidx.compose.ui.text.TextStyle = MaterialTheme.typography.bodyMedium.copy(
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
),
|
||||||
|
containerColor: androidx.compose.ui.graphics.Color = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
borderColor: androidx.compose.ui.graphics.Color = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
shape: androidx.compose.ui.graphics.Shape = MaterialTheme.shapes.medium,
|
||||||
|
height: Int = 45,
|
||||||
|
onFocusChange: ((Boolean) -> Unit)? = null
|
||||||
|
) {
|
||||||
|
var isFocused by remember { mutableStateOf(false) }
|
||||||
|
var localValue by remember(value) { mutableStateOf(value) }
|
||||||
|
|
||||||
|
// Auto-search functionality
|
||||||
|
androidx.compose.runtime.LaunchedEffect(autoSearch, searchDelay, localValue) {
|
||||||
|
if (autoSearch && localValue.isNotBlank()) {
|
||||||
|
kotlinx.coroutines.delay(searchDelay)
|
||||||
|
onSearch(localValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(height.dp)
|
||||||
|
.background(
|
||||||
|
color = containerColor,
|
||||||
|
shape = shape
|
||||||
|
)
|
||||||
|
.border(
|
||||||
|
width = 1.dp,
|
||||||
|
color = if (isFocused)
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
else
|
||||||
|
borderColor,
|
||||||
|
shape = shape
|
||||||
|
)
|
||||||
|
.padding(horizontal = MaterialTheme.dime.md, vertical = MaterialTheme.dime.sm)
|
||||||
|
.onFocusChanged { focusState ->
|
||||||
|
isFocused = focusState.isFocused
|
||||||
|
onFocusChange?.invoke(focusState.isFocused)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
// Clear button (shows when there's text and clear button is enabled)
|
||||||
|
if (showClearButton && localValue.isNotEmpty()) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
localValue = ""
|
||||||
|
onValueChange("")
|
||||||
|
},
|
||||||
|
modifier = Modifier.size(28.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Close,
|
||||||
|
contentDescription = "Clear",
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text field
|
||||||
|
BasicTextField(
|
||||||
|
value = localValue,
|
||||||
|
onValueChange = { newValue ->
|
||||||
|
localValue = newValue
|
||||||
|
onValueChange(newValue)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(horizontal = if (showClearButton && localValue.isNotEmpty()) 4.dp else 0.dp),
|
||||||
|
enabled = enabled,
|
||||||
|
readOnly = readOnly,
|
||||||
|
singleLine = singleLine,
|
||||||
|
textStyle = textStyle,
|
||||||
|
keyboardOptions = keyboardOptions,
|
||||||
|
keyboardActions = keyboardActions,
|
||||||
|
decorationBox = { innerTextField ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
contentAlignment = Alignment.CenterStart
|
||||||
|
) {
|
||||||
|
if (localValue.isEmpty() && !isFocused) {
|
||||||
|
Text(
|
||||||
|
text = placeholder,
|
||||||
|
style = hintStyle
|
||||||
|
)
|
||||||
|
}
|
||||||
|
innerTextField()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Search button
|
||||||
|
if (showSearchButton) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
if (localValue.isNotBlank()) {
|
||||||
|
onSearch(localValue)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.size(32.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Search,
|
||||||
|
contentDescription = "Search",
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
tint = if (localValue.isNotBlank())
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,40 +43,15 @@ fun DarmanContent(
|
|||||||
){
|
){
|
||||||
val drugs = darmanData?.getOrNull()?.data ?: emptyList()
|
val drugs = darmanData?.getOrNull()?.data ?: emptyList()
|
||||||
if (drugs.isEmpty()){
|
if (drugs.isEmpty()){
|
||||||
CustomBox(
|
RetryContent(
|
||||||
child = {
|
modifier = Modifier,
|
||||||
Column {
|
error = "مشکلی از سمت سرور پیش آمده",
|
||||||
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 = {
|
onClick = {
|
||||||
onRetryClick()
|
onRetryClick()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
LazyColumn(
|
LazyColumn {
|
||||||
|
|
||||||
){
|
|
||||||
items(drugs.count() , itemContent ={
|
items(drugs.count() , itemContent ={
|
||||||
i -> DarmanItem(
|
i -> DarmanItem(
|
||||||
darman = darmanData?.getOrNull()!!.data[i] ,
|
darman = darmanData?.getOrNull()!!.data[i] ,
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.approagency.drug.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
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 androidx.compose.ui.unit.dp
|
||||||
|
import com.approagency.drug.presentation.common.CustomBox
|
||||||
|
import com.approagency.drug.presentation.common.PrimaryButton
|
||||||
|
import com.vada.caller.ui.theme.dime
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RetryContent(modifier: Modifier = Modifier , error:String ,onClick:()-> Unit ) {
|
||||||
|
CustomBox(
|
||||||
|
child = {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier.padding(MaterialTheme.dime.md)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "خطا در دریافت اطلاعات",
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = error,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp)
|
||||||
|
)
|
||||||
|
PrimaryButton(
|
||||||
|
text = "تلاش مجدد",
|
||||||
|
height = 40,
|
||||||
|
isLoading = false,
|
||||||
|
onClick = { onClick() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
+205
@@ -0,0 +1,205 @@
|
|||||||
|
package com.approagency.drug.presentation.components
|
||||||
|
|
||||||
|
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.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
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.unit.dp
|
||||||
|
import com.approagency.drug.domain.model.TestGroup
|
||||||
|
import com.approagency.drug.domain.model.TestItem
|
||||||
|
import com.approagency.drug.presentation.common.CustomBox
|
||||||
|
import com.approagency.drug.presentation.common.Loading
|
||||||
|
import com.approagency.drug.presentation.viewModel.SearchResult
|
||||||
|
import com.vada.caller.ui.theme.dime
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.ui.AbsoluteAlignment
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextDirection
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SearchResultsContent(
|
||||||
|
searchResult: SearchResult?,
|
||||||
|
isLoading: Boolean,
|
||||||
|
error: String?,
|
||||||
|
onGroupClick: (TestGroup) -> Unit,
|
||||||
|
onItemClick: (TestItem) -> Unit,
|
||||||
|
onClearSearch: () -> Unit
|
||||||
|
) {
|
||||||
|
when {
|
||||||
|
isLoading -> {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Loading(color = MaterialTheme.colorScheme.primary)
|
||||||
|
Spacer(modifier = Modifier.height(MaterialTheme.dime.sm))
|
||||||
|
Text("در حال جستجو...")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error != null -> {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Text(
|
||||||
|
text = "خطا در جستجو",
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = error,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
searchResult != null -> {
|
||||||
|
val groups = searchResult.groups
|
||||||
|
val items = searchResult.items
|
||||||
|
|
||||||
|
if (groups.isEmpty() && items.isEmpty()) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Text(
|
||||||
|
text = "نتیجهای یافت نشد",
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(MaterialTheme.dime.sm))
|
||||||
|
Text(
|
||||||
|
text = "لطفاً عبارت دیگری را جستجو کنید",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(vertical = MaterialTheme.dime.md),
|
||||||
|
horizontalAlignment = AbsoluteAlignment.Right
|
||||||
|
|
||||||
|
) {
|
||||||
|
if (groups.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = "گروههای آزمایشگاهی (${groups.size})",
|
||||||
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
),
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(vertical = MaterialTheme.dime.md)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
items(groups) { group ->
|
||||||
|
TestGroupItemContent(
|
||||||
|
group = group,
|
||||||
|
onClick = { onGroupClick(group) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.padding(vertical = MaterialTheme.dime.md)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "آیتمهای آزمایشگاهی (${items.size})",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(vertical = MaterialTheme.dime.md)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items(items) { item ->
|
||||||
|
TestItemSearchResult(
|
||||||
|
testItem = item,
|
||||||
|
onClick = { onItemClick(item) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TestItemSearchResult(
|
||||||
|
testItem: TestItem,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
CustomBox(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = MaterialTheme.dime.sm)
|
||||||
|
.clickable(
|
||||||
|
indication = null,
|
||||||
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
|
) {
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(all = MaterialTheme.dime.lg),
|
||||||
|
horizontalAlignment = AbsoluteAlignment.Right
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = testItem.title ?: "بدون عنوان",
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!testItem.normalValue.isNullOrBlank()) {
|
||||||
|
Text(
|
||||||
|
text = "مقدار نرمال: ${testItem.normalValue}",
|
||||||
|
style = MaterialTheme.typography.bodySmall.copy(
|
||||||
|
textDirection = TextDirection.Rtl,
|
||||||
|
textAlign = TextAlign.Right
|
||||||
|
),
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.padding(top = MaterialTheme.dime.xs)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!testItem.detail.isNullOrBlank()) {
|
||||||
|
Text(
|
||||||
|
text = testItem.detail,
|
||||||
|
style = MaterialTheme.typography.bodySmall.copy(
|
||||||
|
textDirection = TextDirection.Rtl,
|
||||||
|
textAlign = TextAlign.Right
|
||||||
|
),
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(top = MaterialTheme.dime.sm)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package com.approagency.drug.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
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 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.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.approagency.drug.domain.model.TestGroup
|
||||||
|
import com.approagency.drug.presentation.common.CustomModalBottomSheet
|
||||||
|
import com.approagency.drug.presentation.viewModel.LabViewModel
|
||||||
|
import com.approagency.drug.presentation.viewModel.TestItemState
|
||||||
|
import com.vada.caller.ui.theme.dime
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun TestDetailSheet(state: TestItemState, testGroup: TestGroup, onDismiss: () -> Unit) {
|
||||||
|
if (state.testItem != null) {
|
||||||
|
val testItems by state.testItem.collectAsState(initial = emptyList())
|
||||||
|
|
||||||
|
CustomModalBottomSheet(
|
||||||
|
onDismiss = onDismiss,
|
||||||
|
content = {
|
||||||
|
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = MaterialTheme.dime.lg),
|
||||||
|
contentPadding = PaddingValues(bottom = MaterialTheme.dime.lg)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = testGroup.fname ?: "جزئیات آزمایش",
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = MaterialTheme.dime.md)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = testGroup.ename ?: "N/A (English Name)",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(bottom = MaterialTheme.dime.sm)
|
||||||
|
)
|
||||||
|
if (testGroup.detail != null){
|
||||||
|
Text(testGroup.detail , style = MaterialTheme.typography.bodySmall,modifier = Modifier.padding(bottom = MaterialTheme.dime.md) )
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
HorizontalDivider(Modifier.padding(bottom = MaterialTheme.dime.md))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (testItems.isEmpty()) {
|
||||||
|
item {
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
items(testItems) { testItem ->
|
||||||
|
TestItemDetailCard(testItem = testItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TestItemDetailCard(testItem: com.approagency.drug.domain.model.TestItem) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = MaterialTheme.dime.md)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = testItem.title ?: "بدون عنوان",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!testItem.normalValue.isNullOrBlank()) {
|
||||||
|
Text(
|
||||||
|
text = "مقدار نرمال: ${testItem.normalValue}",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.padding(top = MaterialTheme.dime.xs)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!testItem.detail.isNullOrBlank()) {
|
||||||
|
Text(
|
||||||
|
text = testItem.detail,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(top = MaterialTheme.dime.sm)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.padding(top = MaterialTheme.dime.md),
|
||||||
|
thickness = 0.5.dp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+90
@@ -0,0 +1,90 @@
|
|||||||
|
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.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.twotone.KeyboardArrowLeft
|
||||||
|
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.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextDirection
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import com.approagency.drug.domain.model.TestGroup
|
||||||
|
import com.approagency.drug.presentation.common.CustomBox
|
||||||
|
import com.vada.caller.ui.theme.dime
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TestGroupItemContent(
|
||||||
|
group: TestGroup,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
|
||||||
|
CustomBox(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(bottom = MaterialTheme.dime.sm)
|
||||||
|
.clickable(
|
||||||
|
indication = null,
|
||||||
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
|
) {
|
||||||
|
onClick()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
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(group.fname ?: "" , style = MaterialTheme.typography.labelLarge , textAlign = TextAlign.Right )
|
||||||
|
Text(group.ename ?: "" , style = MaterialTheme.typography.bodySmall , textAlign = TextAlign.Left)
|
||||||
|
Spacer(modifier = Modifier.padding(vertical = MaterialTheme.dime.xs))
|
||||||
|
Text(group.detail ?: "" , style = MaterialTheme.typography.bodySmall.copy(
|
||||||
|
textDirection = TextDirection.Rtl
|
||||||
|
) , textAlign = TextAlign.Right , maxLines = 2 , softWrap = true , overflow = TextOverflow.Ellipsis)
|
||||||
|
}
|
||||||
|
Row (
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.TwoTone.KeyboardArrowLeft
|
||||||
|
, contentDescription = "see more"
|
||||||
|
)
|
||||||
|
Text("مشاهده جزییات")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -69,6 +69,7 @@ import com.approagency.drug.domain.model.DrugSearchParams
|
|||||||
import com.approagency.drug.presentation.common.CustomBox
|
import com.approagency.drug.presentation.common.CustomBox
|
||||||
import com.approagency.drug.presentation.common.CustomModalBottomSheet
|
import com.approagency.drug.presentation.common.CustomModalBottomSheet
|
||||||
import com.approagency.drug.presentation.common.CustomModalDialog
|
import com.approagency.drug.presentation.common.CustomModalDialog
|
||||||
|
import com.approagency.drug.presentation.common.CustomTextFilled
|
||||||
import com.approagency.drug.presentation.common.Loading
|
import com.approagency.drug.presentation.common.Loading
|
||||||
import com.approagency.drug.presentation.common.PrimaryButton
|
import com.approagency.drug.presentation.common.PrimaryButton
|
||||||
import com.approagency.drug.presentation.components.DarmanContent
|
import com.approagency.drug.presentation.components.DarmanContent
|
||||||
@@ -129,7 +130,7 @@ fun HomeContent(
|
|||||||
onDrugClick: (Int) -> Unit,
|
onDrugClick: (Int) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
var search by remember { mutableStateOf("") }
|
var searchText by remember { mutableStateOf("") }
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxSize().padding(horizontal = MaterialTheme.dime.md)
|
.fillMaxSize().padding(horizontal = MaterialTheme.dime.md)
|
||||||
@@ -139,70 +140,28 @@ fun HomeContent(
|
|||||||
Column (
|
Column (
|
||||||
verticalArrangement = Arrangement.Center
|
verticalArrangement = Arrangement.Center
|
||||||
){
|
){
|
||||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
|
CustomTextFilled(
|
||||||
Box(
|
value = searchText,
|
||||||
modifier = Modifier
|
onValueChange = { searchText = it },
|
||||||
.fillMaxWidth()
|
onSearch = { query ->
|
||||||
.height(45.dp) // Your desired height
|
if (query.isNotBlank()) {
|
||||||
.background(
|
onSearch(query)
|
||||||
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)
|
placeholder = "جستجوی دارو",
|
||||||
) {
|
showClearButton = true,
|
||||||
Icon(
|
showSearchButton = true,
|
||||||
imageVector = Icons.Default.Search,
|
autoSearch = false, // Set to true if you want search while typing
|
||||||
contentDescription = "Search",
|
height = 45
|
||||||
modifier = Modifier.size(18.dp)
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(MaterialTheme.dime.xs))
|
Spacer(modifier = Modifier.height(MaterialTheme.dime.xs))
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
text = "جستجو",
|
text = "جستجو",
|
||||||
height = 40,
|
height = 40,
|
||||||
isLoading = state.drugSearchState.isLoading,
|
isLoading = state.drugSearchState.isLoading,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (search.isNotBlank()) {
|
if (searchText.isNotBlank()) {
|
||||||
onSearch(search)
|
onSearch(searchText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,33 +1,50 @@
|
|||||||
package com.approagency.drug.presentation.screens
|
package com.approagency.drug.presentation.screens
|
||||||
|
|
||||||
|
import android.R.attr.onClick
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.twotone.KeyboardArrowLeft
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.AbsoluteAlignment
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextDirection
|
import androidx.compose.ui.text.style.TextDirection
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.approagency.drug.domain.model.TestGroup
|
import com.approagency.drug.domain.model.TestGroup
|
||||||
|
import com.approagency.drug.domain.model.TestItem
|
||||||
import com.approagency.drug.presentation.common.CustomBox
|
import com.approagency.drug.presentation.common.CustomBox
|
||||||
import com.approagency.drug.presentation.common.CustomModalDialog
|
import com.approagency.drug.presentation.common.CustomModalDialog
|
||||||
|
import com.approagency.drug.presentation.common.CustomTextFilled
|
||||||
import com.approagency.drug.presentation.common.Loading
|
import com.approagency.drug.presentation.common.Loading
|
||||||
import com.approagency.drug.presentation.common.PrimaryButton
|
import com.approagency.drug.presentation.common.PrimaryButton
|
||||||
|
import com.approagency.drug.presentation.components.RetryContent
|
||||||
|
import com.approagency.drug.presentation.components.SearchResultsContent
|
||||||
|
import com.approagency.drug.presentation.components.TestDetailSheet
|
||||||
|
import com.approagency.drug.presentation.components.TestGroupItemContent
|
||||||
import com.approagency.drug.presentation.viewModel.LabViewModel
|
import com.approagency.drug.presentation.viewModel.LabViewModel
|
||||||
import com.vada.caller.ui.theme.dime
|
import com.vada.caller.ui.theme.dime
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -40,11 +57,94 @@ fun LabScreen(
|
|||||||
viewModel: LabViewModel = koinViewModel()
|
viewModel: LabViewModel = koinViewModel()
|
||||||
) {
|
) {
|
||||||
val testGroupsState by viewModel.testGroups.collectAsState()
|
val testGroupsState by viewModel.testGroups.collectAsState()
|
||||||
val isLoading = testGroupsState.isLoading
|
val testItemsState by viewModel.testItems.collectAsState()
|
||||||
val error = testGroupsState.error
|
val searchResultsState by viewModel.searchResults.collectAsState()
|
||||||
|
|
||||||
|
val isLoading = testGroupsState.isLoading || testItemsState.isLoading || searchResultsState.isLoading
|
||||||
|
val error = testGroupsState.error ?: searchResultsState.error
|
||||||
val testGroups = testGroupsState.testGroup
|
val testGroups = testGroupsState.testGroup
|
||||||
|
|
||||||
Column(modifier = modifier.fillMaxSize()) {
|
var selectedGroup by remember { mutableStateOf<TestGroup?>(null) }
|
||||||
|
var selectedItem by remember { mutableStateOf<TestItem?>(null) }
|
||||||
|
var showSheet by remember { mutableStateOf(false) }
|
||||||
|
var searchText by remember { mutableStateOf("") }
|
||||||
|
Column(modifier = modifier.fillMaxSize().padding(horizontal = MaterialTheme.dime.md)
|
||||||
|
.padding(top = MaterialTheme.dime.md),) {
|
||||||
|
CustomTextFilled(
|
||||||
|
value = searchText,
|
||||||
|
onValueChange = {
|
||||||
|
searchText = it
|
||||||
|
viewModel.updateSearchQuery(it)
|
||||||
|
},
|
||||||
|
onSearch = { query ->
|
||||||
|
if (query.isNotBlank()) {
|
||||||
|
viewModel.updateSearchQuery(query)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
placeholder = "جستجو در گروهها و آیتمهای آزمایشگاهی",
|
||||||
|
showClearButton = true,
|
||||||
|
showSearchButton = true,
|
||||||
|
autoSearch = true,
|
||||||
|
height = 45
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(MaterialTheme.dime.xs))
|
||||||
|
PrimaryButton(
|
||||||
|
text = "جستجو",
|
||||||
|
height = 40,
|
||||||
|
isLoading =false,
|
||||||
|
onClick = {}
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(MaterialTheme.dime.xs))
|
||||||
|
if (searchResultsState.query.isNotBlank()) {
|
||||||
|
// Search Results Section
|
||||||
|
SearchResultsContent(
|
||||||
|
searchResult = searchResultsState.searchResults,
|
||||||
|
isLoading = searchResultsState.isLoading,
|
||||||
|
error = searchResultsState.error,
|
||||||
|
onGroupClick = { group ->
|
||||||
|
selectedGroup = group
|
||||||
|
viewModel.getItemByGroupId(group.id)
|
||||||
|
showSheet = true
|
||||||
|
},
|
||||||
|
onItemClick = { item ->
|
||||||
|
selectedItem = item
|
||||||
|
// You can show item detail sheet here
|
||||||
|
// For now, we'll show the parent group
|
||||||
|
viewModel.getItemByGroupId(item.groupId)
|
||||||
|
showSheet = true
|
||||||
|
},
|
||||||
|
onClearSearch = {
|
||||||
|
searchText = ""
|
||||||
|
viewModel.clearSearch()
|
||||||
|
viewModel.loadTestGroups()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else if(error != null){
|
||||||
|
RetryContent(modifier = modifier , error = error , onClick = {
|
||||||
|
viewModel.loadTestGroups()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if (testGroups != null){
|
||||||
|
TestGroupsList(
|
||||||
|
testGroupsFlow = testGroups,
|
||||||
|
viewModel = viewModel,
|
||||||
|
onGroupClick = { group ->
|
||||||
|
selectedGroup = group
|
||||||
|
viewModel.getItemByGroupId(group.id)
|
||||||
|
showSheet = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text("هیچ دادهای موجود نیست")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
CustomModalDialog(
|
CustomModalDialog(
|
||||||
onDismissRequest = { /* maybe disable dismiss while loading */ },
|
onDismissRequest = { /* maybe disable dismiss while loading */ },
|
||||||
@@ -60,54 +160,20 @@ fun LabScreen(
|
|||||||
// .background(MaterialTheme.colorScheme.background.copy(alpha = 0.5f))
|
// .background(MaterialTheme.colorScheme.background.copy(alpha = 0.5f))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else if(error != null){
|
if (showSheet && selectedGroup != null) {
|
||||||
CustomBox(
|
TestDetailSheet(
|
||||||
child = {
|
state = testItemsState,
|
||||||
Column(
|
testGroup = selectedGroup!!,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
onDismiss = { showSheet = false }
|
||||||
modifier = Modifier.padding(MaterialTheme.dime.md)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "خطا در دریافت اطلاعات",
|
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
|
||||||
color = MaterialTheme.colorScheme.error
|
|
||||||
)
|
)
|
||||||
Text(
|
|
||||||
text = error,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
modifier = Modifier.padding(vertical = 8.dp)
|
|
||||||
)
|
|
||||||
PrimaryButton(
|
|
||||||
text = "تلاش مجدد",
|
|
||||||
height = 40,
|
|
||||||
isLoading = false,
|
|
||||||
onClick = { viewModel.loadTestGroups() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else if (testGroups != null){
|
|
||||||
TestGroupsList(
|
|
||||||
testGroupsFlow = testGroups,
|
|
||||||
viewModel = viewModel
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Text("هیچ دادهای موجود نیست")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TestGroupsList(
|
fun TestGroupsList(
|
||||||
testGroupsFlow: Flow<List<TestGroup>>,
|
testGroupsFlow: Flow<List<TestGroup>>,
|
||||||
viewModel: LabViewModel
|
viewModel: LabViewModel,
|
||||||
|
onGroupClick: (TestGroup) -> Unit
|
||||||
) {
|
) {
|
||||||
val testGroups by testGroupsFlow.collectAsState(initial = emptyList())
|
val testGroups by testGroupsFlow.collectAsState(initial = emptyList())
|
||||||
|
|
||||||
@@ -128,56 +194,12 @@ fun TestGroupsList(
|
|||||||
} else {
|
} else {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
|
.fillMaxSize().padding( vertical =MaterialTheme.dime.md )
|
||||||
) {
|
) {
|
||||||
items(testGroups) { group ->
|
items(testGroups) { group ->
|
||||||
TestGroupItem(
|
TestGroupItemContent(
|
||||||
group = group,
|
group = group,
|
||||||
onClick = {
|
onClick = { onGroupClick(group) }
|
||||||
// Navigate to test items of this group
|
|
||||||
// viewModel.loadTestItemsByGroup(group.id)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun TestGroupItem(
|
|
||||||
group: TestGroup,
|
|
||||||
onClick: () -> Unit
|
|
||||||
) {
|
|
||||||
CustomBox (
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(bottom = MaterialTheme.dime.sm)
|
|
||||||
.clickable(
|
|
||||||
indication = null,
|
|
||||||
interactionSource = remember { MutableInteractionSource() }
|
|
||||||
) {
|
|
||||||
onClick()
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Column (
|
|
||||||
verticalArrangement = Arrangement.Center,
|
|
||||||
horizontalAlignment = Alignment.Start
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = group.fname ?: group.ename ?: "بدون نام",
|
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
|
||||||
textAlign = TextAlign.Right,
|
|
||||||
textDirection = TextDirection.Rtl
|
|
||||||
),
|
|
||||||
modifier = Modifier.padding(vertical = MaterialTheme.dime.xs)
|
|
||||||
)
|
|
||||||
group.detail?.let {
|
|
||||||
Text(
|
|
||||||
text = it,
|
|
||||||
style = MaterialTheme.typography.bodySmall.copy(
|
|
||||||
textAlign = TextAlign.Right ,
|
|
||||||
textDirection = TextDirection.Rtl
|
|
||||||
),
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
modifier = Modifier.padding(horizontal = MaterialTheme.dime.lg)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,14 +69,12 @@ fun RootScreen(navHostController: NavHostController, modifier: Modifier){
|
|||||||
titleContentColor = MaterialTheme.colorScheme.primary,
|
titleContentColor = MaterialTheme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
title = {
|
title = {
|
||||||
Text("جستجوی دارویی" , textAlign = TextAlign.Right , style = MaterialTheme.typography.titleLarge , fontWeight = FontWeight.W700 )
|
Text("دستیار سلامت" , textAlign = TextAlign.Right , style = MaterialTheme.typography.titleLarge , fontWeight = FontWeight.W700 )
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}},
|
}},
|
||||||
// modifier = modifier.fillMaxSize(),
|
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
NavigationBar(
|
NavigationBar(
|
||||||
|
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import com.approagency.drug.domain.usecase.GetDarmanUseCase
|
|||||||
import com.approagency.drug.domain.usecase.GetDrugDetailUseCase
|
import com.approagency.drug.domain.usecase.GetDrugDetailUseCase
|
||||||
import com.approagency.drug.domain.usecase.GetDrugSearchUseCase
|
import com.approagency.drug.domain.usecase.GetDrugSearchUseCase
|
||||||
import com.approagency.drug.presentation.screens.HomeScreen
|
import com.approagency.drug.presentation.screens.HomeScreen
|
||||||
|
import com.approagency.drug.utils.retryWithBackoff
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
@@ -34,12 +35,23 @@ class HomeViewModel (
|
|||||||
getDarmani()
|
getDarmani()
|
||||||
}
|
}
|
||||||
fun getDarmani() {
|
fun getDarmani() {
|
||||||
_uiState.update { it.copy(darmanState = it.darmanState.copy(
|
_uiState.update {
|
||||||
isLoading = true
|
it.copy(
|
||||||
) , showDarmanList = true ) }
|
darmanState = it.darmanState.copy(isLoading = true),
|
||||||
|
showDarmanList = true
|
||||||
|
)
|
||||||
|
}
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
retryWithBackoff(
|
||||||
val result = getDarmanUseCase.invoke()
|
maxRetries = 3,
|
||||||
|
onRetry = { attempt, delay ->
|
||||||
|
// Optional: log retry attempt
|
||||||
|
println("Retrying getDarmani, attempt $attempt, delay $delay ms")
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
getDarmanUseCase.invoke()
|
||||||
|
}.fold(
|
||||||
|
onSuccess = { result ->
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
darmanState = it.darmanState.copy(
|
darmanState = it.darmanState.copy(
|
||||||
@@ -48,10 +60,20 @@ class HomeViewModel (
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (e: HttpException){
|
},
|
||||||
handleError(e.message)
|
onFailure = { error ->
|
||||||
|
handleError(error.message ?: "Unknown error")
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
darmanState = it.darmanState.copy(
|
||||||
|
isLoading = false,
|
||||||
|
getDarmani = Result.failure(error)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun searchDrugs(drugSearchParams: DrugSearchParams) {
|
fun searchDrugs(drugSearchParams: DrugSearchParams) {
|
||||||
|
|||||||
@@ -9,16 +9,20 @@ import com.approagency.drug.domain.model.TestGroup
|
|||||||
import com.approagency.drug.domain.model.TestItem
|
import com.approagency.drug.domain.model.TestItem
|
||||||
import com.approagency.drug.domain.usecase.GetTestGroupUseCase
|
import com.approagency.drug.domain.usecase.GetTestGroupUseCase
|
||||||
import com.approagency.drug.domain.usecase.GetTestItemByGroupId
|
import com.approagency.drug.domain.usecase.GetTestItemByGroupId
|
||||||
|
import com.approagency.drug.domain.usecase.SearchTestsUseCase
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.debounce
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class LabViewModel (
|
class LabViewModel (
|
||||||
private val getTestGroupUseCase: GetTestGroupUseCase,
|
private val getTestGroupUseCase: GetTestGroupUseCase,
|
||||||
private val getTestItemByGroupId: GetTestItemByGroupId
|
private val getTestItemByGroupId: GetTestItemByGroupId,
|
||||||
|
private val searchTestsUseCase: SearchTestsUseCase
|
||||||
) : ViewModel(){
|
) : ViewModel(){
|
||||||
private val _testGroups = MutableStateFlow(TestGroupItem())
|
private val _testGroups = MutableStateFlow(TestGroupItem())
|
||||||
val testGroups: StateFlow<TestGroupItem> = _testGroups.asStateFlow()
|
val testGroups: StateFlow<TestGroupItem> = _testGroups.asStateFlow()
|
||||||
@@ -26,10 +30,71 @@ class LabViewModel (
|
|||||||
private val _testItems = MutableStateFlow(TestItemState())
|
private val _testItems = MutableStateFlow(TestItemState())
|
||||||
val testItems: StateFlow<TestItemState> = _testItems.asStateFlow()
|
val testItems: StateFlow<TestItemState> = _testItems.asStateFlow()
|
||||||
|
|
||||||
|
|
||||||
|
private val _searchResults = MutableStateFlow(SearchResultState())
|
||||||
|
val searchResults: StateFlow<SearchResultState> = _searchResults.asStateFlow()
|
||||||
|
|
||||||
|
private val searchQuery = MutableStateFlow("")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
loadTestGroups()
|
loadTestGroups()
|
||||||
|
setupSearch()
|
||||||
|
}
|
||||||
|
private fun setupSearch() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
searchQuery
|
||||||
|
.debounce(1000) // Wait 500ms after user stops typing
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.collect { query ->
|
||||||
|
if (query.isNotBlank()) {
|
||||||
|
performSearch(query)
|
||||||
|
} else {
|
||||||
|
_searchResults.update {
|
||||||
|
SearchResultState(
|
||||||
|
isLoading = false,
|
||||||
|
searchResults = null,
|
||||||
|
query = ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateSearchQuery(query: String) {
|
||||||
|
searchQuery.value = query
|
||||||
|
_searchResults.update {
|
||||||
|
it.copy(
|
||||||
|
isLoading = query.isNotBlank(),
|
||||||
|
query = query
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun performSearch(query: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
val result = searchTestsUseCase.invoke(query)
|
||||||
|
result.collect { (groups, items) ->
|
||||||
|
_searchResults.update {
|
||||||
|
it.copy(
|
||||||
|
isLoading = false,
|
||||||
|
searchResults = SearchResult(groups = groups, items = items),
|
||||||
|
error = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_searchResults.update {
|
||||||
|
it.copy(
|
||||||
|
isLoading = false,
|
||||||
|
searchResults = null,
|
||||||
|
error = e.message ?: "خطا در جستجو"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
fun loadTestGroups(){
|
fun loadTestGroups(){
|
||||||
_testGroups.update {
|
_testGroups.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -51,7 +116,12 @@ class LabViewModel (
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fun clearSearch() {
|
||||||
|
searchQuery.value = ""
|
||||||
|
_searchResults.update {
|
||||||
|
SearchResultState()
|
||||||
|
}
|
||||||
|
}
|
||||||
fun getItemByGroupId(id:Int){
|
fun getItemByGroupId(id:Int){
|
||||||
_testItems.update { it.copy(isLoading = true) }
|
_testItems.update { it.copy(isLoading = true) }
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
@@ -91,4 +161,14 @@ data class TestItemState(
|
|||||||
val testItem: Flow<List<TestItem>>? = null,
|
val testItem: Flow<List<TestItem>>? = null,
|
||||||
val error:String? = null
|
val error:String? = null
|
||||||
)
|
)
|
||||||
|
data class SearchResultState(
|
||||||
|
val isLoading: Boolean = false,
|
||||||
|
val searchResults: SearchResult? = null,
|
||||||
|
val error: String? = null,
|
||||||
|
val query: String = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SearchResult(
|
||||||
|
val groups: List<TestGroup>,
|
||||||
|
val items: List<TestItem>
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.approagency.drug.utils
|
||||||
|
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
|
suspend fun <T> retryWithBackoff(
|
||||||
|
maxRetries: Int = 3,
|
||||||
|
initialDelayMs: Long = 1000,
|
||||||
|
maxDelayMs: Long = 5000,
|
||||||
|
factor: Double = 2.0,
|
||||||
|
onRetry: ((attempt: Int, delayMs: Long) -> Unit)? = null,
|
||||||
|
block: suspend () -> T
|
||||||
|
): Result<T> {
|
||||||
|
var currentDelay = initialDelayMs
|
||||||
|
repeat(maxRetries - 1) { attempt ->
|
||||||
|
try {
|
||||||
|
return Result.success(block())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
onRetry?.invoke(attempt + 1, currentDelay)
|
||||||
|
delay(currentDelay)
|
||||||
|
currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelayMs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Last attempt
|
||||||
|
return try {
|
||||||
|
Result.success(block())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Result.failure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,3 +22,9 @@ kotlin.code.style=official
|
|||||||
# thereby reducing the size of the R class for that library
|
# thereby reducing the size of the R class for that library
|
||||||
android.nonTransitiveRClass=true
|
android.nonTransitiveRClass=true
|
||||||
kotlin.compiler.execution.strategy=in-process
|
kotlin.compiler.execution.strategy=in-process
|
||||||
|
# Disable downloading Gradle sources
|
||||||
|
org.gradle.caching=false
|
||||||
|
org.gradle.daemon=true
|
||||||
|
|
||||||
|
# Prevent Gradle from downloading sources
|
||||||
|
org.gradle.internal.launcher.welcomeMessageDisplayed=false
|
||||||
-1
@@ -1,4 +1,3 @@
|
|||||||
#Thu Feb 26 19:18:21 IRST 2026
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://maven.myket.ir/gradle/distributions/gradle-8.13-bin.zip
|
distributionUrl=https\://maven.myket.ir/gradle/distributions/gradle-8.13-bin.zip
|
||||||
|
|||||||
+8
-8
@@ -1,19 +1,19 @@
|
|||||||
pluginManagement {
|
pluginManagement {
|
||||||
repositories {
|
repositories {
|
||||||
maven { url = uri("https://maven.myket.ir") }
|
maven { url = uri("https://maven.myket.ir") }
|
||||||
google {
|
// google {
|
||||||
content {
|
// content {
|
||||||
includeGroupByRegex("com\\.android.*")
|
// includeGroupByRegex("com\\.android.*")
|
||||||
includeGroupByRegex("com\\.google.*")
|
// includeGroupByRegex("com\\.google.*")
|
||||||
includeGroupByRegex("androidx.*")
|
// includeGroupByRegex("androidx.*")
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
// mavenCentral()
|
// mavenCentral()
|
||||||
// gradlePluginPortal()
|
// gradlePluginPortal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
|
||||||
repositories {
|
repositories {
|
||||||
//google()
|
//google()
|
||||||
//mavenCentral()
|
//mavenCentral()
|
||||||
|
|||||||
Reference in New Issue
Block a user