feat: получение списка посещений пользователя,

фикс бага, что вместо ФИО в профиль выводился логин
This commit is contained in:
yastruckov 2025-02-19 12:59:20 +03:00
parent 645c3e5f62
commit f772c4cb72
22 changed files with 160 additions and 45 deletions

View File

@ -6,8 +6,9 @@ import ru.myitschool.work.utils.DateSerializer
import java.util.Date import java.util.Date
@Serializable @Serializable
data class VisitDTO( data class EmployeeEntranceDTO(
@SerialName("scanTime") @Serializable(with = DateSerializer::class) val scanTime : Date?, @SerialName("id") val id : Int?,
@SerialName("readerId") val readerName: String?, @SerialName("entryTime") @Serializable(with = DateSerializer::class) val scanTime : Date?,
@SerialName("readerName") val readerName: String?,
@SerialName("type") val type: String? @SerialName("type") val type: String?
) )

View File

@ -4,6 +4,6 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
data class VisitListPagingDTO( data class EmployeeEntranceListPagingDTO(
@SerialName("content") val content : List<VisitDTO>? @SerialName("content") val content : List<EmployeeEntranceDTO>?
) )

View File

@ -9,7 +9,7 @@ data class UserDTO(
@SerialName("login") val login: String?, @SerialName("login") val login: String?,
@SerialName("name") val name: String?, @SerialName("name") val name: String?,
@SerialName("authority") val authority : String?, @SerialName("authority") val authority : String?,
@SerialName("photo") val photoUrl: String?, @SerialName("photoUrl") val photoUrl: String?,
@SerialName("position") val position: String? @SerialName("position") val position: String?
) )

View File

@ -0,0 +1,39 @@
package ru.myitschool.work.data.entrance.allEntrances
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.http.HttpStatusCode
import io.ktor.http.headers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import ru.myitschool.work.core.Constants
import ru.myitschool.work.data.UserDataStoreManager
import ru.myitschool.work.data.dto.EmployeeEntranceListPagingDTO
import ru.myitschool.work.utils.NetworkModule
class AllEntranceListNetworkDataSource(
context: Context
) {
private val client = NetworkModule.httpClient
private val userDataStoreManager = UserDataStoreManager.getInstance(context)
suspend fun getList(pageNum: Int, pageSize: Int):Result<EmployeeEntranceListPagingDTO> = withContext(
Dispatchers.IO){
runCatching {
val username = userDataStoreManager.usernameFlow.first()
val password = userDataStoreManager.passwordFlow.first()
val result = client.get("${Constants.SERVER_ADDRESS}/api/entrance/all?page=$pageNum&size=$pageSize"){
headers{
basicAuth(username, password)
}
}
if (result.status != HttpStatusCode.OK) {
error("Status ${result.status}")
}
result.body()
}
}
}

View File

@ -0,0 +1,24 @@
package ru.myitschool.work.data.entrance.allEntrances
import ru.myitschool.work.domain.employeeEntrance.allEntrances.AllEntranceListRepo
import ru.myitschool.work.domain.entities.EmployeeEntranceEntity
class AllEntranceListRepoImpl(
private val networkDataSource: AllEntranceListNetworkDataSource
) : AllEntranceListRepo {
override suspend fun getList(
pageNum: Int,
pageSize: Int
): Result<List<EmployeeEntranceEntity>> {
return networkDataSource.getList(pageNum, pageSize).map { pagingDTO ->
pagingDTO.content?.mapNotNull { dto ->
EmployeeEntranceEntity(
id = dto.id ?: return@mapNotNull null,
scanTime = dto.scanTime ?: return@mapNotNull null,
readerName = dto.readerName ?: return@mapNotNull null,
type = dto.type ?: return@mapNotNull null
)
}?: return Result.failure(IllegalStateException("List parse error"))
}
}
}

View File

@ -1,9 +1,10 @@
package ru.myitschool.work.data.visitsList.employeeEntrances package ru.myitschool.work.data.entrance.employeeEntrances
import android.content.Context import android.content.Context
import io.ktor.client.call.body import io.ktor.client.call.body
import io.ktor.client.request.basicAuth import io.ktor.client.request.basicAuth
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.http.headers import io.ktor.http.headers
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -11,7 +12,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import ru.myitschool.work.core.Constants import ru.myitschool.work.core.Constants
import ru.myitschool.work.data.UserDataStoreManager import ru.myitschool.work.data.UserDataStoreManager
import ru.myitschool.work.data.dto.VisitListPagingDTO import ru.myitschool.work.data.dto.EmployeeEntranceListPagingDTO
import ru.myitschool.work.utils.NetworkModule import ru.myitschool.work.utils.NetworkModule
class EmployeeEntranceListNetworkDataSource( class EmployeeEntranceListNetworkDataSource(
@ -19,7 +20,7 @@ class EmployeeEntranceListNetworkDataSource(
){ ){
private val client = NetworkModule.httpClient private val client = NetworkModule.httpClient
private val userDataStoreManager = UserDataStoreManager.getInstance(context) private val userDataStoreManager = UserDataStoreManager.getInstance(context)
suspend fun getList(pageNum: Int, pageSize: Int):Result<VisitListPagingDTO> = withContext(Dispatchers.IO){ suspend fun getList(pageNum: Int, pageSize: Int):Result<EmployeeEntranceListPagingDTO> = 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()
@ -32,6 +33,7 @@ class EmployeeEntranceListNetworkDataSource(
if (result.status != HttpStatusCode.OK) { if (result.status != HttpStatusCode.OK) {
error("Status ${result.status}") error("Status ${result.status}")
} }
println(result.bodyAsText())
result.body() result.body()
} }
} }

View File

@ -1,7 +1,7 @@
package ru.myitschool.work.data.visitsList.employeeEntrances package ru.myitschool.work.data.entrance.employeeEntrances
import ru.myitschool.work.domain.entities.EmployeeEntranceEntity import ru.myitschool.work.domain.entities.EmployeeEntranceEntity
import ru.myitschool.work.domain.visitsList.EmployeeEntranceListRepo import ru.myitschool.work.domain.employeeEntrance.employeeEntrances.EmployeeEntranceListRepo
class EmployeeEntranceListRepoImpl( class EmployeeEntranceListRepoImpl(
private val networkDataSource: EmployeeEntranceListNetworkDataSource private val networkDataSource: EmployeeEntranceListNetworkDataSource
@ -10,9 +10,10 @@ class EmployeeEntranceListRepoImpl(
return networkDataSource.getList(pageNum, pageSize).map { pagingDTO -> return networkDataSource.getList(pageNum, pageSize).map { pagingDTO ->
pagingDTO.content?.mapNotNull { dto-> pagingDTO.content?.mapNotNull { dto->
EmployeeEntranceEntity( EmployeeEntranceEntity(
id = dto.id ?: return@mapNotNull null,
scanTime = dto.scanTime ?: return@mapNotNull null, scanTime = dto.scanTime ?: return@mapNotNull null,
readerName = dto.readerName ?: return@mapNotNull null, readerName = dto.readerName ?: return@mapNotNull null,
type = dto.type ?: return@mapNotNull null type = dto.type ?: return@mapNotNull null,
) )
}?: return Result.failure(IllegalStateException("List parse error")) }?: return Result.failure(IllegalStateException("List parse error"))
} }

View File

@ -3,7 +3,7 @@ package ru.myitschool.work.data.info
import android.content.Context import android.content.Context
import io.ktor.client.call.body import io.ktor.client.call.body
import io.ktor.client.request.basicAuth 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.client.statement.bodyAsText
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.http.headers import io.ktor.http.headers
@ -25,7 +25,7 @@ class InfoNetworkDataSource(
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.get("${Constants.SERVER_ADDRESS}/api/employee/profile"){ val result = client.post("${Constants.SERVER_ADDRESS}/api/employee/profile"){
headers{ headers{
basicAuth(username, password) basicAuth(username, password)
} }

View File

@ -11,7 +11,7 @@ class InfoRepoImpl(
UserEntity( UserEntity(
id = dto.id ?: 0, id = dto.id ?: 0,
login = dto.login ?: "", login = dto.login ?: "",
name = dto.login ?: "", name = dto.name ?: "",
authority = dto.authority ?: "", authority = dto.authority ?: "",
photoUrl = dto.photoUrl, photoUrl = dto.photoUrl,
position = dto.position ?: "" position = dto.position ?: ""

View File

@ -2,7 +2,8 @@ package ru.myitschool.work.data.login
import io.ktor.client.call.body import io.ktor.client.call.body
import io.ktor.client.request.basicAuth 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 import io.ktor.http.HttpStatusCode
import io.ktor.http.headers import io.ktor.http.headers
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -14,7 +15,8 @@ class LoginNetworkDataSource {
private val client = NetworkModule.httpClient private val client = NetworkModule.httpClient
suspend fun login(username: String, password: String):Result<Unit> = withContext(Dispatchers.IO){ suspend fun login(username: String, password: String):Result<Unit> = withContext(Dispatchers.IO){
runCatching { runCatching {
val result = client.get("${Constants.SERVER_ADDRESS}/api/employee/login"){ println("$username $password")
val result = client.post("${Constants.SERVER_ADDRESS}/api/employee/login"){
headers{ headers{
basicAuth(username, password) basicAuth(username, password)
} }
@ -22,6 +24,7 @@ class LoginNetworkDataSource {
if (result.status != HttpStatusCode.OK) { if (result.status != HttpStatusCode.OK) {
error("Status ${result.status}") error("Status ${result.status}")
} }
println(result.bodyAsText())
result.body() result.body()
} }
} }

View File

@ -0,0 +1,7 @@
package ru.myitschool.work.domain.employeeEntrance.allEntrances
import ru.myitschool.work.domain.entities.EmployeeEntranceEntity
interface AllEntranceListRepo {
suspend fun getList(pageNum : Int, pageSize: Int) : Result<List<EmployeeEntranceEntity>>
}

View File

@ -0,0 +1,7 @@
package ru.myitschool.work.domain.employeeEntrance.allEntrances
class GetAllEmployeesEntranceList(
private val repo: AllEntranceListRepo
) {
suspend operator fun invoke(pageNum : Int, pageSize: Int) = repo.getList(pageNum, pageSize)
}

View File

@ -1,4 +1,4 @@
package ru.myitschool.work.domain.visitsList package ru.myitschool.work.domain.employeeEntrance.employeeEntrances
import ru.myitschool.work.domain.entities.EmployeeEntranceEntity import ru.myitschool.work.domain.entities.EmployeeEntranceEntity

View File

@ -1,4 +1,4 @@
package ru.myitschool.work.domain.visitsList package ru.myitschool.work.domain.employeeEntrance.employeeEntrances
class GetEmployeeEntranceListUseCase( class GetEmployeeEntranceListUseCase(
private val repo: EmployeeEntranceListRepo private val repo: EmployeeEntranceListRepo

View File

@ -1,9 +1,9 @@
package ru.myitschool.work.domain.entities package ru.myitschool.work.domain.entities
import java.util.Date import java.util.Date
data class EmployeeEntranceEntity( data class EmployeeEntranceEntity(
val scanTime : Date, val id : Int?,
val readerName: String, val scanTime : Date?,
val type: String val readerName: String?,
val type: String?
) )

View File

@ -36,6 +36,8 @@ class LoginFragment : Fragment(R.layout.fragment_login) {
} }
} }
binding.username.addTextChangedListener(textWatcher) binding.username.addTextChangedListener(textWatcher)
binding.password.addTextChangedListener(textWatcher)
binding.loginBtn.isEnabled = false binding.loginBtn.isEnabled = false
binding.loginBtn.setOnClickListener{ binding.loginBtn.setOnClickListener{
viewModel.login(binding.username.text.toString(), binding.password.text.toString()) viewModel.login(binding.username.text.toString(), binding.password.text.toString())

View File

@ -28,6 +28,7 @@ class EmployeeEntranceListPagingSource(
}, },
onFailure = { e-> onFailure = { e->
println(e)
LoadResult.Error(e) LoadResult.Error(e)
} }
) )

View File

@ -8,6 +8,7 @@ import androidx.fragment.app.setFragmentResultListener
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.paging.LoadState
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -18,6 +19,7 @@ import ru.myitschool.work.domain.entities.UserEntity
import ru.myitschool.work.ui.qr.scan.QrScanDestination import ru.myitschool.work.ui.qr.scan.QrScanDestination
import ru.myitschool.work.utils.UserState import ru.myitschool.work.utils.UserState
import ru.myitschool.work.utils.collectWhenStarted import ru.myitschool.work.utils.collectWhenStarted
import ru.myitschool.work.utils.collectWithLifecycle
class MainFragment : Fragment(R.layout.fragment_main) { class MainFragment : Fragment(R.layout.fragment_main) {
@ -32,7 +34,7 @@ class MainFragment : Fragment(R.layout.fragment_main) {
_binding = FragmentMainBinding.bind(view) _binding = FragmentMainBinding.bind(view)
viewModel.getUserData() viewModel.getUserData()
binding.refresh.setOnClickListener { viewModel.getUserData() }
binding.logout.setOnClickListener { logout() } binding.logout.setOnClickListener { logout() }
binding.scan.setOnClickListener { onScanClick() } binding.scan.setOnClickListener { onScanClick() }
viewModel.userState.collectWhenStarted(this) { state -> viewModel.userState.collectWhenStarted(this) { state ->
@ -60,7 +62,23 @@ class MainFragment : Fragment(R.layout.fragment_main) {
} }
binding.content.layoutManager = LinearLayoutManager(requireContext()) binding.content.layoutManager = LinearLayoutManager(requireContext())
val adapter = EmployeeEntranceListAdapter() val adapter = EmployeeEntranceListAdapter()
binding.refresh.setOnClickListener {
viewModel.getUserData()
adapter.refresh()
}
binding.content.adapter = adapter binding.content.adapter = adapter
viewModel.listState.collectWithLifecycle(this) { data ->
adapter.submitData(data)
}
adapter.loadStateFlow.collectWithLifecycle(this) { loadState ->
val state = loadState.refresh
binding.error.visibility = if (state is LoadState.Error) View.VISIBLE else View.GONE
binding.loading.visibility = if (state is LoadState.Loading) View.VISIBLE else View.GONE
if (state is LoadState.Error) {
binding.error.text = state.error.message.toString()
}
}
setFragmentResultListener(QrScanDestination.REQUEST_KEY) { _, bundle -> setFragmentResultListener(QrScanDestination.REQUEST_KEY) { _, bundle ->
@ -87,6 +105,7 @@ class MainFragment : Fragment(R.layout.fragment_main) {
private fun showUserData(userEntity: UserEntity) { private fun showUserData(userEntity: UserEntity) {
binding.apply { binding.apply {
fullname.text = userEntity.name fullname.text = userEntity.name
println(userEntity.name)
position.text = userEntity.position position.text = userEntity.position
Picasso.get().load(userEntity.photoUrl).into(photo) Picasso.get().load(userEntity.photoUrl).into(photo)

View File

@ -17,13 +17,11 @@ import kotlinx.coroutines.withContext
import ru.myitschool.work.data.UserDataStoreManager import ru.myitschool.work.data.UserDataStoreManager
import ru.myitschool.work.data.info.InfoNetworkDataSource import ru.myitschool.work.data.info.InfoNetworkDataSource
import ru.myitschool.work.data.info.InfoRepoImpl import ru.myitschool.work.data.info.InfoRepoImpl
import ru.myitschool.work.data.visitsList.employeeEntrances.EmployeeEntranceListNetworkDataSource import ru.myitschool.work.data.entrance.employeeEntrances.EmployeeEntranceListNetworkDataSource
import ru.myitschool.work.data.visitsList.employeeEntrances.EmployeeEntranceListRepoImpl import ru.myitschool.work.data.entrance.employeeEntrances.EmployeeEntranceListRepoImpl
import ru.myitschool.work.domain.info.GetInfoUseCase import ru.myitschool.work.domain.info.GetInfoUseCase
import ru.myitschool.work.domain.visitsList.GetEmployeeEntranceListUseCase import ru.myitschool.work.domain.employeeEntrance.employeeEntrances.GetEmployeeEntranceListUseCase
import ru.myitschool.work.utils.UserState import ru.myitschool.work.utils.UserState
import java.text.SimpleDateFormat
import java.util.Locale
class MainViewModel( class MainViewModel(
private val infoUseCase: GetInfoUseCase, private val infoUseCase: GetInfoUseCase,
@ -41,31 +39,30 @@ class MainViewModel(
println("Creating PagingSource") println("Creating PagingSource")
EmployeeEntranceListPagingSource(listUseCase::invoke) EmployeeEntranceListPagingSource(listUseCase::invoke)
}.flow.cachedIn(viewModelScope) }.flow.cachedIn(viewModelScope)
init {
viewModelScope.launch {
listState.collect { pagingData ->
if (pagingData.toString().isEmpty()) {
println("No data in paging data.")
} else {
println("Data received: $pagingData")
}
}
}
}
private val _userState = MutableStateFlow<UserState>(UserState.Loading) private val _userState = MutableStateFlow<UserState>(UserState.Loading)
val userState: StateFlow<UserState> get() = _userState val userState: StateFlow<UserState> get() = _userState
private val dataStoreManager = UserDataStoreManager(application) private val dataStoreManager = UserDataStoreManager(application)
fun formatDate(date: String): String {
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault())
val outputFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault())
return try {
val formattedDate = inputFormat.parse(date)
if (formattedDate != null) {
outputFormat.format(formattedDate)
} else{}
} catch (_: Exception) {
"Invalid Date"
}.toString()
}
fun getUserData() { fun getUserData() {
_userState.value = UserState.Loading _userState.value = UserState.Loading
viewModelScope.launch { viewModelScope.launch {
infoUseCase.invoke().fold( infoUseCase.invoke().fold(
onSuccess = { data -> _userState.value = UserState.Success(data) }, onSuccess = { data -> _userState.value = UserState.Success(data) },
onFailure = { _userState.value = UserState.Error } onFailure = { e -> _userState.value = UserState.Error
println(e)}
) )
} }
} }

View File

@ -6,7 +6,7 @@ import java.util.Locale
fun dateConverter(date: Date?) : String { fun dateConverter(date: Date?) : String {
if (date != null) { if (date != null) {
val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
println(dateFormat.format(date).toString()) println(dateFormat.format(date).toString())
return dateFormat.format(date).toString() return dateFormat.format(date).toString()

View File

@ -11,7 +11,7 @@ import java.util.Date
import java.util.Locale import java.util.Locale
object DateSerializer : KSerializer<Date> { object DateSerializer : KSerializer<Date> {
private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US) private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US)
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.STRING) override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.STRING)

View File

@ -1,8 +1,10 @@
package ru.myitschool.work.utils package ru.myitschool.work.utils
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -15,4 +17,14 @@ inline fun <T> Flow<T>.collectWhenStarted(
collector.invoke(value) collector.invoke(value)
} }
} }
}
fun <T> Flow<T>.collectWithLifecycle(
fragment: Fragment,
function: suspend (T) -> Unit
){
fragment.viewLifecycleOwner.lifecycleScope.launch {
fragment.repeatOnLifecycle(Lifecycle.State.STARTED) {
collect { function.invoke(it) }
}
}
} }