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