feat: Получение данных пользователя по логину + фиксы багов

This commit is contained in:
yastruckov 2025-02-20 12:32:31 +03:00
parent 0446bddcc9
commit 041db2955f
13 changed files with 221 additions and 80 deletions

View File

@ -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)
}
}
}
}

View File

@ -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()

View File

@ -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<EmployeeDTO> = 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()
}

View File

@ -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<EmployeeEntity> {
return networkDataSource.getProfile(login).map { dto ->
EmployeeEntity(

View File

@ -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<EmployeeEntity>
}

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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>(SearchState.Loading)
val infoState: StateFlow<SearchState> = _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 <T : ViewModel> create(modelClass: Class<T>, 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
}
}
}
}

View File

@ -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))
}
}

View File

@ -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
}

View File

@ -12,6 +12,9 @@
<action
android:id="@+id/action_loginFragment_to_mainFragment"
app:destination="@id/mainFragment" />
<action
android:id="@+id/action_loginFragment_to_admin"
app:destination="@id/admin" />
</fragment>
<fragment
android:id="@+id/mainFragment"
@ -21,6 +24,9 @@
<action
android:id="@+id/action_mainFragment_to_qrScanFragment"
app:destination="@id/qrScanFragment" />
<action
android:id="@+id/action_mainFragment_to_admin"
app:destination="@id/admin" />
</fragment>
<fragment
android:id="@+id/qrScanFragment"
@ -40,4 +46,9 @@
android:id="@+id/action_qrResultFragment_to_mainFragment"
app:destination="@id/mainFragment" />
</fragment>
<fragment
android:id="@+id/admin"
android:name="ru.myitschool.work.ui.admin.AdminFragment"
android:label="fragment_admin"
tools:layout="@layout/fragment_admin" />
</navigation>

View File

@ -18,4 +18,8 @@
<string name="history_title">Visit history</string>
<string name="login_unauthorized">Incorrect login or password</string>
<string name="search_hint">Enter the employee\'s username</string>
<string name="admin_unauthorized">Unauthorized</string>
<string name="admin_forbidden">Forbidden \n
How did you get here?</string>
<string name="not_found"> 404 Not Found</string>
</resources>

View File

@ -20,6 +20,10 @@
<string name="history_title">История посещений</string>
<string name="login_unauthorized">Неправильное имя пользователя или пароль</string>
<string name="search_hint">Введите логин сотрудника</string>
<string name="admin_unauthorized">Вы не авторизованы</string>
<string name="admin_forbidden">Не достаточно прав \n
Как ты сюда попал?</string>
<string name="not_found">404 Не найдено</string>
<!-- TODO: Remove or change this placeholder text -->