diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4bdcaaa..6aab706 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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 { diff --git a/app/src/main/java/ru/myitschool/work/core/Constants.kt b/app/src/main/java/ru/myitschool/work/core/Constants.kt index ffb63c1..e3bd895 100644 --- a/app/src/main/java/ru/myitschool/work/core/Constants.kt +++ b/app/src/main/java/ru/myitschool/work/core/Constants.kt @@ -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" } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/UserDataStoreManager.kt b/app/src/main/java/ru/myitschool/work/data/UserDataStoreManager.kt index f24174a..3478ca7 100644 --- a/app/src/main/java/ru/myitschool/work/data/UserDataStoreManager.kt +++ b/app/src/main/java/ru/myitschool/work/data/UserDataStoreManager.kt @@ -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 = context.applicationContext.dataStore.data.map { prefs -> prefs[USERNAME_KEY] ?: "" } + val roleFlow: Flow = 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() } } diff --git a/app/src/main/java/ru/myitschool/work/data/dto/VisitDTO.kt b/app/src/main/java/ru/myitschool/work/data/dto/VisitDTO.kt new file mode 100644 index 0000000..e7b77a2 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/dto/VisitDTO.kt @@ -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? +) diff --git a/app/src/main/java/ru/myitschool/work/data/dto/VisitListPagingDTO.kt b/app/src/main/java/ru/myitschool/work/data/dto/VisitListPagingDTO.kt new file mode 100644 index 0000000..73130d6 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/dto/VisitListPagingDTO.kt @@ -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? +) diff --git a/app/src/main/java/ru/myitschool/work/data/visitsList/VisitListNetworkDataSource.kt b/app/src/main/java/ru/myitschool/work/data/visitsList/VisitListNetworkDataSource.kt new file mode 100644 index 0000000..cbc833b --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/visitsList/VisitListNetworkDataSource.kt @@ -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 = 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() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/visitsList/VisitListRepoImpl.kt b/app/src/main/java/ru/myitschool/work/data/visitsList/VisitListRepoImpl.kt new file mode 100644 index 0000000..1accfab --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/visitsList/VisitListRepoImpl.kt @@ -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> { + 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")) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/entities/VisitEntity.kt b/app/src/main/java/ru/myitschool/work/domain/entities/VisitEntity.kt new file mode 100644 index 0000000..8cce8f6 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/entities/VisitEntity.kt @@ -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 +) diff --git a/app/src/main/java/ru/myitschool/work/domain/visitsList/GetVisitListUseCase.kt b/app/src/main/java/ru/myitschool/work/domain/visitsList/GetVisitListUseCase.kt new file mode 100644 index 0000000..7735f8e --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/visitsList/GetVisitListUseCase.kt @@ -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) +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/visitsList/VisitListRepo.kt b/app/src/main/java/ru/myitschool/work/domain/visitsList/VisitListRepo.kt new file mode 100644 index 0000000..76937fe --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/visitsList/VisitListRepo.kt @@ -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> +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt b/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt index 4056cc9..39feb40 100644 --- a/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt +++ b/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt @@ -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) + } } + } } } diff --git a/app/src/main/java/ru/myitschool/work/ui/main/VisitListPagingSource.kt b/app/src/main/java/ru/myitschool/work/ui/main/VisitListPagingSource.kt new file mode 100644 index 0000000..04c18b8 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/main/VisitListPagingSource.kt @@ -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> +) : 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 { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultViewModel.kt index d9620e2..34308bd 100644 --- a/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultViewModel.kt +++ b/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultViewModel.kt @@ -23,9 +23,9 @@ class QrResultViewModel( val state: StateFlow = _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 diff --git a/app/src/main/java/ru/myitschool/work/utils/DateSerializer.kt b/app/src/main/java/ru/myitschool/work/utils/DateSerializer.kt new file mode 100644 index 0000000..90bf308 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/utils/DateSerializer.kt @@ -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 { + 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)) + } +} \ No newline at end of file diff --git a/app/src/main/res/font/montserrat_bold.ttf b/app/src/main/res/font/montserrat_bold.ttf deleted file mode 100644 index 4df50da..0000000 Binary files a/app/src/main/res/font/montserrat_bold.ttf and /dev/null differ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c9cc1bc..6a01b6e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" }