feat: Информация о пользователе + фиксы багов

This commit is contained in:
yastruckov 2025-02-20 14:59:33 +03:00
parent eb4a59e4fe
commit 8cd87144b8
13 changed files with 117 additions and 23 deletions

View File

@ -39,15 +39,6 @@ class EmployeeInfoNetworkDataSource(
HttpStatusCode.NotFound -> error(context.getString(R.string.not_found)) HttpStatusCode.NotFound -> error(context.getString(R.string.not_found))
HttpStatusCode.OK -> result.body() 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()) println(result.bodyAsText())
result.body() result.body()
} }

View File

@ -18,11 +18,11 @@ class ScannerStateNetworkDataSource(
) { ) {
private val client = NetworkModule.httpClient private val client = NetworkModule.httpClient
private val userDataStoreManager = UserDataStoreManager.getInstance(context) private val userDataStoreManager = UserDataStoreManager.getInstance(context)
suspend fun setScannerState(state: String, login: String):Result<Unit> = withContext(Dispatchers.IO){ suspend fun setScannerState(login: String):Result<Unit> = withContext(Dispatchers.IO){
runCatching { runCatching {
val username = userDataStoreManager.usernameFlow.first() val username = userDataStoreManager.usernameFlow.first()
val password = userDataStoreManager.passwordFlow.first() val password = userDataStoreManager.passwordFlow.first()
val result = client.patch("${Constants.SERVER_ADDRESS}/api/employee/$login/$state"){ val result = client.patch("${Constants.SERVER_ADDRESS}/api/employee/$login/change_state"){
headers{ headers{
basicAuth(username, password) basicAuth(username, password)
} }

View File

@ -5,7 +5,7 @@ import ru.myitschool.work.domain.scannerBlockState.ScannerStateRepo
class ScannerStateRepoImpl( class ScannerStateRepoImpl(
private val networkDataSource: ScannerStateNetworkDataSource private val networkDataSource: ScannerStateNetworkDataSource
) : ScannerStateRepo { ) : ScannerStateRepo {
override suspend fun changeState(state: String, login: String) : Result<Unit> { override suspend fun changeState(login: String) : Result<Unit> {
return networkDataSource.setScannerState(state, login) return networkDataSource.setScannerState(login)
} }
} }

View File

@ -1,5 +1,5 @@
package ru.myitschool.work.domain.scannerBlockState package ru.myitschool.work.domain.scannerBlockState
interface ScannerStateRepo { interface ScannerStateRepo {
suspend fun changeState(state: String, login: String) : Result<Unit> suspend fun changeState(login: String) : Result<Unit>
} }

View File

@ -3,5 +3,5 @@ package ru.myitschool.work.domain.scannerBlockState
class SetScannerStateUseCase( class SetScannerStateUseCase(
private val repo: ScannerStateRepo private val repo: ScannerStateRepo
) { ) {
suspend operator fun invoke(state: String, login: String) = repo.changeState(state, login) suspend operator fun invoke(login: String) = repo.changeState(login)
} }

View File

@ -8,11 +8,15 @@ import android.view.View
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.squareup.picasso.Picasso
import ru.myitschool.work.R import ru.myitschool.work.R
import ru.myitschool.work.databinding.FragmentAdminBinding import ru.myitschool.work.databinding.FragmentAdminBinding
import ru.myitschool.work.entities.EmployeeEntity import ru.myitschool.work.entities.EmployeeEntity
import ru.myitschool.work.ui.login.LoginViewModel
import ru.myitschool.work.utils.buttonRecolor
import ru.myitschool.work.utils.collectWithLifecycle import ru.myitschool.work.utils.collectWithLifecycle
class AdminFragment : Fragment(R.layout.fragment_admin) { class AdminFragment : Fragment(R.layout.fragment_admin) {
private var _binding: FragmentAdminBinding? = null private var _binding: FragmentAdminBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
@ -37,6 +41,7 @@ class AdminFragment : Fragment(R.layout.fragment_admin) {
val isEnabled = val isEnabled =
search.length >= 3 && !search[0].isDigit() && search.matches(Regex("^[a-zA-Z0-9]*$")) search.length >= 3 && !search[0].isDigit() && search.matches(Regex("^[a-zA-Z0-9]*$"))
binding.searchBtn.isEnabled = isEnabled binding.searchBtn.isEnabled = isEnabled
@Suppress("DEPRECATION")
if (isEnabled) { if (isEnabled) {
binding.searchBtn.setBackgroundColor(resources.getColor(R.color.accent_color)) binding.searchBtn.setBackgroundColor(resources.getColor(R.color.accent_color))
binding.searchBtn.imageTintList = ColorStateList.valueOf( binding.searchBtn.imageTintList = ColorStateList.valueOf(
@ -52,31 +57,63 @@ class AdminFragment : Fragment(R.layout.fragment_admin) {
R.color.secondary_text_color R.color.secondary_text_color
)) ))
} }
} }
} }
binding.search.addTextChangedListener(textWatcher) binding.search.addTextChangedListener(textWatcher)
viewModel.infoState.collectWithLifecycle(this){ state -> viewModel.infoState.collectWithLifecycle(this){ state ->
when(state){ when(state){
is AdminViewModel.SearchState.Error -> { is AdminViewModel.SearchState.Error -> {
binding.error.visibility = View.VISIBLE binding.error.visibility = View.VISIBLE
binding.error.text = state.message binding.error.text = state.message
binding.userInfo.visibility = View.GONE
} }
AdminViewModel.SearchState.Loading -> { AdminViewModel.SearchState.Loading -> {
binding.error.visibility = View.GONE binding.error.visibility = View.GONE
binding binding.userInfo.visibility = View.GONE
} }
is AdminViewModel.SearchState.Success -> { is AdminViewModel.SearchState.Success -> {
binding.error.visibility = View.GONE binding.error.visibility = View.GONE
binding.userInfo.visibility = View.VISIBLE
showUserData(state.data) showUserData(state.data)
} }
} }
} }
binding.blockBtn.setOnClickListener {
viewModel.changeState(binding.search.text.toString())
}
viewModel.blockState.collectWithLifecycle(this){ state ->
when(state){
is AdminViewModel.BlockState.Error -> {
binding.error.visibility = View.VISIBLE
binding.error.text = state.message
}
AdminViewModel.BlockState.Loading -> {
binding.error.visibility = View.GONE
binding.userInfo.visibility = View.GONE
}
AdminViewModel.BlockState.Success -> {
binding.error.visibility = View.GONE
binding.userInfo.visibility = View.VISIBLE
}
}
}
} }
private fun showUserData(user: EmployeeEntity){ private fun showUserData(user: EmployeeEntity){
binding.userName.text = user.name binding.userName.text = user.name
binding.position.text = user.position binding.position.text = user.position
if(user.qrEnabled){
binding.blockBtn.text = ContextCompat.getString(requireContext(), R.string.block_btn)
buttonRecolor(requireContext(), binding.blockBtn, R.color.accent_color, R.color.white )
}
else{
binding.blockBtn.text = ContextCompat.getString(requireContext(), R.string.unblock_btn)
buttonRecolor(requireContext(), binding.blockBtn, R.color.bg_color, R.color.secondary_text_color )
}
Picasso.get().load(user.photoUrl).into(binding.avatar)
} }
} }

View File

@ -12,22 +12,34 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import ru.myitschool.work.data.profile.admin.EmployeeInfoNetworkDataSource import ru.myitschool.work.data.profile.admin.EmployeeInfoNetworkDataSource
import ru.myitschool.work.data.profile.admin.EmployeeInfoRepoImpl import ru.myitschool.work.data.profile.admin.EmployeeInfoRepoImpl
import ru.myitschool.work.data.scannerState.ScannerStateNetworkDataSource
import ru.myitschool.work.data.scannerState.ScannerStateRepoImpl
import ru.myitschool.work.domain.profile.admin.EmployeeInfoRepo import ru.myitschool.work.domain.profile.admin.EmployeeInfoRepo
import ru.myitschool.work.domain.profile.admin.GetEmployeeInfoUseCase import ru.myitschool.work.domain.profile.admin.GetEmployeeInfoUseCase
import ru.myitschool.work.domain.scannerBlockState.SetScannerStateUseCase
import ru.myitschool.work.entities.EmployeeEntity import ru.myitschool.work.entities.EmployeeEntity
class AdminViewModel( class AdminViewModel(
private val getInfoUseCase: GetEmployeeInfoUseCase, private val getInfoUseCase: GetEmployeeInfoUseCase,
private val setScannerStateUseCase : SetScannerStateUseCase,
application: Application application: Application
) : AndroidViewModel(application) { ) : AndroidViewModel(application) {
private val _infoState = MutableStateFlow<SearchState>(SearchState.Loading) private val _infoState = MutableStateFlow<SearchState>(SearchState.Loading)
val infoState: StateFlow<SearchState> = _infoState.asStateFlow() val infoState: StateFlow<SearchState> = _infoState.asStateFlow()
private val _blockState = MutableStateFlow<BlockState>(BlockState.Loading)
val blockState: StateFlow<BlockState> = _blockState.asStateFlow()
sealed class SearchState { sealed class SearchState {
data object Loading : SearchState() data object Loading : SearchState()
data class Success(val data: EmployeeEntity) : SearchState() data class Success(val data: EmployeeEntity) : SearchState()
data class Error(val message: String?) : SearchState() data class Error(val message: String?) : SearchState()
} }
sealed class BlockState {
data object Loading : BlockState()
data object Success : BlockState()
data class Error(val message: String?) : BlockState()
}
fun searchUser(login : String){ fun searchUser(login : String){
_infoState.value = SearchState.Loading _infoState.value = SearchState.Loading
viewModelScope.launch { viewModelScope.launch {
@ -41,6 +53,20 @@ class AdminViewModel(
) )
} }
} }
fun changeState(login: String){
viewModelScope.launch {
_blockState.value = BlockState.Loading
setScannerStateUseCase.invoke(login).fold(
onSuccess = { _ ->
_blockState.value = BlockState.Success
},
onFailure = { e ->
_blockState.value = BlockState.Error(e.message)
}
)
}
searchUser(login)
}
companion object { companion object {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory { val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
@ -50,10 +76,16 @@ class AdminViewModel(
context = extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as Application context = extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as Application
) )
) )
val useCase = GetEmployeeInfoUseCase(infoRepoImpl) val setScannerStateRepoImpl = ScannerStateRepoImpl(
networkDataSource = ScannerStateNetworkDataSource(
context = extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as Application
)
)
val getInfoUseCase = GetEmployeeInfoUseCase(infoRepoImpl)
val setScannerStateUseCase = SetScannerStateUseCase(setScannerStateRepoImpl)
return AdminViewModel( return AdminViewModel(
useCase, extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as Application getInfoUseCase, setScannerStateUseCase, extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as Application
) as T ) as T
} }
} }

View File

@ -39,9 +39,9 @@ class MainFragment : Fragment(R.layout.fragment_main) {
viewModel.getUserData() viewModel.getUserData()
viewModel.getLastEntryDate() viewModel.getLastEntryDate()
findNavController().navigate(R.id.admin)
binding.logout.setOnClickListener { logout() } binding.logout.setOnClickListener { logout() }
binding.scan.setOnClickListener { onScanClick() } binding.scan.setOnClickListener { onScanClick() }
binding.admin.setOnClickListener { findNavController().navigate(R.id.admin) }
viewModel.userState.collectWhenStarted(this) { state -> viewModel.userState.collectWhenStarted(this) { state ->
when (state) { when (state) {
is UserState.Error -> { is UserState.Error -> {

View File

@ -0,0 +1,21 @@
package ru.myitschool.work.utils
import android.content.Context
import android.content.res.ColorStateList
import android.widget.Button
import androidx.core.content.ContextCompat
import ru.myitschool.work.R
fun buttonRecolor(
context: Context,
btn: Button,
bgColor: Int,
textColor: Int
) {
btn.backgroundTintList = ColorStateList.valueOf(
ContextCompat.getColor(
context,
bgColor
))
btn.setTextColor(context.getColor(textColor))
}

View File

@ -78,6 +78,7 @@
app:layout_constraintVertical_bias="0.0"> app:layout_constraintVertical_bias="0.0">
<LinearLayout <LinearLayout
android:id="@+id/userInfo"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="24dp" android:layout_marginBottom="24dp"

View File

@ -5,7 +5,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/bg_color"> android:background="@color/bg_color">
<androidx.constraintlayout.widget.Guideline <androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_left" android:id="@+id/guideline_left"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -18,7 +18,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
app:layout_constraintGuide_end="16dp"/> app:layout_constraintGuide_end="16dp"/>
<LinearLayout <LinearLayout
android:id="@+id/linear" android:id="@+id/linear"
android:layout_width="0dp" android:layout_width="0dp"
@ -150,6 +150,12 @@
app:layout_constraintLeft_toRightOf="@+id/guideline_left" app:layout_constraintLeft_toRightOf="@+id/guideline_left"
app:layout_constraintRight_toLeftOf="@+id/guideline_right"> app:layout_constraintRight_toLeftOf="@+id/guideline_right">
<Button
android:id="@+id/admin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Admin" />
<TextView <TextView
android:id="@+id/position" android:id="@+id/position"
style="@style/font_medium" style="@style/font_medium"

View File

@ -20,4 +20,7 @@
<string name="search_hint">Enter the employee\'s username</string> <string name="search_hint">Enter the employee\'s username</string>
<string name="block_btn">Block</string> <string name="block_btn">Block</string>
<string name="unblock_btn">Unblock</string> <string name="unblock_btn">Unblock</string>
<string name="admin_unauthorized">Вы не авторизованы</string>
<string name="admin_forbidden">Нет доступа</string>
<string name="not_found">404\nEmployee not found</string>
</resources> </resources>

View File

@ -22,6 +22,9 @@
<string name="search_hint">Введите логин сотрудника</string> <string name="search_hint">Введите логин сотрудника</string>
<string name="block_btn">Заблокировать</string> <string name="block_btn">Заблокировать</string>
<string name="unblock_btn">Разблокировать</string> <string name="unblock_btn">Разблокировать</string>
<string name="admin_unauthorized">Вы не авторизованы</string>
<string name="admin_forbidden">Нет доступа</string>
<string name="not_found">404\nСотрудник не найден</string>
<!-- TODO: Remove or change this placeholder text --> <!-- TODO: Remove or change this placeholder text -->