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")
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()
implementation(Dependencies.AndroidX.activity)

View File

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<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
android:minSdkVersion="28"
android:targetSdkVersion="34" />
@ -22,9 +24,6 @@
android:supportsRtl="true"
android:theme="@style/Theme.Default"
tools:targetApi="34">
<activity
android:name=".ui.Main.AdminFragment"
android:exported="false" />
<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,9 +4,9 @@ import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import ru.myitschool.work.api.ApiService
import ru.myitschool.work.core.Constants
import javax.inject.Singleton
@ -16,9 +16,20 @@ object ApiModule {
@Provides
@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()
.baseUrl(Constants.SERVER_ADDRESS)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
}

View File

@ -3,22 +3,56 @@ package ru.myitschool.work.api
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.PATCH
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query
interface ApiService {
@GET("api/{login}/auth")
// Метод для аутентификации
@GET("/api/auth") // Используем GET для аутентификации
suspend fun authenticate(
@Path("login") login: String,
@Header("Authorization") authorization: String // Добавляем заголовок Authorization
): Response<Unit>
@Query("login") login: String, // Передаем логин как параметр запроса
@Query("password") password: String // Передаем пароль как параметр запроса
): 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")
suspend fun openDoor(@Path("login") login: String, @Body body: OpenDoorRequest): Response<Unit>
@GET("/api/employee/{login}") // Получение информации о сотруднике
suspend fun getEmployeeInfo(@Path("login") login: String): Response<EmployeeData>
@PATCH("/api/open") // Открыть дверь
suspend fun openDoor(@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
// БЕРИТЕ И ИЗМЕНЯЙТЕ ХОСТ ТОЛЬКО ЗДЕСЬ И НЕ БЕРИТЕ ИЗ ДРУГИХ МЕСТ. ФАЙЛ ПЕРЕМЕЩАТЬ НЕЛЬЗЯ
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
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import android.view.View
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import ru.myitschool.work.R
import ru.myitschool.work.api.ApiService
import ru.myitschool.work.api.EmployeeData
import ru.myitschool.work.api.ToggleAccessRequest
import ru.myitschool.work.core.Constants
import ru.myitschool.work.databinding.FragmentAdminBinding
class AdminFragment : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.fragment_admin)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
class AdminFragment : Fragment(R.layout.fragment_admin) {
private var _binding: FragmentAdminBinding? = null
private val binding get() = _binding!!
private val apiService: ApiService by lazy {
Retrofit.Builder()
.baseUrl(Constants.SERVER_ADDRESS)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
private var isAccessBlocked: Boolean = false // Переменная для отслеживания состояния доступа
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentAdminBinding.bind(view)
setupUI()
}
private fun setupUI() {
binding.viewEmployeeInfo.setOnClickListener {
val login = binding.employeeLogin.text.toString()
if (login.isNotEmpty()) {
fetchEmployeeInfo(login)
} else {
Toast.makeText(requireContext(), "Введите логин сотрудника", Toast.LENGTH_SHORT).show()
}
}
binding.toggleAccess.setOnClickListener {
val login = binding.employeeLogin.text.toString()
if (login.isNotEmpty()) {
// Определяем действие на основе текущего состояния доступа
val action = if (isAccessBlocked) "unblock" else "block"
toggleEmployeeAccess(login, action)
} else {
Toast.makeText(requireContext(), "Введите логин сотрудника", Toast.LENGTH_SHORT).show()
}
}
}
private fun fetchEmployeeInfo(login: String) {
lifecycleScope.launch {
try {
val response = apiService.getEmployeeInfo(login)
if (response.isSuccessful) {
val employeeData = response.body()
employeeData?.let {
binding.employeeInfo.text = "Имя: ${it.name}, Должность: ${it.position}, Последний визит: ${it.lastVisit}"
binding.employeeInfo.visibility = View.VISIBLE
binding.toggleAccess.visibility = View.VISIBLE
// Здесь можно установить состояние доступа, если оно доступно
isAccessBlocked = false // Предположим, что доступ не заблокирован по умолчанию
}
} else {
Toast.makeText(requireContext(), "Ошибка получения данных", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
e.printStackTrace()
Toast.makeText(requireContext(), "Ошибка сети", Toast.LENGTH_SHORT).show()
}
}
}
private fun toggleEmployeeAccess(login: String, action: String) {
val request = ToggleAccessRequest(login, action)
lifecycleScope.launch {
try {
val response = apiService.toggleAccess(request)
if (response.isSuccessful) {
isAccessBlocked = !isAccessBlocked // Переключаем состояние доступа
val message = if (action == "block") "Доступ заблокирован" else "Доступ разблокирован"
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(requireContext(), "Ошибка изменения доступа", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
e.printStackTrace()
Toast.makeText(requireContext(), "Ошибка сети", Toast.LENGTH_SHORT).show()
}
}
}
override fun onDestroyView() {
_binding = null // Освобождаем binding, когда представление уничтожается
super.onDestroyView()
}
}

View File

@ -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) {
@ -57,6 +59,7 @@ class LoginFragment : Fragment(R.layout.fragment_login) {
performLogin(username, password) // Передаем пароль в метод performLogin
}
// Изначально скрываем индикаторы
binding.loading.visibleOrGone(false)
binding.error.visibleOrGone(false)
}
@ -76,30 +79,35 @@ 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
}
}
private fun subscribe() {
viewModel.state.collectWhenStarted(this) { state ->
binding.loading.visibleOrGone(false)
lifecycleScope.launch {
viewModel.state.collect { state ->
binding.loading.visibleOrGone(false)
try {
if (state.maintenance) {
showMaintenanceDialog() // Показываем диалог о техработах
} else if (state.error != null) {
binding.error.text = state.error
binding.error.visibility = View.VISIBLE
} else if (state.success) {
binding.error.visibility = View.GONE
authPreferences.saveLoginState(true)
authPreferences.saveLogin(binding.username.text.toString()) // Сохраняем логин
// Сохраняем роль пользователя в SessionManager
val userAuthResponse: UserAuthResponse? = state.userAuthResponse
userAuthResponse?.let {
SessionManager.userRole = it.role // Сохраняем роль
}
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())
.setTitle("Технические работы")
.setMessage("Проводятся техработы, пожалуйста, подождите.")
.setPositiveButton("ОК") { dialog, _ -> dialog.dismiss() }
.setPositiveButton("ОК ") { dialog, _ -> dialog.dismiss() }
.setCancelable(false)
.show()
}

View File

@ -1,52 +1,48 @@
package ru.myitschool.work.ui.login
import android.content.Context
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import 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.core.Constants
import ru.myitschool.work.api.UserAuthResponse
import javax.inject.Inject
@HiltViewModel
class LoginViewModel @Inject constructor(
@ApplicationContext private val context: Context,
private val apiService: ApiService
) : ViewModel() {
private val _state = MutableStateFlow(LoginState())
val state = _state.asStateFlow()
private val apiService: ApiService by lazy {
Retrofit.Builder()
.baseUrl(Constants.SERVER_ADDRESS)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
private val _state = MutableStateFlow(LoginState())
val state: StateFlow<LoginState> get() = _state
fun authenticate(username: String, password: String) {
if (isValidUsername(username)) {
viewModelScope.launch {
try {
val credentials = Credentials.basic(username, password) // Создаем Basic Auth заголовок
val response = apiService.authenticate(username, credentials) // Передаем заголовок в запрос
val response = apiService.authenticate(username, password)
Log.d("LoginViewModel", "Response code: ${response.code()}")
if (response.isSuccessful) {
SessionManager.userLogin = username
_state.value = LoginState(success = true)
} else if (response.code() == 503) { // Пример кода для техработ
_state.value = LoginState(maintenance = true)
val userAuthResponse = response.body() // Получаем JSON-ответ
Log.d("LoginViewModel", "User Auth Response: $userAuthResponse") // Логируем ответ
// Обработка JSON-ответа
if (userAuthResponse != null) {
SessionManager.userLogin = username
SessionManager.userRole = userAuthResponse.role // Сохраняем роль
_state.value = LoginState(success = true) // Успешная авторизация
} else {
_state.value = LoginState(error = "Ошибка авторизации: Неверные учетные данные.")
}
} else {
_state.value = LoginState(error = "Ошибка авторизации")
_state.value = LoginState(error = "Ошибка авторизации: ${response.message()}")
}
} catch (e: Exception) {
// Логирование ошибки
e.printStackTrace()
_state.value = LoginState(error = "Ошибка сети. Проверьте подключение к интернету.")
}
@ -57,8 +53,14 @@ class LoginViewModel @Inject constructor(
}
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) {
lifecycleScope.launch {
try {
val response = apiService.openDoor(SessionManager.userLogin, OpenDoorRequest(qrData))
if (response.isSuccessful) {
binding.result.text = "Успешно/Success"
// Проверяем, что userLogin не равен null
val login = SessionManager.userLogin
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 {
binding.result.text = "Что-то пошло не так/Something wrong"
binding.result.text = "Пользователь не авторизован/Unauthorized user"
}
} catch (e: Exception) {
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:textSize="18sp"
android:layout_marginBottom="5dp"
android:visibility="visible" />
android:visibility="gone" />
<!-- Фото пользователя. -->
<ImageView
@ -25,7 +25,7 @@
android:layout_gravity="center"
android:contentDescription="@string/photo_description"
android:layout_marginBottom="5dp"
android:visibility="visible" />
android:visibility="gone" />
<!-- Поле для должности -->
<TextView
@ -34,7 +34,7 @@
android:layout_height="wrap_content"
android:text="@string/position_label"
android:layout_marginBottom="5dp"
android:visibility="visible" />
android:visibility="gone" />
<!-- Поле для даты последнего входа -->
<TextView
@ -43,7 +43,7 @@
android:layout_height="wrap_content"
android:text="2024-02-31 08:31"
android:layout_marginBottom="75dp"
android:visibility="visible" />
android:visibility="gone" />
<!-- Кнопка обновления -->
<Button
@ -63,7 +63,7 @@
android:layout_height="wrap_content"
android:text="@string/error_placeholder"
android:textColor="@android:color/holo_red_dark"
android:visibility="visible" />
android:visibility="gone" />
<!-- RecyclerView для списка проходов -->
<androidx.recyclerview.widget.RecyclerView
@ -82,7 +82,7 @@
android:layout_marginBottom="12dp"
android:backgroundTint="@color/colorPrimary"
android:textColor="@android:color/white"
android:visibility="visible" />
android:visibility="gone" />
<Button
android:id="@+id/logout"
@ -92,5 +92,14 @@
android:layout_marginBottom="50dp"
android:backgroundTint="@color/colorPrimary"
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>

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"