From 67dfbaa59ca94196f39cfbaf2a34c1b5a8823ae6 Mon Sep 17 00:00:00 2001 From: yastruckov Date: Tue, 18 Feb 2025 17:47:55 +0300 Subject: [PATCH] Initial commit --- .gitea/workflows/automerge-with-core.yaml | 26 ++ .gitignore | 10 + .gitmodules | 6 + .kotlin/errors/errors-1732305455885.log | 82 ++++++ .kotlin/errors/errors-1732467098950.log | 82 ++++++ .kotlin/errors/errors-1732726114629.log | 82 ++++++ .kotlin/errors/errors-1732729015076.log | 82 ++++++ README.md | 121 +++++++++ app/.gitignore | 1 + app/build.gradle.kts | 76 ++++++ app/proguard-rules.pro | 21 ++ app/src/main/AndroidManifest.xml | 38 +++ app/src/main/java/ru/myitschool/work/App.kt | 7 + .../java/ru/myitschool/work/core/Constants.kt | 5 + .../work/data/UserDataStoreManager.kt | 33 +++ .../work/data/door/DoorNetworkDataSource.kt | 39 +++ .../myitschool/work/data/door/DoorRepoImpl.kt | 12 + .../work/data/dto/OpenRequestDTO.kt | 9 + .../ru/myitschool/work/data/dto/UserDTO.kt | 15 ++ .../work/data/info/InfoNetworkDataSource.kt | 34 +++ .../myitschool/work/data/info/InfoRepoImpl.kt | 19 ++ .../work/data/login/LoginNetworkDataSource.kt | 26 ++ .../work/data/login/LoginRepoImpl.kt | 11 + .../myitschool/work/domain/door/DoorRepo.kt | 7 + .../work/domain/door/OpenDoorUseCase.kt | 11 + .../work/domain/entities/OpenEntity.kt | 13 + .../work/domain/entities/UserEntity.kt | 8 + .../work/domain/info/GetInfoUseCase.kt | 7 + .../myitschool/work/domain/info/InfoRepo.kt | 7 + .../myitschool/work/domain/login/LoginRepo.kt | 5 + .../work/domain/login/LoginUseCase.kt | 7 + .../ru/myitschool/work/ui/RootActivity.kt | 27 ++ .../myitschool/work/ui/login/LoginFragment.kt | 59 +++++ .../work/ui/login/LoginViewModel.kt | 76 ++++++ .../myitschool/work/ui/main/MainFragment.kt | 117 +++++++++ .../myitschool/work/ui/main/MainViewModel.kt | 81 ++++++ .../work/ui/qr/result/QrResultFragment.kt | 46 ++++ .../work/ui/qr/result/QrResultViewModel.kt | 62 +++++ .../work/ui/qr/scan/QrScanDestination.kt | 30 +++ .../work/ui/qr/scan/QrScanFragment.kt | 139 +++++++++++ .../work/ui/qr/scan/QrScanViewModel.kt | 93 +++++++ .../myitschool/work/utils/FlowExtensions.kt | 10 + .../work/utils/FragmentExtesions.kt | 18 ++ .../ru/myitschool/work/utils/NetworkModule.kt | 20 ++ .../ru/myitschool/work/utils/UserState.kt | 9 + .../myitschool/work/utils/ViewExtensions.kt | 7 + app/src/main/res/drawable/avatar.jpg | Bin 0 -> 15599 bytes app/src/main/res/drawable/ic_close.xml | 5 + .../res/drawable/ic_launcher_background.xml | 170 +++++++++++++ .../res/drawable/ic_launcher_foreground.xml | 30 +++ app/src/main/res/drawable/ic_logout.xml | 5 + app/src/main/res/drawable/ic_no_img.xml | 5 + app/src/main/res/drawable/ic_qr_code.xml | 25 ++ app/src/main/res/drawable/ic_refresh.xml | 5 + app/src/main/res/font/montserrat_bold.ttf | Bin 0 -> 109200 bytes app/src/main/res/font/montserrat_bold.xml | 7 + app/src/main/res/font/montserrat_medium.xml | 7 + app/src/main/res/layout/activity_root.xml | 20 ++ app/src/main/res/layout/fragment_login.xml | 83 +++++++ app/src/main/res/layout/fragment_main.xml | 128 ++++++++++ .../main/res/layout/fragment_qr_result.xml | 41 +++ app/src/main/res/layout/fragment_qr_scan.xml | 35 +++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 + app/src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes app/src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes app/src/main/res/navigation/nav_graph.xml | 43 ++++ app/src/main/res/values-en/strings.xml | 18 ++ app/src/main/res/values/colors.xml | 14 ++ app/src/main/res/values/dimens.xml | 5 + app/src/main/res/values/font_certs.xml | 17 ++ app/src/main/res/values/ids.xml | 4 + app/src/main/res/values/preloaded_fonts.xml | 7 + app/src/main/res/values/strings.xml | 21 ++ app/src/main/res/values/strings_qr.xml | 4 + app/src/main/res/values/themes.xml | 16 ++ app/src/main/res/xml/backup_rules.xml | 13 + .../main/res/xml/data_extraction_rules.xml | 19 ++ .../main/res/xml/network_security_config.xml | 5 + build.gradle.kts | 8 + buildSrc/.gitignore | 2 + buildSrc/build.gradle.kts | 8 + buildSrc/src/main/java/Dependencies.kt | 220 ++++++++++++++++ .../main/java/DependencyHandlerExtensions.kt | 40 +++ buildSrc/src/main/java/Plugin.kt | 67 +++++ buildSrc/src/main/java/Version.kt | 42 ++++ gradle.properties | 21 ++ gradle/README.md | 11 + gradle/libs.versions.toml | 47 ++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 234 ++++++++++++++++++ gradlew.bat | 89 +++++++ settings.gradle.kts | 17 ++ 102 files changed, 3272 insertions(+) create mode 100644 .gitea/workflows/automerge-with-core.yaml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .kotlin/errors/errors-1732305455885.log create mode 100644 .kotlin/errors/errors-1732467098950.log create mode 100644 .kotlin/errors/errors-1732726114629.log create mode 100644 .kotlin/errors/errors-1732729015076.log create mode 100644 README.md create mode 100644 app/.gitignore create mode 100644 app/build.gradle.kts create mode 100644 app/proguard-rules.pro create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/ru/myitschool/work/App.kt create mode 100644 app/src/main/java/ru/myitschool/work/core/Constants.kt create mode 100644 app/src/main/java/ru/myitschool/work/data/UserDataStoreManager.kt create mode 100644 app/src/main/java/ru/myitschool/work/data/door/DoorNetworkDataSource.kt create mode 100644 app/src/main/java/ru/myitschool/work/data/door/DoorRepoImpl.kt create mode 100644 app/src/main/java/ru/myitschool/work/data/dto/OpenRequestDTO.kt create mode 100644 app/src/main/java/ru/myitschool/work/data/dto/UserDTO.kt create mode 100644 app/src/main/java/ru/myitschool/work/data/info/InfoNetworkDataSource.kt create mode 100644 app/src/main/java/ru/myitschool/work/data/info/InfoRepoImpl.kt create mode 100644 app/src/main/java/ru/myitschool/work/data/login/LoginNetworkDataSource.kt create mode 100644 app/src/main/java/ru/myitschool/work/data/login/LoginRepoImpl.kt create mode 100644 app/src/main/java/ru/myitschool/work/domain/door/DoorRepo.kt create mode 100644 app/src/main/java/ru/myitschool/work/domain/door/OpenDoorUseCase.kt create mode 100644 app/src/main/java/ru/myitschool/work/domain/entities/OpenEntity.kt create mode 100644 app/src/main/java/ru/myitschool/work/domain/entities/UserEntity.kt create mode 100644 app/src/main/java/ru/myitschool/work/domain/info/GetInfoUseCase.kt create mode 100644 app/src/main/java/ru/myitschool/work/domain/info/InfoRepo.kt create mode 100644 app/src/main/java/ru/myitschool/work/domain/login/LoginRepo.kt create mode 100644 app/src/main/java/ru/myitschool/work/domain/login/LoginUseCase.kt create mode 100644 app/src/main/java/ru/myitschool/work/ui/RootActivity.kt create mode 100644 app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt create mode 100644 app/src/main/java/ru/myitschool/work/ui/login/LoginViewModel.kt create mode 100644 app/src/main/java/ru/myitschool/work/ui/main/MainFragment.kt create mode 100644 app/src/main/java/ru/myitschool/work/ui/main/MainViewModel.kt create mode 100644 app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultFragment.kt create mode 100644 app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultViewModel.kt create mode 100644 app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanDestination.kt create mode 100644 app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanFragment.kt create mode 100644 app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanViewModel.kt create mode 100644 app/src/main/java/ru/myitschool/work/utils/FlowExtensions.kt create mode 100644 app/src/main/java/ru/myitschool/work/utils/FragmentExtesions.kt create mode 100644 app/src/main/java/ru/myitschool/work/utils/NetworkModule.kt create mode 100644 app/src/main/java/ru/myitschool/work/utils/UserState.kt create mode 100644 app/src/main/java/ru/myitschool/work/utils/ViewExtensions.kt create mode 100644 app/src/main/res/drawable/avatar.jpg create mode 100644 app/src/main/res/drawable/ic_close.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_logout.xml create mode 100644 app/src/main/res/drawable/ic_no_img.xml create mode 100644 app/src/main/res/drawable/ic_qr_code.xml create mode 100644 app/src/main/res/drawable/ic_refresh.xml create mode 100644 app/src/main/res/font/montserrat_bold.ttf create mode 100644 app/src/main/res/font/montserrat_bold.xml create mode 100644 app/src/main/res/font/montserrat_medium.xml create mode 100644 app/src/main/res/layout/activity_root.xml create mode 100644 app/src/main/res/layout/fragment_login.xml create mode 100644 app/src/main/res/layout/fragment_main.xml create mode 100644 app/src/main/res/layout/fragment_qr_result.xml create mode 100644 app/src/main/res/layout/fragment_qr_scan.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/navigation/nav_graph.xml create mode 100644 app/src/main/res/values-en/strings.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/font_certs.xml create mode 100644 app/src/main/res/values/ids.xml create mode 100644 app/src/main/res/values/preloaded_fonts.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/strings_qr.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 app/src/main/res/xml/backup_rules.xml create mode 100644 app/src/main/res/xml/data_extraction_rules.xml create mode 100644 app/src/main/res/xml/network_security_config.xml create mode 100644 build.gradle.kts create mode 100644 buildSrc/.gitignore create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/src/main/java/Dependencies.kt create mode 100644 buildSrc/src/main/java/DependencyHandlerExtensions.kt create mode 100644 buildSrc/src/main/java/Plugin.kt create mode 100644 buildSrc/src/main/java/Version.kt create mode 100644 gradle.properties create mode 100644 gradle/README.md create mode 100644 gradle/libs.versions.toml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle.kts diff --git a/.gitea/workflows/automerge-with-core.yaml b/.gitea/workflows/automerge-with-core.yaml new file mode 100644 index 0000000..e040dbd --- /dev/null +++ b/.gitea/workflows/automerge-with-core.yaml @@ -0,0 +1,26 @@ +name: Merge core/template-android-project to this repo + +env: + CORE_REPO: "https://git.sicampus.ru/core/template-android-project.git" + TOKEN: ${{ secrets.PUSH_TOKEN }} + +run-name: Merge core/template-android-project to ${{ gitea.repository }} +on: + schedule: + - cron: '@daily' + + +jobs: + merge-if-needed: + if: ${{ !contains(gitea.repository, 'core/template-android-project' ) }} + runs-on: ubuntu-latest + steps: + - run: echo "Merge core/template-android-project to ${{ gitea.repository }}" + - name: Check out repository code + uses: actions/checkout@v4 + - name: Sync repos + uses: Vova-SH/sync-upstream-repo@1.0.5 + with: + upstream_repo: ${{ env.CORE_REPO }} + token: ${{ env.TOKEN }} + spawn_logs: false \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..10cfdbf --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b3f5536 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "gradle"] + path = gradle + url = https://git.sicampus.ru/core/gradle.git +[submodule "buildSrc"] + path = buildSrc + url = https://git.sicampus.ru/core/dependecies.git diff --git a/.kotlin/errors/errors-1732305455885.log b/.kotlin/errors/errors-1732305455885.log new file mode 100644 index 0000000..9922b32 --- /dev/null +++ b/.kotlin/errors/errors-1732305455885.log @@ -0,0 +1,82 @@ +kotlin version: 2.0.21 +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:195) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:128) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:170) + at org.gradle.internal.Factories$1.create(Factories.java:31) + at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:267) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:131) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:136) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:165) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:134) + 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\yastr\AppData\Local\Temp\kotlin-backups17205006845707631203 + 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: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) + ... 3 more + + diff --git a/.kotlin/errors/errors-1732467098950.log b/.kotlin/errors/errors-1732467098950.log new file mode 100644 index 0000000..102944b --- /dev/null +++ b/.kotlin/errors/errors-1732467098950.log @@ -0,0 +1,82 @@ +kotlin version: 2.0.21 +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:195) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:128) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:170) + at org.gradle.internal.Factories$1.create(Factories.java:31) + at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:267) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:131) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:136) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:165) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:134) + 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\yastr\AppData\Local\Temp\kotlin-backups11302413676619982638 + 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: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) + ... 3 more + + diff --git a/.kotlin/errors/errors-1732726114629.log b/.kotlin/errors/errors-1732726114629.log new file mode 100644 index 0000000..f473f72 --- /dev/null +++ b/.kotlin/errors/errors-1732726114629.log @@ -0,0 +1,82 @@ +kotlin version: 2.0.21 +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:195) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:128) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:170) + at org.gradle.internal.Factories$1.create(Factories.java:31) + at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:267) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:131) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:136) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:165) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:134) + 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\yastr\AppData\Local\Temp\kotlin-backups4231236667734128612 + 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: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) + ... 3 more + + diff --git a/.kotlin/errors/errors-1732729015076.log b/.kotlin/errors/errors-1732729015076.log new file mode 100644 index 0000000..3b20c8e --- /dev/null +++ b/.kotlin/errors/errors-1732729015076.log @@ -0,0 +1,82 @@ +kotlin version: 2.0.21 +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:195) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:128) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:170) + at org.gradle.internal.Factories$1.create(Factories.java:31) + at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:267) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:131) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:136) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:165) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:134) + 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\yastr\AppData\Local\Temp\kotlin-backups2526552628484561697 + 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: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) + ... 3 more + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..ef91da2 --- /dev/null +++ b/README.md @@ -0,0 +1,121 @@ +[![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//auth` (подробное описание представлено в техническом задании серверной части). +5. В случае отсутствия логина или любой другой неполадки - необходимо вывести ошибку, пока пользователь не изменит текстовое поле или повторно не нажмёт на кнопку. +6. После нажатия на кнопку - логин должен быть сохранён и при следующем открытии приложения экран авторизации не должен быть показан. +7. После нажатия на кнопку - при нажатии стрелки назад - экран авторизации не должен быть показан повторно. +8. Экран авторизации показывается только в случае, если пользователь неавторизован. + + + + +### 2. Главный экран + +> Данный экран содержит общую информацию о пользователе: +>- ФИО +>- Фото +>- Должность +>- Время последнего входа + +#### Элементы, которые должны присутствовать на экране: +- Текстовое поле (`id/fullname`), в котором написано имя пользователя. +- Изображение (`id/photo`), на котором отображено фото пользователя. +- Текстовое поле (`id/position`), в котором написана должность пользователя. +- Текстовое поле (`id/lastEntry`), в котором написана дата и время последнего входа пользователя. +- Кнопка (`id/logout`) для выхода пользователя из аккаунта. +- Кнопка (`id/refresh`) для обновления данных. +- Кнопка (`id/scan`) для сканирования QR кода. +- По умолчанию скрытое текстовое поле с ошибкой (`id/error`). + +#### Требования к компонентам: +- В случае любой ошибки необходимо скрыть все элементы, кроме текстового поля с ошибкой и кнопки обновления данных. +- Для получения данных необходимо использовать сетевой запрос `/api//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//open`, добавив данные из результата и получить ответ. +- Если сервер ответил успешно - то отображаем текст: + *"Успешно/Success"* +- Если сервер ответил любой ошибкой - то отображаем текст: + *"Что-то пошло не так/Something wrong"* +- Кнопка закрытия всегда открывает главный экран. + + + +## 🛠 Решение + +Необходимо загрузить свое решение в систему [по ссылке](https://innovationcampus.ru/lms/mod/quiz/view.php?id=2149). + +Отметим, что работу необходимо осуществлять в представленных проектах-заготовках (шаблонах). + + + +## ✅ Особенности оценивания + +Оценивание происходит с помощью автоматической системы тестирования, которая в автоматическом режиме находит элементы и взаимодействует с ними (именно для этого у каждого элемента указан уникальный идентификатор, по которому будет производится поиск). Каждый тест происходит с чистой установки приложения. +В случае тестирования сервера на него поочередно отправляются команды, описанные в API и ожидаются определенные корректные ответы. +Сервер и приложение тестируются независимо. + diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..4bdcaaa --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,76 @@ +plugins { + androidApplication + jetbrainsKotlinSerialization version Version.Kotlin.language + kotlinAnnotationProcessor + id("com.google.dagger.hilt.android") version("2.51.1") + alias(libs.plugins.kotlin.android) +} + +val packageName = "ru.myitschool.work" +android { + namespace = packageName + compileSdk = 35 + + defaultConfig { + applicationId = packageName + minSdk = 31 + targetSdk = 35 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildFeatures { + viewBinding = true + } + + compileOptions { + sourceCompatibility = Version.Kotlin.javaSource + targetCompatibility = Version.Kotlin.javaSource + } + + kotlinOptions { + jvmTarget = Version.Kotlin.jvmTarget + } +} + +dependencies { + implementation(libs.androidx.appcompat) + implementation(libs.material) + implementation(libs.androidx.activity.ktx) + implementation(libs.androidx.constraintlayout) + implementation(libs.androidx.annotation) + implementation(libs.androidx.lifecycle.livedata.ktx) + implementation(libs.androidx.lifecycle.viewmodel.ktx) + defaultLibrary() + + implementation(Dependencies.AndroidX.activity) + implementation(Dependencies.AndroidX.fragment) + implementation(Dependencies.AndroidX.constraintLayout) + + implementation(libs.androidx.navigation.fragment.ktx) + implementation(libs.androidx.navigation.ui.ktx) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.serialization) + implementation(libs.picasso) + implementation(libs.kotlinx.serialization.json) + implementation(libs.androidx.datastore.preferences) + implementation(libs.barcode.scanning) + implementation(libs.androidx.camera.core) + implementation(libs.androidx.camera.camera2) + implementation(libs.androidx.camera.lifecycle) + implementation(libs.androidx.camera.view) + implementation(libs.androidx.camera.mlkit.vision) + + val hilt = "2.51.1" + implementation(libs.hilt.android) + kapt("com.google.dagger:hilt-android-compiler:$hilt") +} + +kapt { + correctErrorTypes = true +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3033ffc --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/App.kt b/app/src/main/java/ru/myitschool/work/App.kt new file mode 100644 index 0000000..3085135 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/App.kt @@ -0,0 +1,7 @@ +package ru.myitschool.work + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class App : Application() \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/core/Constants.kt b/app/src/main/java/ru/myitschool/work/core/Constants.kt new file mode 100644 index 0000000..ffb63c1 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/core/Constants.kt @@ -0,0 +1,5 @@ +package ru.myitschool.work.core +// БЕРИТЕ И ИЗМЕНЯЙТЕ ХОСТ ТОЛЬКО ЗДЕСЬ И НЕ БЕРИТЕ ИЗ ДРУГИХ МЕСТ. ФАЙЛ ПЕРЕМЕЩАТЬ НЕЛЬЗЯ +object Constants { + const val SERVER_ADDRESS = "http://10.0.2.2:8080" +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/UserDataStoreManager.kt b/app/src/main/java/ru/myitschool/work/data/UserDataStoreManager.kt new file mode 100644 index 0000000..f24174a --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/UserDataStoreManager.kt @@ -0,0 +1,33 @@ +package ru.myitschool.work.data + +import android.content.Context +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +private val Context.dataStore by preferencesDataStore(name = "user_login") +class UserDataStoreManager(private val context: Context) { + + companion object { + private val USERNAME_KEY = stringPreferencesKey("username") + + fun getInstance(context: Context): UserDataStoreManager { + return UserDataStoreManager(context.applicationContext) + } + } + + val usernameFlow: Flow = context.applicationContext.dataStore.data.map { prefs -> + prefs[USERNAME_KEY] ?: "" + } + suspend fun saveUsername(username: String) { + context.dataStore.edit { prefs -> + prefs[USERNAME_KEY] = username + } + } + + suspend fun clearUsername() { + context.applicationContext.dataStore.edit { it.clear() } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/door/DoorNetworkDataSource.kt b/app/src/main/java/ru/myitschool/work/data/door/DoorNetworkDataSource.kt new file mode 100644 index 0000000..4589597 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/door/DoorNetworkDataSource.kt @@ -0,0 +1,39 @@ +package ru.myitschool.work.data.door + +import android.content.Context +import io.ktor.client.call.body +import io.ktor.client.request.patch +import io.ktor.client.request.setBody +import io.ktor.client.statement.bodyAsText +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import io.ktor.http.contentType +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.withContext +import ru.myitschool.work.core.Constants +import ru.myitschool.work.data.UserDataStoreManager +import ru.myitschool.work.data.dto.OpenRequestDTO +import ru.myitschool.work.utils.NetworkModule + +class DoorNetworkDataSource( + context: Context +) { + private val client = NetworkModule.httpClient + private val userDataStoreManager = UserDataStoreManager.getInstance(context) + suspend fun openDoor(openRequestDTO: OpenRequestDTO): Result = withContext(Dispatchers.IO){ + runCatching { + val username = userDataStoreManager.usernameFlow.first() + val result = client.patch("${Constants.SERVER_ADDRESS}/api/$username/open"){ + contentType(ContentType.Application.Json) + setBody(openRequestDTO) + } + if (result.status != HttpStatusCode.OK) { + error("Status ${result.status}") + } + println(result.bodyAsText()) + result.body() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/door/DoorRepoImpl.kt b/app/src/main/java/ru/myitschool/work/data/door/DoorRepoImpl.kt new file mode 100644 index 0000000..510c241 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/door/DoorRepoImpl.kt @@ -0,0 +1,12 @@ +package ru.myitschool.work.data.door + +import ru.myitschool.work.domain.door.DoorRepo +import ru.myitschool.work.domain.entities.OpenEntity + +class DoorRepoImpl( + private val networkDataSource: DoorNetworkDataSource +) : DoorRepo { + override suspend fun openDoor(openEntity: OpenEntity): Result { + return networkDataSource.openDoor(openEntity.toDto()) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/dto/OpenRequestDTO.kt b/app/src/main/java/ru/myitschool/work/data/dto/OpenRequestDTO.kt new file mode 100644 index 0000000..159c5a0 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/dto/OpenRequestDTO.kt @@ -0,0 +1,9 @@ +package ru.myitschool.work.data.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class OpenRequestDTO( + @SerialName("value") val value: Long +) \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/dto/UserDTO.kt b/app/src/main/java/ru/myitschool/work/data/dto/UserDTO.kt new file mode 100644 index 0000000..3e0e4be --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/dto/UserDTO.kt @@ -0,0 +1,15 @@ +package ru.myitschool.work.data.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class UserDTO( + @SerialName("id") val id: Int, + @SerialName("login") val login: String, + @SerialName("name") val name: String, + @SerialName("photo") val photo: String, + @SerialName("position") val position: String, + @SerialName("lastVisit") val lastVisit: String +) + diff --git a/app/src/main/java/ru/myitschool/work/data/info/InfoNetworkDataSource.kt b/app/src/main/java/ru/myitschool/work/data/info/InfoNetworkDataSource.kt new file mode 100644 index 0000000..1b38515 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/info/InfoNetworkDataSource.kt @@ -0,0 +1,34 @@ +package ru.myitschool.work.data.info + +import android.content.Context +import io.ktor.client.call.body +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText +import io.ktor.http.HttpStatusCode +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.withContext +import ru.myitschool.work.core.Constants +import ru.myitschool.work.data.UserDataStoreManager +import ru.myitschool.work.data.dto.UserDTO +import ru.myitschool.work.utils.NetworkModule + +class InfoNetworkDataSource( + context: Context +) { + private val client = NetworkModule.httpClient + + private val userDataStoreManager = UserDataStoreManager.getInstance(context) + suspend fun getInfo():Result = withContext(Dispatchers.IO){ + runCatching { + val username = userDataStoreManager.usernameFlow.first() + val result = client.get("${Constants.SERVER_ADDRESS}/api/$username/info") + + if (result.status != HttpStatusCode.OK) { + error("Status ${result.status}") + } + println(result.bodyAsText()) + result.body() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/info/InfoRepoImpl.kt b/app/src/main/java/ru/myitschool/work/data/info/InfoRepoImpl.kt new file mode 100644 index 0000000..9c82918 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/info/InfoRepoImpl.kt @@ -0,0 +1,19 @@ +package ru.myitschool.work.data.info + +import ru.myitschool.work.domain.entities.UserEntity +import ru.myitschool.work.domain.info.InfoRepo + +class InfoRepoImpl( + private val networkDataSource: InfoNetworkDataSource +): InfoRepo { + override suspend fun getInfo(): Result { + return networkDataSource.getInfo().map { dto -> + UserEntity( + name = dto.name, + position = dto.position, + lastVisit = dto.lastVisit, + photo = dto.photo + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/login/LoginNetworkDataSource.kt b/app/src/main/java/ru/myitschool/work/data/login/LoginNetworkDataSource.kt new file mode 100644 index 0000000..a2b5063 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/login/LoginNetworkDataSource.kt @@ -0,0 +1,26 @@ +package ru.myitschool.work.data.login + +import io.ktor.client.call.body +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText +import io.ktor.http.HttpStatusCode +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import ru.myitschool.work.core.Constants +import ru.myitschool.work.utils.NetworkModule + +class LoginNetworkDataSource { + private val client = NetworkModule.httpClient + suspend fun login(username: String):Result = withContext(Dispatchers.IO){ + runCatching { + val result = client.get("${Constants.SERVER_ADDRESS}/api/$username/auth") + + if (result.status != HttpStatusCode.OK) { + error("Status ${result.status}") + } + println(result.bodyAsText()) + result.body() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/login/LoginRepoImpl.kt b/app/src/main/java/ru/myitschool/work/data/login/LoginRepoImpl.kt new file mode 100644 index 0000000..890931b --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/login/LoginRepoImpl.kt @@ -0,0 +1,11 @@ +package ru.myitschool.work.data.login + +import ru.myitschool.work.domain.login.LoginRepo + +class LoginRepoImpl( + private val networkDataSource: LoginNetworkDataSource +) : LoginRepo { + override suspend fun login(username: String): Result { + return networkDataSource.login(username) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/door/DoorRepo.kt b/app/src/main/java/ru/myitschool/work/domain/door/DoorRepo.kt new file mode 100644 index 0000000..bd96676 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/door/DoorRepo.kt @@ -0,0 +1,7 @@ +package ru.myitschool.work.domain.door + +import ru.myitschool.work.domain.entities.OpenEntity + +interface DoorRepo { + suspend fun openDoor(openEntity: OpenEntity) : Result +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/door/OpenDoorUseCase.kt b/app/src/main/java/ru/myitschool/work/domain/door/OpenDoorUseCase.kt new file mode 100644 index 0000000..f968d4a --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/door/OpenDoorUseCase.kt @@ -0,0 +1,11 @@ +package ru.myitschool.work.domain.door + +import ru.myitschool.work.domain.entities.OpenEntity + +class OpenDoorUseCase( + private val repo: DoorRepo +) { + suspend operator fun invoke(openEntity: OpenEntity) = repo.openDoor( + openEntity = openEntity + ) +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/entities/OpenEntity.kt b/app/src/main/java/ru/myitschool/work/domain/entities/OpenEntity.kt new file mode 100644 index 0000000..288b068 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/entities/OpenEntity.kt @@ -0,0 +1,13 @@ +package ru.myitschool.work.domain.entities + +import ru.myitschool.work.data.dto.OpenRequestDTO + +data class OpenEntity( + val value: Long +){ + fun toDto() : OpenRequestDTO{ + return OpenRequestDTO( + value = value + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/entities/UserEntity.kt b/app/src/main/java/ru/myitschool/work/domain/entities/UserEntity.kt new file mode 100644 index 0000000..4500cb7 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/entities/UserEntity.kt @@ -0,0 +1,8 @@ +package ru.myitschool.work.domain.entities + +data class UserEntity ( + val name: String, + val photo: String, + val position: String, + val lastVisit: String +) \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/info/GetInfoUseCase.kt b/app/src/main/java/ru/myitschool/work/domain/info/GetInfoUseCase.kt new file mode 100644 index 0000000..72760f7 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/info/GetInfoUseCase.kt @@ -0,0 +1,7 @@ +package ru.myitschool.work.domain.info + +class GetInfoUseCase( + private val repo: InfoRepo +) { + suspend operator fun invoke() = repo.getInfo() +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/info/InfoRepo.kt b/app/src/main/java/ru/myitschool/work/domain/info/InfoRepo.kt new file mode 100644 index 0000000..39b68c6 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/info/InfoRepo.kt @@ -0,0 +1,7 @@ +package ru.myitschool.work.domain.info + +import ru.myitschool.work.domain.entities.UserEntity + +interface InfoRepo { + suspend fun getInfo(): Result +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/login/LoginRepo.kt b/app/src/main/java/ru/myitschool/work/domain/login/LoginRepo.kt new file mode 100644 index 0000000..c63ac08 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/login/LoginRepo.kt @@ -0,0 +1,5 @@ +package ru.myitschool.work.domain.login + +interface LoginRepo { + suspend fun login(username: String): Result +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/login/LoginUseCase.kt b/app/src/main/java/ru/myitschool/work/domain/login/LoginUseCase.kt new file mode 100644 index 0000000..5798888 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/login/LoginUseCase.kt @@ -0,0 +1,7 @@ +package ru.myitschool.work.domain.login + +class LoginUseCase( + private val repo: LoginRepo +) { + suspend operator fun invoke(username : String) = repo.login(username) +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/RootActivity.kt b/app/src/main/java/ru/myitschool/work/ui/RootActivity.kt new file mode 100644 index 0000000..edb7a46 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/RootActivity.kt @@ -0,0 +1,27 @@ +package ru.myitschool.work.ui + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.navigation.NavController +import androidx.navigation.fragment.NavHostFragment +import dagger.hilt.android.AndroidEntryPoint +import ru.myitschool.work.R +import ru.myitschool.work.databinding.ActivityRootBinding + +// НЕ ИЗМЕНЯЙТЕ НАЗВАНИЕ КЛАССА! +@AndroidEntryPoint +class RootActivity : AppCompatActivity() { + private lateinit var binding: ActivityRootBinding + private lateinit var navController: NavController + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityRootBinding.inflate(layoutInflater) + setContentView(binding.root) + val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment + navController = navHostFragment.navController + val windowInsetsController = window.insetsController + windowInsetsController?.hide(android.view.WindowInsets.Type.statusBars() or android.view.WindowInsets.Type.navigationBars()) + + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt b/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt new file mode 100644 index 0000000..4056cc9 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt @@ -0,0 +1,59 @@ +package ru.myitschool.work.ui.login + +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.View +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import kotlinx.coroutines.launch +import ru.myitschool.work.R +import ru.myitschool.work.databinding.FragmentLoginBinding + +class LoginFragment : Fragment(R.layout.fragment_login) { + + private var _binding: FragmentLoginBinding? = null + private val binding get() = _binding!! + + private val viewModel: LoginViewModel by viewModels{ LoginViewModel.Factory } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + _binding = FragmentLoginBinding.bind(view) + val textWatcher = object : TextWatcher{ + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { + + } + override fun afterTextChanged(s: Editable?) { } + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + val username = binding.username.text + binding.loginBtn.isEnabled = username.length >= 3 && !username[0].isDigit() && username.matches(Regex("^[a-zA-Z0-9]*$")) + + } + } + binding.username.addTextChangedListener(textWatcher) + binding.loginBtn.isEnabled = false + binding.loginBtn.setOnClickListener{ + viewModel.login(binding.username.text.toString()) + + } + lifecycleScope.launch { + viewModel.state.collect { state -> + with(binding) { + error.visibility = if (state is LoginViewModel.State.Error) View.VISIBLE else View.GONE + username.isEnabled = state !is LoginViewModel.State.Loading + + if (state is LoginViewModel.State.Success) { + findNavController().navigate(R.id.mainFragment) + } + } + } + } + } + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/login/LoginViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/login/LoginViewModel.kt new file mode 100644 index 0000000..e6f1502 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/login/LoginViewModel.kt @@ -0,0 +1,76 @@ +package ru.myitschool.work.ui.login + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import ru.myitschool.work.data.UserDataStoreManager +import ru.myitschool.work.data.login.LoginNetworkDataSource +import ru.myitschool.work.data.login.LoginRepoImpl +import ru.myitschool.work.domain.login.LoginUseCase + +class LoginViewModel( + private val useCase: LoginUseCase, + application: Application +) : AndroidViewModel(application) { + private val dataStoreManager = UserDataStoreManager(application) + private val _state = MutableStateFlow(State.Idle) + val state: StateFlow = _state.asStateFlow() + + init { + viewModelScope.launch{ + val username = dataStoreManager.usernameFlow.first() + if(username != "") + login(username) + } + + } + + sealed class State { + object Idle : State() + object Loading : State() + object Success : State() + data class Error(val message: String?) : State() + } + + + fun login(username: String) { + _state.value = State.Loading + viewModelScope.launch{ + useCase.invoke(username).fold( + onSuccess = { data -> + dataStoreManager.saveUsername(username) + _state.value = State.Success + }, + onFailure = {e-> + println(e) + _state.value = State.Error(e.message) + + } + ) + } + } + companion object { + @Suppress("UNCHECKED_CAST") + val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory { + override fun create(modelClass: Class, extras: CreationExtras): T { + val repoImpl = LoginRepoImpl( + networkDataSource = LoginNetworkDataSource() + ) + + val useCase = LoginUseCase(repoImpl) + + return LoginViewModel( + useCase, extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as Application + ) as T + } + } + } +} diff --git a/app/src/main/java/ru/myitschool/work/ui/main/MainFragment.kt b/app/src/main/java/ru/myitschool/work/ui/main/MainFragment.kt new file mode 100644 index 0000000..69c4b06 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/main/MainFragment.kt @@ -0,0 +1,117 @@ +package ru.myitschool.work.ui.main + +import android.os.Bundle +import android.view.View +import androidx.core.os.bundleOf +import androidx.fragment.app.Fragment +import androidx.fragment.app.setFragmentResultListener +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import com.squareup.picasso.Picasso +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import ru.myitschool.work.R +import ru.myitschool.work.databinding.FragmentMainBinding +import ru.myitschool.work.domain.entities.UserEntity +import ru.myitschool.work.ui.qr.scan.QrScanDestination +import ru.myitschool.work.utils.UserState +import ru.myitschool.work.utils.collectWhenStarted + +class MainFragment : Fragment(R.layout.fragment_main) { + + private var _binding: FragmentMainBinding? = null + private val binding get() = _binding!! + + private val viewModel: MainViewModel by viewModels{ MainViewModel.Factory } + + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + _binding = FragmentMainBinding.bind(view) + + viewModel.getUserData() + binding.refresh.setOnClickListener { viewModel.getUserData() } + binding.logout.setOnClickListener { logout() } + binding.scan.setOnClickListener { onScanClick() } + viewModel.userState.collectWhenStarted(this) { state -> + when (state) { + is UserState.Error -> { + showError() + binding.loading.visibility = View.GONE + binding.refresh.visibility = View.VISIBLE + } + + is UserState.Loading -> { + binding.loading.visibility = View.VISIBLE + setViewsVisibility(View.GONE) + binding.refresh.visibility = View.GONE + } + is UserState.Success -> { + setViewsVisibility(View.VISIBLE) + binding.refresh.visibility = View.VISIBLE + binding.loading.visibility = View.GONE + binding.error.visibility = View.GONE + showUserData(state.userEntity) + + } + } + } + setFragmentResultListener(QrScanDestination.REQUEST_KEY) { _, bundle -> + val qrData = QrScanDestination.getDataIfExist(bundle) + println(qrData) + val bundleToQrResult = bundleOf("qr_data" to qrData) + findNavController().navigate(R.id.qrResultFragment, bundleToQrResult) + + } + + + } + + private fun logout() { + lifecycleScope.launch { + viewModel.clearUsername() + delay(50) // Не всегда успевает скинуть логин + findNavController().navigate(R.id.loginFragment) + } + + } + + + private fun showUserData(userEntity: UserEntity) { + binding.apply { + fullname.text = userEntity.name + position.text = userEntity.position + lastEntry.text = viewModel.formatDate(userEntity.lastVisit) + Picasso.get().load(userEntity.photo).into(photo) + + error.visibility = View.GONE + setViewsVisibility(View.VISIBLE) + } + } + + private fun showError() { + binding.error.visibility = View.VISIBLE + setViewsVisibility(View.GONE) + } + + private fun setViewsVisibility(visibility: Int) { + binding.fullname.visibility = visibility + binding.position.visibility = visibility + binding.lastEntry.visibility = visibility + binding.photo.visibility = visibility + binding.logout.visibility = visibility + binding.scan.visibility = visibility + } + + + private fun onScanClick() { + findNavController().navigate(R.id.qrScanFragment) + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + } + diff --git a/app/src/main/java/ru/myitschool/work/ui/main/MainViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/main/MainViewModel.kt new file mode 100644 index 0000000..641f149 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/main/MainViewModel.kt @@ -0,0 +1,81 @@ +package ru.myitschool.work.ui.main + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import ru.myitschool.work.data.UserDataStoreManager +import ru.myitschool.work.data.info.InfoNetworkDataSource +import ru.myitschool.work.data.info.InfoRepoImpl +import ru.myitschool.work.domain.info.GetInfoUseCase +import ru.myitschool.work.utils.UserState +import java.text.SimpleDateFormat +import java.util.Locale + +class MainViewModel( + private val useCase: GetInfoUseCase, + application: Application +) : AndroidViewModel(application) { + + private val _userState = MutableStateFlow(UserState.Loading) + val userState: StateFlow get() = _userState + + private val dataStoreManager = UserDataStoreManager(application) + + fun formatDate(date: String): String { + val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()) + val outputFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()) + return try { + val formattedDate = inputFormat.parse(date) + if (formattedDate != null) { + outputFormat.format(formattedDate) + } else{} + } catch (_: Exception) { + "Invalid Date" + }.toString() + } + + fun getUserData() { + _userState.value = UserState.Loading + viewModelScope.launch { + useCase.invoke().fold( + onSuccess = { data -> _userState.value = UserState.Success(data) }, + onFailure = { _userState.value = UserState.Error } + ) + } + } + + fun clearUsername() { + viewModelScope.launch{ + withContext(Dispatchers.IO) { + dataStoreManager.clearUsername() + } + } + + } + companion object { + @Suppress("UNCHECKED_CAST") + val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory { + override fun create(modelClass: Class, extras: CreationExtras): T { + val repoImpl = InfoRepoImpl( + networkDataSource = InfoNetworkDataSource( + context = extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as Application + ) + ) + + val useCase = GetInfoUseCase(repoImpl) + + return MainViewModel( + useCase, extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as Application + ) as T + } + } + } +} diff --git a/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultFragment.kt b/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultFragment.kt new file mode 100644 index 0000000..2ac5b52 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultFragment.kt @@ -0,0 +1,46 @@ +package ru.myitschool.work.ui.qr.result + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import ru.myitschool.work.R +import ru.myitschool.work.databinding.FragmentQrResultBinding +import ru.myitschool.work.domain.entities.OpenEntity +import ru.myitschool.work.utils.collectWhenStarted + +class QrResultFragment : Fragment(R.layout.fragment_qr_result) { + private var _binding: FragmentQrResultBinding? = null + private val binding get() = _binding!! + private val viewModel by viewModels{ QrResultViewModel.Factory } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + _binding = FragmentQrResultBinding.bind(view) + var qrData = arguments?.getString("qr_data") + if (qrData != null) { + viewModel.openDoor(OpenEntity(qrData.toLong())) + } + else{ + binding.result.text = getString(R.string.result_null_text) + } + viewModel.state.collectWhenStarted(this){ state-> + when(state) { + QrResultViewModel.State.Error -> { + binding.result.text = getString(R.string.result_fail_text) + } + QrResultViewModel.State.Loading -> { + Unit + } + QrResultViewModel.State.Success -> { + binding.result.text = getString(R.string.result_success_text) + } + } + binding.close.setOnClickListener{ + qrData = "" + findNavController().navigate(R.id.mainFragment) + } + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultViewModel.kt new file mode 100644 index 0000000..d9620e2 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultViewModel.kt @@ -0,0 +1,62 @@ +package ru.myitschool.work.ui.qr.result + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import ru.myitschool.work.data.door.DoorNetworkDataSource +import ru.myitschool.work.data.door.DoorRepoImpl +import ru.myitschool.work.domain.door.OpenDoorUseCase +import ru.myitschool.work.domain.entities.OpenEntity + +class QrResultViewModel( + private val useCase: OpenDoorUseCase, + application: Application +) : AndroidViewModel(application) { + private val _state = MutableStateFlow(State.Loading) + val state: StateFlow = _state.asStateFlow() + + sealed class State{ + object Success : State() + object Loading : State() + object Error : State() + } + fun openDoor(openEntity: OpenEntity){ + _state.value = State.Loading + viewModelScope.launch{ + useCase.invoke(openEntity).fold( + onSuccess = { data-> + _state.value = State.Success + }, + onFailure = { _ -> + _state.value = State.Error + } + ) + } + } + companion object { + @Suppress("UNCHECKED_CAST") + val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory { + override fun create(modelClass: Class, extras: CreationExtras): T { + val repoImpl = DoorRepoImpl( + networkDataSource = DoorNetworkDataSource( + context = extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as Application + ) + ) + + val useCase = OpenDoorUseCase(repoImpl) + + return QrResultViewModel( + useCase, extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as Application + ) as T + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanDestination.kt b/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanDestination.kt new file mode 100644 index 0000000..7e34b28 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanDestination.kt @@ -0,0 +1,30 @@ +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 + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanFragment.kt b/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanFragment.kt new file mode 100644 index 0000000..a9ddaab --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanFragment.kt @@ -0,0 +1,139 @@ +package ru.myitschool.work.ui.qr.scan + +import android.os.Bundle +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 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanViewModel.kt new file mode 100644 index 0000000..14565ab --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanViewModel.kt @@ -0,0 +1,93 @@ +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() + val action = _action.asSharedFlow() + + private val _state = MutableStateFlow(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 + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/utils/FlowExtensions.kt b/app/src/main/java/ru/myitschool/work/utils/FlowExtensions.kt new file mode 100644 index 0000000..87bccc2 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/utils/FlowExtensions.kt @@ -0,0 +1,10 @@ +package ru.myitschool.work.utils + +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow + +fun MutablePublishFlow() = MutableSharedFlow( + replay = 0, + extraBufferCapacity = 1, + BufferOverflow.DROP_OLDEST +) \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/utils/FragmentExtesions.kt b/app/src/main/java/ru/myitschool/work/utils/FragmentExtesions.kt new file mode 100644 index 0000000..8c99ef3 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/utils/FragmentExtesions.kt @@ -0,0 +1,18 @@ +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 Flow.collectWhenStarted( + fragment: Fragment, + crossinline collector: (T) -> Unit +) { + fragment.viewLifecycleOwner.lifecycleScope.launch { + flowWithLifecycle(fragment.viewLifecycleOwner.lifecycle).collect { value -> + collector.invoke(value) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/utils/NetworkModule.kt b/app/src/main/java/ru/myitschool/work/utils/NetworkModule.kt new file mode 100644 index 0000000..fd3afb2 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/utils/NetworkModule.kt @@ -0,0 +1,20 @@ +package ru.myitschool.work.utils + +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.serialization.kotlinx.json.json +import kotlinx.serialization.json.Json + +object NetworkModule { + val httpClient: HttpClient by lazy { + HttpClient(CIO) { + install(ContentNegotiation) { + json(Json { + isLenient = true + ignoreUnknownKeys = true + }) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/utils/UserState.kt b/app/src/main/java/ru/myitschool/work/utils/UserState.kt new file mode 100644 index 0000000..81d83c3 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/utils/UserState.kt @@ -0,0 +1,9 @@ +package ru.myitschool.work.utils + +import ru.myitschool.work.domain.entities.UserEntity + +sealed class UserState { + object Loading : UserState() + data class Success(val userEntity: UserEntity) : UserState() + object Error : UserState() +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/utils/ViewExtensions.kt b/app/src/main/java/ru/myitschool/work/utils/ViewExtensions.kt new file mode 100644 index 0000000..5c38f67 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/utils/ViewExtensions.kt @@ -0,0 +1,7 @@ +package ru.myitschool.work.utils + +import android.view.View + +fun View.visibleOrGone(isVisible: Boolean) { + this.visibility = if (isVisible) View.VISIBLE else View.GONE +} \ No newline at end of file diff --git a/app/src/main/res/drawable/avatar.jpg b/app/src/main/res/drawable/avatar.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bd434fed232619794b8317bb6de89c56ab1e9af9 GIT binary patch literal 15599 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 + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_logout.xml b/app/src/main/res/drawable/ic_logout.xml new file mode 100644 index 0000000..c22a96f --- /dev/null +++ b/app/src/main/res/drawable/ic_logout.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_no_img.xml b/app/src/main/res/drawable/ic_no_img.xml new file mode 100644 index 0000000..44206c9 --- /dev/null +++ b/app/src/main/res/drawable/ic_no_img.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_qr_code.xml b/app/src/main/res/drawable/ic_qr_code.xml new file mode 100644 index 0000000..b03f9ae --- /dev/null +++ b/app/src/main/res/drawable/ic_qr_code.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_refresh.xml b/app/src/main/res/drawable/ic_refresh.xml new file mode 100644 index 0000000..86504d0 --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/font/montserrat_bold.ttf b/app/src/main/res/font/montserrat_bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..4df50dac4d4f5421976a6f30cf94f2247ab57022 GIT binary patch literal 109200 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

fe5P)3nYyKA>K2e`)p*rYx_xSLh7wZo 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 z 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 At^fc4 literal 0 HcmV?d00001 diff --git a/app/src/main/res/font/montserrat_bold.xml b/app/src/main/res/font/montserrat_bold.xml new file mode 100644 index 0000000..afbba1b --- /dev/null +++ b/app/src/main/res/font/montserrat_bold.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/font/montserrat_medium.xml b/app/src/main/res/font/montserrat_medium.xml new file mode 100644 index 0000000..131ca24 --- /dev/null +++ b/app/src/main/res/font/montserrat_medium.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/layout/activity_root.xml b/app/src/main/res/layout/activity_root.xml new file mode 100644 index 0000000..ecc6a87 --- /dev/null +++ b/app/src/main/res/layout/activity_root.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_login.xml b/app/src/main/res/layout/fragment_login.xml new file mode 100644 index 0000000..59512e8 --- /dev/null +++ b/app/src/main/res/layout/fragment_login.xml @@ -0,0 +1,83 @@ + + + + + + + + +