feat: add test items and group tests

This commit is contained in:
2026-05-14 20:45:31 +03:30
parent 989200cd9b
commit 42938fb651
74 changed files with 3516 additions and 2700 deletions
+3 -3
View File
@@ -1,3 +1,3 @@
# Default ignored files # Default ignored files
/shelf/ /shelf/
/workspace.xml /workspace.xml
+5 -5
View File
@@ -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="AndroidProjectSystem"> <component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" /> <option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component> </component>
</project> </project>
+5 -5
View File
@@ -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>
+9 -9
View File
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="deploymentTargetSelector"> <component name="deploymentTargetSelector">
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
</SelectionState> </SelectionState>
</selectionStates> </selectionStates>
</component> </component>
</project> </project>
+18 -18
View File
@@ -1,19 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" /> <component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" /> <option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" /> <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" /> <option value="$PROJECT_DIR$/app" />
</set> </set>
</option> </option>
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
</component> </component>
</project> </project>
+9 -9
View File
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectMigrations"> <component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome"> <option name="MigrateToGradleLocalJavaHome">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
</set> </set>
</option> </option>
</component> </component>
</project> </project>
+2 -1
View File
@@ -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">
+16 -16
View File
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="RunConfigurationProducerService"> <component name="RunConfigurationProducerService">
<option name="ignoredProducers"> <option name="ignoredProducers">
<set> <set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" /> <option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" /> <option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" /> <option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" /> <option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" /> <option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" /> <option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" /> <option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" /> <option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set> </set>
</option> </option>
</component> </component>
</project> </project>
Generated
+5 -5
View File
@@ -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="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" /> <mapping directory="$PROJECT_DIR$" vcs="Git" />
</component> </component>
</project> </project>
+39 -39
View File
@@ -2,44 +2,44 @@ kotlin version: 2.0.21
error message: Failed connecting to the daemon in 4 retries error message: Failed connecting to the daemon in 4 retries
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214) at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159) at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111) at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76) at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63) at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66) at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62) at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100) at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62) at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44) at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41) at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54) at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41) at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59) at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174) at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source) at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:31) at org.gradle.internal.Factories$1.create(Factories.java:31)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263) at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127) at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132) at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source) at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48) at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source) at java.base/java.lang.Thread.run(Unknown Source)
+39 -39
View File
@@ -2,44 +2,44 @@ kotlin version: 2.0.21
error message: Failed connecting to the daemon in 4 retries error message: Failed connecting to the daemon in 4 retries
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214) at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159) at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111) at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76) at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63) at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66) at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62) at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100) at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62) at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44) at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41) at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54) at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41) at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59) at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174) at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source) at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:31) at org.gradle.internal.Factories$1.create(Factories.java:31)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263) at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127) at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132) at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source) at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48) at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source) at java.base/java.lang.Thread.run(Unknown Source)
+39 -39
View File
@@ -2,44 +2,44 @@ kotlin version: 2.0.21
error message: Failed connecting to the daemon in 4 retries error message: Failed connecting to the daemon in 4 retries
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214) at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159) at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111) at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76) at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63) at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66) at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62) at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100) at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62) at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44) at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41) at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54) at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41) at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59) at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174) at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source) at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:31) at org.gradle.internal.Factories$1.create(Factories.java:31)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263) at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127) at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132) at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source) at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48) at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source) at java.base/java.lang.Thread.run(Unknown Source)
+39 -39
View File
@@ -2,44 +2,44 @@ kotlin version: 2.0.21
error message: Failed connecting to the daemon in 4 retries error message: Failed connecting to the daemon in 4 retries
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214) at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159) at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111) at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76) at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63) at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66) at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62) at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100) at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62) at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44) at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41) at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54) at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41) at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59) at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174) at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source) at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:31) at org.gradle.internal.Factories$1.create(Factories.java:31)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263) at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127) at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132) at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source) at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48) at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source) at java.base/java.lang.Thread.run(Unknown Source)
+39 -39
View File
@@ -2,44 +2,44 @@ kotlin version: 2.0.21
error message: Failed connecting to the daemon in 4 retries error message: Failed connecting to the daemon in 4 retries
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214) at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159) at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111) at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76) at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63) at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66) at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62) at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100) at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62) at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44) at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41) at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54) at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41) at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59) at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174) at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source) at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:31) at org.gradle.internal.Factories$1.create(Factories.java:31)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263) at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127) at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132) at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source) at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48) at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source) at java.base/java.lang.Thread.run(Unknown Source)
+3 -3
View File
@@ -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
+30 -30
View File
@@ -1,31 +1,31 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<application <application
android:name=".DrugApp" android:name=".DrugApp"
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.Drug"> android:theme="@style/Theme.Drug">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/Theme.Drug"> android:theme="@style/Theme.Drug">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<meta-data <meta-data
android:name="preloaded_fonts" android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" /> android:resource="@array/preloaded_fonts" />
</application> </application>
</manifest> </manifest>
@@ -1,25 +1,25 @@
package com.approagency.drug package com.approagency.drug
import android.app.Application import android.app.Application
import com.approagency.drug.data.local.database.LabDatabase import com.approagency.drug.data.local.database.LabDatabase
import com.approagency.drug.di.appModule import com.approagency.drug.di.appModule
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
class DrugApp : Application(){ class DrugApp : Application(){
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
startKoin { startKoin {
androidContext(this@DrugApp) androidContext(this@DrugApp)
modules(appModule) modules(appModule)
printLogger() printLogger()
} }
// Preload database (optional, for faster access) // Preload database (optional, for faster access)
GlobalScope.launch { GlobalScope.launch {
LabDatabase.getInstance(this@DrugApp) LabDatabase.getInstance(this@DrugApp)
} }
} }
} }
@@ -1,13 +1,13 @@
package com.approagency.drug.data.dto package com.approagency.drug.data.dto
data class DarmanModel( data class DarmanModel(
val success : String, val success : String,
val message:String, val message:String,
val data: List<DarmanList> val data: List<DarmanList>
) )
data class DarmanList( data class DarmanList(
val cod : Int, val cod : Int,
val nam_fa:String, val nam_fa:String,
val nam_en:String, val nam_en:String,
) )
@@ -1,74 +1,74 @@
package com.approagency.drug.data.dto package com.approagency.drug.data.dto
data class DrugModels( data class DrugModels(
val success: Boolean, val success: Boolean,
val message: String?, val message: String?,
val data: DrugDetail?, val data: DrugDetail?,
val meta: Meta? val meta: Meta?
) )
data class DrugDetail( data class DrugDetail(
val cod: Int?, val cod: Int?,
val goroh_darmani_detail_cod: Int?, val goroh_darmani_detail_cod: Int?,
val goroh_daroei_cod: Int?, val goroh_daroei_cod: Int?,
val goroh_farmakologic_cod: Int?, val goroh_farmakologic_cod: Int?,
val goroh_darmani_cod: Int?, val goroh_darmani_cod: Int?,
val nam_fa: String?, val nam_fa: String?,
val nam_en: String?, val nam_en: String?,
val mavaredmasraf: String?, val mavaredmasraf: String?,
val meghdarmasraf: String?, val meghdarmasraf: String?,
val masrafdarhamelegi: String?, val masrafdarhamelegi: String?,
val masrafdarshirdehi: String?, val masrafdarshirdehi: String?,
val manemasraf: String?, val manemasraf: String?,
val avarez: String?, val avarez: String?,
val tadakhol: String?, val tadakhol: String?,
val mekanismtasir: String?, val mekanismtasir: String?,
val nokte: String?, val nokte: String?,
val hoshdar: String?, val hoshdar: String?,
val sharayetnegahdari: String?, val sharayetnegahdari: String?,
val ashkal_daroei: String?, val ashkal_daroei: String?,
val created_at: String?, val created_at: String?,
val updated_at: String?, val updated_at: String?,
val goroh_daroei: GorohDaroei?, val goroh_daroei: GorohDaroei?,
val goroh_darmani: GorohDarmani?, val goroh_darmani: GorohDarmani?,
val goroh_darmani_detail: GorohDarmaniDetail? val goroh_darmani_detail: GorohDarmaniDetail?
) )
data class GorohDaroei( data class GorohDaroei(
val cod: Int?, val cod: Int?,
val nam: String?, val nam: String?,
val created_at: String?, val created_at: String?,
val updated_at: String? val updated_at: String?
) )
data class GorohDarmani( data class GorohDarmani(
val cod: Int?, val cod: Int?,
val nam_fa: String?, val nam_fa: String?,
val nam_en: String?, val nam_en: String?,
val giyahi_ya_shimiyaei: String?, val giyahi_ya_shimiyaei: String?,
val image: String?, val image: String?,
val created_at: String?, val created_at: String?,
val updated_at: String? val updated_at: String?
) )
data class GorohDarmaniDetail( data class GorohDarmaniDetail(
val cod: Int?, val cod: Int?,
val nam: String?, val nam: String?,
val created_at: String?, val created_at: String?,
val updated_at: String? val updated_at: String?
) )
data class Meta( data class Meta(
val current_page: Int?, val current_page: Int?,
val last_page: Int?, val last_page: Int?,
val per_page: Int?, val per_page: Int?,
val total: Int? val total: Int?
) )
// For list response compatibility // For list response compatibility
data class DrugListResponse( data class DrugListResponse(
val success: Boolean, val success: Boolean,
val message: String?, val message: String?,
val data: List<DrugDetail> = emptyList(), val data: List<DrugDetail> = emptyList(),
val meta: Meta? val meta: Meta?
) )
@@ -1,32 +1,46 @@
package com.approagency.drug.data.local.dao package com.approagency.drug.data.local.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import com.approagency.drug.data.local.entities.TestGroupEntity import com.approagency.drug.data.local.entities.TestGroupEntity
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface TestGroupDao { interface TestGroupDao {
@Query("SELECT * FROM testGroup ORDER BY Id") @Query("SELECT * FROM testGroup ORDER BY Id")
fun getAllGroups(): Flow<List<TestGroupEntity>> fun getAllGroups(): Flow<List<TestGroupEntity>>
@Query("SELECT * FROM testGroup WHERE Id = :groupId") @Query("SELECT * FROM testGroup WHERE Id = :groupId")
suspend fun getGroupById(groupId: Int): TestGroupEntity? suspend fun getGroupById(groupId: Int): TestGroupEntity?
@Query("SELECT * FROM testGroup WHERE Isparent = '1'") @Query("SELECT * FROM testGroup WHERE Isparent = '1'")
fun getParentGroups(): Flow<List<TestGroupEntity>> fun getParentGroups(): Flow<List<TestGroupEntity>>
@Query("SELECT * FROM testGroup WHERE Isparent = '0'") @Query("SELECT * FROM testGroup WHERE Isparent = '0'")
fun getChildGroups(): Flow<List<TestGroupEntity>> fun getChildGroups(): Flow<List<TestGroupEntity>>
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertGroup(group: TestGroupEntity) suspend fun insertGroup(group: TestGroupEntity)
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAllGroups(groups: List<TestGroupEntity>) suspend fun insertAllGroups(groups: List<TestGroupEntity>)
@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>>
} }
@@ -1,36 +1,46 @@
package com.approagency.drug.data.local.dao package com.approagency.drug.data.local.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
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
@Dao @Dao
interface TestItemDao { interface TestItemDao {
@Query("SELECT * FROM testItem ORDER BY Id") @Query("SELECT * FROM testItem ORDER BY Id")
fun getAllItems(): Flow<List<TestItemEntity>> fun getAllItems(): Flow<List<TestItemEntity>>
@Query("SELECT * FROM testItem WHERE Group_Id = :groupId ORDER BY Id") @Query("SELECT * FROM testItem WHERE Group_Id = :groupId ORDER BY Id")
fun getItemsByGroupId(groupId: Int): Flow<List<TestItemEntity>> fun getItemsByGroupId(groupId: Int): Flow<List<TestItemEntity>>
@Query("SELECT * FROM testItem WHERE Id = :itemId") @Query("SELECT * FROM testItem WHERE Id = :itemId")
suspend fun getItemById(itemId: Int): TestItemEntity? suspend fun getItemById(itemId: Int): TestItemEntity?
@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 || '%'
fun searchTestItems(searchQuery: String): Flow<List<TestItemEntity>> OR ti.Detail LIKE '%' || :searchQuery || '%'
OR tg.Fname LIKE '%' || :searchQuery || '%'
@Insert(onConflict = OnConflictStrategy.REPLACE) OR tg.Ename LIKE '%' || :searchQuery || '%'
suspend fun insertItem(item: TestItemEntity) ORDER BY
CASE
@Insert(onConflict = OnConflictStrategy.REPLACE) WHEN ti.Title LIKE '%' || :searchQuery || '%' THEN 1
suspend fun insertAllItems(items: List<TestItemEntity>) WHEN tg.Fname LIKE '%' || :searchQuery || '%' THEN 2
ELSE 3
@Query("DELETE FROM testItem") END
suspend fun deleteAllItems() """)
fun searchTestItems(searchQuery: String): Flow<List<TestItemEntity>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertItem(item: TestItemEntity)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAllItems(items: List<TestItemEntity>)
@Query("DELETE FROM testItem")
suspend fun deleteAllItems()
} }
@@ -1,43 +1,43 @@
package com.approagency.drug.data.local.database package com.approagency.drug.data.local.database
import android.content.Context import android.content.Context
import androidx.room.Database import androidx.room.Database
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import com.approagency.drug.data.local.dao.TestGroupDao import com.approagency.drug.data.local.dao.TestGroupDao
import com.approagency.drug.data.local.dao.TestItemDao 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
@Database( @Database(
entities = [ entities = [
TestGroupEntity::class, TestGroupEntity::class,
TestItemEntity::class TestItemEntity::class
], ],
version = 1, version = 1,
exportSchema = false exportSchema = false
) )
abstract class LabDatabase : RoomDatabase() { abstract class LabDatabase : RoomDatabase() {
abstract fun testGroupDao(): TestGroupDao abstract fun testGroupDao(): TestGroupDao
abstract fun testItemDao(): TestItemDao abstract fun testItemDao(): TestItemDao
companion object { companion object {
@Volatile @Volatile
private var INSTANCE: LabDatabase? = null private var INSTANCE: LabDatabase? = null
fun getInstance(context: Context): LabDatabase { fun getInstance(context: Context): LabDatabase {
return INSTANCE ?: synchronized(this) { return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder( val instance = Room.databaseBuilder(
context.applicationContext, context.applicationContext,
LabDatabase::class.java, LabDatabase::class.java,
"lab_tests.db" // This will use your existing database "lab_tests.db" // This will use your existing database
) )
.createFromAsset("lab_tests.db") // IMPORTANT: Copy from assets .createFromAsset("lab_tests.db") // IMPORTANT: Copy from assets
.fallbackToDestructiveMigration() // For development only .fallbackToDestructiveMigration() // For development only
.build() .build()
INSTANCE = instance INSTANCE = instance
instance instance
} }
} }
} }
} }
@@ -1,24 +1,24 @@
package com.approagency.drug.data.local.entities package com.approagency.drug.data.local.entities
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
@Entity(tableName = "testGroup") @Entity(tableName = "testGroup")
data class TestGroupEntity( data class TestGroupEntity(
@PrimaryKey @PrimaryKey
@ColumnInfo(name = "Id") @ColumnInfo(name = "Id")
val id: Int, val id: Int,
@ColumnInfo(name = "Ename") @ColumnInfo(name = "Ename")
val ename: String?, val ename: String?,
@ColumnInfo(name = "Fname") @ColumnInfo(name = "Fname")
val fname: String?, val fname: String?,
@ColumnInfo(name = "Detail") @ColumnInfo(name = "Detail")
val detail: String?, val detail: String?,
@ColumnInfo(name = "Isparent") @ColumnInfo(name = "Isparent")
val isParent: String? val isParent: String?
) )
@@ -1,37 +1,37 @@
package com.approagency.drug.data.local.entities package com.approagency.drug.data.local.entities
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.Index import androidx.room.Index
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
@Entity( @Entity(
tableName = "testItem", tableName = "testItem",
// foreignKeys = [ // foreignKeys = [
// ForeignKey( // ForeignKey(
// entity = TestGroupEntity::class, // entity = TestGroupEntity::class,
// parentColumns = ["Id"], // parentColumns = ["Id"],
// childColumns = ["Group_Id"], // childColumns = ["Group_Id"],
// onDelete = ForeignKey.CASCADE // onDelete = ForeignKey.CASCADE
// ) // )
// ], // ],
indices = [Index(value = ["Group_Id"])] indices = [Index(value = ["Group_Id"])]
) )
data class TestItemEntity( data class TestItemEntity(
@PrimaryKey @PrimaryKey
@ColumnInfo(name = "Id") @ColumnInfo(name = "Id")
val id: Int, val id: Int,
@ColumnInfo(name = "Group_Id") @ColumnInfo(name = "Group_Id")
val groupId: Int, val groupId: Int,
@ColumnInfo(name = "Title") @ColumnInfo(name = "Title")
val title: String?, val title: String?,
@ColumnInfo(name = "Normal_Value") @ColumnInfo(name = "Normal_Value")
val normalValue: String?, val normalValue: String?,
@ColumnInfo(name = "Detail") @ColumnInfo(name = "Detail")
val detail: String? val detail: String?
) )
@@ -1,29 +1,29 @@
package com.approagency.drug.data.remote package com.approagency.drug.data.remote
import com.approagency.drug.data.dto.DarmanModel import com.approagency.drug.data.dto.DarmanModel
import com.approagency.drug.data.dto.DrugListResponse import com.approagency.drug.data.dto.DrugListResponse
import com.approagency.drug.data.dto.DrugModels import com.approagency.drug.data.dto.DrugModels
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Path import retrofit2.http.Path
import retrofit2.http.Query import retrofit2.http.Query
interface DrugApiService { interface DrugApiService {
@GET("drugs/search") @GET("drugs/search")
suspend fun getDrugs( suspend fun getDrugs(
@Query("q")query: String?, @Query("q")query: String?,
@Query("per_page")perPage:Int? = 20, @Query("per_page")perPage:Int? = 20,
@Query("with_relations")withRelations: Boolean? =true, @Query("with_relations")withRelations: Boolean? =true,
@Query("goroh_daroei_cod")drugGroup:Int?, @Query("goroh_daroei_cod")drugGroup:Int?,
@Query("goroh_darmani_cod")healGroup:Int?, @Query("goroh_darmani_cod")healGroup:Int?,
): DrugListResponse ): DrugListResponse
@GET("drugs/{cod}") @GET("drugs/{cod}")
suspend fun getDrugDetail( suspend fun getDrugDetail(
@Path("cod") cod: Int @Path("cod") cod: Int
): DrugModels ): DrugModels
@GET("drugs/goroh-darmani") @GET("drugs/goroh-darmani")
suspend fun getGorohDaroei(): DarmanModel suspend fun getGorohDaroei(): DarmanModel
} }
@@ -1,39 +1,39 @@
package com.approagency.drug.data.repository package com.approagency.drug.data.repository
import com.approagency.drug.data.dto.DarmanModel import com.approagency.drug.data.dto.DarmanModel
import com.approagency.drug.data.dto.DrugListResponse import com.approagency.drug.data.dto.DrugListResponse
import com.approagency.drug.data.dto.DrugModels import com.approagency.drug.data.dto.DrugModels
import com.approagency.drug.data.remote.DrugApiService import com.approagency.drug.data.remote.DrugApiService
import com.approagency.drug.domain.model.DrugSearchParams import com.approagency.drug.domain.model.DrugSearchParams
import com.approagency.drug.domain.repository.DrugRepository import com.approagency.drug.domain.repository.DrugRepository
class DrugRepositoryImpl( class DrugRepositoryImpl(
private val apiService: DrugApiService private val apiService: DrugApiService
): DrugRepository { ): DrugRepository {
override suspend fun searchDrug(params: DrugSearchParams): Result<DrugListResponse> { override suspend fun searchDrug(params: DrugSearchParams): Result<DrugListResponse> {
return try { return try {
val response = apiService.getDrugs(query = params.query , perPage = params.perPage , withRelations = params.withRelations , healGroup = params.healGroup , drugGroup = params.drugGroup ) val response = apiService.getDrugs(query = params.query , perPage = params.perPage , withRelations = params.withRelations , healGroup = params.healGroup , drugGroup = params.drugGroup )
return Result.success(response) return Result.success(response)
}catch (e: Exception){ }catch (e: Exception){
Result.failure(e) Result.failure(e)
} }
} }
override suspend fun drugDetail(cod: Int): Result<DrugModels> { override suspend fun drugDetail(cod: Int): Result<DrugModels> {
return try { return try {
val response= apiService.getDrugDetail(cod = cod) val response= apiService.getDrugDetail(cod = cod)
return Result.success(response) return Result.success(response)
}catch (e: Exception){ }catch (e: Exception){
Result.failure(e) Result.failure(e)
} }
} }
override suspend fun getGorohDaroei(): Result<DarmanModel> { override suspend fun getGorohDaroei(): Result<DarmanModel> {
return try { return try {
val response = apiService.getGorohDaroei() val response = apiService.getGorohDaroei()
return Result.success(response) return Result.success(response)
}catch (e: Exception){ }catch (e: Exception){
Result.failure(e) Result.failure(e)
} }
} }
} }
@@ -1,24 +1,35 @@
package com.approagency.drug.data.repository package com.approagency.drug.data.repository
import com.approagency.drug.data.local.dao.TestGroupDao import com.approagency.drug.data.local.dao.TestGroupDao
import com.approagency.drug.data.local.dao.TestItemDao 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 (
private val testGroupDao: TestGroupDao, class LabRepositoryImpl (
private val testItemDao: TestItemDao private val testGroupDao: TestGroupDao,
) { private val testItemDao: TestItemDao
// Test Group Operations ) {
fun getAllGroups(): Flow<List<TestGroupEntity>> = testGroupDao.getAllGroups() // Test Group Operations
fun getParentGroups(): Flow<List<TestGroupEntity>> = testGroupDao.getParentGroups() fun getAllGroups(): Flow<List<TestGroupEntity>> = testGroupDao.getAllGroups()
fun getChildGroups(): Flow<List<TestGroupEntity>> = testGroupDao.getChildGroups() fun getParentGroups(): Flow<List<TestGroupEntity>> = testGroupDao.getParentGroups()
suspend fun getGroupById(groupId: Int): TestGroupEntity? = testGroupDao.getGroupById(groupId) fun getChildGroups(): Flow<List<TestGroupEntity>> = testGroupDao.getChildGroups()
suspend fun getGroupById(groupId: Int): TestGroupEntity? = testGroupDao.getGroupById(groupId)
// Test Item Operations
fun getAllItems(): Flow<List<TestItemEntity>> = testItemDao.getAllItems() // Test Item Operations
fun getItemsByGroupId(groupId: Int): Flow<List<TestItemEntity>> = testItemDao.getItemsByGroupId(groupId) fun getAllItems(): Flow<List<TestItemEntity>> = testItemDao.getAllItems()
suspend fun getItemById(itemId: Int): TestItemEntity? = testItemDao.getItemById(itemId) fun getItemsByGroupId(groupId: Int): Flow<List<TestItemEntity>> = testItemDao.getItemsByGroupId(groupId)
fun searchTestItems(query: String): Flow<List<TestItemEntity>> = testItemDao.searchTestItems(query) suspend fun getItemById(itemId: Int): TestItemEntity? = testItemDao.getItemById(itemId)
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)
}
}
} }
@@ -1,79 +1,82 @@
package com.approagency.drug.di package com.approagency.drug.di
import android.app.Application import android.app.Application
import com.approagency.drug.data.local.database.LabDatabase import com.approagency.drug.data.local.database.LabDatabase
import com.approagency.drug.data.remote.DrugApiService import com.approagency.drug.data.remote.DrugApiService
import com.approagency.drug.data.repository.DrugRepositoryImpl import com.approagency.drug.data.repository.DrugRepositoryImpl
import com.approagency.drug.data.repository.LabRepositoryImpl import com.approagency.drug.data.repository.LabRepositoryImpl
import com.approagency.drug.domain.repository.DrugRepository import com.approagency.drug.domain.repository.DrugRepository
import com.approagency.drug.domain.usecase.GetDarmanUseCase 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.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.presentation.viewModel.HomeViewModel import com.approagency.drug.domain.usecase.SearchTestsUseCase
import com.approagency.drug.presentation.viewModel.LabViewModel import com.approagency.drug.presentation.viewModel.HomeViewModel
import com.approagency.drug.utils.Config import com.approagency.drug.presentation.viewModel.LabViewModel
import org.koin.android.ext.koin.androidContext import com.approagency.drug.utils.Config
import org.koin.core.module.dsl.viewModel import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module import org.koin.core.module.dsl.viewModel
import retrofit2.Retrofit import org.koin.dsl.module
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
val appModule= module {
val appModule= module {
single { LabDatabase.getInstance(androidContext()) }
single { LabDatabase.getInstance(androidContext()) }
single { get<LabDatabase>().testGroupDao() }
single { get<LabDatabase>().testItemDao() } single { get<LabDatabase>().testGroupDao() }
single { get<LabDatabase>().testItemDao() }
single {
Retrofit.Builder() single {
.baseUrl(Config.BASE_URL) Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create()) .baseUrl(Config.BASE_URL)
.build() .addConverterFactory(GsonConverterFactory.create())
} .build()
single<DrugApiService> { }
get<Retrofit>().create(DrugApiService::class.java) single<DrugApiService> {
} get<Retrofit>().create(DrugApiService::class.java)
}
//repo
single<DrugRepository> { //repo
DrugRepositoryImpl(get()) single<DrugRepository> {
} DrugRepositoryImpl(get())
}
//UseCase
single { //UseCase
GetDrugSearchUseCase(get()) single {
GetDrugSearchUseCase(get())
}
}
single{
GetDrugDetailUseCase(get()) single{
} GetDrugDetailUseCase(get())
}
single {
GetDarmanUseCase(get()) single {
} GetDarmanUseCase(get())
}
single {
GetTestGroupUseCase(get()) single {
} GetTestGroupUseCase(get())
}
single {
GetTestItemByGroupId(get()) single {
} GetTestItemByGroupId(get())
}
single { LabRepositoryImpl(get(), get()) }
//view model single { LabRepositoryImpl(get(), get()) }
viewModel {
HomeViewModel(get() , get() , get()) single { SearchTestsUseCase(get()) }
} //view model
viewModel {
viewModel { HomeViewModel(get() , get() , get())
LabViewModel(get() , get()) }
}
viewModel {
LabViewModel(get() , get() , get())
}
} }
@@ -1,9 +1,9 @@
package com.approagency.drug.domain.model package com.approagency.drug.domain.model
data class DrugSearchParams( data class DrugSearchParams(
val query: String? = null, val query: String? = null,
val perPage: Int = 20, val perPage: Int = 20,
val withRelations: Boolean = true, val withRelations: Boolean = true,
val drugGroup: Int? = null, val drugGroup: Int? = null,
val healGroup: Int? = null, val healGroup: Int? = null,
) )
@@ -1,11 +1,22 @@
package com.approagency.drug.domain.model package com.approagency.drug.domain.model
data class TestGroup( import com.approagency.drug.data.local.entities.TestGroupEntity
val id: Int,
val ename: String?, data class TestGroup(
val fname: String?, val id: Int,
val detail: String?, val ename: String?,
val isParent: Boolean val fname: String?,
) val detail: String?,
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,9 +1,20 @@
package com.approagency.drug.domain.model package com.approagency.drug.domain.model
data class TestItem( import com.approagency.drug.data.local.entities.TestItemEntity
val id: Int,
val groupId: Int, data class TestItem(
val title: String?, val id: Int,
val normalValue: String?, val groupId: Int,
val detail: String? val title: String?,
) val normalValue: 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
)
}
@@ -1,12 +1,12 @@
package com.approagency.drug.domain.repository package com.approagency.drug.domain.repository
import com.approagency.drug.data.dto.DarmanModel import com.approagency.drug.data.dto.DarmanModel
import com.approagency.drug.data.dto.DrugListResponse import com.approagency.drug.data.dto.DrugListResponse
import com.approagency.drug.data.dto.DrugModels import com.approagency.drug.data.dto.DrugModels
import com.approagency.drug.domain.model.DrugSearchParams import com.approagency.drug.domain.model.DrugSearchParams
interface DrugRepository { interface DrugRepository {
suspend fun searchDrug(params: DrugSearchParams): Result<DrugListResponse> suspend fun searchDrug(params: DrugSearchParams): Result<DrugListResponse>
suspend fun drugDetail(cod:Int): Result<DrugModels> suspend fun drugDetail(cod:Int): Result<DrugModels>
suspend fun getGorohDaroei(): Result<DarmanModel> suspend fun getGorohDaroei(): Result<DarmanModel>
} }
@@ -1,12 +1,12 @@
package com.approagency.drug.domain.usecase package com.approagency.drug.domain.usecase
import com.approagency.drug.data.dto.DarmanModel import com.approagency.drug.data.dto.DarmanModel
import com.approagency.drug.domain.repository.DrugRepository import com.approagency.drug.domain.repository.DrugRepository
class GetDarmanUseCase ( class GetDarmanUseCase (
private val repository: DrugRepository private val repository: DrugRepository
){ ){
suspend operator fun invoke (): Result<DarmanModel>{ suspend operator fun invoke (): Result<DarmanModel>{
return repository.getGorohDaroei() return repository.getGorohDaroei()
} }
} }
@@ -1,12 +1,12 @@
package com.approagency.drug.domain.usecase package com.approagency.drug.domain.usecase
import com.approagency.drug.data.dto.DrugModels import com.approagency.drug.data.dto.DrugModels
import com.approagency.drug.domain.repository.DrugRepository import com.approagency.drug.domain.repository.DrugRepository
class GetDrugDetailUseCase class GetDrugDetailUseCase
( (
private val repository: DrugRepository){ private val repository: DrugRepository){
suspend operator fun invoke(cod:Int): Result<DrugModels>{ suspend operator fun invoke(cod:Int): Result<DrugModels>{
return repository.drugDetail(cod) return repository.drugDetail(cod)
} }
} }
@@ -1,13 +1,13 @@
package com.approagency.drug.domain.usecase package com.approagency.drug.domain.usecase
import com.approagency.drug.data.dto.DrugListResponse import com.approagency.drug.data.dto.DrugListResponse
import com.approagency.drug.domain.model.DrugSearchParams import com.approagency.drug.domain.model.DrugSearchParams
import com.approagency.drug.domain.repository.DrugRepository import com.approagency.drug.domain.repository.DrugRepository
class GetDrugSearchUseCase ( class GetDrugSearchUseCase (
private val repository: DrugRepository private val repository: DrugRepository
){ ){
suspend operator fun invoke(params: DrugSearchParams): Result<DrugListResponse> { suspend operator fun invoke(params: DrugSearchParams): Result<DrugListResponse> {
return repository.searchDrug(params) return repository.searchDrug(params)
} }
} }
@@ -1,24 +1,24 @@
package com.approagency.drug.domain.usecase package com.approagency.drug.domain.usecase
import com.approagency.drug.data.repository.LabRepositoryImpl import com.approagency.drug.data.repository.LabRepositoryImpl
import com.approagency.drug.domain.model.TestGroup import com.approagency.drug.domain.model.TestGroup
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
class GetTestGroupUseCase ( class GetTestGroupUseCase (
private val repository: LabRepositoryImpl private val repository: LabRepositoryImpl
) { ) {
operator fun invoke(): Flow<List<TestGroup>> { operator fun invoke(): Flow<List<TestGroup>> {
return repository.getAllGroups().map { entities -> return repository.getAllGroups().map { entities ->
entities.map { entity -> entities.map { entity ->
TestGroup( TestGroup(
id = entity.id, id = entity.id,
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
) )
} }
} }
} }
} }
@@ -1,26 +1,26 @@
package com.approagency.drug.domain.usecase package com.approagency.drug.domain.usecase
import com.approagency.drug.data.local.entities.TestItemEntity import com.approagency.drug.data.local.entities.TestItemEntity
import com.approagency.drug.data.repository.LabRepositoryImpl import com.approagency.drug.data.repository.LabRepositoryImpl
import com.approagency.drug.domain.model.TestItem import com.approagency.drug.domain.model.TestItem
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
class GetTestItemByGroupId ( class GetTestItemByGroupId (
private val repository: LabRepositoryImpl private val repository: LabRepositoryImpl
) { ) {
operator fun invoke(id:Int): Flow<List<TestItem>> { operator fun invoke(id:Int): Flow<List<TestItem>> {
return repository.getItemsByGroupId(groupId = id).map { return repository.getItemsByGroupId(groupId = id).map {
entities -> entities.map { entities -> entities.map {
entity -> entity ->
TestItem( TestItem(
id = entity.id, id = entity.id,
groupId = entity.groupId, groupId = entity.groupId,
title = entity.title, title = entity.title,
normalValue = entity.normalValue, normalValue = entity.normalValue,
detail = entity.detail, detail = entity.detail,
) )
} }
} }
} }
} }
@@ -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() }
)
}
}
}
@@ -1,24 +1,24 @@
package com.approagency.drug.navigation package com.approagency.drug.navigation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import com.approagency.drug.presentation.screens.HomeScreen import com.approagency.drug.presentation.screens.HomeScreen
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import com.approagency.drug.presentation.screens.RootScreen import com.approagency.drug.presentation.screens.RootScreen
@Composable @Composable
fun AppNavGraph(navController: NavHostController, modifier: Modifier = Modifier) { fun AppNavGraph(navController: NavHostController, modifier: Modifier = Modifier) {
NavHost( NavHost(
navController = navController , navController = navController ,
startDestination = NavRoutes.Home.route startDestination = NavRoutes.Home.route
){ ){
composable(NavRoutes.Home.route) { composable(NavRoutes.Home.route) {
RootScreen( RootScreen(
navHostController = navController , modifier = Modifier navHostController = navController , modifier = Modifier
) )
} }
} }
} }
@@ -1,9 +1,9 @@
package com.approagency.drug.navigation package com.approagency.drug.navigation
abstract class NavRoutes (val route:String) { abstract class NavRoutes (val route:String) {
object Home: NavRoutes("home") object Home: NavRoutes("home")
object Detail: NavRoutes("detail/{cod}"){ object Detail: NavRoutes("detail/{cod}"){
fun createRoute(cod: Int) = "detail/$cod" fun createRoute(cod: Int) = "detail/$cod"
} }
object Lab: NavRoutes("lab") object Lab: NavRoutes("lab")
} }
@@ -1,39 +1,39 @@
package com.approagency.drug.presentation.common package com.approagency.drug.presentation.common
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.vada.caller.ui.theme.dime import com.vada.caller.ui.theme.dime
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun CustomModalBottomSheet( fun CustomModalBottomSheet(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
sheetState: SheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true), sheetState: SheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
scope: CoroutineScope = rememberCoroutineScope(), scope: CoroutineScope = rememberCoroutineScope(),
onDismiss: (() -> Unit)? = null, onDismiss: (() -> Unit)? = null,
content: @Composable () -> Unit content: @Composable () -> Unit
) { ) {
ModalBottomSheet( ModalBottomSheet(
onDismissRequest = { onDismissRequest = {
onDismiss?.invoke() onDismiss?.invoke()
}, },
sheetState = sheetState, sheetState = sheetState,
modifier = modifier, modifier = modifier,
shape = RoundedCornerShape(topStart = MaterialTheme.dime.lg, topEnd = MaterialTheme.dime.lg) shape = RoundedCornerShape(topStart = MaterialTheme.dime.lg, topEnd = MaterialTheme.dime.lg)
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
) { ) {
content() content()
} }
} }
} }
@@ -1,26 +1,26 @@
package com.approagency.drug.presentation.common package com.approagency.drug.presentation.common
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@Composable @Composable
fun CustomBox( fun CustomBox(
modifier: Modifier? = Modifier, modifier: Modifier? = Modifier,
child: @Composable ()-> Unit child: @Composable ()-> Unit
) { ) {
if (modifier != null) { if (modifier != null) {
Box( Box(
modifier = modifier.clip(shape = MaterialTheme.shapes.large).background( modifier = modifier.clip(shape = MaterialTheme.shapes.large).background(
color = MaterialTheme.colorScheme.surfaceContainer color = MaterialTheme.colorScheme.surfaceContainer
) )
) { ) {
child() child()
} }
} }
} }
@@ -1,40 +1,40 @@
package com.approagency.drug.presentation.common package com.approagency.drug.presentation.common
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import com.vada.caller.ui.theme.dime import com.vada.caller.ui.theme.dime
@Composable @Composable
fun CustomModalDialog( fun CustomModalDialog(
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
content: @Composable ColumnScope.() -> Unit content: @Composable ColumnScope.() -> Unit
) { ) {
Dialog( Dialog(
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
){ ){
Surface( Surface(
modifier = modifier modifier = modifier
.wrapContentHeight(), .wrapContentHeight(),
shape = MaterialTheme.shapes.medium, shape = MaterialTheme.shapes.medium,
color = MaterialTheme.colorScheme.surface, color = MaterialTheme.colorScheme.surface,
tonalElevation = MaterialTheme.dime.sm tonalElevation = MaterialTheme.dime.sm
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.padding(MaterialTheme.dime.lg), .padding(MaterialTheme.dime.lg),
verticalArrangement = Arrangement.spacedBy(MaterialTheme.dime.lg), verticalArrangement = Arrangement.spacedBy(MaterialTheme.dime.lg),
content = content content = content
) )
} }
} }
} }
@@ -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
)
}
}
}
}
}
}
@@ -1,21 +1,21 @@
package com.approagency.drug.presentation.common package com.approagency.drug.presentation.common
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
object SnackbarManager { object SnackbarManager {
private var snackbarHostState: SnackbarHostState? = null private var snackbarHostState: SnackbarHostState? = null
private var coroutineScope: CoroutineScope? = null private var coroutineScope: CoroutineScope? = null
fun init(scope: CoroutineScope, hostState: SnackbarHostState) { fun init(scope: CoroutineScope, hostState: SnackbarHostState) {
coroutineScope = scope coroutineScope = scope
snackbarHostState = hostState snackbarHostState = hostState
} }
fun showMessage(message: String) { fun showMessage(message: String) {
coroutineScope?.launch { coroutineScope?.launch {
snackbarHostState?.showSnackbar(message) snackbarHostState?.showSnackbar(message)
} }
} }
} }
@@ -1,7 +1,7 @@
package com.vada.caller.presentation.common package com.vada.caller.presentation.common
sealed interface UiState<out T> { sealed interface UiState<out T> {
object Loading : UiState<Nothing> object Loading : UiState<Nothing>
data class Success<T>(val data: T) : UiState<T> data class Success<T>(val data: T) : UiState<T>
data class Error(val message: String) : UiState<Nothing> data class Error(val message: String) : UiState<Nothing>
} }
@@ -1,25 +1,25 @@
package com.approagency.drug.presentation.common package com.approagency.drug.presentation.common
import android.content.res.Resources.Theme import android.content.res.Resources.Theme
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.vada.caller.ui.theme.dime import com.vada.caller.ui.theme.dime
@Composable @Composable
fun Loading( fun Loading(
color: Color = MaterialTheme.colorScheme.onPrimary, color: Color = MaterialTheme.colorScheme.onPrimary,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
CircularProgressIndicator( CircularProgressIndicator(
color = color, color = color,
modifier = modifier, modifier = modifier,
strokeWidth = 2.dp strokeWidth = 2.dp
) )
} }
@@ -1,39 +1,39 @@
package com.approagency.drug.presentation.common package com.approagency.drug.presentation.common
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
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.unit.dp import androidx.compose.ui.unit.dp
@Composable @Composable
fun PrimaryButton( fun PrimaryButton(
text: String, text: String,
isLoading: Boolean?, isLoading: Boolean?,
onClick: () -> Unit, onClick: () -> Unit,
height: Int? = null height: Int? = null
) { ) {
Button( Button(
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary, containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary contentColor = MaterialTheme.colorScheme.onPrimary
), ),
shape = MaterialTheme.shapes.medium, shape = MaterialTheme.shapes.medium,
modifier = Modifier.fillMaxWidth().height(height?.dp ?: 50.dp), modifier = Modifier.fillMaxWidth().height(height?.dp ?: 50.dp),
onClick = { onClick = {
onClick() onClick()
}, },
) { ) {
if (isLoading == true) { if (isLoading == true) {
Text("... در حال جستجو" , textAlign = TextAlign.Right) Text("... در حال جستجو" , textAlign = TextAlign.Right)
} else { } else {
Text(text) Text(text)
} }
} }
} }
@@ -1,156 +1,131 @@
package com.approagency.drug.presentation.components package com.approagency.drug.presentation.components
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.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth 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.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.twotone.KeyboardArrowLeft import androidx.compose.material.icons.automirrored.twotone.KeyboardArrowLeft
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Icon 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.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.AbsoluteAlignment 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.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.approagency.drug.data.dto.DarmanList import com.approagency.drug.data.dto.DarmanList
import com.approagency.drug.data.dto.DarmanModel import com.approagency.drug.data.dto.DarmanModel
import com.approagency.drug.data.dto.DrugDetail import com.approagency.drug.data.dto.DrugDetail
import com.approagency.drug.data.dto.DrugListResponse import com.approagency.drug.data.dto.DrugListResponse
import com.approagency.drug.presentation.common.CustomBox import com.approagency.drug.presentation.common.CustomBox
import com.approagency.drug.presentation.common.PrimaryButton import com.approagency.drug.presentation.common.PrimaryButton
import com.vada.caller.ui.theme.dime import com.vada.caller.ui.theme.dime
import kotlin.collections.emptyList import kotlin.collections.emptyList
@Composable @Composable
fun DarmanContent( fun DarmanContent(
darmanData: Result<DarmanModel?>?, darmanData: Result<DarmanModel?>?,
onDarmanClick: (Int) -> Unit, onDarmanClick: (Int) -> Unit,
onRetryClick:()-> Unit, onRetryClick:()-> Unit,
){ ){
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( onClick = {
horizontalArrangement = Arrangement.Center, onRetryClick()
modifier = Modifier }
.fillMaxWidth() )
.padding(all = MaterialTheme.dime.sm), } else {
) { LazyColumn {
Text( items(drugs.count() , itemContent ={
text = "مشکل در دریافت اطلاعات", i -> DarmanItem(
fontWeight = FontWeight.W500 darman = darmanData?.getOrNull()!!.data[i] ,
) onClick = { onDarmanClick(drugs[i].cod!!) }
Spacer(modifier = Modifier.width(MaterialTheme.dime.xs)) )
Icon( } )
imageVector = Icons.Default.Search, }
contentDescription = "Search"
) }
}
PrimaryButton(
text = "تلاش دوباره", }
height = 45,
isLoading = false, @Composable
onClick = { fun DarmanItem(
onRetryClick() darman: DarmanList, // Adjust this based on your actual data class
} onClick: (Int?) -> Unit
) ) {
} CustomBox(
} modifier = Modifier
) .padding(bottom = MaterialTheme.dime.sm)
}else { .clickable(
LazyColumn( indication = null,
interactionSource = remember { MutableInteractionSource() }
){ ) {
items(drugs.count() , itemContent ={ onClick(darman.cod)
i -> DarmanItem( },
darman = darmanData?.getOrNull()!!.data[i] , child = {
onClick = { onDarmanClick(drugs[i].cod!!) } Row(
) modifier = Modifier.padding(all = MaterialTheme.dime.lg),
} ) ) {
} Column (
verticalArrangement = Arrangement.Center,
} horizontalAlignment = Alignment.Start
) {
Column(
} modifier = Modifier.fillMaxWidth(),
horizontalAlignment = AbsoluteAlignment.Right
@Composable // horizontalArrangement = Arrangement.SpaceBetween
fun DarmanItem( ) {
darman: DarmanList, // Adjust this based on your actual data class Text(darman.nam_fa.toString() , style = MaterialTheme.typography.labelLarge , textAlign = TextAlign.Right )
onClick: (Int?) -> Unit Text(darman.nam_en.toString() , style = MaterialTheme.typography.bodySmall)
) { }
CustomBox( Row (
modifier = Modifier modifier = Modifier.fillMaxWidth(),
.padding(bottom = MaterialTheme.dime.sm) horizontalArrangement = Arrangement.Center
.clickable( ) {
indication = null, TextButton(
interactionSource = remember { MutableInteractionSource() } onClick = {
) { onClick(darman.cod)
onClick(darman.cod) }
}, ) {
child = { Row(
Row( horizontalArrangement = Arrangement.Center
modifier = Modifier.padding(all = MaterialTheme.dime.lg), ) {
) { Icon(
Column ( imageVector = Icons.AutoMirrored.TwoTone.KeyboardArrowLeft
verticalArrangement = Arrangement.Center, , contentDescription = "see more"
horizontalAlignment = Alignment.Start )
) { Text("مشاهده لیست")
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 ( // Button(
modifier = Modifier.fillMaxWidth(), // onClick = onClick,
horizontalArrangement = Arrangement.Center // modifier = Modifier
) { // .fillMaxWidth()
TextButton( // .padding(vertical = 4.dp)
onClick = { // ) {
onClick(darman.cod) // drug.nam_fa?.let { Text(text = it) } // Adjust based on your drug model
} // }
) {
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
// }
} }
@@ -1,152 +1,152 @@
package com.approagency.drug.presentation.components package com.approagency.drug.presentation.components
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.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.twotone.KeyboardArrowLeft import androidx.compose.material.icons.automirrored.twotone.KeyboardArrowLeft
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Icon 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.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
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.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import com.approagency.drug.data.dto.DrugDetail import com.approagency.drug.data.dto.DrugDetail
import com.approagency.drug.data.dto.DrugListResponse import com.approagency.drug.data.dto.DrugListResponse
import com.approagency.drug.presentation.common.CustomBox import com.approagency.drug.presentation.common.CustomBox
import com.vada.caller.ui.theme.dime import com.vada.caller.ui.theme.dime
@Composable @Composable
fun DrugListContent( fun DrugListContent(
drugsData: Result<DrugListResponse?>?, drugsData: Result<DrugListResponse?>?,
onDrugClick: (Int) -> Unit onDrugClick: (Int) -> Unit
) { ) {
val drugs = drugsData?.getOrNull()?.data ?: emptyList() val drugs = drugsData?.getOrNull()?.data ?: emptyList()
if (drugs.isEmpty()) { if (drugs.isEmpty()) {
CustomBox( CustomBox(
child = { child = {
Row( Row(
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(all = MaterialTheme.dime.sm), .padding(all = MaterialTheme.dime.sm),
) { ) {
Text( Text(
text = "نتیجه‌ای یافت نشد", text = "نتیجه‌ای یافت نشد",
fontWeight = FontWeight.W500 fontWeight = FontWeight.W500
) )
Spacer(modifier = Modifier.width(MaterialTheme.dime.xs)) Spacer(modifier = Modifier.width(MaterialTheme.dime.xs))
Icon( Icon(
imageVector = Icons.Default.Search, imageVector = Icons.Default.Search,
contentDescription = "Search" contentDescription = "Search"
) )
} }
} }
) )
} else { } else {
LazyColumn( LazyColumn(
){ ){
items(drugs.count() , itemContent ={ items(drugs.count() , itemContent ={
i -> DrugItem( i -> DrugItem(
drug = drugs[i], drug = drugs[i],
onClick = { onDrugClick(drugs[i].cod!!) } onClick = { onDrugClick(drugs[i].cod!!) }
) )
} ) } )
} }
} }
} }
@Composable @Composable
fun DrugItem( fun DrugItem(
drug: DrugDetail, // Adjust this based on your actual data class drug: DrugDetail, // Adjust this based on your actual data class
onClick: (Int?) -> Unit onClick: (Int?) -> Unit
) { ) {
CustomBox( CustomBox(
modifier = Modifier modifier = Modifier
.padding(bottom = MaterialTheme.dime.sm) .padding(bottom = MaterialTheme.dime.sm)
.fillMaxWidth() .fillMaxWidth()
.clickable( .clickable(
indication = null, indication = null,
interactionSource = remember { MutableInteractionSource() } interactionSource = remember { MutableInteractionSource() }
) { ) {
onClick(drug.cod) onClick(drug.cod)
}, },
child = { child = {
Row( Row(
modifier = Modifier.padding(all = MaterialTheme.dime.lg), modifier = Modifier.padding(all = MaterialTheme.dime.lg),
) { ) {
Column ( Column (
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.Start horizontalAlignment = Alignment.Start
) { ) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
) { ) {
Text(drug.nam_en.toString() , style = MaterialTheme.typography.bodySmall) Text(drug.nam_en.toString() , style = MaterialTheme.typography.bodySmall)
Text(drug.nam_fa.toString() , style = MaterialTheme.typography.labelLarge,textAlign = TextAlign.Right) Text(drug.nam_fa.toString() , style = MaterialTheme.typography.labelLarge,textAlign = TextAlign.Right)
} }
if (drug.goroh_darmani != null ) if (drug.goroh_darmani != null )
Row( Row(
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
, horizontalArrangement = Arrangement.End , horizontalArrangement = Arrangement.End
) {Text( ) {Text(
"گروه دارویی: ${drug.goroh_darmani.nam_fa.toString()}", "گروه دارویی: ${drug.goroh_darmani.nam_fa.toString()}",
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Right textAlign = TextAlign.Right
) } ) }
Row ( Row (
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center horizontalArrangement = Arrangement.Center
) { ) {
TextButton( TextButton(
onClick = { onClick = {
onClick(drug.cod) onClick(drug.cod)
} }
) { ) {
Row( Row(
horizontalArrangement = Arrangement.Center horizontalArrangement = Arrangement.Center
) { ) {
Icon( Icon(
imageVector = Icons.AutoMirrored.TwoTone.KeyboardArrowLeft imageVector = Icons.AutoMirrored.TwoTone.KeyboardArrowLeft
, contentDescription = "see more" , contentDescription = "see more"
) )
Text("مشاهده جزییات") Text("مشاهده جزییات")
} }
} }
} }
} }
} }
} }
) )
// Button( // Button(
// onClick = onClick, // onClick = onClick,
// modifier = Modifier // modifier = Modifier
// .fillMaxWidth() // .fillMaxWidth()
// .padding(vertical = 4.dp) // .padding(vertical = 4.dp)
// ) { // ) {
// drug.nam_fa?.let { Text(text = it) } // Adjust based on your drug model // drug.nam_fa?.let { Text(text = it) } // Adjust based on your drug model
// } // }
} }
@@ -1,40 +1,40 @@
package com.approagency.drug.presentation.components package com.approagency.drug.presentation.components
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Icon 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.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.approagency.drug.presentation.common.CustomBox import com.approagency.drug.presentation.common.CustomBox
import com.vada.caller.ui.theme.dime import com.vada.caller.ui.theme.dime
@Composable @Composable
fun EmptyStateContent() { fun EmptyStateContent() {
Column( Column(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
, verticalArrangement = Arrangement.Center , verticalArrangement = Arrangement.Center
) { ) {
Icon( Icon(
imageVector = Icons.Default.Search, imageVector = Icons.Default.Search,
contentDescription = "Search", contentDescription = "Search",
modifier = Modifier.size(MaterialTheme.dime.iconLg) modifier = Modifier.size(MaterialTheme.dime.iconLg)
) )
CustomBox( CustomBox(
child = { child = {
Text( Text(
text = "برای جستجو، نام دارو را وارد کنید", text = "برای جستجو، نام دارو را وارد کنید",
modifier = Modifier.padding(MaterialTheme.dime.sm) modifier = Modifier.padding(MaterialTheme.dime.sm)
) )
} }
) )
} }
} }
@@ -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() }
)
}
}
)
}
@@ -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)
)
}
}
}
}
@@ -1,225 +1,225 @@
package com.approagency.drug.presentation.components package com.approagency.drug.presentation.components
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.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
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 items if you were to use it for lists within 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.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import com.approagency.drug.data.dto.DrugDetail // Import DrugDetail directly import com.approagency.drug.data.dto.DrugDetail // Import DrugDetail directly
import com.approagency.drug.data.dto.DrugModels import com.approagency.drug.data.dto.DrugModels
import com.approagency.drug.presentation.common.CustomModalBottomSheet import com.approagency.drug.presentation.common.CustomModalBottomSheet
import com.approagency.drug.presentation.viewModel.DrugDetailState import com.approagency.drug.presentation.viewModel.DrugDetailState
import com.approagency.drug.presentation.viewModel.HomeViewModel import com.approagency.drug.presentation.viewModel.HomeViewModel
import com.vada.caller.ui.theme.dime // Assuming this is your custom spacing/dimension object import com.vada.caller.ui.theme.dime // Assuming this is your custom spacing/dimension object
@Composable @Composable
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
fun DrugDetailSheet( fun DrugDetailSheet(
viewModel: HomeViewModel, viewModel: HomeViewModel,
state: DrugDetailState // Assuming you pass the state containing the detail state: DrugDetailState // Assuming you pass the state containing the detail
) { ) {
val detail: DrugDetail? = state.drugDetail?.getOrNull()?.data val detail: DrugDetail? = state.drugDetail?.getOrNull()?.data
// Only show the sheet if detail is not null // Only show the sheet if detail is not null
if (detail != null) { if (detail != null) {
CustomModalBottomSheet( CustomModalBottomSheet(
onDismiss = { onDismiss = {
viewModel.closeDetail() viewModel.closeDetail()
}, },
content = { content = {
// Use a LazyColumn for scrollable content inside the sheet // Use a LazyColumn for scrollable content inside the sheet
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = MaterialTheme.dime.lg), .padding(horizontal = MaterialTheme.dime.lg),
contentPadding = PaddingValues(bottom = MaterialTheme.dime.lg) // Add padding to the bottom of the content for better scrolling contentPadding = PaddingValues(bottom = MaterialTheme.dime.lg) // Add padding to the bottom of the content for better scrolling
) { ) {
// --- 1. Main Title --- // --- 1. Main Title ---
item { // Use item {} for single composables within LazyColumn item { // Use item {} for single composables within LazyColumn
Text( Text(
text = detail.nam_fa ?: "جزئیات دارو", text = detail.nam_fa ?: "جزئیات دارو",
style = MaterialTheme.typography.headlineMedium, style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
modifier = Modifier.padding(bottom = MaterialTheme.dime.md) modifier = Modifier.padding(bottom = MaterialTheme.dime.md)
) )
Text( Text(
text = detail.nam_en ?: "N/A (English Name)", text = detail.nam_en ?: "N/A (English Name)",
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(bottom = MaterialTheme.dime.lg) modifier = Modifier.padding(bottom = MaterialTheme.dime.lg)
) )
// Divider for visual separation // Divider for visual separation
HorizontalDivider(Modifier.padding(bottom = MaterialTheme.dime.md)) HorizontalDivider(Modifier.padding(bottom = MaterialTheme.dime.md))
} }
// --- 2. Key Groupings --- // --- 2. Key Groupings ---
// Goroh Darmani (Pharmacological Group) // Goroh Darmani (Pharmacological Group)
detail.goroh_darmani?.let { group -> detail.goroh_darmani?.let { group ->
item { item {
DetailRow( DetailRow(
title = "گروه درمانی", title = "گروه درمانی",
value = group.nam_fa ?: "نامشخص" value = group.nam_fa ?: "نامشخص"
) )
} }
group.nam_en?.let { enName -> group.nam_en?.let { enName ->
item { item {
DetailRow( DetailRow(
title = "Group (EN)", title = "Group (EN)",
value = enName value = enName
) )
} }
} }
} }
// Goroh Darmanei (Therapeutic Group) // Goroh Darmanei (Therapeutic Group)
detail.goroh_daroei?.let { group -> detail.goroh_daroei?.let { group ->
item { item {
DetailRow( DetailRow(
title = "گروه دارویی", title = "گروه دارویی",
value = group.nam ?: "نامشخص" value = group.nam ?: "نامشخص"
) )
} }
} }
// Goroh Farmakologic (Pharmacological Class) // Goroh Farmakologic (Pharmacological Class)
detail.goroh_farmakologic_cod?.let { cod -> detail.goroh_farmakologic_cod?.let { cod ->
// NOTE: You might need a separate API call or mapping here // 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. // if cod needs to be translated to a name. For now, we show the code.
item { item {
DetailRow( DetailRow(
title = "کلاس فارماکولوژیک", title = "کلاس فارماکولوژیک",
value = cod.toString() value = cod.toString()
) )
} }
} }
item { // Add a divider as a separate item item { // Add a divider as a separate item
HorizontalDivider(Modifier.padding(vertical = MaterialTheme.dime.md)) HorizontalDivider(Modifier.padding(vertical = MaterialTheme.dime.md))
} }
// --- 3. Usage & Mechanism --- // --- 3. Usage & Mechanism ---
detail.mavaredmasraf?.let { usage -> detail.mavaredmasraf?.let { usage ->
item { item {
DetailSection(title = "موارد مصرف", content = usage) DetailSection(title = "موارد مصرف", content = usage)
} }
} }
detail.mekanismtasir?.let { mechanism -> detail.mekanismtasir?.let { mechanism ->
item { item {
DetailSection(title = "مکانیسم اثر", content = mechanism) DetailSection(title = "مکانیسم اثر", content = mechanism)
} }
} }
// --- 4. Warnings & Storage --- // --- 4. Warnings & Storage ---
detail.hoshdar?.let { warning -> detail.hoshdar?.let { warning ->
item { item {
DetailSection( DetailSection(
title = "هشدارها", title = "هشدارها",
content = warning, content = warning,
isWarning = true isWarning = true
) )
} }
} }
detail.sharayetnegahdari?.let { storage -> detail.sharayetnegahdari?.let { storage ->
item { item {
DetailSection( DetailSection(
title = "شرایط نگهداری", title = "شرایط نگهداری",
content = storage content = storage
) )
} }
} }
} }
} }
} }
) )
} }
} }
// --- Helper Composables for Clean Code --- // --- Helper Composables for Clean Code ---
@Composable @Composable
fun DetailRow( fun DetailRow(
title: String, title: String,
value: String value: String
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = MaterialTheme.dime.sm), .padding(vertical = MaterialTheme.dime.sm),
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
) { ) {
Text( Text(
text = title, text = title,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Right textAlign = TextAlign.Right
) )
Text( Text(
text = value, text = value,
style = MaterialTheme.typography.bodyLarge, style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
textAlign = TextAlign.End, textAlign = TextAlign.End,
// Ensure text takes up available space and doesn't push other elements. // 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. // Using weight(1f, fill = false) is good for aligning to the end if there's space.
modifier = Modifier.weight(1f, fill = false) modifier = Modifier.weight(1f, fill = false)
) )
} }
} }
@Composable @Composable
fun DetailSection( fun DetailSection(
title: String, title: String,
content: String, content: String,
isWarning: Boolean = false isWarning: Boolean = false
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = MaterialTheme.dime.md) .padding(vertical = MaterialTheme.dime.md)
) { ) {
Text( Text(
text = title, text = title,
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
color = if (isWarning) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary, color = if (isWarning) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
modifier = Modifier.padding(bottom = MaterialTheme.dime.sm) modifier = Modifier.padding(bottom = MaterialTheme.dime.sm)
) )
// Use a text block that respects line breaks (usually done with \n) // Use a text block that respects line breaks (usually done with \n)
Text( Text(
text = content, text = content,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface, color = MaterialTheme.colorScheme.onSurface,
// Consider adding softWrap = true if content might overflow // Consider adding softWrap = true if content might overflow
// softWrap = true // softWrap = true
) )
} }
// Add divider after each section for better visual separation // Add divider after each section for better visual separation
HorizontalDivider(Modifier.padding(top = MaterialTheme.dime.sm)) HorizontalDivider(Modifier.padding(top = MaterialTheme.dime.sm))
} }
// Make sure your DrugDetailState in ViewModel looks something like this: // Make sure your DrugDetailState in ViewModel looks something like this:
// data class DrugDetailState( // data class DrugDetailState(
// val isLoading: Boolean = false, // val isLoading: Boolean = false,
// val drugDetail: DrugModels? = null, // Changed to DrugModels? to match data access // val drugDetail: DrugModels? = null, // Changed to DrugModels? to match data access
// val isDetailVisible: Boolean = false, // val isDetailVisible: Boolean = false,
// val error: String? = null // val error: String? = null
// ) // )
@@ -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
)
}
}
@@ -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("مشاهده جزییات")
}
}
}
}
}
}
}
@@ -1,292 +1,251 @@
package com.approagency.drug.presentation.screens package com.approagency.drug.presentation.screens
import android.R import android.R
import android.R.attr.fontWeight import android.R.attr.fontWeight
import android.content.res.Resources import android.content.res.Resources
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.snapping.SnapPosition import androidx.compose.foundation.gestures.snapping.SnapPosition
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.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.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.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.twotone.KeyboardArrowLeft import androidx.compose.material.icons.automirrored.twotone.KeyboardArrowLeft
import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.KeyboardArrowLeft import androidx.compose.material.icons.filled.KeyboardArrowLeft
import androidx.compose.material.icons.filled.KeyboardArrowRight import androidx.compose.material.icons.filled.KeyboardArrowRight
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.twotone.Info import androidx.compose.material.icons.twotone.Info
import androidx.compose.material.icons.twotone.KeyboardArrowLeft import androidx.compose.material.icons.twotone.KeyboardArrowLeft
import androidx.compose.material.icons.twotone.KeyboardArrowRight import androidx.compose.material.icons.twotone.KeyboardArrowRight
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
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.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.approagency.drug.data.dto.DrugDetail import com.approagency.drug.data.dto.DrugDetail
import com.approagency.drug.data.dto.DrugListResponse import com.approagency.drug.data.dto.DrugListResponse
import com.approagency.drug.domain.model.DrugSearchParams 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.Loading import com.approagency.drug.presentation.common.CustomTextFilled
import com.approagency.drug.presentation.common.PrimaryButton import com.approagency.drug.presentation.common.Loading
import com.approagency.drug.presentation.components.DarmanContent import com.approagency.drug.presentation.common.PrimaryButton
import com.approagency.drug.presentation.components.DrugDetailSheet import com.approagency.drug.presentation.components.DarmanContent
import com.approagency.drug.presentation.components.DrugListContent import com.approagency.drug.presentation.components.DrugDetailSheet
import com.approagency.drug.presentation.components.EmptyStateContent import com.approagency.drug.presentation.components.DrugListContent
import com.approagency.drug.presentation.viewModel.CombineState import com.approagency.drug.presentation.components.EmptyStateContent
import com.approagency.drug.presentation.viewModel.HomeEvent import com.approagency.drug.presentation.viewModel.CombineState
import com.approagency.drug.presentation.viewModel.HomeViewModel import com.approagency.drug.presentation.viewModel.HomeEvent
import com.vada.caller.ui.theme.Dime 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 com.vada.caller.ui.theme.dime
import org.koin.androidx.compose.koinViewModel import kotlinx.coroutines.flow.SharedFlow
import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable @OptIn(ExperimentalMaterial3Api::class)
fun HomeScreen( @Composable
navController : NavController, fun HomeScreen(
modifier: Modifier, navController : NavController,
viewModel: HomeViewModel = koinViewModel() modifier: Modifier,
) { viewModel: HomeViewModel = koinViewModel()
val state by viewModel.uiState.collectAsState() ) {
val focusManager = LocalFocusManager.current val state by viewModel.uiState.collectAsState()
val keyboardController = LocalSoftwareKeyboardController.current val focusManager = LocalFocusManager.current
if (state.drugDetailState.isDetailVisible) { val keyboardController = LocalSoftwareKeyboardController.current
DrugDetailSheet( if (state.drugDetailState.isDetailVisible) {
viewModel = viewModel, DrugDetailSheet(
state = state.drugDetailState viewModel = viewModel,
) state = state.drugDetailState
} )
HomeContent( }
modifier = modifier, HomeContent(
state = state, modifier = modifier,
viewModelEvent = viewModel.event, state = state,
onSearch = { value -> viewModelEvent = viewModel.event,
if (value is Int) { onSearch = { value ->
viewModel.searchDrugs(DrugSearchParams(healGroup = value)) if (value is Int) {
} else if (value is String) { viewModel.searchDrugs(DrugSearchParams(healGroup = value))
viewModel.searchDrugs(DrugSearchParams(query = value)) } else if (value is String) {
} viewModel.searchDrugs(DrugSearchParams(query = value))
keyboardController?.hide() }
focusManager.clearFocus()}, keyboardController?.hide()
onRetry = { focusManager.clearFocus()},
viewModel.getDarmani() onRetry = {
}, viewModel.getDarmani()
onDrugClick = { drugId -> viewModel.drugDetail(drugId , allDrugs = state.drugSearchState.drugsData) } },
) onDrugClick = { drugId -> viewModel.drugDetail(drugId , allDrugs = state.drugSearchState.drugsData) }
} )
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable @OptIn(ExperimentalMaterial3Api::class)
fun HomeContent( @Composable
state: CombineState, fun HomeContent(
viewModelEvent: SharedFlow<HomeEvent>, state: CombineState,
onSearch: (Any) -> Unit, viewModelEvent: SharedFlow<HomeEvent>,
onRetry: () -> Unit, onSearch: (Any) -> Unit,
onDrugClick: (Int) -> Unit, onRetry: () -> Unit,
modifier: Modifier = Modifier onDrugClick: (Int) -> Unit,
) { modifier: Modifier = Modifier
var search by remember { mutableStateOf("") } ) {
Column( var searchText by remember { mutableStateOf("") }
modifier = modifier Column(
.fillMaxSize().padding(horizontal = MaterialTheme.dime.md) modifier = modifier
.padding(top = MaterialTheme.dime.md), .fillMaxSize().padding(horizontal = MaterialTheme.dime.md)
horizontalAlignment = Alignment.Start, .padding(top = MaterialTheme.dime.md),
) { horizontalAlignment = Alignment.Start,
Column ( ) {
verticalArrangement = Arrangement.Center Column (
){ verticalArrangement = Arrangement.Center
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { ){
Box( CustomTextFilled(
modifier = Modifier value = searchText,
.fillMaxWidth() onValueChange = { searchText = it },
.height(45.dp) // Your desired height onSearch = { query ->
.background( if (query.isNotBlank()) {
color = MaterialTheme.colorScheme.surfaceVariant, onSearch(query)
shape =MaterialTheme.shapes.medium }
) },
.border( placeholder = "جستجوی دارو",
width = 1.dp, showClearButton = true,
color = MaterialTheme.colorScheme.primaryContainer, showSearchButton = true,
shape =MaterialTheme.shapes.medium autoSearch = false, // Set to true if you want search while typing
) height = 45
.padding(horizontal = MaterialTheme.dime.md, vertical = MaterialTheme.dime.sm) )
) { Spacer(modifier = Modifier.height(MaterialTheme.dime.xs))
Row( PrimaryButton(
verticalAlignment = Alignment.CenterVertically, text = "جستجو",
modifier = Modifier.fillMaxSize() height = 40,
) { isLoading = state.drugSearchState.isLoading,
BasicTextField( onClick = {
value = search, if (searchText.isNotBlank()) {
onValueChange = { search = it }, onSearch(searchText)
modifier = Modifier.weight(1f), }
singleLine = true, }
textStyle = MaterialTheme.typography.bodyLarge, )
decorationBox = { innerTextField -> Spacer(modifier = Modifier.height(MaterialTheme.dime.xs))
Box { Box(modifier = Modifier.fillMaxSize()) {
if (search.isEmpty()) {
Text( // Always draw the data if available
"جستجوی دارو", if (state.drugSearchState.drugsData != null && !state.showDarmanList) {
style = MaterialTheme.typography.bodyMedium, Column {
color = MaterialTheme.colorScheme.onSurfaceVariant Row(
) horizontalArrangement = Arrangement.End,
} modifier = Modifier.fillMaxWidth()
innerTextField() ) {
} TextButton(
} onClick = {
) onRetry()
IconButton( }
onClick = { ) {
if (search.isNotBlank()) { Text("مشاهده تمامی گروه های دارویی")
onSearch(search) Icon(
} imageVector = Icons.Default.KeyboardArrowRight,
}, contentDescription = "back"
modifier = Modifier.size(32.dp) )
) { Spacer(modifier = Modifier.width(MaterialTheme.dime.sm))
Icon( }
imageVector = Icons.Default.Search, }
contentDescription = "Search", DrugListContent(
modifier = Modifier.size(18.dp) drugsData = state.drugSearchState.drugsData,
) onDrugClick = onDrugClick
} )
} }
} }
} else if (state.darmanState.getDarmani != null ){
Spacer(modifier = Modifier.height(MaterialTheme.dime.xs)) Column {
PrimaryButton(
text = "جستجو", Row(
height = 40, modifier = Modifier.fillMaxWidth(),
isLoading = state.drugSearchState.isLoading, horizontalArrangement = Arrangement.End
onClick = { ) {
if (search.isNotBlank()) { Text("لیست گروه های دارویی" , style = MaterialTheme.typography.labelLarge.copy(
onSearch(search) color = MaterialTheme.colorScheme.primary
} ) ,modifier= Modifier.padding(MaterialTheme.dime.md))
} }
)
Spacer(modifier = Modifier.height(MaterialTheme.dime.xs)) DarmanContent(
Box(modifier = Modifier.fillMaxSize()) { darmanData = state.darmanState.getDarmani,
onDarmanClick = { value ->
// Always draw the data if available onSearch(value)
if (state.drugSearchState.drugsData != null && !state.showDarmanList) { },
Column { onRetryClick = {
Row( onRetry()
horizontalArrangement = Arrangement.End, }
modifier = Modifier.fillMaxWidth() )
) { }
TextButton( } else {
onClick = { EmptyStateContent()
onRetry() }
}
) {
Text("مشاهده تمامی گروه های دارویی")
Icon( // Draw the loading dialog on top if needed
imageVector = Icons.Default.KeyboardArrowRight, if (state.drugSearchState.isLoading || state.drugDetailState.isLoading || state.darmanState.isLoading) {
contentDescription = "back" CustomModalDialog(
) onDismissRequest = { /* maybe disable dismiss while loading */ },
Spacer(modifier = Modifier.width(MaterialTheme.dime.sm)) content = {
} Column(horizontalAlignment = Alignment.CenterHorizontally) {
} Loading(color = MaterialTheme.colorScheme.primary)
DrugListContent( Spacer(modifier = Modifier.height(MaterialTheme.dime.sm))
drugsData = state.drugSearchState.drugsData, Text("در حال جستجو..." , textAlign = TextAlign.Right)
onDrugClick = onDrugClick }
) },
} // modifier = Modifier
} // .matchParentSize()
else if (state.darmanState.getDarmani != null ){ // .background(MaterialTheme.colorScheme.background.copy(alpha = 0.5f))
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))
)
}
}
}
}
}
@@ -1,185 +1,207 @@
package com.approagency.drug.presentation.screens package com.approagency.drug.presentation.screens
import androidx.compose.foundation.clickable import android.R.attr.onClick
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.layout.height
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Text import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable import androidx.compose.material.icons.Icons
import androidx.compose.runtime.collectAsState import androidx.compose.material.icons.automirrored.twotone.KeyboardArrowLeft
import androidx.compose.runtime.getValue import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.remember import androidx.compose.material3.Icon
import androidx.compose.ui.Alignment import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.Modifier import androidx.compose.material3.Text
import androidx.compose.ui.text.style.TextAlign import androidx.compose.material3.TextButton
import androidx.compose.ui.text.style.TextDirection import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp import androidx.compose.runtime.collectAsState
import com.approagency.drug.domain.model.TestGroup import androidx.compose.runtime.getValue
import com.approagency.drug.presentation.common.CustomBox import androidx.compose.runtime.mutableStateOf
import com.approagency.drug.presentation.common.CustomModalDialog import androidx.compose.runtime.remember
import com.approagency.drug.presentation.common.Loading import androidx.compose.runtime.setValue
import com.approagency.drug.presentation.common.PrimaryButton import androidx.compose.ui.AbsoluteAlignment
import com.approagency.drug.presentation.viewModel.LabViewModel import androidx.compose.ui.Alignment
import com.vada.caller.ui.theme.dime import androidx.compose.ui.Modifier
import kotlinx.coroutines.flow.Flow import androidx.compose.ui.text.style.TextAlign
import kotlinx.coroutines.flow.count import androidx.compose.ui.text.style.TextDirection
import org.koin.androidx.compose.koinViewModel import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@Composable import com.approagency.drug.domain.model.TestGroup
fun LabScreen( import com.approagency.drug.domain.model.TestItem
modifier: Modifier = Modifier, import com.approagency.drug.presentation.common.CustomBox
viewModel: LabViewModel = koinViewModel() import com.approagency.drug.presentation.common.CustomModalDialog
) { import com.approagency.drug.presentation.common.CustomTextFilled
val testGroupsState by viewModel.testGroups.collectAsState() import com.approagency.drug.presentation.common.Loading
val isLoading = testGroupsState.isLoading import com.approagency.drug.presentation.common.PrimaryButton
val error = testGroupsState.error import com.approagency.drug.presentation.components.RetryContent
val testGroups = testGroupsState.testGroup import com.approagency.drug.presentation.components.SearchResultsContent
import com.approagency.drug.presentation.components.TestDetailSheet
Column(modifier = modifier.fillMaxSize()) { import com.approagency.drug.presentation.components.TestGroupItemContent
if (isLoading) { import com.approagency.drug.presentation.viewModel.LabViewModel
CustomModalDialog( import com.vada.caller.ui.theme.dime
onDismissRequest = { /* maybe disable dismiss while loading */ }, import kotlinx.coroutines.flow.Flow
content = { import kotlinx.coroutines.flow.count
Column(horizontalAlignment = Alignment.CenterHorizontally) { import org.koin.androidx.compose.koinViewModel
Loading(color = MaterialTheme.colorScheme.primary)
Spacer(modifier = Modifier.height(MaterialTheme.dime.sm)) @Composable
Text("در حال جستجو..." , textAlign = TextAlign.Right) fun LabScreen(
} modifier: Modifier = Modifier,
}, viewModel: LabViewModel = koinViewModel()
// modifier = Modifier ) {
// .matchParentSize() val testGroupsState by viewModel.testGroups.collectAsState()
// .background(MaterialTheme.colorScheme.background.copy(alpha = 0.5f)) val testItemsState by viewModel.testItems.collectAsState()
) val searchResultsState by viewModel.searchResults.collectAsState()
}
else if(error != null){ val isLoading = testGroupsState.isLoading || testItemsState.isLoading || searchResultsState.isLoading
CustomBox( val error = testGroupsState.error ?: searchResultsState.error
child = { val testGroups = testGroupsState.testGroup
Column(
horizontalAlignment = Alignment.CenterHorizontally, var selectedGroup by remember { mutableStateOf<TestGroup?>(null) }
modifier = Modifier.padding(MaterialTheme.dime.md) var selectedItem by remember { mutableStateOf<TestItem?>(null) }
) { var showSheet by remember { mutableStateOf(false) }
Text( var searchText by remember { mutableStateOf("") }
text = "خطا در دریافت اطلاعات", Column(modifier = modifier.fillMaxSize().padding(horizontal = MaterialTheme.dime.md)
style = MaterialTheme.typography.bodyLarge, .padding(top = MaterialTheme.dime.md),) {
color = MaterialTheme.colorScheme.error CustomTextFilled(
) value = searchText,
Text( onValueChange = {
text = error, searchText = it
style = MaterialTheme.typography.bodySmall, viewModel.updateSearchQuery(it)
modifier = Modifier.padding(vertical = 8.dp) },
) onSearch = { query ->
PrimaryButton( if (query.isNotBlank()) {
text = "تلاش مجدد", viewModel.updateSearchQuery(query)
height = 40, }
isLoading = false, },
onClick = { viewModel.loadTestGroups() } placeholder = "جستجو در گروه‌ها و آیتم‌های آزمایشگاهی",
) showClearButton = true,
} showSearchButton = true,
} autoSearch = true,
) height = 45
} )
else if (testGroups != null){ Spacer(modifier = Modifier.height(MaterialTheme.dime.xs))
TestGroupsList( PrimaryButton(
testGroupsFlow = testGroups, text = "جستجو",
viewModel = viewModel height = 40,
) isLoading =false,
} else { onClick = {}
Box( )
modifier = Modifier.fillMaxSize(), Spacer(modifier = Modifier.height(MaterialTheme.dime.xs))
contentAlignment = Alignment.Center if (searchResultsState.query.isNotBlank()) {
) { // Search Results Section
Text("هیچ داده‌ای موجود نیست") SearchResultsContent(
} searchResult = searchResultsState.searchResults,
} isLoading = searchResultsState.isLoading,
error = searchResultsState.error,
} onGroupClick = { group ->
} selectedGroup = group
viewModel.getItemByGroupId(group.id)
@Composable showSheet = true
fun TestGroupsList( },
testGroupsFlow: Flow<List<TestGroup>>, onItemClick = { item ->
viewModel: LabViewModel selectedItem = item
) { // You can show item detail sheet here
val testGroups by testGroupsFlow.collectAsState(initial = emptyList()) // For now, we'll show the parent group
viewModel.getItemByGroupId(item.groupId)
if (testGroups.isEmpty()) { showSheet = true
CustomModalDialog( },
onDismissRequest = { /* maybe disable dismiss while loading */ }, onClearSearch = {
content = { searchText = ""
Column(horizontalAlignment = Alignment.CenterHorizontally) { viewModel.clearSearch()
Loading(color = MaterialTheme.colorScheme.primary) viewModel.loadTestGroups()
Spacer(modifier = Modifier.height(MaterialTheme.dime.sm)) }
Text("در حال جستجو..." , textAlign = TextAlign.Right) )
} } else if(error != null){
}, RetryContent(modifier = modifier , error = error , onClick = {
// modifier = Modifier viewModel.loadTestGroups()
// .matchParentSize() })
// .background(MaterialTheme.colorScheme.background.copy(alpha = 0.5f)) }
) else if (testGroups != null){
} else { TestGroupsList(
LazyColumn( testGroupsFlow = testGroups,
modifier = Modifier.fillMaxSize() viewModel = viewModel,
) { onGroupClick = { group ->
items(testGroups) { group -> selectedGroup = group
TestGroupItem( viewModel.getItemByGroupId(group.id)
group = group, showSheet = true
onClick = { }
// Navigate to test items of this group )
// viewModel.loadTestItemsByGroup(group.id) }
} else {
) Box(
} modifier = Modifier.fillMaxSize(),
} contentAlignment = Alignment.Center
} ) {
} Text("هیچ داده‌ای موجود نیست")
}
@Composable }
fun TestGroupItem(
group: TestGroup, }
onClick: () -> Unit if (isLoading) {
) { CustomModalDialog(
CustomBox ( onDismissRequest = { /* maybe disable dismiss while loading */ },
modifier = Modifier content = {
.padding(bottom = MaterialTheme.dime.sm) Column(horizontalAlignment = Alignment.CenterHorizontally) {
.clickable( Loading(color = MaterialTheme.colorScheme.primary)
indication = null, Spacer(modifier = Modifier.height(MaterialTheme.dime.sm))
interactionSource = remember { MutableInteractionSource() } Text("در حال جستجو..." , textAlign = TextAlign.Right)
) { }
onClick() },
}, // modifier = Modifier
) { // .matchParentSize()
Column ( // .background(MaterialTheme.colorScheme.background.copy(alpha = 0.5f))
verticalArrangement = Arrangement.Center, )
horizontalAlignment = Alignment.Start }
) { if (showSheet && selectedGroup != null) {
Text( TestDetailSheet(
text = group.fname ?: group.ename ?: "بدون نام", state = testItemsState,
style = MaterialTheme.typography.titleMedium.copy( testGroup = selectedGroup!!,
textAlign = TextAlign.Right, onDismiss = { showSheet = false }
textDirection = TextDirection.Rtl )
), }
modifier = Modifier.padding(vertical = MaterialTheme.dime.xs) }
)
group.detail?.let { @Composable
Text( fun TestGroupsList(
text = it, testGroupsFlow: Flow<List<TestGroup>>,
style = MaterialTheme.typography.bodySmall.copy( viewModel: LabViewModel,
textAlign = TextAlign.Right , onGroupClick: (TestGroup) -> Unit
textDirection = TextDirection.Rtl ) {
), val testGroups by testGroupsFlow.collectAsState(initial = emptyList())
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(horizontal = MaterialTheme.dime.lg) if (testGroups.isEmpty()) {
) 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))
)
} else {
LazyColumn(
modifier = Modifier.fillMaxSize()
.fillMaxSize().padding( vertical =MaterialTheme.dime.md )
) {
items(testGroups) { group ->
TestGroupItemContent(
group = group,
onClick = { onGroupClick(group) }
)
}
}
}
}
@@ -1,128 +1,126 @@
package com.approagency.drug.presentation.screens package com.approagency.drug.presentation.screens
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Build import androidx.compose.material.icons.filled.Build
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarDefaults import androidx.compose.material3.NavigationBarDefaults
import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults import androidx.compose.material3.NavigationBarItemDefaults
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.ScaffoldDefaults import androidx.compose.material3.ScaffoldDefaults
import androidx.compose.material3.SegmentedButtonDefaults.Icon import androidx.compose.material3.SegmentedButtonDefaults.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
@SuppressLint("ConfigurationScreenWidthHeight") @SuppressLint("ConfigurationScreenWidthHeight")
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun RootScreen(navHostController: NavHostController, modifier: Modifier){ fun RootScreen(navHostController: NavHostController, modifier: Modifier){
val pages = listOf( val pages = listOf(
"دارو" to Icons.Default.Search, "دارو" to Icons.Default.Search,
"آزمایش" to Icons.Default.Build "آزمایش" to Icons.Default.Build
) )
var seletedTab by remember { mutableStateOf(0) } var seletedTab by remember { mutableStateOf(0) }
val context = LocalContext.current val context = LocalContext.current
val activity = context as Activity val activity = context as Activity
Scaffold ( Scaffold (
contentWindowInsets = ScaffoldDefaults.contentWindowInsets, contentWindowInsets = ScaffoldDefaults.contentWindowInsets,
topBar = { CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl){ topBar = { CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl){
TopAppBar( TopAppBar(
colors = TopAppBarDefaults.topAppBarColors( colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer, containerColor = MaterialTheme.colorScheme.primaryContainer,
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,
modifier = Modifier
containerColor = MaterialTheme.colorScheme.surface, .fillMaxWidth()
modifier = Modifier .height(110.dp).padding(bottom = 2.dp).clip(MaterialTheme.shapes.extraLarge),
.fillMaxWidth() tonalElevation = 8.dp, // subtle shadow
.height(110.dp).padding(bottom = 2.dp).clip(MaterialTheme.shapes.extraLarge), windowInsets = WindowInsets.navigationBars
tonalElevation = 8.dp, // subtle shadow .only(WindowInsetsSides.Bottom)
windowInsets = WindowInsets.navigationBars ) {
.only(WindowInsetsSides.Bottom) pages.forEachIndexed { index, item ->
) { val selected = seletedTab == index
pages.forEachIndexed { index, item ->
val selected = seletedTab == index NavigationBarItem(
colors = NavigationBarItemDefaults.colors(
NavigationBarItem( selectedIconColor = MaterialTheme.colorScheme.onPrimary,
colors = NavigationBarItemDefaults.colors( selectedTextColor = MaterialTheme.colorScheme.primary,
selectedIconColor = MaterialTheme.colorScheme.onPrimary, indicatorColor = MaterialTheme.colorScheme.primary,
selectedTextColor = MaterialTheme.colorScheme.primary, unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
indicatorColor = MaterialTheme.colorScheme.primary, unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant, ),
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant selected = selected,
), onClick = { seletedTab = index },
selected = selected,
onClick = { seletedTab = index }, icon = {
Icon(
icon = { imageVector = item.second,
Icon( contentDescription = item.first,
imageVector = item.second, modifier = Modifier.size(16.dp)
contentDescription = item.first, )
modifier = Modifier.size(16.dp) },
)
}, label = {
Text(
label = { item.first,
Text( style = MaterialTheme.typography.labelMedium
item.first, )
style = MaterialTheme.typography.labelMedium },
)
}, alwaysShowLabel = true
)
alwaysShowLabel = true }
) }
} }
} ) {
} padding -> when (seletedTab) {
) { 0 -> HomeScreen( navController = navHostController,Modifier.padding(padding).consumeWindowInsets(padding))
padding -> when (seletedTab) { 1 -> LabScreen(Modifier.padding(padding))
0 -> HomeScreen( navController = navHostController,Modifier.padding(padding).consumeWindowInsets(padding)) }
1 -> LabScreen(Modifier.padding(padding)) }
} }
}
}
@@ -1,165 +1,187 @@
package com.approagency.drug.presentation.viewModel package com.approagency.drug.presentation.viewModel
import android.net.http.HttpException import android.net.http.HttpException
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.approagency.drug.data.dto.DarmanModel import com.approagency.drug.data.dto.DarmanModel
import com.approagency.drug.data.dto.DrugDetail import com.approagency.drug.data.dto.DrugDetail
import com.approagency.drug.data.dto.DrugListResponse import com.approagency.drug.data.dto.DrugListResponse
import com.approagency.drug.data.dto.DrugModels import com.approagency.drug.data.dto.DrugModels
import com.approagency.drug.domain.model.DrugSearchParams import com.approagency.drug.domain.model.DrugSearchParams
import com.approagency.drug.domain.usecase.GetDarmanUseCase 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 kotlinx.coroutines.flow.MutableSharedFlow import com.approagency.drug.utils.retryWithBackoff
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class HomeViewModel (
private val getDrugSearchUseCase: GetDrugSearchUseCase, class HomeViewModel (
private val getDrugDetailUseCase: GetDrugDetailUseCase, private val getDrugSearchUseCase: GetDrugSearchUseCase,
private val getDarmanUseCase: GetDarmanUseCase private val getDrugDetailUseCase: GetDrugDetailUseCase,
): ViewModel() { private val getDarmanUseCase: GetDarmanUseCase
private val _uiState = MutableStateFlow(CombineState()) ): ViewModel() {
val uiState : StateFlow<CombineState> = _uiState private val _uiState = MutableStateFlow(CombineState())
val uiState : StateFlow<CombineState> = _uiState
// Add SharedFlow for one-time events
private val _event = MutableSharedFlow<HomeEvent>() // Add SharedFlow for one-time events
val event = _event.asSharedFlow() private val _event = MutableSharedFlow<HomeEvent>()
init { val event = _event.asSharedFlow()
getDarmani() init {
} getDarmani()
fun getDarmani(){ }
_uiState.update { it.copy(darmanState = it.darmanState.copy( fun getDarmani() {
isLoading = true _uiState.update {
) , showDarmanList = true ) } it.copy(
viewModelScope.launch { darmanState = it.darmanState.copy(isLoading = true),
try { showDarmanList = true
val result = getDarmanUseCase.invoke() )
_uiState.update { }
it.copy( viewModelScope.launch {
darmanState = it.darmanState.copy( retryWithBackoff(
isLoading = false, maxRetries = 3,
getDarmani = result onRetry = { attempt, delay ->
) // Optional: log retry attempt
) println("Retrying getDarmani, attempt $attempt, delay $delay ms")
} }
} catch (e: HttpException){ ) {
handleError(e.message) getDarmanUseCase.invoke()
} }.fold(
} onSuccess = { result ->
} _uiState.update {
it.copy(
fun searchDrugs(drugSearchParams: DrugSearchParams) { darmanState = it.darmanState.copy(
_uiState.update { it.copy(drugSearchState = it.drugSearchState.copy( isLoading = false,
isLoading = true getDarmani = result
) , showDarmanList = false) } )
viewModelScope.launch { )
try { }
val result = getDrugSearchUseCase.invoke(drugSearchParams) },
_uiState.update { it.copy( onFailure = { error ->
drugSearchState = it.drugSearchState.copy( handleError(error.message ?: "Unknown error")
isLoading = false, _uiState.update {
drugsData = result it.copy(
) darmanState = it.darmanState.copy(
isLoading = false,
) } getDarmani = Result.failure(error)
} catch (e: HttpException) { )
handleError(e.message) )
}catch (e: Exception) { }
handleError(e.localizedMessage ?: "Unknown error") }
} )
} }
} }
fun drugDetail(cod: Int , allDrugs:Result<DrugListResponse?>?) { fun searchDrugs(drugSearchParams: DrugSearchParams) {
_uiState.update { it.copy(drugDetailState = it.drugDetailState.copy( _uiState.update { it.copy(drugSearchState = it.drugSearchState.copy(
isLoading = true isLoading = true
) , showDarmanList = false) } ) , showDarmanList = false) }
viewModelScope.launch { viewModelScope.launch {
try { try {
val result = getDrugDetailUseCase.invoke(cod) val result = getDrugSearchUseCase.invoke(drugSearchParams)
_uiState.update { _uiState.update { it.copy(
it.copy( drugSearchState = it.drugSearchState.copy(
drugDetailState = it.drugDetailState.copy( isLoading = false,
isLoading = false, drugsData = result
drugDetail = result, )
isDetailVisible = true
), ) }
drugSearchState = it.drugSearchState.copy( } catch (e: HttpException) {
isLoading = false, handleError(e.message)
drugsData = allDrugs }catch (e: Exception) {
) handleError(e.localizedMessage ?: "Unknown error")
) }
} }
println(result) }
} catch (e: HttpException) {
handleError(e.message) fun drugDetail(cod: Int , allDrugs:Result<DrugListResponse?>?) {
}catch (e: Exception) { _uiState.update { it.copy(drugDetailState = it.drugDetailState.copy(
handleError(e.localizedMessage ?: "Unknown error") isLoading = true
} ) , showDarmanList = false) }
} viewModelScope.launch {
} try {
fun closeDetail() { val result = getDrugDetailUseCase.invoke(cod)
_uiState.update { _uiState.update {
it.copy( it.copy(
drugDetailState = it.drugDetailState.copy( drugDetailState = it.drugDetailState.copy(
isDetailVisible = false, isLoading = false,
drugDetail = null // optional, depends if you want to keep last detail cached drugDetail = result,
) isDetailVisible = true
) ),
} drugSearchState = it.drugSearchState.copy(
} isLoading = false,
drugsData = allDrugs
private suspend fun handleError(message: String?) { )
_uiState.update { it.copy(drugSearchState = it.drugSearchState.copy( )
isLoading = false }
) , drugDetailState = it.drugDetailState.copy( println(result)
isLoading = false } catch (e: HttpException) {
)) } handleError(e.message)
_event.emit(HomeEvent.ShowError(message ?: "Error occurred")) }catch (e: Exception) {
} handleError(e.localizedMessage ?: "Unknown error")
}
// Clear error when consumed }
fun clearError() { }
// _uiState.update { it.copy(error = null) } fun closeDetail() {
} _uiState.update {
} it.copy(
drugDetailState = it.drugDetailState.copy(
isDetailVisible = false,
sealed class HomeEvent { drugDetail = null // optional, depends if you want to keep last detail cached
data class ShowError(val message: String) : HomeEvent() )
data class ShowDetailDrug(val drugDetail:Result<DrugModels?>?): HomeEvent() )
} }
}
data class CombineState(
val drugSearchState: DrugSearchState = DrugSearchState() , private suspend fun handleError(message: String?) {
val drugDetailState: DrugDetailState =DrugDetailState(), _uiState.update { it.copy(drugSearchState = it.drugSearchState.copy(
val darmanState: GetDarmaniState = GetDarmaniState(), isLoading = false
val showDarmanList: Boolean = true ) , drugDetailState = it.drugDetailState.copy(
) isLoading = false
)) }
data class DrugSearchState( _event.emit(HomeEvent.ShowError(message ?: "Error occurred"))
val isLoading: Boolean = false, }
val drugsData: Result<DrugListResponse?>? = null,
val drugDetail: Result<DrugModels?>? = null, // Clear error when consumed
val error:String? = null fun clearError() {
) // _uiState.update { it.copy(error = null) }
}
data class DrugDetailState( }
val isLoading: Boolean = false,
val drugDetail:Result<DrugModels?>? = null,
val isDetailVisible: Boolean = false, sealed class HomeEvent {
val error:String? = null data class ShowError(val message: String) : HomeEvent()
) data class ShowDetailDrug(val drugDetail:Result<DrugModels?>?): HomeEvent()
}
data class GetDarmaniState(
val isLoading: Boolean = false, data class CombineState(
val getDarmani:Result<DarmanModel?>? = null, val drugSearchState: DrugSearchState = DrugSearchState() ,
val error:String? = null val drugDetailState: DrugDetailState =DrugDetailState(),
) val darmanState: GetDarmaniState = GetDarmaniState(),
val showDarmanList: Boolean = true
)
data class DrugSearchState(
val isLoading: Boolean = false,
val drugsData: Result<DrugListResponse?>? = null,
val drugDetail: Result<DrugModels?>? = null,
val error:String? = null
)
data class DrugDetailState(
val isLoading: Boolean = false,
val drugDetail:Result<DrugModels?>? = null,
val isDetailVisible: Boolean = false,
val error:String? = null
)
data class GetDarmaniState(
val isLoading: Boolean = false,
val getDarmani:Result<DarmanModel?>? = null,
val error:String? = null
)
@@ -1,94 +1,174 @@
package com.approagency.drug.presentation.viewModel package com.approagency.drug.presentation.viewModel
import android.net.http.HttpException import android.net.http.HttpException
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.approagency.drug.data.dto.DrugListResponse import com.approagency.drug.data.dto.DrugListResponse
import com.approagency.drug.data.dto.DrugModels import com.approagency.drug.data.dto.DrugModels
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.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 kotlinx.coroutines.flow.Flow import com.approagency.drug.domain.usecase.SearchTestsUseCase
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
class LabViewModel ( import kotlinx.coroutines.flow.update
private val getTestGroupUseCase: GetTestGroupUseCase, import kotlinx.coroutines.launch
private val getTestItemByGroupId: GetTestItemByGroupId
) : ViewModel(){ class LabViewModel (
private val _testGroups = MutableStateFlow(TestGroupItem()) private val getTestGroupUseCase: GetTestGroupUseCase,
val testGroups: StateFlow<TestGroupItem> = _testGroups.asStateFlow() private val getTestItemByGroupId: GetTestItemByGroupId,
private val searchTestsUseCase: SearchTestsUseCase
private val _testItems = MutableStateFlow(TestItemState()) ) : ViewModel(){
val testItems: StateFlow<TestItemState> = _testItems.asStateFlow() private val _testGroups = MutableStateFlow(TestGroupItem())
val testGroups: StateFlow<TestGroupItem> = _testGroups.asStateFlow()
init {
loadTestGroups() private val _testItems = MutableStateFlow(TestItemState())
} val testItems: StateFlow<TestItemState> = _testItems.asStateFlow()
fun loadTestGroups(){
_testGroups.update { private val _searchResults = MutableStateFlow(SearchResultState())
it.copy( val searchResults: StateFlow<SearchResultState> = _searchResults.asStateFlow()
isLoading = true
) private val searchQuery = MutableStateFlow("")
}
viewModelScope.launch { init {
try { loadTestGroups()
val result = getTestGroupUseCase.invoke(); setupSearch()
_testGroups.update { }
it.copy( private fun setupSearch() {
isLoading = false, viewModelScope.launch {
testGroup = result, searchQuery
) .debounce(1000) // Wait 500ms after user stops typing
} .distinctUntilChanged()
} catch (e:HttpException){ .collect { query ->
handleError(e.message) if (query.isNotBlank()) {
} performSearch(query)
} else {
} _searchResults.update {
} SearchResultState(
isLoading = false,
fun getItemByGroupId(id:Int){ searchResults = null,
_testItems.update { it.copy(isLoading = true) } query = ""
viewModelScope.launch { )
try { }
val result = getTestItemByGroupId.invoke(id); }
_testItems.update { }
it.copy( }
isLoading = false, }
testItem = result
) fun updateSearchQuery(query: String) {
} searchQuery.value = query
} catch (e:HttpException){ _searchResults.update {
handleError(e.message) it.copy(
} isLoading = query.isNotBlank(),
} query = query
} )
private suspend fun handleError(message: String?) { }
_testGroups.update { }
it.copy(
isLoading = false, private fun performSearch(query: String) {
testGroup = null, viewModelScope.launch {
error = message try {
) val result = searchTestsUseCase.invoke(query)
} result.collect { (groups, items) ->
} _searchResults.update {
it.copy(
} isLoading = false,
searchResults = SearchResult(groups = groups, items = items),
data class TestGroupItem( error = null
val isLoading: Boolean = false, )
val testGroup: Flow<List<TestGroup>>? = null, }
val error:String? = null }
) } catch (e: Exception) {
_searchResults.update {
data class TestItemState( it.copy(
val isLoading: Boolean = false, isLoading = false,
val testItem: Flow<List<TestItem>>? = null, searchResults = null,
val error:String? = null error = e.message ?: "خطا در جستجو"
) )
}
}
}
}
fun loadTestGroups(){
_testGroups.update {
it.copy(
isLoading = true
)
}
viewModelScope.launch {
try {
val result = getTestGroupUseCase.invoke();
_testGroups.update {
it.copy(
isLoading = false,
testGroup = result,
)
}
} catch (e:HttpException){
handleError(e.message)
}
}
}
fun clearSearch() {
searchQuery.value = ""
_searchResults.update {
SearchResultState()
}
}
fun getItemByGroupId(id:Int){
_testItems.update { it.copy(isLoading = true) }
viewModelScope.launch {
try {
val result = getTestItemByGroupId.invoke(id);
_testItems.update {
it.copy(
isLoading = false,
testItem = result
)
}
} catch (e:HttpException){
handleError(e.message)
}
}
}
private suspend fun handleError(message: String?) {
_testGroups.update {
it.copy(
isLoading = false,
testGroup = null,
error = message
)
}
}
}
data class TestGroupItem(
val isLoading: Boolean = false,
val testGroup: Flow<List<TestGroup>>? = null,
val error:String? = null
)
data class TestItemState(
val isLoading: Boolean = false,
val testItem: Flow<List<TestItem>>? = 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>
)
@@ -1,20 +1,20 @@
package com.vada.caller.ui.theme package com.vada.caller.ui.theme
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
data class Dime ( data class Dime (
val xxs: Dp = 2.dp, val xxs: Dp = 2.dp,
val xs: Dp = 4.dp, val xs: Dp = 4.dp,
val sm: Dp = 8.dp, val sm: Dp = 8.dp,
val md: Dp = 12.dp, val md: Dp = 12.dp,
val lg: Dp = 16.dp, val lg: Dp = 16.dp,
val xl: Dp = 20.dp, val xl: Dp = 20.dp,
val xxl: Dp = 24.dp, val xxl: Dp = 24.dp,
val iconSm: Dp = 20.dp, val iconSm: Dp = 20.dp,
val iconMd: Dp = 24.dp, val iconMd: Dp = 24.dp,
val iconLg: Dp = 32.dp, val iconLg: Dp = 32.dp,
) )
@@ -1,5 +1,5 @@
package com.vada.caller.ui.theme package com.vada.caller.ui.theme
import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.runtime.staticCompositionLocalOf
val LocalDime = staticCompositionLocalOf { Dime() } val LocalDime = staticCompositionLocalOf { Dime() }
@@ -1,8 +1,8 @@
package com.vada.caller.ui.theme package com.vada.caller.ui.theme
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
val MaterialTheme.dime: Dime val MaterialTheme.dime: Dime
@Composable @Composable
get() =LocalDime.current get() =LocalDime.current
@@ -1,12 +1,12 @@
package com.fabirt.podcastapp.ui.theme package com.fabirt.podcastapp.ui.theme
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Shapes import androidx.compose.material3.Shapes
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
val Shapes = Shapes( val Shapes = Shapes(
small = RoundedCornerShape(4.dp), small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(12.dp), medium = RoundedCornerShape(12.dp),
large = RoundedCornerShape(16.dp), large = RoundedCornerShape(16.dp),
extraLarge = RoundedCornerShape(20.dp) extraLarge = RoundedCornerShape(20.dp)
) )
@@ -1,5 +1,5 @@
package com.approagency.drug.utils package com.approagency.drug.utils
object Config { object Config {
const val BASE_URL = "https://drug.approagency.ir/api/" const val BASE_URL = "https://drug.approagency.ir/api/"
} }
@@ -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)
}
}
+7 -7
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto" <font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts" app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms" app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="Aldrich" app:fontProviderQuery="Aldrich"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs"> app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family> </font-family>
+17 -17
View File
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<array name="com_google_android_gms_fonts_certs"> <array name="com_google_android_gms_fonts_certs">
<item>@array/com_google_android_gms_fonts_certs_dev</item> <item>@array/com_google_android_gms_fonts_certs_dev</item>
<item>@array/com_google_android_gms_fonts_certs_prod</item> <item>@array/com_google_android_gms_fonts_certs_prod</item>
</array> </array>
<string-array name="com_google_android_gms_fonts_certs_dev"> <string-array name="com_google_android_gms_fonts_certs_dev">
<item> <item>
MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs= MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
</item> </item>
</string-array> </string-array>
<string-array name="com_google_android_gms_fonts_certs_prod"> <string-array name="com_google_android_gms_fonts_certs_prod">
<item> <item>
MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
</item> </item>
</string-array> </string-array>
</resources> </resources>
+6 -6
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<array name="preloaded_fonts" translatable="false"> <array name="preloaded_fonts" translatable="false">
<item>@font/aldrich</item> <item>@font/aldrich</item>
</array> </array>
</resources> </resources>
+7 -1
View File
@@ -21,4 +21,10 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies, # resources declared in the library itself and none from the library's dependencies,
# 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
View File
@@ -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
Vendored
+94 -94
View File
@@ -1,94 +1,94 @@
@rem @rem
@rem Copyright 2015 the original author or authors. @rem Copyright 2015 the original author or authors.
@rem @rem
@rem Licensed under the Apache License, Version 2.0 (the "License"); @rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License. @rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at @rem You may obtain a copy of the License at
@rem @rem
@rem https://www.apache.org/licenses/LICENSE-2.0 @rem https://www.apache.org/licenses/LICENSE-2.0
@rem @rem
@rem Unless required by applicable law or agreed to in writing, software @rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS, @rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0 @rem SPDX-License-Identifier: Apache-2.0
@rem @rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@rem @rem
@rem ########################################################################## @rem ##########################################################################
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused @rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter. @rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2 echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2 echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2 echo location of your Java installation. 1>&2
goto fail goto fail
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. 1>&2 echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2 echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2 echo location of your Java installation. 1>&2
goto fail goto fail
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH= set CLASSPATH=
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd if %ERRORLEVEL% equ 0 goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL% set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1 if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE% exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal
:omega :omega
+8 -8
View File
@@ -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()