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
@Serializable
data class VisitDTO(
@SerialName("scanTime") @Serializable(with = DateSerializer::class) val scanTime : Date?,
@SerialName("readerId") val readerName: String?,
data class EmployeeEntranceDTO(
@SerialName("id") val id : Int?,
@SerialName("entryTime") @Serializable(with = DateSerializer::class) val scanTime : Date?,
@SerialName("readerName") val readerName: String?,
@SerialName("type") val type: String?
)

View File

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

View File

@ -9,7 +9,7 @@ data class UserDTO(
@SerialName("login") val login: String?,
@SerialName("name") val name: String?,
@SerialName("authority") val authority : String?,
@SerialName("photo") val photoUrl: String?,
@SerialName("photoUrl") val photoUrl: 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 io.ktor.client.call.body
import io.ktor.client.request.basicAuth
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import io.ktor.http.HttpStatusCode
import io.ktor.http.headers
import kotlinx.coroutines.Dispatchers
@ -11,7 +12,7 @@ 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.VisitListPagingDTO
import ru.myitschool.work.data.dto.EmployeeEntranceListPagingDTO
import ru.myitschool.work.utils.NetworkModule
class EmployeeEntranceListNetworkDataSource(
@ -19,7 +20,7 @@ class EmployeeEntranceListNetworkDataSource(
){
private val client = NetworkModule.httpClient
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 {
val username = userDataStoreManager.usernameFlow.first()
val password = userDataStoreManager.passwordFlow.first()
@ -32,6 +33,7 @@ class EmployeeEntranceListNetworkDataSource(
if (result.status != HttpStatusCode.OK) {
error("Status ${result.status}")
}
println(result.bodyAsText())
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.visitsList.EmployeeEntranceListRepo
import ru.myitschool.work.domain.employeeEntrance.employeeEntrances.EmployeeEntranceListRepo
class EmployeeEntranceListRepoImpl(
private val networkDataSource: EmployeeEntranceListNetworkDataSource
@ -10,9 +10,10 @@ class EmployeeEntranceListRepoImpl(
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
type = dto.type ?: return@mapNotNull null,
)
}?: return Result.failure(IllegalStateException("List parse error"))
}

View File

@ -3,7 +3,7 @@ package ru.myitschool.work.data.info
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
import io.ktor.http.headers
@ -25,7 +25,7 @@ class InfoNetworkDataSource(
runCatching {
val username = userDataStoreManager.usernameFlow.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{
basicAuth(username, password)
}

View File

@ -11,7 +11,7 @@ class InfoRepoImpl(
UserEntity(
id = dto.id ?: 0,
login = dto.login ?: "",
name = dto.login ?: "",
name = dto.name ?: "",
authority = dto.authority ?: "",
photoUrl = dto.photoUrl,
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.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.headers
import kotlinx.coroutines.Dispatchers
@ -14,7 +15,8 @@ class LoginNetworkDataSource {
private val client = NetworkModule.httpClient
suspend fun login(username: String, password: String):Result<Unit> = withContext(Dispatchers.IO){
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{
basicAuth(username, password)
}
@ -22,6 +24,7 @@ class LoginNetworkDataSource {
if (result.status != HttpStatusCode.OK) {
error("Status ${result.status}")
}
println(result.bodyAsText())
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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,6 +8,7 @@ import androidx.fragment.app.setFragmentResultListener
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.paging.LoadState
import androidx.recyclerview.widget.LinearLayoutManager
import com.squareup.picasso.Picasso
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.utils.UserState
import ru.myitschool.work.utils.collectWhenStarted
import ru.myitschool.work.utils.collectWithLifecycle
class MainFragment : Fragment(R.layout.fragment_main) {
@ -32,7 +34,7 @@ class MainFragment : Fragment(R.layout.fragment_main) {
_binding = FragmentMainBinding.bind(view)
viewModel.getUserData()
binding.refresh.setOnClickListener { viewModel.getUserData() }
binding.logout.setOnClickListener { logout() }
binding.scan.setOnClickListener { onScanClick() }
viewModel.userState.collectWhenStarted(this) { state ->
@ -60,7 +62,23 @@ class MainFragment : Fragment(R.layout.fragment_main) {
}
binding.content.layoutManager = LinearLayoutManager(requireContext())
val adapter = EmployeeEntranceListAdapter()
binding.refresh.setOnClickListener {
viewModel.getUserData()
adapter.refresh()
}
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 ->
@ -87,6 +105,7 @@ class MainFragment : Fragment(R.layout.fragment_main) {
private fun showUserData(userEntity: UserEntity) {
binding.apply {
fullname.text = userEntity.name
println(userEntity.name)
position.text = userEntity.position
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.info.InfoNetworkDataSource
import ru.myitschool.work.data.info.InfoRepoImpl
import ru.myitschool.work.data.visitsList.employeeEntrances.EmployeeEntranceListNetworkDataSource
import ru.myitschool.work.data.visitsList.employeeEntrances.EmployeeEntranceListRepoImpl
import ru.myitschool.work.data.entrance.employeeEntrances.EmployeeEntranceListNetworkDataSource
import ru.myitschool.work.data.entrance.employeeEntrances.EmployeeEntranceListRepoImpl
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 java.text.SimpleDateFormat
import java.util.Locale
class MainViewModel(
private val infoUseCase: GetInfoUseCase,
@ -41,31 +39,30 @@ class MainViewModel(
println("Creating PagingSource")
EmployeeEntranceListPagingSource(listUseCase::invoke)
}.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)
val userState: StateFlow<UserState> get() = _userState
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() {
_userState.value = UserState.Loading
viewModelScope.launch {
infoUseCase.invoke().fold(
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 {
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())
return dateFormat.format(date).toString()

View File

@ -11,7 +11,7 @@ import java.util.Date
import java.util.Locale
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)

View File

@ -1,8 +1,10 @@
package ru.myitschool.work.utils
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
@ -15,4 +17,14 @@ inline fun <T> Flow<T>.collectWhenStarted(
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) }
}
}
}