From 041db2955f38db5cb0b052b05345352c74e1276c Mon Sep 17 00:00:00 2001 From: yastruckov Date: Thu, 20 Feb 2025 12:32:31 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=9F=D0=BE=D0=BB=D1=83=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=BF=D0=BE=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BD?= =?UTF-8?q?=D1=83=20+=20=D1=84=D0=B8=D0=BA=D1=81=D1=8B=20=D0=B1=D0=B0?= =?UTF-8?q?=D0=B3=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/ru/myitschool/work/admin.kt | 59 ------------- .../LastEntranceNetworkDataSource.kt | 2 +- ...ce.kt => EmployeeInfoNetworkDataSource.kt} | 26 ++++-- ...ileRepoImpl.kt => EmployeeInfoRepoImpl.kt} | 8 +- ...oyeeProfileRepo.kt => EmployeeInfoRepo.kt} | 2 +- ...leUseCase.kt => GetEmployeeInfoUseCase.kt} | 4 +- .../myitschool/work/ui/admin/AdminFragment.kt | 83 +++++++++++++++++++ .../work/ui/admin/AdminViewModel.kt | 61 ++++++++++++++ .../myitschool/work/ui/login/LoginFragment.kt | 3 + .../myitschool/work/ui/main/MainFragment.kt | 34 ++++++-- app/src/main/res/navigation/nav_graph.xml | 11 +++ app/src/main/res/values-en/strings.xml | 4 + app/src/main/res/values/strings.xml | 4 + 13 files changed, 221 insertions(+), 80 deletions(-) delete mode 100644 app/src/main/java/ru/myitschool/work/admin.kt rename app/src/main/java/ru/myitschool/work/data/profile/admin/{EmployeeNetworkDataSource.kt => EmployeeInfoNetworkDataSource.kt} (60%) rename app/src/main/java/ru/myitschool/work/data/profile/admin/{EmployeeProfileRepoImpl.kt => EmployeeInfoRepoImpl.kt} (77%) rename app/src/main/java/ru/myitschool/work/domain/profile/admin/{EmployeeProfileRepo.kt => EmployeeInfoRepo.kt} (83%) rename app/src/main/java/ru/myitschool/work/domain/profile/admin/{GetEmployeeProfileUseCase.kt => GetEmployeeInfoUseCase.kt} (62%) create mode 100644 app/src/main/java/ru/myitschool/work/ui/admin/AdminFragment.kt create mode 100644 app/src/main/java/ru/myitschool/work/ui/admin/AdminViewModel.kt diff --git a/app/src/main/java/ru/myitschool/work/admin.kt b/app/src/main/java/ru/myitschool/work/admin.kt deleted file mode 100644 index 4e9f75f..0000000 --- a/app/src/main/java/ru/myitschool/work/admin.kt +++ /dev/null @@ -1,59 +0,0 @@ -package ru.myitschool.work - -import android.os.Bundle -import androidx.fragment.app.Fragment -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup - -// 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" - -/** - * A simple [Fragment] subclass. - * Use the [admin.newInstance] factory method to - * create an instance of this fragment. - */ -class admin : Fragment() { - // TODO: Rename and change types of parameters - private var param1: String? = null - private var param2: String? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - arguments?.let { - param1 = it.getString(ARG_PARAM1) - param2 = it.getString(ARG_PARAM2) - } - } - - 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) - } - - 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 admin. - */ - // TODO: Rename and change types and number of parameters - @JvmStatic - fun newInstance(param1: String, param2: String) = - admin().apply { - arguments = Bundle().apply { - putString(ARG_PARAM1, param1) - putString(ARG_PARAM2, param2) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/entrance/lastEntrance/LastEntranceNetworkDataSource.kt b/app/src/main/java/ru/myitschool/work/data/entrance/lastEntrance/LastEntranceNetworkDataSource.kt index 6500ba5..c9ce080 100644 --- a/app/src/main/java/ru/myitschool/work/data/entrance/lastEntrance/LastEntranceNetworkDataSource.kt +++ b/app/src/main/java/ru/myitschool/work/data/entrance/lastEntrance/LastEntranceNetworkDataSource.kt @@ -28,7 +28,7 @@ class LastEntranceNetworkDataSource( basicAuth(username, password) } } - if (result.status != HttpStatusCode.OK) { + if (result.status != HttpStatusCode.OK && result.status != HttpStatusCode.NoContent) { error("Status ${result.status}") } result.body() diff --git a/app/src/main/java/ru/myitschool/work/data/profile/admin/EmployeeNetworkDataSource.kt b/app/src/main/java/ru/myitschool/work/data/profile/admin/EmployeeInfoNetworkDataSource.kt similarity index 60% rename from app/src/main/java/ru/myitschool/work/data/profile/admin/EmployeeNetworkDataSource.kt rename to app/src/main/java/ru/myitschool/work/data/profile/admin/EmployeeInfoNetworkDataSource.kt index 0c13576..06e8821 100644 --- a/app/src/main/java/ru/myitschool/work/data/profile/admin/EmployeeNetworkDataSource.kt +++ b/app/src/main/java/ru/myitschool/work/data/profile/admin/EmployeeInfoNetworkDataSource.kt @@ -3,6 +3,7 @@ package ru.myitschool.work.data.profile.admin import android.content.Context import io.ktor.client.call.body import io.ktor.client.request.basicAuth +import io.ktor.client.request.get import io.ktor.client.request.post import io.ktor.client.statement.bodyAsText import io.ktor.http.HttpStatusCode @@ -10,28 +11,43 @@ import io.ktor.http.headers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext +import ru.myitschool.work.R import ru.myitschool.work.core.Constants import ru.myitschool.work.data.UserDataStoreManager import ru.myitschool.work.dto.EmployeeDTO import ru.myitschool.work.utils.NetworkModule -class EmployeeNetworkDataSource( - context: Context +class EmployeeInfoNetworkDataSource( + private val context: Context ) { private val client = NetworkModule.httpClient private val userDataStoreManager = UserDataStoreManager.getInstance(context) suspend fun getProfile(login : String):Result = withContext(Dispatchers.IO){ runCatching { + println(login) val username = userDataStoreManager.usernameFlow.first() + println(username) val password = userDataStoreManager.passwordFlow.first() - val result = client.post("${Constants.SERVER_ADDRESS}/api/employee/$login"){ + val result = client.get("${Constants.SERVER_ADDRESS}/api/employee/$login"){ headers{ basicAuth(username, password) } } - if (result.status != HttpStatusCode.OK) { - error("Status ${result.status}") + when(result.status){ + HttpStatusCode.Unauthorized -> {context.getString(R.string.admin_unauthorized)} + HttpStatusCode.Forbidden -> error(context.getString(R.string.admin_forbidden)) + HttpStatusCode.NotFound -> error(context.getString(R.string.not_found)) + HttpStatusCode.OK -> result.body() } + if(result.status == HttpStatusCode.Unauthorized){ + error(context.getString(R.string.admin_unauthorized)) + } + if (result.status != HttpStatusCode.OK) { + println(result.status) + error("Status ${result.status}") + + } + println(result.bodyAsText()) result.body() } diff --git a/app/src/main/java/ru/myitschool/work/data/profile/admin/EmployeeProfileRepoImpl.kt b/app/src/main/java/ru/myitschool/work/data/profile/admin/EmployeeInfoRepoImpl.kt similarity index 77% rename from app/src/main/java/ru/myitschool/work/data/profile/admin/EmployeeProfileRepoImpl.kt rename to app/src/main/java/ru/myitschool/work/data/profile/admin/EmployeeInfoRepoImpl.kt index ee9c8de..98dd1f8 100644 --- a/app/src/main/java/ru/myitschool/work/data/profile/admin/EmployeeProfileRepoImpl.kt +++ b/app/src/main/java/ru/myitschool/work/data/profile/admin/EmployeeInfoRepoImpl.kt @@ -1,11 +1,11 @@ package ru.myitschool.work.data.profile.admin import ru.myitschool.work.entities.EmployeeEntity -import ru.myitschool.work.domain.profile.admin.EmployeeProfileRepo +import ru.myitschool.work.domain.profile.admin.EmployeeInfoRepo -class EmployeeProfileRepoImpl( - private val networkDataSource: EmployeeNetworkDataSource -) : EmployeeProfileRepo { +class EmployeeInfoRepoImpl( + private val networkDataSource: EmployeeInfoNetworkDataSource +) : EmployeeInfoRepo { override suspend fun getInfo(login : String): Result { return networkDataSource.getProfile(login).map { dto -> EmployeeEntity( diff --git a/app/src/main/java/ru/myitschool/work/domain/profile/admin/EmployeeProfileRepo.kt b/app/src/main/java/ru/myitschool/work/domain/profile/admin/EmployeeInfoRepo.kt similarity index 83% rename from app/src/main/java/ru/myitschool/work/domain/profile/admin/EmployeeProfileRepo.kt rename to app/src/main/java/ru/myitschool/work/domain/profile/admin/EmployeeInfoRepo.kt index a15a9b0..0d87fca 100644 --- a/app/src/main/java/ru/myitschool/work/domain/profile/admin/EmployeeProfileRepo.kt +++ b/app/src/main/java/ru/myitschool/work/domain/profile/admin/EmployeeInfoRepo.kt @@ -2,6 +2,6 @@ package ru.myitschool.work.domain.profile.admin import ru.myitschool.work.entities.EmployeeEntity -interface EmployeeProfileRepo { +interface EmployeeInfoRepo { suspend fun getInfo(login : String): Result } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/profile/admin/GetEmployeeProfileUseCase.kt b/app/src/main/java/ru/myitschool/work/domain/profile/admin/GetEmployeeInfoUseCase.kt similarity index 62% rename from app/src/main/java/ru/myitschool/work/domain/profile/admin/GetEmployeeProfileUseCase.kt rename to app/src/main/java/ru/myitschool/work/domain/profile/admin/GetEmployeeInfoUseCase.kt index bd4d478..8294d15 100644 --- a/app/src/main/java/ru/myitschool/work/domain/profile/admin/GetEmployeeProfileUseCase.kt +++ b/app/src/main/java/ru/myitschool/work/domain/profile/admin/GetEmployeeInfoUseCase.kt @@ -1,7 +1,7 @@ package ru.myitschool.work.domain.profile.admin -class GetEmployeeProfileUseCase( - private val repo : EmployeeProfileRepo +class GetEmployeeInfoUseCase( + private val repo : EmployeeInfoRepo ) { suspend operator fun invoke(login : String) = repo.getInfo(login) } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/admin/AdminFragment.kt b/app/src/main/java/ru/myitschool/work/ui/admin/AdminFragment.kt new file mode 100644 index 0000000..7d25b93 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/admin/AdminFragment.kt @@ -0,0 +1,83 @@ +package ru.myitschool.work.ui.admin + +import android.content.res.ColorStateList +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.View +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import ru.myitschool.work.R +import ru.myitschool.work.databinding.FragmentAdminBinding +import ru.myitschool.work.entities.EmployeeEntity +import ru.myitschool.work.ui.login.LoginViewModel +import ru.myitschool.work.utils.collectWithLifecycle + +class AdminFragment : Fragment(R.layout.fragment_admin) { + private var _binding: FragmentAdminBinding? = null + private val binding get() = _binding!! + private val viewModel: AdminViewModel by viewModels{ AdminViewModel.Factory } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + _binding = FragmentAdminBinding.bind(view) + binding.searchBtn.setOnClickListener { + viewModel.searchUser( + binding.search.text.toString() + ) + } + + val textWatcher = object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { + + } + + override fun afterTextChanged(s: Editable?) {} + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + val search = binding.search.text + val isEnabled = + search.length >= 3 && !search[0].isDigit() && search.matches(Regex("^[a-zA-Z0-9]*$")) + binding.searchBtn.isEnabled = isEnabled + if (isEnabled) { + binding.searchBtn.setBackgroundColor(resources.getColor(R.color.accent_color)) + binding.searchBtn.imageTintList = ColorStateList.valueOf( + ContextCompat.getColor( + requireContext(), + R.color.white + )) + } else { + binding.searchBtn.setBackgroundColor(resources.getColor(R.color.bg_color)) + binding.searchBtn.imageTintList = ColorStateList.valueOf( + ContextCompat.getColor( + requireContext(), + R.color.secondary_text_color + )) + } + + + } + } + binding.search.addTextChangedListener(textWatcher) + viewModel.infoState.collectWithLifecycle(this){ state -> + when(state){ + is AdminViewModel.SearchState.Error -> { + binding.error.visibility = View.VISIBLE + binding.error.text = state.message + + } + AdminViewModel.SearchState.Loading -> { + binding.error.visibility = View.GONE + binding + } + is AdminViewModel.SearchState.Success -> { + binding.error.visibility = View.GONE + showUserData(state.data) + } + } + } + } + private fun showUserData(user: EmployeeEntity){ + binding.userName.text = user.name + binding.position.text = user.position + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/admin/AdminViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/admin/AdminViewModel.kt new file mode 100644 index 0000000..c1ff377 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/admin/AdminViewModel.kt @@ -0,0 +1,61 @@ +package ru.myitschool.work.ui.admin + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import ru.myitschool.work.data.profile.admin.EmployeeInfoNetworkDataSource +import ru.myitschool.work.data.profile.admin.EmployeeInfoRepoImpl +import ru.myitschool.work.domain.profile.admin.EmployeeInfoRepo +import ru.myitschool.work.domain.profile.admin.GetEmployeeInfoUseCase +import ru.myitschool.work.entities.EmployeeEntity + +class AdminViewModel( + private val getInfoUseCase: GetEmployeeInfoUseCase, + application: Application +) : AndroidViewModel(application) { + private val _infoState = MutableStateFlow(SearchState.Loading) + val infoState: StateFlow = _infoState.asStateFlow() + + sealed class SearchState { + data object Loading : SearchState() + data class Success(val data: EmployeeEntity) : SearchState() + data class Error(val message: String?) : SearchState() + } + fun searchUser(login : String){ + _infoState.value = SearchState.Loading + viewModelScope.launch { + getInfoUseCase.invoke(login).fold( + onSuccess = { data -> + _infoState.value = SearchState.Success(data) + }, + onFailure = { e -> + _infoState.value = SearchState.Error(e.message) + } + ) + } + } + companion object { + @Suppress("UNCHECKED_CAST") + val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory { + override fun create(modelClass: Class, extras: CreationExtras): T { + val infoRepoImpl = EmployeeInfoRepoImpl( + networkDataSource = EmployeeInfoNetworkDataSource( + context = extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as Application + ) + ) + val useCase = GetEmployeeInfoUseCase(infoRepoImpl) + + return AdminViewModel( + useCase, extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as Application + ) as T + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt b/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt index bc56fc0..c14e160 100644 --- a/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt +++ b/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt @@ -23,6 +23,7 @@ class LoginFragment : Fragment(R.layout.fragment_login) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { _binding = FragmentLoginBinding.bind(view) + val textWatcher = object : TextWatcher{ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { @@ -37,9 +38,11 @@ class LoginFragment : Fragment(R.layout.fragment_login) { binding.loginBtn.isEnabled = isEnabled if(isEnabled){ binding.loginBtn.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.accent_color)) + binding.loginBtn.setTextColor(ContextCompat.getColor(requireContext(), R.color.white)) } else{ binding.loginBtn.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.bg_color)) + binding.loginBtn.setTextColor(ContextCompat.getColor(requireContext(), R.color.secondary_text_color)) } } diff --git a/app/src/main/java/ru/myitschool/work/ui/main/MainFragment.kt b/app/src/main/java/ru/myitschool/work/ui/main/MainFragment.kt index b18895f..1362c4f 100644 --- a/app/src/main/java/ru/myitschool/work/ui/main/MainFragment.kt +++ b/app/src/main/java/ru/myitschool/work/ui/main/MainFragment.kt @@ -1,7 +1,10 @@ package ru.myitschool.work.ui.main +import android.content.res.ColorStateList import android.os.Bundle import android.view.View + +import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import androidx.fragment.app.Fragment import androidx.fragment.app.setFragmentResultListener @@ -9,6 +12,7 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.paging.LoadState +import androidx.paging.map import androidx.recyclerview.widget.LinearLayoutManager import com.squareup.picasso.Picasso import kotlinx.coroutines.delay @@ -36,7 +40,7 @@ class MainFragment : Fragment(R.layout.fragment_main) { viewModel.getUserData() viewModel.getLastEntryDate() - + findNavController().navigate(R.id.admin) binding.logout.setOnClickListener { logout() } binding.scan.setOnClickListener { onScanClick() } viewModel.userState.collectWhenStarted(this) { state -> @@ -125,15 +129,27 @@ class MainFragment : Fragment(R.layout.fragment_main) { } private fun showUserData(employeeEntity: EmployeeEntity) { - binding.apply { - fullname.text = employeeEntity.name - println(employeeEntity.name) - position.text = employeeEntity.position - Picasso.get().load(employeeEntity.photoUrl).into(photo) - error.visibility = View.GONE - setViewsVisibility(View.VISIBLE) + binding.fullname.text = employeeEntity.name + println(employeeEntity.name) + binding.position.text = employeeEntity.position + Picasso.get().load(employeeEntity.photoUrl).into(binding.photo) + + binding.error.visibility = View.GONE + setViewsVisibility(View.VISIBLE) + + binding.scan.isEnabled = employeeEntity.qrEnabled + when(employeeEntity.qrEnabled){ + true -> { + binding.scan.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.accent_color)) + binding.scan.setTextColor(ContextCompat.getColor(requireContext(), R.color.white)) + } + false -> { + binding.scan.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.bg_color)) + binding.scan.setTextColor(ContextCompat.getColor(requireContext(), R.color.secondary_text_color)) + } } + } private fun showError() { @@ -147,6 +163,8 @@ class MainFragment : Fragment(R.layout.fragment_main) { binding.photo.visibility = visibility binding.logout.visibility = visibility binding.scan.visibility = visibility + binding.blockMain.visibility = visibility + binding.blockHistory.visibility = visibility } diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index c09caf1..60bde23 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -12,6 +12,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index d35c830..7be807f 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -18,4 +18,8 @@ Visit history Incorrect login or password Enter the employee\'s username + Unauthorized + Forbidden \n + How did you get here? + 404 Not Found \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a2ec4ef..1f74782 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -20,6 +20,10 @@ История посещений Неправильное имя пользователя или пароль Введите логин сотрудника + Вы не авторизованы + Не достаточно прав \n + Как ты сюда попал? + 404 Не найдено