fix: data и domain слои получения списка посещений + начало создания пагинации
Some checks failed
Merge core/template-android-project to this repo / merge-if-needed (push) Has been cancelled

This commit is contained in:
yastruckov 2025-02-18 20:35:15 +03:00
parent c3c88798a9
commit 5a490f37f4
16 changed files with 189 additions and 17 deletions

View File

@ -1,3 +1,5 @@
import com.android.sdklib.AndroidVersion.VersionCodes.UPSIDE_DOWN_CAKE
plugins {
androidApplication
jetbrainsKotlinSerialization version Version.Kotlin.language
@ -9,12 +11,12 @@ plugins {
val packageName = "ru.myitschool.work"
android {
namespace = packageName
compileSdk = 35
compileSdk = UPSIDE_DOWN_CAKE
defaultConfig {
applicationId = packageName
minSdk = 31
targetSdk = 35
targetSdk = UPSIDE_DOWN_CAKE
versionCode = 1
versionName = "1.0"
@ -38,12 +40,11 @@ android {
dependencies {
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.annotation)
implementation(libs.androidx.lifecycle.livedata.ktx)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
defaultLibrary()
implementation(Dependencies.AndroidX.activity)
implementation(Dependencies.AndroidX.fragment)
@ -65,10 +66,10 @@ dependencies {
implementation(libs.androidx.camera.lifecycle)
implementation(libs.androidx.camera.view)
implementation(libs.androidx.camera.mlkit.vision)
val hilt = "2.51.1"
implementation(libs.androidx.paging.runtime.ktx)
defaultLibrary()
implementation(libs.hilt.android)
kapt("com.google.dagger:hilt-android-compiler:$hilt")
kapt(libs.hilt.android.compiler)
}
kapt {

View File

@ -1,5 +1,5 @@
package ru.myitschool.work.core
// БЕРИТЕ И ИЗМЕНЯЙТЕ ХОСТ ТОЛЬКО ЗДЕСЬ И НЕ БЕРИТЕ ИЗ ДРУГИХ МЕСТ. ФАЙЛ ПЕРЕМЕЩАТЬ НЕЛЬЗЯ
object Constants {
const val SERVER_ADDRESS = "http://10.0.2.2:8080"
const val SERVER_ADDRESS = "http://192.168.1.145:8080"
}

View File

@ -12,6 +12,7 @@ class UserDataStoreManager(private val context: Context) {
companion object {
private val USERNAME_KEY = stringPreferencesKey("username")
private val ROLE_KEY = stringPreferencesKey("role")
fun getInstance(context: Context): UserDataStoreManager {
return UserDataStoreManager(context.applicationContext)
@ -21,12 +22,19 @@ class UserDataStoreManager(private val context: Context) {
val usernameFlow: Flow<String> = context.applicationContext.dataStore.data.map { prefs ->
prefs[USERNAME_KEY] ?: ""
}
val roleFlow: Flow<String> = context.applicationContext.dataStore.data.map{ prefs ->
prefs[ROLE_KEY] ?: ""
}
suspend fun saveUsername(username: String) {
context.dataStore.edit { prefs ->
prefs[USERNAME_KEY] = username
}
}
suspend fun saveRole(role: String){
context.dataStore.edit { prefs ->
prefs[ROLE_KEY] = role
}
}
suspend fun clearUsername() {
context.applicationContext.dataStore.edit { it.clear() }
}

View File

@ -0,0 +1,14 @@
package ru.myitschool.work.data.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
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 readerId: Long?,
@SerialName("type") val type: String?
)

View File

@ -0,0 +1,9 @@
package ru.myitschool.work.data.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class VisitListPagingDTO(
@SerialName("content") val content : List<VisitDTO>?
)

View File

@ -0,0 +1,32 @@
package ru.myitschool.work.data.visitsList
import android.content.Context
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import io.ktor.http.HttpStatusCode
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.VisitListPagingDTO
import ru.myitschool.work.utils.NetworkModule
class VisitListNetworkDataSource(
context: Context
){
private val client = NetworkModule.httpClient
private val userDataStoreManager = UserDataStoreManager.getInstance(context)
suspend fun getList(pageNum: Int, pageSize: Int):Result<VisitListPagingDTO> = withContext(Dispatchers.IO){
runCatching {
val username = userDataStoreManager.usernameFlow.first()
val result = client.get("${Constants.SERVER_ADDRESS}/api/$username/visits?page=$pageNum&size=$pageSize")
if (result.status != HttpStatusCode.OK) {
error("Status ${result.status}")
}
result.body()
}
}
}

View File

@ -0,0 +1,21 @@
package ru.myitschool.work.data.visitsList
import ru.myitschool.work.domain.entities.UserEntity
import ru.myitschool.work.domain.entities.VisitEntity
import ru.myitschool.work.domain.visitsList.VisitListRepo
class VisitListRepoImpl(
private val networkDataSource: VisitListNetworkDataSource
) : VisitListRepo {
override suspend fun getList(pageNum: Int, pageSize: Int): Result<List<VisitEntity>> {
return networkDataSource.getList(pageNum, pageSize).map { pagingDTO ->
pagingDTO.content?.mapNotNull { dto->
VisitEntity(
scanTime = dto.scanTime ?: return@mapNotNull null,
readerId = dto.readerId ?: return@mapNotNull null,
type = dto.type ?: return@mapNotNull null
)
}?: return Result.failure(IllegalStateException("List parse error"))
}
}
}

View File

@ -0,0 +1,9 @@
package ru.myitschool.work.domain.entities
import java.util.Date
data class VisitEntity(
val scanTime : Date,
val readerId: Long,
val type: String
)

View File

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

View File

@ -0,0 +1,8 @@
package ru.myitschool.work.domain.visitsList
import ru.myitschool.work.domain.entities.UserEntity
import ru.myitschool.work.domain.entities.VisitEntity
interface VisitListRepo {
suspend fun getList(pageNum : Int, pageSize: Int) : Result<List<VisitEntity>>
}

View File

@ -42,12 +42,27 @@ class LoginFragment : Fragment(R.layout.fragment_login) {
lifecycleScope.launch {
viewModel.state.collect { state ->
with(binding) {
error.visibility = if (state is LoginViewModel.State.Error) View.VISIBLE else View.GONE
username.isEnabled = state !is LoginViewModel.State.Loading
if (state is LoginViewModel.State.Success) {
findNavController().navigate(R.id.mainFragment)
when(state){
is LoginViewModel.State.Error -> {
error.visibility = View.VISIBLE
loading.visibility = View.GONE
username.isEnabled = true
}
is LoginViewModel.State.Idle -> {
loading.visibility = View.GONE
error.visibility = View.GONE
username.isEnabled = true
}
is LoginViewModel.State.Loading -> {
loading.visibility = View.VISIBLE
error.visibility = View.GONE
username.isEnabled = false
}
is LoginViewModel.State.Success -> {
findNavController().navigate(R.id.mainFragment)
}
}
}
}
}

View File

@ -0,0 +1,20 @@
package ru.myitschool.work.ui.main
import androidx.paging.PagingSource
import androidx.paging.PagingState
import ru.myitschool.work.domain.entities.VisitEntity
class VisitListPagingSource(
private val request: suspend(pageNum: Int, pageSize: Int) ->Result<List<VisitEntity>>
) : PagingSource<Int, VisitEntity>() {
override fun getRefreshKey(state: PagingState<Int, VisitEntity>): 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, VisitEntity> {
TODO("Not yet implemented")
}
}

View File

@ -23,9 +23,9 @@ class QrResultViewModel(
val state: StateFlow<State> = _state.asStateFlow()
sealed class State{
object Success : State()
object Loading : State()
object Error : State()
data object Success : State()
data object Loading : State()
data object Error : State()
}
fun openDoor(openEntity: OpenEntity){
_state.value = State.Loading

View File

@ -0,0 +1,25 @@
package ru.myitschool.work.utils
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
object DateSerializer : KSerializer<Date> {
private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US)
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): Date {
return dateFormat.parse(decoder.decodeString())!!
}
override fun serialize(encoder: Encoder, value: Date) {
encoder.encodeString(dateFormat.format(value))
}
}

View File

@ -17,6 +17,7 @@ lifecycleLivedataKtx = "2.8.7"
lifecycleViewmodelKtx = "2.8.7"
navigationFragmentKtx = "2.8.7"
navigationUiKtx = "2.8.7"
pagingRuntimeKtx = "3.3.6"
picasso = "2.8"
[libraries]
androidx-activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" }
@ -29,8 +30,10 @@ androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
androidx-navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigationUiKtx" }
androidx-navigation-fragment-ktx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigationFragmentKtx" }
androidx-paging-runtime-ktx = { module = "androidx.paging:paging-runtime-ktx", version.ref = "pagingRuntimeKtx" }
barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "barcodeScanning" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktorClientContentNegotiation" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktorClientContentNegotiation" }