Merge branch 'Frontend' into Frontend_UI

# Conflicts:
#	app/src/main/AndroidManifest.xml
#	app/src/main/java/ru/myitschool/work/ui/Main/AdminFragment.kt
#	app/src/main/res/layout/fragment_main.xml
This commit is contained in:
EgorVorobev 2025-02-19 17:05:06 +03:00
commit ee24f241db
15 changed files with 329 additions and 99 deletions

View File

@ -47,6 +47,10 @@ dependencies {
implementation("androidx.constraintlayout:constraintlayout:2.1.4") implementation("androidx.constraintlayout:constraintlayout:2.1.4")
kapt ("com.github.bumptech.glide:compiler:4.15.1") kapt ("com.github.bumptech.glide:compiler:4.15.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1")
defaultLibrary() defaultLibrary()
implementation(Dependencies.AndroidX.activity) implementation(Dependencies.AndroidX.activity)

View File

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> <!-- Минимальная и целевая версия SDK по тз (28 и 34) --> xmlns:tools="http://schemas.android.com/tools">
<!-- Минимальная и целевая версия SDK по тз (28 и 34) -->
<uses-sdk <uses-sdk
android:minSdkVersion="28" android:minSdkVersion="28"
android:targetSdkVersion="34" /> android:targetSdkVersion="34" />
@ -22,9 +24,6 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.Default" android:theme="@style/Theme.Default"
tools:targetApi="34"> tools:targetApi="34">
<activity
android:name=".ui.Main.AdminFragment"
android:exported="false" />
<activity <activity
android:name=".ui.RootActivity" android:name=".ui.RootActivity"
android:exported="true"> android:exported="true">

View File

@ -1,5 +1,12 @@
package ru.myitschool.work package ru.myitschool.work
object SessionManager { object SessionManager {
var userLogin: String = "" var userLogin: String? = null // Логин пользователя
var userRole: String? = null // Роль пользователя
// Метод для очистки данных сессии
fun clearSession() {
userLogin = null
userRole = null
}
} }

View File

@ -4,9 +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 ru.myitschool.work.core.Constants
import javax.inject.Singleton import javax.inject.Singleton
@ -16,9 +16,20 @@ object ApiModule {
@Provides @Provides
@Singleton @Singleton
fun provideRetrofit(): Retrofit { fun provideOkHttpClient(): OkHttpClient {
val username = "pivanov" // Замените на ваш логин
val password = "password123" // Замените на ваш пароль
return OkHttpClient.Builder()
.addInterceptor(AuthInterceptor(username, password))
.build()
}
@Provides
@Singleton
fun provideRetrofit(client: OkHttpClient): Retrofit {
return Retrofit.Builder() return Retrofit.Builder()
.baseUrl(Constants.SERVER_ADDRESS) .baseUrl(Constants.SERVER_ADDRESS)
.client(client)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
.build() .build()
} }

View File

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

View File

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

View File

@ -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:8090\"\n" const val SERVER_ADDRESS = "http://192.168.1.131:8080"
} }

View File

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

View File

@ -10,7 +10,6 @@ import androidx.fragment.app.setFragmentResultListener
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -47,11 +46,24 @@ class MainFragment : Fragment(R.layout.fragment_main) {
setupUI() setupUI()
fetchUserData() fetchUserData()
checkAdminAccess() // Проверяем доступ администратора
// Проверяем, есть ли результат QR // Проверяем, есть ли результат QR
checkQrResult() checkQrResult()
} }
private fun checkAdminAccess() {
// Проверяем, является ли пользователь администратором
if (SessionManager.userRole == "admin") {
binding.adminPanel.visibility = View.VISIBLE // Показываем кнопку AdminPanel
binding.adminPanel.setOnClickListener {
findNavController().navigate(R.id.adminFragment) // Переход на экран администратора
}
} else {
binding.adminPanel.visibility = View.GONE // Скрываем кнопку для обычных пользователей
}
}
private fun checkQrResult() { private fun checkQrResult() {
// Слушаем результат QR сканирования // Слушаем результат QR сканирования
setFragmentResultListener(QrScanDestination.REQUEST_KEY) { _, bundle -> setFragmentResultListener(QrScanDestination.REQUEST_KEY) { _, bundle ->
@ -81,25 +93,28 @@ class MainFragment : Fragment(R.layout.fragment_main) {
lifecycleScope.launch { lifecycleScope.launch {
showError(null) // Скрыть ошибку, если она была showError(null) // Скрыть ошибку, если она была
try { try {
val response = apiService.getUserInfo(SessionManager.userLogin) // Получаем данные пользователя val response =
if (response.isSuccessful) { SessionManager.userLogin?.let { apiService.getUserInfo(it) } // Получаем данные пользователя
response.body()?.let { data -> if (response != null) {
// Извлекаем значения из Map if (response.isSuccessful) {
val fullName = data["name"] as? String ?: "Неизвестно" response.body()?.let { data ->
val position = data["position"] as? String ?: "Неизвестно" // Извлекаем значения из Map
val lastVisit = data["lastVisit"] as? String ?: "Неизвестно" val fullName = data["name"] as? String ?: "Неизвестно"
val photoUrl = data["photo"] as? String ?: "" val position = data["position"] as? String ?: "Неизвестно"
val lastVisit = data["lastVisit"] as? String ?: "Неизвестно"
val photoUrl = data["photo"] as? String ?: ""
// Обновляем UI // Обновляем UI
updateUI(fullName, position, lastVisit, photoUrl) updateUI(fullName, position, lastVisit, photoUrl)
// Здесь вы можете добавить данные проходов в список // Здесь вы можете добавить данные проходов в список
// Пример: // Пример:
accessLogs.add(AccessLog("2024-02-31 08:31", "Считыватель 1", "карта")) accessLogs.add(AccessLog("2024-02-31 08:31", "Считыватель 1", "карта"))
accessLogAdapter.notifyDataSetChanged() // Обновляем адаптер accessLogAdapter.notifyDataSetChanged() // Обновляем адаптер
}
} else {
showError(getString(R.string.error_loading_data)) // Показываем ошибку, если данные не загрузились
} }
} else {
showError(getString(R.string.error_loading_data)) // Показываем ошибку, если данные не загрузились
} }
} catch (e: Exception) { } catch (e: Exception) {
showError(e.localizedMessage) // Показываем ошибку при исключении showError(e.localizedMessage) // Показываем ошибку при исключении

View File

@ -10,15 +10,17 @@ import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController 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.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
import ru.myitschool.work.utils.AuthPreferences import ru.myitschool.work.utils.AuthPreferences
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import androidx.lifecycle.lifecycleScope
@AndroidEntryPoint @AndroidEntryPoint
class LoginFragment : Fragment(R.layout.fragment_login) { class LoginFragment : Fragment(R.layout.fragment_login) {
@ -57,6 +59,7 @@ class LoginFragment : Fragment(R.layout.fragment_login) {
performLogin(username, password) // Передаем пароль в метод performLogin performLogin(username, password) // Передаем пароль в метод performLogin
} }
// Изначально скрываем индикаторы
binding.loading.visibleOrGone(false) binding.loading.visibleOrGone(false)
binding.error.visibleOrGone(false) binding.error.visibleOrGone(false)
} }
@ -76,30 +79,35 @@ class LoginFragment : Fragment(R.layout.fragment_login) {
private fun performLogin(username: String, password: String) { private fun performLogin(username: String, password: String) {
lifecycleScope.launch { lifecycleScope.launch {
binding.loading.visibleOrGone(true) // Показываем индикатор загрузки
viewModel.authenticate(username, password) // Передаем пароль в метод authenticate viewModel.authenticate(username, password) // Передаем пароль в метод authenticate
} }
} }
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()
} }
} }
} }
@ -108,7 +116,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,52 +1,48 @@
package ru.myitschool.work.ui.login package ru.myitschool.work.ui.login
import android.content.Context 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.SessionManager
import ru.myitschool.work.api.ApiService import ru.myitschool.work.api.ApiService
import ru.myitschool.work.core.Constants import ru.myitschool.work.api.UserAuthResponse
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 credentials = Credentials.basic(username, password) // Создаем Basic Auth заголовок val response = apiService.authenticate(username, password)
val response = apiService.authenticate(username, credentials) // Передаем заголовок в запрос Log.d("LoginViewModel", "Response code: ${response.code()}")
if (response.isSuccessful) { if (response.isSuccessful) {
SessionManager.userLogin = username val userAuthResponse = response.body() // Получаем JSON-ответ
_state.value = LoginState(success = true) Log.d("LoginViewModel", "User Auth Response: $userAuthResponse") // Логируем ответ
} else if (response.code() == 503) { // Пример кода для техработ
_state.value = LoginState(maintenance = true) // Обработка JSON-ответа
if (userAuthResponse != null) {
SessionManager.userLogin = username
SessionManager.userRole = userAuthResponse.role // Сохраняем роль
_state.value = LoginState(success = true) // Успешная авторизация
} else {
_state.value = LoginState(error = "Ошибка авторизации: Неверные учетные данные.")
}
} else { } else {
_state.value = LoginState(error = "Ошибка авторизации") _state.value = LoginState(error = "Ошибка авторизации: ${response.message()}")
} }
} catch (e: Exception) { } catch (e: Exception) {
// Логирование ошибки
e.printStackTrace() e.printStackTrace()
_state.value = LoginState(error = "Ошибка сети. Проверьте подключение к интернету.") _state.value = LoginState(error = "Ошибка сети. Проверьте подключение к интернету.")
} }
@ -57,8 +53,14 @@ 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, val error: String? = null, val maintenance: Boolean = false) // Состояние аутентификации
} data class LoginState(
val success: Boolean = false, // Успешность аутентификации
val userAuthResponse: UserAuthResponse? = null, // Ответ с информацией о пользователе
val error: String? = null, // Сообщение об ошибке
val maintenance: Boolean = false // Состояние техобслуживания
)

View File

@ -55,11 +55,19 @@ class QrResult : Fragment(R.layout.fragment_qr_scan_result) {
private fun sendRequestToServer(qrData: String) { private fun sendRequestToServer(qrData: String) {
lifecycleScope.launch { lifecycleScope.launch {
try { try {
val response = apiService.openDoor(SessionManager.userLogin, OpenDoorRequest(qrData)) // Проверяем, что userLogin не равен null
if (response.isSuccessful) { val login = SessionManager.userLogin
binding.result.text = "Успешно/Success" if (login != null) {
// Создаем объект OpenDoorRequest с логином и кодом
val openDoorRequest = OpenDoorRequest(login, qrData.toLong()) // Преобразуем qrData в Long, если это необходимо
val response = apiService.openDoor(openDoorRequest) // Теперь передаем только openDoorRequest
if (response.isSuccessful) {
binding.result.text = "Успешно/Success"
} else {
binding.result.text = "Что-то пошло не так/Something wrong"
}
} else { } else {
binding.result.text = "Что-то пошло не так/Something wrong" binding.result.text = "Пользователь не авторизован/Unauthorized user"
} }
} catch (e: Exception) { } catch (e: Exception) {
binding.result.text = "Что-то пошло не так/Something wrong" binding.result.text = "Что-то пошло не так/Something wrong"

View File

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

View File

@ -15,7 +15,7 @@
android:text="@string/fullname_label" android:text="@string/fullname_label"
android:textSize="18sp" android:textSize="18sp"
android:layout_marginBottom="5dp" android:layout_marginBottom="5dp"
android:visibility="visible" /> android:visibility="gone" />
<!-- Фото пользователя. --> <!-- Фото пользователя. -->
<ImageView <ImageView
@ -25,7 +25,7 @@
android:layout_gravity="center" android:layout_gravity="center"
android:contentDescription="@string/photo_description" android:contentDescription="@string/photo_description"
android:layout_marginBottom="5dp" android:layout_marginBottom="5dp"
android:visibility="visible" /> android:visibility="gone" />
<!-- Поле для должности --> <!-- Поле для должности -->
<TextView <TextView
@ -34,7 +34,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/position_label" android:text="@string/position_label"
android:layout_marginBottom="5dp" android:layout_marginBottom="5dp"
android:visibility="visible" /> android:visibility="gone" />
<!-- Поле для даты последнего входа --> <!-- Поле для даты последнего входа -->
<TextView <TextView
@ -43,7 +43,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="2024-02-31 08:31" android:text="2024-02-31 08:31"
android:layout_marginBottom="75dp" android:layout_marginBottom="75dp"
android:visibility="visible" /> android:visibility="gone" />
<!-- Кнопка обновления --> <!-- Кнопка обновления -->
<Button <Button
@ -63,7 +63,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/error_placeholder" android:text="@string/error_placeholder"
android:textColor="@android:color/holo_red_dark" android:textColor="@android:color/holo_red_dark"
android:visibility="visible" /> android:visibility="gone" />
<!-- RecyclerView для списка проходов --> <!-- RecyclerView для списка проходов -->
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
@ -82,7 +82,7 @@
android:layout_marginBottom="12dp" android:layout_marginBottom="12dp"
android:backgroundTint="@color/colorPrimary" android:backgroundTint="@color/colorPrimary"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:visibility="visible" /> android:visibility="gone" />
<Button <Button
android:id="@+id/logout" android:id="@+id/logout"
@ -92,5 +92,14 @@
android:layout_marginBottom="50dp" android:layout_marginBottom="50dp"
android:backgroundTint="@color/colorPrimary" android:backgroundTint="@color/colorPrimary"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:visibility="visible" /> android:visibility="gone" />
<Button
android:id="@+id/admin_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Admin Panel"
android:layout_marginTop="16dp"
android:visibility="gone" />
</LinearLayout> </LinearLayout>

View File

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