day2_commit_10_fixed_alotofbugs_added_admin_logic

This commit is contained in:
Terebov_Maksim 2025-02-19 14:12:47 +03:00
parent 1d9207a5f5
commit 85a5c82d8c
10 changed files with 238 additions and 83 deletions

View File

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

View File

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

View File

@ -4,21 +4,41 @@ import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.PATCH
import retrofit2.http.POST
import retrofit2.http.Path
interface ApiService {
@GET("api/{login}/auth")
suspend fun authenticate(
@Path("login") login: String,
@Header("Authorization") authorization: String // Добавляем заголовок Authorization
): Response<Unit>
@Header("Authorization") authorization: String
): Response<UserAuthResponse> // Изменяем на UserAuthResponse
@GET("api/{login}/info")
suspend fun getUserInfo(@Path("login") login: String): Response<Map<String, Any>> // Возвращаем Map вместо UserInfo
suspend fun getUserInfo(@Path("login") login: String): Response<Map<String, Any>>
@PATCH("api/{login}/open")
suspend fun openDoor(@Path("login") login: String, @Body body: OpenDoorRequest): Response<Unit>
@POST("api/employee/toggleAccess")
suspend fun toggleAccess(@Body request: ToggleAccessRequest): Response<Unit>
// Метод для получения информации о сотруднике
@GET("api/employee/{login}")
suspend fun getEmployeeInfo(@Path("login") login: String): Response<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"
)

View File

@ -1,60 +1,108 @@
package ru.myitschool.work.ui.Main
package ru.myitschool.work.ui.admin
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import ru.myitschool.work.R
import ru.myitschool.work.api.ApiService
import ru.myitschool.work.api.EmployeeData
import ru.myitschool.work.api.ToggleAccessRequest
import ru.myitschool.work.core.Constants
import ru.myitschool.work.databinding.FragmentAdminBinding
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
class AdminFragment : Fragment(R.layout.fragment_admin) {
private var _binding: FragmentAdminBinding? = null
private val binding get() = _binding!!
/**
* A simple [Fragment] subclass.
* Use the [AdminFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class AdminFragment : Fragment() {
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
private val apiService: ApiService by lazy {
Retrofit.Builder()
.baseUrl(Constants.SERVER_ADDRESS)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
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()
}
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_admin, container, false)
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()
}
}
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment AdminFragment.
*/
// TODO: Rename and change types and number of parameters
@JvmStatic
fun newInstance(param1: String, param2: String) =
AdminFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
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.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -47,11 +46,24 @@ class MainFragment : Fragment(R.layout.fragment_main) {
setupUI()
fetchUserData()
checkAdminAccess() // Проверяем доступ администратора
// Проверяем, есть ли результат QR
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() {
// Слушаем результат QR сканирования
setFragmentResultListener(QrScanDestination.REQUEST_KEY) { _, bundle ->
@ -81,25 +93,28 @@ class MainFragment : Fragment(R.layout.fragment_main) {
lifecycleScope.launch {
showError(null) // Скрыть ошибку, если она была
try {
val response = apiService.getUserInfo(SessionManager.userLogin) // Получаем данные пользователя
if (response.isSuccessful) {
response.body()?.let { data ->
// Извлекаем значения из Map
val fullName = data["name"] as? String ?: "Неизвестно"
val position = data["position"] as? String ?: "Неизвестно"
val lastVisit = data["lastVisit"] as? String ?: "Неизвестно"
val photoUrl = data["photo"] as? String ?: ""
val response =
SessionManager.userLogin?.let { apiService.getUserInfo(it) } // Получаем данные пользователя
if (response != null) {
if (response.isSuccessful) {
response.body()?.let { data ->
// Извлекаем значения из Map
val fullName = data["name"] as? String ?: "Неизвестно"
val position = data["position"] as? String ?: "Неизвестно"
val lastVisit = data["lastVisit"] as? String ?: "Неизвестно"
val photoUrl = data["photo"] as? String ?: ""
// Обновляем UI
updateUI(fullName, position, lastVisit, photoUrl)
// Обновляем UI
updateUI(fullName, position, lastVisit, photoUrl)
// Здесь вы можете добавить данные проходов в список
// Пример:
accessLogs.add(AccessLog("2024-02-31 08:31", "Считыватель 1", "карта"))
accessLogAdapter.notifyDataSetChanged() // Обновляем адаптер
// Здесь вы можете добавить данные проходов в список
// Пример:
accessLogs.add(AccessLog("2024-02-31 08:31", "Считыватель 1", "карта"))
accessLogAdapter.notifyDataSetChanged() // Обновляем адаптер
}
} else {
showError(getString(R.string.error_loading_data)) // Показываем ошибку, если данные не загрузились
}
} else {
showError(getString(R.string.error_loading_data)) // Показываем ошибку, если данные не загрузились
}
} catch (e: Exception) {
showError(e.localizedMessage) // Показываем ошибку при исключении

View File

@ -10,15 +10,17 @@ import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import dagger.hilt.android.AndroidEntryPoint
import ru.myitschool.work.R
import ru.myitschool.work.SessionManager
import ru.myitschool.work.api.UserAuthResponse
import ru.myitschool.work.databinding.FragmentLoginBinding
import ru.myitschool.work.utils.collectWhenStarted
import ru.myitschool.work.utils.visibleOrGone
import ru.myitschool.work.utils.AuthPreferences
import kotlinx.coroutines.launch
import androidx.lifecycle.lifecycleScope
@AndroidEntryPoint
class LoginFragment : Fragment(R.layout.fragment_login) {
@ -76,6 +78,7 @@ class LoginFragment : Fragment(R.layout.fragment_login) {
private fun performLogin(username: String, password: String) {
lifecycleScope.launch {
binding.loading.visibleOrGone(true) // Показываем индикатор загрузки
viewModel.authenticate(username, password) // Передаем пароль в метод authenticate
}
}
@ -94,6 +97,13 @@ class LoginFragment : Fragment(R.layout.fragment_login) {
binding.error.visibility = View.GONE
authPreferences.saveLoginState(true)
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()
navigateToMainScreen()
}

View File

@ -13,6 +13,7 @@ import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import ru.myitschool.work.SessionManager
import ru.myitschool.work.api.ApiService
import ru.myitschool.work.api.UserAuthResponse
import ru.myitschool.work.core.Constants
import javax.inject.Inject
@ -38,8 +39,12 @@ class LoginViewModel @Inject constructor(
val credentials = Credentials.basic(username, password) // Создаем Basic Auth заголовок
val response = apiService.authenticate(username, credentials) // Передаем заголовок в запрос
if (response.isSuccessful) {
SessionManager.userLogin = username
_state.value = LoginState(success = true)
val userAuthResponse = response.body() // Получаем ответ с ролью пользователя
userAuthResponse?.let {
SessionManager.userLogin = username // Сохраняем логин
SessionManager.userRole = it.role // Сохраняем роль
}
_state.value = LoginState(success = true, userAuthResponse = userAuthResponse)
} else if (response.code() == 503) { // Пример кода для техработ
_state.value = LoginState(maintenance = true)
} else {
@ -60,5 +65,10 @@ class LoginViewModel @Inject constructor(
return username.length >= 3 && !username.first().isDigit() && username.all { it.isLetterOrDigit() }
}
data class LoginState(val success: Boolean = false, val error: String? = null, val maintenance: Boolean = false)
data class LoginState(
val success: Boolean = false,
val error: String? = null,
val maintenance: Boolean = false,
val userAuthResponse: UserAuthResponse? = null // Добавляем поле для хранения информации о роли
)
}

View File

@ -1,14 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.Main.AdminFragment">
android:orientation="vertical"
android:padding="16dp">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Введите логин сотрудника:"
android:textSize="18sp"
android:layout_marginBottom="8dp" />
</FrameLayout>
<EditText
android:id="@+id/employee_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Логин сотрудника"
android:inputType="text" />
<Button
android:id="@+id/view_employee_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Просмотреть информацию о сотруднике"
android:layout_marginTop="16dp" />
<TextView
android:id="@+id/employee_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textSize="16sp"
android:visibility="gone" />
<Button
android:id="@+id/toggle_access"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Блокировать/Разблокировать доступ"
android:layout_marginTop="16dp"
android:visibility="gone" />
</LinearLayout>

View File

@ -93,4 +93,13 @@
android:backgroundTint="@color/colorPrimary"
android:textColor="@android:color/white"
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>

View File

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