From f76d744fd71112597531c28b91b5c361044542e7 Mon Sep 17 00:00:00 2001 From: a1pha Date: Wed, 19 Feb 2025 18:53:18 +0300 Subject: [PATCH] feat: View profile use case done --- .../work/data/PassRepositoryImpl.kt | 51 +++++++++++++++++ .../ru/myitschool/work/data/dto/PassDto.kt | 15 +++++ .../data/network/PassNetworkDataSource.kt | 56 +++++++++++++++++++ .../work/domain/entities/PassEntity.kt | 7 +++ .../domain/passes/GetCurrentPassesUseCase.kt | 11 ++++ .../work/domain/passes/PassRepository.kt | 9 +++ .../work/ui/NoInternetNotificationFragment.kt | 27 +++++++++ .../work/ui/profile/PassesListAdapter.kt | 50 +++++++++++++++++ .../work/ui/profile/PassesPagingSource.kt | 30 ++++++++++ .../work/ui/profile/UserViewModel.kt | 28 +++++++++- 10 files changed, 282 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/ru/myitschool/work/data/PassRepositoryImpl.kt create mode 100644 app/src/main/java/ru/myitschool/work/data/dto/PassDto.kt create mode 100644 app/src/main/java/ru/myitschool/work/data/network/PassNetworkDataSource.kt create mode 100644 app/src/main/java/ru/myitschool/work/domain/entities/PassEntity.kt create mode 100644 app/src/main/java/ru/myitschool/work/domain/passes/GetCurrentPassesUseCase.kt create mode 100644 app/src/main/java/ru/myitschool/work/domain/passes/PassRepository.kt create mode 100644 app/src/main/java/ru/myitschool/work/ui/NoInternetNotificationFragment.kt create mode 100644 app/src/main/java/ru/myitschool/work/ui/profile/PassesListAdapter.kt create mode 100644 app/src/main/java/ru/myitschool/work/ui/profile/PassesPagingSource.kt diff --git a/app/src/main/java/ru/myitschool/work/data/PassRepositoryImpl.kt b/app/src/main/java/ru/myitschool/work/data/PassRepositoryImpl.kt new file mode 100644 index 0000000..8a5ede7 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/PassRepositoryImpl.kt @@ -0,0 +1,51 @@ +package ru.myitschool.work.data + +import ru.myitschool.work.data.dto.PassDto +import ru.myitschool.work.data.local.CredentialsLocalDataSource +import ru.myitschool.work.data.network.PassNetworkDataSource +import ru.myitschool.work.domain.entities.PassEntity +import ru.myitschool.work.domain.passes.PassRepository + +class PassRepositoryImpl( + private val networkDataSource: PassNetworkDataSource, + private val credentialsLocalDataSource: CredentialsLocalDataSource +) : PassRepository { + + override suspend fun getCurrentPasses(pageNum: Int, pageSize: Int): Result> { + return map( + networkDataSource.getCurrentPasses( + pageNum, + pageSize, + credentialsLocalDataSource.getToken() + ) + ) + } + + override suspend fun getUsersPasses( + pageNum: Int, + pageSize: Int, + login: String + ): Result> { + return map( + networkDataSource.getUsersPasses( + login = login, + pageNum = pageNum, + pageSize = pageSize, + token = credentialsLocalDataSource.getToken() + ) + ) + } + + private fun map(listDto: Result>): Result> { + return listDto.map { successListDto -> + successListDto.mapNotNull { dto -> + PassEntity( + type = dto.type ?: return@mapNotNull null, + name = dto.name ?: return@mapNotNull null, + time = dto.time ?: return@mapNotNull null + ) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/dto/PassDto.kt b/app/src/main/java/ru/myitschool/work/data/dto/PassDto.kt new file mode 100644 index 0000000..22dc062 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/dto/PassDto.kt @@ -0,0 +1,15 @@ +package ru.myitschool.work.data.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + + +@Serializable +data class PassDto( + @SerialName("type") + val type: String?, + @SerialName("name") + val name: String?, + @SerialName("time") + val time: String? +) diff --git a/app/src/main/java/ru/myitschool/work/data/network/PassNetworkDataSource.kt b/app/src/main/java/ru/myitschool/work/data/network/PassNetworkDataSource.kt new file mode 100644 index 0000000..d9e638c --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/network/PassNetworkDataSource.kt @@ -0,0 +1,56 @@ +package ru.myitschool.work.data.network + +import io.ktor.client.call.body +import io.ktor.client.request.get +import io.ktor.client.request.headers +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 +import ru.myitschool.work.data.dto.PassDto + +object PassNetworkDataSource { + private val client = KtorClient.client + + suspend fun getCurrentPasses( + pageNum: Int, + pageSize: Int, + token: String + ): Result> = + withContext(Dispatchers.IO) { + runCatching { + val response = + client.get("$SERVER_ADDRESS/api/passes?pageNum=$pageNum&pageSize=$pageSize") { + headers { + append(HttpHeaders.Authorization, token) + } + } + if (response.status != HttpStatusCode.OK) + error("${response.status}") + response.body() + } + + } + + suspend fun getUsersPasses( + login: String, + pageNum: Int, + pageSize: Int, + token: String + ): Result> = withContext(Dispatchers.IO) { + runCatching { + + val response = + client.get("$SERVER_ADDRESS/api/passes?login=$login&pageNum=$pageNum&pageSize=$pageSize") { + headers { + append(HttpHeaders.Authorization, token) + } + } + if (response.status != HttpStatusCode.OK) + error("Status ${response.status}") + response.body() + } + + } +} diff --git a/app/src/main/java/ru/myitschool/work/domain/entities/PassEntity.kt b/app/src/main/java/ru/myitschool/work/domain/entities/PassEntity.kt new file mode 100644 index 0000000..52319a7 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/entities/PassEntity.kt @@ -0,0 +1,7 @@ +package ru.myitschool.work.domain.entities + +data class PassEntity( + val type: String, + val name: String, + val time: String +) diff --git a/app/src/main/java/ru/myitschool/work/domain/passes/GetCurrentPassesUseCase.kt b/app/src/main/java/ru/myitschool/work/domain/passes/GetCurrentPassesUseCase.kt new file mode 100644 index 0000000..33f7206 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/passes/GetCurrentPassesUseCase.kt @@ -0,0 +1,11 @@ +package ru.myitschool.work.domain.passes + +import ru.myitschool.work.domain.entities.PassEntity + +class GetCurrentPassesUseCase( + private val repository: PassRepository +) { + + suspend operator fun invoke(pageNum: Int, pageSize: Int): Result> = + repository.getCurrentPasses(pageNum, pageSize) +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/passes/PassRepository.kt b/app/src/main/java/ru/myitschool/work/domain/passes/PassRepository.kt new file mode 100644 index 0000000..580c46f --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/passes/PassRepository.kt @@ -0,0 +1,9 @@ +package ru.myitschool.work.domain.passes + +import ru.myitschool.work.domain.entities.PassEntity + +interface PassRepository { + + suspend fun getCurrentPasses(pageNum: Int, pageSize: Int): Result> + suspend fun getUsersPasses(pageNum: Int, pageSize: Int, login: String): Result> +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/NoInternetNotificationFragment.kt b/app/src/main/java/ru/myitschool/work/ui/NoInternetNotificationFragment.kt new file mode 100644 index 0000000..eaa1f68 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/NoInternetNotificationFragment.kt @@ -0,0 +1,27 @@ +package ru.myitschool.work.ui + +import android.os.Bundle +import android.view.View +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import ru.myitschool.work.R +import ru.myitschool.work.databinding.FragmentNoInternetNotificationBinding +import ru.myitschool.work.utils.isOnline + +class NoInternetNotificationFragment: BottomSheetDialogFragment(R.layout.fragment_no_internet_notification) { + + private var _binding: FragmentNoInternetNotificationBinding? = null + private val binding: FragmentNoInternetNotificationBinding get() = _binding!! + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + _binding = FragmentNoInternetNotificationBinding.bind(view) + + binding.close.setOnClickListener { + if (isOnline(requireActivity())) dismiss() + } + } + + override fun onDestroy() { + _binding = null + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/profile/PassesListAdapter.kt b/app/src/main/java/ru/myitschool/work/ui/profile/PassesListAdapter.kt new file mode 100644 index 0000000..9ddc1f7 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/profile/PassesListAdapter.kt @@ -0,0 +1,50 @@ +package ru.myitschool.work.ui.profile + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.paging.PagingDataAdapter +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import ru.myitschool.work.databinding.PassItemBinding +import ru.myitschool.work.domain.entities.PassEntity + +class PassesListAdapter: + PagingDataAdapter(CenterDiff) { + + class ViewHolder( + private val binding: PassItemBinding, + ) : RecyclerView.ViewHolder(binding.root) { + fun bind(item: PassEntity) { + binding.time.text = item.time + binding.name.text = item.name + binding.type.text = item.type + } + } + + object CenterDiff : DiffUtil.ItemCallback() { + override fun areContentsTheSame(oldItem: PassEntity, newItem: PassEntity): Boolean { + return oldItem.name == newItem.name + } + + override fun areItemsTheSame(oldItem: PassEntity, newItem: PassEntity): Boolean { + return oldItem == newItem + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder( + PassItemBinding.inflate(LayoutInflater.from(parent.context), parent, false), + ) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind( + getItem(position) ?: PassEntity( + type = "Загрузка ...", + name = "Терминал №...", + time = "Давным-давно..." + ) + ) + } + +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/profile/PassesPagingSource.kt b/app/src/main/java/ru/myitschool/work/ui/profile/PassesPagingSource.kt new file mode 100644 index 0000000..de2ec84 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/profile/PassesPagingSource.kt @@ -0,0 +1,30 @@ +package ru.myitschool.work.ui.profile + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import ru.myitschool.work.domain.entities.PassEntity + +class PassesPagingSource( + private val request: suspend (pageNum: Int, pageSize: Int) -> Result> +) : PagingSource() { + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition?.let { + state.closestPageToPosition(it)?.prevKey?.plus(1) + ?: state.closestPageToPosition(it)?.nextKey?.minus(1) + } + } + + override suspend fun load(params: LoadParams): LoadResult { + val pageNum = params.key ?: 0 + return request.invoke(pageNum, params.loadSize).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) } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/profile/UserViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/profile/UserViewModel.kt index d037094..de33173 100644 --- a/app/src/main/java/ru/myitschool/work/ui/profile/UserViewModel.kt +++ b/app/src/main/java/ru/myitschool/work/ui/profile/UserViewModel.kt @@ -4,25 +4,43 @@ 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.cachedIn import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +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.PassNetworkDataSource import ru.myitschool.work.data.network.UserNetworkDataSource import ru.myitschool.work.domain.entities.UserEntity import ru.myitschool.work.domain.login.LogoutUseCase +import ru.myitschool.work.domain.passes.GetCurrentPassesUseCase import ru.myitschool.work.domain.user.GetCurrentUserUseCase class UserViewModel( private val getCurrentUserUseCase: GetCurrentUserUseCase, - private val logoutUseCase: LogoutUseCase + private val logoutUseCase: LogoutUseCase, + private val getCurrentPassesUseCase: GetCurrentPassesUseCase ) : ViewModel() { private val _state = MutableStateFlow(State.Loading) val state = _state.asStateFlow() + val listState = Pager( + config = PagingConfig( + pageSize = 10, + enablePlaceholders = false, + maxSize = 50 + ) + ) { + PassesPagingSource(getCurrentPassesUseCase::invoke) + }.flow + .cachedIn(viewModelScope) + init { updateState() } @@ -57,7 +75,13 @@ class UserViewModel( ) return UserViewModel( getCurrentUserUseCase = GetCurrentUserUseCase(repository = repository), - logoutUseCase = LogoutUseCase(repository = repository) + logoutUseCase = LogoutUseCase(repository = repository), + getCurrentPassesUseCase = GetCurrentPassesUseCase( + repository = PassRepositoryImpl( + networkDataSource = PassNetworkDataSource, + credentialsLocalDataSource = CredentialsLocalDataSource.getInstance() + ) + ) ) as T } }