day2_commit_1_xml_з123123213

This commit is contained in:
Terebov_Maksim 2025-02-19 18:59:50 +03:00
parent 3da4b99cc0
commit 8535cf6370
15 changed files with 126 additions and 243 deletions

122
README.md
View File

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

View File

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

View File

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

View File

@ -1,19 +1,23 @@
package ru.myitschool.work.api package ru.myitschool.work.api
import okhttp3.ResponseBody
import retrofit2.Response import retrofit2.Response
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.PATCH import retrofit2.http.PATCH
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.Path import retrofit2.http.Path
import retrofit2.http.Query
interface ApiService { interface ApiService {
// Метод для аутентификации // Метод для аутентификации
@POST("/api/auth") // Изменено на POST и путь к аутентификации @GET("/api/auth")
suspend fun authenticate( suspend fun authenticate(
@Body payload: AuthRequest // Передаем объект с логином и паролем @Query("login") login: String,
): Response<UserAuthResponse> // Возвращаем UserAuthResponse @Query("password") password: String
): Response<String> // Измените ResponseBody на String // Возвращаем ResponseBody вместо String
// Другие методы...
@GET("/api/{login}/info") // Получение информации о пользователе @GET("/api/{login}/info") // Получение информации о пользователе
suspend fun getUserInfo(@Path("login") login: String): Response<Map<String, Any>> suspend fun getUserInfo(@Path("login") login: String): Response<Map<String, Any>>
@ -30,17 +34,6 @@ interface ApiService {
suspend fun getAllWorkers(): Response<List<EmployeeData>> suspend fun getAllWorkers(): Response<List<EmployeeData>>
} }
// Модель данных для запроса аутентификации
data class AuthRequest(
val login: String,
val password: String // Поле для пароля
)
// Модель данных для ответа аутентификации
data class UserAuthResponse(
val role: String // Добавляем поле для роли
)
// Модель данных для информации о сотруднике // Модель данных для информации о сотруднике
data class EmployeeData( data class EmployeeData(
val name: String, val name: String,

View File

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

View File

@ -1,4 +1,4 @@
package ru.myitschool.work.ui.admin package ru.myitschool.work.ui.Main
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View

View File

@ -1,5 +1,6 @@
package ru.myitschool.work.ui.login package ru.myitschool.work.ui.login
import ru.myitschool.work.api.ApiService
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.text.InputType import android.text.InputType
@ -15,7 +16,6 @@ import androidx.navigation.fragment.findNavController
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import ru.myitschool.work.R import ru.myitschool.work.R
import ru.myitschool.work.SessionManager import ru.myitschool.work.SessionManager
import ru.myitschool.work.api.UserAuthResponse
import ru.myitschool.work.databinding.FragmentLoginBinding import ru.myitschool.work.databinding.FragmentLoginBinding
import ru.myitschool.work.utils.collectWhenStarted import ru.myitschool.work.utils.collectWhenStarted
import ru.myitschool.work.utils.visibleOrGone import ru.myitschool.work.utils.visibleOrGone
@ -40,7 +40,7 @@ class LoginFragment : Fragment(R.layout.fragment_login) {
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState) // Убедитесь, что этот вызов находится здесь
_binding = FragmentLoginBinding.bind(view) _binding = FragmentLoginBinding.bind(view)
setupUI() setupUI()
@ -85,32 +85,23 @@ class LoginFragment : Fragment(R.layout.fragment_login) {
} }
private fun subscribe() { private fun subscribe() {
viewModel.state.collectWhenStarted(this) { state -> lifecycleScope.launch {
binding.loading.visibleOrGone(false) viewModel.state.collect { state ->
binding.loading.visibleOrGone(false)
try {
if (state.maintenance) { if (state.maintenance) {
showMaintenanceDialog() // Показываем диалог о техработах showMaintenanceDialog() // Показываем диалог о техработах
} else if (state.error != null) {
binding.error.text = state.error
binding.error.visibility = View.VISIBLE
} else if (state.success) { } else if (state.success) {
binding.error.visibility = View.GONE binding.error.visibility = View.GONE
authPreferences.saveLoginState(true) authPreferences.saveLoginState(true)
authPreferences.saveLogin(binding.username.text.toString()) // Сохраняем логин authPreferences.saveLogin(binding.username.text.toString()) // Сохраняем логин
// Сохраняем роль пользователя в SessionManager
val userAuthResponse: UserAuthResponse? = state.userAuthResponse
userAuthResponse?.let {
SessionManager.userRole = it.role // Сохраняем роль
}
Toast.makeText(context, "Авторизация прошла успешно", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Авторизация прошла успешно", Toast.LENGTH_SHORT).show()
navigateToMainScreen() navigateToMainScreen() // Перенаправление на следующий экран
} else if (state.error != null) {
binding.error.text = state.error
binding.error.visibility = View.VISIBLE
} }
} catch (e: Exception) {
Log.e("LoginFragment", "Ошибка при обработке состояния", e)
Toast.makeText(context, "Произошла ошибка. Пожалуйста, попробуйте снова.", Toast.LENGTH_SHORT).show()
} }
} }
} }
@ -119,7 +110,7 @@ class LoginFragment : Fragment(R.layout.fragment_login) {
AlertDialog.Builder(requireContext()) AlertDialog.Builder(requireContext())
.setTitle("Технические работы") .setTitle("Технические работы")
.setMessage("Проводятся техработы, пожалуйста, подождите.") .setMessage("Проводятся техработы, пожалуйста, подождите.")
.setPositiveButton("ОК") { dialog, _ -> dialog.dismiss() } .setPositiveButton("ОК ") { dialog, _ -> dialog.dismiss() }
.setCancelable(false) .setCancelable(false)
.show() .show()
} }

View File

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

View File

@ -11,10 +11,10 @@
android:id="@+id/username" android:id="@+id/username"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="Введите логин" android:hint="@string/username_hint"
android:inputType="text" android:inputType="text"
android:padding="12dp" android:padding="12dp"
android:background="#F0F0F0" android:background="@drawable/ic_android_black_24dp"
android:layout_marginBottom="16dp"/> android:layout_marginBottom="16dp"/>
<EditText <EditText
@ -24,17 +24,18 @@
android:hint="Введите пароль" android:hint="Введите пароль"
android:inputType="textPassword" android:inputType="textPassword"
android:padding="12dp" android:padding="12dp"
android:background="#F0F0F0" android:background="@drawable/ic_android_black_24dp"
android:layout_marginBottom="16dp"/> android:layout_marginBottom="16dp"/>
<Button <Button
android:id="@+id/login" android:id="@+id/login"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Войти" android:text="@string/login_button"
android:backgroundTint="@color/colorPrimary" android:backgroundTint="@color/colorPrimary"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:padding="12dp" android:padding="12dp"
app:cornerRadius="16dp"
android:layout_marginBottom="16dp"/> android:layout_marginBottom="16dp"/>
<TextView <TextView

View File

@ -12,7 +12,7 @@
android:id="@+id/fullname" android:id="@+id/fullname"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Имя Фамилия" android:text="@string/fullname_label"
android:textSize="18sp" android:textSize="18sp"
android:layout_marginBottom="5dp" android:layout_marginBottom="5dp"
android:visibility="gone" /> android:visibility="gone" />
@ -32,7 +32,7 @@
android:id="@+id/position" android:id="@+id/position"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Должность" android:text="@string/position_label"
android:layout_marginBottom="5dp" android:layout_marginBottom="5dp"
android:visibility="gone" /> android:visibility="gone" />
@ -71,7 +71,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
android:visibility="visible" /> android:visibility="gone" />
<!-- Кнопки --> <!-- Кнопки -->
<Button <Button
@ -98,7 +98,8 @@
android:id="@+id/admin_panel" android:id="@+id/admin_panel"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Admin Panel" android:text="@string/admin_panel"
android:backgroundTint="@color/colorPrimary"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:visibility="gone" /> android:visibility="gone" />

View File

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

View File

@ -11,7 +11,7 @@
android:id="@+id/result" android:id="@+id/result"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Результат" android:text="@string/result"
android:textSize="18sp" android:textSize="18sp"
android:gravity="center" android:gravity="center"
android:padding="16dp" /> android:padding="16dp" />
@ -20,7 +20,9 @@
android:id="@+id/close" android:id="@+id/close"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Закрыть" android:text="@string/close_button"
android:backgroundTint="@color/colorPrimary"
android:textColor="@android:color/white"
android:padding="12dp" android:padding="12dp"
android:layout_marginTop="24dp" /> android:layout_marginTop="24dp" />

View File

@ -32,4 +32,6 @@
<string name="qr_scan_success">Successfully</string> <string name="qr_scan_success">Successfully</string>
<string name="qr_scan_failure">Something went wrong.</string> <string name="qr_scan_failure">Something went wrong.</string>
<string name="qr_scan_cancelled">Operation was cancelled</string> <string name="qr_scan_cancelled">Operation was cancelled</string>
<string name="close_button">Close</string>
<string name="result">Result</string>
</resources> </resources>

View File

@ -32,8 +32,7 @@
<string name="qr_scan_success">Успешно</string> <string name="qr_scan_success">Успешно</string>
<string name="qr_scan_failure">Что-то пошло не так</string> <string name="qr_scan_failure">Что-то пошло не так</string>
<string name="qr_scan_cancelled">Вход был отменён / Operation was cancelled</string> <string name="qr_scan_cancelled">Вход был отменён / Operation was cancelled</string>
<!-- TODO: Remove or change this placeholder text --> <string name="close_button">Закрыть</string>
<string name="hello_blank_fragment">Hello blank fragment</string> <string name="result">Результат</string>
<string name="admin_panel" translatable="false">Admin Panel</string>
</resources> </resources>

View File

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