second commit

This commit is contained in:
pedro 2025-02-19 15:32:17 +03:00
parent 9cd1a28cdf
commit aa918de503
86 changed files with 903 additions and 1943 deletions

7
.gitignore vendored
View File

@ -1,7 +1,12 @@
*.iml
.gradle
/local.properties
/.idea
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures

6
.gitmodules vendored
View File

@ -1,6 +0,0 @@
[submodule "gradle"]
path = gradle
url = https://git.sicampus.ru/core/gradle.git
[submodule "buildSrc"]
path = buildSrc
url = https://git.sicampus.ru/core/dependecies.git

3
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

1
.idea/.name generated Normal file
View File

@ -0,0 +1 @@
nto_minipigs

123
.idea/codeStyles/Project.xml generated Normal file
View File

@ -0,0 +1,123 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

6
.idea/compiler.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

10
.idea/deploymentTargetSelector.xml generated Normal file
View File

@ -0,0 +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>
</project>

20
.idea/gradle.xml generated Normal file
View File

@ -0,0 +1,20 @@
<?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>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

6
.idea/kotlinc.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.0.0" />
</component>
</project>

10
.idea/migrations.xml generated Normal file
View File

@ -0,0 +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>
</project>

9
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,9 @@
<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">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

17
.idea/runConfigurations.xml generated Normal file
View File

@ -0,0 +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>
</project>

View File

@ -1,45 +0,0 @@
kotlin version: 2.0.21
error message: androidx.compose.compiler.plugins.kotlin.IncompatibleComposeRuntimeVersionException: The Compose Compiler requires the Compose Runtime to be on the class path, but none could be found. The compose compiler plugin you are using (version 1.5.14) expects a minimum runtime version of 1.0.0.
at androidx.compose.compiler.plugins.kotlin.VersionChecker.noRuntimeOnClasspathError(VersionChecker.kt:221)
at androidx.compose.compiler.plugins.kotlin.VersionChecker.check(VersionChecker.kt:193)
at androidx.compose.compiler.plugins.kotlin.ComposeIrGenerationExtension.generate(ComposeIrGenerationExtension.kt:73)
at org.jetbrains.kotlin.fir.pipeline.ConvertToIrKt.applyIrGenerationExtensions(convertToIr.kt:442)
at org.jetbrains.kotlin.fir.pipeline.Fir2IrPipeline.runActualizationPipeline(convertToIr.kt:246)
at org.jetbrains.kotlin.fir.pipeline.Fir2IrPipeline.convertToIrAndActualize(convertToIr.kt:130)
at org.jetbrains.kotlin.fir.pipeline.ConvertToIrKt.convertToIrAndActualize(convertToIr.kt:99)
at org.jetbrains.kotlin.fir.pipeline.ConvertToIrKt.convertToIrAndActualize$default(convertToIr.kt:72)
at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.convertToIrAndActualizeForJvm(jvmCompilerPipeline.kt:196)
at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.convertAnalyzedFirToIr(jvmCompilerPipeline.kt:169)
at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:140)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.tryCompileIncrementally$lambda$9$compile(IncrementalCompilerRunner.kt:249)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.tryCompileIncrementally(IncrementalCompilerRunner.kt:267)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:120)
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)

View File

@ -1,45 +0,0 @@
kotlin version: 2.0.21
error message: androidx.compose.compiler.plugins.kotlin.IncompatibleComposeRuntimeVersionException: The Compose Compiler requires the Compose Runtime to be on the class path, but none could be found. The compose compiler plugin you are using (version 1.5.14) expects a minimum runtime version of 1.0.0.
at androidx.compose.compiler.plugins.kotlin.VersionChecker.noRuntimeOnClasspathError(VersionChecker.kt:221)
at androidx.compose.compiler.plugins.kotlin.VersionChecker.check(VersionChecker.kt:193)
at androidx.compose.compiler.plugins.kotlin.ComposeIrGenerationExtension.generate(ComposeIrGenerationExtension.kt:73)
at org.jetbrains.kotlin.fir.pipeline.ConvertToIrKt.applyIrGenerationExtensions(convertToIr.kt:442)
at org.jetbrains.kotlin.fir.pipeline.Fir2IrPipeline.runActualizationPipeline(convertToIr.kt:246)
at org.jetbrains.kotlin.fir.pipeline.Fir2IrPipeline.convertToIrAndActualize(convertToIr.kt:130)
at org.jetbrains.kotlin.fir.pipeline.ConvertToIrKt.convertToIrAndActualize(convertToIr.kt:99)
at org.jetbrains.kotlin.fir.pipeline.ConvertToIrKt.convertToIrAndActualize$default(convertToIr.kt:72)
at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.convertToIrAndActualizeForJvm(jvmCompilerPipeline.kt:196)
at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.convertAnalyzedFirToIr(jvmCompilerPipeline.kt:169)
at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:140)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.tryCompileIncrementally$lambda$9$compile(IncrementalCompilerRunner.kt:249)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.tryCompileIncrementally(IncrementalCompilerRunner.kt:267)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:120)
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)

View File

@ -0,0 +1,82 @@
kotlin version: 2.0.0
error message: Daemon compilation failed: null
java.lang.Exception
at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:69)
at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:65)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:240)
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:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
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)
Caused by: java.nio.file.DirectoryNotEmptyException: C:\Users\User\AppData\Local\Temp\kotlin-backups9600370536754382189
at java.base/sun.nio.fs.WindowsFileSystemProvider.implDelete(Unknown Source)
at java.base/sun.nio.fs.AbstractFileSystemProvider.delete(Unknown Source)
at java.base/java.nio.file.Files.delete(Unknown Source)
at org.jetbrains.kotlin.incremental.RecoverableCompilationTransaction$cleanupStash$2$1$1.invoke(CompilationTransaction.kt:244)
at org.jetbrains.kotlin.incremental.RecoverableCompilationTransaction$cleanupStash$2$1$1.invoke(CompilationTransaction.kt:244)
at org.jetbrains.kotlin.incremental.RecoverableCompilationTransaction.cleanupStash$lambda$11$lambda$10$lambda$9(CompilationTransaction.kt:244)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(Unknown Source)
at java.base/java.util.ArrayList.forEach(Unknown Source)
at java.base/java.util.stream.SortedOps$RefSortingSink.end(Unknown Source)
at java.base/java.util.stream.Sink$ChainedReference.end(Unknown Source)
at java.base/java.util.stream.AbstractPipeline.copyInto(Unknown Source)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(Unknown Source)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(Unknown Source)
at java.base/java.util.stream.AbstractPipeline.evaluate(Unknown Source)
at java.base/java.util.stream.ReferencePipeline.forEach(Unknown Source)
at org.jetbrains.kotlin.incremental.RecoverableCompilationTransaction.cleanupStash(CompilationTransaction.kt:244)
at org.jetbrains.kotlin.incremental.RecoverableCompilationTransaction.close(CompilationTransaction.kt:254)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.tryCompileIncrementally(IncrementalCompilerRunner.kt:747)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:120)
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:676)
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1661)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
... 3 more

121
README.md
View File

@ -1,121 +0,0 @@
[![Android Studio version](https://img.shields.io/endpoint?url=https%3A%2F%2Fsicampus.ru%2Fgitea%2Fcore%2Fdocs%2Fraw%2Fbranch%2Fmain%2Fandroid-studio-label.json)](https://sicampus.ru/gitea/core/docs/src/branch/main/how-upload-project.md)
# НТО 2024. II отборочный этап. Командные задани — клиентская часть
## 📖 Предыстория
В компании S контроль доступа в офис осуществляется с помощью СКУД (системы контроля управления доступом). На данный момент у каждого сотрудника компании есть карта-пропуск с NFC меткой. А у каждой входной двери есть считыватель с обеих сторон. При поднесении карты к считывателю, дверь открывается, а информация о времени входа или выхода сотрудника фиксируется в базе данных.
Администрации компании S требуется мобильное приложение, как для рядовых сотрудников, так и для администрации с возможностью просмотра посещений и работой электронного пропуска как временной замены обычного (при помощи сканировании QR кода, который находится на считывателе). Поскольку в приложении есть возможность использовать телефон как пропуск - то к данному приложению повышенные требования к безопасности всех данных, находящихся внутри него.
## 📋 Системные требования
| **Параметр** | **Требование** |
|-----------------------------|---------------------------------------|
| **Минимальная версия Android** | 9.0 (API 28) |
| **Целевая версия Android** | 14 (API 34) |
| **Поддерживаемые устройства** | смартфоны, планшеты |
| **Ориентация экранов** | портретная |
| **Языки** | русский, английский |
| **Разрешения** | доступ к интернету, камера (при необходимости) |
## 📱 Техническое задание
Требуется разработать нативное мобильное приложение, которое будет содержать следующие экраны.
### 1. Экран авторизации
> Данный экран должен быть показан при первом входе в приложение, а также в ситуациях, когда пользователь не зарегистрировался в приложении.
#### Элементы, которые должны присутствовать на экране:
- Поле ввода (`id/username`), в котором пользователю необходимо ввести свой логин.
- Кнопка входа (`id/login`), по нажатию на которую пользователь авторизуется в системе.
- По умолчанию скрытое (`GONE`) текстовое поле с ошибкой (`id/error`).
#### Требования к компонентам:
1. В пустом поле ввода должна отображаться подсказка, что требуется ввести пользователю.
2. Если хотя бы одно из условий ниже соблюдено - кнопка должна быть неактивной:
- Поле ввода пустое.
- Количество символов менее 3х.
- Логин начинается с цифры.
- Логин содержит символы, отличные от латинского алфавита и цифр.
3. Поле ввода и кнопку должно быть видно при раскрытии клавиатуры.
4. - При нажатии на кнопку входа необходимо проверить, что данный пользователь существует с помощью запроса `api/<LOGIN>/auth` (подробное описание представлено в техническом задании серверной части).
5. В случае отсутствия логина или любой другой неполадки - необходимо вывести ошибку, пока пользователь не изменит текстовое поле или повторно не нажмёт на кнопку.
6. После нажатия на кнопку - логин должен быть сохранён и при следующем открытии приложения экран авторизации не должен быть показан.
7. После нажатия на кнопку - при нажатии стрелки назад - экран авторизации не должен быть показан повторно.
8. Экран авторизации показывается только в случае, если пользователь неавторизован.
### 2. Главный экран
> Данный экран содержит общую информацию о пользователе:
>- ФИО
>- Фото
>- Должность
>- Время последнего входа
#### Элементы, которые должны присутствовать на экране:
- Текстовое поле (`id/fullname`), в котором написано имя пользователя.
- Изображение (`id/photo`), на котором отображено фото пользователя.
- Текстовое поле (`id/position`), в котором написана должность пользователя.
- Текстовое поле (`id/lastEntry`), в котором написана дата и время последнего входа пользователя.
- Кнопка (`id/logout`) для выхода пользователя из аккаунта.
- Кнопка (`id/refresh`) для обновления данных.
- Кнопка (`id/scan`) для сканирования QR кода.
- По умолчанию скрытое текстовое поле с ошибкой (`id/error`).
#### Требования к компонентам:
- В случае любой ошибки необходимо скрыть все элементы, кроме текстового поля с ошибкой и кнопки обновления данных.
- Для получения данных необходимо использовать сетевой запрос `/api/<LOGIN>/info`.
- Формат даты и времени последнего входа пользователя: `yyyy-MM-dd HH:mm` (например: 2024-02-31 08:31). Время необходимо отображать с сервера, без поправок на часовой пояс или локальное смещение.
- При нажатии на кнопку выход все данные (если есть) пользователя должны быть очищены, а приложение должно открыть экран авторизации.
- При нажатии кнопки сканирования необходимо открыть экран сканирования QR кода.
- При нажатии на кнопку обновления данных - необходимо повторно вызывать сетевой запрос для получения актуальных данных.
### 3. Экран сканирования QR-кода
> Данный экран позволяет отсканировать код на турникете и войти с помощью смартфона. В данном случае данный экран будет уже написан и представлен dам в готовом виде в заготовке. Вам необходимо только подписаться на его результат с помощью **Result API** и обработать считанные данные из QR кода. **Данный экран нельзя модифицировать. Он поставляется как есть.**
### 4. Экран с результатом сканирования QR кода
> На данном экране необходимо вывести успешность или неуспешность входа с помощью метода QR кода.
#### Элементы, которые должны присутствовать на экране:
- Текстовое поле (`id/result`), где содержится текст об успешности или неуспешности входа.
- Кнопка (`id/close`) для закрытия данного экрана.
#### Требования к компонентам:
- В случае, если результат пришёл пустым или со статусом “Отмена” - необходимо вывести пользователю текст:
*"Вход был отменён/Operation was cancelled"*
- В случае, если данные пришли, то необходимо их отправить на сервер с запросом `api/<LOGIN>/open`, добавив данные из результата и получить ответ.
- Если сервер ответил успешно - то отображаем текст:
*"Успешно/Success"*
- Если сервер ответил любой ошибкой - то отображаем текст:
*"Что-то пошло не так/Something wrong"*
- Кнопка закрытия всегда открывает главный экран.
## 🛠 Решение
Необходимо загрузить свое решение в систему [по ссылке](https://innovationcampus.ru/lms/mod/quiz/view.php?id=2149).
Отметим, что работу необходимо осуществлять в представленных проектах-заготовках (шаблонах).
## ✅ Особенности оценивания
Оценивание происходит с помощью автоматической системы тестирования, которая в автоматическом режиме находит элементы и взаимодействует с ними (именно для этого у каждого элемента указан уникальный идентификатор, по которому будет производится поиск). Каждый тест происходит с чистой установки приложения.
В случае тестирования сервера на него поочередно отправляются команды, описанные в API и ожидаются определенные корректные ответы.
Сервер и приложение тестируются независимо.

View File

@ -1,94 +1,70 @@
plugins {
kotlinAndroid
androidApplication
jetbrainsKotlinSerialization version Version.Kotlin.language
kotlinAnnotationProcessor
id("com.google.dagger.hilt.android").version("2.51.1")
id("org.jetbrains.kotlin.plugin.compose") version "2.0.0"
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
id("org.jetbrains.kotlin.plugin.serialization") version "2.1.20-Beta2"
}
val packageName = "ru.myitschool.work"
android {
namespace = packageName
compileSdk = Version.Android.Sdk.compile
namespace = "com.example.nto_minipigs"
compileSdk = 35
defaultConfig {
applicationId = packageName
minSdk = Version.Android.Sdk.min
targetSdk = Version.Android.Sdk.target
applicationId = "com.example.nto_minipigs"
minSdk = 24
targetSdk = 35
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures.viewBinding = true
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures {
compose = true
}
compileOptions {
sourceCompatibility = Version.Kotlin.javaSource
targetCompatibility = Version.Kotlin.javaSource
}
kotlinOptions {
jvmTarget = Version.Kotlin.jvmTarget
}
}
dependencies {
defaultLibrary()
val nav_version = "2.8.7"
val lifecycle_version = "2.2.0"
implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")
val composeBom = platform("androidx.compose:compose-bom:2024.10.01")
implementation(composeBom)
androidTestImplementation(composeBom)
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui-tooling-preview")
debugImplementation("androidx.compose.ui:ui-tooling")
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-test-manifest")
implementation(Dependencies.AndroidX.activity)
implementation(Dependencies.AndroidX.fragment)
implementation(Dependencies.AndroidX.constraintLayout)
implementation(Dependencies.AndroidX.Navigation.fragment)
implementation(Dependencies.AndroidX.Navigation.navigationUi)
implementation(Dependencies.Retrofit.library)
implementation(Dependencies.Retrofit.gsonConverter)
implementation("com.squareup.picasso:picasso:2.8")
implementation("androidx.navigation:navigation-compose:$nav_version")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
implementation("androidx.datastore:datastore-preferences:1.1.1")
implementation("com.google.mlkit:barcode-scanning:17.3.0")
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
val cameraX = "1.3.4"
implementation("androidx.camera:camera-core:$cameraX")
implementation("androidx.camera:camera-camera2:$cameraX")
implementation("androidx.camera:camera-lifecycle:$cameraX")
implementation("androidx.camera:camera-view:$cameraX")
implementation("androidx.camera:camera-mlkit-vision:1.4.0-rc04")
val retrofitVersion = "2.11.0"
val hilt = "2.51.1"
implementation("com.google.dagger:hilt-android:$hilt")
kapt("com.google.dagger:hilt-android-compiler:$hilt")
implementation("com.squareup.okhttp3:logging-interceptor:4.10.0")
implementation("com.google.code.gson:gson:2.11.0")
}
kapt {
correctErrorTypes = true
}
implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
implementation("com.squareup.retrofit2:converter-gson:$retrofitVersion")
implementation("androidx.datastore:datastore-preferences:1.1.2")
implementation("androidx.datastore:datastore:1.1.2")
}

View File

@ -18,8 +18,4 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-if class androidx.credentials.CredentialManager
-keep class androidx.credentials.playservices.** {
*;
}
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,24 @@
package com.example.nto_minipigs
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.nto_minipigs", appContext.packageName)
}
}

View File

@ -1,27 +1,24 @@
<?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-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:name=".App"
android:allowBackup="true"
android:usesCleartextTraffic="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Default"
android:theme="@style/Theme.Nto_minipigs"
tools:targetApi="31">
<activity
android:name=".ui.RootActivity"
android:exported="true">
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Nto_minipigs">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@ -0,0 +1,23 @@
package com.example.nto_minipigs
import RetrofitClient
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class LoginViewModel :ViewModel() {
private val serviceApi = RetrofitClient.apiService
fun getData(status: String, func: () -> Unit) {
viewModelScope.launch {
try {
val response = serviceApi.auth(status)
Log.d("auth:", response.toString())
func()
} catch (e: Exception) {
Log.d("exception:", e.toString())
}
}
}
}

View File

@ -0,0 +1,69 @@
package com.example.nto_minipigs
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.nto_minipigs.ui.screens.LoginScreen
import com.example.nto_minipigs.ui.theme.Nto_minipigsTheme
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.serialization.Serializable
class MainActivity : ComponentActivity() {
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
val context = applicationContext
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val exampleCounterFlow: Flow<Int> = context.dataStore.data
.map { preferences ->
// No type safety.
preferences[EXAMPLE_COUNTER] ?: 0
}
enableEdgeToEdge()
setContent {
Nto_minipigsTheme {
val navController = rememberNavController()
val loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java]
NavHost(
navController = navController,
startDestination = Login
) {
composable<Login> { LoginScreen( onNavigateToMain = { navController.navigate(route = Main)}, viewModel = loginViewModel ) }
composable<Main> { MainScreen() }
}
}
}
}
}
@Serializable
object Login
@Serializable
object Main
@Composable
fun MainScreen() {
Text("Main")
}

View File

@ -1,11 +1,13 @@
package ru.myitschool.work
package com.example.nto_minipigs.Retrofit
import com.example.nto_minipigs.Retrofit.Models.Data
import okhttp3.ResponseBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.PATCH
import retrofit2.http.Path
interface ApiService {
public interface ApiService {
@GET("/api/{LOGIN}/auth")
suspend fun auth(@Path("LOGIN") login: String): ResponseBody

View File

@ -0,0 +1,5 @@
package com.example.nto_minipigs.Retrofit.Models
data class Data(
val value: String
)

View File

@ -1,4 +1,4 @@
package ru.myitschool.work
package com.example.nto_minipigs.Retrofit.Models
data class User(
val id: Int,

View File

@ -1,7 +1,8 @@
import ru.myitschool.work.core.Constants.SERVER_ADDRESS
import com.example.nto_minipigs.Retrofit.ApiService
import com.example.nto_minipigs.core.Constants.SERVER_ADDRESS
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import ru.myitschool.work.ApiService
import retrofit2.create
object RetrofitClient {
val baseUrl = SERVER_ADDRESS
@ -12,4 +13,5 @@ object RetrofitClient {
.build()
}
val apiService : ApiService = getInstance().create(ApiService::class.java)
}

View File

@ -0,0 +1,7 @@
package com.example.nto_minipigs.core
object Constants {
const val SHARED_PREFS = "shared_prefs"
const val EMAIL_KEY = "email_key"
const val SERVER_ADDRESS = "http://192.168.1.127:5000"
}

View File

@ -0,0 +1,67 @@
package com.example.nto_minipigs.ui.screens
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.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.example.nto_minipigs.ui.theme.Nto_minipigsTheme
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.unit.dp
import com.example.nto_minipigs.LoginViewModel
@Composable
fun LoginScreen( onNavigateToMain: () -> Unit, viewModel: LoginViewModel) {
Nto_minipigsTheme {
Surface {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = "MiniPigs",
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(bottom = 36.dp)
)
var login by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
OutlinedTextField(
value = login,
onValueChange = { login = it },
label = { Text("Login") },
modifier = Modifier.padding(bottom = 4.dp),
shape = RoundedCornerShape(12.dp)
)
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
modifier = Modifier.padding(bottom = 12.dp),
shape = RoundedCornerShape(12.dp),
)
Button(
onClick = { viewModel.getData(login, onNavigateToMain) },
shape = RoundedCornerShape(12.dp),
) {
Text(text = "Sign In", style = MaterialTheme.typography.bodyLarge)
}
}
}
}
}

View File

@ -0,0 +1,66 @@
package com.example.nto_minipigs.ui.screens
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.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.example.nto_minipigs.ui.theme.Nto_minipigsTheme
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.unit.dp
@Composable
fun MainScreen( onNavigateToMain: () -> Unit ) {
Nto_minipigsTheme {
Surface {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = "MiniPigs",
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(bottom = 36.dp)
)
var login by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
OutlinedTextField(
value = login,
onValueChange = { login = it },
label = { Text("Login") },
modifier = Modifier.padding(bottom = 4.dp),
shape = RoundedCornerShape(12.dp)
)
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
modifier = Modifier.padding(bottom = 12.dp),
shape = RoundedCornerShape(12.dp),
)
Button(
onClick = { onNavigateToMain() },
shape = RoundedCornerShape(12.dp),
) {
Text(text = "Sign In", style = MaterialTheme.typography.bodyLarge)
}
}
}
}
}

View File

@ -0,0 +1,11 @@
package com.example.nto_minipigs.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

View File

@ -0,0 +1,58 @@
package com.example.nto_minipigs.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun Nto_minipigsTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@ -0,0 +1,34 @@
package com.example.nto_minipigs.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

View File

@ -1,7 +0,0 @@
package ru.myitschool.work
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class App : Application()

View File

@ -1,5 +0,0 @@
package ru.myitschool.work
data class Data(
val value: String
)

View File

@ -1,5 +0,0 @@
package ru.myitschool.work.core
// БЕРИТЕ И ИЗМЕНЯЙТЕ ХОСТ ТОЛЬКО ЗДЕСЬ И НЕ БЕРИТЕ ИЗ ДРУГИХ МЕСТ. ФАЙЛ ПЕРЕМЕЩАТЬ НЕЛЬЗЯ
object Constants {
const val SERVER_ADDRESS = "http://172.31.254.138:8090"
}

View File

@ -1,6 +0,0 @@
package ru.myitschool.work.core
object env {
const val SHARED_PREFS = "shared_prefs"
const val EMAIL_KEY = "email_key"
}

View File

@ -1,90 +0,0 @@
package ru.myitschool.work.ui
import RetrofitClient
import android.os.Bundle
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.util.Log
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.navigation.NavController
import androidx.navigation.createGraph
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.fragment
import dagger.hilt.android.AndroidEntryPoint
import ru.myitschool.work.R
import ru.myitschool.work.ui.login.LoginDestination
import ru.myitschool.work.ui.login.LoginFragment
import ru.myitschool.work.ui.main.MainDestination
import ru.myitschool.work.ui.main.MainFragment
import ru.myitschool.work.ui.qr.scan.QrScanDestination
import ru.myitschool.work.ui.qr.scan.QrScanFragment
import ru.myitschool.work.core.env.SHARED_PREFS
import ru.myitschool.work.core.env.EMAIL_KEY
import ru.myitschool.work.ui.qr.results.QrResultDestination
import ru.myitschool.work.ui.qr.results.QrResultFragment
// НЕ ИЗМЕНЯЙТЕ НАЗВАНИЕ КЛАССА!
@AndroidEntryPoint
class RootActivity : AppCompatActivity() {
private lateinit var sharedpreferences: SharedPreferences
public lateinit var navController: NavController
private var username: String? = null
var qrData = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedpreferences = getSharedPreferences(SHARED_PREFS, Context.MODE_PRIVATE)
username = sharedpreferences.getString(EMAIL_KEY, null)
setContentView(R.layout.activity_root)
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
navController = navHostFragment?.navController!!
if (navHostFragment != null) {
navController?.graph = navController?.createGraph(
startDestination = if (username != null) MainDestination else LoginDestination
) {
fragment<LoginFragment, LoginDestination>()
fragment<MainFragment, MainDestination>()
fragment<QrScanFragment, QrScanDestination>()
fragment<QrResultFragment, QrResultDestination>()
}!!
// navController.navigate(QrResultDestination)
}
onBackPressedDispatcher.addCallback(
this,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
onSupportNavigateUp()
}
}
)
}
override fun onSupportNavigateUp(): Boolean {
val popBackResult = if (navController.previousBackStackEntry != null) {
navController.popBackStack()
} else {
false
}
return popBackResult || super.onSupportNavigateUp()
}
public fun navigate(a: Int, b: androidx.fragment.app.Fragment) {
supportFragmentManager.beginTransaction()
.replace(a, b)
.commit()
}
}

View File

@ -1,6 +0,0 @@
package ru.myitschool.work.ui.login
import kotlinx.serialization.Serializable
@Serializable
data object LoginDestination

View File

@ -1,91 +0,0 @@
package ru.myitschool.work.ui.login
import RetrofitClient
import RetrofitClient.getInstance
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import ru.myitschool.work.R
import ru.myitschool.work.databinding.FragmentLoginBinding
import ru.myitschool.work.ui.RootActivity
import ru.myitschool.work.ui.main.MainFragment
import ru.myitschool.work.ApiService
import ru.myitschool.work.core.env.EMAIL_KEY
import ru.myitschool.work.core.env.SHARED_PREFS
import ru.myitschool.work.ui.main.MainDestination
import ru.myitschool.work.ui.qr.scan.QrScanDestination
@AndroidEntryPoint
class LoginFragment : Fragment(R.layout.fragment_login) {
private lateinit var sharedpreferences: SharedPreferences
private var _binding: FragmentLoginBinding? = null
private val binding: FragmentLoginBinding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentLoginBinding.bind(view)
binding.login.setOnClickListener(View.OnClickListener {
handleLogin()
})
binding.username.addTextChangedListener {
val name = binding.username.text.toString()
if(name != "" && name.length >= 3 && !isNumericToX(name[0].toString()) && name.matches(Regex("^[A-Za-z0-9]+\$"))) {
binding.login.isEnabled = true
binding.login.isClickable = true
} else {
binding.login.isEnabled = false
binding.login.isClickable = false
}
}
}
fun isNumericToX(toCheck: String): Boolean {
return toCheck.toDoubleOrNull() != null
}
private fun handleLogin() {
binding.loading.visibility = View.VISIBLE
binding.fields.visibility = View.GONE
sharedpreferences = activity?.getSharedPreferences(SHARED_PREFS, Context.MODE_PRIVATE)!!
MainScope().launch {
try {
val apiInterface = getInstance().create(ApiService::class.java)
val result = apiInterface.auth(binding.username.text.toString())
with(sharedpreferences.edit()) {
putString(EMAIL_KEY, binding.username.text.toString())
apply()
}
(activity as RootActivity).navController?.popBackStack()
(activity as RootActivity).navController?.navigate(MainDestination)
} catch (e: Exception) {
binding.loading.visibility = View.GONE
binding.fields.visibility = View.VISIBLE
binding.error.visibility = View.VISIBLE
binding.error.text = e.toString()
}
}
}
override fun onDestroyView() {
_binding = null
super.onDestroyView()
}
}

View File

@ -1,17 +0,0 @@
package ru.myitschool.work.ui.login
import android.content.Context
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
@HiltViewModel
class LoginViewModel @Inject constructor(
@ApplicationContext private val context: Context,
) : ViewModel() {
private val _state = MutableStateFlow(true)
val state = _state.asStateFlow()
}

View File

@ -1,6 +0,0 @@
package ru.myitschool.work.ui.main
import kotlinx.serialization.Serializable
@Serializable
data object MainDestination

View File

@ -1,112 +0,0 @@
package ru.myitschool.work.ui.main
import RetrofitClient.getInstance
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.ImageView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResultListener
import androidx.lifecycle.lifecycleScope
import com.google.gson.Gson
import com.squareup.picasso.Picasso
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.Response
import ru.myitschool.work.ApiService
import ru.myitschool.work.R
import ru.myitschool.work.User
import ru.myitschool.work.core.env.EMAIL_KEY
import ru.myitschool.work.core.env.SHARED_PREFS
import ru.myitschool.work.databinding.FragmentMainBinding
import ru.myitschool.work.ui.RootActivity
import ru.myitschool.work.ui.login.LoginDestination
import ru.myitschool.work.ui.login.LoginFragment
import ru.myitschool.work.ui.qr.results.QrResultDestination
import ru.myitschool.work.ui.qr.scan.QrScanDestination
import ru.myitschool.work.ui.qr.scan.QrScanFragment
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
@AndroidEntryPoint
class MainFragment: Fragment(R.layout.fragment_main) {
private var _binding: FragmentMainBinding? = null
private val binding: FragmentMainBinding get() = _binding!!
private lateinit var sharedpreferences: SharedPreferences
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentMainBinding.bind(view)
sharedpreferences = activity?.getSharedPreferences(SHARED_PREFS, Context.MODE_PRIVATE)!!
refresh()
binding.logout.setOnClickListener(View.OnClickListener {
with(sharedpreferences.edit()) {
clear()
apply()
}
(activity as RootActivity).navController?.popBackStack()
(activity as RootActivity).navController?.navigate(LoginDestination)
})
binding.scan.setOnClickListener(View.OnClickListener {
(activity as RootActivity).navController?.navigate(QrScanDestination)
})
binding.refresh.setOnClickListener(View.OnClickListener {
refresh()
})
setFragmentResultListener(QrScanDestination.REQUEST_KEY) { requestKey, bundle ->
val result = bundle.getString("key_qr")
if(result != null) {
(activity as RootActivity).qrData = result.toString()
(activity as RootActivity).navController?.navigate(QrResultDestination)
}
}
}
fun refresh() {
val username = sharedpreferences.getString(EMAIL_KEY, null)
val gson = Gson()
MainScope().launch {
val task = async {
try {
val apiInterface = getInstance().create(ApiService::class.java)
val response = apiInterface.info(""+username)
return@async response
} catch (e: Exception) {
binding.innerFields.visibility = View.GONE
binding.error.visibility = View.VISIBLE
binding.error.text = e.toString()
return@async null
}
}
val info = gson.fromJson(task.await()?.string(), User::class.java)
if(info != null) {
withContext(Dispatchers.Main) {
binding.error.visibility = View.GONE
binding.innerFields.visibility = View.VISIBLE
binding.fullname.text = info.name
binding.position.text = info.position
val time = LocalDateTime.parse(info.lastVisit).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
binding.lastEntry.text = time.toString()
val imageView: ImageView = binding.photo
Picasso.get().load(info.photo).into(imageView)
}
}
}
}
}

View File

@ -1,17 +0,0 @@
package ru.myitschool.work.ui.main
import android.content.Context
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
@HiltViewModel
class MainViewModel @Inject constructor(
@ApplicationContext private val context: Context,
) : ViewModel() {
private val _state = MutableStateFlow(true)
val state = _state.asStateFlow()
}

View File

@ -1,6 +0,0 @@
package ru.myitschool.work.ui.qr.results
import kotlinx.serialization.Serializable
@Serializable
object QrResultDestination {
}

View File

@ -1,63 +0,0 @@
package ru.myitschool.work.ui.qr.results
import RetrofitClient.getInstance
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import ru.myitschool.work.ApiService
import ru.myitschool.work.Data
import ru.myitschool.work.R
import ru.myitschool.work.core.env.EMAIL_KEY
import ru.myitschool.work.core.env.SHARED_PREFS
import ru.myitschool.work.databinding.FragmentMainBinding
import ru.myitschool.work.databinding.FragmentQrResultBinding
import ru.myitschool.work.databinding.FragmentQrScanBinding
import ru.myitschool.work.ui.RootActivity
import ru.myitschool.work.ui.login.LoginDestination
import ru.myitschool.work.ui.main.MainDestination
import ru.myitschool.work.ui.qr.scan.QrScanDestination
class QrResultFragment: Fragment(R.layout.fragment_qr_result) {
private var _binding: FragmentQrResultBinding? = null
private lateinit var sharedpreferences: SharedPreferences
private val binding: FragmentQrResultBinding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentQrResultBinding.bind(view)
sharedpreferences = activity?.getSharedPreferences(SHARED_PREFS, Context.MODE_PRIVATE)!!
val username = sharedpreferences.getString(EMAIL_KEY, null)
val data = (activity as RootActivity).qrData
if(data == "" || data == "Отмена") {
binding.result.text = resources.getString(R.string.canceled)
} else {
MainScope().launch {
try {
val apiInterface = getInstance().create(ApiService::class.java)
val req: Data = Data(data)
val result = apiInterface.open(req, ""+username)
binding.result.text = resources.getString(R.string.success)
} catch (e: Exception) {
binding.result.text = resources.getString(R.string.smthwr)
}
}
}
(activity as RootActivity).qrData = "null"
binding.close.setOnClickListener(View.OnClickListener {
goBack()
})
}
private fun goBack() {
(activity as RootActivity).navController?.popBackStack()
}
}

View File

@ -1,17 +0,0 @@
package ru.myitschool.work.ui.main
import android.content.Context
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
@HiltViewModel
class QrResultViewModel @Inject constructor(
@ApplicationContext private val context: Context,
) : ViewModel() {
private val _state = MutableStateFlow(true)
val state = _state.asStateFlow()
}

View File

@ -1,30 +0,0 @@
package ru.myitschool.work.ui.qr.scan
import android.os.Bundle
import androidx.core.os.bundleOf
import kotlinx.serialization.Serializable
// НЕ ИЗМЕНЯЙТЕ ЭТОТ ФАЙЛ. В ТЕСТАХ ОН БУДЕМ ВОЗВРАЩЁН В ИСХОДНОЕ СОСТОЯНИЕ
@Serializable
data object QrScanDestination {
const val REQUEST_KEY = "qr_result"
private const val KEY_QR_DATA = "key_qr"
fun newInstance(): QrScanFragment {
return QrScanFragment()
}
fun getDataIfExist(bundle: Bundle): String? {
return if (bundle.containsKey(KEY_QR_DATA)) {
bundle.getString(KEY_QR_DATA)
} else {
null
}
}
internal fun packToBundle(data: String): Bundle {
return bundleOf(
KEY_QR_DATA to data
)
}
}

View File

@ -1,140 +0,0 @@
package ru.myitschool.work.ui.qr.scan
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.camera.core.ImageAnalysis
import androidx.camera.mlkit.vision.MlKitAnalyzer
import androidx.camera.view.LifecycleCameraController
import androidx.camera.view.PreviewView
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResult
import androidx.fragment.app.viewModels
import androidx.navigation.NavController
import androidx.navigation.fragment.findNavController
import com.google.mlkit.vision.barcode.BarcodeScanner
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
import ru.myitschool.work.R
import ru.myitschool.work.databinding.FragmentQrScanBinding
import ru.myitschool.work.utils.collectWhenStarted
import ru.myitschool.work.utils.visibleOrGone
// НЕ ИЗМЕНЯЙТЕ ЭТОТ ФАЙЛ. В ТЕСТАХ ОН БУДЕМ ВОЗВРАЩЁН В ИСХОДНОЕ СОСТОЯНИЕ
class QrScanFragment : Fragment(R.layout.fragment_qr_scan) {
private var _binding: FragmentQrScanBinding? = null
private val binding: FragmentQrScanBinding get() = _binding!!
private var barcodeScanner: BarcodeScanner? = null
private var isCameraInit: Boolean = false
private val permissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted -> viewModel.onPermissionResult(isGranted) }
private val viewModel: QrScanViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentQrScanBinding.bind(view)
sendResult(bundleOf())
subscribe()
initCallback()
}
private fun initCallback() {
binding.close.setOnClickListener { viewModel.close() }
}
private fun subscribe() {
viewModel.state.collectWhenStarted(this) { state ->
binding.loading.visibleOrGone(state is QrScanViewModel.State.Loading)
binding.viewFinder.visibleOrGone(state is QrScanViewModel.State.Scan)
if (!isCameraInit && state is QrScanViewModel.State.Scan) {
startCamera()
isCameraInit = true
}
}
viewModel.action.collectWhenStarted(this) { action ->
when (action) {
is QrScanViewModel.Action.RequestPermission -> requestPermission(action.permission)
is QrScanViewModel.Action.CloseWithCancel -> {
goBack()
}
is QrScanViewModel.Action.CloseWithResult -> {
sendResult(QrScanDestination.packToBundle(action.result))
goBack()
}
}
}
}
private fun requestPermission(permission: String) {
permissionLauncher.launch(permission)
}
private fun startCamera() {
val context = requireContext()
val cameraController = LifecycleCameraController(context)
val previewView: PreviewView = binding.viewFinder
val executor = ContextCompat.getMainExecutor(context)
val options = BarcodeScannerOptions.Builder()
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
.build()
val barcodeScanner = BarcodeScanning.getClient(options)
this.barcodeScanner = barcodeScanner
cameraController.setImageAnalysisAnalyzer(
executor,
MlKitAnalyzer(
listOf(barcodeScanner),
ImageAnalysis.COORDINATE_SYSTEM_VIEW_REFERENCED,
executor
) { result ->
result?.getValue(barcodeScanner)?.firstOrNull()?.let { value ->
viewModel.findBarcode(value)
}
}
)
cameraController.bindToLifecycle(this)
previewView.controller = cameraController
}
override fun onDestroyView() {
barcodeScanner?.close()
barcodeScanner = null
_binding = null
super.onDestroyView()
}
private fun goBack() {
findNavControllerOrNull()?.popBackStack()
?: requireActivity().onBackPressedDispatcher.onBackPressed()
}
private fun sendResult(bundle: Bundle) {
setFragmentResult(
QrScanDestination.REQUEST_KEY,
bundle
)
findNavControllerOrNull()
?.previousBackStackEntry
?.savedStateHandle
?.set(QrScanDestination.REQUEST_KEY, bundle)
}
private fun findNavControllerOrNull(): NavController? {
return try {
findNavController()
} catch (_: Throwable) {
null
}
}
}

View File

@ -1,93 +0,0 @@
package ru.myitschool.work.ui.qr.scan
import android.Manifest
import android.app.Application
import android.content.pm.PackageManager
import androidx.core.content.ContextCompat
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.google.mlkit.vision.barcode.common.Barcode
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import ru.myitschool.work.utils.MutablePublishFlow
// НЕ ИЗМЕНЯЙТЕ ЭТОТ ФАЙЛ. В ТЕСТАХ ОН БУДЕМ ВОЗВРАЩЁН В ИСХОДНОЕ СОСТОЯНИЕ
class QrScanViewModel(
application: Application
) : AndroidViewModel(application) {
private val _action = MutablePublishFlow<Action>()
val action = _action.asSharedFlow()
private val _state = MutableStateFlow<State>(initialState)
val state = _state.asStateFlow()
init {
checkPermission()
}
fun onPermissionResult(isGranted: Boolean) {
viewModelScope.launch {
if (isGranted) {
_state.update { State.Scan }
} else {
_action.emit(Action.CloseWithCancel)
}
}
}
private fun checkPermission() {
viewModelScope.launch {
val isPermissionGranted = ContextCompat.checkSelfPermission(
getApplication(),
CAMERA_PERMISSION
) == PackageManager.PERMISSION_GRANTED
if (isPermissionGranted) {
_state.update { State.Scan }
} else {
delay(1000)
_action.emit(Action.RequestPermission(CAMERA_PERMISSION))
}
}
}
fun findBarcode(barcode: Barcode) {
viewModelScope.launch {
barcode.rawValue?.let { value ->
_action.emit(Action.CloseWithResult(value))
}
}
}
fun close() {
viewModelScope.launch {
_action.emit(Action.CloseWithCancel)
}
}
sealed interface State {
data object Loading : State
data object Scan : State
}
sealed interface Action {
data class RequestPermission(
val permission: String
) : Action
data object CloseWithCancel : Action
data class CloseWithResult(
val result: String
) : Action
}
private companion object {
val initialState = State.Loading
const val CAMERA_PERMISSION = Manifest.permission.CAMERA
}
}

View File

@ -1,10 +0,0 @@
package ru.myitschool.work.utils
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
fun <T> MutablePublishFlow() = MutableSharedFlow<T>(
replay = 0,
extraBufferCapacity = 1,
BufferOverflow.DROP_OLDEST
)

View File

@ -1,18 +0,0 @@
package ru.myitschool.work.utils
import androidx.fragment.app.Fragment
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
inline fun <T> Flow<T>.collectWhenStarted(
fragment: Fragment,
crossinline collector: (T) -> Unit
) {
fragment.viewLifecycleOwner.lifecycleScope.launch {
flowWithLifecycle(fragment.viewLifecycleOwner.lifecycle).collect { value ->
collector.invoke(value)
}
}
}

View File

@ -1,12 +0,0 @@
package ru.myitschool.work.utils
import android.text.Editable
import android.text.TextWatcher
open class TextChangedListener: TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
override fun afterTextChanged(s: Editable?) = Unit
}

View File

@ -1,7 +0,0 @@
package ru.myitschool.work.utils
import android.view.View
fun View.visibleOrGone(isVisible: Boolean) {
this.visibility = if (isVisible) View.VISIBLE else View.GONE
}

View File

@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M17,7l-1.41,1.41L18.17,11H8v2h10.17l-2.58,2.58L17,17l5,-5zM4,5h8V3H4c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h8v-2H4V5z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M21.9,21.9l-8.49,-8.49l0,0L3.59,3.59l0,0L2.1,2.1L0.69,3.51L3,5.83V19c0,1.1 0.9,2 2,2h13.17l2.31,2.31L21.9,21.9zM5,18l3.5,-4.5l2.5,3.01L12.17,15l3,3H5zM21,18.17L5.83,3H19c1.1,0 2,0.9 2,2V18.17z"/>
</vector>

View File

@ -1,25 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M3,11h8V3H3V11zM5,5h4v4H5V5z"/>
<path android:fillColor="@android:color/white" android:pathData="M3,21h8v-8H3V21zM5,15h4v4H5V15z"/>
<path android:fillColor="@android:color/white" android:pathData="M13,3v8h8V3H13zM19,9h-4V5h4V9z"/>
<path android:fillColor="@android:color/white" android:pathData="M19,19h2v2h-2z"/>
<path android:fillColor="@android:color/white" android:pathData="M13,13h2v2h-2z"/>
<path android:fillColor="@android:color/white" android:pathData="M15,15h2v2h-2z"/>
<path android:fillColor="@android:color/white" android:pathData="M13,17h2v2h-2z"/>
<path android:fillColor="@android:color/white" android:pathData="M15,19h2v2h-2z"/>
<path android:fillColor="@android:color/white" android:pathData="M17,17h2v2h-2z"/>
<path android:fillColor="@android:color/white" android:pathData="M17,13h2v2h-2z"/>
<path android:fillColor="@android:color/white" android:pathData="M19,15h2v2h-2z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
</vector>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="14dp"/>
</shape>

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
/>
</FrameLayout>

View File

@ -1,57 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="50dp">
<ProgressBar
android:id="@+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/fields"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:gravity="center">
<TextView
android:id="@+id/error"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:visibility="gone" />
<EditText
android:inputType="textEmailAddress"
android:autofillHints="emailAddress"
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:hint="@string/enter_email" />
<Button
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/log_in"
android:enabled="false"
android:clickable="false"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,89 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="30dp">
<RelativeLayout
android:id="@+id/fields"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"
tools:layout_editor_absoluteX="30dp"
tools:layout_editor_absoluteY="30dp">
<RelativeLayout
android:id="@+id/inner_fields"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="visible">
<ImageView
android:id="@+id/photo"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_marginBottom="20dp" />
<TextView
android:id="@+id/fullname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/photo"
android:layout_marginBottom="20dp"
android:textSize="20dp" />
<TextView
android:id="@+id/position"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/fullname"
android:layout_marginBottom="20dp"
android:textSize="20dp" />
<TextView
android:id="@+id/lastEntry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/position"
android:layout_marginBottom="20dp"
android:textSize="20dp" />
<Button
android:id="@+id/logout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/lastEntry"
android:text="@string/log_out" />
<Button
android:id="@+id/scan"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/lastEntry"
android:layout_marginLeft="10dp"
android:layout_toEndOf="@+id/logout"
android:text="@string/scan" />
</RelativeLayout>
<Button
android:id="@+id/refresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/inner_fields"
android:text="@string/refresh"
android:visibility="visible" />
<TextView
android:id="@+id/error"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/refresh"
android:visibility="gone" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/result"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/result"
android:text="@string/close_button" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.camera.view.PreviewView
android:id="@+id/viewFinder"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ProgressBar
android:id="@+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:contentDescription="@string/close_button"
android:src="@drawable/ic_close"
app:elevation="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,11 +0,0 @@
<resources>
<string name="app_name">NTO Pass</string>
<string name="log_in">Войти</string>
<string name="log_out">Выйти</string>
<string name="refresh">Обновить</string>
<string name="scan">QR-Скан</string>
<string name="enter_email">Введите ваш логин</string>
<string name="smthwr">Что-то пошло не так</string>
<string name="success">Успешно</string>
<string name="canceled">Вход был отменён</string>
</resources>

View File

@ -1,5 +0,0 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="fragment_horizontal_margin">16dp</dimen>
<dimen name="fragment_vertical_margin">16dp</dimen>
</resources>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="rootContainer" type="id"/>
</resources>

View File

@ -1,11 +1,3 @@
<resources>
<string name="app_name">NTO Pass</string>
<string name="log_in">Log in</string>
<string name="log_out">Log out</string>
<string name="refresh">Refresh</string>
<string name="scan">Scan</string>
<string name="enter_email">Enter your login</string>
<string name="smthwr">Something wrong</string>
<string name="success">Успешно</string>
<string name="canceled">Operation was cancelled</string>
<string name="app_name">nto_minipigs</string>
</resources>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="close_button">Close</string>
</resources>

View File

@ -1,16 +1,5 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Default" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Nto_minipigs" parent="android:Theme.Material.Light.NoActionBar" />
</resources>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true"/>
</network-security-config>

View File

@ -0,0 +1,17 @@
package com.example.nto_minipigs
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

View File

@ -1,7 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
androidApplication version Version.agp apply false
kotlinJvm version Version.Kotlin.language apply false
kotlinAnnotationProcessor version Version.Kotlin.language apply false
id("com.google.dagger.hilt.android") version "2.51.1" apply false
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
}

2
buildSrc/.gitignore vendored
View File

@ -1,2 +0,0 @@
/.gradle
/build

View File

@ -1,8 +0,0 @@
plugins {
`kotlin-dsl`
}
repositories {
google()
mavenCentral()
}

View File

@ -1,220 +0,0 @@
data class Dependency(
val name: String,
val version: String,
) {
val fullPath get() = "$name:$version"
}
object Dependencies {
/**
* Type-safe HTTP client for Android and Java by Square, Inc.
*
* [Documentation](http://square.github.io/retrofit/)
*
* [Github](https://github.com/square/retrofit)
*
* [Apache License 2.0](https://github.com/square/retrofit/blob/master/LICENSE.txt)
*
* [Changelog](https://github.com/square/retrofit/blob/master/CHANGELOG.md)
*/
object Retrofit {
private const val version = "2.9.0"
val library = Dependency("com.squareup.retrofit2:retrofit", version)
val gsonConverter = Dependency("com.squareup.retrofit2:converter-gson", version)
}
/**
* [Documentation](https://developer.android.com/jetpack/androidx)
*
* [Releases](https://developer.android.com/jetpack/androidx/versions).
*/
object AndroidX {
/**
* [Changelog](https://developer.android.com/jetpack/androidx/releases/core)
*/
val core = Dependency("androidx.core:core-ktx", "1.13.1")
/**
* [Changelog](https://developer.android.com/jetpack/androidx/releases/appcompat)
*/
val appcompat = Dependency("androidx.appcompat:appcompat", "1.7.0")
/**
* [Changelog](https://developer.android.com/jetpack/androidx/releases/activity)
*/
val activity = Dependency("androidx.activity:activity", "1.9.3")
/**
* [Changelog](https://developer.android.com/jetpack/androidx/releases/fragment)
*/
val fragment = Dependency("androidx.fragment:fragment-ktx", "1.8.4")
/**
* [Changelog](https://developer.android.com/jetpack/androidx/releases/recyclerview)
*/
val recyclerView = Dependency("androidx.recyclerview:recyclerview", "1.3.2")
/**
* [Changelog](https://developer.android.com/jetpack/androidx/releases/cardview)
*/
val cardView = Dependency("androidx.cardview:cardview", "1.0.0")
/**
* [Changelog](https://developer.android.com/jetpack/androidx/releases/gridlayout)
*/
val gridLayout = Dependency("androidx.gridlayout:gridlayout", "1.0.0")
/**
* A ConstraintLayout is a ViewGroup which allows you to position and size widgets in a flexible way.
*
* [Documentation](https://developer.android.com/reference/android/support/constraint/ConstraintLayout)
*
* [Changelog](https://developer.android.com/jetpack/androidx/releases/constraintlayout)
*/
val constraintLayout = Dependency("androidx.constraintlayout:constraintlayout", "2.1.4")
/**
* CoordinatorLayout is a super-powered FrameLayout.
* CoordinatorLayout is intended for two primary use cases:
* 1. As a top-level application decor or chrome layout
* 2. As a container for a specific interaction with one or more child views
*
* [Documentation](https://developer.android.com/jetpack/androidx/releases/coordinatorlayout)
*
* [Changelog](https://developer.android.com/jetpack/androidx/releases/coordinatorlayout)
*/
val coordinatorLayout = Dependency("androidx.coordinatorlayout:coordinatorlayout", "1.2.0")
/**
* The SwipeRefreshLayout should be used whenever the user
* can refresh the contents of a view via a vertical swipe gesture.
*
* [Documentation](https://developer.android.com/jetpack/androidx/releases/swiperefreshlayout)
*
* [Changelog](https://developer.android.com/jetpack/androidx/releases/swiperefreshlayout)
*/
val swipeRefreshLayout = Dependency("androidx.swiperefreshlayout:swiperefreshlayout", "1.1.0")
/**
* [Changelog](https://developer.android.com/jetpack/androidx/releases/test/)
*/
object Testing {
/**
* [Documentation](https://developer.android.com/training/testing)
*/
val core = Dependency("androidx.test:core", "1.5.0")
/**
* [Documentation](https://developer.android.com/training/testing)
*/
val junit = Dependency("androidx.test.ext:junit-ktx", "1.1.5")
/**
* [Documentation](https://developer.android.com/training/testing/espresso)
*/
object Espresso {
private const val version = "3.5.1"
val core = Dependency("androidx.test.espresso:espresso-core", version)
val intents = Dependency("androidx.test.espresso:espresso-intents", version)
val contrib = Dependency("androidx.test.espresso:espresso-contrib", version)
}
/**
* [Documentation](https://developer.android.com/training/testing/junit-runner)
*/
val runner = Dependency("androidx.test:runner", "1.5.2")
/**
* [Documentation](https://developer.android.com/training/testing/junit-rules)
*/
val rules = Dependency("androidx.test:rules", "1.5.0")
/**
* [Documentation](https://developer.android.com/training/testing/junit-rules)
*/
val compose = Dependency("androidx.compose.ui:ui-test-junit4", "1.6.0")
/**
* [Documentation](https://developer.android.com/training/testing/junit-runner#using-android-test-orchestrator)
*/
val orchestrator = Dependency("androidx.test:orchestrator", "1.4.2")
/**
* [Documentation](https://developer.android.com/training/testing/ui-automator)
*/
val uiAutomator = Dependency("androidx.test.uiautomator:uiautomator", "2.2.0")
}
/**
* [Documentation](https://material.io/develop/android/)
*
* [Github](https://github.com/material-components/material-components-android)
*
* [Changelog](https://github.com/material-components/material-components-android/releases)
*/
val materialDesign = Dependency("com.google.android.material:material", "1.11.0")
object Lifecycle {
private const val version = "2.6.1"
val viewModel = Dependency("androidx.lifecycle:lifecycle-viewmodel-ktx", version)
val common = Dependency("androidx.lifecycle:lifecycle-common", version)
}
object Navigation {
private const val version = "2.8.3"
val fragment = Dependency("androidx.navigation:navigation-fragment-ktx", version)
val navigationUi = Dependency("androidx.navigation:navigation-ui-ktx", version)
}
}
/**
* JUnit is a simple framework to write repeatable tests.
*
* [Documentation](https://junit.org/junit4/)
*
* [Github](https://github.com/junit-team/junit4)
*
* [Eclipse Public License 1.0](https://github.com/junit-team/junit4/blob/master/LICENSE-junit.txt)
*
* [Changelog](https://github.com/junit-team/junit4/wiki)
*/
val junit = Dependency("junit:junit", "4.13")
/**
* Truth makes your test assertions and failure messages more readable.
* Similar to AssertJ, it natively supports many JDK and Guava types,
* and it is extensible to others.
*
* [Documentation](https://truth.dev/)
*
* [Github](https://github.com/google/truth)
*
* [Apache License 2.0](https://github.com/google/truth/blob/master/LICENSE)
*
* [Changelog](https://github.com/google/truth/releases)
*/
val truth = Dependency("com.google.truth:truth", "1.3.0")
/**
* Kaspresso is a framework for Android UI testing. Based on Espresso and UI Automator.
*
* [Documentation](https://kasperskylab.github.io/Kaspresso/)
*
* [Github](https://github.com/KasperskyLab/Kaspresso)
*
* [Apache License 2.0](https://github.com/KasperskyLab/Kaspresso/blob/master/LICENSE.txt)
*
* [Changelog](https://github.com/KasperskyLab/Kaspresso/releases)
*/
object Kaspresso {
private const val version = "1.5.3"
val core = Dependency("com.kaspersky.android-components:kaspresso", version)
val composeSupport = Dependency("com.kaspersky.android-components:kaspresso-compose-support", version)
}
}

View File

@ -1,40 +0,0 @@
import org.gradle.api.artifacts.dsl.DependencyHandler
fun DependencyHandler.implementation(dependency: Dependency) {
add(Type.IMPLEMENTATION, dependency.fullPath)
}
fun DependencyHandler.testImplementation(dependency: Dependency) {
add(Type.TEST_IMPLEMENTATION, dependency.fullPath)
}
fun DependencyHandler.androidTestImplementation(dependency: Dependency) {
add(Type.ANDROID_TEST_IMPLEMENTATION, dependency.fullPath)
}
fun DependencyHandler.api(dependency: Dependency) {
add(Type.API, dependency.fullPath)
}
fun DependencyHandler.kapt(dependency: Dependency) {
add(Type.KAPT, dependency.fullPath)
}
fun DependencyHandler.ksp(dependency: Dependency) {
add(Type.KSP, dependency.fullPath)
}
fun DependencyHandler.defaultLibrary() {
api(Dependencies.AndroidX.core)
api(Dependencies.AndroidX.appcompat)
api(Dependencies.AndroidX.materialDesign)
}
private object Type {
const val IMPLEMENTATION = "implementation"
const val TEST_IMPLEMENTATION = "testImplementation"
const val ANDROID_TEST_IMPLEMENTATION = "androidTestImplementation"
const val API = "api"
const val KAPT = "kapt"
const val KSP = "ksp"
}

View File

@ -1,67 +0,0 @@
import org.gradle.kotlin.dsl.version
import org.gradle.plugin.use.PluginDependenciesSpec
import org.gradle.plugin.use.PluginDependencySpec
val PluginDependenciesSpec.androidApplication: PluginDependencySpec
get() = id(Plugin.Id.Android.application)
val PluginDependenciesSpec.androidLibrary: PluginDependencySpec
get() = id(Plugin.Id.Android.library)
val PluginDependenciesSpec.kotlinJvm: PluginDependencySpec
get() = id(Plugin.Id.Kotlin.jvm)
val PluginDependenciesSpec.kotlinAndroid: PluginDependencySpec
get() = id(Plugin.Id.Kotlin.android)
val PluginDependenciesSpec.kotlinParcelize: PluginDependencySpec
get() = id(Plugin.Id.Kotlin.parcelize)
val PluginDependenciesSpec.kotlinAnnotationProcessor: PluginDependencySpec
get() = id(Plugin.Id.Kotlin.annotationProcessor)
val PluginDependenciesSpec.kotlinSerialization: PluginDependencySpec
get() = id(Plugin.Id.Kotlin.serialization)
val PluginDependenciesSpec.jetbrainsKotlinSerialization: PluginDependencySpec
get() = id(Plugin.Id.JetBrains.serialization)
object Plugin {
object Id {
object Android {
/**
* [Documentation](https://google.github.io/android-gradle-dsl/current/)
* [Changelog](https://developer.android.com/studio/releases/gradle-plugin)
*/
const val application = "com.android.application"
/**
* [Documentation](https://google.github.io/android-gradle-dsl/current/)
* [Changelog](https://developer.android.com/studio/releases/gradle-plugin)
*/
const val library = "com.android.library"
}
object Kotlin {
/**
* Plugin published in https://plugins.gradle.org/
*/
const val jvm = "org.jetbrains.kotlin.jvm"
/**
* Plugin published in https://plugins.gradle.org/
*/
const val android = "org.jetbrains.kotlin.android"
/**
* Plugin published in https://plugins.gradle.org/
*/
const val parcelize = "kotlin-parcelize"
/**
* Plugin published in https://plugins.gradle.org/
*/
const val annotationProcessor = "org.jetbrains.kotlin.kapt"
/**
* Plugin published in https://plugins.gradle.org/
*/
const val serialization = "plugin.serialization"
}
object JetBrains {
const val serialization = "org.jetbrains.kotlin.plugin.serialization"
}
}
}

View File

@ -1,42 +0,0 @@
import org.gradle.api.JavaVersion
object Version {
/**
* Gradle is an open-source build automation tool focused on flexibility and performance.
*
* [Documentation](https://docs.gradle.org/current/userguide/userguide.html)
*
* [Github](https://github.com/gradle/gradle)
*
* [Apache 2.0 License](https://github.com/gradle/gradle/blob/master/LICENSE)
*
* [Changelog](https://gradle.org/releases/)
*/
const val agp = "8.7.1"
object Kotlin {
/**
* [Documentation](https://kotlinlang.org/)
*
* [Source Code](https://github.com/JetBrains/kotlin/)
*
* [Apache 2.0 License](https://github.com/JetBrains/kotlin/blob/master/license/LICENSE.txt)
*
* [Changelog](https://kotlinlang.org/releases.html)
*/
const val language = "2.0.21"
val javaSource = JavaVersion.VERSION_11
const val jvmTarget = "11"
}
object Android {
object Sdk {
const val min = 24
const val compile = 34
const val target = 34
}
}
}

View File

@ -8,13 +8,15 @@
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library

View File

@ -1,11 +0,0 @@
# Файл для управления дистрибутивом Gradle
Данный репозиторий необходим для поддержки актуальности дистрибутива Gradle в проектах. Данный проект необходимо подключать подмодулём и использовать во всех проектах.
## Как обвновлять дистрибутив
Перед обновлением необходимо удостоверится, что версия самого Gradle установлена не ниже, чем в дистрибутиве. Сделать это можно [здесь](https://sicampus.ru/gitea/core/dependecies/src/branch/main/src/main/java/Version.kt#L16).
Процесс обновления выглядит следуюющим образом:
1. В начале обновляем саму версию Gradle ([в этом репозитории](https://sicampus.ru/gitea/core/dependecies)).
2. Обновлем ссылку на дестрибутив и проверяем совместимость.

32
gradle/libs.versions.toml Normal file
View File

@ -0,0 +1,32 @@
[versions]
agp = "8.8.0"
kotlin = "2.0.0"
coreKtx = "1.15.0"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.10.0"
composeBom = "2024.04.01"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

View File

@ -1,6 +1,6 @@
#Thu Jan 04 22:32:26 NOVT 2024
#Tue Feb 18 19:43:36 MSK 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

257
gradlew vendored
View File

@ -1,7 +1,7 @@
#!/bin/sh
#!/usr/bin/env sh
#
# Copyright © 2015-2021 the original authors.
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -17,101 +17,67 @@
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
MAX_FD="maximum"
warn () {
echo "$*"
} >&2
}
die () {
echo
echo "$*"
echo
exit 1
} >&2
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@ -121,9 +87,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD=$JAVA_HOME/bin/java
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -132,7 +98,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@ -140,95 +106,80 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=$( cygpath --unix "$JAVACMD" )
JAVACMD=`cygpath --unix "$JAVACMD"`
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

View File

@ -1,6 +1,12 @@
pluginManagement {
repositories {
google()
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
@ -13,5 +19,6 @@ dependencyResolutionManagement {
}
}
rootProject.name = "Work"
rootProject.name = "nto_minipigs"
include(":app")