Merge remote-tracking branch 'origin/master'
@ -5,6 +5,11 @@ plugins {
|
||||
kotlinAnnotationProcessor
|
||||
id("com.google.dagger.hilt.android").version("2.51.1")
|
||||
}
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
force("org.hamcrest:hamcrest-junit:2.0.0.0")
|
||||
}
|
||||
}
|
||||
|
||||
val packageName = "ru.myitschool.work"
|
||||
|
||||
@ -29,15 +34,37 @@ android {
|
||||
targetCompatibility = Version.Kotlin.javaSource
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
isIncludeAndroidResources = true
|
||||
}
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = Version.Kotlin.jvmTarget
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
val fragmentVersion = "1.8.6"
|
||||
debugImplementation("androidx.fragment:fragment-testing-manifest:$fragmentVersion")
|
||||
androidTestImplementation("androidx.fragment:fragment-testing:$fragmentVersion")
|
||||
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
|
||||
androidTestImplementation("androidx.test:runner:1.6.2")
|
||||
androidTestImplementation("androidx.test:rules:1.6.1")
|
||||
|
||||
testImplementation("androidx.test:core:1.6.1")
|
||||
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
testImplementation("org.robolectric:robolectric:4.14")
|
||||
|
||||
defaultLibrary()
|
||||
|
||||
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0")
|
||||
|
||||
implementation("androidx.paging:paging-runtime:3.3.6")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01")
|
||||
|
||||
val ktorClientCore = "3.0.3"
|
||||
@ -54,9 +81,6 @@ dependencies {
|
||||
implementation(Dependencies.AndroidX.Navigation.fragment)
|
||||
implementation(Dependencies.AndroidX.Navigation.navigationUi)
|
||||
|
||||
implementation(Dependencies.Retrofit.library)
|
||||
implementation(Dependencies.Retrofit.gsonConverter)
|
||||
|
||||
implementation("com.squareup.picasso:picasso:2.8")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
|
||||
implementation("androidx.datastore:datastore-preferences:1.1.2")
|
||||
|
26
app/src/androidTest/kotlin/LoginFragmentTest.kt
Normal file
@ -0,0 +1,26 @@
|
||||
import androidx.fragment.app.testing.launchFragmentInContainer
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.swipeDown
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.filters.LargeTest
|
||||
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import ru.myitschool.work.R
|
||||
import ru.myitschool.work.ui.login.LoginFragment
|
||||
import utils.SwipeRefreshLayoutMatchers.isRefreshing
|
||||
|
||||
@RunWith(AndroidJUnit4ClassRunner::class)
|
||||
@LargeTest
|
||||
class LoginFragmentTest {
|
||||
@get:Rule
|
||||
val fragmentRule = launchFragmentInContainer<LoginFragment>()
|
||||
|
||||
@Test
|
||||
fun onSwipeDataRefreshes() {
|
||||
onView(withId(R.id.refresh)).perform(swipeDown())
|
||||
onView(withId(R.id.refresh)).check(matches(isRefreshing()))
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package utils
|
||||
|
||||
import android.view.View
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import androidx.test.espresso.matcher.BoundedMatcher
|
||||
import org.hamcrest.Description
|
||||
import org.hamcrest.Matcher
|
||||
|
||||
object SwipeRefreshLayoutMatchers {
|
||||
|
||||
@JvmStatic
|
||||
fun isRefreshing(): Matcher<View> {
|
||||
return object : BoundedMatcher<View, SwipeRefreshLayout>(
|
||||
SwipeRefreshLayout::class.java) {
|
||||
|
||||
override fun describeTo(description: Description) {
|
||||
description.appendText("is refreshing")
|
||||
}
|
||||
|
||||
override fun matchesSafely(view: SwipeRefreshLayout): Boolean {
|
||||
return view.isRefreshing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
|
BIN
app/src/main/ic_launcher-playstore.png
Normal file
After Width: | Height: | Size: 32 KiB |
@ -1,6 +1,6 @@
|
||||
package ru.myitschool.work.core
|
||||
// БЕРИТЕ И ИЗМЕНЯЙТЕ ХОСТ ТОЛЬКО ЗДЕСЬ И НЕ БЕРИТЕ ИЗ ДРУГИХ МЕСТ. ФАЙЛ ПЕРЕМЕЩАТЬ НЕЛЬЗЯ
|
||||
object Constants {
|
||||
const val SERVER_ADDRESS = "http://192.168.1.103:8080"
|
||||
const val SERVER_ADDRESS = "http://10.6.66.74:8080"
|
||||
const val TOKEN_KEY = "token"
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package ru.myitschool.work.data
|
||||
|
||||
import ru.myitschool.work.data.local.CredentialsLocalDataSource
|
||||
import ru.myitschool.work.data.network.AdminNetworkDataSource
|
||||
import ru.myitschool.work.domain.admin.AdminRepository
|
||||
|
||||
class AdminRepositoryImpl(
|
||||
private val networkDataSource: AdminNetworkDataSource,
|
||||
private val localCredentialsLocalDataSource: CredentialsLocalDataSource
|
||||
) : AdminRepository {
|
||||
override suspend fun blockUser(login: String): Result<Unit> {
|
||||
return networkDataSource.blockUser(login, localCredentialsLocalDataSource.getToken())
|
||||
}
|
||||
|
||||
override suspend fun unblockUser(login: String): Result<Unit> {
|
||||
return networkDataSource.unblockUser(login, localCredentialsLocalDataSource.getToken())
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package ru.myitschool.work.data
|
||||
|
||||
import android.util.Log
|
||||
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<List<PassEntity>> {
|
||||
return map(
|
||||
networkDataSource.getCurrentPasses(
|
||||
pageNum,
|
||||
pageSize,
|
||||
credentialsLocalDataSource.getToken()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getUsersPasses(
|
||||
pageNum: Int,
|
||||
pageSize: Int,
|
||||
login: String
|
||||
): Result<List<PassEntity>> {
|
||||
return map(
|
||||
networkDataSource.getUsersPasses(
|
||||
login = login,
|
||||
pageNum = pageNum,
|
||||
pageSize = pageSize,
|
||||
token = credentialsLocalDataSource.getToken()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun map(listDto: Result<List<PassDto>>): Result<List<PassEntity>> {
|
||||
return listDto.map { successListDto ->
|
||||
successListDto.mapNotNull { dto ->
|
||||
PassEntity(
|
||||
type = dto.terminal?.type ?: return@mapNotNull null,
|
||||
name = dto.terminal.name ?: return@mapNotNull null,
|
||||
time = dto.time ?: return@mapNotNull null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package ru.myitschool.work.data
|
||||
|
||||
import ru.myitschool.work.data.local.CredentialsLocalDataSource
|
||||
import ru.myitschool.work.data.network.QrNetworkDataSource
|
||||
import ru.myitschool.work.domain.entities.QrEntity
|
||||
import ru.myitschool.work.domain.qr.QrRepository
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package ru.myitschool.work.data
|
||||
|
||||
import android.util.Log
|
||||
import ru.myitschool.work.data.dto.UserDto
|
||||
import ru.myitschool.work.data.local.CredentialsLocalDataSource
|
||||
import ru.myitschool.work.data.local.UserLocalDataSource
|
||||
@ -19,16 +18,29 @@ class UserRepositoryImpl(
|
||||
|
||||
return runCatching {
|
||||
networkDataSource.login(credentialsLocalDataSource.updateToken(login, password))
|
||||
.onSuccess { dto ->
|
||||
map(dto).onSuccess { userLocalDataSource.cacheData(it) }
|
||||
}
|
||||
.fold(
|
||||
onSuccess = { dto ->
|
||||
map(dto).fold(
|
||||
onSuccess = {
|
||||
userLocalDataSource.cacheData(it)
|
||||
},
|
||||
onFailure = { error(it) }
|
||||
)
|
||||
},
|
||||
onFailure = { error(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun authorize(token: String): Result<Unit> {
|
||||
return networkDataSource.login(token).fold(
|
||||
onSuccess = { Result.success(Unit) },
|
||||
onFailure = { error -> Result.failure(error) }
|
||||
onSuccess = { dto ->
|
||||
map(dto).fold(
|
||||
onSuccess = { Result.success(userLocalDataSource.cacheData(it)) },
|
||||
onFailure = { Result.failure(it) }
|
||||
)
|
||||
},
|
||||
onFailure = { Result.failure(it) }
|
||||
)
|
||||
}
|
||||
|
||||
@ -45,15 +57,27 @@ class UserRepositoryImpl(
|
||||
return userLocalDataSource.getUser()!!
|
||||
}
|
||||
|
||||
private fun map(userDto: UserDto): Result<UserEntity> {
|
||||
private suspend fun map(userDto: UserDto): Result<UserEntity> {
|
||||
return runCatching {
|
||||
UserEntity(
|
||||
id = userDto.id ?: error("Null user id"),
|
||||
name = userDto.name ?: error("Null user name"),
|
||||
lastVisit = userDto.lastVisit ?: error("Null user lastVisit"),
|
||||
lastVisit = userDto.lastVisit ?: "",
|
||||
photoUrl = userDto.photoUrl ?: error("Null user photoUrl"),
|
||||
position = userDto.position ?: error("Null user position")
|
||||
position = userDto.position ?: error("Null user position"),
|
||||
isAdmin = userDto.roleId?.let { it ->
|
||||
networkDataSource.isRoleHasAdminPermissions(
|
||||
it,
|
||||
credentialsLocalDataSource.getToken()
|
||||
).fold(onSuccess = { it }, onFailure = { error(it) })
|
||||
} ?: error("Null user roleId"),
|
||||
isCardBlocked = userDto.isCardBlocked ?: error("Null user isCardBlocked")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getUserByLogin(login: String): Result<UserEntity> {
|
||||
return networkDataSource.getUserByLogin(login, credentialsLocalDataSource.getToken()).fold(
|
||||
onSuccess = { map(it) }, onFailure = { Result.failure(it) }
|
||||
)
|
||||
}
|
||||
}
|
21
app/src/main/java/ru/myitschool/work/data/dto/PassDto.kt
Normal file
@ -0,0 +1,21 @@
|
||||
package ru.myitschool.work.data.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
@Serializable
|
||||
data class PassDto(
|
||||
@SerialName("localDateTime")
|
||||
val time: String?,
|
||||
@SerialName("terminal")
|
||||
val terminal: TerminalDto?
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TerminalDto(
|
||||
@SerialName("type")
|
||||
val type: String?,
|
||||
@SerialName("name")
|
||||
val name: String?
|
||||
)
|
@ -1,31 +0,0 @@
|
||||
package ru.myitschool.work.data.dto;
|
||||
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import kotlinx.serialization.Serializable;
|
||||
|
||||
@Serializable
|
||||
public class UserDto {
|
||||
|
||||
@Nullable
|
||||
@SerializedName("id")
|
||||
public String id;
|
||||
@Nullable
|
||||
@SerializedName("name")
|
||||
public String name;
|
||||
@Nullable
|
||||
@SerializedName("lastVisit")
|
||||
public String lastVisit;
|
||||
@Nullable
|
||||
@SerializedName("photo")
|
||||
public String photoUrl;
|
||||
@Nullable
|
||||
@SerializedName("position")
|
||||
public String position;
|
||||
@Nullable
|
||||
@SerializedName("login")
|
||||
public String login;
|
||||
}
|
22
app/src/main/java/ru/myitschool/work/data/dto/UserDto.kt
Normal file
@ -0,0 +1,22 @@
|
||||
package ru.myitschool.work.data.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class UserDto(
|
||||
@SerialName("authority_id")
|
||||
val roleId: Int?,
|
||||
@SerialName("name")
|
||||
val name: String?,
|
||||
@SerialName("lastVisit")
|
||||
val lastVisit: String?,
|
||||
@SerialName("photo")
|
||||
val photoUrl: String?,
|
||||
@SerialName("position")
|
||||
val position: String?,
|
||||
@SerialName("username")
|
||||
val login: String?,
|
||||
@SerialName("isCardBlocked")
|
||||
val isCardBlocked: Boolean?
|
||||
)
|
@ -0,0 +1,44 @@
|
||||
package ru.myitschool.work.data.network
|
||||
|
||||
import io.ktor.client.request.headers
|
||||
import io.ktor.client.request.patch
|
||||
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
|
||||
|
||||
object AdminNetworkDataSource {
|
||||
|
||||
private val client = KtorClient.client
|
||||
|
||||
suspend fun blockUser(login: String, token: String): Result<Unit> =
|
||||
withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val response = client.patch("$SERVER_ADDRESS/api/users/block?username=$login") {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, token)
|
||||
}
|
||||
}
|
||||
|
||||
if (response.status != HttpStatusCode.OK)
|
||||
error("Status ${response.status}")
|
||||
Unit
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun unblockUser(login: String, token: String): Result<Unit> =
|
||||
withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val response = client.patch("$SERVER_ADDRESS/api/users/unblock?username=$login") {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, token)
|
||||
}
|
||||
}
|
||||
|
||||
if (response.status != HttpStatusCode.OK)
|
||||
error("Status ${response.status}")
|
||||
Unit
|
||||
}
|
||||
}
|
||||
}
|
@ -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<List<PassDto>> =
|
||||
withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val response =
|
||||
client.get("$SERVER_ADDRESS/api/passes/paginated/?page=$pageNum&size=$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<List<PassDto>> = 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()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package ru.myitschool.work.data
|
||||
package ru.myitschool.work.data.network
|
||||
|
||||
import io.ktor.client.request.headers
|
||||
import io.ktor.client.request.patch
|
||||
@ -11,28 +11,25 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import ru.myitschool.work.core.Constants
|
||||
import ru.myitschool.work.data.dto.QrDto
|
||||
import ru.myitschool.work.data.network.KtorClient
|
||||
import ru.myitschool.work.domain.entities.QrEntity
|
||||
|
||||
object QrNetworkDataSource {
|
||||
|
||||
suspend fun pushQr(qrEntity: QrEntity, token: String): Result<Unit> = withContext(Dispatchers.IO) {
|
||||
|
||||
runCatching {
|
||||
|
||||
val response = KtorClient.client.patch("${Constants.SERVER_ADDRESS}/api/push_qr") {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, token)
|
||||
}
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(
|
||||
QrDto(code = qrEntity.code)
|
||||
)
|
||||
suspend fun pushQr(qrEntity: QrEntity, token: String): Result<Unit> =
|
||||
withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
|
||||
val response =
|
||||
KtorClient.client.patch("${Constants.SERVER_ADDRESS}/api/open") {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, token)
|
||||
}
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(QrDto(qrEntity.code))
|
||||
}
|
||||
if (response.status != HttpStatusCode.OK)
|
||||
error("Status ${response.status}")
|
||||
Unit
|
||||
}
|
||||
if (response.status != HttpStatusCode.OK)
|
||||
error("Status ${response.status}")
|
||||
Unit
|
||||
}
|
||||
}
|
||||
}
|
@ -22,16 +22,45 @@ object UserNetworkDataSource {
|
||||
suspend fun login(token: String): Result<UserDto> =
|
||||
withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val result = KtorClient.client.get("${Constants.SERVER_ADDRESS}/api/users/login") {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, token)
|
||||
val result =
|
||||
KtorClient.client.get("${Constants.SERVER_ADDRESS}/api/users/login") {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, token)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.status != HttpStatusCode.OK)
|
||||
error("Status ${result.status}")
|
||||
result.body()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getUserByLogin() {}
|
||||
suspend fun isRoleHasAdminPermissions(roleId: Int, token: String): Result<Boolean> =
|
||||
withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val response =
|
||||
KtorClient.client.get("${Constants.SERVER_ADDRESS}/api/authority/$roleId") {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, token)
|
||||
}
|
||||
}
|
||||
|
||||
response.status == HttpStatusCode.OK
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getUserByLogin(login: String, token: String): Result<UserDto> =
|
||||
withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val response =
|
||||
KtorClient.client.get("${Constants.SERVER_ADDRESS}/api/users/get?username=${login}") {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, token)
|
||||
}
|
||||
}
|
||||
|
||||
if (response.status != HttpStatusCode.OK)
|
||||
error("Status ${response.status}")
|
||||
response.body()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package ru.myitschool.work.domain.admin
|
||||
|
||||
interface AdminRepository {
|
||||
suspend fun blockUser(login: String): Result<Unit>
|
||||
suspend fun unblockUser(login: String): Result<Unit>
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package ru.myitschool.work.domain.admin
|
||||
|
||||
class BlockUserUseCase(
|
||||
private val repository: AdminRepository
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(login: String) = repository.blockUser(login)
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package ru.myitschool.work.domain.admin
|
||||
|
||||
class UnblockUserUseCase(
|
||||
private val repository: AdminRepository
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(login: String) = repository.unblockUser(login)
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package ru.myitschool.work.domain.entities
|
||||
|
||||
data class PassEntity(
|
||||
val type: String,
|
||||
val name: String,
|
||||
val time: String
|
||||
)
|
@ -1,9 +1,10 @@
|
||||
package ru.myitschool.work.domain.entities
|
||||
|
||||
data class UserEntity(
|
||||
val id: String,
|
||||
val isAdmin: Boolean,
|
||||
val name: String,
|
||||
val lastVisit: String,
|
||||
val photoUrl: String,
|
||||
val position: String,
|
||||
val isCardBlocked: Boolean
|
||||
)
|
||||
|
@ -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<List<PassEntity>> =
|
||||
repository.getCurrentPasses(pageNum, pageSize)
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package ru.myitschool.work.domain.passes
|
||||
|
||||
class GetUsersPassesUseCase(
|
||||
private val repository: PassRepository
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(pageNum: Int, pageSize: Int, login: String) =
|
||||
repository.getUsersPasses(pageNum, pageSize, login)
|
||||
}
|
@ -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<List<PassEntity>>
|
||||
suspend fun getUsersPasses(pageNum: Int, pageSize: Int, login: String): Result<List<PassEntity>>
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package ru.myitschool.work.domain.user
|
||||
|
||||
import ru.myitschool.work.domain.entities.UserEntity
|
||||
|
||||
class GetUserByLoginUseCase(
|
||||
private val repository: UserRepository
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(login: String): Result<UserEntity> = repository.getUserByLogin(login)
|
||||
}
|
@ -5,4 +5,5 @@ import ru.myitschool.work.domain.entities.UserEntity
|
||||
interface UserRepository {
|
||||
|
||||
suspend fun getCurrentUser(): UserEntity
|
||||
suspend fun getUserByLogin(login: String) : Result<UserEntity>
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -4,13 +4,18 @@ import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import ru.myitschool.work.R
|
||||
import ru.myitschool.work.utils.isOnline
|
||||
|
||||
// НЕ ИЗМЕНЯЙТЕ НАЗВАНИЕ КЛАССА!
|
||||
@AndroidEntryPoint
|
||||
class RootActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_root)
|
||||
|
||||
if (!isOnline(this)) {
|
||||
val dialog = NoInternetNotificationFragment()
|
||||
dialog.isCancelable = false
|
||||
dialog.show(supportFragmentManager, "NO_INTERNET")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package ru.myitschool.work.ui.admin.search
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import ru.myitschool.work.R
|
||||
import ru.myitschool.work.databinding.FragmentFindEmployeeBinding
|
||||
import ru.myitschool.work.utils.collectWithLifecycle
|
||||
|
||||
class AdminFragment : Fragment(R.layout.fragment_find_employee) {
|
||||
|
||||
private var _binding: FragmentFindEmployeeBinding? = null
|
||||
private val binding: FragmentFindEmployeeBinding get() = _binding!!
|
||||
|
||||
private val viewModel by viewModels<AdminViewModel> { AdminViewModel.Factory }
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
_binding = FragmentFindEmployeeBinding.bind(view)
|
||||
|
||||
viewModel.state.collectWithLifecycle(this) { state ->
|
||||
binding.username.isEnabled = state !is AdminViewModel.State.Loading
|
||||
when (state) {
|
||||
is AdminViewModel.State.Error -> binding.error.text = state.errorMessage
|
||||
is AdminViewModel.State.Loading -> Unit
|
||||
is AdminViewModel.State.Show -> findNavController().navigate(R.id.action_adminFragment_to_viewUserAsAdminFragment)
|
||||
is AdminViewModel.State.Waiting -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
binding.find.setOnClickListener {
|
||||
viewModel.onFind(binding.username.text.toString())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
_binding = null
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
package ru.myitschool.work.ui.admin.search
|
||||
|
||||
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.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.myitschool.work.data.AdminRepositoryImpl
|
||||
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.AdminNetworkDataSource
|
||||
import ru.myitschool.work.data.network.PassNetworkDataSource
|
||||
import ru.myitschool.work.data.network.UserNetworkDataSource
|
||||
import ru.myitschool.work.domain.admin.BlockUserUseCase
|
||||
import ru.myitschool.work.domain.admin.UnblockUserUseCase
|
||||
import ru.myitschool.work.domain.entities.PassEntity
|
||||
import ru.myitschool.work.domain.entities.UserEntity
|
||||
import ru.myitschool.work.domain.passes.GetUsersPassesUseCase
|
||||
import ru.myitschool.work.domain.user.GetUserByLoginUseCase
|
||||
import ru.myitschool.work.ui.admin.view.UsersPassesPagingSource
|
||||
|
||||
class AdminViewModel(
|
||||
private val getUserByLoginUseCase: GetUserByLoginUseCase,
|
||||
private val getUsersPassesUseCase: GetUsersPassesUseCase,
|
||||
private val unBlockUserUseCase: UnblockUserUseCase,
|
||||
private val blockUserUseCase: BlockUserUseCase
|
||||
) : ViewModel() {
|
||||
|
||||
private val _state = MutableStateFlow<State>(State.Waiting)
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
// private var _listState: Flow<PagingData<PassEntity>>? = null
|
||||
// val listState: Flow<PagingData<PassEntity>> get() = _listState!!
|
||||
|
||||
private var currentLogin: String = "pivanov"
|
||||
|
||||
fun onFind(login: String) {
|
||||
viewModelScope.launch {
|
||||
_state.emit(State.Loading)
|
||||
getUserByLoginUseCase(login).fold(
|
||||
onSuccess = { data ->
|
||||
currentLogin = login
|
||||
_state.emit(State.Show(data))
|
||||
setUpPager(login)
|
||||
},
|
||||
onFailure = { _state.emit(State.Error(it.message.toString())) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onRefresh() {
|
||||
updateState()
|
||||
}
|
||||
|
||||
private fun updateState() {
|
||||
viewModelScope.launch {
|
||||
_state.emit(State.Loading)
|
||||
getUserByLoginUseCase(currentLogin!!).fold(
|
||||
onSuccess = { _state.emit(State.Show(it)) },
|
||||
onFailure = { _state.emit(State.Error(it.message.toString())) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setUpPager(login: String) {
|
||||
// _listState = Pager(
|
||||
// config = PagingConfig(
|
||||
// pageSize = 10,
|
||||
// enablePlaceholders = false,
|
||||
// maxSize = 50
|
||||
// )
|
||||
// ) {
|
||||
// UsersPassesPagingSource(getUsersPassesUseCase::invoke, login)
|
||||
// }.flow
|
||||
// .cachedIn(viewModelScope)
|
||||
}
|
||||
|
||||
fun onBlock() {
|
||||
viewModelScope.launch {
|
||||
blockUserUseCase(currentLogin!!)
|
||||
}
|
||||
}
|
||||
|
||||
fun unblock() {
|
||||
viewModelScope.launch {
|
||||
unBlockUserUseCase(currentLogin!!)
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface State {
|
||||
data class Show(val user: UserEntity) : State
|
||||
data object Waiting : State
|
||||
data object Loading : State
|
||||
data class Error(val errorMessage: String) : State
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
|
||||
val adminRepository = AdminRepositoryImpl(
|
||||
networkDataSource = AdminNetworkDataSource,
|
||||
localCredentialsLocalDataSource = CredentialsLocalDataSource.getInstance()
|
||||
)
|
||||
return AdminViewModel(
|
||||
getUserByLoginUseCase = GetUserByLoginUseCase(
|
||||
repository = UserRepositoryImpl(
|
||||
credentialsLocalDataSource = CredentialsLocalDataSource.getInstance(),
|
||||
userLocalDataSource = UserLocalDataSource,
|
||||
networkDataSource = UserNetworkDataSource
|
||||
)
|
||||
),
|
||||
getUsersPassesUseCase = GetUsersPassesUseCase(
|
||||
repository = PassRepositoryImpl(
|
||||
networkDataSource = PassNetworkDataSource,
|
||||
credentialsLocalDataSource = CredentialsLocalDataSource.getInstance()
|
||||
)
|
||||
),
|
||||
blockUserUseCase = BlockUserUseCase(
|
||||
repository = adminRepository
|
||||
),
|
||||
unBlockUserUseCase = UnblockUserUseCase(
|
||||
repository = adminRepository
|
||||
)
|
||||
) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package ru.myitschool.work.ui.admin.view
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import ru.myitschool.work.domain.entities.PassEntity
|
||||
|
||||
class UsersPassesPagingSource(
|
||||
private val request: suspend (pageNum: Int, pageSize: Int, login: String) -> Result<List<PassEntity>>,
|
||||
private val login: String
|
||||
) : PagingSource<Int, PassEntity>() {
|
||||
override fun getRefreshKey(state: PagingState<Int, PassEntity>): 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, PassEntity> {
|
||||
val pageNum = params.key ?: 0
|
||||
return request.invoke(pageNum, params.loadSize, login).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) }
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package ru.myitschool.work.ui.admin.view
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.paging.LoadState
|
||||
import com.squareup.picasso.Picasso
|
||||
import ru.myitschool.work.R
|
||||
import ru.myitschool.work.databinding.FragmentUserBinding
|
||||
import ru.myitschool.work.ui.admin.search.AdminViewModel
|
||||
import ru.myitschool.work.ui.profile.PassesListAdapter
|
||||
import ru.myitschool.work.utils.collectWithLifecycle
|
||||
import ru.myitschool.work.utils.visibleOrGone
|
||||
|
||||
class ViewUserAsAdminFragment : Fragment(R.layout.fragment_user) {
|
||||
|
||||
private var _binding: FragmentUserBinding? = null
|
||||
private val binding: FragmentUserBinding get() = _binding!!
|
||||
|
||||
private val viewModel by viewModels<AdminViewModel> { AdminViewModel.Factory }
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
_binding = FragmentUserBinding.bind(view)
|
||||
|
||||
binding.findUser.visibleOrGone(false)
|
||||
binding.logout.visibleOrGone(false)
|
||||
|
||||
viewModel.state.collectWithLifecycle(this) { state ->
|
||||
binding.refresh.isRefreshing = state is AdminViewModel.State.Loading
|
||||
binding.content.visibleOrGone(state is AdminViewModel.State.Show)
|
||||
|
||||
when (state) {
|
||||
is AdminViewModel.State.Loading -> Unit
|
||||
is AdminViewModel.State.Show -> {
|
||||
val user = state.user
|
||||
binding.block.visibleOrGone(!user.isCardBlocked)
|
||||
binding.unblock.visibleOrGone(user.isCardBlocked)
|
||||
binding.fullname.text = user.name
|
||||
binding.position.text = user.position
|
||||
binding.lastEntry.text = user.lastVisit
|
||||
Picasso.get().load(user.photoUrl).into(binding.photo)
|
||||
}
|
||||
|
||||
is AdminViewModel.State.Error -> binding.error.text = state.errorMessage
|
||||
is AdminViewModel.State.Waiting -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
// viewModel.listState.collectWithLifecycle(this) { listState ->
|
||||
// adapter.submitData(listState)
|
||||
// }
|
||||
|
||||
// adapter.loadStateFlow.collectWithLifecycle(this) { data ->
|
||||
// val dataState = data.refresh
|
||||
// binding.refresh.isRefreshing = dataState is LoadState.Loading
|
||||
// binding.error.visibleOrGone(dataState is LoadState.Error)
|
||||
//
|
||||
// if (dataState is LoadState.Error) {
|
||||
// binding.error.text = dataState.error.toString()
|
||||
// }
|
||||
// }
|
||||
|
||||
binding.refresh.setOnRefreshListener {
|
||||
viewModel.onRefresh()
|
||||
// adapter.refresh()
|
||||
}
|
||||
|
||||
|
||||
binding.block.setOnClickListener {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle("Блокировка доступа")
|
||||
.setMessage("Вы уверены, что хотите заблокировать доступ работнику: ${binding.fullname}?")
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.onBlock() }
|
||||
.show()
|
||||
}
|
||||
|
||||
binding.unblock.setOnClickListener {
|
||||
viewModel.unblock()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
_binding = null
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
package ru.myitschool.work.ui.login
|
||||
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
@ -32,11 +33,18 @@ class LoginFragment : Fragment(R.layout.fragment_login) {
|
||||
binding.error.visibleOrGone(state is LoginViewModel.State.Error)
|
||||
|
||||
when (state) {
|
||||
is LoginViewModel.State.Error -> binding.error.text = state.errorMessage
|
||||
is LoginViewModel.State.Loading -> Unit
|
||||
is LoginViewModel.State.Error -> {
|
||||
binding.error.text = state.errorMessage
|
||||
setButtonActive()
|
||||
}
|
||||
is LoginViewModel.State.Loading -> setButtonInactive()
|
||||
is LoginViewModel.State.Waiting -> Unit
|
||||
is LoginViewModel.State.LoginCheckCompleted -> binding.login.isEnabled =
|
||||
state.isCompleted
|
||||
is LoginViewModel.State.LoginCheckCompleted -> {
|
||||
state.isCompleted.let {
|
||||
if (it) setButtonActive() else setButtonInactive()
|
||||
binding.login.isEnabled = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,6 +65,18 @@ class LoginFragment : Fragment(R.layout.fragment_login) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun setButtonInactive() {
|
||||
binding.login.setTextColor(Color.BLACK)
|
||||
binding.login.background =
|
||||
ContextCompat.getDrawable(requireContext(), R.drawable.inactive_button)
|
||||
}
|
||||
|
||||
private fun setButtonActive() {
|
||||
binding.login.setTextColor(Color.WHITE)
|
||||
binding.login.background =
|
||||
ContextCompat.getDrawable(requireContext(), R.drawable.main_button)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
_binding = null
|
||||
super.onDestroy()
|
||||
|
@ -9,14 +9,10 @@ import androidx.navigation.fragment.findNavController
|
||||
import ru.myitschool.work.R
|
||||
import ru.myitschool.work.core.Constants
|
||||
import ru.myitschool.work.data.local.CredentialsLocalDataSource
|
||||
import ru.myitschool.work.databinding.FragmentSplashBinding
|
||||
import ru.myitschool.work.utils.collectWithLifecycle
|
||||
|
||||
class SplashFragment : Fragment(R.layout.fragment_splash) {
|
||||
|
||||
private var _binding: FragmentSplashBinding? = null
|
||||
private val binding: FragmentSplashBinding get() = _binding!!
|
||||
|
||||
private val viewModel by viewModels<LoginViewModel> { LoginViewModel.Factory }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@ -30,21 +26,15 @@ class SplashFragment : Fragment(R.layout.fragment_splash) {
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
_binding = FragmentSplashBinding.bind(view)
|
||||
|
||||
viewModel.action.collectWithLifecycle(this) { action ->
|
||||
val navController = findNavController()
|
||||
when (action) {
|
||||
is LoginViewModel.Action.GoToLogin -> navController.navigate(R.id.loginFragment)
|
||||
is LoginViewModel.Action.OpenApp ->
|
||||
navController.navigate(R.id.action_loginFragment_to_userFragment)
|
||||
navController.navigate(R.id.action_splashFragment_to_userFragment)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
_binding = null
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package ru.myitschool.work.ui.profile
|
||||
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.paging.PagingDataAdapter
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import ru.myitschool.work.databinding.PassItemBinding
|
||||
import ru.myitschool.work.domain.entities.PassEntity
|
||||
|
||||
class PassesListAdapter:
|
||||
ListAdapter<PassEntity, PassesListAdapter.ViewHolder>(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<PassEntity>() {
|
||||
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))
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package ru.myitschool.work.ui.profile
|
||||
|
||||
import android.util.Log
|
||||
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<List<PassEntity>>
|
||||
) : PagingSource<Int, PassEntity>() {
|
||||
override fun getRefreshKey(state: PagingState<Int, PassEntity>): 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, PassEntity> {
|
||||
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) }
|
||||
)
|
||||
}
|
||||
}
|
@ -1,17 +1,21 @@
|
||||
package ru.myitschool.work.ui.profile
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.squareup.picasso.Picasso
|
||||
import ru.myitschool.work.R
|
||||
import ru.myitschool.work.databinding.FragmentUserBinding
|
||||
import ru.myitschool.work.ui.qr.result.RESPONSE_KEY
|
||||
import ru.myitschool.work.ui.qr.scan.QrScanDestination
|
||||
import ru.myitschool.work.utils.collectWithLifecycle
|
||||
import ru.myitschool.work.utils.visibleOrGone
|
||||
|
||||
|
||||
class UserFragment : Fragment(R.layout.fragment_user) {
|
||||
|
||||
private var _binding: FragmentUserBinding? = null
|
||||
@ -22,30 +26,59 @@ class UserFragment : Fragment(R.layout.fragment_user) {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
_binding = FragmentUserBinding.bind(view)
|
||||
|
||||
val adapter = PassesListAdapter()
|
||||
binding.passes.adapter = adapter
|
||||
|
||||
viewModel.state.collectWithLifecycle(this) { state ->
|
||||
(binding.refresh as SwipeRefreshLayout).isRefreshing =
|
||||
state is UserViewModel.State.Loading
|
||||
binding.content?.visibleOrGone(state is UserViewModel.State.Show)
|
||||
binding.refresh.isRefreshing = state is UserViewModel.State.Loading
|
||||
binding.content.visibleOrGone(state is UserViewModel.State.Show)
|
||||
when (state) {
|
||||
is UserViewModel.State.Loading -> Unit
|
||||
is UserViewModel.State.Show -> {
|
||||
val user = state.userEntity
|
||||
adapter.submitList(state.passes)
|
||||
binding.scan.visibleOrGone(!user.isCardBlocked)
|
||||
binding.findUser.visibleOrGone(user.isAdmin)
|
||||
binding.fullname.text = user.name
|
||||
binding.position.text = user.position
|
||||
binding.lastEntry.text = user.lastVisit
|
||||
Picasso.get().load(user.photoUrl).into(binding.photo)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
(binding.refresh as SwipeRefreshLayout).setOnRefreshListener {
|
||||
viewModel.onRefresh()
|
||||
}
|
||||
|
||||
binding.logout.setOnClickListener {
|
||||
viewModel.onLogout()
|
||||
findNavController().navigate(R.id.action_userFragment_to_loginFragment)
|
||||
}
|
||||
}
|
||||
|
||||
binding.refresh.setOnRefreshListener {
|
||||
viewModel.onRefresh()
|
||||
}
|
||||
|
||||
binding.logout.setOnClickListener {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle("Выход")
|
||||
.setMessage("Вы уверены, что хотите выйти из аккаунта?")
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
viewModel.onLogout()
|
||||
findNavController().navigate(R.id.action_userFragment_to_loginFragment)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
binding.findUser.setOnClickListener {
|
||||
findNavController().navigate(R.id.action_userFragment_to_adminFragment)
|
||||
}
|
||||
|
||||
binding.scan.setOnClickListener {
|
||||
findNavController().navigate(R.id.action_userFragment_to_qrScanFragment)
|
||||
}
|
||||
|
||||
parentFragmentManager.setFragmentResultListener(
|
||||
QrScanDestination.REQUEST_KEY, this
|
||||
) { _, result ->
|
||||
parentFragmentManager.setFragmentResult(RESPONSE_KEY, result)
|
||||
findNavController().navigate(R.id.action_userFragment_to_qrResultFragment)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -1,35 +1,63 @@
|
||||
package ru.myitschool.work.ui.profile
|
||||
|
||||
import android.util.Log
|
||||
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.PassEntity
|
||||
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>(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()
|
||||
}
|
||||
|
||||
private fun updateState() {
|
||||
viewModelScope.launch {
|
||||
State.Show(getCurrentUserUseCase())
|
||||
_state.emit(State.Loading)
|
||||
|
||||
_state.emit(
|
||||
State.Show(
|
||||
getCurrentUserUseCase(),
|
||||
getCurrentPassesUseCase(0, 30).getOrNull()!!
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,7 +71,7 @@ class UserViewModel(
|
||||
|
||||
sealed interface State {
|
||||
data object Loading : State
|
||||
data class Show(val userEntity: UserEntity) : State
|
||||
data class Show(val userEntity: UserEntity, val passes: List<PassEntity>) : State
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ -57,7 +85,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
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import ru.myitschool.work.R
|
||||
import ru.myitschool.work.databinding.FragmentQrResultBinding
|
||||
import ru.myitschool.work.ui.qr.scan.QrScanDestination
|
||||
import ru.myitschool.work.domain.entities.QrEntity
|
||||
import ru.myitschool.work.ui.qr.scan.QrScanDestination.getDataIfExist
|
||||
import ru.myitschool.work.utils.collectWithLifecycle
|
||||
|
||||
@ -20,25 +20,18 @@ class QrResultFragment : Fragment(R.layout.fragment_qr_result) {
|
||||
private var _binding: FragmentQrResultBinding? = null
|
||||
private val binding: FragmentQrResultBinding get() = _binding!!
|
||||
|
||||
private var _resultQr: String? = null
|
||||
private val resultQr: String = _resultQr!!
|
||||
|
||||
private val viewModel by viewModels<QrResultViewModel> { QrResultViewModel.Factory }
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
_binding = FragmentQrResultBinding.bind(view)
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
_resultQr = savedInstanceState.getString(QrScanDestination.REQUEST_KEY)
|
||||
}
|
||||
|
||||
parentFragmentManager.setFragmentResultListener(RESPONSE_KEY, this) { _, result ->
|
||||
_resultQr = getDataIfExist(result)
|
||||
viewModel.update(resultQr)
|
||||
getDataIfExist(result)?.let { viewModel.setQr(QrEntity(it)) }
|
||||
viewModel.update()
|
||||
}
|
||||
|
||||
viewModel.state.collectWithLifecycle(this) { state ->
|
||||
if (_resultQr == null) {
|
||||
if (viewModel._qrEntity == null) {
|
||||
binding.result.setText(R.string.door_closed)
|
||||
binding.close.background =
|
||||
ContextCompat.getDrawable(requireContext(), R.drawable.warn_button)
|
||||
@ -65,12 +58,6 @@ class QrResultFragment : Fragment(R.layout.fragment_qr_result) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putString(QrScanDestination.REQUEST_KEY, resultQr)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
_binding = null
|
||||
super.onDestroy()
|
||||
|
@ -7,7 +7,7 @@ import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.myitschool.work.data.QrNetworkDataSource
|
||||
import ru.myitschool.work.data.network.QrNetworkDataSource
|
||||
import ru.myitschool.work.data.QrRepositoryImpl
|
||||
import ru.myitschool.work.data.local.CredentialsLocalDataSource
|
||||
import ru.myitschool.work.domain.entities.QrEntity
|
||||
@ -20,9 +20,16 @@ class QrResultViewModel(
|
||||
private val _state = MutableStateFlow<State>(State.Loading)
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
fun update(qrValue: String) {
|
||||
var _qrEntity: QrEntity? = null
|
||||
private val qrEntity: QrEntity get() = _qrEntity!!
|
||||
|
||||
fun setQr(qrEntity: QrEntity) {
|
||||
_qrEntity = qrEntity
|
||||
}
|
||||
|
||||
fun update() {
|
||||
viewModelScope.launch {
|
||||
pushQrUseCase(QrEntity(code = qrValue)).fold(
|
||||
pushQrUseCase(qrEntity).fold(
|
||||
onSuccess = { _state.emit(State.Show) },
|
||||
onFailure = { _state.emit(State.Error(it.message.toString())) }
|
||||
)
|
||||
|
@ -0,0 +1,22 @@
|
||||
package ru.myitschool.work.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
|
||||
fun isOnline(context: Context): Boolean {
|
||||
val connectivityManager =
|
||||
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
val capabilities =
|
||||
connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
|
||||
if (capabilities != null) {
|
||||
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
|
||||
return true
|
||||
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
|
||||
return true
|
||||
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -1,18 +1,19 @@
|
||||
package ru.myitschool.work.utils
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.flowWithLifecycle
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
inline fun <T> Flow<T>.collectWithLifecycle(
|
||||
fun <T> Flow<T>.collectWithLifecycle(
|
||||
fragment: Fragment,
|
||||
crossinline collector: (T) -> Unit
|
||||
function: suspend (T) -> Unit
|
||||
) {
|
||||
fragment.viewLifecycleOwner.lifecycleScope.launch {
|
||||
flowWithLifecycle(fragment.viewLifecycleOwner.lifecycle).collect { value ->
|
||||
collector(value)
|
||||
fragment.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
collect { function.invoke(it) }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,170 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
android:height="108dp"
|
||||
android:width="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
</vector>
|
||||
|
@ -1,30 +1,26 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="221"
|
||||
android:viewportHeight="221">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h221v221h-221z"/>
|
||||
<path
|
||||
android:pathData="M0,0h221v221h-221z"
|
||||
android:fillColor="#001A8C"/>
|
||||
<path
|
||||
android:pathData="M66.3,0.4V19.58H39.06L39.06,81.83H66.3L66.3,101H-7.2V81.83H19.89L19.89,19.58H-7.2V0.4H66.3ZM100.84,77.79V19.58H92.92L92.92,101H73.75L73.75,23.61C73.75,10.73 84.08,0.4 96.96,0.4C103.09,0.4 108.99,2.81 113.26,7.24C117.61,11.5 120.01,17.48 120.01,23.61V81.83H128.09V0.4H147.26V77.79C147.26,90.68 136.78,101 124.05,101C117.92,101 112.02,98.59 107.67,94.17C103.4,89.9 100.84,83.92 100.84,77.79ZM177.99,73.91C165.11,73.91 154.71,63.43 154.71,50.62V0.4H173.88V54.74H177.68C183.89,54.74 189.79,57.14 194.21,61.49C198.56,65.92 200.97,71.82 200.97,78.02V81.83H209.04V0.4H228.21V77.79C228.21,90.68 217.73,101 205,101C192.27,101 181.8,90.68 181.8,77.79V73.91H177.99ZM-51.58,175.74H-20.45V167.67H-51.58C-64.46,167.67 -74.79,157.34 -74.79,144.61C-74.79,131.73 -64.46,121.4 -51.58,121.4H25.81L25.81,140.58H-55.61V148.49H-24.49C-11.76,148.49 -1.28,158.97 -1.28,171.7C-1.28,184.43 -11.76,194.91 -24.49,194.91H-55.61V202.83H25.81V222H-51.58C-64.46,222 -74.79,211.68 -74.79,198.79C-74.79,186.06 -64.46,175.74 -51.58,175.74ZM52.45,140.58V222H33.27V144.69C33.27,131.81 43.68,121.4 56.56,121.4H83.49C96.3,121.4 106.78,131.81 106.78,144.69V171.63C106.78,184.43 96.3,194.91 83.49,194.91H79.54V202.83H106.78V222H83.57C77.44,222 71.54,219.59 67.19,215.17C62.93,210.9 60.36,204.92 60.36,198.79C60.36,186.06 70.84,175.74 83.57,175.74H87.61V140.58H52.45ZM187.74,198.79C187.74,204.92 185.17,210.9 180.9,215.17C176.56,219.59 170.66,222 164.53,222H114.23V202.83H168.56V194.91H164.6C158.47,194.91 152.57,192.35 148.23,188C143.88,183.66 141.32,177.76 141.32,171.63V167.67H137.44C131.31,167.67 125.33,165.26 121.06,160.91C116.64,156.64 114.23,150.67 114.23,144.61C114.23,138.48 116.64,132.51 121.06,128.24C125.33,123.81 131.31,121.4 137.44,121.4H187.74V140.58H133.4V148.49H137.21C143.41,148.49 149.31,151.06 153.74,155.4C158.09,159.75 160.49,165.65 160.49,171.78V175.74H164.53C170.66,175.74 176.56,178.15 180.9,182.49C185.17,186.76 187.74,192.74 187.74,198.79ZM218.39,175.74H249.52V167.67H218.39C205.51,167.67 195.18,157.34 195.18,144.61C195.18,131.73 205.51,121.4 218.39,121.4H295.78V140.58H214.36V148.49H245.48C258.21,148.49 268.69,158.97 268.69,171.7C268.69,184.43 258.21,194.91 245.48,194.91H214.36V202.83H295.78V222H218.39C205.51,222 195.18,211.68 195.18,198.79C195.18,186.06 205.51,175.74 218.39,175.74Z"
|
||||
android:strokeAlpha="0.3"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillAlpha="0.3"/>
|
||||
<path
|
||||
android:pathData="M109.12,53C141.65,53 168,78.74 168,110.5C168,142.26 141.64,168 109.12,168C90.77,168 73.8,159.74 62.72,145.88C62.41,145.5 62.18,145.05 62.04,144.58C61.91,144.1 61.87,143.6 61.93,143.11C61.99,142.61 62.15,142.14 62.4,141.71C62.65,141.28 62.98,140.9 63.38,140.61C64.19,139.99 65.2,139.71 66.21,139.84C67.22,139.96 68.14,140.47 68.78,141.26C73.63,147.28 79.78,152.14 86.76,155.47C93.75,158.8 101.39,160.51 109.13,160.48C137.39,160.48 160.3,138.1 160.3,110.5C160.3,82.9 137.39,60.52 109.13,60.52C101.5,60.49 93.96,62.15 87.06,65.39C80.15,68.63 74.05,73.36 69.2,79.24C68.55,80.02 67.62,80.52 66.61,80.63C65.6,80.73 64.58,80.44 63.78,79.82C63.39,79.51 63.06,79.14 62.82,78.7C62.57,78.27 62.42,77.79 62.36,77.3C62.31,76.81 62.35,76.31 62.49,75.83C62.64,75.35 62.87,74.91 63.19,74.53C74.29,61.02 91.03,53 109.12,53ZM112.26,92.46L127.82,108.02C129.33,109.53 129.36,111.93 127.89,113.4L112.68,128.61C112.33,128.96 111.9,129.24 111.44,129.43C110.98,129.62 110.48,129.71 109.98,129.7C109.48,129.7 108.98,129.59 108.52,129.39C108.06,129.2 107.65,128.91 107.3,128.55C106.94,128.2 106.65,127.78 106.45,127.32C106.26,126.86 106.15,126.37 106.15,125.87C106.14,125.37 106.24,124.87 106.42,124.4C106.61,123.94 106.89,123.52 107.24,123.16L116.14,114.26L56.85,114.27C56.35,114.27 55.85,114.18 55.39,114C54.92,113.81 54.5,113.54 54.14,113.18C53.78,112.83 53.5,112.42 53.3,111.96C53.11,111.5 53,111 53,110.5C53,108.42 54.72,106.74 56.85,106.74H115.89L106.93,97.78C106.58,97.43 106.29,97.01 106.09,96.55C105.89,96.09 105.79,95.59 105.78,95.09C105.78,94.59 105.87,94.1 106.06,93.63C106.25,93.17 106.53,92.74 106.88,92.39C107.23,92.04 107.66,91.76 108.12,91.58C108.58,91.39 109.08,91.3 109.58,91.3C110.08,91.31 110.57,91.41 111.03,91.61C111.49,91.81 111.9,92.09 112.25,92.45"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M112.26,92.46L127.82,108.02C129.33,109.53 129.36,111.93 127.89,113.4L112.68,128.61C112.33,128.96 111.9,129.24 111.44,129.43C110.98,129.62 110.48,129.71 109.98,129.7C109.48,129.7 108.98,129.59 108.52,129.39C108.06,129.2 107.65,128.91 107.3,128.55C106.94,128.2 106.65,127.78 106.45,127.32C106.26,126.86 106.15,126.37 106.15,125.87C106.14,125.37 106.24,124.87 106.42,124.4C106.61,123.94 106.89,123.52 107.24,123.16L116.14,114.26L56.85,114.27C56.35,114.27 55.85,114.18 55.39,114C54.92,113.81 54.5,113.54 54.14,113.18C53.78,112.83 53.5,112.42 53.3,111.96C53.11,111.5 53,111 53,110.5C53,108.42 54.72,106.74 56.85,106.74H115.89L106.93,97.78C106.58,97.43 106.29,97.01 106.09,96.55C105.89,96.09 105.79,95.59 105.78,95.09C105.78,94.59 105.87,94.1 106.06,93.63C106.25,93.17 106.53,92.74 106.88,92.39C107.23,92.04 107.66,91.76 108.12,91.58C108.58,91.39 109.08,91.3 109.58,91.3C110.08,91.31 110.57,91.41 111.03,91.61C111.49,91.81 111.9,92.09 112.25,92.45M109.12,53C141.65,53 168,78.74 168,110.5C168,142.26 141.64,168 109.12,168C90.77,168 73.8,159.74 62.72,145.88C62.41,145.5 62.18,145.05 62.04,144.58C61.91,144.1 61.87,143.6 61.93,143.11C61.99,142.61 62.15,142.14 62.4,141.71C62.65,141.28 62.98,140.9 63.38,140.61C64.19,139.99 65.2,139.71 66.21,139.84C67.22,139.96 68.14,140.47 68.78,141.26C73.63,147.28 79.78,152.14 86.76,155.47C93.75,158.8 101.39,160.51 109.13,160.48C137.39,160.48 160.3,138.1 160.3,110.5C160.3,82.9 137.39,60.52 109.13,60.52C101.5,60.49 93.96,62.15 87.06,65.39C80.15,68.63 74.05,73.36 69.2,79.24C68.55,80.02 67.62,80.52 66.61,80.63C65.6,80.73 64.58,80.44 63.78,79.82C63.39,79.51 63.06,79.14 62.82,78.7C62.57,78.27 62.42,77.79 62.36,77.3C62.31,76.81 62.35,76.31 62.49,75.83C62.64,75.35 62.87,74.91 63.19,74.53C74.29,61.02 91.03,53 109.12,53Z"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"/>
|
||||
</group>
|
||||
</vector>
|
||||
|
BIN
app/src/main/res/drawable/no_wifi_pic.png
Normal file
After Width: | Height: | Size: 55 KiB |
11
app/src/main/res/drawable/splash_1.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="375dp"
|
||||
android:height="655dp"
|
||||
android:viewportWidth="375"
|
||||
android:viewportHeight="655">
|
||||
<path
|
||||
android:pathData="M102.93,0.51V37.2H50.8L50.8,156.31H102.93V193H-37.72V156.31H14.11L14.11,37.2H-37.72V0.51L102.93,0.51ZM169.02,148.59L169.02,37.2H153.87L153.87,193H117.18L117.18,44.92C117.18,20.27 136.94,0.51 161.59,0.51C173.32,0.51 184.61,5.12 192.78,13.58C201.1,21.75 205.7,33.19 205.7,44.92L205.7,156.31H221.15V0.51H257.84V148.59C257.84,173.25 237.79,193 213.43,193C201.69,193 190.41,188.4 182.09,179.93C173.92,171.76 169.02,160.32 169.02,148.59ZM316.65,141.16C291.99,141.16 272.09,121.11 272.09,96.61V0.51L308.77,0.51V104.48H316.05C327.93,104.48 339.22,109.08 347.69,117.4C356.01,125.87 360.61,137.15 360.61,149.04V156.31H376.06V0.51H412.74V148.59C412.74,173.25 392.69,193 368.33,193C343.98,193 323.92,173.25 323.92,148.59V141.16H316.65ZM58.2,335.48H117.76V320.03H58.2C33.55,320.03 13.8,300.28 13.8,275.92C13.8,251.26 33.55,231.51 58.2,231.51H206.28V268.2H50.48V283.35H110.04C134.4,283.35 154.45,303.4 154.45,327.76C154.45,352.11 134.4,372.17 110.04,372.17H50.48V387.31H206.28V424H58.2C33.55,424 13.8,404.25 13.8,379.59C13.8,355.23 33.55,335.48 58.2,335.48ZM257.26,268.2V424H220.57V276.07C220.57,251.41 240.47,231.51 265.13,231.51H316.67C341.17,231.51 361.22,251.41 361.22,276.07V327.61C361.22,352.11 341.17,372.17 316.67,372.17H309.09V387.31H361.22V424H316.81C305.08,424 293.79,419.4 285.48,410.93C277.31,402.76 272.4,391.33 272.4,379.59C272.4,355.23 292.46,335.48 316.81,335.48H324.54V268.2H257.26ZM154.45,610.59C154.45,622.33 149.55,633.76 141.38,641.93C133.06,650.4 121.77,655 110.04,655H13.8L13.8,618.31H117.76V603.16H110.19C98.45,603.16 87.17,598.26 78.85,589.95C70.53,581.63 65.63,570.34 65.63,558.61V551.03H58.2C46.47,551.03 35.03,546.43 26.87,538.11C18.4,529.94 13.8,518.51 13.8,506.92C13.8,495.19 18.4,483.75 26.87,475.58C35.03,467.12 46.47,462.51 58.2,462.51H154.45L154.45,499.2H50.48V514.35H57.76C69.64,514.35 80.93,519.25 89.39,527.57C97.71,535.88 102.32,547.17 102.32,558.9V566.48H110.04C121.77,566.48 133.06,571.08 141.38,579.4C149.55,587.57 154.45,599.01 154.45,610.59ZM213.11,566.48H272.67V551.03H213.11C188.46,551.03 168.7,531.28 168.7,506.92C168.7,482.27 188.46,462.51 213.11,462.51H361.19V499.2H205.39V514.35H264.95C289.3,514.35 309.36,534.4 309.36,558.76C309.36,583.11 289.3,603.16 264.95,603.16H205.39V618.31H361.19V655H213.11C188.46,655 168.7,635.25 168.7,610.59C168.7,586.23 188.46,566.48 213.11,566.48Z"
|
||||
android:strokeAlpha="0.3"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillAlpha="0.3"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/splash_2.xml
Normal file
14
app/src/main/res/drawable/splash_3.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="117dp"
|
||||
android:height="117dp"
|
||||
android:viewportWidth="117"
|
||||
android:viewportHeight="117">
|
||||
<path
|
||||
android:pathData="M57.12,1C89.65,1 116,26.74 116,58.5C116,90.26 89.64,116 57.12,116C38.77,116 21.8,107.74 10.72,93.89C10.41,93.5 10.18,93.05 10.04,92.58C9.91,92.1 9.87,91.6 9.93,91.11C9.99,90.61 10.15,90.14 10.4,89.71C10.65,89.28 10.98,88.91 11.38,88.61C12.19,87.99 13.2,87.72 14.21,87.84C15.22,87.96 16.14,88.47 16.78,89.26C21.63,95.28 27.78,100.14 34.76,103.47C41.75,106.8 49.39,108.51 57.13,108.48C85.39,108.48 108.3,86.1 108.3,58.5C108.3,30.9 85.39,8.52 57.13,8.52C49.5,8.49 41.96,10.15 35.06,13.39C28.15,16.63 22.05,21.36 17.2,27.24C16.55,28.02 15.62,28.52 14.61,28.63C13.6,28.73 12.58,28.44 11.78,27.82C11.39,27.51 11.06,27.14 10.82,26.7C10.57,26.27 10.42,25.79 10.36,25.3C10.31,24.81 10.35,24.31 10.49,23.83C10.64,23.35 10.87,22.91 11.19,22.53C22.29,9.02 39.03,1 57.12,1ZM60.26,40.46L75.82,56.02C77.33,57.53 77.36,59.93 75.89,61.4L60.69,76.61C60.33,76.96 59.91,77.24 59.44,77.43C58.98,77.62 58.48,77.71 57.98,77.7C57.48,77.7 56.98,77.59 56.52,77.39C56.06,77.2 55.65,76.91 55.3,76.55C54.94,76.2 54.65,75.78 54.45,75.32C54.26,74.86 54.15,74.37 54.15,73.87C54.14,73.37 54.23,72.87 54.42,72.41C54.61,71.94 54.89,71.52 55.24,71.16L64.14,62.26L4.85,62.27C4.35,62.27 3.85,62.18 3.39,62C2.92,61.81 2.5,61.54 2.14,61.19C1.78,60.84 1.5,60.42 1.3,59.96C1.11,59.5 1,59 1,58.5C1,56.42 2.72,54.74 4.85,54.74H63.89L54.94,45.78C54.58,45.43 54.29,45.01 54.09,44.55C53.89,44.09 53.79,43.59 53.78,43.09C53.78,42.59 53.87,42.1 54.06,41.63C54.25,41.17 54.53,40.74 54.88,40.39C55.23,40.04 55.66,39.76 56.12,39.58C56.58,39.39 57.08,39.3 57.58,39.3C58.08,39.31 58.57,39.41 59.03,39.61C59.49,39.81 59.91,40.09 60.25,40.45"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M60.26,40.46L75.82,56.02C77.33,57.53 77.36,59.93 75.89,61.4L60.69,76.61C60.33,76.96 59.91,77.24 59.44,77.43C58.98,77.62 58.48,77.71 57.98,77.7C57.48,77.7 56.98,77.59 56.52,77.39C56.06,77.2 55.65,76.91 55.3,76.55C54.94,76.2 54.65,75.78 54.45,75.32C54.26,74.86 54.15,74.37 54.15,73.87C54.14,73.37 54.23,72.87 54.42,72.41C54.61,71.94 54.89,71.52 55.24,71.16L64.14,62.26L4.85,62.27C4.35,62.27 3.85,62.18 3.39,62C2.92,61.81 2.5,61.54 2.14,61.19C1.78,60.84 1.5,60.42 1.3,59.96C1.11,59.5 1,59 1,58.5C1,56.42 2.72,54.74 4.85,54.74H63.89L54.94,45.78C54.58,45.43 54.29,45.01 54.09,44.55C53.89,44.09 53.79,43.59 53.78,43.09C53.78,42.59 53.87,42.1 54.06,41.63C54.25,41.17 54.53,40.74 54.88,40.39C55.23,40.04 55.66,39.76 56.12,39.58C56.58,39.39 57.08,39.3 57.58,39.3C58.08,39.31 58.57,39.41 59.03,39.61C59.49,39.81 59.91,40.09 60.25,40.45M57.12,1C89.65,1 116,26.74 116,58.5C116,90.26 89.64,116 57.12,116C38.77,116 21.8,107.74 10.72,93.89C10.41,93.5 10.18,93.05 10.04,92.58C9.91,92.1 9.87,91.6 9.93,91.11C9.99,90.61 10.15,90.14 10.4,89.71C10.65,89.28 10.98,88.91 11.38,88.61C12.19,87.99 13.2,87.72 14.21,87.84C15.22,87.96 16.14,88.47 16.78,89.26C21.63,95.28 27.78,100.14 34.76,103.47C41.75,106.8 49.39,108.51 57.13,108.48C85.39,108.48 108.3,86.1 108.3,58.5C108.3,30.9 85.39,8.52 57.13,8.52C49.5,8.49 41.96,10.15 35.06,13.39C28.15,16.63 22.05,21.36 17.2,27.24C16.55,28.02 15.62,28.52 14.61,28.63C13.6,28.73 12.58,28.44 11.78,27.82C11.39,27.51 11.06,27.14 10.82,26.7C10.57,26.27 10.42,25.79 10.36,25.3C10.31,24.81 10.35,24.31 10.49,23.83C10.64,23.35 10.87,22.91 11.19,22.53C22.29,9.02 39.03,1 57.12,1Z"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"/>
|
||||
</vector>
|
@ -14,6 +14,7 @@
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
@ -132,7 +133,21 @@
|
||||
android:background="@drawable/warn_button_outline"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:text="@string/block_card_button_text"
|
||||
android:textColor="@color/warn_button_color" />
|
||||
android:textColor="@color/warn_button_color"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/unblock"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/main_button_outline"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:text="@string/unblock_card_button_text"
|
||||
android:textColor="@color/main_button_color"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/logout"
|
||||
|
@ -5,19 +5,19 @@
|
||||
android:background="@color/white">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_gravity="start"
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:fontFamily="@font/manrope_bold"
|
||||
android:text="@string/find_employee_header"
|
||||
android:text="@string/find_employee_button_text"
|
||||
android:textSize="24sp" />
|
||||
|
||||
<EditText
|
||||
@ -41,6 +41,16 @@
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:text="@string/find_button_text"
|
||||
android:textColor="@color/white" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/error"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/warn_button_color"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
|
||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||
android:id="@+id/drag_handle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintTop_toBottomOf="@id/drag_handle">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/no_wifi_pic" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/offline_error"
|
||||
android:textFontWeight="700"
|
||||
android:textSize="24sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/no_internet_instructions"
|
||||
android:textFontWeight="400"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<Button
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:textColor="@color/white"
|
||||
android:layout_marginTop="16dp"
|
||||
android:id="@+id/close"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/to_main_menu"
|
||||
android:background="@drawable/main_button" />
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -15,7 +15,7 @@
|
||||
android:id="@+id/result"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="24dp"
|
||||
android:textSize="24sp"
|
||||
tools:text="Успешно!"/>
|
||||
|
||||
<Button
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent" >
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,9 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textColor="@color/warn_button_color"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Some error" />
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/refresh"
|
||||
android:layout_width="match_parent"
|
||||
@ -95,15 +107,6 @@
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:text="@string/scan_qr_text"
|
||||
android:textColor="@color/white" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textColor="@color/warn_button_color"
|
||||
android:visibility="gone"
|
||||
tools:text="Some error" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
@ -116,17 +119,31 @@
|
||||
android:id="@+id/block"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/warn_button_outline"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:text="@string/block_card_button_text"
|
||||
android:textColor="@color/warn_button_color" />
|
||||
android:textColor="@color/warn_button_color"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/unblock"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/main_button_outline"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:text="@string/unblock_card_button_text"
|
||||
android:textColor="@color/main_button_color"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/logout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="@drawable/warn_button"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/exit" />
|
||||
@ -143,6 +160,7 @@
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/passes"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
|
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 12 KiB |
@ -22,6 +22,9 @@
|
||||
<action
|
||||
android:id="@+id/action_userFragment_to_qrScanFragment"
|
||||
app:destination="@id/qrScanFragment" />
|
||||
<action
|
||||
android:id="@+id/action_userFragment_to_adminFragment"
|
||||
app:destination="@id/adminFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/qrScanFragment"
|
||||
@ -62,5 +65,24 @@
|
||||
<action
|
||||
android:id="@+id/action_splashFragment_to_loginFragment"
|
||||
app:destination="@id/loginFragment" />
|
||||
<action
|
||||
android:id="@+id/action_splashFragment_to_userFragment"
|
||||
app:destination="@id/userFragment"
|
||||
app:popUpTo="@id/nav_graph"
|
||||
app:popUpToInclusive="true" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/adminFragment"
|
||||
android:name="ru.myitschool.work.ui.admin.search.AdminFragment"
|
||||
android:label="AdminFragment"
|
||||
tools:layout="@layout/fragment_find_employee">
|
||||
<action
|
||||
android:id="@+id/action_adminFragment_to_viewUserAsAdminFragment"
|
||||
app:destination="@id/viewUserAsAdminFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/viewUserAsAdminFragment"
|
||||
android:name="ru.myitschool.work.ui.admin.view.ViewUserAsAdminFragment"
|
||||
android:label="ViewUserAsAdminFragment"
|
||||
tools:layout="@layout/fragment_user" />
|
||||
</navigation>
|
@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">NTO Pass</string>
|
||||
<string name="app_name">QR Pass</string>
|
||||
<string name="scan_qr_text">Scan QR</string>
|
||||
<string name="login_hint">Enter login</string>
|
||||
<string name="welcome_text">Welcome to NTO Pass app</string>
|
||||
<string name="welcome_text">Welcome to\nQR Pass app</string>
|
||||
<string name="login">Login</string>
|
||||
<string name="refresh_data">Refresh</string>
|
||||
<string name="to_main_menu">Close</string>
|
||||
@ -13,4 +13,13 @@
|
||||
<string name="close_button">Close</string>
|
||||
<string name="logout_text">Logout</string>
|
||||
<string name="last_visit">"Last visit:"</string>
|
||||
<string name="password_input_hint">Password</string>
|
||||
<string name="block_card_button_text">Block card</string>
|
||||
<string name="card_using_header">Pass using</string>
|
||||
<string name="find_employee_button_text">Find employee</string>
|
||||
<string name="find_button_text">Find</string>
|
||||
<string name="offline_error">Seems you are offline...</string>
|
||||
<string name="no_internet_instructions">Check your internet connection\\nand try again</string>
|
||||
<string name="reload_text">" Reload"</string>
|
||||
<string name="unblock_card_button_text">Unblock card</string>
|
||||
</resources>
|
@ -1,8 +1,8 @@
|
||||
<resources>
|
||||
<string name="app_name">NTO Pass</string>
|
||||
<string name="app_name">QR Pass</string>
|
||||
<string name="scan_qr_text">Сканировать QR-код</string>
|
||||
<string name="login_hint">Введите логин</string>
|
||||
<string name="welcome_text">Добро пожаловать в NTO Pass</string>
|
||||
<string name="welcome_text">Добро пожаловать в\nQR Pass</string>
|
||||
<string name="login">Войти</string>
|
||||
<string name="refresh_data">Обновить данные</string>
|
||||
<string name="to_main_menu">Закрыть</string>
|
||||
@ -12,10 +12,12 @@
|
||||
<string name="logout_text">Выйти из аккаунта</string>
|
||||
<string name="last_visit">Последний вход: </string>
|
||||
<string name="password_input_hint">Введите пароль</string>
|
||||
<string name="register_process_button_text">Зарегестрироваться</string>
|
||||
<string name="block_card_button_text">Заблокировать карту</string>
|
||||
<string name="card_using_header">Использование пропуска</string>
|
||||
<string name="find_employee_button_text">Поиск сотрудника</string>
|
||||
<string name="find_button_text">Найти</string>
|
||||
<string name="find_employee_header">Поиск сотрудника</string>
|
||||
<string name="offline_error">Кажется вы оффлайн...</string>
|
||||
<string name="no_internet_instructions">Проверьте подключение к сети\nи попробуйте еще раз</string>
|
||||
<string name="reload_text">Перезагрузить</string>
|
||||
<string name="unblock_card_button_text">Разблокировать карту</string>
|
||||
</resources>
|
49
app/src/test/kotlin/AdminRepositoryImplTest.kt
Normal file
@ -0,0 +1,49 @@
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import ru.myitschool.work.core.Constants
|
||||
import ru.myitschool.work.data.AdminRepositoryImpl
|
||||
import ru.myitschool.work.data.local.CredentialsLocalDataSource
|
||||
import ru.myitschool.work.data.network.AdminNetworkDataSource
|
||||
import ru.myitschool.work.domain.admin.AdminRepository
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class AdminRepositoryImplTest {
|
||||
|
||||
private lateinit var context: Context
|
||||
private lateinit var repository: AdminRepository
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
CredentialsLocalDataSource.buildSource(
|
||||
context.getSharedPreferences(
|
||||
Constants.TOKEN_KEY,
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
)
|
||||
repository = AdminRepositoryImpl(
|
||||
networkDataSource = AdminNetworkDataSource,
|
||||
localCredentialsLocalDataSource = CredentialsLocalDataSource.getInstance()
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When user have admin permissions and blocked user exist process` () = runTest {
|
||||
CredentialsLocalDataSource.getInstance().updateToken("pivanov", "admin")
|
||||
assertNull(repository.blockUser("").exceptionOrNull())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When user don not have admin permissions error` () = runTest {
|
||||
CredentialsLocalDataSource.getInstance().updateToken("", "")
|
||||
assertNotNull(repository.blockUser("").exceptionOrNull())
|
||||
}
|
||||
}
|
68
app/src/test/kotlin/PassesRepositoryImplTest.kt
Normal file
@ -0,0 +1,68 @@
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import ru.myitschool.work.core.Constants
|
||||
import ru.myitschool.work.data.PassRepositoryImpl
|
||||
import ru.myitschool.work.data.local.CredentialsLocalDataSource
|
||||
import ru.myitschool.work.data.network.PassNetworkDataSource
|
||||
import ru.myitschool.work.domain.entities.PassEntity
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class PassesRepositoryImplTest {
|
||||
|
||||
private lateinit var repository: PassRepositoryImpl
|
||||
private lateinit var context: Context
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
CredentialsLocalDataSource.buildSource(
|
||||
context.getSharedPreferences(
|
||||
Constants.TOKEN_KEY,
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
)
|
||||
repository = PassRepositoryImpl(
|
||||
networkDataSource = PassNetworkDataSource,
|
||||
credentialsLocalDataSource = CredentialsLocalDataSource.getInstance()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When user authorized as user gets only its passes`() = runTest {
|
||||
CredentialsLocalDataSource.getInstance().updateToken("", "")
|
||||
assertEquals(
|
||||
listOf(
|
||||
PassEntity("", "", ""),
|
||||
PassEntity("", "", ""),
|
||||
PassEntity("", "", "")
|
||||
),
|
||||
repository.getCurrentPasses(1, 3).getOrNull()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When user authorized as admin gets any user's passes`() = runTest {
|
||||
CredentialsLocalDataSource.getInstance().updateToken("pivanov", "admin")
|
||||
assertEquals(
|
||||
listOf(
|
||||
PassEntity("", "", ""),
|
||||
PassEntity("", "", ""),
|
||||
PassEntity("", "", "")
|
||||
),
|
||||
repository.getUsersPasses(1, 3, "").getOrNull()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When user tries to access admin method error` () = runTest {
|
||||
CredentialsLocalDataSource.getInstance().updateToken("", "")
|
||||
assertNull(repository.getUsersPasses(1, 3, ""))
|
||||
}
|
||||
}
|
55
app/src/test/kotlin/QrRepositoryImplTest.kt
Normal file
@ -0,0 +1,55 @@
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import ru.myitschool.work.core.Constants
|
||||
import ru.myitschool.work.data.network.QrNetworkDataSource
|
||||
import ru.myitschool.work.data.QrRepositoryImpl
|
||||
import ru.myitschool.work.data.local.CredentialsLocalDataSource
|
||||
import ru.myitschool.work.domain.entities.QrEntity
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class QrRepositoryImplTest {
|
||||
|
||||
private lateinit var context: Context
|
||||
private lateinit var repository: QrRepositoryImpl
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
CredentialsLocalDataSource.buildSource(
|
||||
context.getSharedPreferences(
|
||||
Constants.SERVER_ADDRESS,
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
)
|
||||
repository = QrRepositoryImpl(
|
||||
networkDataSource = QrNetworkDataSource,
|
||||
credentialsLocalDataSource = CredentialsLocalDataSource.getInstance()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When QR valid and user logged process`() = runTest {
|
||||
CredentialsLocalDataSource.getInstance().updateToken("", "")
|
||||
assertNull(repository.pushQr(QrEntity("")).exceptionOrNull())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When QR invalid and user logged error`() = runTest {
|
||||
CredentialsLocalDataSource.getInstance().updateToken("", "")
|
||||
assertNotNull(repository.pushQr(QrEntity("*")).exceptionOrNull())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When QR valid and user not logged error`() = runTest {
|
||||
runCatching {
|
||||
repository.pushQr(QrEntity("*")).exceptionOrNull()
|
||||
}.let { assert(it.isFailure) }
|
||||
}
|
||||
}
|
130
app/src/test/kotlin/UserRepositoryImplTest.kt
Normal file
@ -0,0 +1,130 @@
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import ru.myitschool.work.core.Constants
|
||||
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.UserNetworkDataSource
|
||||
import ru.myitschool.work.domain.entities.UserEntity
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class UserRepositoryImplTest {
|
||||
|
||||
private lateinit var context: Context
|
||||
private lateinit var repository: UserRepositoryImpl
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
CredentialsLocalDataSource.buildSource(
|
||||
context.getSharedPreferences(
|
||||
Constants.TOKEN_KEY,
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
)
|
||||
repository = UserRepositoryImpl(
|
||||
credentialsLocalDataSource = CredentialsLocalDataSource.getInstance(),
|
||||
userLocalDataSource = UserLocalDataSource,
|
||||
networkDataSource = UserNetworkDataSource
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When user exist return true`() = runTest {
|
||||
assertEquals(true, repository.isUserExist("pivanov").getOrNull())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When user does not exist return false`() = runTest {
|
||||
assertEquals(false, repository.isUserExist("pivanov").getOrNull())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When user existing and password matches success login`() = runTest {
|
||||
assertEquals(
|
||||
UserEntity(
|
||||
isAdmin = true,
|
||||
name = "",
|
||||
lastVisit = "",
|
||||
photoUrl = "",
|
||||
position = "",
|
||||
isCardBlocked = false
|
||||
), repository.login("pivanov", "admin").getOrNull()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When user enter incorrect login`() = runTest {
|
||||
assertNull(repository.login("***", "admin").getOrNull())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When user enter incorrect password`() = runTest {
|
||||
assertNull(repository.login("pivanov", "***").getOrNull())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When user enter incorrect login and password`() = runTest {
|
||||
assertNull(repository.login("***", "***").getOrNull())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When user logouts local credentials wipes`() = runTest {
|
||||
repository.logout()
|
||||
runCatching {
|
||||
CredentialsLocalDataSource.getInstance().getToken()
|
||||
}.let { assert(it.isFailure) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Get user by login returns correct user`() = runTest {
|
||||
CredentialsLocalDataSource.getInstance().updateToken("pivanov", "admin")
|
||||
assertEquals(
|
||||
UserEntity(
|
||||
isAdmin = false,
|
||||
name = "",
|
||||
lastVisit = "",
|
||||
photoUrl = "",
|
||||
position = "",
|
||||
isCardBlocked = false
|
||||
),
|
||||
repository.getUserByLogin("").getOrNull()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Get user by login works with admin roots`() = runTest {
|
||||
CredentialsLocalDataSource.getInstance().updateToken("pivanov", "admin")
|
||||
assertNotNull(repository.getUserByLogin("").getOrNull())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Get user by login don not works without admin roots`() = runTest {
|
||||
CredentialsLocalDataSource.getInstance().updateToken("", "")
|
||||
assertNull(repository.getUserByLogin("").getOrNull())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When user successfully logged for the first time it will authorize automatically`() =
|
||||
runTest {
|
||||
val loggedUser = repository.login("pivanov", "admin").getOrNull()
|
||||
assertEquals(
|
||||
loggedUser,
|
||||
repository.authorize(CredentialsLocalDataSource.getInstance().getToken())
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When user successfully logged its info saved locally for current session`() = runTest {
|
||||
val loggedUser = repository.login("pivanov", "admin").getOrNull()
|
||||
assertEquals(loggedUser, repository.getCurrentUser())
|
||||
}
|
||||
}
|