Compare commits

...

88 Commits

Author SHA1 Message Date
89d8794f55 day3_commit.1.5_push_to_main 2025-02-20 16:51:04 +03:00
60110e8e68 Merge branch 'Frontend' 2025-02-20 16:50:25 +03:00
3ed1bc84d7 day3_commit2_vse_slomalos231231 2025-02-20 16:49:59 +03:00
97fb903db3 day3_commit.1.4_push_to_main 2025-02-20 16:48:32 +03:00
636b2a22fc Merge branch 'refs/heads/Frontend' 2025-02-20 16:48:21 +03:00
239f4f8a22 day3_commit2_vse_slomalos 2025-02-20 16:47:15 +03:00
018decb0e8 Merge branch 'Frontend_UI' into Frontend 2025-02-20 16:46:43 +03:00
537daa1072 Merge remote-tracking branch 'origin/Frontend' into Frontend
# Conflicts:
#	app/src/main/java/ru/myitschool/work/api/ApiService.kt
#	app/src/main/java/ru/myitschool/work/ui/Main/MainFragment.kt
#	app/src/main/java/ru/myitschool/work/ui/qr/result/QrResult.kt
2025-02-20 16:42:46 +03:00
0eb8ec5872 day3_commit2_vse_slomalos 2025-02-20 16:41:48 +03:00
333d9d6227 day3_commit.1.3_push_to_main 2025-02-20 16:33:25 +03:00
44e17c69de Merge branch 'Frontend_UI' 2025-02-20 16:32:30 +03:00
ebc2f1675b day3_commit2_front_nice_backendDIIE 2025-02-20 15:46:37 +03:00
cabbe30c42 day3_commit2_qr_mini_fix_and_photo_img 2025-02-20 15:36:25 +03:00
1e8729f108 Merge remote-tracking branch 'origin/Frontend' into Frontend 2025-02-20 14:21:05 +03:00
e83718bce0 day3_commit2_qr_mini_fix 2025-02-20 14:20:07 +03:00
9a23befe71 day3_commit2_temp 2025-02-20 12:47:53 +03:00
654099623a day3_commit.1.2_temp 2025-02-20 12:44:11 +03:00
cb5fbdd634 day3_commit2_added_qr_scan 2025-02-20 12:43:28 +03:00
9818c97148 day3_commit1_fixed_mainfragment 2025-02-20 12:18:31 +03:00
194a1dc466 day3_commit1_fixed_loginauth 2025-02-20 12:08:52 +03:00
8fc1f2decd Merge branch 'Frontend_UI' into Frontend
# Conflicts:
#	app/src/main/java/ru/myitschool/work/core/Constants.kt
2025-02-20 10:33:59 +03:00
bddcda4b72 Merge remote-tracking branch 'origin/Frontend' into Frontend
# Conflicts:
#	app/build.gradle.kts
#	app/src/main/java/ru/myitschool/work/api/ApiModule.kt
#	app/src/main/java/ru/myitschool/work/api/ApiService.kt
#	app/src/main/java/ru/myitschool/work/core/Constants.kt
#	app/src/main/java/ru/myitschool/work/ui/Main/AdminFragment.kt
#	app/src/main/java/ru/myitschool/work/ui/login/LoginViewModel.kt
2025-02-20 10:30:48 +03:00
c510b6d1fb day3_commit1_fixed_loginauth 2025-02-20 10:28:54 +03:00
3f8e032428 day3_commit1_fixed_loginauth 2025-02-20 10:28:10 +03:00
cecbde25af Merge remote-tracking branch 'origin/Frontend_UI' into Frontend_UI 2025-02-20 10:03:31 +03:00
3850be2eb5 day3_commit.1.1_add_close_button_at_admin_panel 2025-02-20 10:03:13 +03:00
cd32de6116 day2_commit_1_xml_з123123213 2025-02-19 19:00:09 +03:00
8535cf6370 day2_commit_1_xml_з123123213 2025-02-19 18:59:50 +03:00
a2a82383a2 revert c13d4c12d654ca07fc40bcad056b398f3c707795
revert day2_commit.1.100_temple
2025-02-19 15:55:26 +00:00
c13d4c12d6 day2_commit.1.100_temple 2025-02-19 18:53:35 +03:00
856b00e591 day2_commit.1.24_change_port 2025-02-19 18:22:16 +03:00
a6f5b89b00 day2_commit.1.23_fragment_login_edited 2025-02-19 18:11:21 +03:00
0cc5854451 day2_commit.1.22_add_landscape_for_admin_panel 2025-02-19 17:49:35 +03:00
f6d313e8a3 day2_commit.1.2 2025-02-19 17:45:25 +03:00
064a29139f Merge remote-tracking branch 'origin/Frontend_UI' 2025-02-19 17:44:19 +03:00
65c2669c37 day2_commit.1.21_закругление_кнопок 2025-02-19 17:41:42 +03:00
d608af144b Merge branch 'Frontend' into Frontend_UI 2025-02-19 17:33:56 +03:00
cc37714881 day2_commit.1.20_add_strings_for_admin_panel 2025-02-19 17:33:18 +03:00
a926e0f57c day2_commit_1_xml_закругление кнопок 2025-02-19 17:30:44 +03:00
763eb5293c day2_commit.1.19_strings_edited_and_add_landscape_for_fragment_main 2025-02-19 17:23:29 +03:00
ee24f241db Merge branch 'Frontend' into Frontend_UI
# Conflicts:
#	app/src/main/AndroidManifest.xml
#	app/src/main/java/ru/myitschool/work/ui/Main/AdminFragment.kt
#	app/src/main/res/layout/fragment_main.xml
2025-02-19 17:05:06 +03:00
644ff7ae02 day2_commit_1_// 2025-02-19 16:59:24 +03:00
907c1c5d1c day2_commit_13_back_to_Json 2025-02-19 16:56:30 +03:00
78ac39a0fa day2_commit.1.18_change_admin_screen 2025-02-19 16:50:18 +03:00
9ecbc6d6d6 Merge remote-tracking branch 'origin/Frontend_UI' into Frontend_UI 2025-02-19 16:46:56 +03:00
698f40a98d day2_commit.1.17_update_qr_scan 2025-02-19 16:46:46 +03:00
87931483bc day2_commit.1.17_landscape_for_qr_scan 2025-02-19 16:43:18 +03:00
e025e1eff7 Обновить README.md 2025-02-19 13:29:50 +00:00
94cda378ae day2_commit.1.16 2025-02-19 15:58:15 +03:00
faadb231c3 day2_commit.1.15 2025-02-19 15:17:01 +03:00
3da4b99cc0 day2_commit_12_MEGAFIX_MEEEEEGAAPI 2025-02-19 15:01:42 +03:00
c5be8a2114 day2_commit_11_MEGAFIX 2025-02-19 14:36:58 +03:00
d7474c88a2 day2_commit.1.14_strings_edited 2025-02-19 14:29:37 +03:00
85a5c82d8c day2_commit_10_fixed_alotofbugs_added_admin_logic 2025-02-19 14:12:47 +03:00
0f38f4797a day2_commit.1.13_manifest_edited 2025-02-19 14:11:14 +03:00
233f9bc174 day2_commit.1.13_fragment_login_edited_p2 2025-02-19 14:07:37 +03:00
3792a48f5b day2_commit.1.12_fragment_login_edited 2025-02-19 13:57:16 +03:00
651b2ec439 day2_commit.1.11_strings_edited_and_add_land_orientation_for_login_and)qr_scan_result 2025-02-19 12:47:40 +03:00
4410d2970d day2_commit.1.10_merged 2025-02-19 12:21:40 +03:00
bc159d38a0 Merge branch 'refs/heads/Frontend' into Frontend_UI
# Conflicts:
#	app/src/main/res/values/strings.xml
2025-02-19 12:18:32 +03:00
1d9207a5f5 day2_commit_9_fixed_gradle_part2_and_add_AdminPanel 2025-02-19 12:12:50 +03:00
05efbd0003 day2_commit_1.9_strings_and_fragment_qr_scan_result_edited 2025-02-19 12:02:51 +03:00
b1d2318d08 day2_commit_8.2_fixed_buttons2_added_RV_test 2025-02-19 11:57:50 +03:00
c5fd7ffe21 day2_commit_8.1_fixed_buttons_added_RV_test 2025-02-19 11:56:58 +03:00
5323532ad1 day2_commit_7.1_gone_buttons 2025-02-19 11:29:54 +03:00
52194be6e2 day2_commit_7_gone_buttons 2025-02-19 11:28:43 +03:00
59013160f4 day2_commit_5_UI_merged_ui/ux 2025-02-19 11:24:32 +03:00
ef8eb6b431 Merge branch 'Frontend_UI' into Frontend 2025-02-19 11:22:52 +03:00
861da763cd day2_commit_1.8_fragment_main_edited 2025-02-19 11:20:47 +03:00
b08ab102ad day2_commit_1.7_fragment_main_edited 2025-02-19 11:16:38 +03:00
cfe46a3317 day2_commit_1.6_fragment_main_edited 2025-02-19 11:14:58 +03:00
1a887da8c7 day2_commit_4_UI_fixed_crash_app_when_backend_off 2025-02-19 11:12:23 +03:00
2ec434ea1d day2_commit_1.5_fragment_main_edited 2025-02-19 11:12:01 +03:00
32378b78fe day2_commit_1.5_deleted_corner_radius 2025-02-19 10:55:41 +03:00
102b9073c4 day2_commit_3_UI_fixed_gadle_and_CR 2025-02-19 10:55:39 +03:00
606d94d37b Merge branch 'Frontend' into Frontend_UI
# Conflicts:
#	app/src/main/res/layout/fragment_login.xml
2025-02-19 10:49:05 +03:00
b4e21dc69a Merge branch 'Frontend_UI' into Frontend 2025-02-19 10:48:35 +03:00
42e6e68a07 day2_commit_1.5_strings 2025-02-19 10:46:58 +03:00
1afc51cab6 day2_commit_1.4_strings 2025-02-19 10:45:18 +03:00
06b7ab6608 day2_commit_2_UI_added_password_cheak_and_basic_auth_(test) 2025-02-19 10:39:48 +03:00
265eb29b99 day2_commit_1.2_xml_edited 2025-02-19 10:36:01 +03:00
c68417891c day2_commit_2_UI_added_password_button 2025-02-19 10:31:21 +03:00
e710759066 day2_commit_1.1_xml_edited 2025-02-19 10:11:30 +03:00
68a936c899 day2_commit_1_fixed_gradle 2025-02-19 10:03:53 +03:00
682eb972cc day1_commit_4_fixed_loginButton_Del_custom_UserName_Cheak 2025-02-18 17:34:18 +03:00
26a3f2ee2b day1_commit_4_fixed_loginButton_added_customUserName_Cheak 2025-02-18 17:32:44 +03:00
d257df7a93 Merge remote-tracking branch 'origin/Frontend_Egor' into Frontend 2025-02-18 16:59:57 +03:00
4b79545ae3 day1_commit_3_added_Backup_false_securityhelp 2025-02-18 16:47:22 +03:00
42 changed files with 1441 additions and 409 deletions

View File

@ -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\User\AppData\Local\Temp\kotlin-backups14802231755905846813
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

View File

@ -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\User\AppData\Local\Temp\kotlin-backups11164380389062662786
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

View File

@ -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\User\AppData\Local\Temp\kotlin-backups8341820686666786180
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

View File

@ -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\User\AppData\Local\Temp\kotlin-backups1146231745401124119
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

122
README.md
View File

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

View File

@ -1,30 +1,30 @@
plugins {
kotlinAndroid
androidApplication
jetbrainsKotlinSerialization version Version.Kotlin.language
kotlinAnnotationProcessor
id("com.google.dagger.hilt.android").version("2.51.1")
id("com.android.application")
id("kotlin-android")
id("kotlin-kapt") // Добавлено для KAPT
id("dagger.hilt.android.plugin") // Используйте этот синтаксис для Hilt
id("org.jetbrains.kotlin.plugin.serialization") version Version.Kotlin.language // Убедитесь, что версия актуальна
}
val packageName = "ru.myitschool.work"
android {
namespace = packageName
compileSdk = Version.Android.Sdk.compile
compileSdk = 35 // Обновлено до 35
defaultConfig {
applicationId = packageName
minSdk = Version.Android.Sdk.min
targetSdk = Version.Android.Sdk.target
targetSdk = 35 // Обновлено до 35
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures.viewBinding = true
buildFeatures {
viewBinding = true
}
compileOptions {
sourceCompatibility = Version.Kotlin.javaSource
@ -37,30 +37,41 @@ android {
}
dependencies {
implementation ("com.squareup.retrofit2:retrofit:2.9.0")
implementation ("com.squareup.retrofit2:converter-gson:2.9.0")
implementation ("com.squareup.okhttp3:okhttp:4.9.0")
implementation ("com.github.bumptech.glide:glide:4.15.1")
kapt ("com.github.bumptech.glide:compiler:4.15.1")
// Retrofit and OkHttp
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:okhttp:4.9.0")
defaultLibrary()
// Glide
implementation("com.github.bumptech.glide:glide:4.15.1")
kapt("com.github.bumptech.glide:compiler:4.15.1")
implementation(Dependencies.AndroidX.activity)
implementation(Dependencies.AndroidX.fragment)
implementation(Dependencies.AndroidX.constraintLayout)
// AndroidX Libraries
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.10.0")
implementation("androidx.activity:activity:1.10.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1")
// Coroutines
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
// Hilt dependencies
implementation("com.google.dagger:hilt-android:2.51.1")
kapt("com.google.dagger:hilt-android-compiler:2.51.1")
// Navigation
implementation(Dependencies.AndroidX.Navigation.fragment)
implementation(Dependencies.AndroidX.Navigation.navigationUi)
implementation(Dependencies.Retrofit.library)
implementation(Dependencies.Retrofit.gsonConverter)
implementation("com.squareup.picasso:picasso:2.8")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
// DataStore
implementation("androidx.datastore:datastore-preferences:1.1.1")
// ML Kit
implementation("com.google.mlkit:barcode-scanning:17.3.0")
// CameraX
val cameraX = "1.3.4"
implementation("androidx.camera:camera-core:$cameraX")
implementation("androidx.camera:camera-camera2:$cameraX")
@ -68,11 +79,13 @@ dependencies {
implementation("androidx.camera:camera-view:$cameraX")
implementation("androidx.camera:camera-mlkit-vision:1.4.0-rc04")
val hilt = "2.51.1"
implementation("com.google.dagger:hilt-android:$hilt")
kapt("com.google.dagger:hilt-android-compiler:$hilt")
// Picasso
implementation("com.squareup.picasso:picasso:2.8")
// Kotlin Serialization
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
}
kapt {
correctErrorTypes = true
}
}

View File

@ -23,7 +23,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Default"
tools:targetApi="31">
tools:targetApi="34">
<activity
android:name=".ui.RootActivity"
android:exported="true">
@ -33,6 +33,7 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -4,4 +4,9 @@ import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class App : Application()
class App : Application() {
override fun onCreate() {
super.onCreate()
SessionManager.init(this) // Инициализация SessionManager
}
}

View File

@ -1,5 +1,41 @@
package ru.myitschool.work
import android.content.Context
import android.content.SharedPreferences
import java.util.Base64
object SessionManager {
var userLogin: String = ""
private const val PREF_NAME = "user_session"
private const val KEY_USER_LOGIN = "user_login"
private const val KEY_USER_ROLE = "user_role"
private lateinit var preferences: SharedPreferences
fun init(context: Context) {
preferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
}
var userLogin: String?
get() = preferences.getString(KEY_USER_LOGIN, null)
set(value) {
preferences.edit().putString(KEY_USER_LOGIN, value).apply()
}
var userRole: String?
get() = preferences.getString(KEY_USER_ROLE, null)
set(value) {
preferences.edit().putString(KEY_USER_ROLE, value).apply()
}
fun getAuthHeader(): String {
val username = userLogin ?: return ""
val password = "password123" // Замените на ваш пароль
val credential = Base64.getEncoder().encodeToString("$username:$password".toByteArray())
return "Basic $credential"
}
fun clearSession() {
userLogin = null
userRole = null
}
}

View File

@ -0,0 +1,7 @@
package ru.myitschool.work.api
data class AccessLog(
val scanTime: String,
val readerId: String,
val accessType: String // "карта" или "смартфон"
)

View File

@ -1,13 +1,13 @@
package ru.myitschool.work.api
import LoggingInterceptor
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import ru.myitschool.work.api.ApiService
import ru.myitschool.work.core.Constants
import javax.inject.Singleton
@Module
@ -16,9 +16,27 @@ object ApiModule {
@Provides
@Singleton
fun provideRetrofit(): Retrofit {
fun provideOkHttpClient(authInterceptor: AuthInterceptor): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(authInterceptor) // Добавляем интерсептор аутентификации
.addInterceptor(LoggingInterceptor()) // Добавляем интерсептор логирования
.build()
}
@Provides
@Singleton
fun provideAuthInterceptor(): AuthInterceptor {
val username = "pivanov" // Замените на ваш логин
val password = "password123" // Замените на ваш пароль
return AuthInterceptor(username, password)
}
@Provides
@Singleton
fun provideRetrofit(client: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(Constants.SERVER_ADDRESS)
.baseUrl("http://10.6.66.110:8080/") // Убедитесь, что URL корректен
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
}

View File

@ -3,18 +3,57 @@ package ru.myitschool.work.api
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.PATCH
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query
interface ApiService {
@GET("api/{login}/auth")
suspend fun authenticate(@Path("login") login: String): Response<Unit>
// Метод для аутентификации
@GET("/api/auth")
suspend fun authenticate(
@Query("login") login: String,
@Query("password") password: String
): Response<Unit> // Измените Response<String> на Response<Unit>
@GET("api/{login}/info")
suspend fun getUserInfo(@Path("login") login: String): Response<Map<String, Any>> // Возвращаем Map вместо UserInfo
@GET("/api/{login}/info")
suspend fun getUserInfo(
@Path("login") login: String,
@Header("Authorization") authHeader: String
): Response<EmployeeData>
@PATCH("api/{login}/open")
suspend fun openDoor(@Path("login") login: String, @Body body: OpenDoorRequest): Response<Unit>
@GET("/api/employee/{login}") // Получение информации о сотруднике
suspend fun getEmployeeInfo(@Path("login") login: String): Response<EmployeeData>
@PATCH("/api/open") // Открыть дверь
suspend fun openDoor(
@Header("Authorization") authHeader: String,
@Body request: OpenDoorRequest
): Response<Unit> // Измените Response<String> на Response<Unit>
@POST("/api/employee/toggleAccess") // Метод для блокировки/разблокировки доступа
suspend fun toggleAccess(@Body request: ToggleAccessRequest): Response<Unit>
@GET("/api/workers") // Получить всех сотрудников
suspend fun getAllWorkers(): Response<List<EmployeeData>>
}
data class OpenDoorRequest(val value: String)
// Модель данных для информации о сотруднике
data class EmployeeData(
val name: String,
val position: String,
val lastVisit: String,
val avatarUrl: String? // Добавьте это поле
)
// Модель данных для запроса блокировки/разблокировки доступа
data class ToggleAccessRequest(
val login: String, // Логин сотрудника
val action: String // Действие: "block" или "unblock"
)
// Модель данных для запроса открытия двери
data class OpenDoorRequest(
val value: Long // Код для открытия двери
)

View File

@ -0,0 +1,16 @@
package ru.myitschool.work.api
import okhttp3.Interceptor
import okhttp3.Response
import java.util.Base64
class AuthInterceptor(private val username: String, private val password: String) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val credential = Base64.getEncoder().encodeToString("$username:$password".toByteArray())
val newRequest = originalRequest.newBuilder()
.addHeader("Authorization", "Basic $credential")
.build()
return chain.proceed(newRequest)
}
}

View File

@ -0,0 +1,15 @@
import okhttp3.Interceptor
import okhttp3.Response
import android.util.Log
class LoggingInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
Log.d("LoggingInterceptor", "Sending request to ${request.url} with headers ${request.headers}")
val response = chain.proceed(request)
Log.d("LoggingInterceptor", "Received response for ${response.request.url} with code ${response.code}")
return response
}
}

View File

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

View File

@ -0,0 +1,34 @@
package ru.myitschool.work.ui.main
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import ru.myitschool.work.R
import ru.myitschool.work.api.AccessLog
class AccessLogAdapter(private val accessLogs: List<AccessLog>) : RecyclerView.Adapter<AccessLogAdapter.AccessLogViewHolder>() {
class AccessLogViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val scanTime: TextView = itemView.findViewById(R.id.scan_time)
val readerId: TextView = itemView.findViewById(R.id.reader_id)
val accessType: TextView = itemView.findViewById(R.id.access_type)
}
//.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccessLogViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_access_log, parent, false)
return AccessLogViewHolder(view)
}
override fun onBindViewHolder(holder: AccessLogViewHolder, position: Int) {
val log = accessLogs[position]
holder.scanTime.text = log.scanTime
holder.readerId.text = log.readerId
holder.accessType.text = log.accessType
}
override fun getItemCount(): Int {
return accessLogs.size
}
}

View File

@ -0,0 +1,107 @@
package ru.myitschool.work.ui.main
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import ru.myitschool.work.R
import ru.myitschool.work.api.ApiService
import ru.myitschool.work.api.ToggleAccessRequest
import ru.myitschool.work.core.Constants
import ru.myitschool.work.databinding.FragmentAdminBinding
class AdminFragment : Fragment(R.layout.fragment_admin) {
private var _binding: FragmentAdminBinding? = null
private val binding get() = _binding!!
private val apiService: ApiService by lazy {
Retrofit.Builder()
.baseUrl(Constants.SERVER_ADDRESS)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
private var isAccessBlocked: Boolean = false // Переменная для отслеживания состояния доступа
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentAdminBinding.bind(view)
setupUI()
}
//.
private fun setupUI() {
binding.viewEmployeeInfo.setOnClickListener {
val login = binding.employeeLogin.text.toString()
if (login.isNotEmpty()) {
fetchEmployeeInfo(login)
} else {
Toast.makeText(requireContext(), "Введите логин сотрудника", Toast.LENGTH_SHORT).show()
}
}
binding.toggleAccess.setOnClickListener {
val login = binding.employeeLogin.text.toString()
if (login.isNotEmpty()) {
// Определяем действие на основе текущего состояния доступа
val action = if (isAccessBlocked) "unblock" else "block"
toggleEmployeeAccess(login, action)
} else {
Toast.makeText(requireContext(), "Введите логин сотрудника", Toast.LENGTH_SHORT).show()
}
}
}
private fun fetchEmployeeInfo(login: String) {
lifecycleScope.launch {
try {
val response = apiService.getEmployeeInfo(login)
if (response.isSuccessful) {
val employeeData = response.body()
employeeData?.let {
binding.employeeInfo.text = "Имя: ${it.name}, Должность: ${it.position}, Последний визит: ${it.lastVisit}"
binding.employeeInfo.visibility = View.VISIBLE
binding.toggleAccess.visibility = View.VISIBLE
// Здесь можно установить состояние доступа, если оно доступно
isAccessBlocked = false // Предположим, что доступ не заблокирован по умолчанию
}
} else {
Toast.makeText(requireContext(), "Ошибка получения данных", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
e.printStackTrace()
Toast.makeText(requireContext(), "Ошибка сети", Toast.LENGTH_SHORT).show()
}
}
}
private fun toggleEmployeeAccess(login: String, action: String) {
val request = ToggleAccessRequest(login, action)
lifecycleScope.launch {
try {
val response = apiService.toggleAccess(request)
if (response.isSuccessful) {
isAccessBlocked = !isAccessBlocked // Переключаем состояние доступа
val message = if (action == "block") "Доступ заблокирован" else "Доступ разблокирован"
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(requireContext(), "Ошибка изменения доступа", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
e.printStackTrace()
Toast.makeText(requireContext(), "Ошибка сети", Toast.LENGTH_SHORT).show()
}
}
}
override fun onDestroyView() {
_binding = null // Освобождаем binding, когда представление уничтожается
super.onDestroyView()
}
}

View File

@ -1,34 +1,34 @@
package ru.myitschool.work.ui.main
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResultListener
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import kotlinx.coroutines.Dispatchers
import com.squareup.picasso.Picasso
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import ru.myitschool.work.R
import ru.myitschool.work.SessionManager
import ru.myitschool.work.api.ApiService
import ru.myitschool.work.api.EmployeeData
import ru.myitschool.work.core.Constants
import ru.myitschool.work.databinding.FragmentMainBinding
import ru.myitschool.work.SessionManager
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import ru.myitschool.work.ui.qr.scan.QrScanDestination
import java.net.HttpURLConnection
import java.net.URL
class MainFragment : Fragment(R.layout.fragment_main) {
private var _binding: FragmentMainBinding? = null
private val binding get() = _binding
private val binding get() = _binding!!
private val apiService: ApiService by lazy {
Retrofit.Builder()
private lateinit var apiService: ApiService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
apiService = Retrofit.Builder()
.baseUrl(Constants.SERVER_ADDRESS)
.addConverterFactory(GsonConverterFactory.create())
.build()
@ -37,10 +37,10 @@ class MainFragment : Fragment(R.layout.fragment_main) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentMainBinding.bind(view) // Подключаем binding
_binding = FragmentMainBinding.bind(view)
setupUI()
fetchUserData()
fetchUserInfo()
// Проверяем, есть ли результат QR
checkQrResult()
@ -52,117 +52,93 @@ class MainFragment : Fragment(R.layout.fragment_main) {
val qrData = QrScanDestination.getDataIfExist(bundle)
if (qrData != null) {
// Если данные QR есть, переходим на экран с результатом
findNavController().navigate(R.id.qrResultFragment)
val resultBundle = QrScanDestination.packToBundle(qrData)
findNavController().navigate(R.id.qrResultFragment, resultBundle)
} else {
Toast.makeText(requireContext(), "QR данные не найдены", Toast.LENGTH_SHORT).show()
}
}
}
private fun setupUI() {
// Проверяем, что binding не null, прежде чем устанавливать слушателей
binding?.apply {
refresh.setOnClickListener { fetchUserData() }
logout.setOnClickListener { logout() }
scan.setOnClickListener { navigateToQrScan() }
binding.refresh.setOnClickListener {
fetchUserInfo()
}
binding.scanQrCode?.setOnClickListener {
// Переход к экрану сканирования QR-кода
findNavController().navigate(R.id.qrScanFragment) // Убедитесь, что у вас есть правильный ID для навигации
}
// Проверяем роль пользователя и показываем кнопку AdminPanel, если роль admin
if (SessionManager.userRole == "admin") {
binding.adminPanelButton?.visibility = View.VISIBLE
binding.adminPanelButton?.setOnClickListener {
findNavController().navigate(R.id.adminFragment) // Переход к экрану администратора
}
} else {
binding.adminPanelButton?.visibility = View.GONE // Скрываем кнопку, если не admin
}
}
private fun fetchUserData() {
private fun fetchUserInfo() {
lifecycleScope.launch {
showError(null) // Скрыть ошибку, если она была
try {
val response = apiService.getUserInfo(SessionManager.userLogin) // Получаем данные пользователя
if (response.isSuccessful) {
response.body()?.let { data ->
// Извлекаем значения из Map
val fullName = data["name"] as? String ?: "Неизвестно"
val position = data["position"] as? String ?: "Неизвестно"
val lastVisit = data["lastVisit"] as? String ?: "Неизвестно"
val photoUrl = data["photo"] as? String ?: ""
val login = SessionManager.userLogin ?: run {
binding.error.text = "Пользователь не авторизован"
binding.error.visibility = View.VISIBLE
return@launch
}
val authHeader = SessionManager.getAuthHeader() ?: run {
binding.error.text = "Ошибка авторизации"
binding.error.visibility = View.VISIBLE
return@launch
}
// Обновляем UI
updateUI(fullName, position, lastVisit, photoUrl)
try {
val response = apiService.getUserInfo(login, authHeader)
// Логируем код ответа
Log.d("MainFragment", "Response code: ${response.code()}")
if (response.isSuccessful) {
val employeeData = response.body()
employeeData?.let {
binding.fullname.text = it.name
binding.position.text = it.position
binding.lastEntry.text = it.lastVisit
// Логируем URL аватара
Log.d("MainFragment", "Avatar URL: ${it.avatarUrl}")
// Загрузка аватара с помощью Picasso
if (it.avatarUrl != null) {
Picasso.get()
.load(it.avatarUrl)
.placeholder(R.drawable.ic_refresh) // Замените на ваш ресурс-заполнитель
.error(R.drawable.ic_close) // Замените на ваш ресурс ошибки
.into(binding.photo)
binding.photo.visibility = View.VISIBLE
} else {
binding.photo.visibility = View.GONE // Скрыть, если URL нет
}
// Показываем кнопку "Сканировать QR-код" после успешного получения данных
binding.scanQrCode?.visibility = View.VISIBLE
}
} else {
showError(getString(R.string.error_loading_data)) // Показываем ошибку, если данные не загрузились
binding.error.text = "Ошибка получения данных: ${response.message()}"
binding.error.visibility = View.VISIBLE
}
} catch (e: Exception) {
showError(e.localizedMessage) // Показываем ошибку при исключении
Log.e("MainFragment", "Error fetching user info", e)
binding.error.text = "Ошибка сети: ${e.message}"
binding.error.visibility = View.VISIBLE
}
}
}
private fun updateUI(fullName: String, position1: String, lastVisit: String, photoUrl: String) {
// Проверяем, что binding не null, прежде чем обновлять UI
binding?.apply {
fullname.text = fullName
position.text = position1
lastEntry.text = lastVisit
if (photoUrl.isNotEmpty()) {
// Загружаем изображение
lifecycleScope.launch {
val bitmap = loadImageFromUrl(photoUrl)
bitmap?.let { photo.setImageBitmap(it) }
}
}
// Показываем элементы
fullname.visibility = View.VISIBLE
position.visibility = View.VISIBLE
lastEntry.visibility = View.VISIBLE
photo.visibility = View.VISIBLE
logout.visibility = View.VISIBLE
scan.visibility = View.VISIBLE
}
}
private suspend fun loadImageFromUrl(urlString: String): Bitmap? {
return withContext(Dispatchers.IO) {
try {
val url = URL(urlString)
val connection = url.openConnection() as HttpURLConnection
connection.doInput = true
connection.connect()
val inputStream = connection.inputStream
BitmapFactory.decodeStream(inputStream)
} catch (e: Exception) {
e.printStackTrace()
null
}
}
}
private fun showError(message: String?) {
// Проверяем, что binding не null, прежде чем обновлять ошибку
binding?.apply {
if (message != null) {
error.text = message
error.visibility = View.VISIBLE
// Скрываем остальные элементы, когда возникает ошибка
fullname.visibility = View.GONE
position.visibility = View.GONE
lastEntry.visibility = View.GONE
photo.visibility = View.GONE
logout.visibility = View.GONE
scan.visibility = View.GONE
} else {
error.visibility = View.GONE
}
}
}
private fun logout() {
// Очистите данные пользователя
Toast.makeText(requireContext(), getString(R.string.logged_out), Toast.LENGTH_SHORT).show()
findNavController().navigate(R.id.loginFragment) // Переход на экран входа
}
private fun navigateToQrScan() {
findNavController().navigate(R.id.qrScanFragment) // Переход на экран сканирования QR
}
override fun onDestroyView() {
_binding = null // Освобождаем binding, когда представление уничтожается
_binding = null
super.onDestroyView()
}
}

View File

@ -1,35 +1,40 @@
package ru.myitschool.work.ui.main
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import ru.myitschool.work.SessionManager
import ru.myitschool.work.api.ApiService
import ru.myitschool.work.api.EmployeeData
import javax.inject.Inject
@HiltViewModel
class MainViewModel @Inject constructor(
private val apiService: ApiService
) : ViewModel() {
private val _userInfoState = MutableStateFlow<Map<String, Any>?>(null)
val userInfoState: StateFlow<Map<String, Any>?> = _userInfoState
init {
loadUserData()
}
private val _employeeData = MutableStateFlow<EmployeeData?>(null)
val employeeData: StateFlow<EmployeeData?> get() = _employeeData
private fun loadUserData() {
private val _errorMessage = MutableStateFlow<String?>(null)
val errorMessage: StateFlow<String?> get() = _errorMessage
fun fetchUserInfo(login: String, authHeader: String) {
viewModelScope.launch {
val login = SessionManager.userLogin
if (login != null) {
val response = apiService.getUserInfo(login)
try {
val response = apiService.getUserInfo(login, authHeader)
if (response.isSuccessful) {
_userInfoState.value = response.body()
_employeeData.value = response.body()
} else {
_errorMessage.value = "Ошибка получения данных: ${response.message()}"
}
} catch (e: Exception) {
Log.e("MainViewModel", "Error fetching user info", e)
_errorMessage.value = "Ошибка сети: ${e.message}"
}
}
}
}
}

View File

@ -22,7 +22,7 @@ class RootActivity : AppCompatActivity() {
navController.setGraph(R.navigation.nav_graph) // Устанавливаем граф навигации
}
// Настраиваем кнопку "Назад"
// Настройка кнопки "Назад"
onBackPressedDispatcher.addCallback(
this,
object : OnBackPressedCallback(true) {
@ -44,4 +44,4 @@ class RootActivity : AppCompatActivity() {
}
return popBackResult || super.onSupportNavigateUp()
}
}
}

View File

@ -1,5 +1,6 @@
package ru.myitschool.work.ui.login
import ru.myitschool.work.api.ApiService
import android.os.Bundle
import android.text.Editable
import android.text.InputType
@ -7,17 +8,19 @@ import android.text.TextWatcher
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import dagger.hilt.android.AndroidEntryPoint
import ru.myitschool.work.R
import ru.myitschool.work.SessionManager
import ru.myitschool.work.databinding.FragmentLoginBinding
import ru.myitschool.work.utils.collectWhenStarted
import ru.myitschool.work.utils.visibleOrGone
import ru.myitschool.work.utils.AuthPreferences
import kotlinx.coroutines.launch
import androidx.lifecycle.lifecycleScope
@AndroidEntryPoint
class LoginFragment : Fragment(R.layout.fragment_login) {
@ -37,7 +40,7 @@ class LoginFragment : Fragment(R.layout.fragment_login) {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
super.onViewCreated(view, savedInstanceState) // Убедитесь, что этот вызов находится здесь
_binding = FragmentLoginBinding.bind(view)
setupUI()
@ -52,9 +55,11 @@ class LoginFragment : Fragment(R.layout.fragment_login) {
binding.login.setOnClickListener {
val username = binding.username.text.toString()
performLogin(username) // Вызываем метод performLogin
val password = binding.password.text.toString() // Получаем пароль
performLogin(username, password) // Передаем пароль в метод performLogin
}
// Изначально скрываем индикаторы
binding.loading.visibleOrGone(false)
binding.error.visibleOrGone(false)
}
@ -72,29 +77,44 @@ class LoginFragment : Fragment(R.layout.fragment_login) {
})
}
private fun performLogin(username: String) {
private fun performLogin(username: String, password: String) {
lifecycleScope.launch {
viewModel.authenticate(username) // Вызываем метод authenticate из ViewModel
binding.loading.visibleOrGone(true) // Показываем индикатор загрузки
viewModel.authenticate(username, password) // Передаем пароль в метод authenticate
}
}
private fun subscribe() {
viewModel.state.collectWhenStarted(this) { state ->
binding.loading.visibleOrGone(false)
lifecycleScope.launch {
viewModel.state.collect { state ->
binding.loading.visibleOrGone(false)
if (state.error != null) {
binding.error.text = state.error
binding.error.visibility = View.VISIBLE
} else if (state.success) {
binding.error.visibility = View.GONE
authPreferences.saveLoginState(true)
authPreferences.saveLogin(binding.username.text.toString()) // Сохраняем логин
Toast.makeText(context, "Авторизация прошла успешно", Toast.LENGTH_SHORT).show()
navigateToMainScreen()
if (state.maintenance) {
showMaintenanceDialog() // Показываем диалог о техработах
} else if (state.success) {
binding.error.visibility = View.GONE
authPreferences.saveLoginState(true)
authPreferences.saveLogin(binding.username.text.toString()) // Сохраняем логин
Toast.makeText(context, "Авторизация прошла успешно", Toast.LENGTH_SHORT).show()
navigateToMainScreen() // Перенаправление на следующий экран
} else if (state.error != null) {
binding.error.text = state.error
binding.error.visibility = View.VISIBLE
}
}
}
}
private fun showMaintenanceDialog() {
AlertDialog.Builder(requireContext())
.setTitle("Технические работы")
.setMessage("Проводятся техработы, пожалуйста, подождите.")
.setPositiveButton("ОК ") { dialog, _ -> dialog.dismiss() }
.setCancelable(false)
.show()
}
private fun navigateToMainScreen() {
try {
findNavController().apply {
@ -102,7 +122,7 @@ class LoginFragment : Fragment(R.layout.fragment_login) {
navigate(R.id.mainFragment)
}
} catch (e: Exception) {
Log.e("LF", "Nav_err", e)
Log.e("LoginFragment", "Nav_err", e)
Toast.makeText(context, "Ошибка перехода", Toast.LENGTH_SHORT).show()
}
}

View File

@ -1,54 +1,70 @@
package ru.myitschool.work.ui.login
import android.content.Context
import ru.myitschool.work.api.ApiService
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import ru.myitschool.work.SessionManager
import ru.myitschool.work.api.ApiService
import ru.myitschool.work.core.Constants
import javax.inject.Inject
@HiltViewModel
class LoginViewModel @Inject constructor(
@ApplicationContext private val context: Context,
private val apiService: ApiService
) : ViewModel() {
private val _state = MutableStateFlow(LoginState())
val state = _state.asStateFlow()
val state: StateFlow<LoginState> get() = _state
private val apiService: ApiService by lazy {
Retrofit.Builder()
.baseUrl(Constants.SERVER_ADDRESS)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
fun authenticate(username: String, password: String) {
Log.d("LoginViewModel", "Authenticating user: $username") // Логируем начало аутентификации
fun authenticate(username: String) {
if (isValidUsername(username)) {
viewModelScope.launch {
val response = apiService.authenticate(username)
if (response.isSuccessful) {
SessionManager.userLogin = username
_state.value = LoginState(success = true)
} else {
_state.value = LoginState(error = "Ошибка авторизации")
try {
Log.d("LoginViewModel", "Sending authentication request to server") // Логируем отправку запроса
val response = apiService.authenticate(username, password)
Log.d("LoginViewModel", "Response code: ${response.code()}") // Логируем код ответа
// Проверяем код ответа
when (response.code()) {
200 -> {
SessionManager.userLogin = username
_state.value = LoginState(success = true) // Успешная авторизация
Log.d("LoginViewModel", "Authentication successful") // Логируем успешную аутентификацию
}
400 -> {
_state.value = LoginState(error = "Ошибка авторизации: Неверные учетные данные.")
Log.d("LoginViewModel", "Authentication failed: Invalid credentials") // Логируем ошибку
}
else -> {
_state.value = LoginState(error = "Ошибка авторизации: ${response.message()}")
Log.d("LoginViewModel", "Authentication failed: ${response.message()}") // Логируем ошибку
}
}
} catch (e: Exception) {
e.printStackTrace()
_state.value = LoginState(error = "Ошибка сети. Проверьте подключение к интернету.")
Log.e("LoginViewModel", "Network error: ${e.message}") // Логируем ошибку сети
}
}
} else {
_state.value = LoginState(error = "Неправильный логин")
Log.d("LoginViewModel", "Invalid username: $username") // Логируем неправильный логин
}
}
private fun isValidUsername(username: String): Boolean {
return username.length >= 3 && !username.first().isDigit() && username.all { it.isLetterOrDigit() }
return username.isNotEmpty() // Пример проверки логина
}
}
data class LoginState(val success: Boolean = false, val error: String? = null)
}
// Состояние аутентификации
data class LoginState(
val success: Boolean = false, // Успешность аутентификации
val error: String? = null, // Сообщение об ошибке
val maintenance: Boolean = false // Состояние техобслуживания
)

View File

@ -5,8 +5,10 @@ import androidx.navigation.fragment.findNavController
import ru.myitschool.work.SessionManager
import ru.myitschool.work.api.OpenDoorRequest
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.util.Base64
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
@ -19,7 +21,9 @@ import ru.myitschool.work.core.Constants
import ru.myitschool.work.databinding.FragmentQrScanResultBinding
class QrResult : Fragment(R.layout.fragment_qr_scan_result) {
private lateinit var binding: FragmentQrScanResultBinding
private var _binding: FragmentQrScanResultBinding? = null
private val binding get() = _binding!!
private lateinit var apiService: ApiService
override fun onCreateView(
@ -27,7 +31,7 @@ class QrResult : Fragment(R.layout.fragment_qr_scan_result) {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentQrScanResultBinding.inflate(inflater, container, false)
_binding = FragmentQrScanResultBinding.inflate(inflater, container, false)
apiService = Retrofit.Builder()
.baseUrl(Constants.SERVER_ADDRESS)
.addConverterFactory(GsonConverterFactory.create())
@ -40,8 +44,12 @@ class QrResult : Fragment(R.layout.fragment_qr_scan_result) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Получаем данные из аргументов
val qrData = QrScanDestination.getDataIfExist(requireArguments())
Log.d("QrResult", "QR Data: $qrData") // Логируем полученные данные
if (qrData != null) {
binding.result.text = "Результат сканирования: $qrData" // Отображаем результат сканирования
sendRequestToServer(qrData)
} else {
binding.result.text = "Вход был отменён/Operation was cancelled"
@ -55,15 +63,40 @@ class QrResult : Fragment(R.layout.fragment_qr_scan_result) {
private fun sendRequestToServer(qrData: String) {
lifecycleScope.launch {
try {
val response = apiService.openDoor(SessionManager.userLogin, OpenDoorRequest(qrData))
val qrValue = qrData.toLong() // Преобразуем данные QR-кода в Long
val login = SessionManager.userLogin ?: "default_login" // Замените на ваш логин
val password = "your_password" // Замените на ваш пароль
val authHeader = "Basic " + Base64.encodeToString("$login:$password".toByteArray(), Base64.NO_WRAP)
// Логируем данные перед отправкой
Log.d("QrResult", "Sending request with QR value: $qrValue and authHeader: $authHeader")
// Создаем объект запроса
val request = OpenDoorRequest(qrValue)
// Вызываем метод openDoor с правильными параметрами
val response = apiService.openDoor(authHeader, request)
// Логируем код ответа и тело ответа
Log.d("QrResult", "Response code: ${response.code()}")
Log.d("QrResult", "Response body: ${response.body()}")
if (response.isSuccessful) {
binding.result.text = "Успешно/Success"
binding.result.text = "Door Opened" // Сообщение о том, что дверь открыта
} else {
binding.result.text = "Что-то пошло не так/Something wrong"
binding.result.text = "Door Closed" // Сообщение о том, что дверь закрыта
}
} catch (e: NumberFormatException) {
binding.result.text = "Некорректные данные QR-кода"
} catch (e: Exception) {
binding.result.text = "Что-то пошло не так/Something wrong"
binding.result.text = "Что-то пошло не так/Something went wrong: ${e.message}"
Log.e("QrResult", "Error sending request to server", e)
}
}
}
override fun onDestroyView() {
_binding = null
super.onDestroyView()
}
}

View File

@ -0,0 +1,24 @@
package ru.myitschool.work.utils
import android.view.View
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
// Функция для сбора данных из Flow, когда жизненный цикл находится в состоянии STARTED
fun <T> Flow<T>.collectWhenStarted(lifecycleOwner: LifecycleOwner, collector: (T) -> Unit) {
lifecycleOwner.lifecycleScope.launch {
lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
collect { value -> collector(value) }
}
}
}
// Функция для управления видимостью View
fun View?.visibleOrGone(isVisible: Boolean) {
this?.visibility = if (isVisible) View.VISIBLE else View.GONE
}

View File

@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#F0F0F0" />
<corners android:radius="16dp" />
</shape>

View File

@ -0,0 +1,60 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/enter_worker_username"
android:textSize="18sp"
android:layout_marginBottom="8dp" />
<EditText
android:id="@+id/employee_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/worker_username"
android:padding="12dp"
android:background="@drawable/ic_android_black_24dp"
android:inputType="text" />
<Button
android:id="@+id/view_employee_info"
android:layout_width="280dp"
android:layout_gravity="center_horizontal"
android:layout_height="wrap_content"
android:text="@string/watch_info_about_worker"
android:backgroundTint="@color/colorPrimary"
app:cornerRadius="16dp"
android:layout_marginTop="16dp" />
<TextView
android:id="@+id/employee_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textSize="16sp"
android:visibility="gone" />
<Button
android:id="@+id/toggle_access"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/block_or_unblock"
android:layout_marginTop="16dp"
android:visibility="gone" />
<Button
android:id="@+id/close"
android:layout_width="280dp"
android:layout_gravity="center_horizontal"
android:layout_height="wrap_content"
android:text="@string/back"
android:backgroundTint="@color/colorPrimary"
app:cornerRadius="16dp"
android:layout_marginTop="0dp" />
</LinearLayout>

View File

@ -0,0 +1,56 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp"
android:background="@android:color/white">
<EditText
android:id="@+id/username"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:hint="@string/username_hint"
android:inputType="text"
android:padding="12dp"
android:background="@drawable/ic_android_black_24dp"
android:layout_marginBottom="16dp"/>
<EditText
android:id="@+id/password"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:hint="@string/password_hint"
android:inputType="textPassword"
android:padding="12dp"
android:background="@drawable/ic_android_black_24dp"
android:layout_marginBottom="16dp"/>
<Button
android:id="@+id/login"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:text="@string/login_button"
android:backgroundTint="@color/colorPrimary"
android:textColor="@android:color/white"
android:padding="12dp"
app:cornerRadius="16dp"
android:layout_marginBottom="16dp"/>
<TextView
android:id="@+id/error"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:gravity="center_horizontal"
android:textColor="@android:color/holo_red_light"
android:layout_marginBottom="16dp"/>
<ProgressBar
android:id="@+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
</LinearLayout>

View File

@ -0,0 +1,124 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:gravity="bottom"
android:padding="16dp"
android:background="@android:color/white">
<LinearLayout
android:layout_width="250dp"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- Поле для ФИО -->
<TextView
android:id="@+id/fullname"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="@string/fullname_label"
android:textSize="18sp"
android:layout_marginBottom="5dp"
android:visibility="gone" />
<!-- Фото пользователя. -->
<ImageView
android:id="@+id/photo"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:contentDescription="@string/photo_description"
android:layout_marginBottom="5dp"
android:visibility="gone" />
<!-- Поле для должности -->
<TextView
android:id="@+id/position"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/position_label"
android:layout_marginBottom="5dp"
android:visibility="gone" />
<!-- Поле для даты последнего входа -->
<TextView
android:id="@+id/lastEntry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="2024-02-31 08:31"
android:layout_marginBottom="75dp"
android:visibility="gone" />
<!-- RecyclerView для списка проходов -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:visibility="gone" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- Кнопка обновления -->
<Button
android:id="@+id/refresh"
android:layout_width="280dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/refresh"
app:cornerRadius="16dp"
android:backgroundTint="@color/colorPrimary"
android:textColor="@android:color/white"
android:padding="12dp"/>
<!-- Поле ошибки -->
<TextView
android:id="@+id/error"
android:layout_width="280dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:text="@string/error_placeholder"
android:textColor="@android:color/holo_red_dark"
android:visibility="gone" />
<!-- Кнопки -->
<Button
android:id="@+id/scan"
android:layout_width="280dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/scan_qr_code"
app:cornerRadius="16dp"
android:backgroundTint="@color/colorPrimary"
android:textColor="@android:color/white"
android:visibility="gone" />
<Button
android:id="@+id/logout"
android:layout_width="280dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/logout"
app:cornerRadius="16dp"
android:backgroundTint="@color/colorPrimary"
android:textColor="@android:color/white"
android:visibility="gone" />
<Button
android:id="@+id/admin_panel"
android:layout_width="280dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/admin_panel"
app:cornerRadius="16dp"
android:backgroundTint="@color/colorPrimary"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>

View File

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

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/qrScanResultLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:gravity="center">
<TextView
android:id="@+id/result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/result"
android:textSize="18sp"
android:gravity="center"
android:padding="16dp" />
<Button
android:id="@+id/close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/close_button"
app:cornerRadius="16dp"
android:backgroundTint="@color/colorPrimary"
android:textColor="@android:color/white"
android:padding="12dp"
android:layout_marginTop="24dp" />
</LinearLayout>

View File

@ -0,0 +1,61 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/enter_worker_username"
android:textSize="18sp"
android:layout_marginBottom="8dp" />
<EditText
android:id="@+id/employee_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/worker_username"
android:padding="12dp"
android:background="@drawable/ic_android_black_24dp"
android:inputType="text" />
<Button
android:id="@+id/view_employee_info"
android:layout_width="280dp"
android:layout_gravity="center_horizontal"
android:layout_height="wrap_content"
android:text="@string/watch_info_about_worker"
android:backgroundTint="@color/colorPrimary"
app:cornerRadius="16dp"
android:layout_marginTop="16dp" />
<TextView
android:id="@+id/employee_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textSize="16sp"
android:visibility="gone" />
<Button
android:id="@+id/toggle_access"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/block_or_unblock"
android:layout_marginTop="16dp"
android:visibility="gone" />
<Button
android:id="@+id/close"
android:layout_width="280dp"
android:layout_gravity="center_horizontal"
android:layout_height="wrap_content"
android:text="@string/back"
android:backgroundTint="@color/colorPrimary"
app:cornerRadius="16dp"
android:layout_marginTop="0dp" />
</LinearLayout>

View File

@ -1,31 +1,52 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp"
android:background="@android:color/white">
<EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Введите логин"
android:inputType="text"/>
android:hint="@string/username_hint"
android:inputType="text"
android:padding="12dp"
android:background="@drawable/ic_android_black_24dp"
android:layout_marginBottom="16dp"/>
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password_hint"
android:inputType="textPassword"
android:padding="12dp"
android:background="@drawable/ic_android_black_24dp"
android:layout_marginBottom="16dp"/>
<Button
android:id="@+id/login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Войти"
android:visibility="gone" />
android:text="@string/login_button"
android:backgroundTint="@color/colorPrimary"
android:textColor="@android:color/white"
android:padding="12dp"
app:cornerRadius="16dp"
android:layout_marginBottom="16dp"/>
<TextView
android:id="@+id/error"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:textColor="@android:color/holo_red_light" />
android:gravity="center_horizontal"
android:textColor="@android:color/holo_red_light"
android:layout_marginBottom="16dp"/>
<!-- Добавьте ProgressBar для индикатора загрузки -->
<ProgressBar
android:id="@+id/loading"
android:layout_width="wrap_content"

View File

@ -1,72 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:padding="16dp">
<!-- Поле для ФИО -->
<TextView
android:id="@+id/fullname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Имя Фамилия"
android:textSize="18sp"
android:visibility="gone" />
android:textSize="18sp" />
<!-- Фото пользователя -->
<ImageView
android:id="@+id/photo"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:contentDescription="@string/photo_description"
android:visibility="gone" />
android:contentDescription="@string/photo_description" />
<!-- Поле для должности -->
<TextView
android:id="@+id/position"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Должность"
android:visibility="gone" />
android:textSize="16sp" />
<!-- Поле для даты последнего входа -->
<TextView
android:id="@+id/lastEntry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="2024-02-31 08:31"
android:visibility="gone" />
<!-- Кнопки -->
<Button
android:id="@+id/logout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/logout"
android:visibility="gone" />
android:textSize="14sp" />
<Button
android:id="@+id/refresh"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/colorPrimary"
android:textColor="@android:color/white"
android:padding="12dp"
app:cornerRadius="16dp"
android:text="@string/refresh" />
<Button
android:id="@+id/scan"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/scan_qr_code"
android:visibility="gone" />
<!-- Поле ошибки -->
<TextView
android:id="@+id/error"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/error_placeholder"
android:textColor="@android:color/holo_red_dark"
android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<Button
android:id="@+id/scan_qr_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/colorPrimary"
android:textColor="@android:color/white"
android:padding="12dp"
app:cornerRadius="16dp"
android:text="Сканировать QR-код"
android:visibility="gone" />
<Button
android:id="@+id/adminPanelButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/colorPrimary"
android:textColor="@android:color/white"
android:padding="12dp"
app:cornerRadius="16dp"
android:text="Admin Panel"
android:visibility="gone" />
</LinearLayout>

View File

@ -30,6 +30,7 @@
android:contentDescription="@string/close_button"
android:src="@drawable/ic_close"
app:elevation="0dp"
android:backgroundTint="@color/colorPrimary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/qrScanResultLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -11,7 +12,7 @@
android:id="@+id/result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Результат"
android:text="@string/result"
android:textSize="18sp"
android:gravity="center"
android:padding="16dp" />
@ -20,8 +21,10 @@
android:id="@+id/close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Закрыть"
android:text="@string/close_button"
app:cornerRadius="16dp"
android:backgroundTint="@color/colorPrimary"
android:textColor="@android:color/white"
android:padding="12dp"
android:layout_marginTop="24dp" />
</LinearLayout>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/scan_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp" />
<TextView
android:id="@+id/reader_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp" />
<TextView
android:id="@+id/access_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp" />
</LinearLayout>

View File

@ -22,6 +22,12 @@
android:label="Main Fragment"
tools:layout="@layout/fragment_main" />
<fragment
android:id="@+id/adminFragment"
android:name="ru.myitschool.work.ui.main.AdminFragment"
android:label="Admin Fragment"
tools:layout="@layout/fragment_admin" />
<fragment
android:id="@+id/qrResultFragment"
android:name="ru.myitschool.work.ui.qr.result.QrResult"

View File

@ -13,7 +13,8 @@
<string name="logged_out">You have logged out of your account</string>
<!-- Строки для экрана авторизации -->
<string name="username_hint">Enter your username</string>
<string name="username_hint">Enter username</string>
<string name="password_hint">Enter the password</string>
<string name="login_button">Enter</string>
<string name="error_invalid_username">The login is invalid. Check the entered data.</string>
<string name="error_empty_username">The login field must not be empty.</string>
@ -31,4 +32,13 @@
<string name="qr_scan_success">Successfully</string>
<string name="qr_scan_failure">Something went wrong.</string>
<string name="qr_scan_cancelled">Operation was cancelled</string>
<string name="close_button">Close</string>
<string name="result">Result</string>
<!-- Админ панель -->
<string name="enter_worker_username">Enter the employee\'s username:</string>
<string name="worker_username">Employee\'s login</string>
<string name="watch_info_about_worker">View employee information</string>
<string name="block_or_unblock">Block/Unblock access</string>
<string name="back">Back</string>
</resources>

View File

@ -7,4 +7,6 @@
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="colorPrimary">#2196F3</color>
<color name="light_gray">#F0F0F0</color>
</resources>

View File

@ -1,35 +1,45 @@
<resources>
<string name="app_name">NTO Pass</string>
<string name="welcome_message">Добро пожаловать в приложение!</string>
<string name="navigate">Перейти</string>
<string name="app_name" translatable="false">NTO Pass</string>
<string name="welcome_message">Добро пожаловать в приложение!</string>
<string name="navigate">Перейти</string>
<string name="photo_description">Фото пользователя</string>
<string name="logout">Выйти</string>
<string name="refresh">Обновить</string>
<string name="scan_qr_code">Сканировать QR</string>
<string name="error_placeholder">Произошла ошибка</string>
<string name="error_loading_data">Ошибка загрузки данных</string>
<string name="logged_out">Вы вышли из аккаунта</string>
<string name="photo_description">Фото пользователя</string>
<string name="logout">Выйти</string>
<string name="refresh">Обновить</string>
<string name="scan_qr_code">Сканировать QR</string>
<string name="error_placeholder">Произошла ошибка</string>
<string name="error_loading_data">Ошибка загрузки данных</string>
<string name="logged_out">Вы вышли из аккаунта</string>
<!-- Строки для экрана авторизации -->
<string name="username_hint">Введите логин</string>
<string name="login_button">Войти</string>
<string name="error_invalid_username">Логин недействителен. Проверьте введенные данные.</string>
<string name="error_empty_username">Поле логина не должно быть пустым.</string>
<string name="error_username_too_short">Логин должен содержать не менее 3 символов.</string>
<string name="error_username_starts_with_digit">Логин не должен начинаться с цифры.</string>
<string name="error_username_invalid_characters">Логин может содержать только латинские буквы и цифры.</string>
<!-- Строки для экрана авторизации -->
<string name="username_hint">Введите логин</string>
<string name="password_hint">Введите пароль</string>
<string name="login_button">Войти</string>
<string name="error_invalid_username">Логин недействителен. Проверьте введенные данные.</string>
<string name="error_empty_username">Поле логина не должно быть пустым.</string>
<string name="error_username_too_short">Логин должен содержать не менее 3 символов.</string>
<string name="error_username_starts_with_digit">Логин не должен начинаться с цифры.</string>
<string name="error_username_invalid_characters">Логин может содержать только латинские буквы и цифры.</string>
<!-- Строки для главного экрана -->
<string name="fullname_label">ФИО</string>
<string name="position_label">Должность</string>
<string name="last_entry_label">Время последнего входа</string>
<string name="error_no_user_data">Нет данных о пользователе.</string>
<!-- Строки для главного экрана -->
<string name="fullname_label">ФИО</string>
<string name="position_label">Должность</string>
<string name="last_entry_label">Время последнего входа</string>
<string name="error_no_user_data">Нет данных о пользователе.</string>
<string name="admin_panel" translatable="false">Admin Panel</string>
<!-- Строки для экрана сканирования QR-кода -->
<string name="qr_scan_success">Успешно</string>
<string name="qr_scan_failure">Что-то пошло не так</string>
<string name="qr_scan_cancelled">Вход был отменён/Operation was cancelled</string>
<!-- Строки для экрана сканирования QR-кода -->
<string name="qr_scan_success">Успешно</string>
<string name="qr_scan_failure">Что-то пошло не так</string>
<string name="qr_scan_cancelled">Вход был отменён / Operation was cancelled</string>
<string name="close_button">Закрыть</string>
<string name="result">Результат</string>
<!-- Админ панель -->
<string name="enter_worker_username">Введите логин сотрудника:</string>
<string name="worker_username">Логин сотрудника</string>
<string name="watch_info_about_worker">Просмотреть информацию о сотруднике</string>
<string name="block_or_unblock">Блокировать/Разблокировать доступ</string>
<string name="back">Назад</string>
</resources>

View File

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

View File

@ -4,5 +4,6 @@ plugins {
kotlinJvm version Version.Kotlin.language apply false
kotlinAnnotationProcessor version Version.Kotlin.language apply false
id("com.google.dagger.hilt.android") version "2.51.1" apply false
id("org.jetbrains.kotlin.android") version "1.9.24" apply false
}