feat: admin section done

This commit is contained in:
a1pha 2025-02-19 18:53:46 +03:00
parent f76d744fd7
commit 9393cb89b0
10 changed files with 359 additions and 1 deletions

View File

@ -0,0 +1,14 @@
package ru.myitschool.work.data
import ru.myitschool.work.data.local.CredentialsLocalDataSource
import ru.myitschool.work.data.network.AdminNetworkDataSource
import ru.myitschool.work.domain.admin.AdminRepository
class AdminRepositoryImpl(
private val networkDataSource: AdminNetworkDataSource,
private val localCredentialsLocalDataSource: CredentialsLocalDataSource
): AdminRepository {
override suspend fun blockUser(login: String): Result<Unit> {
return networkDataSource.blockUser(login, localCredentialsLocalDataSource.getToken())
}
}

View File

@ -0,0 +1,30 @@
package ru.myitschool.work.data.network
import io.ktor.client.request.headers
import io.ktor.client.request.patch
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import ru.myitschool.work.core.Constants.SERVER_ADDRESS
object AdminNetworkDataSource {
private val client = KtorClient.client
suspend fun blockUser(login: String, token: String): Result<Unit> =
withContext(Dispatchers.IO) {
runCatching {
val response = client.patch("$SERVER_ADDRESS/api/employees/block&login=${login}") {
headers {
append(HttpHeaders.Authorization, token)
}
}
if (response.status != HttpStatusCode.OK)
error("Status ${response.status}")
Unit
}
}
}

View File

@ -0,0 +1,5 @@
package ru.myitschool.work.domain.admin
interface AdminRepository {
suspend fun blockUser(login: String): Result<Unit>
}

View File

@ -0,0 +1,8 @@
package ru.myitschool.work.domain.admin
class BlockUserUseCase(
private val repository: AdminRepository
) {
suspend operator fun invoke(login: String) = repository.blockUser(login)
}

View File

@ -0,0 +1,9 @@
package ru.myitschool.work.domain.passes
class GetUsersPassesUseCase(
private val repository: PassRepository
) {
suspend operator fun invoke(pageNum: Int, pageSize: Int, login: String) =
repository.getUsersPasses(pageNum, pageSize, login)
}

View File

@ -4,13 +4,18 @@ import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import dagger.hilt.android.AndroidEntryPoint
import ru.myitschool.work.R
import ru.myitschool.work.utils.isOnline
// НЕ ИЗМЕНЯЙТЕ НАЗВАНИЕ КЛАССА!
@AndroidEntryPoint
class RootActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_root)
if (!isOnline(this)) {
val dialog = NoInternetNotificationFragment()
dialog.isCancelable = false
dialog.show(supportFragmentManager, "NO_INTERNET")
}
}
}

View File

@ -0,0 +1,41 @@
package ru.myitschool.work.ui.admin.search
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import ru.myitschool.work.R
import ru.myitschool.work.databinding.FragmentFindEmployeeBinding
import ru.myitschool.work.utils.collectWithLifecycle
class AdminFragment : Fragment(R.layout.fragment_find_employee) {
private var _binding: FragmentFindEmployeeBinding? = null
private val binding: FragmentFindEmployeeBinding get() = _binding!!
private val viewModel by viewModels<AdminViewModel> { AdminViewModel.Factory }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
_binding = FragmentFindEmployeeBinding.bind(view)
viewModel.state.collectWithLifecycle(this) { state ->
binding.username.isEnabled = state !is AdminViewModel.State.Loading
when (state) {
is AdminViewModel.State.Error -> binding.error.text = state.errorMessage
is AdminViewModel.State.Loading -> Unit
is AdminViewModel.State.Show -> {}
is AdminViewModel.State.Waiting -> Unit
}
}
binding.find.setOnClickListener {
viewModel.onFind(binding.username.text.toString())
}
}
override fun onDestroy() {
_binding = null
super.onDestroy()
}
}

View File

@ -0,0 +1,127 @@
package ru.myitschool.work.ui.admin.search
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import ru.myitschool.work.data.AdminRepositoryImpl
import ru.myitschool.work.data.PassRepositoryImpl
import ru.myitschool.work.data.UserRepositoryImpl
import ru.myitschool.work.data.local.CredentialsLocalDataSource
import ru.myitschool.work.data.local.UserLocalDataSource
import ru.myitschool.work.data.network.AdminNetworkDataSource
import ru.myitschool.work.data.network.PassNetworkDataSource
import ru.myitschool.work.data.network.UserNetworkDataSource
import ru.myitschool.work.domain.admin.BlockUserUseCase
import ru.myitschool.work.domain.entities.PassEntity
import ru.myitschool.work.domain.entities.UserEntity
import ru.myitschool.work.domain.passes.GetUsersPassesUseCase
import ru.myitschool.work.domain.user.GetUserByLoginUseCase
import ru.myitschool.work.ui.admin.view.UsersPassesPagingSource
class AdminViewModel(
private val getUserByLoginUseCase: GetUserByLoginUseCase,
private val getUsersPassesUseCase: GetUsersPassesUseCase,
private val blockUserUseCase: BlockUserUseCase
) : ViewModel() {
private val _state = MutableStateFlow<State>(State.Waiting)
val state = _state.asStateFlow()
private var _listState: Flow<PagingData<PassEntity>>? = null
val listState: Flow<PagingData<PassEntity>> get() = _listState!!
private var currentLogin: String? = null
fun onFind(login: String) {
viewModelScope.launch {
_state.emit(State.Loading)
getUserByLoginUseCase(login).fold(
onSuccess = { data ->
currentLogin = login
_state.emit(State.Show(data))
setUpPager(login)
},
onFailure = { _state.emit(State.Error(it.message.toString())) }
)
}
}
fun onRefresh() {
updateState()
}
private fun updateState() {
viewModelScope.launch {
_state.emit(State.Loading)
getUserByLoginUseCase(currentLogin!!).fold(
onSuccess = { _state.emit(State.Show(it)) },
onFailure = { _state.emit(State.Error(it.message.toString())) }
)
}
}
private fun setUpPager(login: String) {
_listState = Pager(
config = PagingConfig(
pageSize = 10,
enablePlaceholders = false,
maxSize = 50
)
) {
UsersPassesPagingSource(getUsersPassesUseCase::invoke, login)
}.flow
.cachedIn(viewModelScope)
}
fun onBlock() {
viewModelScope.launch {
blockUserUseCase(currentLogin!!)
}
}
sealed interface State {
data class Show(val user: UserEntity) : State
data object Waiting : State
data object Loading : State
data class Error(val errorMessage: String) : State
}
companion object {
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
return AdminViewModel(
getUserByLoginUseCase = GetUserByLoginUseCase(
repository = UserRepositoryImpl(
credentialsLocalDataSource = CredentialsLocalDataSource.getInstance(),
userLocalDataSource = UserLocalDataSource,
networkDataSource = UserNetworkDataSource
)
),
getUsersPassesUseCase = GetUsersPassesUseCase(
repository = PassRepositoryImpl(
networkDataSource = PassNetworkDataSource,
credentialsLocalDataSource = CredentialsLocalDataSource.getInstance()
)
),
blockUserUseCase = BlockUserUseCase(
repository = AdminRepositoryImpl(
networkDataSource = AdminNetworkDataSource,
localCredentialsLocalDataSource = CredentialsLocalDataSource.getInstance()
)
)
) as T
}
}
}
}

View File

@ -0,0 +1,31 @@
package ru.myitschool.work.ui.admin.view
import androidx.paging.PagingSource
import androidx.paging.PagingState
import ru.myitschool.work.domain.entities.PassEntity
class UsersPassesPagingSource(
private val request: suspend (pageNum: Int, pageSize: Int, login: String) -> Result<List<PassEntity>>,
private val login: String
) : PagingSource<Int, PassEntity>() {
override fun getRefreshKey(state: PagingState<Int, PassEntity>): Int? {
return state.anchorPosition?.let {
state.closestPageToPosition(it)?.prevKey?.plus(1)
?: state.closestPageToPosition(it)?.nextKey?.minus(1)
}
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, PassEntity> {
val pageNum = params.key ?: 0
return request.invoke(pageNum, params.loadSize, login).fold(
onSuccess = { value ->
LoadResult.Page(
data = value,
prevKey = (pageNum - 1).takeIf { it >= 0 },
nextKey = (pageNum + 1).takeIf { value.size == params.loadSize }
)
},
onFailure = { error -> LoadResult.Error(error) }
)
}
}

View File

@ -0,0 +1,88 @@
package ru.myitschool.work.ui.admin.view
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.paging.LoadState
import com.squareup.picasso.Picasso
import ru.myitschool.work.R
import ru.myitschool.work.databinding.FragmentUserBinding
import ru.myitschool.work.ui.admin.search.AdminViewModel
import ru.myitschool.work.ui.profile.PassesListAdapter
import ru.myitschool.work.utils.collectWithLifecycle
import ru.myitschool.work.utils.visibleOrGone
class ViewUserAsAdminFragment : Fragment(R.layout.fragment_user) {
private var _binding: FragmentUserBinding? = null
private val binding: FragmentUserBinding get() = _binding!!
private val viewModel by viewModels<AdminViewModel> { AdminViewModel.Factory }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
_binding = FragmentUserBinding.bind(view)
binding.findUser.visibleOrGone(false)
binding.logout.visibleOrGone(false)
binding.block.visibleOrGone(true)
val adapter = PassesListAdapter()
binding.passes.adapter = adapter
viewModel.state.collectWithLifecycle(this) { state ->
binding.refresh.isRefreshing = state is AdminViewModel.State.Loading
binding.content?.visibleOrGone(state is AdminViewModel.State.Show)
when (state) {
is AdminViewModel.State.Loading -> Unit
is AdminViewModel.State.Show -> {
val user = state.user
binding.findUser.visibleOrGone(user.isAdmin)
binding.fullname.text = user.name
binding.position.text = user.position
binding.lastEntry.text = user.lastVisit
Picasso.get().load(user.photoUrl).into(binding.photo)
}
is AdminViewModel.State.Error -> binding.error.text = state.errorMessage
is AdminViewModel.State.Waiting -> Unit
}
viewModel.listState.collectWithLifecycle(this) { listState ->
adapter.submitData(listState)
}
adapter.loadStateFlow.collectWithLifecycle(this) { data ->
val dataState = data.refresh
binding.refresh.isRefreshing = dataState is LoadState.Loading
binding.error.visibleOrGone(dataState is LoadState.Error)
if (dataState is LoadState.Error) {
binding.error.text = dataState.error.toString()
}
}
binding.refresh.setOnRefreshListener {
viewModel.onRefresh()
adapter.refresh()
}
}
binding.block.setOnClickListener {
AlertDialog.Builder(requireContext())
.setTitle("Блокировка доступа")
.setMessage("Вы уверены, что хотите заблокировать доступ работнику: ${binding.fullname}?")
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.onBlock() }
.show()
}
}
override fun onDestroy() {
_binding = null
super.onDestroy()
}
}