Перенос
							
								
								
									
										26
									
								
								.gitea/workflows/automerge-with-core.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,26 @@ | ||||
| name: Merge core/template-android-project to this repo | ||||
| 
 | ||||
| env: | ||||
|   CORE_REPO: "https://git.sicampus.ru/core/template-android-project.git" | ||||
|   TOKEN: ${{ secrets.PUSH_TOKEN }} | ||||
| 
 | ||||
| run-name: Merge core/template-android-project to ${{ gitea.repository }} | ||||
| on: | ||||
|   schedule: | ||||
|     - cron: '@daily' | ||||
| 
 | ||||
| 
 | ||||
| jobs: | ||||
|   merge-if-needed: | ||||
|     if: ${{ !contains(gitea.repository, 'core/template-android-project' ) }} | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - run: echo "Merge core/template-android-project to ${{ gitea.repository }}" | ||||
|       - name: Check out repository code | ||||
|         uses: actions/checkout@v4 | ||||
|       - name: Sync repos | ||||
|         uses: Vova-SH/sync-upstream-repo@1.0.5 | ||||
|         with: | ||||
|           upstream_repo: ${{ env.CORE_REPO }} | ||||
|           token: ${{ env.TOKEN }} | ||||
|           spawn_logs: false | ||||
							
								
								
									
										97
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,89 +1,10 @@ | ||||
| # ---> Android | ||||
| # Gradle files | ||||
| .gradle/ | ||||
| build/ | ||||
| 
 | ||||
| # Local configuration file (sdk path, etc) | ||||
| local.properties | ||||
| 
 | ||||
| # Log/OS Files | ||||
| *.log | ||||
| 
 | ||||
| # Android Studio generated files and folders | ||||
| captures/ | ||||
| .externalNativeBuild/ | ||||
| .cxx/ | ||||
| *.apk | ||||
| output.json | ||||
| 
 | ||||
| # IntelliJ | ||||
| *.iml | ||||
| .idea/ | ||||
| misc.xml | ||||
| deploymentTargetDropDown.xml | ||||
| render.experimental.xml | ||||
| 
 | ||||
| # Keystore files | ||||
| *.jks | ||||
| *.keystore | ||||
| 
 | ||||
| # Google Services (e.g. APIs or Firebase) | ||||
| google-services.json | ||||
| 
 | ||||
| # Android Profiling | ||||
| *.hprof | ||||
| 
 | ||||
| # ---> Java | ||||
| # Compiled class file | ||||
| *.class | ||||
| 
 | ||||
| # Log file | ||||
| *.log | ||||
| 
 | ||||
| # BlueJ files | ||||
| *.ctxt | ||||
| 
 | ||||
| # Mobile Tools for Java (J2ME) | ||||
| .mtj.tmp/ | ||||
| 
 | ||||
| # Package Files # | ||||
| *.jar | ||||
| *.war | ||||
| *.nar | ||||
| *.ear | ||||
| *.zip | ||||
| *.tar.gz | ||||
| *.rar | ||||
| 
 | ||||
| # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml | ||||
| hs_err_pid* | ||||
| replay_pid* | ||||
| 
 | ||||
| # ---> Kotlin | ||||
| # Compiled class file | ||||
| *.class | ||||
| 
 | ||||
| # Log file | ||||
| *.log | ||||
| 
 | ||||
| # BlueJ files | ||||
| *.ctxt | ||||
| 
 | ||||
| # Mobile Tools for Java (J2ME) | ||||
| .mtj.tmp/ | ||||
| 
 | ||||
| # Package Files # | ||||
| *.jar | ||||
| *.war | ||||
| *.nar | ||||
| *.ear | ||||
| *.zip | ||||
| *.tar.gz | ||||
| *.rar | ||||
| 
 | ||||
| # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml | ||||
| hs_err_pid* | ||||
| replay_pid* | ||||
| 
 | ||||
| # Kotlin Gradle plugin data, see https://kotlinlang.org/docs/whatsnew20.html#new-directory-for-kotlin-data-in-gradle-projects | ||||
| .kotlin/ | ||||
| .gradle | ||||
| /local.properties | ||||
| /.idea | ||||
| .DS_Store | ||||
| /build | ||||
| /captures | ||||
| .externalNativeBuild | ||||
| .cxx | ||||
| local.properties | ||||
|  | ||||
							
								
								
									
										6
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,6 @@ | ||||
| [submodule "gradle"] | ||||
| 	path = gradle | ||||
| 	url = https://git.sicampus.ru/core/gradle.git | ||||
| [submodule "buildSrc"] | ||||
| 	path = buildSrc | ||||
| 	url = https://git.sicampus.ru/core/dependecies.git | ||||
							
								
								
									
										2
									
								
								.gradle/buildOutputCleanup/cache.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,2 @@ | ||||
| #Tue Feb 18 18:22:47 MSK 2025 | ||||
| gradle.version=8.9 | ||||
							
								
								
									
										2
									
								
								.gradle/config.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,2 @@ | ||||
| #Tue Feb 18 18:22:40 MSK 2025 | ||||
| java.home=C\:\\Program Files\\Android\\Android Studio\\jbr | ||||
							
								
								
									
										121
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @ -1,2 +1,121 @@ | ||||
| # Androck-front | ||||
| [](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 и ожидаются определенные корректные ответы. | ||||
| Сервер и приложение тестируются независимо. | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										1
									
								
								app/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1 @@ | ||||
| /build | ||||
							
								
								
									
										69
									
								
								app/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,69 @@ | ||||
| plugins { | ||||
|     kotlinAndroid | ||||
|     androidApplication | ||||
|     jetbrainsKotlinSerialization version Version.Kotlin.language | ||||
|     kotlinAnnotationProcessor | ||||
|     id("com.google.dagger.hilt.android").version("2.51.1") | ||||
| } | ||||
| 
 | ||||
| val packageName = "ru.myitschool.work" | ||||
| 
 | ||||
| android { | ||||
|     namespace = packageName | ||||
|     compileSdk = Version.Android.Sdk.compile | ||||
| 
 | ||||
|     defaultConfig { | ||||
|         applicationId = packageName | ||||
|         minSdk = Version.Android.Sdk.min | ||||
|         targetSdk = Version.Android.Sdk.target | ||||
|         versionCode = 1 | ||||
|         versionName = "1.0" | ||||
| 
 | ||||
|         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" | ||||
|     } | ||||
| 
 | ||||
|     buildFeatures.viewBinding = true | ||||
| 
 | ||||
|     compileOptions { | ||||
|         sourceCompatibility = Version.Kotlin.javaSource | ||||
|         targetCompatibility = Version.Kotlin.javaSource | ||||
|     } | ||||
| 
 | ||||
|     kotlinOptions { | ||||
|         jvmTarget = Version.Kotlin.jvmTarget | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| dependencies { | ||||
|     defaultLibrary() | ||||
| 
 | ||||
|     implementation(Dependencies.AndroidX.activity) | ||||
|     implementation(Dependencies.AndroidX.fragment) | ||||
|     implementation(Dependencies.AndroidX.constraintLayout) | ||||
| 
 | ||||
|     implementation(Dependencies.AndroidX.Navigation.fragment) | ||||
|     implementation(Dependencies.AndroidX.Navigation.navigationUi) | ||||
| 
 | ||||
|     implementation(Dependencies.Retrofit.library) | ||||
|     implementation(Dependencies.Retrofit.gsonConverter) | ||||
| 
 | ||||
|     implementation("com.squareup.picasso:picasso:2.8") | ||||
|     implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1") | ||||
|     implementation("androidx.datastore:datastore-preferences:1.1.1") | ||||
|     implementation("com.google.mlkit:barcode-scanning:17.3.0") | ||||
| 
 | ||||
|     val cameraX = "1.3.4" | ||||
|     implementation("androidx.camera:camera-core:$cameraX") | ||||
|     implementation("androidx.camera:camera-camera2:$cameraX") | ||||
|     implementation("androidx.camera:camera-lifecycle:$cameraX") | ||||
|     implementation("androidx.camera:camera-view:$cameraX") | ||||
|     implementation("androidx.camera:camera-mlkit-vision:1.4.0-rc04") | ||||
| 
 | ||||
|     val hilt = "2.51.1" | ||||
|     implementation("com.google.dagger:hilt-android:$hilt") | ||||
|     kapt("com.google.dagger:hilt-android-compiler:$hilt") | ||||
| } | ||||
| 
 | ||||
| kapt { | ||||
|     correctErrorTypes = true | ||||
| } | ||||
							
								
								
									
										21
									
								
								app/proguard-rules.pro
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,21 @@ | ||||
| # Add project specific ProGuard rules here. | ||||
| # You can control the set of applied configuration files using the | ||||
| # proguardFiles setting in build.gradle. | ||||
| # | ||||
| # For more details, see | ||||
| #   http://developer.android.com/guide/developing/tools/proguard.html | ||||
| 
 | ||||
| # If your project uses WebView with JS, uncomment the following | ||||
| # and specify the fully qualified class name to the JavaScript interface | ||||
| # class: | ||||
| #-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||||
| #   public *; | ||||
| #} | ||||
| 
 | ||||
| # Uncomment this to preserve the line number information for | ||||
| # debugging stack traces. | ||||
| #-keepattributes SourceFile,LineNumberTable | ||||
| 
 | ||||
| # If you keep the line number information, uncomment this to | ||||
| # hide the original source file name. | ||||
| #-renamesourcefileattribute SourceFile | ||||
							
								
								
									
										
											BIN
										
									
								
								app/release/app-release.apk
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								app/release/baselineProfiles/0/app-release.dm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								app/release/baselineProfiles/1/app-release.dm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										37
									
								
								app/release/output-metadata.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,37 @@ | ||||
| { | ||||
|   "version": 3, | ||||
|   "artifactType": { | ||||
|     "type": "APK", | ||||
|     "kind": "Directory" | ||||
|   }, | ||||
|   "applicationId": "ru.myitschool.work", | ||||
|   "variantName": "release", | ||||
|   "elements": [ | ||||
|     { | ||||
|       "type": "SINGLE", | ||||
|       "filters": [], | ||||
|       "attributes": [], | ||||
|       "versionCode": 1, | ||||
|       "versionName": "1.0", | ||||
|       "outputFile": "app-release.apk" | ||||
|     } | ||||
|   ], | ||||
|   "elementType": "File", | ||||
|   "baselineProfiles": [ | ||||
|     { | ||||
|       "minApi": 28, | ||||
|       "maxApi": 30, | ||||
|       "baselineProfiles": [ | ||||
|         "baselineProfiles/1/app-release.dm" | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "minApi": 31, | ||||
|       "maxApi": 2147483647, | ||||
|       "baselineProfiles": [ | ||||
|         "baselineProfiles/0/app-release.dm" | ||||
|       ] | ||||
|     } | ||||
|   ], | ||||
|   "minSdkVersionForDexing": 24 | ||||
| } | ||||
							
								
								
									
										33
									
								
								app/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,33 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools"> | ||||
| 
 | ||||
|     <uses-feature android:name="android.hardware.camera.any" /> | ||||
| 
 | ||||
|     <uses-permission android:name="android.permission.CAMERA" /> | ||||
|     <uses-permission android:name="android.permission.INTERNET" /> | ||||
| 
 | ||||
|     <application | ||||
|         android:name=".App" | ||||
|         android:allowBackup="true" | ||||
|         android:dataExtractionRules="@xml/data_extraction_rules" | ||||
|         android:fullBackupContent="@xml/backup_rules" | ||||
|         android:icon="@mipmap/ic_launcher" | ||||
|         android:label="@string/app_name" | ||||
|         android:networkSecurityConfig="@xml/network_security_config" | ||||
|         android:roundIcon="@mipmap/ic_launcher_round" | ||||
|         android:supportsRtl="true" | ||||
|         android:theme="@style/Theme.Default" | ||||
|         tools:targetApi="31"> | ||||
|         <activity | ||||
|             android:name=".ui.RootActivity" | ||||
|             android:exported="true"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.MAIN" /> | ||||
| 
 | ||||
|                 <category android:name="android.intent.category.LAUNCHER" /> | ||||
|             </intent-filter> | ||||
|         </activity> | ||||
|     </application> | ||||
| 
 | ||||
| </manifest> | ||||
							
								
								
									
										7
									
								
								app/src/main/java/ru/myitschool/work/App.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,7 @@ | ||||
| package ru.myitschool.work | ||||
| 
 | ||||
| import android.app.Application | ||||
| import dagger.hilt.android.HiltAndroidApp | ||||
| 
 | ||||
| @HiltAndroidApp | ||||
| class App : Application() | ||||
							
								
								
									
										10
									
								
								app/src/main/java/ru/myitschool/work/api/ApiServiceLogin.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,10 @@ | ||||
| package ru.myitschool.work.api | ||||
| 
 | ||||
| import retrofit2.Call | ||||
| import retrofit2.http.GET | ||||
| import retrofit2.http.Path | ||||
| 
 | ||||
| interface ApiServiceLogin { | ||||
|     @GET("api/{login}/auth") | ||||
|     fun authenticate(@Path("login") login: String): Call<Void> | ||||
| } | ||||
							
								
								
									
										10
									
								
								app/src/main/java/ru/myitschool/work/api/ApiServiceMain.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,10 @@ | ||||
| package ru.myitschool.work.api | ||||
| 
 | ||||
| import retrofit2.Call | ||||
| import retrofit2.http.GET | ||||
| import retrofit2.http.Path | ||||
| 
 | ||||
| interface ApiServiceMain { | ||||
|     @GET("api/{login}/info") | ||||
|     fun getDataUser(@Path("login") login: String): Call<UserInfo> | ||||
| } | ||||
							
								
								
									
										30
									
								
								app/src/main/java/ru/myitschool/work/api/NetworkModule.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,30 @@ | ||||
| package ru.myitschool.work.api | ||||
| 
 | ||||
| import dagger.Module | ||||
| import dagger.Provides | ||||
| import dagger.hilt.InstallIn | ||||
| import dagger.hilt.components.SingletonComponent | ||||
| import javax.inject.Singleton | ||||
| 
 | ||||
| @Module | ||||
| @InstallIn(SingletonComponent::class) | ||||
| object NetworkModule { | ||||
| 
 | ||||
|     @Provides | ||||
|     @Singleton | ||||
|     fun provideRetrofitClient(): RetrofitClient { | ||||
|         return RetrofitClient() | ||||
|     } | ||||
| 
 | ||||
|     @Provides | ||||
|     @Singleton | ||||
|     fun provideApiServiceLogin(retrofitClient: RetrofitClient): ApiServiceLogin { | ||||
|         return retrofitClient.getApiServiceLogin() | ||||
|     } | ||||
| 
 | ||||
|     @Provides | ||||
|     @Singleton | ||||
|     fun provideApiServiceMain(retrofitClient: RetrofitClient): ApiServiceMain { | ||||
|         return retrofitClient.getApiServiceMain() | ||||
|     } | ||||
| } | ||||
							
								
								
									
										33
									
								
								app/src/main/java/ru/myitschool/work/api/RetrofitClient.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,33 @@ | ||||
| package ru.myitschool.work.api | ||||
| 
 | ||||
| import okhttp3.OkHttpClient | ||||
| import retrofit2.Retrofit | ||||
| import retrofit2.converter.gson.GsonConverterFactory | ||||
| import ru.myitschool.work.core.Constants.SERVER_ADDRESS | ||||
| import java.util.concurrent.TimeUnit | ||||
| 
 | ||||
| class RetrofitClient { | ||||
| 
 | ||||
|     private val serverAddress = SERVER_ADDRESS | ||||
| 
 | ||||
|     private val httpClient = OkHttpClient.Builder() | ||||
|         .connectTimeout(30, TimeUnit.SECONDS) | ||||
|         .readTimeout(30, TimeUnit.SECONDS) | ||||
|         .build() | ||||
| 
 | ||||
|     private val retrofit: Retrofit by lazy { | ||||
|         Retrofit.Builder() | ||||
|             .baseUrl(serverAddress) | ||||
|             .client(httpClient) | ||||
|             .addConverterFactory(GsonConverterFactory.create()) | ||||
|             .build() | ||||
|     } | ||||
| 
 | ||||
|     fun getApiServiceLogin(): ApiServiceLogin { | ||||
|         return retrofit.create(ApiServiceLogin::class.java) | ||||
|     } | ||||
| 
 | ||||
|     fun getApiServiceMain(): ApiServiceMain { | ||||
|         return retrofit.create(ApiServiceMain::class.java) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										23
									
								
								app/src/main/java/ru/myitschool/work/api/UserInfo.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,23 @@ | ||||
| package ru.myitschool.work.api | ||||
| 
 | ||||
| import com.google.gson.annotations.SerializedName | ||||
| 
 | ||||
| data class UserInfo( | ||||
|     @SerializedName("id") val id: Int, | ||||
|     @SerializedName("login") val login: String, | ||||
|     @SerializedName("name") val name: String, | ||||
|     @SerializedName("photo") val photoUrl: String, | ||||
|     @SerializedName("position") val position: String, | ||||
|     @SerializedName("lastVisit") val lastVisit: String, | ||||
| ) { | ||||
|     companion object { | ||||
|         val Initial = UserInfo( | ||||
|             id = 0, | ||||
|             login = "", | ||||
|             name = "", | ||||
|             photoUrl = "", | ||||
|             position = "", | ||||
|             lastVisit = "", | ||||
|         ) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										5
									
								
								app/src/main/java/ru/myitschool/work/core/Constants.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,5 @@ | ||||
| package ru.myitschool.work.core | ||||
| // БЕРИТЕ И ИЗМЕНЯЙТЕ ХОСТ ТОЛЬКО ЗДЕСЬ И НЕ БЕРИТЕ ИЗ ДРУГИХ МЕСТ. ФАЙЛ ПЕРЕМЕЩАТЬ НЕЛЬЗЯ | ||||
| object Constants { | ||||
|     const val SERVER_ADDRESS = "http://localhost:8090" | ||||
| } | ||||
							
								
								
									
										59
									
								
								app/src/main/java/ru/myitschool/work/ui/RootActivity.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,59 @@ | ||||
| package ru.myitschool.work.ui | ||||
| 
 | ||||
| import android.os.Bundle | ||||
| import androidx.activity.OnBackPressedCallback | ||||
| import androidx.appcompat.app.AppCompatActivity | ||||
| import androidx.navigation.createGraph | ||||
| import androidx.navigation.findNavController | ||||
| import androidx.navigation.fragment.NavHostFragment | ||||
| import androidx.navigation.fragment.fragment | ||||
| import dagger.hilt.android.AndroidEntryPoint | ||||
| import ru.myitschool.work.R | ||||
| import ru.myitschool.work.ui.login.LoginDestination | ||||
| import ru.myitschool.work.ui.login.LoginFragment | ||||
| import ru.myitschool.work.ui.main.MainDestination | ||||
| import ru.myitschool.work.ui.main.MainFragment | ||||
| import ru.myitschool.work.ui.qr.scan.QrScanDestination | ||||
| import ru.myitschool.work.ui.qr.scan.QrScanFragment | ||||
| 
 | ||||
| // НЕ ИЗМЕНЯЙТЕ НАЗВАНИЕ КЛАССА! | ||||
| @AndroidEntryPoint | ||||
| class RootActivity : AppCompatActivity() { | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|         setContentView(R.layout.activity_root) | ||||
| 
 | ||||
|         val navHostFragment = supportFragmentManager | ||||
|             .findFragmentById(R.id.nav_host_fragment) as NavHostFragment? | ||||
| 
 | ||||
|         if (navHostFragment != null) { | ||||
|             val navController = navHostFragment.navController | ||||
|             navController.graph = navController.createGraph( | ||||
|                 startDestination = LoginDestination | ||||
|             ) { | ||||
|                 fragment<LoginFragment, LoginDestination>() | ||||
|                 fragment<QrScanFragment, QrScanDestination>() | ||||
|                 fragment<MainFragment, MainDestination>() | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         onBackPressedDispatcher.addCallback( | ||||
|             this, | ||||
|             object : OnBackPressedCallback(true) { | ||||
|                 override fun handleOnBackPressed() { | ||||
|                     onSupportNavigateUp() | ||||
|                 } | ||||
|             } | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     override fun onSupportNavigateUp(): Boolean { | ||||
|         val navController = findNavController(R.id.nav_host_fragment) | ||||
|         val popBackResult = if (navController.previousBackStackEntry != null) { | ||||
|             navController.popBackStack() | ||||
|         } else { | ||||
|             false | ||||
|         } | ||||
|         return popBackResult || super.onSupportNavigateUp() | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,6 @@ | ||||
| package ru.myitschool.work.ui.login | ||||
| 
 | ||||
| import kotlinx.serialization.Serializable | ||||
| 
 | ||||
| @Serializable | ||||
| data object LoginDestination | ||||
							
								
								
									
										136
									
								
								app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,136 @@ | ||||
| package ru.myitschool.work.ui.login | ||||
| 
 | ||||
| import android.os.Bundle | ||||
| import android.text.Editable | ||||
| import android.text.InputType | ||||
| import android.text.TextWatcher | ||||
| import android.util.Log | ||||
| import android.view.View | ||||
| import android.widget.Toast | ||||
| import androidx.fragment.app.Fragment | ||||
| import androidx.fragment.app.viewModels | ||||
| import androidx.lifecycle.lifecycleScope | ||||
| import dagger.hilt.android.AndroidEntryPoint | ||||
| import kotlinx.coroutines.launch | ||||
| import ru.myitschool.work.R | ||||
| import ru.myitschool.work.databinding.FragmentLoginBinding | ||||
| import ru.myitschool.work.utils.collectWhenStarted | ||||
| import ru.myitschool.work.utils.visibleOrGone | ||||
| import androidx.navigation.fragment.findNavController | ||||
| import ru.myitschool.work.ui.main.MainDestination | ||||
| import ru.myitschool.work.utils.AuthPreferences | ||||
| import java.util.regex.Pattern | ||||
| 
 | ||||
| @AndroidEntryPoint | ||||
| class LoginFragment : Fragment(R.layout.fragment_login) { | ||||
|     private var _binding: FragmentLoginBinding? = null | ||||
|     private val binding: FragmentLoginBinding get() = _binding!! | ||||
|     private val viewModel: LoginViewModel by viewModels() | ||||
|     private lateinit var authPreferences: AuthPreferences | ||||
| 
 | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|         authPreferences = AuthPreferences(requireContext()) | ||||
| 
 | ||||
|         if (authPreferences.isLoggedIn()) { | ||||
|             navigateToMainScreen() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||||
|         super.onViewCreated(view, savedInstanceState) | ||||
|         _binding = FragmentLoginBinding.bind(view) | ||||
| 
 | ||||
|         setupLoginComponents() | ||||
|         observeLoginState() | ||||
|     } | ||||
| 
 | ||||
|     private fun setupLoginComponents() { | ||||
|         binding.username.apply { | ||||
|             inputType = InputType.TYPE_CLASS_TEXT | ||||
|             addTextChangedListener(object : TextWatcher { | ||||
|                 override fun afterTextChanged(s: Editable?) { | ||||
|                     val login = s.toString() | ||||
|                     binding.login.isEnabled = validateLogin(login) | ||||
|                 } | ||||
|                 override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} | ||||
|                 override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} | ||||
|             }) | ||||
|         } | ||||
| 
 | ||||
|         binding.login.setOnClickListener { | ||||
|             val login = binding.username.text.toString() | ||||
|             performLogin(login) | ||||
|         } | ||||
| 
 | ||||
|         binding.loading.visibleOrGone(false) | ||||
|         binding.error.visibleOrGone(false) | ||||
|     } | ||||
| 
 | ||||
|     private fun observeLoginState() { | ||||
|         viewModel.state.collectWhenStarted(this) { state -> | ||||
|             when (state) { | ||||
|                 is LoginViewModel.LoginState.Loading -> { | ||||
|                     binding.loading.visibleOrGone(true) | ||||
|                     binding.error.visibleOrGone(false) | ||||
|                 } | ||||
|                 is LoginViewModel.LoginState.Success -> { | ||||
|                     binding.loading.visibleOrGone(false) | ||||
|                     binding.error.visibleOrGone(false) | ||||
| 
 | ||||
|                     authPreferences.saveLoginState(true) | ||||
|                     authPreferences.saveLogin(binding.username.text.toString()) // Сохраняем логин | ||||
|                     Toast.makeText(context, "Авторизация прошла успешно", Toast.LENGTH_SHORT).show() | ||||
|                     navigateToMainScreen() | ||||
|                 } | ||||
|                 is LoginViewModel.LoginState.InvalidCredentials -> { | ||||
|                     binding.loading.visibleOrGone(false) | ||||
|                     binding.error.apply { | ||||
|                         visibleOrGone(true) | ||||
|                         text = "Неверный логин" | ||||
|                     } | ||||
|                 } | ||||
|                 is LoginViewModel.LoginState.Error -> { | ||||
|                     binding.loading.visibleOrGone(false) | ||||
|                     binding.error.apply { | ||||
|                         visibleOrGone(true) | ||||
|                         text = "Ошибка авторизации" | ||||
|                     } | ||||
|                     Log.d("Authentication", "Ошибка авторизации") | ||||
|                 } | ||||
| 
 | ||||
|                 LoginViewModel.LoginState.Initial -> binding.loading.visibleOrGone(false) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun performLogin(login: String) { | ||||
|         lifecycleScope.launch { | ||||
|             viewModel.authenticate(login) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun navigateToMainScreen() { | ||||
|         try { | ||||
|             findNavController().apply { | ||||
|                 popBackStack(MainDestination, false) | ||||
|                 navigate(MainDestination) | ||||
|             } | ||||
|         } catch (e: Exception) { | ||||
|             Log.e("LoginFragment", "Navigation error", e) | ||||
|             Toast.makeText(context, "Ошибка перехода", Toast.LENGTH_SHORT).show() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun validateLogin(login: String): Boolean { | ||||
|         return !(login.isEmpty() || | ||||
|                 login.length < 3 || | ||||
|                 Pattern.matches("^[0-9].*", login) || | ||||
|                 !Pattern.matches("^[a-zA-Z0-9]+$", login)) | ||||
|     } | ||||
| 
 | ||||
|     override fun onDestroyView() { | ||||
|         _binding = null | ||||
|         super.onDestroyView() | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,61 @@ | ||||
| package ru.myitschool.work.ui.login | ||||
| 
 | ||||
| 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.asStateFlow | ||||
| import kotlinx.coroutines.launch | ||||
| import retrofit2.Call | ||||
| import retrofit2.Response | ||||
| import ru.myitschool.work.api.ApiServiceLogin | ||||
| import javax.inject.Inject | ||||
| 
 | ||||
| @HiltViewModel | ||||
| class LoginViewModel @Inject constructor( | ||||
|     private val apiService: ApiServiceLogin | ||||
| ) : ViewModel() { | ||||
| 
 | ||||
|     private val _state = MutableStateFlow<LoginState>(LoginState.Initial) | ||||
|     val state = _state.asStateFlow() | ||||
| 
 | ||||
|     fun authenticate(login: String) { | ||||
|         viewModelScope.launch { | ||||
|             _state.value = LoginState.Loading // Устанавливаем состояние загрузки | ||||
| 
 | ||||
|             apiService.authenticate(login).enqueue(object : retrofit2.Callback<Void> { | ||||
|                 override fun onResponse(call: Call<Void>, response: Response<Void>) { | ||||
|                     Log.d("Authentication", "Response code: ${response.code()}") | ||||
| 
 | ||||
|                     when (response.code()) { | ||||
|                         200 -> { | ||||
|                             _state.value = LoginState.Success | ||||
|                         } | ||||
|                         400 -> { | ||||
|                             _state.value = LoginState.InvalidCredentials | ||||
|                         } | ||||
|                         401 -> { | ||||
|                             _state.value = LoginState.Error | ||||
|                         } | ||||
|                         else -> { | ||||
|                             _state.value = LoginState.Error | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 override fun onFailure(call: Call<Void>, t: Throwable) { | ||||
|                     _state.value = LoginState.Error | ||||
|                 } | ||||
|             }) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     sealed class LoginState { | ||||
|         data object Initial : LoginState() | ||||
|         data object Loading : LoginState() | ||||
|         data object Success : LoginState() | ||||
|         data object InvalidCredentials : LoginState() | ||||
|         data object Error : LoginState() | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,6 @@ | ||||
| package ru.myitschool.work.ui.main | ||||
| 
 | ||||
| import kotlinx.serialization.Serializable | ||||
| 
 | ||||
| @Serializable | ||||
| data object MainDestination | ||||
							
								
								
									
										158
									
								
								app/src/main/java/ru/myitschool/work/ui/main/MainFragment.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,158 @@ | ||||
| package ru.myitschool.work.ui.main | ||||
| 
 | ||||
| import android.graphics.BitmapFactory | ||||
| import android.os.Bundle | ||||
| import android.util.Log | ||||
| import android.view.View | ||||
| import androidx.fragment.app.Fragment | ||||
| import androidx.fragment.app.viewModels | ||||
| import androidx.navigation.fragment.findNavController | ||||
| import dagger.hilt.android.AndroidEntryPoint | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.launch | ||||
| import kotlinx.coroutines.withContext | ||||
| import ru.myitschool.work.R | ||||
| import ru.myitschool.work.databinding.MainFragmentBinding | ||||
| import ru.myitschool.work.ui.login.LoginDestination | ||||
| import ru.myitschool.work.ui.qr.scan.QrScanDestination | ||||
| import ru.myitschool.work.utils.AuthPreferences | ||||
| import ru.myitschool.work.utils.collectWhenStarted | ||||
| import java.net.URL | ||||
| import java.util.Locale | ||||
| 
 | ||||
| @AndroidEntryPoint | ||||
| class MainFragment : Fragment(R.layout.main_fragment) { | ||||
|     private var _binding: MainFragmentBinding? = null | ||||
|     private val binding: MainFragmentBinding get() = _binding!! | ||||
|     private lateinit var authPreferences: AuthPreferences | ||||
|     private val viewModel: MainViewModel by viewModels() | ||||
| 
 | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|         authPreferences = AuthPreferences(requireContext()) | ||||
| 
 | ||||
|         if (!authPreferences.isLoggedIn()) { | ||||
|             navigateToLogin() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||||
|         super.onViewCreated(view, savedInstanceState) | ||||
|         _binding = MainFragmentBinding.bind(view) | ||||
| 
 | ||||
|         if (authPreferences.isLoggedIn()) { | ||||
|             setupMainComponents() | ||||
|             observeUserState() | ||||
|             viewModel.getUserData(authPreferences.getLogin() ?: "") | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun hideAll() { | ||||
|         binding.apply { | ||||
|             username.visibility = View.GONE | ||||
|             position.visibility = View.GONE | ||||
|             lastEntry.visibility = View.GONE | ||||
|             scan.visibility = View.GONE | ||||
|             logout.visibility = View.GONE | ||||
| 
 | ||||
|             error.visibility = View.VISIBLE | ||||
|             refresh.visibility = View.VISIBLE | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun observeUserState() { | ||||
|         viewModel.state.collectWhenStarted(this) { state -> | ||||
|             when (state) { | ||||
|                 is MainViewModel.MainState.Initial -> { | ||||
|                     binding.error.visibility = View.GONE | ||||
|                 } | ||||
|                 is MainViewModel.MainState.Loading -> { | ||||
|                     binding.error.visibility = View.GONE | ||||
|                 } | ||||
|                 is MainViewModel.MainState.Success -> { | ||||
|                     binding.apply { | ||||
|                         username.visibility = View.VISIBLE | ||||
|                         position.visibility = View.VISIBLE | ||||
|                         lastEntry.visibility = View.VISIBLE | ||||
|                         scan.visibility = View.VISIBLE | ||||
|                         logout.visibility = View.VISIBLE | ||||
|                         error.visibility = View.GONE | ||||
| 
 | ||||
|                         username.text = state.userInfo.name | ||||
|                         position.text = state.userInfo.position | ||||
| 
 | ||||
|                         // Загрузка изображения из URL | ||||
|                         loadImageFromUrl(state.userInfo.photoUrl) | ||||
| 
 | ||||
|                         val inputFormat = java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()) | ||||
|                         val outputFormat = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()) | ||||
| 
 | ||||
|                         try { | ||||
|                             val parsedDate = inputFormat.parse(state.userInfo.lastVisit) | ||||
|                             lastEntry.text = parsedDate?.let { outputFormat.format(it) } ?: state.userInfo.lastVisit | ||||
|                         } catch (e: Exception) { | ||||
|                             lastEntry.text = state.userInfo.lastVisit | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 is MainViewModel.MainState.Error -> { | ||||
|                     hideAll() | ||||
|                     binding.error.text = state.message | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun loadImageFromUrl(url: String) { | ||||
|         CoroutineScope(Dispatchers.IO).launch { | ||||
|             val bitmap = try { | ||||
|                 val input = URL(url).openStream() | ||||
|                 BitmapFactory.decodeStream(input) | ||||
|             } catch (e: Exception) { | ||||
|                 Log.e("MainFragment", "Error loading image", e) | ||||
|                 null | ||||
|             } | ||||
| 
 | ||||
|             withContext(Dispatchers.Main) { | ||||
|                 if (bitmap != null) { | ||||
|                     binding.photo.setImageBitmap(bitmap) | ||||
|                 } else { | ||||
|                     binding.photo.setImageResource(R.drawable.ic_no_img) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun setupMainComponents() { | ||||
|         binding.logout.setOnClickListener { | ||||
|             authPreferences.clearLoginState() | ||||
|             navigateToLogin() | ||||
|         } | ||||
| 
 | ||||
|         binding.refresh.setOnClickListener { | ||||
|             viewModel.getUserData(authPreferences.getLogin() ?: "") | ||||
|         } | ||||
| 
 | ||||
|         binding.scan.setOnClickListener { | ||||
|             findNavController().navigate(QrScanDestination) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun navigateToLogin() { | ||||
|         try { | ||||
|             authPreferences.clearLoginState() | ||||
|             findNavController().apply { | ||||
|                 popBackStack(LoginDestination, false) | ||||
|                 navigate(LoginDestination) | ||||
|             } | ||||
|         } catch (e: Exception) { | ||||
|             Log.e("MainFragment", "Error navigating to login", e) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     override fun onDestroyView() { | ||||
|         super.onDestroyView() | ||||
|         _binding = null | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,66 @@ | ||||
| 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.asStateFlow | ||||
| import kotlinx.coroutines.launch | ||||
| import retrofit2.Call | ||||
| import retrofit2.Callback | ||||
| import retrofit2.Response | ||||
| import ru.myitschool.work.api.ApiServiceMain | ||||
| import ru.myitschool.work.api.UserInfo | ||||
| import javax.inject.Inject | ||||
| 
 | ||||
| @HiltViewModel | ||||
| class MainViewModel @Inject constructor( | ||||
|     private val apiService: ApiServiceMain | ||||
| ) : ViewModel() { | ||||
| 
 | ||||
|     private val _state = MutableStateFlow<MainState>(MainState.Initial) | ||||
|     val state = _state.asStateFlow() | ||||
| 
 | ||||
|     fun getUserData(login: String) { | ||||
|         viewModelScope.launch { | ||||
|             _state.value = MainState.Loading | ||||
| 
 | ||||
| 
 | ||||
|             apiService.getDataUser(login).enqueue(object : Callback<UserInfo> { | ||||
|                 override fun onResponse(call: Call<UserInfo>, response: Response<UserInfo>) { | ||||
| 
 | ||||
|                     when { | ||||
|                         response.isSuccessful -> { | ||||
|                             val userInfo = response.body() ?: UserInfo.Initial | ||||
|                             _state.value = MainState.Success(userInfo) | ||||
|                         } | ||||
|                         response.code() == 401 -> { | ||||
|                             _state.value = MainState.Error("Unauthorized") | ||||
|                         } | ||||
|                         else -> { | ||||
|                             _state.value = MainState.Error("Error") | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 override fun onFailure(call: Call<UserInfo>, t: Throwable) { | ||||
|                     Log.e(TAG, "Network Error", t) | ||||
|                     _state.value = MainState.Error("Network Error") | ||||
|                 } | ||||
|             }) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Sealed класс для состояний | ||||
|     sealed class MainState { | ||||
|         data object Initial : MainState() | ||||
|         data object Loading : MainState() | ||||
|         data class Success(val userInfo: UserInfo) : MainState() | ||||
|         data class Error(val message: String) : MainState() | ||||
|     } | ||||
| 
 | ||||
|     companion object { | ||||
|         private const val TAG = "MainViewModel" | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,30 @@ | ||||
| package ru.myitschool.work.ui.qr.scan | ||||
| 
 | ||||
| import android.os.Bundle | ||||
| import androidx.core.os.bundleOf | ||||
| import kotlinx.serialization.Serializable | ||||
| 
 | ||||
| // НЕ ИЗМЕНЯЙТЕ ЭТОТ ФАЙЛ. В ТЕСТАХ ОН БУДЕМ ВОЗВРАЩЁН В ИСХОДНОЕ СОСТОЯНИЕ | ||||
| @Serializable | ||||
| data object QrScanDestination { | ||||
|     const val REQUEST_KEY = "qr_result" | ||||
|     private const val KEY_QR_DATA = "key_qr" | ||||
| 
 | ||||
|     fun newInstance(): QrScanFragment { | ||||
|         return QrScanFragment() | ||||
|     } | ||||
| 
 | ||||
|     fun getDataIfExist(bundle: Bundle): String? { | ||||
|         return if (bundle.containsKey(KEY_QR_DATA)) { | ||||
|             bundle.getString(KEY_QR_DATA) | ||||
|         } else { | ||||
|             null | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     internal fun packToBundle(data: String): Bundle { | ||||
|         return bundleOf( | ||||
|             KEY_QR_DATA to data | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,139 @@ | ||||
| package ru.myitschool.work.ui.qr.scan | ||||
| 
 | ||||
| import android.os.Bundle | ||||
| import android.view.View | ||||
| import androidx.activity.result.contract.ActivityResultContracts | ||||
| import androidx.camera.core.ImageAnalysis | ||||
| import androidx.camera.mlkit.vision.MlKitAnalyzer | ||||
| import androidx.camera.view.LifecycleCameraController | ||||
| import androidx.camera.view.PreviewView | ||||
| import androidx.core.content.ContextCompat | ||||
| import androidx.core.os.bundleOf | ||||
| import androidx.fragment.app.Fragment | ||||
| import androidx.fragment.app.setFragmentResult | ||||
| import androidx.fragment.app.viewModels | ||||
| import androidx.navigation.NavController | ||||
| import androidx.navigation.fragment.findNavController | ||||
| import com.google.mlkit.vision.barcode.BarcodeScanner | ||||
| import com.google.mlkit.vision.barcode.BarcodeScannerOptions | ||||
| import com.google.mlkit.vision.barcode.BarcodeScanning | ||||
| import com.google.mlkit.vision.barcode.common.Barcode | ||||
| import ru.myitschool.work.R | ||||
| import ru.myitschool.work.databinding.FragmentQrScanBinding | ||||
| import ru.myitschool.work.utils.collectWhenStarted | ||||
| import ru.myitschool.work.utils.visibleOrGone | ||||
| 
 | ||||
| // НЕ ИЗМЕНЯЙТЕ ЭТОТ ФАЙЛ. В ТЕСТАХ ОН БУДЕМ ВОЗВРАЩЁН В ИСХОДНОЕ СОСТОЯНИЕ | ||||
| class QrScanFragment : Fragment(R.layout.fragment_qr_scan) { | ||||
|     private var _binding: FragmentQrScanBinding? = null | ||||
|     private val binding: FragmentQrScanBinding get() = _binding!! | ||||
| 
 | ||||
|     private var barcodeScanner: BarcodeScanner? = null | ||||
|     private var isCameraInit: Boolean = false | ||||
|     private val permissionLauncher = registerForActivityResult( | ||||
|         ActivityResultContracts.RequestPermission() | ||||
|     ) { isGranted -> viewModel.onPermissionResult(isGranted) } | ||||
| 
 | ||||
|     private val viewModel: QrScanViewModel by viewModels() | ||||
| 
 | ||||
|     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||||
|         super.onViewCreated(view, savedInstanceState) | ||||
|         _binding = FragmentQrScanBinding.bind(view) | ||||
|         sendResult(bundleOf()) | ||||
|         subscribe() | ||||
|         initCallback() | ||||
|     } | ||||
| 
 | ||||
|     private fun initCallback() { | ||||
|         binding.close.setOnClickListener { viewModel.close() } | ||||
|     } | ||||
| 
 | ||||
|     private fun subscribe() { | ||||
|         viewModel.state.collectWhenStarted(this) { state -> | ||||
|             binding.loading.visibleOrGone(state is QrScanViewModel.State.Loading) | ||||
|             binding.viewFinder.visibleOrGone(state is QrScanViewModel.State.Scan) | ||||
|             if (!isCameraInit && state is QrScanViewModel.State.Scan) { | ||||
|                 startCamera() | ||||
|                 isCameraInit = true | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         viewModel.action.collectWhenStarted(this) { action -> | ||||
|             when (action) { | ||||
|                 is QrScanViewModel.Action.RequestPermission -> requestPermission(action.permission) | ||||
|                 is QrScanViewModel.Action.CloseWithCancel -> { | ||||
|                     goBack() | ||||
|                 } | ||||
|                 is QrScanViewModel.Action.CloseWithResult -> { | ||||
|                     sendResult(QrScanDestination.packToBundle(action.result)) | ||||
|                     goBack() | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun requestPermission(permission: String) { | ||||
|         permissionLauncher.launch(permission) | ||||
|     } | ||||
| 
 | ||||
|     private fun startCamera() { | ||||
|         val context = requireContext() | ||||
|         val cameraController = LifecycleCameraController(context) | ||||
|         val previewView: PreviewView = binding.viewFinder | ||||
|         val executor = ContextCompat.getMainExecutor(context) | ||||
| 
 | ||||
|         val options = BarcodeScannerOptions.Builder() | ||||
|             .setBarcodeFormats(Barcode.FORMAT_QR_CODE) | ||||
|             .build() | ||||
|         val barcodeScanner = BarcodeScanning.getClient(options) | ||||
|         this.barcodeScanner = barcodeScanner | ||||
| 
 | ||||
|         cameraController.setImageAnalysisAnalyzer( | ||||
|             executor, | ||||
|             MlKitAnalyzer( | ||||
|                 listOf(barcodeScanner), | ||||
|                 ImageAnalysis.COORDINATE_SYSTEM_VIEW_REFERENCED, | ||||
|                 executor | ||||
|             ) { result -> | ||||
|                 result?.getValue(barcodeScanner)?.firstOrNull()?.let { value -> | ||||
|                     viewModel.findBarcode(value) | ||||
| 
 | ||||
|                 } | ||||
|             } | ||||
|         ) | ||||
| 
 | ||||
|         cameraController.bindToLifecycle(this) | ||||
|         previewView.controller = cameraController | ||||
|     } | ||||
| 
 | ||||
|     override fun onDestroyView() { | ||||
|         barcodeScanner?.close() | ||||
|         barcodeScanner = null | ||||
|         _binding = null | ||||
|         super.onDestroyView() | ||||
|     } | ||||
| 
 | ||||
|     private fun goBack() { | ||||
|         findNavControllerOrNull()?.popBackStack() | ||||
|             ?: requireActivity().onBackPressedDispatcher.onBackPressed() | ||||
|     } | ||||
| 
 | ||||
|     private fun sendResult(bundle: Bundle) { | ||||
|         setFragmentResult( | ||||
|             QrScanDestination.REQUEST_KEY, | ||||
|             bundle | ||||
|         ) | ||||
|         findNavControllerOrNull() | ||||
|             ?.previousBackStackEntry | ||||
|             ?.savedStateHandle | ||||
|             ?.set(QrScanDestination.REQUEST_KEY, bundle) | ||||
|     } | ||||
| 
 | ||||
|     private fun findNavControllerOrNull(): NavController? { | ||||
|         return try { | ||||
|             findNavController() | ||||
|         } catch (_: Throwable) { | ||||
|             null | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,93 @@ | ||||
| package ru.myitschool.work.ui.qr.scan | ||||
| 
 | ||||
| import android.Manifest | ||||
| import android.app.Application | ||||
| import android.content.pm.PackageManager | ||||
| import androidx.core.content.ContextCompat | ||||
| import androidx.lifecycle.AndroidViewModel | ||||
| import androidx.lifecycle.viewModelScope | ||||
| import com.google.mlkit.vision.barcode.common.Barcode | ||||
| import kotlinx.coroutines.delay | ||||
| import kotlinx.coroutines.flow.MutableStateFlow | ||||
| import kotlinx.coroutines.flow.asSharedFlow | ||||
| import kotlinx.coroutines.flow.asStateFlow | ||||
| import kotlinx.coroutines.flow.update | ||||
| import kotlinx.coroutines.launch | ||||
| import ru.myitschool.work.utils.MutablePublishFlow | ||||
| 
 | ||||
| // НЕ ИЗМЕНЯЙТЕ ЭТОТ ФАЙЛ. В ТЕСТАХ ОН БУДЕМ ВОЗВРАЩЁН В ИСХОДНОЕ СОСТОЯНИЕ | ||||
| class QrScanViewModel( | ||||
|     application: Application | ||||
| ) : AndroidViewModel(application) { | ||||
| 
 | ||||
|     private val _action = MutablePublishFlow<Action>() | ||||
|     val action = _action.asSharedFlow() | ||||
| 
 | ||||
|     private val _state = MutableStateFlow<State>(initialState) | ||||
|     val state = _state.asStateFlow() | ||||
| 
 | ||||
|     init { | ||||
|         checkPermission() | ||||
|     } | ||||
| 
 | ||||
|     fun onPermissionResult(isGranted: Boolean) { | ||||
|         viewModelScope.launch { | ||||
|             if (isGranted) { | ||||
|                 _state.update { State.Scan } | ||||
|             } else { | ||||
|                 _action.emit(Action.CloseWithCancel) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun checkPermission() { | ||||
|         viewModelScope.launch { | ||||
|             val isPermissionGranted = ContextCompat.checkSelfPermission( | ||||
|                 getApplication(), | ||||
|                 CAMERA_PERMISSION | ||||
|             ) == PackageManager.PERMISSION_GRANTED | ||||
|             if (isPermissionGranted) { | ||||
|                 _state.update { State.Scan } | ||||
|             } else { | ||||
|                 delay(1000) | ||||
|                 _action.emit(Action.RequestPermission(CAMERA_PERMISSION)) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun findBarcode(barcode: Barcode) { | ||||
|         viewModelScope.launch { | ||||
|             barcode.rawValue?.let { value -> | ||||
|                 _action.emit(Action.CloseWithResult(value)) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun close() { | ||||
|         viewModelScope.launch { | ||||
|             _action.emit(Action.CloseWithCancel) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     sealed interface State { | ||||
|         data object Loading : State | ||||
| 
 | ||||
|         data object Scan : State | ||||
|     } | ||||
| 
 | ||||
|     sealed interface Action { | ||||
|         data class RequestPermission( | ||||
|             val permission: String | ||||
|         ) : Action | ||||
|         data object CloseWithCancel : Action | ||||
|         data class CloseWithResult( | ||||
|             val result: String | ||||
|         ) : Action | ||||
|     } | ||||
| 
 | ||||
|     private companion object { | ||||
|         val initialState = State.Loading | ||||
| 
 | ||||
|         const val CAMERA_PERMISSION = Manifest.permission.CAMERA | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,45 @@ | ||||
| package ru.myitschool.work.utils | ||||
| 
 | ||||
| import android.content.Context | ||||
| import android.content.SharedPreferences | ||||
| 
 | ||||
| class AuthPreferences(context: Context) { | ||||
|     private val sharedPreferences: SharedPreferences = context.getSharedPreferences( | ||||
|         PREFS_NAME, | ||||
|         Context.MODE_PRIVATE | ||||
|     ) | ||||
| 
 | ||||
|     fun saveLoginState(isLoggedIn: Boolean) { | ||||
|         sharedPreferences.edit().apply { | ||||
|             putBoolean(KEY_IS_LOGGED_IN, isLoggedIn) | ||||
|             apply() // Асинхронное сохранение | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun isLoggedIn(): Boolean { | ||||
|         return sharedPreferences.getBoolean(KEY_IS_LOGGED_IN, false) | ||||
|     } | ||||
| 
 | ||||
|     fun clearLoginState() { | ||||
|         sharedPreferences.edit().apply { | ||||
|             remove(KEY_IS_LOGGED_IN) | ||||
|             apply() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun saveLogin(login: String) { | ||||
|         sharedPreferences.edit().apply { | ||||
|             putString("user_login", login) | ||||
|             apply() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun getLogin(): String? { | ||||
|         return sharedPreferences.getString("user_login", null) | ||||
|     } | ||||
| 
 | ||||
|     companion object { | ||||
|         private const val PREFS_NAME = "AuthPreferences" | ||||
|         private const val KEY_IS_LOGGED_IN = "is_logged_in" | ||||
|     } | ||||
| } | ||||
							
								
								
									
										10
									
								
								app/src/main/java/ru/myitschool/work/utils/FlowExtensions.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,10 @@ | ||||
| package ru.myitschool.work.utils | ||||
| 
 | ||||
| import kotlinx.coroutines.channels.BufferOverflow | ||||
| import kotlinx.coroutines.flow.MutableSharedFlow | ||||
| 
 | ||||
| fun <T> MutablePublishFlow() = MutableSharedFlow<T>( | ||||
|     replay = 0, | ||||
|     extraBufferCapacity = 1, | ||||
|     BufferOverflow.DROP_OLDEST | ||||
| ) | ||||
| @ -0,0 +1,18 @@ | ||||
| package ru.myitschool.work.utils | ||||
| 
 | ||||
| import androidx.fragment.app.Fragment | ||||
| import androidx.lifecycle.flowWithLifecycle | ||||
| import androidx.lifecycle.lifecycleScope | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| import kotlinx.coroutines.launch | ||||
| 
 | ||||
| inline fun <T> Flow<T>.collectWhenStarted( | ||||
|     fragment: Fragment, | ||||
|     crossinline collector: (T) -> Unit | ||||
| ) { | ||||
|     fragment.viewLifecycleOwner.lifecycleScope.launch { | ||||
|         flowWithLifecycle(fragment.viewLifecycleOwner.lifecycle).collect { value -> | ||||
|             collector.invoke(value) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,12 @@ | ||||
| package ru.myitschool.work.utils | ||||
| 
 | ||||
| import android.text.Editable | ||||
| import android.text.TextWatcher | ||||
| 
 | ||||
| open class TextChangedListener: TextWatcher { | ||||
|     override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit | ||||
| 
 | ||||
|     override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit | ||||
| 
 | ||||
|     override fun afterTextChanged(s: Editable?) = Unit | ||||
| } | ||||
| @ -0,0 +1,7 @@ | ||||
| package ru.myitschool.work.utils | ||||
| 
 | ||||
| import android.view.View | ||||
| 
 | ||||
| fun View.visibleOrGone(isVisible: Boolean) { | ||||
|     this.visibility = if (isVisible) View.VISIBLE else View.GONE | ||||
| } | ||||
							
								
								
									
										5
									
								
								app/src/main/res/drawable/ic_close.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,5 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> | ||||
|        | ||||
|     <path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/> | ||||
|      | ||||
| </vector> | ||||
							
								
								
									
										170
									
								
								app/src/main/res/drawable/ic_launcher_background.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,170 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:width="108dp" | ||||
|     android:height="108dp" | ||||
|     android:viewportWidth="108" | ||||
|     android:viewportHeight="108"> | ||||
|     <path | ||||
|         android:fillColor="#3DDC84" | ||||
|         android:pathData="M0,0h108v108h-108z" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M9,0L9,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,0L19,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M29,0L29,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M39,0L39,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M49,0L49,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M59,0L59,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M69,0L69,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M79,0L79,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M89,0L89,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M99,0L99,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,9L108,9" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,19L108,19" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,29L108,29" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,39L108,39" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,49L108,49" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,59L108,59" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,69L108,69" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,79L108,79" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,89L108,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,99L108,99" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,29L89,29" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,39L89,39" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,49L89,49" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,59L89,59" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,69L89,69" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,79L89,79" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M29,19L29,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M39,19L39,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M49,19L49,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M59,19L59,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M69,19L69,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M79,19L79,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
| </vector> | ||||
							
								
								
									
										30
									
								
								app/src/main/res/drawable/ic_launcher_foreground.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,30 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:aapt="http://schemas.android.com/aapt" | ||||
|     android:width="108dp" | ||||
|     android:height="108dp" | ||||
|     android:viewportWidth="108" | ||||
|     android:viewportHeight="108"> | ||||
|     <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z"> | ||||
|         <aapt:attr name="android:fillColor"> | ||||
|             <gradient | ||||
|                 android:endX="85.84757" | ||||
|                 android:endY="92.4963" | ||||
|                 android:startX="42.9492" | ||||
|                 android:startY="49.59793" | ||||
|                 android:type="linear"> | ||||
|                 <item | ||||
|                     android:color="#44000000" | ||||
|                     android:offset="0.0" /> | ||||
|                 <item | ||||
|                     android:color="#00000000" | ||||
|                     android:offset="1.0" /> | ||||
|             </gradient> | ||||
|         </aapt:attr> | ||||
|     </path> | ||||
|     <path | ||||
|         android:fillColor="#FFFFFF" | ||||
|         android:fillType="nonZero" | ||||
|         android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" | ||||
|         android:strokeWidth="1" | ||||
|         android:strokeColor="#00000000" /> | ||||
| </vector> | ||||
							
								
								
									
										5
									
								
								app/src/main/res/drawable/ic_logout.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,5 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> | ||||
|        | ||||
|     <path android:fillColor="@android:color/white" android:pathData="M17,7l-1.41,1.41L18.17,11H8v2h10.17l-2.58,2.58L17,17l5,-5zM4,5h8V3H4c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h8v-2H4V5z"/> | ||||
|      | ||||
| </vector> | ||||
							
								
								
									
										5
									
								
								app/src/main/res/drawable/ic_no_img.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,5 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> | ||||
|        | ||||
|     <path android:fillColor="@android:color/white" android:pathData="M21.9,21.9l-8.49,-8.49l0,0L3.59,3.59l0,0L2.1,2.1L0.69,3.51L3,5.83V19c0,1.1 0.9,2 2,2h13.17l2.31,2.31L21.9,21.9zM5,18l3.5,-4.5l2.5,3.01L12.17,15l3,3H5zM21,18.17L5.83,3H19c1.1,0 2,0.9 2,2V18.17z"/> | ||||
|      | ||||
| </vector> | ||||
							
								
								
									
										25
									
								
								app/src/main/res/drawable/ic_qr_code.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,25 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> | ||||
|        | ||||
|     <path android:fillColor="@android:color/white" android:pathData="M3,11h8V3H3V11zM5,5h4v4H5V5z"/> | ||||
|        | ||||
|     <path android:fillColor="@android:color/white" android:pathData="M3,21h8v-8H3V21zM5,15h4v4H5V15z"/> | ||||
|        | ||||
|     <path android:fillColor="@android:color/white" android:pathData="M13,3v8h8V3H13zM19,9h-4V5h4V9z"/> | ||||
|        | ||||
|     <path android:fillColor="@android:color/white" android:pathData="M19,19h2v2h-2z"/> | ||||
|        | ||||
|     <path android:fillColor="@android:color/white" android:pathData="M13,13h2v2h-2z"/> | ||||
|        | ||||
|     <path android:fillColor="@android:color/white" android:pathData="M15,15h2v2h-2z"/> | ||||
|        | ||||
|     <path android:fillColor="@android:color/white" android:pathData="M13,17h2v2h-2z"/> | ||||
|        | ||||
|     <path android:fillColor="@android:color/white" android:pathData="M15,19h2v2h-2z"/> | ||||
|        | ||||
|     <path android:fillColor="@android:color/white" android:pathData="M17,17h2v2h-2z"/> | ||||
|        | ||||
|     <path android:fillColor="@android:color/white" android:pathData="M17,13h2v2h-2z"/> | ||||
|        | ||||
|     <path android:fillColor="@android:color/white" android:pathData="M19,15h2v2h-2z"/> | ||||
|      | ||||
| </vector> | ||||
							
								
								
									
										5
									
								
								app/src/main/res/drawable/ic_refresh.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,5 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> | ||||
|        | ||||
|     <path android:fillColor="@android:color/white" android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/> | ||||
|      | ||||
| </vector> | ||||
							
								
								
									
										13
									
								
								app/src/main/res/layout/activity_root.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,13 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent"> | ||||
| 
 | ||||
|     <androidx.fragment.app.FragmentContainerView | ||||
|         android:id="@+id/nav_host_fragment" | ||||
|         android:name="androidx.navigation.fragment.NavHostFragment" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" | ||||
|         app:defaultNavHost="true" /> | ||||
| </FrameLayout> | ||||
							
								
								
									
										71
									
								
								app/src/main/res/layout/fragment_login.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,71 @@ | ||||
| <?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"> | ||||
| 
 | ||||
|     <ProgressBar | ||||
|         android:id="@+id/loading" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintTop_toTopOf="parent" | ||||
|         app:layout_constraintBottom_toBottomOf="parent" /> | ||||
| 
 | ||||
|     <LinearLayout | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" | ||||
|         android:orientation="vertical" | ||||
|         android:padding="24dp" | ||||
|         android:gravity="center"> | ||||
|         <TextView | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:text="@string/welcomeText" | ||||
|             android:textSize="24sp" | ||||
|             android:textStyle="bold" | ||||
|             android:gravity="center" /> | ||||
|         <TextView | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_marginTop="16dp" | ||||
|             android:text="@string/inputLoginText" | ||||
|             android:textSize="18sp" | ||||
|             android:gravity="center" /> | ||||
|         <TextView | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_marginTop="4dp" | ||||
|             android:text="@string/welcomeDescriptionLoginText" | ||||
|             android:textSize="14sp" | ||||
|             android:gravity="center" /> | ||||
|         <EditText | ||||
|             android:id="@+id/username" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_marginTop="16dp" | ||||
|             android:autofillHints="" | ||||
|             android:hint="@string/loginText" | ||||
|             android:inputType="text" | ||||
|             android:padding="12dp" /> | ||||
|         <Button | ||||
|             android:id="@+id/login" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_marginTop="24dp" | ||||
|             android:text="@string/continueText" | ||||
|             android:textSize="18sp" | ||||
|             android:enabled="false"/> | ||||
|         <TextView | ||||
|             android:id="@+id/error" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_marginTop="4dp" | ||||
|             android:text="@string/loginError" | ||||
|             android:textSize="14sp" | ||||
|             android:gravity="center" | ||||
|             android:visibility="gone"/> | ||||
|     </LinearLayout> | ||||
| 
 | ||||
| </androidx.constraintlayout.widget.ConstraintLayout> | ||||
							
								
								
									
										35
									
								
								app/src/main/res/layout/fragment_qr_scan.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,35 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent"> | ||||
| 
 | ||||
|     <androidx.camera.view.PreviewView | ||||
|         android:id="@+id/viewFinder" | ||||
|         android:layout_width="0dp" | ||||
|         android:layout_height="0dp" | ||||
|         app:layout_constraintBottom_toBottomOf="parent" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintTop_toTopOf="parent" /> | ||||
| 
 | ||||
|     <ProgressBar | ||||
|         android:id="@+id/loading" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         app:layout_constraintBottom_toBottomOf="parent" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintTop_toTopOf="parent" /> | ||||
| 
 | ||||
|     <com.google.android.material.floatingactionbutton.FloatingActionButton | ||||
|         android:id="@+id/close" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_margin="16dp" | ||||
|         android:contentDescription="@string/close_button" | ||||
|         android:src="@drawable/ic_close" | ||||
|         app:elevation="0dp" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintTop_toTopOf="parent" /> | ||||
| </androidx.constraintlayout.widget.ConstraintLayout> | ||||
							
								
								
									
										25
									
								
								app/src/main/res/layout/fragment_scan_result.xml
									
									
									
									
									
										Normal 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="match_parent" | ||||
|     android:gravity="center" | ||||
|     android:orientation="vertical" | ||||
|     android:padding="16dp"> | ||||
| 
 | ||||
|     <TextView | ||||
|         android:id="@+id/result" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_gravity="center_horizontal" | ||||
|         android:text="@string/resulText" | ||||
|         android:textColor="@android:color/black" | ||||
|         android:textSize="18sp" /> | ||||
| 
 | ||||
|     <Button | ||||
|         android:id="@+id/close" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_marginTop="16dp" | ||||
|         android:text="@string/closeText" /> | ||||
| 
 | ||||
| </LinearLayout> | ||||
							
								
								
									
										123
									
								
								app/src/main/res/layout/main_fragment.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,123 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent" | ||||
|     android:id="@+id/main_fragment"> | ||||
| 
 | ||||
|     <LinearLayout | ||||
|         android:id="@+id/topButtonLayout" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:orientation="horizontal" | ||||
|         android:padding="16dp" | ||||
|         app:layout_constraintTop_toTopOf="parent" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintEnd_toEndOf="parent"> | ||||
| 
 | ||||
|         <ImageButton | ||||
|             android:id="@+id/logout" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:background="@null" | ||||
|             android:contentDescription="@string/contentDescriptionLogout" | ||||
|             android:padding="8dp" | ||||
|             android:src="@drawable/ic_logout" | ||||
|             android:minHeight="64dp" | ||||
|             android:minWidth="64dp"/> | ||||
| 
 | ||||
|         <Space | ||||
|             android:layout_width="0dp" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_weight="1" /> | ||||
| 
 | ||||
|         <ImageButton | ||||
|             android:id="@+id/refresh" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:background="@null" | ||||
|             android:contentDescription="@string/contentDescriptionRefresh" | ||||
|             android:padding="8dp" | ||||
|             android:src="@drawable/ic_refresh" | ||||
|             android:minHeight="64dp" | ||||
|             android:minWidth="64dp"/> | ||||
| 
 | ||||
|         <ImageButton | ||||
|             android:id="@+id/scan" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:background="@null" | ||||
|             android:contentDescription="@string/contentDescriptionScan" | ||||
|             android:padding="8dp" | ||||
|             android:src="@drawable/ic_qr_code" | ||||
|             android:minHeight="64dp" | ||||
|             android:minWidth="64dp"/> | ||||
|     </LinearLayout> | ||||
| 
 | ||||
|     <LinearLayout | ||||
|         android:id="@+id/profileLayout" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_gravity="center" | ||||
|         android:orientation="vertical" | ||||
|         android:padding="16dp" | ||||
|         app:layout_constraintTop_toBottomOf="@id/topButtonLayout" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintBottom_toBottomOf="parent"> | ||||
| 
 | ||||
|         <!-- Существующие TextView и ImageView --> | ||||
|         <ImageView | ||||
|             android:id="@+id/photo" | ||||
|             android:layout_width="128dp" | ||||
|             android:layout_height="128dp" | ||||
|             android:layout_gravity="center_horizontal" | ||||
|             android:contentDescription="@string/contentDescriptionPhoto" | ||||
|             android:src="@drawable/ic_no_img" /> | ||||
| 
 | ||||
|         <TextView | ||||
|             android:id="@+id/username" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_gravity="center_horizontal" | ||||
|             android:layout_marginTop="16dp" | ||||
|             android:text="@string/fullNameText" | ||||
|             android:textColor="@color/black" | ||||
|             android:textSize="20sp" | ||||
|             android:textStyle="bold" /> | ||||
| 
 | ||||
|         <TextView | ||||
|             android:id="@+id/position" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_gravity="center_horizontal" | ||||
|             android:layout_marginTop="8dp" | ||||
|             android:text="@string/jobText" | ||||
|             android:textColor="@color/black" | ||||
|             android:textSize="16sp" /> | ||||
| 
 | ||||
|         <TextView | ||||
|             android:id="@+id/lastEntry" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_gravity="center_horizontal" | ||||
|             android:layout_marginTop="8dp" | ||||
|             android:text="@string/lastLoginText" | ||||
|             android:textColor="@color/black" | ||||
|             android:textSize="14sp" /> | ||||
| 
 | ||||
|         <TextView | ||||
|             android:id="@+id/error" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_gravity="center_horizontal" | ||||
|             android:layout_marginTop="16dp" | ||||
|             android:text="@string/errorText" | ||||
|             android:textColor="@color/black" | ||||
|             android:textSize="16sp" | ||||
|             android:visibility="gone" /> | ||||
|     </LinearLayout> | ||||
| 
 | ||||
| 
 | ||||
| </androidx.constraintlayout.widget.ConstraintLayout> | ||||
							
								
								
									
										6
									
								
								app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <background android:drawable="@drawable/ic_launcher_background" /> | ||||
|     <foreground android:drawable="@drawable/ic_launcher_foreground" /> | ||||
|     <monochrome android:drawable="@drawable/ic_launcher_foreground" /> | ||||
| </adaptive-icon> | ||||
							
								
								
									
										6
									
								
								app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <background android:drawable="@drawable/ic_launcher_background" /> | ||||
|     <foreground android:drawable="@drawable/ic_launcher_foreground" /> | ||||
|     <monochrome android:drawable="@drawable/ic_launcher_foreground" /> | ||||
| </adaptive-icon> | ||||
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-hdpi/ic_launcher.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-mdpi/ic_launcher.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 982 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-xhdpi/ic_launcher.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 5.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 7.6 KiB | 
							
								
								
									
										12
									
								
								app/src/main/res/values/colors.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,12 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <color name="purple_200">#FFBB86FC</color> | ||||
|     <color name="purple_500">#FF6200EE</color> | ||||
|     <color name="purple_700">#FF3700B3</color> | ||||
|     <color name="teal_200">#FF03DAC5</color> | ||||
|     <color name="teal_700">#FF018786</color> | ||||
|     <color name="black">#FF000000</color> | ||||
|     <color name="white">#FFFFFFFF</color> | ||||
|     <color name="buttonBackground">#5C5C5C</color> | ||||
|     <color name="buttonText">#C0C0C0</color> | ||||
| </resources> | ||||
							
								
								
									
										4
									
								
								app/src/main/res/values/ids.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,4 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <item name="rootContainer" type="id"/> | ||||
| </resources> | ||||
							
								
								
									
										21
									
								
								app/src/main/res/values/strings.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,21 @@ | ||||
| <resources> | ||||
|     <string name="app_name">NTO Pass</string> | ||||
|     <string name="loginText">Логин</string> | ||||
|     <string name="welcomeText">Добро пожаловать!</string> | ||||
|     <string name="inputLoginText">Введите свой логин</string> | ||||
|     <string name="welcomeDescriptionLoginText">для авторизации в приложении</string> | ||||
|     <string name="continueText">Продолжить</string> | ||||
|     <string name="loginError">Логин должен содержать не менее трех символов</string> | ||||
|     <string name="hello_blank_fragment">Hello blank fragment</string> | ||||
|     <string name="errorText">Ошибка</string> | ||||
|     <string name="lastLoginText">Последний вход: 01.01.2023 12:00</string> | ||||
|     <string name="jobText">Должность: Менеджер</string> | ||||
|     <string name="fullNameText">Андрей Андреевич Андреев</string> | ||||
|     <string name="contentDescriptionScan">scan</string> | ||||
|     <string name="contentDescriptionRefresh">refresh</string> | ||||
|     <string name="contentDescriptionLogout">logout</string> | ||||
|     <string name="contentDescriptionPhoto">photo</string> | ||||
|     <string name="resulText">Вход был отменён</string> | ||||
|     <string name="closeText">Закрыть/Close</string> | ||||
|     <string name="errorLoginText">Ошибка входа</string> | ||||
| </resources> | ||||
							
								
								
									
										4
									
								
								app/src/main/res/values/strings_qr.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,4 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <string name="close_button">Close</string> | ||||
| </resources> | ||||
							
								
								
									
										16
									
								
								app/src/main/res/values/themes.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,16 @@ | ||||
| <resources xmlns:tools="http://schemas.android.com/tools"> | ||||
|     <!-- Base application theme. --> | ||||
|     <style name="Theme.Default" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> | ||||
|         <!-- Primary brand color. --> | ||||
|         <item name="colorPrimary">@color/purple_500</item> | ||||
|         <item name="colorPrimaryVariant">@color/purple_700</item> | ||||
|         <item name="colorOnPrimary">@color/white</item> | ||||
|         <!-- Secondary brand color. --> | ||||
|         <item name="colorSecondary">@color/teal_200</item> | ||||
|         <item name="colorSecondaryVariant">@color/teal_700</item> | ||||
|         <item name="colorOnSecondary">@color/black</item> | ||||
|         <!-- Status bar color. --> | ||||
|         <item name="android:statusBarColor">?attr/colorPrimaryVariant</item> | ||||
|         <!-- Customize your theme here. --> | ||||
|     </style> | ||||
| </resources> | ||||
							
								
								
									
										13
									
								
								app/src/main/res/xml/backup_rules.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,13 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?><!-- | ||||
|    Sample backup rules file; uncomment and customize as necessary. | ||||
|    See https://developer.android.com/guide/topics/data/autobackup | ||||
|    for details. | ||||
|    Note: This file is ignored for devices older that API 31 | ||||
|    See https://developer.android.com/about/versions/12/backup-restore | ||||
| --> | ||||
| <full-backup-content> | ||||
|     <!-- | ||||
|    <include domain="sharedpref" path="."/> | ||||
|    <exclude domain="sharedpref" path="device.xml"/> | ||||
| --> | ||||
| </full-backup-content> | ||||
							
								
								
									
										19
									
								
								app/src/main/res/xml/data_extraction_rules.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,19 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?><!-- | ||||
|    Sample data extraction rules file; uncomment and customize as necessary. | ||||
|    See https://developer.android.com/about/versions/12/backup-restore#xml-changes | ||||
|    for details. | ||||
| --> | ||||
| <data-extraction-rules> | ||||
|     <cloud-backup> | ||||
|         <!-- TODO: Use <include> and <exclude> to control what is backed up. | ||||
|         <include .../> | ||||
|         <exclude .../> | ||||
|         --> | ||||
|     </cloud-backup> | ||||
|     <!-- | ||||
|     <device-transfer> | ||||
|         <include .../> | ||||
|         <exclude .../> | ||||
|     </device-transfer> | ||||
|     --> | ||||
| </data-extraction-rules> | ||||
							
								
								
									
										4
									
								
								app/src/main/res/xml/network_security_config.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,4 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <network-security-config> | ||||
|     <base-config cleartextTrafficPermitted="true"/> | ||||
| </network-security-config> | ||||
							
								
								
									
										8
									
								
								build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,8 @@ | ||||
| // Top-level build file where you can add configuration options common to all sub-projects/modules. | ||||
| plugins { | ||||
|     androidApplication version Version.agp apply false | ||||
|     kotlinJvm version Version.Kotlin.language apply false | ||||
|     kotlinAnnotationProcessor version Version.Kotlin.language apply false | ||||
|     id("com.google.dagger.hilt.android") version "2.51.1" apply false | ||||
|     id("org.jetbrains.kotlin.android") version "1.9.24" apply false | ||||
| } | ||||
							
								
								
									
										2
									
								
								buildSrc/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,2 @@ | ||||
| /.gradle | ||||
| /build | ||||
							
								
								
									
										2
									
								
								buildSrc/.gradle/buildOutputCleanup/cache.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,2 @@ | ||||
| #Tue Feb 18 18:22:47 MSK 2025 | ||||
| gradle.version=8.9 | ||||
							
								
								
									
										8
									
								
								buildSrc/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,8 @@ | ||||
| plugins { | ||||
|     `kotlin-dsl` | ||||
| } | ||||
| 
 | ||||
| repositories { | ||||
|     google() | ||||
|     mavenCentral() | ||||
| } | ||||
							
								
								
									
										220
									
								
								buildSrc/src/main/java/Dependencies.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,220 @@ | ||||
| data class Dependency( | ||||
|         val name: String, | ||||
|         val version: String, | ||||
| ) { | ||||
|     val fullPath get() = "$name:$version" | ||||
| } | ||||
| 
 | ||||
| object Dependencies { | ||||
| 
 | ||||
|     /** | ||||
|      * Type-safe HTTP client for Android and Java by Square, Inc. | ||||
|      * | ||||
|      * [Documentation](http://square.github.io/retrofit/) | ||||
|      * | ||||
|      * [Github](https://github.com/square/retrofit) | ||||
|      * | ||||
|      * [Apache License 2.0](https://github.com/square/retrofit/blob/master/LICENSE.txt) | ||||
|      * | ||||
|      * [Changelog](https://github.com/square/retrofit/blob/master/CHANGELOG.md) | ||||
|      */ | ||||
|     object Retrofit { | ||||
|         private const val version = "2.9.0" | ||||
| 
 | ||||
|         val library = Dependency("com.squareup.retrofit2:retrofit", version) | ||||
|         val gsonConverter = Dependency("com.squareup.retrofit2:converter-gson", version) | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * [Documentation](https://developer.android.com/jetpack/androidx) | ||||
|      * | ||||
|      * [Releases](https://developer.android.com/jetpack/androidx/versions). | ||||
|      */ | ||||
|     object AndroidX { | ||||
|         /** | ||||
|          * [Changelog](https://developer.android.com/jetpack/androidx/releases/core) | ||||
|          */ | ||||
|         val core = Dependency("androidx.core:core-ktx", "1.13.1") | ||||
| 
 | ||||
|         /** | ||||
|          * [Changelog](https://developer.android.com/jetpack/androidx/releases/appcompat) | ||||
|          */ | ||||
|         val appcompat = Dependency("androidx.appcompat:appcompat", "1.7.0") | ||||
| 
 | ||||
|         /** | ||||
|          * [Changelog](https://developer.android.com/jetpack/androidx/releases/activity) | ||||
|          */ | ||||
|         val activity = Dependency("androidx.activity:activity", "1.9.3") | ||||
| 
 | ||||
|         /** | ||||
|          * [Changelog](https://developer.android.com/jetpack/androidx/releases/fragment) | ||||
|          */ | ||||
|         val fragment = Dependency("androidx.fragment:fragment-ktx", "1.8.4") | ||||
| 
 | ||||
|         /** | ||||
|          * [Changelog](https://developer.android.com/jetpack/androidx/releases/recyclerview) | ||||
|          */ | ||||
|         val recyclerView = Dependency("androidx.recyclerview:recyclerview", "1.3.2") | ||||
| 
 | ||||
|         /** | ||||
|          * [Changelog](https://developer.android.com/jetpack/androidx/releases/cardview) | ||||
|          */ | ||||
|         val cardView = Dependency("androidx.cardview:cardview", "1.0.0") | ||||
| 
 | ||||
|         /** | ||||
|          * [Changelog](https://developer.android.com/jetpack/androidx/releases/gridlayout) | ||||
|          */ | ||||
|         val gridLayout = Dependency("androidx.gridlayout:gridlayout", "1.0.0") | ||||
| 
 | ||||
|         /** | ||||
|          * A ConstraintLayout is a ViewGroup which allows you to position and size widgets in a flexible way. | ||||
|          * | ||||
|          * [Documentation](https://developer.android.com/reference/android/support/constraint/ConstraintLayout) | ||||
|          * | ||||
|          * [Changelog](https://developer.android.com/jetpack/androidx/releases/constraintlayout) | ||||
|          */ | ||||
|         val constraintLayout = Dependency("androidx.constraintlayout:constraintlayout", "2.1.4") | ||||
| 
 | ||||
|         /** | ||||
|          * CoordinatorLayout is a super-powered FrameLayout. | ||||
|          * CoordinatorLayout is intended for two primary use cases: | ||||
|          * 1. As a top-level application decor or chrome layout | ||||
|          * 2. As a container for a specific interaction with one or more child views | ||||
|          * | ||||
|          * [Documentation](https://developer.android.com/jetpack/androidx/releases/coordinatorlayout) | ||||
|          * | ||||
|          * [Changelog](https://developer.android.com/jetpack/androidx/releases/coordinatorlayout) | ||||
|          */ | ||||
|         val coordinatorLayout = Dependency("androidx.coordinatorlayout:coordinatorlayout", "1.2.0") | ||||
| 
 | ||||
|         /** | ||||
|          * The SwipeRefreshLayout should be used whenever the user | ||||
|          * can refresh the contents of a view via a vertical swipe gesture. | ||||
|          * | ||||
|          * [Documentation](https://developer.android.com/jetpack/androidx/releases/swiperefreshlayout) | ||||
|          * | ||||
|          * [Changelog](https://developer.android.com/jetpack/androidx/releases/swiperefreshlayout) | ||||
|          */ | ||||
|         val swipeRefreshLayout = Dependency("androidx.swiperefreshlayout:swiperefreshlayout", "1.1.0") | ||||
| 
 | ||||
|         /** | ||||
|          * [Changelog](https://developer.android.com/jetpack/androidx/releases/test/) | ||||
|          */ | ||||
|         object Testing { | ||||
| 
 | ||||
|             /** | ||||
|              * [Documentation](https://developer.android.com/training/testing) | ||||
|              */ | ||||
|             val core = Dependency("androidx.test:core", "1.5.0") | ||||
| 
 | ||||
|             /** | ||||
|              * [Documentation](https://developer.android.com/training/testing) | ||||
|              */ | ||||
|             val junit = Dependency("androidx.test.ext:junit-ktx", "1.1.5") | ||||
| 
 | ||||
|             /** | ||||
|              * [Documentation](https://developer.android.com/training/testing/espresso) | ||||
|              */ | ||||
|             object Espresso { | ||||
|                 private const val version = "3.5.1" | ||||
|                 val core = Dependency("androidx.test.espresso:espresso-core", version) | ||||
|                 val intents = Dependency("androidx.test.espresso:espresso-intents", version) | ||||
|                 val contrib = Dependency("androidx.test.espresso:espresso-contrib", version) | ||||
|             } | ||||
| 
 | ||||
|             /** | ||||
|              * [Documentation](https://developer.android.com/training/testing/junit-runner) | ||||
|              */ | ||||
|             val runner = Dependency("androidx.test:runner", "1.5.2") | ||||
| 
 | ||||
|             /** | ||||
|              * [Documentation](https://developer.android.com/training/testing/junit-rules) | ||||
|              */ | ||||
|             val rules = Dependency("androidx.test:rules", "1.5.0") | ||||
| 
 | ||||
|             /** | ||||
|              * [Documentation](https://developer.android.com/training/testing/junit-rules) | ||||
|              */ | ||||
|             val compose = Dependency("androidx.compose.ui:ui-test-junit4", "1.6.0") | ||||
| 
 | ||||
|             /** | ||||
|              * [Documentation](https://developer.android.com/training/testing/junit-runner#using-android-test-orchestrator) | ||||
|              */ | ||||
|             val orchestrator = Dependency("androidx.test:orchestrator", "1.4.2") | ||||
| 
 | ||||
|             /** | ||||
|              * [Documentation](https://developer.android.com/training/testing/ui-automator) | ||||
|              */ | ||||
|             val uiAutomator = Dependency("androidx.test.uiautomator:uiautomator", "2.2.0") | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * [Documentation](https://material.io/develop/android/) | ||||
|          * | ||||
|          * [Github](https://github.com/material-components/material-components-android) | ||||
|          * | ||||
|          * [Changelog](https://github.com/material-components/material-components-android/releases) | ||||
|          */ | ||||
|         val materialDesign = Dependency("com.google.android.material:material", "1.11.0") | ||||
| 
 | ||||
|         object Lifecycle { | ||||
|             private const val version = "2.6.1" | ||||
| 
 | ||||
|             val viewModel = Dependency("androidx.lifecycle:lifecycle-viewmodel-ktx", version) | ||||
|             val common = Dependency("androidx.lifecycle:lifecycle-common", version) | ||||
|         } | ||||
| 
 | ||||
|         object Navigation { | ||||
|             private const val version = "2.8.3" | ||||
| 
 | ||||
|             val fragment = Dependency("androidx.navigation:navigation-fragment-ktx", version) | ||||
|             val navigationUi = Dependency("androidx.navigation:navigation-ui-ktx", version) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * JUnit is a simple framework to write repeatable tests. | ||||
|      * | ||||
|      * [Documentation](https://junit.org/junit4/) | ||||
|      * | ||||
|      * [Github](https://github.com/junit-team/junit4) | ||||
|      * | ||||
|      * [Eclipse Public License 1.0](https://github.com/junit-team/junit4/blob/master/LICENSE-junit.txt) | ||||
|      * | ||||
|      * [Changelog](https://github.com/junit-team/junit4/wiki) | ||||
|      */ | ||||
|     val junit = Dependency("junit:junit", "4.13") | ||||
| 
 | ||||
|     /** | ||||
|      * Truth makes your test assertions and failure messages more readable. | ||||
|      * Similar to AssertJ, it natively supports many JDK and Guava types, | ||||
|      * and it is extensible to others. | ||||
|      * | ||||
|      * [Documentation](https://truth.dev/) | ||||
|      * | ||||
|      * [Github](https://github.com/google/truth) | ||||
|      * | ||||
|      * [Apache License 2.0](https://github.com/google/truth/blob/master/LICENSE) | ||||
|      * | ||||
|      * [Changelog](https://github.com/google/truth/releases) | ||||
|      */ | ||||
|     val truth = Dependency("com.google.truth:truth", "1.3.0") | ||||
| 
 | ||||
|     /** | ||||
|      * Kaspresso is a framework for Android UI testing. Based on Espresso and UI Automator. | ||||
|      * | ||||
|      * [Documentation](https://kasperskylab.github.io/Kaspresso/) | ||||
|      * | ||||
|      * [Github](https://github.com/KasperskyLab/Kaspresso) | ||||
|      * | ||||
|      * [Apache License 2.0](https://github.com/KasperskyLab/Kaspresso/blob/master/LICENSE.txt) | ||||
|      * | ||||
|      * [Changelog](https://github.com/KasperskyLab/Kaspresso/releases) | ||||
|      */ | ||||
|     object Kaspresso { | ||||
|         private const val version = "1.5.3" | ||||
|         val core = Dependency("com.kaspersky.android-components:kaspresso", version) | ||||
|         val composeSupport = Dependency("com.kaspersky.android-components:kaspresso-compose-support", version) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										40
									
								
								buildSrc/src/main/java/DependencyHandlerExtensions.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,40 @@ | ||||
| import org.gradle.api.artifacts.dsl.DependencyHandler | ||||
| 
 | ||||
| fun DependencyHandler.implementation(dependency: Dependency) { | ||||
|     add(Type.IMPLEMENTATION, dependency.fullPath) | ||||
| } | ||||
| 
 | ||||
| fun DependencyHandler.testImplementation(dependency: Dependency) { | ||||
|     add(Type.TEST_IMPLEMENTATION, dependency.fullPath) | ||||
| } | ||||
| 
 | ||||
| fun DependencyHandler.androidTestImplementation(dependency: Dependency) { | ||||
|     add(Type.ANDROID_TEST_IMPLEMENTATION, dependency.fullPath) | ||||
| } | ||||
| 
 | ||||
| fun DependencyHandler.api(dependency: Dependency) { | ||||
|     add(Type.API, dependency.fullPath) | ||||
| } | ||||
| 
 | ||||
| fun DependencyHandler.kapt(dependency: Dependency) { | ||||
|     add(Type.KAPT, dependency.fullPath) | ||||
| } | ||||
| 
 | ||||
| fun DependencyHandler.ksp(dependency: Dependency) { | ||||
|     add(Type.KSP, dependency.fullPath) | ||||
| } | ||||
| 
 | ||||
| fun DependencyHandler.defaultLibrary() { | ||||
|     api(Dependencies.AndroidX.core) | ||||
|     api(Dependencies.AndroidX.appcompat) | ||||
|     api(Dependencies.AndroidX.materialDesign) | ||||
| } | ||||
| 
 | ||||
| private object Type { | ||||
|     const val IMPLEMENTATION = "implementation" | ||||
|     const val TEST_IMPLEMENTATION = "testImplementation" | ||||
|     const val ANDROID_TEST_IMPLEMENTATION = "androidTestImplementation" | ||||
|     const val API = "api" | ||||
|     const val KAPT = "kapt" | ||||
|     const val KSP = "ksp" | ||||
| } | ||||
							
								
								
									
										67
									
								
								buildSrc/src/main/java/Plugin.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,67 @@ | ||||
| import org.gradle.kotlin.dsl.version | ||||
| import org.gradle.plugin.use.PluginDependenciesSpec | ||||
| import org.gradle.plugin.use.PluginDependencySpec | ||||
| 
 | ||||
| val PluginDependenciesSpec.androidApplication: PluginDependencySpec | ||||
|     get() = id(Plugin.Id.Android.application) | ||||
| val PluginDependenciesSpec.androidLibrary: PluginDependencySpec | ||||
|     get() = id(Plugin.Id.Android.library) | ||||
| val PluginDependenciesSpec.kotlinJvm: PluginDependencySpec | ||||
|     get() = id(Plugin.Id.Kotlin.jvm) | ||||
| val PluginDependenciesSpec.kotlinAndroid: PluginDependencySpec | ||||
|     get() = id(Plugin.Id.Kotlin.android) | ||||
| val PluginDependenciesSpec.kotlinParcelize: PluginDependencySpec | ||||
|     get() = id(Plugin.Id.Kotlin.parcelize) | ||||
| val PluginDependenciesSpec.kotlinAnnotationProcessor: PluginDependencySpec | ||||
|     get() = id(Plugin.Id.Kotlin.annotationProcessor) | ||||
| val PluginDependenciesSpec.kotlinSerialization: PluginDependencySpec | ||||
|     get() = id(Plugin.Id.Kotlin.serialization) | ||||
| val PluginDependenciesSpec.jetbrainsKotlinSerialization: PluginDependencySpec | ||||
|     get() = id(Plugin.Id.JetBrains.serialization) | ||||
| 
 | ||||
| object Plugin { | ||||
|     object Id { | ||||
|         object Android { | ||||
|             /** | ||||
|              * [Documentation](https://google.github.io/android-gradle-dsl/current/) | ||||
|              * [Changelog](https://developer.android.com/studio/releases/gradle-plugin) | ||||
|              */ | ||||
|             const val application = "com.android.application" | ||||
|             /** | ||||
|              * [Documentation](https://google.github.io/android-gradle-dsl/current/) | ||||
|              * [Changelog](https://developer.android.com/studio/releases/gradle-plugin) | ||||
|              */ | ||||
|             const val library = "com.android.library" | ||||
|         } | ||||
| 
 | ||||
|         object Kotlin { | ||||
|             /** | ||||
|              * Plugin published in https://plugins.gradle.org/ | ||||
|              */ | ||||
|             const val jvm = "org.jetbrains.kotlin.jvm" | ||||
|             /** | ||||
|              * Plugin published in https://plugins.gradle.org/ | ||||
|              */ | ||||
|             const val android = "org.jetbrains.kotlin.android" | ||||
| 
 | ||||
|             /** | ||||
|              * Plugin published in https://plugins.gradle.org/ | ||||
|              */ | ||||
|             const val parcelize = "kotlin-parcelize" | ||||
| 
 | ||||
|             /** | ||||
|              * Plugin published in https://plugins.gradle.org/ | ||||
|              */ | ||||
|             const val annotationProcessor = "org.jetbrains.kotlin.kapt" | ||||
| 
 | ||||
|             /** | ||||
|              * Plugin published in https://plugins.gradle.org/ | ||||
|              */ | ||||
|             const val serialization = "plugin.serialization" | ||||
|         } | ||||
| 
 | ||||
|         object JetBrains { | ||||
|             const val serialization = "org.jetbrains.kotlin.plugin.serialization" | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										42
									
								
								buildSrc/src/main/java/Version.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,42 @@ | ||||
| import org.gradle.api.JavaVersion | ||||
| 
 | ||||
| object Version { | ||||
| 
 | ||||
|     /** | ||||
|      * Gradle is an open-source build automation tool focused on flexibility and performance. | ||||
|      * | ||||
|      * [Documentation](https://docs.gradle.org/current/userguide/userguide.html) | ||||
|      * | ||||
|      * [Github](https://github.com/gradle/gradle) | ||||
|      * | ||||
|      * [Apache 2.0 License](https://github.com/gradle/gradle/blob/master/LICENSE) | ||||
|      * | ||||
|      * [Changelog](https://gradle.org/releases/) | ||||
|      */ | ||||
|     const val agp = "8.7.1" | ||||
| 
 | ||||
|     object Kotlin { | ||||
| 
 | ||||
|         /** | ||||
|          * [Documentation](https://kotlinlang.org/) | ||||
|          * | ||||
|          * [Source Code](https://github.com/JetBrains/kotlin/) | ||||
|          * | ||||
|          * [Apache 2.0 License](https://github.com/JetBrains/kotlin/blob/master/license/LICENSE.txt) | ||||
|          * | ||||
|          * [Changelog](https://kotlinlang.org/releases.html) | ||||
|          */ | ||||
|         const val language = "2.0.21" | ||||
| 
 | ||||
|         val javaSource = JavaVersion.VERSION_11 | ||||
|         const val jvmTarget = "11" | ||||
|     } | ||||
| 
 | ||||
|     object Android { | ||||
|         object Sdk { | ||||
|             const val min = 24 | ||||
|             const val compile = 34 | ||||
|             const val target = 34 | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										21
									
								
								gradle.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,21 @@ | ||||
| # Project-wide Gradle settings. | ||||
| # IDE (e.g. Android Studio) users: | ||||
| # Gradle settings configured through the IDE *will override* | ||||
| # any settings specified in this file. | ||||
| # For more details on how to configure your build environment visit | ||||
| # http://www.gradle.org/docs/current/userguide/build_environment.html | ||||
| # Specifies the JVM arguments used for the daemon process. | ||||
| # The setting is particularly useful for tweaking memory settings. | ||||
| org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 | ||||
| # When configured, Gradle will run in incubating parallel mode. | ||||
| # This option should only be used with decoupled projects. More details, visit | ||||
| # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects | ||||
| # org.gradle.parallel=true | ||||
| # AndroidX package structure to make it clearer which packages are bundled with the | ||||
| # Android operating system, and which are packaged with your app's APK | ||||
| # https://developer.android.com/topic/libraries/support-library/androidx-rn | ||||
| android.useAndroidX=true | ||||
| # Enables namespacing of each library's R class so that its R class includes only the | ||||
| # resources declared in the library itself and none from the library's dependencies, | ||||
| # thereby reducing the size of the R class for that library | ||||
| android.nonTransitiveRClass=true | ||||
							
								
								
									
										11
									
								
								gradle/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,11 @@ | ||||
| # Файл для управления дистрибутивом Gradle | ||||
| 
 | ||||
| Данный репозиторий необходим для поддержки актуальности дистрибутива Gradle в проектах. Данный проект необходимо подключать подмодулём и использовать во всех проектах. | ||||
| 
 | ||||
| ## Как обвновлять дистрибутив | ||||
| 
 | ||||
| Перед обновлением необходимо удостоверится, что версия самого Gradle установлена не ниже, чем в дистрибутиве. Сделать это можно [здесь](https://sicampus.ru/gitea/core/dependecies/src/branch/main/src/main/java/Version.kt#L16). | ||||
| 
 | ||||
| Процесс обновления выглядит следуюющим образом: | ||||
| 1. В начале обновляем саму версию Gradle ([в этом репозитории](https://sicampus.ru/gitea/core/dependecies)). | ||||
| 2. Обновлем ссылку на дестрибутив и проверяем совместимость. | ||||
							
								
								
									
										
											BIN
										
									
								
								gradle/wrapper/gradle-wrapper.jar
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										6
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,6 @@ | ||||
| #Thu Jan 04 22:32:26 NOVT 2024 | ||||
| distributionBase=GRADLE_USER_HOME | ||||
| distributionPath=wrapper/dists | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip | ||||
| zipStoreBase=GRADLE_USER_HOME | ||||
| zipStorePath=wrapper/dists | ||||
							
								
								
									
										234
									
								
								gradlew
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,234 @@ | ||||
| #!/bin/sh | ||||
| 
 | ||||
| # | ||||
| # Copyright © 2015-2021 the original authors. | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #      https://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
| 
 | ||||
| ############################################################################## | ||||
| # | ||||
| #   Gradle start up script for POSIX generated by Gradle. | ||||
| # | ||||
| #   Important for running: | ||||
| # | ||||
| #   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is | ||||
| #       noncompliant, but you have some other compliant shell such as ksh or | ||||
| #       bash, then to run this script, type that shell name before the whole | ||||
| #       command line, like: | ||||
| # | ||||
| #           ksh Gradle | ||||
| # | ||||
| #       Busybox and similar reduced shells will NOT work, because this script | ||||
| #       requires all of these POSIX shell features: | ||||
| #         * functions; | ||||
| #         * expansions «$var», «${var}», «${var:-default}», «${var+SET}», | ||||
| #           «${var#prefix}», «${var%suffix}», and «$( cmd )»; | ||||
| #         * compound commands having a testable exit status, especially «case»; | ||||
| #         * various built-in commands including «command», «set», and «ulimit». | ||||
| # | ||||
| #   Important for patching: | ||||
| # | ||||
| #   (2) This script targets any POSIX shell, so it avoids extensions provided | ||||
| #       by Bash, Ksh, etc; in particular arrays are avoided. | ||||
| # | ||||
| #       The "traditional" practice of packing multiple parameters into a | ||||
| #       space-separated string is a well documented source of bugs and security | ||||
| #       problems, so this is (mostly) avoided, by progressively accumulating | ||||
| #       options in "$@", and eventually passing that to Java. | ||||
| # | ||||
| #       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, | ||||
| #       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; | ||||
| #       see the in-line comments for details. | ||||
| # | ||||
| #       There are tweaks for specific operating systems such as AIX, CygWin, | ||||
| #       Darwin, MinGW, and NonStop. | ||||
| # | ||||
| #   (3) This script is generated from the Groovy template | ||||
| #       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt | ||||
| #       within the Gradle project. | ||||
| # | ||||
| #       You can find Gradle at https://github.com/gradle/gradle/. | ||||
| # | ||||
| ############################################################################## | ||||
| 
 | ||||
| # Attempt to set APP_HOME | ||||
| 
 | ||||
| # Resolve links: $0 may be a link | ||||
| app_path=$0 | ||||
| 
 | ||||
| # Need this for daisy-chained symlinks. | ||||
| while | ||||
|     APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path | ||||
|     [ -h "$app_path" ] | ||||
| do | ||||
|     ls=$( ls -ld "$app_path" ) | ||||
|     link=${ls#*' -> '} | ||||
|     case $link in             #( | ||||
|       /*)   app_path=$link ;; #( | ||||
|       *)    app_path=$APP_HOME$link ;; | ||||
|     esac | ||||
| done | ||||
| 
 | ||||
| APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit | ||||
| 
 | ||||
| APP_NAME="Gradle" | ||||
| APP_BASE_NAME=${0##*/} | ||||
| 
 | ||||
| # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||||
| DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' | ||||
| 
 | ||||
| # Use the maximum available, or set MAX_FD != -1 to use that value. | ||||
| MAX_FD=maximum | ||||
| 
 | ||||
| warn () { | ||||
|     echo "$*" | ||||
| } >&2 | ||||
| 
 | ||||
| die () { | ||||
|     echo | ||||
|     echo "$*" | ||||
|     echo | ||||
|     exit 1 | ||||
| } >&2 | ||||
| 
 | ||||
| # OS specific support (must be 'true' or 'false'). | ||||
| cygwin=false | ||||
| msys=false | ||||
| darwin=false | ||||
| nonstop=false | ||||
| case "$( uname )" in                #( | ||||
|   CYGWIN* )         cygwin=true  ;; #( | ||||
|   Darwin* )         darwin=true  ;; #( | ||||
|   MSYS* | MINGW* )  msys=true    ;; #( | ||||
|   NONSTOP* )        nonstop=true ;; | ||||
| esac | ||||
| 
 | ||||
| CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | ||||
| 
 | ||||
| 
 | ||||
| # Determine the Java command to use to start the JVM. | ||||
| if [ -n "$JAVA_HOME" ] ; then | ||||
|     if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | ||||
|         # IBM's JDK on AIX uses strange locations for the executables | ||||
|         JAVACMD=$JAVA_HOME/jre/sh/java | ||||
|     else | ||||
|         JAVACMD=$JAVA_HOME/bin/java | ||||
|     fi | ||||
|     if [ ! -x "$JAVACMD" ] ; then | ||||
|         die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME | ||||
| 
 | ||||
| Please set the JAVA_HOME variable in your environment to match the | ||||
| location of your Java installation." | ||||
|     fi | ||||
| else | ||||
|     JAVACMD=java | ||||
|     which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||||
| 
 | ||||
| Please set the JAVA_HOME variable in your environment to match the | ||||
| location of your Java installation." | ||||
| fi | ||||
| 
 | ||||
| # Increase the maximum file descriptors if we can. | ||||
| if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then | ||||
|     case $MAX_FD in #( | ||||
|       max*) | ||||
|         MAX_FD=$( ulimit -H -n ) || | ||||
|             warn "Could not query maximum file descriptor limit" | ||||
|     esac | ||||
|     case $MAX_FD in  #( | ||||
|       '' | soft) :;; #( | ||||
|       *) | ||||
|         ulimit -n "$MAX_FD" || | ||||
|             warn "Could not set maximum file descriptor limit to $MAX_FD" | ||||
|     esac | ||||
| fi | ||||
| 
 | ||||
| # Collect all arguments for the java command, stacking in reverse order: | ||||
| #   * args from the command line | ||||
| #   * the main class name | ||||
| #   * -classpath | ||||
| #   * -D...appname settings | ||||
| #   * --module-path (only if needed) | ||||
| #   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. | ||||
| 
 | ||||
| # For Cygwin or MSYS, switch paths to Windows format before running java | ||||
| if "$cygwin" || "$msys" ; then | ||||
|     APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) | ||||
|     CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) | ||||
| 
 | ||||
|     JAVACMD=$( cygpath --unix "$JAVACMD" ) | ||||
| 
 | ||||
|     # Now convert the arguments - kludge to limit ourselves to /bin/sh | ||||
|     for arg do | ||||
|         if | ||||
|             case $arg in                                #( | ||||
|               -*)   false ;;                            # don't mess with options #( | ||||
|               /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath | ||||
|                     [ -e "$t" ] ;;                      #( | ||||
|               *)    false ;; | ||||
|             esac | ||||
|         then | ||||
|             arg=$( cygpath --path --ignore --mixed "$arg" ) | ||||
|         fi | ||||
|         # Roll the args list around exactly as many times as the number of | ||||
|         # args, so each arg winds up back in the position where it started, but | ||||
|         # possibly modified. | ||||
|         # | ||||
|         # NB: a `for` loop captures its iteration list before it begins, so | ||||
|         # changing the positional parameters here affects neither the number of | ||||
|         # iterations, nor the values presented in `arg`. | ||||
|         shift                   # remove old arg | ||||
|         set -- "$@" "$arg"      # push replacement arg | ||||
|     done | ||||
| fi | ||||
| 
 | ||||
| # Collect all arguments for the java command; | ||||
| #   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of | ||||
| #     shell script including quotes and variable substitutions, so put them in | ||||
| #     double quotes to make sure that they get re-expanded; and | ||||
| #   * put everything else in single quotes, so that it's not re-expanded. | ||||
| 
 | ||||
| set -- \ | ||||
|         "-Dorg.gradle.appname=$APP_BASE_NAME" \ | ||||
|         -classpath "$CLASSPATH" \ | ||||
|         org.gradle.wrapper.GradleWrapperMain \ | ||||
|         "$@" | ||||
| 
 | ||||
| # Use "xargs" to parse quoted args. | ||||
| # | ||||
| # With -n1 it outputs one arg per line, with the quotes and backslashes removed. | ||||
| # | ||||
| # In Bash we could simply go: | ||||
| # | ||||
| #   readarray ARGS < <( xargs -n1 <<<"$var" ) && | ||||
| #   set -- "${ARGS[@]}" "$@" | ||||
| # | ||||
| # but POSIX shell has neither arrays nor command substitution, so instead we | ||||
| # post-process each arg (as a line of input to sed) to backslash-escape any | ||||
| # character that might be a shell metacharacter, then use eval to reverse | ||||
| # that process (while maintaining the separation between arguments), and wrap | ||||
| # the whole thing up as a single "set" statement. | ||||
| # | ||||
| # This will of course break if any of these variables contains a newline or | ||||
| # an unmatched quote. | ||||
| # | ||||
| 
 | ||||
| eval "set -- $( | ||||
|         printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | | ||||
|         xargs -n1 | | ||||
|         sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | | ||||
|         tr '\n' ' ' | ||||
|     )" '"$@"' | ||||
| 
 | ||||
| exec "$JAVACMD" "$@" | ||||
							
								
								
									
										89
									
								
								gradlew.bat
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,89 @@ | ||||
| @rem | ||||
| @rem Copyright 2015 the original author or authors. | ||||
| @rem | ||||
| @rem Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| @rem you may not use this file except in compliance with the License. | ||||
| @rem You may obtain a copy of the License at | ||||
| @rem | ||||
| @rem      https://www.apache.org/licenses/LICENSE-2.0 | ||||
| @rem | ||||
| @rem Unless required by applicable law or agreed to in writing, software | ||||
| @rem distributed under the License is distributed on an "AS IS" BASIS, | ||||
| @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| @rem See the License for the specific language governing permissions and | ||||
| @rem limitations under the License. | ||||
| @rem | ||||
| 
 | ||||
| @if "%DEBUG%" == "" @echo off | ||||
| @rem ########################################################################## | ||||
| @rem | ||||
| @rem  Gradle startup script for Windows | ||||
| @rem | ||||
| @rem ########################################################################## | ||||
| 
 | ||||
| @rem Set local scope for the variables with windows NT shell | ||||
| if "%OS%"=="Windows_NT" setlocal | ||||
| 
 | ||||
| set DIRNAME=%~dp0 | ||||
| if "%DIRNAME%" == "" set DIRNAME=. | ||||
| set APP_BASE_NAME=%~n0 | ||||
| set APP_HOME=%DIRNAME% | ||||
| 
 | ||||
| @rem Resolve any "." and ".." in APP_HOME to make it shorter. | ||||
| for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi | ||||
| 
 | ||||
| @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||||
| set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" | ||||
| 
 | ||||
| @rem Find java.exe | ||||
| if defined JAVA_HOME goto findJavaFromJavaHome | ||||
| 
 | ||||
| set JAVA_EXE=java.exe | ||||
| %JAVA_EXE% -version >NUL 2>&1 | ||||
| if "%ERRORLEVEL%" == "0" goto execute | ||||
| 
 | ||||
| echo. | ||||
| echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||||
| echo. | ||||
| echo Please set the JAVA_HOME variable in your environment to match the | ||||
| echo location of your Java installation. | ||||
| 
 | ||||
| goto fail | ||||
| 
 | ||||
| :findJavaFromJavaHome | ||||
| set JAVA_HOME=%JAVA_HOME:"=% | ||||
| set JAVA_EXE=%JAVA_HOME%/bin/java.exe | ||||
| 
 | ||||
| if exist "%JAVA_EXE%" goto execute | ||||
| 
 | ||||
| echo. | ||||
| echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% | ||||
| echo. | ||||
| echo Please set the JAVA_HOME variable in your environment to match the | ||||
| echo location of your Java installation. | ||||
| 
 | ||||
| goto fail | ||||
| 
 | ||||
| :execute | ||||
| @rem Setup the command line | ||||
| 
 | ||||
| set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar | ||||
| 
 | ||||
| 
 | ||||
| @rem Execute Gradle | ||||
| "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* | ||||
| 
 | ||||
| :end | ||||
| @rem End local scope for the variables with windows NT shell | ||||
| if "%ERRORLEVEL%"=="0" goto mainEnd | ||||
| 
 | ||||
| :fail | ||||
| rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of | ||||
| rem the _cmd.exe /c_ return code! | ||||
| if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 | ||||
| exit /b 1 | ||||
| 
 | ||||
| :mainEnd | ||||
| if "%OS%"=="Windows_NT" endlocal | ||||
| 
 | ||||
| :omega | ||||
							
								
								
									
										8
									
								
								local.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,8 @@ | ||||
| ## This file must *NOT* be checked into Version Control Systems, | ||||
| # as it contains information specific to your local configuration. | ||||
| # | ||||
| # Location of the SDK. This is only used by Gradle. | ||||
| # For customization when using a Version Control System, please read the | ||||
| # header note. | ||||
| #Tue Feb 18 18:22:40 MSK 2025 | ||||
| sdk.dir=C\:\\Users\\User\\AppData\\Local\\Android\\Sdk | ||||
							
								
								
									
										17
									
								
								settings.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,17 @@ | ||||
| pluginManagement { | ||||
|     repositories { | ||||
|         google() | ||||
|         mavenCentral() | ||||
|         gradlePluginPortal() | ||||
|     } | ||||
| } | ||||
| dependencyResolutionManagement { | ||||
|     repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) | ||||
|     repositories { | ||||
|         google() | ||||
|         mavenCentral() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| rootProject.name = "Work" | ||||
| include(":app") | ||||
 DKaverznev
						DKaverznev