Compare commits

..

No commits in common. "master" and "v1.0" have entirely different histories.
master ... v1.0

16 changed files with 89 additions and 180 deletions

View File

@ -71,5 +71,3 @@
- Если сервер ответил любой ошибкой - то отображаем текст: - Если сервер ответил любой ошибкой - то отображаем текст:
*"Что-то пошло не так/Something wrong"* *"Что-то пошло не так/Something wrong"*
- Кнопка закрытия всегда открывает главный экран. - Кнопка закрытия всегда открывает главный экран.
### Ссылка на макеты в фигме: https://www.figma.com/design/MoPjxN7VnWnor3Mq0CNB8s/%D0%9D%D0%A2%D0%9E?node-id=49-193&t=FTPnWNwArVdAGM33-1

View File

@ -1,6 +1,5 @@
package ru.myitschool.work.data package ru.myitschool.work.data
import android.util.Log
import ru.myitschool.work.data.dto.PassDto import ru.myitschool.work.data.dto.PassDto
import ru.myitschool.work.data.local.CredentialsLocalDataSource import ru.myitschool.work.data.local.CredentialsLocalDataSource
import ru.myitschool.work.data.network.PassNetworkDataSource import ru.myitschool.work.data.network.PassNetworkDataSource

View File

@ -77,7 +77,7 @@ class UserRepositoryImpl(
override suspend fun getUserByLogin(login: String): Result<UserEntity> { override suspend fun getUserByLogin(login: String): Result<UserEntity> {
return networkDataSource.getUserByLogin(login, credentialsLocalDataSource.getToken()).fold( return networkDataSource.getUserByLogin(login, credentialsLocalDataSource.getToken()).fold(
onSuccess = { map(it) }, onFailure = { Result.failure(it) } onSuccess = { map(it) }, onFailure = { error(it) }
) )
} }
} }

View File

@ -6,9 +6,9 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class PassDto( data class PassDto(
@SerialName("localDateTime") @SerialName("time")
val time: String?, val time: String?,
@SerialName("terminal") @SerialName("localDateTime")
val terminal: TerminalDto? val terminal: TerminalDto?
) )

View File

@ -15,7 +15,7 @@ object AdminNetworkDataSource {
suspend fun blockUser(login: String, token: String): Result<Unit> = suspend fun blockUser(login: String, token: String): Result<Unit> =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
runCatching { runCatching {
val response = client.patch("$SERVER_ADDRESS/api/users/block?username=$login") { val response = client.patch("$SERVER_ADDRESS/api/employees/block?login=$login") {
headers { headers {
append(HttpHeaders.Authorization, token) append(HttpHeaders.Authorization, token)
} }
@ -30,7 +30,7 @@ object AdminNetworkDataSource {
suspend fun unblockUser(login: String, token: String): Result<Unit> = suspend fun unblockUser(login: String, token: String): Result<Unit> =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
runCatching { runCatching {
val response = client.patch("$SERVER_ADDRESS/api/users/unblock?username=$login") { val response = client.patch("$SERVER_ADDRESS/api/employees/unblock?login=$login") {
headers { headers {
append(HttpHeaders.Authorization, token) append(HttpHeaders.Authorization, token)
} }

View File

@ -52,13 +52,13 @@ object UserNetworkDataSource {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
runCatching { runCatching {
val response = val response =
KtorClient.client.get("${Constants.SERVER_ADDRESS}/api/users/get?username=${login}") { KtorClient.client.get("${Constants.SERVER_ADDRESS}/api/employees/find?${login}") {
headers { headers {
append(HttpHeaders.Authorization, token) append(HttpHeaders.Authorization, token)
} }
} }
if (response.status != HttpStatusCode.OK) if (response.status == HttpStatusCode.OK)
error("Status ${response.status}") error("Status ${response.status}")
response.body() response.body()
} }

View File

@ -4,7 +4,6 @@ import android.os.Bundle
import android.view.View import android.view.View
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import ru.myitschool.work.R import ru.myitschool.work.R
import ru.myitschool.work.databinding.FragmentFindEmployeeBinding import ru.myitschool.work.databinding.FragmentFindEmployeeBinding
import ru.myitschool.work.utils.collectWithLifecycle import ru.myitschool.work.utils.collectWithLifecycle
@ -24,7 +23,7 @@ class AdminFragment : Fragment(R.layout.fragment_find_employee) {
when (state) { when (state) {
is AdminViewModel.State.Error -> binding.error.text = state.errorMessage is AdminViewModel.State.Error -> binding.error.text = state.errorMessage
is AdminViewModel.State.Loading -> Unit is AdminViewModel.State.Loading -> Unit
is AdminViewModel.State.Show -> findNavController().navigate(R.id.action_adminFragment_to_viewUserAsAdminFragment) is AdminViewModel.State.Show -> {}
is AdminViewModel.State.Waiting -> Unit is AdminViewModel.State.Waiting -> Unit
} }
} }
@ -32,6 +31,7 @@ class AdminFragment : Fragment(R.layout.fragment_find_employee) {
binding.find.setOnClickListener { binding.find.setOnClickListener {
viewModel.onFind(binding.username.text.toString()) viewModel.onFind(binding.username.text.toString())
} }
} }
override fun onDestroy() { override fun onDestroy() {

View File

@ -1,14 +1,14 @@
package ru.myitschool.work.ui.admin.search package ru.myitschool.work.ui.admin.search
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.CreationExtras import androidx.lifecycle.viewmodel.CreationExtras
import androidx.paging.Pager import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn import androidx.paging.cachedIn
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -38,18 +38,17 @@ class AdminViewModel(
private val _state = MutableStateFlow<State>(State.Waiting) private val _state = MutableStateFlow<State>(State.Waiting)
val state = _state.asStateFlow() val state = _state.asStateFlow()
private val _listState = MutableLiveData<Pager<Int, PassEntity>>() private var _listState: Flow<PagingData<PassEntity>>? = null
val listState = _listState.value?.flow?.cachedIn(viewModelScope) val listState: Flow<PagingData<PassEntity>> get() = _listState!!
private val _currentLogin = MutableLiveData<String>() private var currentLogin: String? = null
private val currentLogin: LiveData<String> get() = _currentLogin
fun onFind(login: String) { fun onFind(login: String) {
viewModelScope.launch { viewModelScope.launch {
_state.emit(State.Loading) _state.emit(State.Loading)
getUserByLoginUseCase(login).fold( getUserByLoginUseCase(login).fold(
onSuccess = { data -> onSuccess = { data ->
_currentLogin.postValue(login) currentLogin = login
_state.emit(State.Show(data)) _state.emit(State.Show(data))
setUpPager(login) setUpPager(login)
}, },
@ -65,7 +64,7 @@ class AdminViewModel(
private fun updateState() { private fun updateState() {
viewModelScope.launch { viewModelScope.launch {
_state.emit(State.Loading) _state.emit(State.Loading)
getUserByLoginUseCase(currentLogin.value!!).fold( getUserByLoginUseCase(currentLogin!!).fold(
onSuccess = { _state.emit(State.Show(it)) }, onSuccess = { _state.emit(State.Show(it)) },
onFailure = { _state.emit(State.Error(it.message.toString())) } onFailure = { _state.emit(State.Error(it.message.toString())) }
) )
@ -73,7 +72,7 @@ class AdminViewModel(
} }
private fun setUpPager(login: String) { private fun setUpPager(login: String) {
_listState.value = Pager( _listState = Pager(
config = PagingConfig( config = PagingConfig(
pageSize = 10, pageSize = 10,
enablePlaceholders = false, enablePlaceholders = false,
@ -81,18 +80,19 @@ class AdminViewModel(
) )
) { ) {
UsersPassesPagingSource(getUsersPassesUseCase::invoke, login) UsersPassesPagingSource(getUsersPassesUseCase::invoke, login)
} }.flow
.cachedIn(viewModelScope)
} }
fun onBlock() { fun onBlock() {
viewModelScope.launch { viewModelScope.launch {
blockUserUseCase(currentLogin.value!!) blockUserUseCase(currentLogin!!)
} }
} }
fun unblock() { fun unblock() {
viewModelScope.launch { viewModelScope.launch {
unBlockUserUseCase(currentLogin.value!!) unBlockUserUseCase(currentLogin!!)
} }
} }

View File

@ -49,9 +49,8 @@ class ViewUserAsAdminFragment : Fragment(R.layout.fragment_user) {
is AdminViewModel.State.Error -> binding.error.text = state.errorMessage is AdminViewModel.State.Error -> binding.error.text = state.errorMessage
is AdminViewModel.State.Waiting -> Unit is AdminViewModel.State.Waiting -> Unit
} }
}
viewModel.listState?.collectWithLifecycle(this) { listState -> viewModel.listState.collectWithLifecycle(this) { listState ->
adapter.submitData(listState) adapter.submitData(listState)
} }
@ -69,7 +68,7 @@ class ViewUserAsAdminFragment : Fragment(R.layout.fragment_user) {
viewModel.onRefresh() viewModel.onRefresh()
adapter.refresh() adapter.refresh()
} }
}
binding.block.setOnClickListener { binding.block.setOnClickListener {
AlertDialog.Builder(requireContext()) AlertDialog.Builder(requireContext())

View File

@ -8,7 +8,7 @@ import androidx.recyclerview.widget.RecyclerView
import ru.myitschool.work.databinding.PassItemBinding import ru.myitschool.work.databinding.PassItemBinding
import ru.myitschool.work.domain.entities.PassEntity import ru.myitschool.work.domain.entities.PassEntity
class PassesListAdapter : class PassesListAdapter:
PagingDataAdapter<PassEntity, PassesListAdapter.ViewHolder>(CenterDiff) { PagingDataAdapter<PassEntity, PassesListAdapter.ViewHolder>(CenterDiff) {
class ViewHolder( class ViewHolder(
@ -40,9 +40,9 @@ class PassesListAdapter :
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind( holder.bind(
getItem(position) ?: PassEntity( getItem(position) ?: PassEntity(
type = "Loading...", type = "Загрузка ...",
name = "Loading...", name = "Терминал №...",
time = "Loading..." time = "Давным-давно..."
) )
) )
} }

View File

@ -1,6 +1,5 @@
package ru.myitschool.work.ui.profile package ru.myitschool.work.ui.profile
import android.util.Log
import androidx.paging.PagingSource import androidx.paging.PagingSource
import androidx.paging.PagingState import androidx.paging.PagingState
import ru.myitschool.work.domain.entities.PassEntity import ru.myitschool.work.domain.entities.PassEntity
@ -16,12 +15,12 @@ class PassesPagingSource(
} }
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, PassEntity> { override suspend fun load(params: LoadParams<Int>): LoadResult<Int, PassEntity> {
val pageNum = params.key ?: 0 val pageNum = params.key ?: 1
return request.invoke(pageNum, params.loadSize).fold( return request.invoke(pageNum, params.loadSize).fold(
onSuccess = { value -> onSuccess = { value ->
LoadResult.Page( LoadResult.Page(
data = value, data = value,
prevKey = (pageNum - 1).takeIf { it > 0 }, prevKey = (pageNum - 1).takeIf { it >= 0 },
nextKey = (pageNum + 1).takeIf { value.size == params.loadSize } nextKey = (pageNum + 1).takeIf { value.size == params.loadSize }
) )
}, },

View File

@ -33,6 +33,7 @@ class UserFragment : Fragment(R.layout.fragment_user) {
viewModel.state.collectWithLifecycle(this) { state -> viewModel.state.collectWithLifecycle(this) { state ->
binding.refresh.isRefreshing = state is UserViewModel.State.Loading binding.refresh.isRefreshing = state is UserViewModel.State.Loading
binding.content.visibleOrGone(state is UserViewModel.State.Show) binding.content.visibleOrGone(state is UserViewModel.State.Show)
Log.d("info", state.toString())
when (state) { when (state) {
is UserViewModel.State.Loading -> Unit is UserViewModel.State.Loading -> Unit
is UserViewModel.State.Show -> { is UserViewModel.State.Show -> {
@ -45,14 +46,12 @@ class UserFragment : Fragment(R.layout.fragment_user) {
Picasso.get().load(user.photoUrl).into(binding.photo) Picasso.get().load(user.photoUrl).into(binding.photo)
} }
} }
}
viewModel.listState.collectWithLifecycle(this) { listState -> viewModel.listState.collectWithLifecycle(this) { listState ->
adapter.submitData(listState) adapter.submitData(listState)
} }
adapter.loadStateFlow.collectWithLifecycle(this) { data -> adapter.loadStateFlow.collectWithLifecycle(this) { data ->
Log.d("info", data.refresh.toString())
val dataState = data.refresh val dataState = data.refresh
binding.refresh.isRefreshing = dataState is LoadState.Loading binding.refresh.isRefreshing = dataState is LoadState.Loading
binding.error.visibleOrGone(dataState is LoadState.Error) binding.error.visibleOrGone(dataState is LoadState.Error)
@ -80,7 +79,7 @@ class UserFragment : Fragment(R.layout.fragment_user) {
} }
binding.findUser.setOnClickListener { binding.findUser.setOnClickListener {
findNavController().navigate(R.id.action_userFragment_to_adminFragment) findNavController().navigate(R.id.find_user)
} }
binding.scan.setOnClickListener { binding.scan.setOnClickListener {
@ -93,7 +92,7 @@ class UserFragment : Fragment(R.layout.fragment_user) {
parentFragmentManager.setFragmentResult(RESPONSE_KEY, result) parentFragmentManager.setFragmentResult(RESPONSE_KEY, result)
findNavController().navigate(R.id.action_userFragment_to_qrResultFragment) findNavController().navigate(R.id.action_userFragment_to_qrResultFragment)
} }
}
} }
override fun onDestroy() { override fun onDestroy() {

View File

@ -1,6 +1,5 @@
package ru.myitschool.work.ui.profile package ru.myitschool.work.ui.profile
import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@ -17,7 +16,6 @@ import ru.myitschool.work.data.local.CredentialsLocalDataSource
import ru.myitschool.work.data.local.UserLocalDataSource import ru.myitschool.work.data.local.UserLocalDataSource
import ru.myitschool.work.data.network.PassNetworkDataSource import ru.myitschool.work.data.network.PassNetworkDataSource
import ru.myitschool.work.data.network.UserNetworkDataSource import ru.myitschool.work.data.network.UserNetworkDataSource
import ru.myitschool.work.domain.entities.PassEntity
import ru.myitschool.work.domain.entities.UserEntity import ru.myitschool.work.domain.entities.UserEntity
import ru.myitschool.work.domain.login.LogoutUseCase import ru.myitschool.work.domain.login.LogoutUseCase
import ru.myitschool.work.domain.passes.GetCurrentPassesUseCase import ru.myitschool.work.domain.passes.GetCurrentPassesUseCase
@ -50,14 +48,7 @@ class UserViewModel(
private fun updateState() { private fun updateState() {
viewModelScope.launch { viewModelScope.launch {
_state.emit(State.Loading) _state.emit(State.Loading)
_state.emit(State.Show(getCurrentUserUseCase()))
_state.emit(
State.Show(
getCurrentUserUseCase(),
getCurrentPassesUseCase(0, 30).getOrNull()!!
)
)
} }
} }
@ -71,7 +62,7 @@ class UserViewModel(
sealed interface State { sealed interface State {
data object Loading : State data object Loading : State
data class Show(val userEntity: UserEntity, val passes: List<PassEntity>) : State data class Show(val userEntity: UserEntity) : State
} }
companion object { companion object {

View File

@ -1,74 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View File

@ -160,7 +160,6 @@
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/passes" android:id="@+id/passes"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp" android:layout_marginHorizontal="20dp"

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" /> <background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground" /> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon> </adaptive-icon>